The WinRT type system

PyWinRT is a “projection” that uses the Windows Runtime (WinRT) type system to automatically generate Python bindings for the Windows SDK. This page describes how the WinRT types look and feel in this Python projection.

Naming conventions

In the WinRT documentation, most names use PascalCasing in the style of .NET. In the Python projection, names are adapted to fit PEP8 naming conventions.

  • Type names remain as CapitalizedWords.

  • Constants, such as enum members are converted to UPPER_CASE_WITH_UNDERSCORES.

  • Namespaces are converted to lowercasewithoutunderscores.

  • All other identifiers are converted to lower_case_with_underscores (fields, properties, methods, events, etc.).

  • If an identifier is a Python keyword, an underscore is appended to the name.

Distribution packages

Each WinRT namespace is projected as a separate Python package. The packages are named like winrt-Windows.Foundation where winrt is the root namespace for a specific SDK or DLL and Windows.Foundation is a WinRT namespace within that SDK or DLL.

There are also some PyWinRT-specific sub-packages distributed in the winrt-runtime package. As well as some extra interop packages that bridge between WinRT and Win32 types.

See also

API reference

Namespaces

Each Windows SDK namespace is projected as a Python package and follows the usual Python import conventions:

# import members of a namespace
from winrt.windows.foundation import Uri
# or import the namespace module itself
import winrt.windows.foundation as wf

As with the distribution package names, the WinRT namespace names have a root namespace that matches the part of the package name before the - followed by the namespace. The modules names are lowercasewithoutunderscores, so namespaces with multiple words in one segment, like Windows.AI.MachineLearning are projected as winrt.windows.ai.machinelearning.

Fundamental types

The WinRT type system defines the following fundamental types which are mapped to the corresponding Python builtin types.

WinRT type

Python type

Python format string

Description

Boolean

bool

"?"

an 8-bit Boolean value

Int8

int

"b"

an 8-bit signed integer

Int16

int

"h"

a 16-bit signed integer

Int32

int

"i"

a 32-bit signed integer

Int64

int

"q"

a 64-bit signed integer

UInt8

int

"B"

an 8-bit unsigned integer

UInt16

int

"H"

a 16-bit unsigned integer

UInt32

int

"I"

a 32-bit unsigned integer

UInt64

int

"Q"

a 64-bit unsigned integer

Single

float

"f"

a 32-bit IEEE 754 floating point number

Double

float

"d"

a 64-bit IEEE 754 floating point number

Char16

str [1]

"u" [2]

a 16-bit non-numeric value representing a UTF-16 code unit

String

str

"P"

an immutable sequence of Char16 used to represent text

Guid

uuid.UUID

"T{I2H8B}" [3]

a 128-bit standard globally unique identifier

The Python format strings use the syntax defined in the struct module and the PEP 3118 additions and are used as the format for the buffer protocol for arrays of these types. These format strings can be read at runtime by using memoryview.format.

Enums

WinRT enums are projected using the Python standard library enum module. The WinRT Type system has a [Flags] attribute that indicates if an enum is treated as bit flags or not. Enum types without this attribute are projected as an enum.IntEnum type or if the [Flags] attribute is present, the type is projected as an enum.IntFlag type.

For arrays and buffers containing enums, use the "i" format string for regular enums and "I" for flags.

Structs

Structs are simple data types that are passed by value in WinRT. In the Python projection, each struct is wrapped in a Python object that is similar to a typing.NamedTuple or frozen dataclasses. The names of fields are converted to use the lower_case_with_underscores naming convention.

Projected structs are @typing.final classes, so they cannot be subclassed. Projected structs are immutable. To modify a struct, use copy.replace() which creates a modified copy.

As a convenience, plain tuple objects can be used in place of projected structs when calling methods that expect a projected struct or when setting properties that expect a projected struct.

Each projected struct with more than one field also has a unpack() method the converts the projected struct to a tuple.

Example:

# window.position is a winrt.windows.graphics.PointInt32

# fails with AttributeError because Point is immutable
window.position.x = 200

# modifying
new = copy.replace(window.position, x=200)

# passing a tuple
window.move_and_resize((100, 100, 800, 600))

# unpacking
x, y = window.position.unpack()

Changed in version 2.2: Changed __repr__ implementation to give a representation that can be passed to eval().

Changed in version 3.0:

  • Structs are now immutable. In previous versions, attributes could be set.

  • Added __replace__ method to allow for use with copy.replace().

  • Added unpack() method to convert to a tuple. Added support for using plain tuples in place of projected structs as method arguments.

Objects

Todo

explain objects aka runtime classes

Methods

Properties

Properties are projected as Python descriptor-like attributes. Properties with WinRT getter support getting and properties with a WinRT setter allow setting. Deleting properties is never allowed.

Names of properties are converted to use the lower_case_with_underscores naming convention.

Example:

from winrt.windows.foundation import Uri

uri = Uri("https://example.com")
print(uri.scheme_name)

Static properties are implemented as class attributes via a metaclass, so are accessed by using the type object rather than an instance object:

from winrt.windows.foundation import GuidHelper

empty_uuid = GuidHelper.empty

Changed in version v1.0.0b8: Previous beta releases implemented static properties as get_name() and put_name() static methods instead of class attributes.

Events

Interfaces

Todo

explain interfaces

Delegates

Todo

explain delegates

Todo

explain exceptions in callbacks

Arrays

Todo

document array types

Exceptions

PyWinRT uses the CppWinRT projection under the hood. Any C++ exception that is not handled gets propagated to Python as an OSError exception with OSError.winerror set to an HRESULT error code.

See also

winrt.system.hresult for some common error codes.

On the other hand, Python exceptions cannot be propagated to C++ code. WinRT requires that errors are serializable, but Python exceptions are not. So if a Python callback from C++ code (i.e. an event handler or other delegate or a method of a Python subclass of a WinRT interface) raises an exception that isn’t handled before the method returns, it will trigger the sys.unraisablehook() handler in Python and cause the C++ code to receive a an HRESULT error code of PYWINRT_E_UNRAISABLE_PYTHON_EXCEPTION. This can cause undefined behavior in the C++ code in some cases, so it should be avoided.

Specialized types

Some types have special handling and don’t strictly follow the patterns described above.

Awaitables

WinRT has many _async methods that that perform background operations and return a type that derives from Windows.Foundation.IAsyncInfo to be able to wait for the result. There are four fundamental types that derive from this:

Synchronous usage

PyWinRT exposes the CppWinRT extension methods for calling these methods synchronously (i.e. when not using asyncio).

IAsyncAction.get(self) None
IAsyncOperation.get(self) TResult
IAsyncActionWithProgress.get(self) None
IAsyncOperationWithProgress.get(self) TResult

These methods block until the operation is complete.

Returns:

The result of the operation.

Raises:

OSError if the operation failed or was canceled. RuntimeError if called from a single-threaded apartment (i.e. a GUI thread).

Warning

This method can’t be interrupted by CTRL+C which means that a KeyboardInterrupt will not be raised until the operation is complete.

Added in version 3.2.

IAsyncAction.wait(self, timeout: float) AsyncStatus
IAsyncOperation.wait(self, timeout: float) AsyncStatus
IAsyncActionWithProgress.wait(self, timeout: float) AsyncStatus
IAsyncOperationWithProgress.wait(self, timeout: float) AsyncStatus

These methods block until the operation is complete or the timeout is reached, whichever comes first.

Args:

timeout: The timeout in seconds.

Returns:

The status of the operation. In case of a timeout, the status will be AsyncStatus.STARTED.

Raises:

RuntimeError if called from a single-threaded apartment (i.e. a GUI thread).

Warning

This method can’t be interrupted by CTRL+C, which means a KeyboardInterrupt will not be raised until the operation is complete or the timeout is reached.

Added in version 3.2.

Asynchronous usage

Note

WinRT async methods look like Python coroutines (methods defined with async def) but they are not. This means they do not return a Coroutine object and therefore cannot be used with methods like asyncio.create_task(). They are only Awaitable objects.

If you are using asyncio, then you can use the await keyword to wait for the result of async WinRT methods:

thing = await winrt_obj.get_thing_async()

Changed in version 3.2: If the Awaitable that wraps the operation is canceled, it will now propagate the cancellation to the WinRT async action/operation. To restore the previous behavior, you can wrap the operation in asyncio.shield().

Other usage

If you can’t use either the synchronous helpers or asyncio (i.e. a GUI app that doesn’t use asyncio), then you can use the completed property to set a callback that will be called when the operation is complete. In GUI apps where the async method is called from the main thread (that is initialized as a single- threaded apartment), the callback will occur on the main thread. Otherwise, the callback will be called on a different thread.

You must also be careful about not creating a reference cycle to the operation, otherwise it will cause a memory leak. This can happen if the callback is a closure and references an object that references the operation itself.

Todo

add tips on how to iterate over progress events

Buffers

Windows.Storage.Streams.IBuffer is projected as winrt.system.Buffer, which is an alias for collections.abc.Buffer. When used as a method parameter, any Python object that implements the buffer protocol can be used, for example, a bytearray, bytes, or a memoryview. Although care must be taken to ensure that immutable types like bytes are not used when the WinRT API expects a writeable buffer! The WinRT type system does not distinguish between read-only and writeable buffers, so there isn’t a way to enforce this at runtime.

Buffers received as a return value can likewise be used with anything that supports the buffer protocol. For example, memoryview can be used to access the buffer memory directly. Or, the struct module can be used to unpack formatted binary data.

Using native Python classes to access the memory is significantly more efficient than using the WinRT Windows.Storage.Streams.IDataReader or Windows.Storage.Streams.IDataWriter classes.

Windows.Foundation.IMemoryBuffer may also be accessed using the Python buffer protocol via the Windows.Foundation.IMemoryBufferReference. Care should be taken since the underlying memory can be released.

Collections

Most of the generic types in Windows.Foundation.Collections are projected as collections.abc types. The WinRT runtime types are still present behind the scenes, bute the type hints use only the Python standard library types to encourage users to use the Pythonic APIs. Furthermore, types that inherit from these interfaces are also extended to support the Pythonic APIs.

Sequences

IVector<T> is projected as MutableSequence[T] and IVectorView<T> is projected as Sequence[T]. These types behave very much like Python lists, so you can iterate them with a for loop, index them seq[0], slice them seq[:3] use them with len(seq) and search with value in seq.

For mutable sequences, items can be modified with seq[0] = value, deleted with del seq[0], appended with seq.append(value) or seq.extend(seq2), inserted with seq.insert(0, value), removed with seq.remove(value), and cleared with seq.clear(). You can also reverse the sequence in place with seq.reverse(), pop an item at a specific index using seq.pop(index) (or the last item with seq.pop()), or add items using the += operator like seq += [value1, value2].

Mappings

IMap<K, V> is projected as MutableMapping[K, V] and IMapView<K, V> is projected as Mapping[K, V]. These types behave like Python dictionaries, so you can access values with map[key] or map.get(key), check for keys with key in map, and iterate over keys with a for loop. You can also retrieve all keys, values, or key/value pairs using map.keys(), map.values(), and map.items() respectively. Equality operators (== and !=) can be used to compare mappings for equality based on their key-value pairs. You can also use len(map) to get the number of keys in the mapping.

Note

Python iterates over the keys only which might not be what you expect if you are used to using the same types in .NET. In Python, you will write:

for key in map:
    print(key)

To get both the keys and values, use the items() method:

for key, value in map.items():
    print(key, value)

For mutable mappings, you can add or update items with map[key] = value, use value = map.setdefault(key, default) to insert a key with a default value if it doesn’t exist, delete items with del map[key], and clear all items with map.clear(). You can also remove and return a value while removing the key using map.pop(key) or map.popitem(). Multiple values can be set at the same time using map.update(other).

There is also a special handling for IIterable<IKeyValuePair<K, V>> when used as an argument to methods where a Python mapping is allowed. This means you can pass a Python dictionary as the argument.

Example:

data = NotificationData({"my_key": "my_value"})

Iterators

IIterable<T> becomes Iterable[T]. Any iterable Python type can be used as an argument and return values can be used as any other iterable in Python. Don’t use the WinRT first() method to get an iterator, instead use the builtin iter() function or other Python features like for loops.

IIterator<T> is projected as Iterator[T] however these objects are rarely used directly in Python. Instead, use for loops or generator expressions to do the iterating for you. In rare cases, iterators might be used with next(). The WinRT methods on this object should be avoided.

Context managers

Any type that implements IClosable can (and probably should) be used as a context manager in Python:

from winrt.windows.foundation import MemoryBuffer

with MemoryBuffer(256) as buf:
    ...

Generally, when an object it closable, it means that there are unmanaged resources that may need to be released in a deterministic manner as opposed waiting for the garbage collector to run to clean them up.

Note

.NET programmers may recognize this as similar to using statements in C# with IDisposable types.

Date and time

There are a few foundational time-related types in WinRT that are projected as the analogous Python types.

Windows.Foundation.DateTime

This type is converted to a datetime.datetime object from the standard Python library.

WinRT has a resolution of 100 nanoseconds while the Python type has a resolution of 1 microsecond, so there is a small loss of precision in the conversion. Python also has a much smaller allowed range of dates (years from 1 to 9999).

WinRT serializes values of this type as 100s of nanoseconds since since January 1, 1601 (UTC). It uses a signed 64-bit integer for this, so the "q" format string is used in Python.

WinRT uses UTC for all values, so any datetime object returned from a Windows API will use that timezone. It is recommended to use the UTC timezone when creating datetime objects to pass to Windows APIs as well. “Naive” datetime objects (without a timezone) are assumed to use the local timezone and will be converted to UTC.

Example:

# set notification to expire 10 seconds from now
toaster.expiration_time = datetime.now(timezone.utc) + timedelta(seconds=10)

Windows.Foundation.TimeSpan

This type is converted to a datetime.timedelta object from the standard Python library.

WinRT has a resolution of 100 nanoseconds while the Python type has a resolution of 1 microsecond, so there is a small loss of precision in the conversion. Python is also limited to +/-999999999 days.

WinRT serializes values of this type as 100s of nanoseconds. It uses a signed 64-bit integer for this, so the "q" format string is used in Python.