From 74c657d5afaccfa44f66eae5fed67d6109d6ff05 Mon Sep 17 00:00:00 2001 From: Leilei Zhang Date: Thu, 13 Aug 2026 17:57:54 +0800 Subject: [PATCH] Unify Python package and release artifacts Publish the Python runtime as dynwinrt, update generated imports, and attach tested wheels to ordinary v-tag GitHub releases using tag-derived versions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ffc13440-4470-4483-b896-abf263588d05 --- .github/ISSUE_TEMPLATE/bug_report.yml | 3 +- .github/ISSUE_TEMPLATE/feature_request.yml | 3 +- .github/workflows/build.yml | 2 +- .github/workflows/python-release.yml | 96 +++++++++++++------ .pipelines/ci.yml | 2 +- bindings/py/Cargo.toml | 2 +- bindings/py/README.md | 33 ++++--- bindings/py/{dynwinrt_py.pyi => dynwinrt.pyi} | 0 bindings/py/pyproject.toml | 2 +- bindings/py/src/async_runtime.rs | 6 +- bindings/py/src/lib.rs | 2 +- bindings/py/stubtest_allowlist.txt | 2 +- bindings/py/tests/test_basic.py | 4 +- bindings/py/tests/test_e2e_winrt.py | 2 +- bindings/py/tests/test_phase1.py | 8 +- bindings/py/tests/test_runtime.py | 4 +- bindings/py/tests/test_typing_package.py | 4 +- bindings/py/uv.lock | 2 +- docs/status/PYTHON_CHECKLIST.md | 11 ++- eng/coverage/coverage.ps1 | 4 +- eng/coverage/python-coveragerc | 2 +- eng/release/python/prepare_python_release.py | 69 +++++++++++++ .../python/test_python_codegen_wheel.ps1 | 4 +- .../python/test_python_runtime_wheel.ps1 | 13 +-- eng/release/python/verify_python_release.py | 15 +-- .../winui-tic-tac-toe-code-only/README.md | 4 +- .../python/winui-tic-tac-toe-code-only/app.py | 2 +- samples/python/winui-tic-tac-toe/README.md | 4 +- samples/python/winui-tic-tac-toe/app.py | 2 +- tests/e2e/runners/py_runner.py | 24 ++--- tests/e2e/runners/py_winui_runner.py | 2 +- tests/e2e/typecheck/python_generated_api.py | 2 +- tools/dynwinrt-codegen/README.md | 2 +- tools/dynwinrt-codegen/src/codegen/package.rs | 4 +- .../codegen/winrt/python/generator/class.rs | 6 +- .../src/codegen/winrt/python/generator/mod.rs | 8 +- .../codegen/winrt/python/generator/types.rs | 6 +- .../src/codegen/winrt/python/stubs.rs | 8 +- .../tests/element_factory_test.rs | 2 +- ...i_iterator_i_www_form_url_decoder_entry.py | 6 +- .../tests/snapshots/uri_py/i_stringable.py | 4 +- ...ntime_class_with_absolute_canonical_uri.py | 4 +- .../uri_py/i_www_form_url_decoder_entry.py | 4 +- .../tests/snapshots/uri_py/uri.py | 4 +- .../snapshots/uri_py/www_form_url_decoder.py | 6 +- ..._iterator_i_www_form_url_decoder_entry.pyi | 2 +- .../tests/snapshots/uri_pyi/i_stringable.pyi | 2 +- ...time_class_with_absolute_canonical_uri.pyi | 2 +- .../uri_pyi/i_www_form_url_decoder_entry.pyi | 2 +- .../tests/snapshots/uri_pyi/uri.pyi | 2 +- .../uri_pyi/www_form_url_decoder.pyi | 2 +- 51 files changed, 260 insertions(+), 151 deletions(-) rename bindings/py/{dynwinrt_py.pyi => dynwinrt.pyi} (100%) create mode 100644 eng/release/python/prepare_python_release.py diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 0edd8528..9060a06e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -41,7 +41,7 @@ body: - "dynwinrt (core library)" - "dynwinrt-codegen (code generator)" - "JS bindings (@microsoft/dynwinrt)" - - "Python bindings (dynwinrt-py)" + - "Python bindings (dynwinrt)" - "Other" id: component - type: dropdown @@ -64,4 +64,3 @@ body: - Node.js: 22.x (if using JS bindings) - Python: 3.12 (if using Python bindings) id: environment - diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 073784f8..9df84bf6 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -31,7 +31,7 @@ body: - "dynwinrt (core library)" - "dynwinrt-codegen (code generator)" - "JS bindings (@microsoft/dynwinrt)" - - "Python bindings (dynwinrt-py)" + - "Python bindings (dynwinrt)" - "New component" - type: textarea attributes: @@ -43,4 +43,3 @@ body: label: Open Questions description: | Please list any open questions or issues that you think still need to be addressed before this suggestion can be implemented. - diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b6d083a3..1a32bf05 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -121,7 +121,7 @@ jobs: - name: Test Python binding run: | .\bindings\py\.venv\Scripts\python.exe -m pytest bindings\py\tests -q - .\bindings\py\.venv\Scripts\python.exe -m mypy.stubtest dynwinrt_py --allowlist bindings\py\stubtest_allowlist.txt --ignore-disjoint-bases + .\bindings\py\.venv\Scripts\python.exe -m mypy.stubtest dynwinrt --allowlist bindings\py\stubtest_allowlist.txt --ignore-disjoint-bases - name: Run E2E tests run: .\tests\e2e\e2e_test.ps1 -SkipBuild - name: Test JS TSFN lifecycle and queue handling diff --git a/.github/workflows/python-release.yml b/.github/workflows/python-release.yml index fd5a2ab2..01bbfaae 100644 --- a/.github/workflows/python-release.yml +++ b/.github/workflows/python-release.yml @@ -10,17 +10,18 @@ on: - '.github/workflows/python-release.yml' - 'bindings/py/**' - 'crates/dynwinrt/**' + - 'eng/release/python/prepare_python_release.py' - 'eng/release/python/test_python_*_wheel.ps1' - 'eng/release/python/verify_python_release.py' - 'tools/dynwinrt-codegen/**' - 'Cargo.lock' - 'Cargo.toml' push: - tags: ['python-v*'] + tags: ['v*'] workflow_dispatch: inputs: release_version: - description: Cargo/package version expected from the selected ref + description: Version to build; required as confirmation when publishing to PyPI required: false type: string publish_pypi: @@ -41,6 +42,7 @@ jobs: runs-on: windows-latest outputs: version: ${{ steps.version.outputs.version }} + python_version: ${{ steps.version.outputs.python_version }} steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 @@ -57,27 +59,41 @@ jobs: run: | python -m pip install "packaging>=24,<27" --quiet $cargo = Get-Content bindings\py\Cargo.toml -Raw - $version = [regex]::Match($cargo, '(?m)^version = "([^"]+)"$').Groups[1].Value - if (-not $version) { throw 'Could not read bindings/py Cargo version' } + $sourceVersion = [regex]::Match($cargo, '(?m)^version = "([^"]+)"$').Groups[1].Value + if (-not $sourceVersion) { throw 'Could not read bindings/py Cargo version' } - $arguments = @('eng\release\python\verify_python_release.py', 'source') - if ($env:RELEASE_VERSION) { - $arguments += @('--release-version', $env:RELEASE_VERSION) - } + python eng\release\python\verify_python_release.py source + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + + $version = $sourceVersion if ($env:REF_TYPE -eq 'tag') { - $arguments += @('--tag', $env:REF_NAME) + if ($env:REF_NAME -notmatch '^v(.+)$') { + throw "Release tags must match v, got '$env:REF_NAME'" + } + $version = $Matches[1] + } + + if ($env:RELEASE_VERSION) { + if ($env:REF_TYPE -eq 'tag' -and $env:RELEASE_VERSION -ne $version) { + throw "Confirmed release_version '$env:RELEASE_VERSION' does not match tag version '$version'" + } + $version = $env:RELEASE_VERSION } - python @arguments + + $env:EFFECTIVE_VERSION = $version + $pythonVersion = (python -c "import os; from packaging.version import Version; print(Version(os.environ['EFFECTIVE_VERSION']))").Trim() if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + Write-Host "Effective Python version: $pythonVersion" if ($env:PUBLISH_PYPI -eq 'true' -and - ($env:REF_TYPE -ne 'tag' -or $env:REF_NAME -notlike 'python-v*')) { - throw 'PyPI publication requires dispatching an existing python-v tag' + ($env:REF_TYPE -ne 'tag' -or $env:REF_NAME -notlike 'v*')) { + throw 'PyPI publication requires dispatching an existing v tag' } if ($env:PUBLISH_PYPI -eq 'true' -and -not $env:RELEASE_VERSION) { throw 'PyPI publication requires an explicit release_version confirmation' } "version=$version" >> $env:GITHUB_OUTPUT + "python_version=$pythonVersion" >> $env:GITHUB_OUTPUT runtime-x64: needs: validate @@ -104,6 +120,10 @@ jobs: run: | python -c "import platform, sys; assert platform.machine().lower() in ('amd64', 'x86_64'); assert f'{sys.version_info.major}.{sys.version_info.minor}' == '${{ matrix.python }}'" python -m pip install "maturin>=1.11,<2" "packaging>=24,<27" --quiet + python eng\release\python\prepare_python_release.py --version $env:VERSION + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + python eng\release\python\verify_python_release.py source --release-version $env:VERSION + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } Remove-Item artifacts\runtime -Recurse -Force -ErrorAction SilentlyContinue python -m maturin build --release --locked ` --manifest-path bindings\py\Cargo.toml ` @@ -114,7 +134,7 @@ jobs: if ($wheel.Count -ne 1) { throw "Expected one runtime wheel, found $($wheel.Count)" } python eng\release\python\verify_python_release.py wheel ` --wheel $wheel[0].FullName ` - --package dynwinrt-py ` + --package dynwinrt ` --version $env:VERSION ` --python-tag '${{ matrix.tag }}' ` --abi-tag '${{ matrix.tag }}' ` @@ -151,6 +171,10 @@ jobs: run: | python -c "import platform, struct, sys; assert platform.machine().lower() in ('arm64', 'aarch64'); assert struct.calcsize('P') == 8; assert f'{sys.version_info.major}.{sys.version_info.minor}' == '${{ matrix.python }}'" python -m pip install "maturin>=1.11,<2" "packaging>=24,<27" --quiet + python eng\release\python\prepare_python_release.py --version $env:VERSION + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + python eng\release\python\verify_python_release.py source --release-version $env:VERSION + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } Remove-Item artifacts\runtime -Recurse -Force -ErrorAction SilentlyContinue python -m maturin build --release --locked ` --manifest-path bindings\py\Cargo.toml ` @@ -161,7 +185,7 @@ jobs: if ($wheel.Count -ne 1) { throw "Expected one runtime wheel, found $($wheel.Count)" } python eng\release\python\verify_python_release.py wheel ` --wheel $wheel[0].FullName ` - --package dynwinrt-py ` + --package dynwinrt ` --version $env:VERSION ` --python-tag '${{ matrix.tag }}' ` --abi-tag '${{ matrix.tag }}' ` @@ -188,6 +212,10 @@ jobs: VERSION: ${{ needs.validate.outputs.version }} run: | python -m pip install "maturin>=1.11,<2" "packaging>=24,<27" --quiet + python eng\release\python\prepare_python_release.py --version $env:VERSION + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + python eng\release\python\verify_python_release.py source --release-version $env:VERSION + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } Remove-Item artifacts\codegen -Recurse -Force -ErrorAction SilentlyContinue python -m maturin build --release --locked ` --manifest-path tools\dynwinrt-codegen\Cargo.toml ` @@ -226,6 +254,10 @@ jobs: run: | python -c "import platform, struct; assert platform.machine().lower() in ('arm64', 'aarch64'); assert struct.calcsize('P') == 8" python -m pip install "maturin>=1.11,<2" "packaging>=24,<27" --quiet + python eng\release\python\prepare_python_release.py --version $env:VERSION + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + python eng\release\python\verify_python_release.py source --release-version $env:VERSION + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } Remove-Item artifacts\codegen -Recurse -Force -ErrorAction SilentlyContinue python -m maturin build --release --locked ` --manifest-path tools\dynwinrt-codegen\Cargo.toml ` @@ -325,6 +357,7 @@ jobs: -RuntimeWheelDirectory artifacts\runtime ` -CodegenWheelDirectory artifacts\codegen ` -Version '${{ needs.validate.outputs.version }}' ` + -PythonPackageVersion '${{ needs.validate.outputs.python_version }}' ` -Architecture x64 -PythonMinor '${{ matrix.python }}' consume-runtime-arm64: @@ -360,6 +393,7 @@ jobs: -RuntimeWheelDirectory artifacts\runtime ` -CodegenWheelDirectory artifacts\codegen ` -Version '${{ needs.validate.outputs.version }}' ` + -PythonPackageVersion '${{ needs.validate.outputs.python_version }}' ` -Architecture arm64 -PythonMinor '${{ matrix.python }}' assemble-release: @@ -417,22 +451,24 @@ jobs: GH_TOKEN: ${{ github.token }} GH_REPO: ${{ github.repository }} TAG: ${{ github.ref_name }} - VERSION: ${{ needs.validate.outputs.version }} run: | $wheels = (Get-ChildItem dist\*.whl -Recurse).FullName - & gh release view $env:TAG *> $null - if ($LASTEXITCODE -eq 0) { - & gh release upload $env:TAG --clobber @wheels - if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - exit 0 + $releaseFound = $false + for ($attempt = 1; $attempt -le 40; $attempt++) { + & gh release view $env:TAG --repo $env:GH_REPO *> $null + if ($LASTEXITCODE -eq 0) { + $releaseFound = $true + break + } + Write-Host "Waiting for the JavaScript release $env:TAG ($attempt/40)..." + Start-Sleep -Seconds 30 } - $arguments = @( - 'release', 'create', $env:TAG, - '--verify-tag', - '--generate-notes', - '--title', "Python v$env:VERSION" - ) - if ($env:VERSION -match '-') { $arguments += '--prerelease' } + + if (-not $releaseFound) { + throw "JavaScript GitHub release $env:TAG was not found after 20 minutes" + } + + $arguments = @('release', 'upload', $env:TAG, '--repo', $env:GH_REPO, '--clobber') $arguments += $wheels & gh @arguments if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } @@ -442,7 +478,7 @@ jobs: github.event_name == 'workflow_dispatch' && inputs.publish_pypi && github.ref_type == 'tag' && - startsWith(github.ref_name, 'python-v') + startsWith(github.ref_name, 'v') needs: [validate, assemble-release] runs-on: ubuntu-latest environment: pypi @@ -458,7 +494,7 @@ jobs: shell: bash run: | mkdir -p publish/runtime - find dist -type f -name 'dynwinrt_py-*.whl' -exec cp '{}' publish/runtime/ \; + find dist -type f -name 'dynwinrt-*.whl' -exec cp '{}' publish/runtime/ \; test "$(find publish/runtime -type f -name '*.whl' | wc -l)" -eq 8 - uses: pypa/gh-action-pypi-publish@release/v1 with: @@ -471,7 +507,7 @@ jobs: github.event_name == 'workflow_dispatch' && inputs.publish_pypi && github.ref_type == 'tag' && - startsWith(github.ref_name, 'python-v') + startsWith(github.ref_name, 'v') needs: [validate, assemble-release, publish-runtime] runs-on: ubuntu-latest environment: pypi diff --git a/.pipelines/ci.yml b/.pipelines/ci.yml index 54e5271d..c13690e7 100644 --- a/.pipelines/ci.yml +++ b/.pipelines/ci.yml @@ -240,7 +240,7 @@ extends: script: | .\bindings\py\.venv\Scripts\python.exe -m pytest bindings\py\tests -q if ($LASTEXITCODE -ne 0) { Write-Error "Python binding tests failed"; exit 1 } - .\bindings\py\.venv\Scripts\python.exe -m mypy.stubtest dynwinrt_py --allowlist bindings\py\stubtest_allowlist.txt --ignore-disjoint-bases + .\bindings\py\.venv\Scripts\python.exe -m mypy.stubtest dynwinrt --allowlist bindings\py\stubtest_allowlist.txt --ignore-disjoint-bases if ($LASTEXITCODE -ne 0) { Write-Error "Python runtime stub validation failed"; exit 1 } # E2E tests: winmd → generate → type-check → call real WinRT and COM APIs diff --git a/bindings/py/Cargo.toml b/bindings/py/Cargo.toml index 8d19590c..2adb0c25 100644 --- a/bindings/py/Cargo.toml +++ b/bindings/py/Cargo.toml @@ -7,7 +7,7 @@ description = "Python bindings for dynwinrt via PyO3" repository = "https://github.com/microsoft/dynwinrt" authors = ["Microsoft"] [lib] -name = "dynwinrt_py" +name = "dynwinrt" crate-type = ["cdylib"] [dependencies] diff --git a/bindings/py/README.md b/bindings/py/README.md index f6f677a8..44415f31 100644 --- a/bindings/py/README.md +++ b/bindings/py/README.md @@ -1,6 +1,6 @@ -# dynwinrt-py +# dynwinrt -`dynwinrt-py` is the native CPython runtime for generated dynwinrt projections. +`dynwinrt` is the native CPython runtime for generated dynwinrt projections. Release wheels support CPython 3.11 through 3.14 on Windows x64 and ARM64. The floor is intentionally the first CPython line for which the release infrastructure can install and execute a native Windows ARM64 interpreter; the @@ -10,12 +10,12 @@ Install the runtime and the standalone generator from an approved feed or a downloaded release: ```powershell -python -m pip install dynwinrt-py dynwinrt-codegen +python -m pip install dynwinrt dynwinrt-codegen dynwinrt-codegen generate --namespace Windows.Foundation --class-name Uri ` --lang py --output generated_uri ``` -Generated Python package manifests pin `dynwinrt-py` to exactly the version of +Generated Python package manifests pin `dynwinrt` to exactly the version of `dynwinrt-codegen` that emitted them. ## Develop @@ -36,7 +36,7 @@ The wheel includes `__init__.pyi` and `py.typed` for static type checking. installed command with Rust removed from `PATH`. Pull requests run the x64 matrix. ARM64 jobs run only for manual dispatches and -`python-v*` tags on the repository's existing +`v*` release tags on the repository's existing `[self-hosted, Windows, ARM64, winui]` runner. This avoids executing untrusted pull-request code on a self-hosted machine. A release remains blocked rather than producing ARM64 artifacts if that runner or one of its native CPython @@ -44,16 +44,19 @@ versions is unavailable. To release: -1. Set the same version in `bindings\py\Cargo.toml` and - `tools\dynwinrt-codegen\Cargo.toml`, update `Cargo.lock`, and merge it. -2. Create `python-v` (for example, `python-v0.1.0`). The workflow - rejects mismatched or malformed tags and attaches only the complete tested - wheel set to the GitHub release. A hyphenated version is marked prerelease. +1. Create the ordinary repository release tag (for example, + `v0.1.0-preview.20`). The workflow derives the Python package version from + the tag and updates its build workspaces without modifying the repository. + PEP 440 normalizes `preview.N` wheel versions to `rcN`. +2. The JavaScript release pipeline creates the GitHub release. After all ten + Python wheels pass their consumption tests, this workflow waits for that + release and uploads the wheels alongside the npm tarballs. It never creates + a separate Python GitHub release. 3. Configure the protected `pypi` GitHub environment with required reviewers - and PyPI trusted publishers for both projects. To publish, manually dispatch - the workflow **from that existing tag** with `publish_pypi` enabled and - `release_version` set. OIDC trusted publishing is used; no API token is - stored in the repository. + and PyPI trusted publishers for `dynwinrt` and `dynwinrt-codegen`. To + publish, manually dispatch the workflow **from that existing tag** with + `publish_pypi` enabled and `release_version` set. OIDC trusted publishing is + used; no API token is stored in the repository. Do not enable publication from a branch, pull request, or ordinary push. @@ -223,7 +226,7 @@ Use a projected lifetime scope inside the COM apartment so generated wrappers release their native values before `RoUninitialize`: ```python -from dynwinrt_py import RoApartment, projected_lifetime_scope +from dynwinrt import RoApartment, projected_lifetime_scope with RoApartment(0), projected_lifetime_scope(): app = Application.create() diff --git a/bindings/py/dynwinrt_py.pyi b/bindings/py/dynwinrt.pyi similarity index 100% rename from bindings/py/dynwinrt_py.pyi rename to bindings/py/dynwinrt.pyi diff --git a/bindings/py/pyproject.toml b/bindings/py/pyproject.toml index ec519c12..94b3f0b7 100644 --- a/bindings/py/pyproject.toml +++ b/bindings/py/pyproject.toml @@ -3,7 +3,7 @@ requires = ["maturin>=1.11,<2.0"] build-backend = "maturin" [project] -name = "dynwinrt-py" +name = "dynwinrt" description = "Python bindings for dynamic WinRT API invocation" requires-python = ">=3.11,<3.15" license = {text = "MIT"} diff --git a/bindings/py/src/async_runtime.rs b/bindings/py/src/async_runtime.rs index a131e7d6..87793a5e 100644 --- a/bindings/py/src/async_runtime.rs +++ b/bindings/py/src/async_runtime.rs @@ -177,14 +177,14 @@ impl AsyncOperation { let converter = self.converter.clone_ref(py); let convert_future = py - .import("dynwinrt_py.dynwinrt_py")? + .import("dynwinrt.dynwinrt")? .getattr("_dynwinrt_convert_future")?; let coroutine = convert_future.call1((raw_future.clone(), converter))?; let future = py .import("asyncio")? .call_method0("get_running_loop")? .call_method1("create_task", (coroutine,))?; - py.import("dynwinrt_py.dynwinrt_py")? + py.import("dynwinrt.dynwinrt")? .getattr("_dynwinrt_link_cancellation")? .call1((future.clone(), raw_future))?; let future = future.unbind(); @@ -372,7 +372,7 @@ impl DynWinRTAsyncWithProgress { })?; let converter = self.progress_converter.clone_ref(py); let dispatch_progress = py - .import("dynwinrt_py.dynwinrt_py")? + .import("dynwinrt.dynwinrt")? .getattr("_dynwinrt_dispatch_progress")? .unbind(); let callback_context = py diff --git a/bindings/py/src/lib.rs b/bindings/py/src/lib.rs index 1deace6c..c57ff19a 100644 --- a/bindings/py/src/lib.rs +++ b/bindings/py/src/lib.rs @@ -9,7 +9,7 @@ mod runtime; mod values; #[pymodule] -mod dynwinrt_py { +mod dynwinrt { use pyo3::prelude::*; #[pymodule_init] diff --git a/bindings/py/stubtest_allowlist.txt b/bindings/py/stubtest_allowlist.txt index 3e9a0941..b0caff0f 100644 --- a/bindings/py/stubtest_allowlist.txt +++ b/bindings/py/stubtest_allowlist.txt @@ -1 +1 @@ -dynwinrt_py\.dynwinrt_py +dynwinrt\.dynwinrt diff --git a/bindings/py/tests/test_basic.py b/bindings/py/tests/test_basic.py index 716c0e0c..a5117af7 100644 --- a/bindings/py/tests/test_basic.py +++ b/bindings/py/tests/test_basic.py @@ -1,8 +1,8 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. -import dynwinrt_py -from dynwinrt_py import ( +import dynwinrt +from dynwinrt import ( DynWinRTType, DynWinRTMethodSig, DynWinRTValue, diff --git a/bindings/py/tests/test_e2e_winrt.py b/bindings/py/tests/test_e2e_winrt.py index ab4b30c6..b1d17f75 100644 --- a/bindings/py/tests/test_e2e_winrt.py +++ b/bindings/py/tests/test_e2e_winrt.py @@ -13,7 +13,7 @@ Requires: Windows 10/11 with standard SDK (no extra installs needed). """ -from dynwinrt_py import ( +from dynwinrt import ( DynWinRTType, DynWinRTMethodSig, DynWinRTValue, diff --git a/bindings/py/tests/test_phase1.py b/bindings/py/tests/test_phase1.py index 998684d8..b67f7840 100644 --- a/bindings/py/tests/test_phase1.py +++ b/bindings/py/tests/test_phase1.py @@ -22,8 +22,8 @@ import pytest -import dynwinrt_py -from dynwinrt_py import ( +import dynwinrt +from dynwinrt import ( DynWinRTType, DynWinRTMethodSig, DynWinRTOverrideInterface, @@ -41,7 +41,7 @@ ro_initialize, register_xaml_runtime_class, ) -from dynwinrt_py.dynwinrt_py import ( +from dynwinrt.dynwinrt import ( _DynWinRTAsync, _DynWinRTAsyncWithProgress, _dynwinrt_dispatch_progress, @@ -536,7 +536,7 @@ def __del__(self): def test_async_protocols_are_public(): assert WinRTAsync.__name__ == "WinRTAsync" assert WinRTAsyncWithProgress.__name__ == "WinRTAsyncWithProgress" - assert not hasattr(dynwinrt_py, "_DynWinRTAsync") + assert not hasattr(dynwinrt, "_DynWinRTAsync") def test_ro_apartment_balances_nested_initialization(): diff --git a/bindings/py/tests/test_runtime.py b/bindings/py/tests/test_runtime.py index 2d35fb72..bf3baed8 100644 --- a/bindings/py/tests/test_runtime.py +++ b/bindings/py/tests/test_runtime.py @@ -1,9 +1,9 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. -import dynwinrt_py +import dynwinrt import pytest -from dynwinrt_py import DynWinRTType, DynWinRTValue, WinGUID, ro_initialize +from dynwinrt import DynWinRTType, DynWinRTValue, WinGUID, ro_initialize def test_to_number(): diff --git a/bindings/py/tests/test_typing_package.py b/bindings/py/tests/test_typing_package.py index 5139a984..9d0c4551 100644 --- a/bindings/py/tests/test_typing_package.py +++ b/bindings/py/tests/test_typing_package.py @@ -3,11 +3,11 @@ from pathlib import Path -import dynwinrt_py +import dynwinrt def test_wheel_contains_typing_metadata(): - package_dir = Path(dynwinrt_py.__file__).parent + package_dir = Path(dynwinrt.__file__).parent assert (package_dir / "__init__.pyi").is_file() assert (package_dir / "py.typed").is_file() diff --git a/bindings/py/uv.lock b/bindings/py/uv.lock index 38d498c8..1a93bbe3 100644 --- a/bindings/py/uv.lock +++ b/bindings/py/uv.lock @@ -18,7 +18,7 @@ wheels = [ ] [[package]] -name = "dynwinrt-py" +name = "dynwinrt" source = { editable = "." } [package.dev-dependencies] diff --git a/docs/status/PYTHON_CHECKLIST.md b/docs/status/PYTHON_CHECKLIST.md index 5578b7ff..e45c7f3e 100644 --- a/docs/status/PYTHON_CHECKLIST.md +++ b/docs/status/PYTHON_CHECKLIST.md @@ -47,7 +47,7 @@ of a dynamic projection. ## P0: runtime and generated API agreement -- [x] Ship a `.pyi` and `py.typed` marker for the `dynwinrt_py` extension. +- [x] Ship a `.pyi` and `py.typed` marker for the `dynwinrt` extension. - [x] Return declared enum members as generated `IntEnum` instances while preserving unknown values as integers. - [x] Use `invoke_all()` for arbitrary multiple-out methods. @@ -82,8 +82,9 @@ of a dynamic projection. runner and fail closed; local x64 validation does not count as ARM64 validation. - [x] Remove unverified PyPy metadata. -- [x] Require `python-v` release tags to match both Cargo package - versions rather than rewriting release sources in CI. +- [x] Derive Python artifact versions from ordinary `v` release tags, + inject them only into ephemeral CI workspaces, and attach the complete + wheel set to the shared JavaScript GitHub release. - [ ] Publish signed/provenanced wheels to an approved internal Python feed. - [x] Add a protected-environment, OIDC trusted-publishing path that requires manual opt-in from an existing release tag. @@ -123,7 +124,7 @@ of a dynamic projection. Windows App SDK, and custom WinMD namespaces. - [ ] Add runnable samples for files, notifications, imaging, async, events, collections, and custom WinMD consumption. -- [ ] Document generated-code version compatibility with `dynwinrt-py`. +- [ ] Document generated-code version compatibility with `dynwinrt`. - [ ] Add troubleshooting for metadata, apartment, bootstrap, architecture, and wheel compatibility failures. - [ ] Add progress-callback tests for worker-thread delivery and operations @@ -187,7 +188,7 @@ of a dynamic projection. 6. [x] Use `invoke_all()` for arbitrary multiple-out methods. 7. [x] Model FillArray as caller-owned `(capacity, buffer)` ABI. 8. [x] Verify Python and JavaScript `IndexOf` and `GetMany`, including zero items. -9. [x] Ship typed `dynwinrt_py` wheels and validate the native extension stubs. +9. [x] Ship typed `dynwinrt` wheels and validate the native extension stubs. 10. [x] Generate Python stubs by default and type-check generated E2E APIs. 11. [x] Unbox `IReference` returns as `T | None`. 12. [x] Box native Python values and `None` for `IReference` inputs. diff --git a/eng/coverage/coverage.ps1 b/eng/coverage/coverage.ps1 index b2b67b50..1de9f89c 100644 --- a/eng/coverage/coverage.ps1 +++ b/eng/coverage/coverage.ps1 @@ -320,7 +320,7 @@ function Write-Reports { $measuredFiles = @($coverageJson["files"].Keys) $productFiles = @($measuredFiles | Where-Object { $_ -match "tests[\\/]e2e[\\/]e2e_generated[\\/]python_bindings" -or - ($_ -match "dynwinrt_py" -and $_ -notmatch "bindings[\\/]py[\\/]tests") + ($_ -match "dynwinrt" -and $_ -notmatch "bindings[\\/]py[\\/]tests") }) if ($productFiles.Count -gt 0) { Invoke-Step "Python HTML coverage report" { @@ -607,7 +607,7 @@ try { & $script:pythonExe -m pytest bindings\py\tests -q } Invoke-Step "Python stubtest" { - & $script:pythonExe -m mypy.stubtest dynwinrt_py ` + & $script:pythonExe -m mypy.stubtest dynwinrt ` --allowlist bindings\py\stubtest_allowlist.txt ` --ignore-disjoint-bases } diff --git a/eng/coverage/python-coveragerc b/eng/coverage/python-coveragerc index f9f23c4e..9a369589 100644 --- a/eng/coverage/python-coveragerc +++ b/eng/coverage/python-coveragerc @@ -5,7 +5,7 @@ relative_files = True [report] include = - */dynwinrt_py/* + */dynwinrt/* tests/e2e/e2e_generated/python_bindings/* omit = */__pycache__/* diff --git a/eng/release/python/prepare_python_release.py b/eng/release/python/prepare_python_release.py new file mode 100644 index 00000000..03d4abad --- /dev/null +++ b/eng/release/python/prepare_python_release.py @@ -0,0 +1,69 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +from __future__ import annotations + +import argparse +import re +from pathlib import Path + +from packaging.version import Version + + +ROOT = Path(__file__).resolve().parents[3] +PACKAGE_MANIFESTS = ( + Path("bindings/py/Cargo.toml"), + Path("tools/dynwinrt-codegen/Cargo.toml"), +) +PACKAGE_NAMES = ("dynwinrt-py", "dynwinrt-codegen") +CARGO_SEMVER = re.compile( + r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)" + r"(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?" + r"(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$" +) + + +def replace_once(path: Path, pattern: str, replacement: str) -> None: + with path.open("r", encoding="utf-8", newline="") as stream: + contents = stream.read() + updated, count = re.subn(pattern, replacement, contents, count=1) + if count != 1: + raise RuntimeError(f"Expected one version entry in {path}, found {count}") + if updated != contents: + with path.open("w", encoding="utf-8", newline="") as stream: + stream.write(updated) + + +def prepare_release(root: Path, version: str) -> None: + if not CARGO_SEMVER.fullmatch(version): + raise ValueError(f"Release version is not valid Cargo SemVer: {version!r}") + + python_version = Version(version) + for relative_path in PACKAGE_MANIFESTS: + replace_once( + root / relative_path, + r'(?ms)(^\[package\].*?^version\s*=\s*")[^"]+(")', + rf"\g<1>{version}\g<2>", + ) + + lock_path = root / "Cargo.lock" + for package_name in PACKAGE_NAMES: + replace_once( + lock_path, + rf'(?ms)(^\[\[package\]\]\r?\nname = "{re.escape(package_name)}"\r?\nversion = ")[^"]+(")', + rf"\g<1>{version}\g<2>", + ) + + print(f"Prepared Python release version {version} (PEP 440: {python_version})") + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--version", required=True) + parser.add_argument("--root", type=Path, default=ROOT) + args = parser.parse_args() + prepare_release(args.root.resolve(), args.version) + + +if __name__ == "__main__": + main() diff --git a/eng/release/python/test_python_codegen_wheel.ps1 b/eng/release/python/test_python_codegen_wheel.ps1 index aa1bf6e5..243fc39e 100644 --- a/eng/release/python/test_python_codegen_wheel.ps1 +++ b/eng/release/python/test_python_codegen_wheel.ps1 @@ -50,7 +50,7 @@ if (-not (Get-ChildItem $pyOutput -Filter *.py -File -Recurse) -or } $manifest = Get-Content (Join-Path $pyOutput 'pyproject.toml') -Raw -if ($manifest -notmatch [regex]::Escape("dependencies = [`"dynwinrt-py==$Version`"]")) { - throw "Generated package does not pin dynwinrt-py==$Version" +if ($manifest -notmatch [regex]::Escape("dependencies = [`"dynwinrt==$Version`"]")) { + throw "Generated package does not pin dynwinrt==$Version" } Write-Host "Standalone $Architecture codegen wheel consumed without cargo or rustc" diff --git a/eng/release/python/test_python_runtime_wheel.ps1 b/eng/release/python/test_python_runtime_wheel.ps1 index 14e48c71..95a596c9 100644 --- a/eng/release/python/test_python_runtime_wheel.ps1 +++ b/eng/release/python/test_python_runtime_wheel.ps1 @@ -6,6 +6,7 @@ param( [Parameter(Mandatory = $true)][string]$RuntimeWheelDirectory, [Parameter(Mandatory = $true)][string]$CodegenWheelDirectory, [Parameter(Mandatory = $true)][string]$Version, + [Parameter(Mandatory = $true)][string]$PythonPackageVersion, [Parameter(Mandatory = $true)][ValidateSet('x64', 'arm64')][string]$Architecture, [Parameter(Mandatory = $true)][string]$PythonMinor ) @@ -20,7 +21,7 @@ New-Item $root -ItemType Directory -Force | Out-Null & $Python -m venv (Join-Path $root 'venv') $venvPython = Join-Path $root 'venv\Scripts\python.exe' & $venvPython -m pip install --disable-pip-version-check --no-index ` - --find-links $RuntimeWheelDirectory "dynwinrt-py==$Version" + --find-links $RuntimeWheelDirectory "dynwinrt==$Version" if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } & $venvPython -m pip install --disable-pip-version-check --no-index ` --find-links $CodegenWheelDirectory "dynwinrt-codegen==$Version" @@ -46,14 +47,14 @@ assert platform.machine().lower() in ( ("amd64", "x86_64") if expected_arch == "x64" else ("arm64", "aarch64") ) assert f"{sys.version_info.major}.{sys.version_info.minor}" == expected_minor -assert importlib.metadata.version("dynwinrt-py") == expected_version +assert importlib.metadata.version("dynwinrt") == expected_version assert importlib.metadata.version("dynwinrt-codegen") == expected_version -import dynwinrt_py -from dynwinrt_py import RoApartment, projected_lifetime_scope +import dynwinrt +from dynwinrt import RoApartment, projected_lifetime_scope from generated_uri import Uri -assert ".python-release" in str(pathlib.Path(dynwinrt_py.__file__).resolve()) +assert ".python-release" in str(pathlib.Path(dynwinrt.__file__).resolve()) with RoApartment(1), projected_lifetime_scope(): uri = Uri("https://example.com:443/release?q=1") assert uri.host == "example.com" @@ -64,7 +65,7 @@ print("isolated runtime consumer OK") Push-Location $root try { - & $venvPython -c $test $Architecture $PythonMinor $Version + & $venvPython -c $test $Architecture $PythonMinor $PythonPackageVersion if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } } finally { Pop-Location diff --git a/eng/release/python/verify_python_release.py b/eng/release/python/verify_python_release.py index c0600fbe..2d0c7ad8 100644 --- a/eng/release/python/verify_python_release.py +++ b/eng/release/python/verify_python_release.py @@ -43,11 +43,12 @@ def verify_source(tag: str | None, release_version: str | None) -> None: ROOT / "tools" / "dynwinrt-codegen" / "pyproject.toml" )["project"] - assert runtime_cargo["name"] == runtime_project["name"] == "dynwinrt-py" + assert runtime_cargo["name"] == "dynwinrt-py" + assert runtime_project["name"] == "dynwinrt" assert codegen_cargo["name"] == codegen_project["name"] == "dynwinrt-codegen" assert runtime_cargo["version"] == codegen_cargo["version"], ( "Runtime and codegen Cargo versions must match because generated packages " - "pin dynwinrt-py to the codegen version" + "pin dynwinrt to the codegen version" ) assert SpecifierSet(runtime_project["requires-python"]) == SpecifierSet( RUNTIME_REQUIRES @@ -60,7 +61,7 @@ def verify_source(tag: str | None, release_version: str | None) -> None: ROOT / "tools" / "dynwinrt-codegen" / "src" / "codegen" / "package.rs" ).read_text(encoding="utf-8") assert f'requires-python = \\"{RUNTIME_REQUIRES}\\"' in generated_manifest - assert 'dependencies = [\\"dynwinrt-py=={runtime_version}\\"]' in generated_manifest + assert 'dependencies = [\\"dynwinrt=={runtime_version}\\"]' in generated_manifest pyo3 = runtime_manifest["dependencies"]["pyo3"] assert Version(str(pyo3)) >= Version("0.29.0") @@ -73,8 +74,8 @@ def verify_source(tag: str | None, release_version: str | None) -> None: f"version {raw_version!r}" ) if tag: - match = re.fullmatch(r"python-v(.+)", tag) - assert match, f"Python release tags must match python-v, got {tag!r}" + match = re.fullmatch(r"v(.+)", tag) + assert match, f"Release tags must match v, got {tag!r}" assert match.group(1) == raw_version, ( f"Tag {tag!r} does not exactly match Cargo version {raw_version!r}" ) @@ -108,7 +109,7 @@ def verify_wheel(args: argparse.Namespace) -> None: assert canonicalize_name(metadata["Name"]) == canonicalize_name(args.package) assert Version(metadata["Version"]) == Version(args.version) expected_requires = ( - RUNTIME_REQUIRES if args.package == "dynwinrt-py" else CODEGEN_REQUIRES + RUNTIME_REQUIRES if args.package == "dynwinrt" else CODEGEN_REQUIRES ) assert SpecifierSet(metadata["Requires-Python"]) == SpecifierSet( expected_requires @@ -116,7 +117,7 @@ def verify_wheel(args: argparse.Namespace) -> None: dependencies = metadata.get_all("Requires-Dist", []) assert dependencies == [], f"Unexpected wheel dependencies: {dependencies}" names = archive.namelist() - if args.package == "dynwinrt-py": + if args.package == "dynwinrt": assert any(name.endswith(".pyd") for name in names) assert any(name.endswith("/py.typed") for name in names) assert any(name.endswith(".pyi") for name in names) diff --git a/samples/python/winui-tic-tac-toe-code-only/README.md b/samples/python/winui-tic-tac-toe-code-only/README.md index ea7ed6bc..3c68d230 100644 --- a/samples/python/winui-tic-tac-toe-code-only/README.md +++ b/samples/python/winui-tic-tac-toe-code-only/README.md @@ -20,7 +20,7 @@ returned object as the projected `SystemBackdrop` expected by `Microsoft.WindowsAppRuntime.Bootstrap.dll`. The matching x64 WinAppSDK framework/runtime packages and resources must also be installed or available to the unpackaged runtime. -- `dynwinrt_py` installed in the Python interpreter used to run the sample. +- `dynwinrt` installed in the Python interpreter used to run the sample. - A compatible `dynwinrt-codegen` executable, either on `PATH` or passed with `-Codegen`. @@ -45,7 +45,7 @@ Generate the local projection package and copy the bootstrap DLL: ``` If codegen is installed on `PATH`, omit `-Codegen`. Run with the Python -environment that contains `dynwinrt_py`: +environment that contains `dynwinrt`: ```powershell .\run.ps1 -Python C:\path\to\python.exe diff --git a/samples/python/winui-tic-tac-toe-code-only/app.py b/samples/python/winui-tic-tac-toe-code-only/app.py index 3bbc60d0..7f8ebece 100644 --- a/samples/python/winui-tic-tac-toe-code-only/app.py +++ b/samples/python/winui-tic-tac-toe-code-only/app.py @@ -6,7 +6,7 @@ ROOT / ".runtime" / "Microsoft.WindowsAppRuntime.Bootstrap.dll" ) -from dynwinrt_py import ( +from dynwinrt import ( DynWinRTMethodSig, DynWinRTType, DynWinRTValue, diff --git a/samples/python/winui-tic-tac-toe/README.md b/samples/python/winui-tic-tac-toe/README.md index 1b765337..30313763 100644 --- a/samples/python/winui-tic-tac-toe/README.md +++ b/samples/python/winui-tic-tac-toe/README.md @@ -13,7 +13,7 @@ window resizing, a named Python `TicTacToePanel` XAML registration, a native `Microsoft.WindowsAppRuntime.Bootstrap.dll`. The matching x64 WinAppSDK framework/runtime packages and resources must also be installed or available to the unpackaged runtime. -- `dynwinrt_py` installed in the Python interpreter used to run the sample. +- `dynwinrt` installed in the Python interpreter used to run the sample. - A compatible `dynwinrt-codegen` executable, either on `PATH` or passed with `-Codegen`. @@ -40,7 +40,7 @@ DLL (replace the fixture paths): ``` If the CLI is installed on `PATH`, omit `-Codegen`. Run with the same Python -environment that contains `dynwinrt_py`: +environment that contains `dynwinrt`: ```powershell .\run.ps1 -Python C:\path\to\python.exe diff --git a/samples/python/winui-tic-tac-toe/app.py b/samples/python/winui-tic-tac-toe/app.py index 3cfa5393..3069c1ed 100644 --- a/samples/python/winui-tic-tac-toe/app.py +++ b/samples/python/winui-tic-tac-toe/app.py @@ -7,7 +7,7 @@ ROOT / ".runtime" / "Microsoft.WindowsAppRuntime.Bootstrap.dll" ) -from dynwinrt_py import RoApartment, init_winappsdk, projected_lifetime_scope +from dynwinrt import RoApartment, init_winappsdk, projected_lifetime_scope from generated.microsoft.ui.windowing import SizeInt32 from generated.microsoft.ui.xaml import Application, ApplicationTheme, Window from generated.microsoft.ui.xaml.controls import Button, Size, StackPanel, TextBlock diff --git a/tests/e2e/runners/py_runner.py b/tests/e2e/runners/py_runner.py index 39cacc77..b218512b 100644 --- a/tests/e2e/runners/py_runner.py +++ b/tests/e2e/runners/py_runner.py @@ -75,7 +75,7 @@ def projected_values_equal(left, right): def wrap_arg(val): """Wrap a Python value into a DynWinRTValue for method args.""" - import dynwinrt_py as dw + import dynwinrt as dw if isinstance(val, str): return dw.DynWinRTValue.from_hstring(val) if isinstance(val, bool): @@ -115,7 +115,7 @@ async def run_spec(spec: dict, generated_dir: str, pkg_name: str) -> dict: obj = None if inst_kind == 'activate': - import dynwinrt_py as dw + import dynwinrt as dw raw = dw.DynWinRTValue.activation_factory(f'{ns}.{cls_name}').activate() obj = cls(raw) elif inst_kind == 'static_factory': @@ -326,7 +326,7 @@ async def run_check( cr['pass'] = True elif kind == 'vector_get_many': - import dynwinrt_py as dw + import dynwinrt as dw vec = getattr(obj, member) capacity = min(check.get('capacity', 4), vec.size) @@ -411,7 +411,7 @@ async def run_check( cr['pass'] = True elif kind == 'array_roundtrip': - import dynwinrt_py as dw + import dynwinrt as dw elem_type = check['element_type'] values = check['values'] @@ -660,7 +660,7 @@ async def run_check( cr['pass'] = True elif kind == 'value_set_event_lifecycle': - import dynwinrt_py as dw + import dynwinrt as dw counts = {'on': 0, 'subscribe': 0, 'once': 0} @@ -815,7 +815,7 @@ def fail(*_args): cr['error'] = 'closed object allowed context re-entry' except RuntimeError: if hasattr(resource, 'create_reference'): - from dynwinrt_py import release_projected + from dynwinrt import release_projected release_projected(resource) try: @@ -855,8 +855,8 @@ def fail(*_args): cr['pass'] = True elif kind == 'async_memory_roundtrip': - import dynwinrt_py as dw - from dynwinrt_py.dynwinrt_py import _DynWinRTAsync + import dynwinrt as dw + from dynwinrt.dynwinrt import _DynWinRTAsync write_val = check.get('write_value', 42) stream = cls.create() if hasattr(cls, 'create') else cls.create_default() @@ -884,7 +884,7 @@ def fail(*_args): read_val = reader.read_int32() def blocking_store(): - import dynwinrt_py as dw + import dynwinrt as dw dw.ro_initialize(1) try: @@ -1330,7 +1330,7 @@ def progress_without_loop(): cr['pass'] = True elif kind == 'async_cancellation': - import dynwinrt_py as dw + import dynwinrt as dw info_iid = dw.WinGUID.parse('00000036-0000-0000-c000-000000000046') info_type = ( @@ -1516,7 +1516,7 @@ def block_on_sta(): elif kind == 'generated_helper_matrix': from pathlib import Path - import dynwinrt_py as dw + import dynwinrt as dw # This spec runs last. Earlier E2E specs validate real generated # call sites; this matrix covers each emitted copy of shared helpers. @@ -1716,7 +1716,7 @@ def main(): sys.path.insert(0, gen_parent) # Init WinRT - import dynwinrt_py as dw + import dynwinrt as dw dw.ro_initialize(1) # Load specs diff --git a/tests/e2e/runners/py_winui_runner.py b/tests/e2e/runners/py_winui_runner.py index 7e8ab13d..e283e37e 100644 --- a/tests/e2e/runners/py_winui_runner.py +++ b/tests/e2e/runners/py_winui_runner.py @@ -23,7 +23,7 @@ def run(bindings_dir: Path, bootstrap_dll: Path, major: int, minor: int) -> None sys.path.insert(0, str(bindings_dir.parent)) package = bindings_dir.name - from dynwinrt_py import ( + from dynwinrt import ( DynWinRTType, DynWinRTValue, RoApartment, diff --git a/tests/e2e/typecheck/python_generated_api.py b/tests/e2e/typecheck/python_generated_api.py index bef0d88a..1acfab37 100644 --- a/tests/e2e/typecheck/python_generated_api.py +++ b/tests/e2e/typecheck/python_generated_api.py @@ -4,7 +4,7 @@ from collections.abc import Sequence from typing import Awaitable, List, Tuple -from dynwinrt_py import ( +from dynwinrt import ( DynWinRTArray, WinRTAsync, WinRTAsyncWithProgress, diff --git a/tools/dynwinrt-codegen/README.md b/tools/dynwinrt-codegen/README.md index f868411e..828a27af 100644 --- a/tools/dynwinrt-codegen/README.md +++ b/tools/dynwinrt-codegen/README.md @@ -19,7 +19,7 @@ dynwinrt-codegen generate --namespace Windows.Foundation --class-name Uri ` The Python distribution contains only the executable. Its wheels are `py3-none-win_amd64` and `py3-none-win_arm64`, work with CPython 3.8–3.14, and do not install or invoke Cargo/Rust. Generated Python packages require CPython -3.11–3.14 and pin `dynwinrt-py` to the exact codegen version. +3.11–3.14 and pin `dynwinrt` to the exact codegen version. ## Usage diff --git a/tools/dynwinrt-codegen/src/codegen/package.rs b/tools/dynwinrt-codegen/src/codegen/package.rs index 6d1c68c8..af54b689 100644 --- a/tools/dynwinrt-codegen/src/codegen/package.rs +++ b/tools/dynwinrt-codegen/src/codegen/package.rs @@ -59,7 +59,7 @@ pub fn render_python_pyproject(input: &PythonPackageManifestInput<'_>) -> String name = \"{distribution_name}\"\n\ version = \"{package_version}\"\n\ requires-python = \">=3.11,<3.15\"\n\ - dependencies = [\"dynwinrt-py=={runtime_version}\"]\n\ + dependencies = [\"dynwinrt=={runtime_version}\"]\n\ \n\ [tool.setuptools]\n\ packages = [{packages}]\n\ @@ -277,7 +277,7 @@ mod tests { "packages = [\"contoso_winrt\", \"contoso_winrt.windows\", \ \"contoso_winrt.windows.foundation\"]" )); - assert!(out.contains("dependencies = [\"dynwinrt-py==0.4.5\"]")); + assert!(out.contains("dependencies = [\"dynwinrt==0.4.5\"]")); assert!(out.contains("\"contoso_winrt\" = [\"py.typed\", \"*.pyi\", \"**/*.pyi\"]")); } } diff --git a/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/class.rs b/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/class.rs index 3d78e4f0..62ed8026 100644 --- a/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/class.rs +++ b/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/class.rs @@ -57,7 +57,7 @@ pub fn generate_class( out.push_str(IMPORT_LINE); if has_public_composition { out.push_str( - "from dynwinrt_py import register_xaml_runtime_class as _dynwinrt_register_xaml_runtime_class\n", + "from dynwinrt import register_xaml_runtime_class as _dynwinrt_register_xaml_runtime_class\n", ); } if winui::is_dispatcher_queue(class) { @@ -74,7 +74,7 @@ pub fn generate_class( collection_mixins.dedup(); if !collection_mixins.is_empty() { out.push_str(&format!( - "from dynwinrt_py.dynwinrt_py import {}\n", + "from dynwinrt.dynwinrt import {}\n", collection_mixins.join(", ") )); } @@ -488,7 +488,7 @@ pub fn generate_class( )); if bootstrap.supports_unpackaged_resources { out.push_str( - " from dynwinrt_py import has_package_identity, get_winappsdk_resource_pri_path\n", + " from dynwinrt import has_package_identity, get_winappsdk_resource_pri_path\n", ); out.push_str(" if not has_package_identity():\n"); out.push_str(&format!( diff --git a/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/mod.rs b/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/mod.rs index bad0814f..60ef8166 100644 --- a/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/mod.rs +++ b/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/mod.rs @@ -49,11 +49,11 @@ from datetime import datetime, timedelta from typing import TYPE_CHECKING from uuid import UUID from weakref import ref as _weakref_ref -from dynwinrt_py import ( +from dynwinrt import ( DynWinRTType, DynWinRTMethodSig, DynWinRTValue, DynWinRTArray, DynWinRTStruct, DynWinRtDelegate, DynWinRTOverrideInterface, WinGUID, ) -from dynwinrt_py.dynwinrt_py import ( +from dynwinrt.dynwinrt import ( _dynwinrt_array, _dynwinrt_bind_overload, _dynwinrt_datetime_to_ticks, _dynwinrt_guid, _dynwinrt_map, _dynwinrt_new_vector, _dynwinrt_ticks_to_datetime, _dynwinrt_ticks_to_timedelta, _dynwinrt_timedelta_to_ticks, _dynwinrt_track_projected, _dynwinrt_uuid, _dynwinrt_vector, @@ -95,8 +95,8 @@ def _dynwinrt_delegate(value, iid, parameter_types): \n"; const ASYNC_IMPORT_LINE: &str = "\ -from dynwinrt_py import WinRTAsync, WinRTAsyncWithProgress -from dynwinrt_py.dynwinrt_py import _DynWinRTAsync, _DynWinRTAsyncWithProgress +from dynwinrt import WinRTAsync, WinRTAsyncWithProgress +from dynwinrt.dynwinrt import _DynWinRTAsync, _DynWinRTAsyncWithProgress "; const IREFERENCE_HELPER: &str = "\ diff --git a/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/types.rs b/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/types.rs index d2ba2677..443941f5 100644 --- a/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/types.rs +++ b/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/types.rs @@ -94,14 +94,14 @@ pub fn generate_interface( let is_element_factory = iface.namespace == "Microsoft.UI.Xaml" && iface.name == "IElementFactory"; if is_element_factory { - out.push_str("from dynwinrt_py import DynWinRtElementFactory\n"); + out.push_str("from dynwinrt import DynWinRtElementFactory\n"); } let collection_kind = interface_kind(iface); let observable_vector = observable_vector_name(iface); if observable_vector.is_none() && let Some(mixin) = collection_kind.and_then(runtime_mixin) { - out.push_str(&format!("from dynwinrt_py.dynwinrt_py import {mixin}\n")); + out.push_str(&format!("from dynwinrt.dynwinrt import {mixin}\n")); } if methods_have_async_output(iface.methods.iter()) { out.push_str(ASYNC_IMPORT_LINE); @@ -470,7 +470,7 @@ fn generate_delegate(iface: &InterfaceMeta) -> String { let mut out = String::new(); out.push_str(HEADER); out.push_str(FUTURE_ANNOTATIONS); - out.push_str("from dynwinrt_py import DynWinRTType, WinGUID\n\n"); + out.push_str("from dynwinrt import DynWinRTType, WinGUID\n\n"); let invoke = iface.methods.iter().find(|m| m.name == "Invoke"); if iface.generic_piid.is_some() { diff --git a/tools/dynwinrt-codegen/src/codegen/winrt/python/stubs.rs b/tools/dynwinrt-codegen/src/codegen/winrt/python/stubs.rs index b4b728d3..de749abe 100644 --- a/tools/dynwinrt-codegen/src/codegen/winrt/python/stubs.rs +++ b/tools/dynwinrt-codegen/src/codegen/winrt/python/stubs.rs @@ -40,10 +40,10 @@ from collections.abc import ( from datetime import datetime, timedelta from uuid import UUID from typing import overload -from dynwinrt_py import ( +from dynwinrt import ( DynWinRTType, DynWinRTValue, DynWinRTArray, DynWinRTStruct, DynWinRtDelegate, WinGUID, )\n"; -const ASYNC_IMPORT_LINE: &str = "from dynwinrt_py import WinRTAsync, WinRTAsyncWithProgress\n"; +const ASYNC_IMPORT_LINE: &str = "from dynwinrt import WinRTAsync, WinRTAsyncWithProgress\n"; /// Generate a `.pyi` stub for an enum. Returns `None` for non-enum TypeMeta. pub fn generate_enum_stub(en: &TypeMeta) -> Option { @@ -89,7 +89,7 @@ pub fn generate_interface_stub( let mut out = String::new(); out.push_str(HEADER); out.push_str(FUTURE_ANNOTATIONS); - out.push_str("from dynwinrt_py import DynWinRTType, WinGUID\n\n"); + out.push_str("from dynwinrt import DynWinRTType, WinGUID\n\n"); out.push_str(&format!("IID_{}: WinGUID\n", iface.name)); out.push_str(&format!("{}_PARAM_TYPES: list[DynWinRTType]\n", iface.name)); return out; @@ -323,7 +323,7 @@ pub fn generate_class_stub( out.push_str("from typing import NoReturn\n"); } if has_public_composition { - out.push_str("from dynwinrt_py import DynWinRTXamlRegistration\n"); + out.push_str("from dynwinrt import DynWinRTXamlRegistration\n"); } if methods_have_async_output( class diff --git a/tools/dynwinrt-codegen/tests/element_factory_test.rs b/tools/dynwinrt-codegen/tests/element_factory_test.rs index 01579277..f03fae64 100644 --- a/tools/dynwinrt-codegen/tests/element_factory_test.rs +++ b/tools/dynwinrt-codegen/tests/element_factory_test.rs @@ -257,7 +257,7 @@ fn element_factory_projects_js_callback_constructor() { assert!(dts.contains( "static create(getElement: (args: ElementFactoryGetArgs) => UIElement, recycleElement: (args: ElementFactoryRecycleArgs) => void): IElementFactory & { releaseCallbacks(): void };", )); - assert!(py.contains("from dynwinrt_py import DynWinRtElementFactory")); + assert!(py.contains("from dynwinrt import DynWinRtElementFactory")); assert!(py.contains("def create(get_element, recycle_element):")); assert!(py.contains("native_element = native.cast(")); assert!(py.contains("elements[native_element.identity_raw()] = element")); diff --git a/tools/dynwinrt-codegen/tests/snapshots/uri_py/i_iterator_i_www_form_url_decoder_entry.py b/tools/dynwinrt-codegen/tests/snapshots/uri_py/i_iterator_i_www_form_url_decoder_entry.py index 98ac90f1..fe72e1fa 100644 --- a/tools/dynwinrt-codegen/tests/snapshots/uri_py/i_iterator_i_www_form_url_decoder_entry.py +++ b/tools/dynwinrt-codegen/tests/snapshots/uri_py/i_iterator_i_www_form_url_decoder_entry.py @@ -11,11 +11,11 @@ from typing import TYPE_CHECKING from uuid import UUID from weakref import ref as _weakref_ref -from dynwinrt_py import ( +from dynwinrt import ( DynWinRTType, DynWinRTMethodSig, DynWinRTValue, DynWinRTArray, DynWinRTStruct, DynWinRtDelegate, DynWinRTOverrideInterface, WinGUID, ) -from dynwinrt_py.dynwinrt_py import ( +from dynwinrt.dynwinrt import ( _dynwinrt_array, _dynwinrt_bind_overload, _dynwinrt_datetime_to_ticks, _dynwinrt_guid, _dynwinrt_map, _dynwinrt_new_vector, _dynwinrt_ticks_to_datetime, _dynwinrt_ticks_to_timedelta, _dynwinrt_timedelta_to_ticks, _dynwinrt_track_projected, _dynwinrt_uuid, _dynwinrt_vector, @@ -55,7 +55,7 @@ def _dynwinrt_delegate(value, iid, parameter_types): raise TypeError('delegate value must be callable or a DynWinRTValue') return _dynwinrt_create_delegate(iid, parameter_types, value).to_value() -from dynwinrt_py.dynwinrt_py import _WinRTIteratorMixin +from dynwinrt.dynwinrt import _WinRTIteratorMixin if TYPE_CHECKING: from .i_www_form_url_decoder_entry import IID_IWwwFormUrlDecoderEntry, IWwwFormUrlDecoderEntry # noqa: F401 diff --git a/tools/dynwinrt-codegen/tests/snapshots/uri_py/i_stringable.py b/tools/dynwinrt-codegen/tests/snapshots/uri_py/i_stringable.py index 92de17fb..beee4fdb 100644 --- a/tools/dynwinrt-codegen/tests/snapshots/uri_py/i_stringable.py +++ b/tools/dynwinrt-codegen/tests/snapshots/uri_py/i_stringable.py @@ -11,11 +11,11 @@ from typing import TYPE_CHECKING from uuid import UUID from weakref import ref as _weakref_ref -from dynwinrt_py import ( +from dynwinrt import ( DynWinRTType, DynWinRTMethodSig, DynWinRTValue, DynWinRTArray, DynWinRTStruct, DynWinRtDelegate, DynWinRTOverrideInterface, WinGUID, ) -from dynwinrt_py.dynwinrt_py import ( +from dynwinrt.dynwinrt import ( _dynwinrt_array, _dynwinrt_bind_overload, _dynwinrt_datetime_to_ticks, _dynwinrt_guid, _dynwinrt_map, _dynwinrt_new_vector, _dynwinrt_ticks_to_datetime, _dynwinrt_ticks_to_timedelta, _dynwinrt_timedelta_to_ticks, _dynwinrt_track_projected, _dynwinrt_uuid, _dynwinrt_vector, diff --git a/tools/dynwinrt-codegen/tests/snapshots/uri_py/i_uri_runtime_class_with_absolute_canonical_uri.py b/tools/dynwinrt-codegen/tests/snapshots/uri_py/i_uri_runtime_class_with_absolute_canonical_uri.py index cccbc209..1cee8dbc 100644 --- a/tools/dynwinrt-codegen/tests/snapshots/uri_py/i_uri_runtime_class_with_absolute_canonical_uri.py +++ b/tools/dynwinrt-codegen/tests/snapshots/uri_py/i_uri_runtime_class_with_absolute_canonical_uri.py @@ -11,11 +11,11 @@ from typing import TYPE_CHECKING from uuid import UUID from weakref import ref as _weakref_ref -from dynwinrt_py import ( +from dynwinrt import ( DynWinRTType, DynWinRTMethodSig, DynWinRTValue, DynWinRTArray, DynWinRTStruct, DynWinRtDelegate, DynWinRTOverrideInterface, WinGUID, ) -from dynwinrt_py.dynwinrt_py import ( +from dynwinrt.dynwinrt import ( _dynwinrt_array, _dynwinrt_bind_overload, _dynwinrt_datetime_to_ticks, _dynwinrt_guid, _dynwinrt_map, _dynwinrt_new_vector, _dynwinrt_ticks_to_datetime, _dynwinrt_ticks_to_timedelta, _dynwinrt_timedelta_to_ticks, _dynwinrt_track_projected, _dynwinrt_uuid, _dynwinrt_vector, diff --git a/tools/dynwinrt-codegen/tests/snapshots/uri_py/i_www_form_url_decoder_entry.py b/tools/dynwinrt-codegen/tests/snapshots/uri_py/i_www_form_url_decoder_entry.py index 40a1d544..28e8306b 100644 --- a/tools/dynwinrt-codegen/tests/snapshots/uri_py/i_www_form_url_decoder_entry.py +++ b/tools/dynwinrt-codegen/tests/snapshots/uri_py/i_www_form_url_decoder_entry.py @@ -11,11 +11,11 @@ from typing import TYPE_CHECKING from uuid import UUID from weakref import ref as _weakref_ref -from dynwinrt_py import ( +from dynwinrt import ( DynWinRTType, DynWinRTMethodSig, DynWinRTValue, DynWinRTArray, DynWinRTStruct, DynWinRtDelegate, DynWinRTOverrideInterface, WinGUID, ) -from dynwinrt_py.dynwinrt_py import ( +from dynwinrt.dynwinrt import ( _dynwinrt_array, _dynwinrt_bind_overload, _dynwinrt_datetime_to_ticks, _dynwinrt_guid, _dynwinrt_map, _dynwinrt_new_vector, _dynwinrt_ticks_to_datetime, _dynwinrt_ticks_to_timedelta, _dynwinrt_timedelta_to_ticks, _dynwinrt_track_projected, _dynwinrt_uuid, _dynwinrt_vector, diff --git a/tools/dynwinrt-codegen/tests/snapshots/uri_py/uri.py b/tools/dynwinrt-codegen/tests/snapshots/uri_py/uri.py index 0d03c20c..d90b0620 100644 --- a/tools/dynwinrt-codegen/tests/snapshots/uri_py/uri.py +++ b/tools/dynwinrt-codegen/tests/snapshots/uri_py/uri.py @@ -11,11 +11,11 @@ from typing import TYPE_CHECKING from uuid import UUID from weakref import ref as _weakref_ref -from dynwinrt_py import ( +from dynwinrt import ( DynWinRTType, DynWinRTMethodSig, DynWinRTValue, DynWinRTArray, DynWinRTStruct, DynWinRtDelegate, DynWinRTOverrideInterface, WinGUID, ) -from dynwinrt_py.dynwinrt_py import ( +from dynwinrt.dynwinrt import ( _dynwinrt_array, _dynwinrt_bind_overload, _dynwinrt_datetime_to_ticks, _dynwinrt_guid, _dynwinrt_map, _dynwinrt_new_vector, _dynwinrt_ticks_to_datetime, _dynwinrt_ticks_to_timedelta, _dynwinrt_timedelta_to_ticks, _dynwinrt_track_projected, _dynwinrt_uuid, _dynwinrt_vector, diff --git a/tools/dynwinrt-codegen/tests/snapshots/uri_py/www_form_url_decoder.py b/tools/dynwinrt-codegen/tests/snapshots/uri_py/www_form_url_decoder.py index e52b0a07..69c49115 100644 --- a/tools/dynwinrt-codegen/tests/snapshots/uri_py/www_form_url_decoder.py +++ b/tools/dynwinrt-codegen/tests/snapshots/uri_py/www_form_url_decoder.py @@ -11,11 +11,11 @@ from typing import TYPE_CHECKING from uuid import UUID from weakref import ref as _weakref_ref -from dynwinrt_py import ( +from dynwinrt import ( DynWinRTType, DynWinRTMethodSig, DynWinRTValue, DynWinRTArray, DynWinRTStruct, DynWinRtDelegate, DynWinRTOverrideInterface, WinGUID, ) -from dynwinrt_py.dynwinrt_py import ( +from dynwinrt.dynwinrt import ( _dynwinrt_array, _dynwinrt_bind_overload, _dynwinrt_datetime_to_ticks, _dynwinrt_guid, _dynwinrt_map, _dynwinrt_new_vector, _dynwinrt_ticks_to_datetime, _dynwinrt_ticks_to_timedelta, _dynwinrt_timedelta_to_ticks, _dynwinrt_track_projected, _dynwinrt_uuid, _dynwinrt_vector, @@ -55,7 +55,7 @@ def _dynwinrt_delegate(value, iid, parameter_types): raise TypeError('delegate value must be callable or a DynWinRTValue') return _dynwinrt_create_delegate(iid, parameter_types, value).to_value() -from dynwinrt_py.dynwinrt_py import _WinRTIterableMixin, _WinRTSequenceMixin +from dynwinrt.dynwinrt import _WinRTIterableMixin, _WinRTSequenceMixin if TYPE_CHECKING: from .i_iterator_i_www_form_url_decoder_entry import IIterator_IWwwFormUrlDecoderEntry # noqa: F401 diff --git a/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/i_iterator_i_www_form_url_decoder_entry.pyi b/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/i_iterator_i_www_form_url_decoder_entry.pyi index ef1ac3a2..f3ebdb17 100644 --- a/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/i_iterator_i_www_form_url_decoder_entry.pyi +++ b/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/i_iterator_i_www_form_url_decoder_entry.pyi @@ -7,7 +7,7 @@ from collections.abc import ( from datetime import datetime, timedelta from uuid import UUID from typing import overload -from dynwinrt_py import ( +from dynwinrt import ( DynWinRTType, DynWinRTValue, DynWinRTArray, DynWinRTStruct, DynWinRtDelegate, WinGUID, ) diff --git a/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/i_stringable.pyi b/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/i_stringable.pyi index 3396f057..a34159e9 100644 --- a/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/i_stringable.pyi +++ b/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/i_stringable.pyi @@ -7,7 +7,7 @@ from collections.abc import ( from datetime import datetime, timedelta from uuid import UUID from typing import overload -from dynwinrt_py import ( +from dynwinrt import ( DynWinRTType, DynWinRTValue, DynWinRTArray, DynWinRTStruct, DynWinRtDelegate, WinGUID, ) diff --git a/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/i_uri_runtime_class_with_absolute_canonical_uri.pyi b/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/i_uri_runtime_class_with_absolute_canonical_uri.pyi index d04cd06a..107ff32c 100644 --- a/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/i_uri_runtime_class_with_absolute_canonical_uri.pyi +++ b/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/i_uri_runtime_class_with_absolute_canonical_uri.pyi @@ -7,7 +7,7 @@ from collections.abc import ( from datetime import datetime, timedelta from uuid import UUID from typing import overload -from dynwinrt_py import ( +from dynwinrt import ( DynWinRTType, DynWinRTValue, DynWinRTArray, DynWinRTStruct, DynWinRtDelegate, WinGUID, ) diff --git a/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/i_www_form_url_decoder_entry.pyi b/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/i_www_form_url_decoder_entry.pyi index 208f7c84..36398607 100644 --- a/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/i_www_form_url_decoder_entry.pyi +++ b/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/i_www_form_url_decoder_entry.pyi @@ -7,7 +7,7 @@ from collections.abc import ( from datetime import datetime, timedelta from uuid import UUID from typing import overload -from dynwinrt_py import ( +from dynwinrt import ( DynWinRTType, DynWinRTValue, DynWinRTArray, DynWinRTStruct, DynWinRtDelegate, WinGUID, ) diff --git a/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/uri.pyi b/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/uri.pyi index e712929f..ac4ddc7c 100644 --- a/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/uri.pyi +++ b/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/uri.pyi @@ -7,7 +7,7 @@ from collections.abc import ( from datetime import datetime, timedelta from uuid import UUID from typing import overload -from dynwinrt_py import ( +from dynwinrt import ( DynWinRTType, DynWinRTValue, DynWinRTArray, DynWinRTStruct, DynWinRtDelegate, WinGUID, ) from typing import Type, TypeVar diff --git a/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/www_form_url_decoder.pyi b/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/www_form_url_decoder.pyi index bb864c39..b79bed46 100644 --- a/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/www_form_url_decoder.pyi +++ b/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/www_form_url_decoder.pyi @@ -7,7 +7,7 @@ from collections.abc import ( from datetime import datetime, timedelta from uuid import UUID from typing import overload -from dynwinrt_py import ( +from dynwinrt import ( DynWinRTType, DynWinRTValue, DynWinRTArray, DynWinRTStruct, DynWinRtDelegate, WinGUID, ) from typing import Type, TypeVar