Skip to content

PyWinRT 4.0

PyWinRT 4.0 changes how the projection is built and published more than any release before it, and changes less about the Python you write than that might suggest. A projection package is no longer compiled code, each package is versioned after the upstream release it projects, and the Windows App SDK and WebView2 packages are back under winrt. This page describes those changes and the others that a user of 3.x will meet. Each package's own changelog, linked from PyPI, lists what changed in that package one line at a time.

Coming from 3.x, start with Moving from 3.x.

Projection packages are data

A projection package such as winrt-Windows.Storage no longer contains a compiled extension module. It is one py3-none-any wheel holding the type stubs, a small __init__.py and a table that describes the namespace: its types, their members and the call that each member makes. winrt-runtime reads the table and makes the calls, so it and the interop packages are the only parts of PyWinRT that are still compiled.

What this means in practice:

  • One wheel serves every supported version of Python and every architecture, so a new version of Python works with the projection packages as soon as winrt-runtime has wheels for it.
  • Installing a package from its source distribution needs neither a C++ compiler nor the Windows SDK.
  • The 349 projection packages are 6.7 MB of wheels in all. The wheels of v3.2.1 were 49.2 MB for each version of Python and each architecture.
  • A call into WinRT costs a little more. When the table projection was measured against v3.2.1 on one machine, most kinds of member cost 10 to 45 % more per call, which is tens of nanoseconds on calls that took 60 to 400 ns. Passing a dict where an IMap is expected and calling get() on an async operation that has already finished cost about twice as much. What the member itself does, such as reaching a device, a file or the display, still takes far longer. The table projection has had one round of tuning so far.
  • A frozen application has to carry each namespace's table, the _table.pywinrt beside its __init__.py. winrt-runtime ships a PyInstaller hook that does this, and Freezing an application gives the options for cx_Freeze and Nuitka.

Versioning

A projection package is versioned by the upstream release it projects, rather than by the version of the code generator that read it. winrt-Windows.Storage is 4!10.0.28000.2705, the version of the Windows SDK it was generated from, and the Windows App SDK, WinUI 2 and WebView2 packages are numbered after their own releases in the same way. The 4! is a PEP 440 epoch. It says which generation of PyWinRT a package belongs to and makes the new numbers sort above every 3.x release.

The packages written by hand, winrt-runtime, winrt-table-compiler and the Windows SDK interop packages, are versioned on their own from 4.0.0 and released when they change. The two Windows App SDK interop packages are released with the App SDK packages and carry their version.

A package requires winrt-runtime>=4.0.0,<5 rather than one ~= release of it, and every other package it names with a floor, so pip is free to install newer ones. The exception is the Windows App SDK, which Microsoft releases as one set and PyWinRT publishes as one package per component: a component requires the other components whose types it hands back with ~=, so that all of them come from the same release. Versioning explains the scheme, the upper bounds and how to pin.

Package names

The Windows App SDK and WebView2 packages carry the winrt- prefix again and are imported from the winrt top-level package, as they were before v3.0:

  • winui3.microsoft.ui.xaml is winrt.microsoft.ui.xaml, and every other winui3. module likewise.
  • webview2.microsoft.web.webview2.core is winrt.microsoft.web.webview2.core, and its distribution is winrt-Microsoft.Web.WebView2.
  • The Windows App SDK is published as 7 packages, one per NuGet component such as winrt-Microsoft.WindowsAppSDK.WinUI, rather than one per namespace.
  • The App SDK's bootstrapper is winrt-Microsoft.WindowsAppRuntime.Bootstrap, named after the .dll it carries. Its module is still winrt.microsoft.windows.applicationmodel.dynamicdependency.bootstrap.
  • WinUI 2 is one package, winui2-Microsoft.UI.Xaml, which keeps its own prefix and top-level package, since it defines the same namespaces as the App SDK's WinUI.
  • winrt-Microsoft.Web.WebView2.Dll is new. It redistributes the .dll that the WebView2 projection needs, and that projection requires it.

Which module a namespace is imported from changes only for the two renamed prefixes. What changes for the regrouped families is which package to install. The v3 to v4 migration guide has the full tables, and its script reports what in your code and your requirements needs to change.

An overloaded method has one name again and is called by the number of arguments, where v3.x gave each overload a name of its own. In the Windows SDK packages the v3.x names still work and raise a DeprecationWarning. In the others they are gone, along with the prefixes.

The runtime

  • Wheels are published for Python 3.11 to 3.15. Python 3.9 and 3.10 are no longer supported.
  • Wheels are published for the free-threaded Python 3.14t and 3.15t, which keep the GIL disabled. Threads and apartments says what to watch for.
  • The runtime can no longer be imported into a subinterpreter. It raises ImportError, since a callback from WinRT reaches the main interpreter only.
  • winrt-runtime no longer ships MSVCP140.dll, and nothing in PyWinRT needs the Visual C++ redistributable.
  • An interop module reaches the runtime through the Python functions of winrt.runtime.interop rather than a C API, so a module of your own can use them the same way. winrt-sdk, which shipped the C++ headers that a compiled module used to need, is no longer published.
  • A member is no longer checked against ApiInformation before every call. A member that this version of Windows lacks fails when it is called, with an AttributeError that names it.
  • Several conversions raise the exception that Python itself would: ValueError for a negative unsigned integer or a character a Char16 cannot hold, ModuleNotFoundError for a type whose package is not installed, and TypeError for an async def handler or for None as an event handler.

The Python surface

  • winrt.system.Array is a Sequence whose items can be assigned. It has no insert() or del, a slice is a new Array, two arrays compare by value, and its repr() shows its items. It takes the type of its elements, such as Int32 or bool, in place of a format string, which is deprecated. See Arrays.
  • A projected vector counts a negative index from the end and raises IndexError out of range, and its slices with a step of 1 can be assigned and deleted. A projected map compares by content, so it is not hashable, like a dict.
  • Structs are hashable by their fields and have __match_args__, so a class pattern matches one by position.
  • Calling an interface type such as IStringable() raises TypeError. isinstance() with an interface asks the object for it, as as_() does, and issubclass() answers for a projected class and the interfaces it implements.
  • Type._from(obj) is deprecated and warns. Write obj.as_(Type) instead. See Query interface.
  • HResult and EventRegistrationToken are subclasses of int. Their value field is deprecated. See Error codes and event tokens.
  • The structs of Windows.Foundation.Numerics export a buffer that NumPy reads without a copy, and @ is the matrix product. Matrix * Matrix raises TypeError. See Numerics.
  • An IMemoryBuffer supports the buffer protocol, and len() of an IBuffer is its length. A buffer that WinRT writes into must be writable, so a bytes is refused there. See Buffers.
  • Importing Buffer from winrt.system is deprecated and warns. Use collections.abc.Buffer, or ReadableBuffer or WriteableBuffer to say whether WinRT reads the buffer or writes into it. See Buffer.
  • Awaiting an async operation that has finished returns its result again, and an error from an operation that no task waits for any more is reported to the event loop. See Awaitables.
  • A Python class can derive from a composable class with protected constructors, such as Panel, and its protected and overridable members are projected with a leading underscore. A Python implementation of an interface is a winrt.system.Object. See Implementing an interface.
  • A Python subclass of a composable class derives from one class, as in C#, and can add only WinRT interfaces as further bases. A mixin, a second runtime class or typing.Generic as a base raises TypeError. See Objects.

Type hints

The stubs use modern annotation syntax and the conventions type checkers expect of a stub. The projected collections derive from their collections.abc counterparts, winrt.system.Object is the base of every interface, members that WinRT marks deprecated carry @deprecated, and more return types include None, mostly in Windows.Storage and the Bluetooth namespaces. Constructors and many functions are positional-only, an integer parameter takes any typing.SupportsIndex, and an IBuffer parameter is typed as ReadableBuffer or WriteableBuffer. Code that type-checked against 3.x may need a narrowing or a cast here and there.

Projecting your own components

The PyWinRT tool on NuGet writes a table and type stubs instead of C++, so projecting a component of your own needs no compiler, and the package it makes is built with winrt-table-compiler. The tool needs the .NET 10 runtime and ships the ABI call shape census of the winrt-runtime it goes with. A component whose members need a call shape the census lacks is reported when it is generated. Projections of your own components describes the packaging.

Moving from 3.x

A 3.x projection package cannot be loaded by the 4.0 runtime, so upgrade every package together. After pip install -U winrt-runtime on its own, importing a 3.x package fails with:

AttributeError: module 'winrt._winrt' has no attribute '_C_API'

The v3 to v4 migration guide lists the renamed and regrouped packages and the code changes, and the script beside it finds them in your code. Then run a type checker over your code with the 4.0 stubs, which reports most of what is left. Versioning says more about the two generations.