=====================
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.
.. _Windows Runtime (WinRT) type system: https://learn.microsoft.com/en-us/uwp/winrt-cref/winrt-type-system
.. _naming:
------------------
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.
.. _PEP8 naming conventions: https://peps.python.org/pep-0008/#naming-conventions
---------------------
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.
.. seealso:: :doc:`api/index`
----------
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`` :class:`bool` ``"?"`` an 8-bit Boolean value
``Int8`` :class:`int` ``"b"`` an 8-bit signed integer
``Int16`` :class:`int` ``"h"`` a 16-bit signed integer
``Int32`` :class:`int` ``"i"`` a 32-bit signed integer
``Int64`` :class:`int` ``"q"`` a 64-bit signed integer
``UInt8`` :class:`int` ``"B"`` an 8-bit unsigned integer
``UInt16`` :class:`int` ``"H"`` a 16-bit unsigned integer
``UInt32`` :class:`int` ``"I"`` a 32-bit unsigned integer
``UInt64`` :class:`int` ``"Q"`` a 64-bit unsigned integer
``Single`` :class:`float` ``"f"`` a 32-bit IEEE 754 floating point number
``Double`` :class:`float` ``"d"`` a 64-bit IEEE 754 floating point number
``Char16`` :class:`str` [#s]_ ``"u"`` [#u]_ a 16-bit non-numeric value representing a UTF-16 code unit
``String`` :class:`str` ``"P"`` an immutable sequence of Char16 used to represent text
``Guid`` :class:`uuid.UUID` ``"T{I2H8B}"`` [#g]_ 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
:attr:`memoryview.format`.
.. _defined in the struct module: https://docs.python.org/3/library/struct.html#format-characters
.. _PEP 3118 additions: https://peps.python.org/pep-3118/#additions-to-the-struct-string-syntax
.. [#s] Strings that are converted to ``Char16`` can only contain one character,
similar to how :func:`ord` works.
.. [#u] ``"u"`` is deprecated in the :mod:`array` module and is not
compatible with the :mod:`struct` module. Use ``"H"`` instead if needed.
.. [#g] Use ``"I2H8B"`` with the :mod:`struct` module since it does not support
the PEP 3118 ``T{}`` syntax.
.. _Windows GUID Structure: https://learn.microsoft.com/en-us/windows/win32/api/guiddef/ns-guiddef-guid
-----
Enums
-----
WinRT enums are projected using the Python standard library :mod:`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 :class:`enum.IntEnum` type or if the ``[Flags]`` attribute is present, the type is
projected as an :class:`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
:class:`typing.NamedTuple` or frozen :mod:`dataclasses`. The names of fields are
converted to use the ``lower_case_with_underscores`` :ref:`naming convention `.
Projected structs are :func:`@typing.final ` classes, so they
cannot be subclassed. Projected structs are immutable. To modify a struct, use
:func:`copy.replace` which creates a modified copy.
As a convenience, plain :class:`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()
.. versionchanged:: 2.2
Changed ``__repr__`` implementation to give a representation that can be
passed to ``eval()``.
.. versionchanged:: 3.0
* Structs are now immutable. In previous versions, attributes could be set.
* Added ``__replace__`` method to allow for use with :func:`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
=======
.. todo:: explain method naming and overload rules
https://github.com/pywinrt/pywinrt/blob/main/projection/readme.md#method-overloading
Properties
==========
Properties are projected as Python :term:`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``
:ref:`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
.. versionchanged:: v1.0.0b8
Previous beta releases implemented static properties as ``get_name()`` and
``put_name()`` static methods instead of class attributes.
Events
======
.. todo:: explain events
https://github.com/pywinrt/pywinrt/blob/main/projection/readme.md#event-handlers
----------
Interfaces
----------
.. todo:: explain interfaces
---------
Delegates
---------
.. todo:: explain delegates
.. todo:: explain exceptions in callbacks
------
Arrays
------
.. todo:: document array types
.. _exceptions-projection:
----------
Exceptions
----------
PyWinRT uses the CppWinRT projection under the hood. Any C++ exception that is
not handled gets propagated to Python as an :class:`OSError` exception with
:attr:`OSError.winerror` set to an `HRESULT`_ error code.
.. seealso:: :mod:`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 :func:`sys.unraisablehook`
handler in Python and cause the C++ code to receive a an HRESULT error code
of :attr:`~winrt.system.hresult.PYWINRT_E_UNRAISABLE_PYTHON_EXCEPTION`.
This can cause undefined behavior in the C++ code in some cases, so it should
be avoided.
.. _HRESULT: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/0642cb2f-2075-4469-918c-4441e69c548a
-----------------
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:
- `IAsyncAction `_
\- represents an operation that does not return a value
- `IAsyncOperation `_
\- represents an operation that returns a value of type ``TResult``
- `IAsyncOperationWithProgress `_
\- represents an operation that returns a value of type ``TResult`` and reports
progress of type ``TProgress``
- `IAsyncActionWithProgress `_
\- represents an operation that does not return a value and reports progress
of type ``TProgress``
Synchronous usage
-----------------
PyWinRT exposes the CppWinRT extension methods for calling these methods
synchronously (i.e. when not using ``asyncio``).
.. method:: 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:
:class:`OSError` if the operation failed or was canceled.
:class:`RuntimeError` if called from a single-threaded apartment (i.e.
a GUI thread).
.. warning:: This method can't be interrupted by :kbd:`CTRL+C` which means
that a :class:`KeyboardInterrupt` will not be raised until the operation
is complete.
.. versionadded:: 3.2
.. method:: 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:
:class:`RuntimeError` if called from a single-threaded apartment (i.e.
a GUI thread).
.. warning:: This method can't be interrupted by :kbd:`CTRL+C`, which means
a :class:`KeyboardInterrupt` will not be raised until the operation is
complete or the timeout is reached.
.. versionadded:: 3.2
.. _async-projection:
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
:class:`~collections.abc.Coroutine` object and therefore cannot be
used with methods like :func:`asyncio.create_task`. They are only
:class:`~collections.abc.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()
.. versionchanged:: 3.2
If the :class:`~collections.abc.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 :func:`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
.. _buffer-projection:
Buffers
=======
`Windows.Storage.Streams.IBuffer`_ is projected as :class:`winrt.system.Buffer`,
which is an alias for :class:`collections.abc.Buffer`. When used as a method
parameter, any Python object that implements the buffer protocol can be used,
for example, a :class:`bytearray`, :class:`bytes`, or a :class:`memoryview`.
Although care must be taken to ensure that immutable types like :class:`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, :class:`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.
.. _Windows.Storage.Streams.IBuffer: https://learn.microsoft.com/en-us/uwp/api/windows.storage.streams.ibuffer
.. _Windows.Foundation.IMemoryBuffer: https://learn.microsoft.com/en-us/uwp/api/windows.foundation.imemorybuffer
.. _Windows.Foundation.IMemoryBufferReference: https://learn.microsoft.com/en-us/uwp/api/windows.foundation.imemorybufferreference
.. _Windows.Storage.Streams.IDataReader: https://learn.microsoft.com/en-us/uwp/api/windows.storage.streams.idatareader
.. _Windows.Storage.Streams.IDataWriter: https://learn.microsoft.com/en-us/uwp/api/windows.storage.streams.idatawriter
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.
.. _Windows.Foundation.Collections: https://learn.microsoft.com/en-us/uwp/api/windows.foundation.collections
.. _collections.abc: https://docs.python.org/3/library/collections.abc.html
Sequences
---------
`IVector`_ is projected as `MutableSequence[T]`_ and `IVectorView`_ 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]``.
.. _IVector: https://learn.microsoft.com/en-us/uwp/api/windows.foundation.collections.ivector-1
.. _MutableSequence[T]: https://docs.python.org/3/library/collections.abc.html#collections.abc.MutableSequence
.. _IVectorView: https://learn.microsoft.com/en-us/uwp/api/windows.foundation.collections.ivectorview-1
.. _Sequence[T]: https://docs.python.org/3/library/collections.abc.html#collections.abc.Sequence
Mappings
--------
`IMap`_ is projected as `MutableMapping[K, V]`_ and `IMapView`_ 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>`` 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"})
.. _IMap: https://learn.microsoft.com/en-us/uwp/api/windows.foundation.collections.imap-2
.. _MutableMapping[K, V]: https://docs.python.org/3/library/collections.abc.html#collections.abc.MutableMapping
.. _IMapView: https://learn.microsoft.com/en-us/uwp/api/windows.foundation.collections.imapview-2
.. _Mapping[K, V]: https://docs.python.org/3/library/collections.abc.html#collections.abc.Mapping
Iterators
---------
`IIterable`_ 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 :func:`iter()` function or other Python features like ``for`` loops.
`IIterator`_ 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 :func:`next()`.
The WinRT methods on this object should be avoided.
.. _IIterable: https://learn.microsoft.com/en-us/uwp/api/windows.foundation.collections.iiterable-1
.. _Iterable[T]: https://docs.python.org/3/library/collections.abc.html#collections.abc.Iterable
.. _IIterator: https://learn.microsoft.com/en-us/uwp/api/windows.foundation.collections.iiterator-1
.. _Iterator[T]: https://docs.python.org/3/library/collections.abc.html#collections.abc.Iterator
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 :class:`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 :class:`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.