Repository navigation
Add Python support with DynWinRT bindings and development identity - #1062
leileizhang (lei9444) wants to merge 23 commits into
Conversation
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
The Python test suite fails on Windows environments whose temporary path uses an 8.3 short-path spelling.
1 open finding
What changed in this PR
Adds first-class Python support through winapp-py, combining the native CLI with DynWinRT bindings, dependency restoration, isolated development identity, packaging, samples, and release automation.
Changes:
- Adds the Python wrapper, secure runtime/identity management, and comprehensive tests.
- Extends native project detection and binding generation for Python projects.
- Adds wheels, documentation, samples, CI validation, signing, and PyPI publishing.
Release native build and 177 targeted tests passed. The Python suite exposed one path-normalization test failure on Windows short paths.
| File | Description |
|---|---|
.github/workflows/build-package.yml |
Builds and validates Python wheels. |
.github/workflows/test-samples.yml |
Adds Python sample validation. |
.pipelines/README.md |
Documents wheel signing order. |
.pipelines/release.yml |
Adds gated PyPI publication. |
.pipelines/templates/build-env.yaml |
Configures Python build tooling. |
.pipelines/templates/build.yaml |
Packages and validates signed wheels. |
README.md |
Introduces Python usage. |
docs/guides/python.md |
Adds the Python WinUI guide. |
docs/telemetry.md |
Documents Python project telemetry. |
docs/usage.md |
Documents winapp-py workflows. |
plugins/winapp/com.github.copilot/agents/winapp.agent.md |
Teaches the agent Python workflows. |
plugins/winapp/skills/winapp-frameworks/SKILL.md |
Adds Python framework guidance. |
samples/python-app/.gitignore |
Excludes generated Python artifacts. |
samples/python-app/README.md |
Documents the counter sample. |
samples/python-app/app.py |
Adds the sample entry point. |
samples/python-app/counter.py |
Implements persisted counter state. |
samples/python-app/pyproject.toml |
Declares sample metadata and bindings. |
samples/python-app/runtime.py |
Initializes Windows App SDK runtime. |
samples/python-app/test.Tests.ps1 |
Tests the guide and packaged sample. |
samples/python-app/test_app.py |
Adds sample unit tests. |
samples/python-app/window.py |
Implements the WinUI window. |
scripts/package-python.ps1 |
Builds architecture-specific wheels. |
scripts/test-python.ps1 |
Validates installed-wheel workflows. |
scripts/test-samples.ps1 |
Supports Python sample artifacts. |
scripts/tests/artifact-workflow.Tests.ps1 |
Tests pipeline artifact integration. |
scripts/tests/package-python.Tests.ps1 |
Tests wheel packaging behavior. |
scripts/tests/verify-signatures.Tests.ps1 |
Tests wheel signature inspection. |
scripts/verify-signatures.ps1 |
Verifies binaries embedded in wheels. |
src/winapp-CLI/Directory.Packages.props |
Adds the TOML parser dependency. |
src/winapp-CLI/WinApp.Cli.Tests/CppWinrtServiceTests.cs |
Tests Python binding asset copying. |
src/winapp-CLI/WinApp.Cli.Tests/DetectedProjectTests.cs |
Tests Python project results. |
src/winapp-CLI/WinApp.Cli.Tests/IncrementalCopyHelperTests.cs |
Tests safe incremental copies. |
src/winapp-CLI/WinApp.Cli.Tests/InitCommandPythonContextTests.cs |
Tests Python initialization context. |
src/winapp-CLI/WinApp.Cli.Tests/InitCommandTests.cs |
Extends initialization coverage. |
src/winapp-CLI/WinApp.Cli.Tests/ProjectContextDetectorTests.cs |
Tests Python context detection. |
src/winapp-CLI/WinApp.Cli.Tests/ProjectDetectionServiceAccessDeniedTests.cs |
Tests inaccessible Python metadata. |
src/winapp-CLI/WinApp.Cli.Tests/ProjectDetectionServiceTests.cs |
Tests Python project markers. |
src/winapp-CLI/WinApp.Cli.Tests/TelemetryEventsTests.cs |
Tests Python telemetry fields. |
src/winapp-CLI/WinApp.Cli/Commands/InitCommand.cs |
Reports machine-readable initialization results. |
src/winapp-CLI/WinApp.Cli/Services/CppWinrtService.cs |
Supports Python binding outputs. |
src/winapp-CLI/WinApp.Cli/Services/DetectedProject.cs |
Represents detected Python projects. |
src/winapp-CLI/WinApp.Cli/Services/DetectedProjectType.cs |
Adds the Python project type. |
src/winapp-CLI/WinApp.Cli/Services/IncrementalCopyHelper.cs |
Safely copies binding assets. |
src/winapp-CLI/WinApp.Cli/Services/ProjectContext.cs |
Carries Python project context. |
src/winapp-CLI/WinApp.Cli/Services/ProjectContextDetector.cs |
Detects Python metadata. |
src/winapp-CLI/WinApp.Cli/Services/ProjectDetectionService.cs |
Integrates Python project discovery. |
src/winapp-CLI/WinApp.Cli/Services/PythonProjectMetadata.cs |
Parses Python project metadata safely. |
src/winapp-CLI/WinApp.Cli/Services/WorkspaceSetupService.cs |
Prepares Python binding workspaces. |
src/winapp-CLI/WinApp.Cli/Telemetry/Events/ProjectContextEvent.cs |
Adds Python telemetry classification. |
src/winapp-CLI/WinApp.Cli/WinApp.Cli.csproj |
References TOML parsing support. |
src/winapp-python/.gitignore |
Excludes Python build artifacts. |
src/winapp-python/LICENSE |
Includes package licensing. |
src/winapp-python/README.md |
Documents the Python package. |
src/winapp-python/docs/development.md |
Documents contributor workflows. |
src/winapp-python/pyproject.toml |
Defines package metadata and entry points. |
src/winapp-python/setup.py |
Builds platform-specific wheels. |
src/winapp-python/src/winapp_cli/__init__.py |
Exposes package metadata. |
src/winapp-python/src/winapp_cli/__main__.py |
Supports module execution. |
src/winapp-python/src/winapp_cli/cli.py |
Implements winapp-py commands. |
src/winapp-python/src/winapp_cli/config.py |
Parses Python binding configuration. |
src/winapp-python/src/winapp_cli/dependencies.py |
Restores project dependencies. |
src/winapp-python/src/winapp_cli/errors.py |
Defines wrapper errors. |
src/winapp-python/src/winapp_cli/generate.py |
Generates and publishes bindings. |
src/winapp-python/src/winapp_cli/identity.py |
Manages development identity execution. |
src/winapp-python/src/winapp_cli/identity_environment.py |
Prepares isolated identity dependencies. |
src/winapp-python/src/winapp_cli/metadata.py |
Reads native package inventories. |
src/winapp-python/src/winapp_cli/native.py |
Locates and validates bundled CLI binaries. |
src/winapp-python/src/winapp_cli/paths.py |
Enforces safe project paths. |
src/winapp-python/src/winapp_cli/progress.py |
Provides terminal progress reporting. |
src/winapp-python/src/winapp_cli/python_runtime.py |
Downloads and extracts private runtimes. |
src/winapp-python/src/winapp_cli/tools.py |
Runs native and Python tools. |
src/winapp-python/tests/fixtures/appsdk-nonui/app.py |
Adds a non-UI fixture application. |
src/winapp-python/tests/fixtures/appsdk-nonui/pyproject.toml |
Configures the non-UI fixture. |
src/winapp-python/tests/fixtures/appsdk-nonui/winapp.yaml |
Pins fixture SDK packages. |
src/winapp-python/tests/fixtures/uri/app.py |
Adds a bindings fixture application. |
src/winapp-python/tests/fixtures/uri/identity_probe.py |
Probes development identity. |
src/winapp-python/tests/fixtures/uri/pyproject.toml |
Configures URI bindings. |
src/winapp-python/tests/fixtures/uri/requirements-dev.txt |
Declares fixture development dependencies. |
src/winapp-python/tests/fixtures/uri/requirements.txt |
Declares fixture dependencies. |
src/winapp-python/tests/fixtures/uri/winapp.yaml |
Pins fixture SDK packages. |
src/winapp-python/tests/helpers.py |
Provides shared Python test fixtures. |
src/winapp-python/tests/test_cli_tools.py |
Tests command execution helpers. |
src/winapp-python/tests/test_config_metadata.py |
Tests configuration and inventory parsing. |
src/winapp-python/tests/test_dependencies.py |
Tests dependency synchronization. |
src/winapp-python/tests/test_generate.py |
Tests binding generation and publication. |
src/winapp-python/tests/test_help.py |
Tests CLI help and version output. |
src/winapp-python/tests/test_identity.py |
Tests identity lifecycle and contention. |
src/winapp-python/tests/test_identity_environment.py |
Tests isolated dependency preparation. |
src/winapp-python/tests/test_init.py |
Tests Python initialization. |
src/winapp-python/tests/test_native.py |
Tests bundled native payload handling. |
src/winapp-python/tests/test_progress.py |
Tests terminal progress behavior. |
src/winapp-python/tests/test_python_runtime.py |
Tests runtime selection and extraction. |
🧠 Review effort: Balanced
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Build Metrics ReportValidation passed. All required build and validation jobs succeeded. Binary Sizes
.NET Test Results (TRX reports)Other suites are reflected in the overall validation status above. ✅ 10669 passed, 43 skipped out of 10712 tests in 2189.1s (+5 tests, -395.2s vs. baseline) Test Coverage✅ 87.4% line coverage, 81.5% branch coverage · ✅ no change vs. baseline CLI Startup Time78ms median (x64, Updated 2026-10-23 11:48:24 UTC · commit |
Nikola Metulev (nmetulev)
left a comment
There was a problem hiding this comment.
🤖 AI-generated review (winappcli pr-review skill) — verify before acting.
Decision: changes required
Python support fits winapp, and a PyPI wheel as a peer to the npm package is the right shape. The core flows work end to end against this PR's own native build:
init --add-python-bindingssucceeds.runworks.run --identityworks:GetCurrentPackageFullNamesucceeded and the script's exit code came through. Cold start took 79 s, a warm run 6 s.identity clearunregistered cleanly.
Before merge:
- CI needs to go green.
- Four bugs need fixing; each breaks user config, user files, or script output.
restoreneeds to stop pulling an unpinned generator.- The native CLI changes should shrink.
- The names should match our other channels.
- The guide should follow the same journey as the other framework guides.
Must fix
1. CI is red because of this PR
validate-tests (Auxiliary):docs/guides/python.mdopts into MS Learn publishing but isn't in the generated TOC. Add it to$TocLabelsand the framework-guides$tocTreeinscripts/port-mslearn-docs.ps1.validate-python:test_native.test_bundled_path_is_absolute_and_matches_python_platformcomparesC:/Users/runneradmin/...againstC:/Users/RUNNER~1/.... Resolve the expected path before comparing.- Fails locally, not in CI:
ProjectContextDetectorTests.DetectDirectory_Python_UsesOnlyRecognizedProjectMetadatawalks above%TEMP%and picks up project markers in a parent folder. Add a.gitboundary to the fixture.
2. restore installs and runs the newest prerelease generator every time
- What is wrong:
restore,generateandupdateall runpip install --upgrade --force-reinstall --pre dynwinrt-codegenand then execute it.config.pyrejects any attempt to pin a version. - Show me:
winapp-py restore --verboseprints "Uninstalling dynwinrt-codegen-0.1.0rc23 … Successfully installed 0.1.0rc23" on every run. It will pick up rc24 the day it ships, and upstream preview.23 already changed the Python typing/write contracts. - Why it matters:
- Clones and CI get different bindings without any project change.
- A release published after ours gets executed on a routine restore.
- Restore always needs network access.
- Smallest fix:
init/updaterecord the resolved version in winapp-managed project data.restore/generateinstall==<version>. Onlyupdateadvances it. Apps still don't need to declare the generator or runtime themselves. - Location:
src/winapp-python/src/winapp_cli/dependencies.py:454-476,cli.py:196-220,config.py:132-139
3. init --use-defaults erases existing bindings settings
- Show me: start with
scope = "types",types = ["Windows.Foundation.Uri"], then runwinapp-py init . --use-defaults --config-only. Result:scope = "default",types = [], exit code 0. - Smallest fix: keep existing settings under
--use-defaults, and only reset them in the explicit reset branch. - Location:
cli.py:278-289,config.py:169-177
4. init can permanently break the user's requirements files
- Show me: if
requirements.txtcontains-r requirements-dev.txt,initappends-r requirements.txtto the dev file. pip then fails with "recursively references itself", and the edit stays on disk. - Smallest fix: skip the reverse include when the dev file is already reachable from the app requirements.
- Location:
dependencies.py:309-316
5. run writes its own status lines into the script's stdout
- Show me: after
winapp-py run app.py > out.txt,out.txtcontainsUsing project .venv: …before the script's own output. The identity path's progress lines andRunning x.py…also go to stdout. - Why it matters: a script that emits JSON or text for piping becomes unparseable.
- Smallest fix: send wrapper status to stderr, or print it only under
--verbose. - Location:
cli.py:177,identity.py:422,510
6. Keep the native CLI changes to what every wrapper needs
- Remove the
WINAPP_PY_INIT_RESULTside channel (InitCommand.WritePythonInitResultplusWorkspaceSetupService.MetadataDirectory).- It's a hidden contract between native and the wheel that only the wheel uses.
- npm gets the same information with no native changes: it works out the selected workspace and init mode from its own arguments plus
winapp.yamland.winapp/winmds.lock.json(seesrc/winapp-npm/src/jsbindings/cli-hooks.ts). The wheel can do the same.
- Drop Python from native project detection, but keep the
.venv/pyvenv.cfgskip.- Nothing in native behaves differently for a Python project. Detection only changes the init label, telemetry, and where the subdirectory search stops.
- To get that, it adds a third-party TOML parser (Tomlyn) to the native binary. For comparison, Electron detection uses the built-in JSON parser, and Rust detection only checks whether
Cargo.tomlexists. - It also causes a regression. At a repo root whose
pyproject.tomlhas a name, interactivewinapp initstops searching and no longer offers a nested.csprojor Electron app. - If the "Python project detected" label is wanted, check that the marker file exists, without parsing it and without stopping the search.
7. Match our other channels' naming
- Today:
- winget:
Microsoft.winappcli→winapp - npm:
@microsoft/winappcli→winapp - this PR:
winapp-cli→winapp-py
- winget:
- Proposal: distribution
winappcli, console scriptwinapp, import packagewinappcli.- Tools on PyPI that bundle a native CLI install the real command name (
cmake,ninja,ruff). - npm's
winappalready layers extra behavior over the native commands, and an active venv puts its ownwinappfirst on PATH. - It also fixes forwarded help, which already prints
Usage: winapp.
- Tools on PyPI that bundle a native CLI install the real command name (
- Nothing has been published yet, so rename outright with no
winapp-pyalias. - Reserve the PyPI name and confirm ESRP can publish to it before docs advertise
pip install. All the candidate names currently return 404 on PyPI.
8. The guide should follow the same journey as the other framework guides
-
What is wrong:
docs/guides/python.mdis built around a 130-line code-built WinUI picker run withrun --identity. Nothing in it needs identity.- The guide itself says the picker doesn't.
- Identity is only being used to make the Windows App Runtime available, and the counter sample does that with the bootstrapper instead.
-
Why it matters:
- It teaches that WinUI needs identity, which isn't true.
- It routes newcomers through the slowest path first: a private Python runtime download (about 72 MB) and a 79 s cold start.
- It leads with WinUI hosting, which upstream DynWinRT calls experimental.
- The demand in #117 and #61 is scripts and services that need an identity-gated API.
-
Smallest fix: mirror the Rust guide.
- A minimal
app.pythat printsPackage.current.id.family_name. - A plain run prints "Not packaged".
winapp-py run --identity app.pyprints the family name.identity clear.- Package.
The non-UI identity probe in
tests/fixtures/uri/is a good starting point. Move the WinUI picker to the sample or a follow-up section. - A minimal
-
While there:
- Use
mkdir/cdlike every other guide, instead ofNew-Item/Set-Location. - Drop the PEP 440 version-format tip; it's implementation detail.
- Don't have users hand-write the bindings table that
initcreates.
- Use
Non-blocking
-
--quiet/--jsonbehave differently depending on where they're placed.winapp-py restore --quietexits with code 2.winapp-py --quiet restoreis forwarded to native and silently skips the bindings restore.run --json x.pygoes to native, whilerun x.py --jsonstays in Python.
Normalize shared flags before dispatch, and keep script arguments after
--. -
The identity runtime trusts a catalog on astral-sh/uv
main. The download URL and its SHA-256 both come fromraw.githubusercontent.com/astral-sh/uv/main/.... Pin it to a commit, or vendor the hashes per release. Each project also downloads its own copy of the runtime (about 72 MB measured). -
A failed codegen during
initleaves a dependency on a missing package. Dependency edits are written before codegen and never rolled back. Write them after generation succeeds (cli.py:234-248).
Not verified
- ARM64, and x64 Python emulated on ARM64.
- Building and signing the wheel, and the ESRP/PyPI publish.
- The interactive monorepo
winapp initregression: found by reading the code, not reproduced.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Keep shared output options on Python workflows and register generated dependencies only after successful generation. Read identity CI diagnostics from stderr and isolate metadata test fixtures from parent projects. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Replace native TOML parsing with marker-only Python hints that do not prune nested projects. Remove the Python-specific initialization result channel and confirm SDK artifact targets in the Python wrapper. Update related documentation and regression coverage. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Nikola Metulev (nmetulev)
left a comment
There was a problem hiding this comment.
Thanks for the updates. All 5 must-fixes from the last round are fixed, and I re-checked each one:
restoreinstalls the pinnedcodegen-version.init --use-defaultskeeps an existing bindings selection.- An existing
-r requirements-dev.txtno longer causes a recursive include. runkeeps wrapper status off the script's stdout.- CI is green.
The flag-placement, codegen-failure rollback and nested-project detection items are fixed too. Two things remain, plus merge conflicts.
1. The identity runtime catalog pin is written into the user's project and needs the GitHub API
- What happens: the first
winapp-py run --identitywrites[tool.winapp.python-identity] uv-catalog-versioninto the user'spyproject.toml("Pinned uv PBS catalog 0.13.1 in pyproject.toml"). After that, every fresh preparation calls the unauthenticatedapi.github.com/repos/astral-sh/uv/releasesto check the release (identity.py:430-440,python_runtime.py:172-188). The local.winapprecord isn't committed, so this repeats on every clone and CI job. - Why it matters:
- A
runcommand edits a tracked file. - The identity path depends on a rate-limited API (60 requests per hour per IP), which shared CI runners can hit.
- Once this ships, the setting becomes user configuration we can't remove without breaking projects.
- A
- Smallest fix: ship one pin per release as a constant in the wheel: a catalog URL at a fixed commit plus the catalog's SHA-256, or vendored hashes. That removes the setting, the API call, the
pyproject.tomlwrite, the schema bump and the docs section.
2. Native project detection crashes on a folder it can't list
- What happens: the new
requirements.txtcheck inProjectDetectionService.cs:171-176callsEnumerateFiles("*.py")without catchingUnauthorizedAccessException/IOException. The other detection helpers in that file do catch them. - Repro: a folder with
requirements.txt+a.py, thenicacls <folder> /deny <me>:(RD), thenwinapp init .\locked --use-defaults --setup-sdks none→ unhandledUnauthorizedAccessException, exit 1. The interactive search from a parent folder takes the same path for any such subfolder, so this also affects non-Python users. - Smallest fix: wrap the enumeration the way the neighboring helpers do, treat the folder as not detected, and add a case to
ProjectDetectionServiceAccessDeniedTests.
3. Merge conflicts with main
GitHub reports conflicts in CustomHelpAction.cs, CompactHelpRenderer.cs, build-package.yml and artifact-workflow.Tests.ps1.
After these, I think this is close. I'd rather not do more rounds on medium items.
Publish as microsoft-winappcli with the winapp module and command, and remove Python-specific native help name rewriting. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Keep both Python and DevTools validation gates and require both before publishing successful baseline metrics. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Remove native Python project detection while keeping virtual environment exclusions. Keep C# initialization results when Python bindings are unsupported. Preserve uv dependency conditions, reject mismatched bindings, avoid implicit uv lookup, and leave unchanged requirements untouched. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Compare expected executable paths after resolving Windows short names, without changing uv lookup or weakening the exact-path assertions. Clarify that PBS downloads do not require uv, but uv-managed dependency restoration does. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Restore native sources and tests to main and keep the WinUI sample focused on the counter. The C++/WinRT response-file fix and space-path regression are tracked separately in #1174; Python workflow fixtures use ordinary directory names. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

What and why
Add
microsoft-winappcli, a Python package that includes the native Windows App Development CLI for x64 and ARM64. It provides thewinappcommand andpython -m winappclientry point. Python projects can generate bindings for Windows Runtime (WinRT) APIs, restore their declared dependencies, and run scripts with Windows package identity when an API requires an installed-app registration.Ordinary
runuses the project's existing.venv.run --identityprepares a separate Python runtime and environment without changing the ordinary.venvor a shared Python installation. This is a development workflow; distributing an application still requires packaging its runtime and dependencies.After installing this PR's wheel, use these commands from a Python project:
Once published, install with
python -m pip install --upgrade microsoft-winappcli. The Python identity sample covers console execution and MSIX packaging. The separate Python WinUI sample provides an unpackaged counter without development identity.Related issue: N/A.
Before / after
Same command and script, with a project
.venvavailable.app.pycontainsprint("Hello from Python"):winapp run -q .\app.pyBefore — native CLI from main: Python files are not runnable inputs.
After — installed Python package: the script runs with the project interpreter.
Not tested / risks
microsoft-winappclistill needs confirmation.Checklist
plugins/winapp/skills/updated if user-facing behavior changedNotes for reviewers
Native winapp continues to own SDK setup, manifests, signing and MSIX packaging; its source and tests are unchanged in this PR. Python adds bindings and dependency handling.
Initialization records the generator version in
pyproject.toml;restoreandgeneratereuse it, whileupdateadvances it. The private Python runtime is selected from a download catalog pinned by the installed tool, not from a project-level catalog setting.