Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions docs/source/high-level-service/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ on the class with the decorator methods :func:`@dbus_method()
<dbus_fast.service.dbus_method>`, :func:`@dbus_property()
<dbus_fast.service.dbus_property>`, and :func:`@dbus_signal()
<dbus_fast.service.dbus_signal>`. The parameters of the decorated class
methods must be annotated with DBus type strings to indicate the types
methods must be annotated using :class:`typing.Annotated` with a
:class:`DBusSignature <dbus_fast.annotations.DBusSignature>` to indicate the types
of values they expect. See the documentation on `the type system
</type-system/index.html>`_ for more information on how DBus types are
mapped to Python values with signature strings. The decorator methods
Expand Down Expand Up @@ -56,9 +57,10 @@ lost.
A class method decorated with ``@dbus_signal()`` will be exposed as a
DBus signal. The value returned by the class method will be emitted as a
signal and broadcast to clients who are listening to the signal. The
returned value must conform to the return annotation of the class method
as a DBus signature string. If the signal has more than one argument,
they must be returned within a ``tuple``.
returned value must conform to the return annotation of the class method.
The annotation must be as a :class:`typing.Annotated` with a
:class:`DBusSignature <dbus_fast.annotations.DBusSignature>`.
If the signal has more than one argument, they must be returned within a ``tuple``.

A class method decorated with ``@dbus_method()`` or ``@dbus_property()``
may throw a :class:`DBusError <dbus_fast.DBusError>` to return a
Expand Down
17 changes: 16 additions & 1 deletion src/dbus_fast/_private/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import re
from collections.abc import Callable
from typing import Annotated, Any, get_args, get_origin
from warnings import warn

from dbus_fast.annotations import DBusSignature

Expand Down Expand Up @@ -120,10 +121,18 @@ def parse_annotation(annotation: Any, module: Any) -> str:
# a way to distinguish between a string constant and a forward reference
# other than by heuristics.

# TODO: Change this to FutureWarning in 2027 and remove support for
# string annotations in 2028.

# If it looks like a dbus signature, return it directly. These are sorted
# in the order of the "Summary of types" table in the D-Bus spec to make
# verification easier.
if re.match(r"^[ybnqiuxtdsoga\(\)v\{\}h]+$", annotation):
warn(
"String annotations are deprecated and support will be removed in the future. Use typing.Annotated with the appropriate annotation from dbus_fast.annotations instead.",
DeprecationWarning,
stacklevel=2,
)
return annotation

# Otherwise, assume deferred evaluation of annotations.
Expand All @@ -132,7 +141,13 @@ def parse_annotation(annotation: Any, module: Any) -> str:
# It could be a string literal, e.g "'s'", in which case this will
# effectively strip the quotes. Other literals would pass here, but
# they aren't expected, so we just let those fail later.
return ast.literal_eval(annotation)
literal = ast.literal_eval(annotation)
warn(
"String annotations are deprecated and support will be removed in the future. Use typing.Annotated with the appropriate annotation from dbus_fast.annotations instead.",
DeprecationWarning,
stacklevel=2,
)
return literal
except ValueError:
# Anything that isn't a Python literal will raise ValueError.
pass
Expand Down
54 changes: 30 additions & 24 deletions src/dbus_fast/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,33 +108,36 @@ def dbus_method(
:param disabled: If set to true, the method will not be visible to clients.
:type disabled: bool

:example:

::
:class:`typing.Annotated` is used to specify the D-Bus signature along with
the Python type::

@dbus_method()
def echo(self, val: 's') -> 's':
def echo(self, val: DBusStr) -> DBusStr:
return val

@dbus_method()
def echo_two(self, val1: 's', val2: 'u') -> 'su':
def echo_two(
self, val1: DBusStr, val2: DBusUInt32
) -> Annotated[tuple[str, int], DBusSignature("su")]:
return val1, val2

If you use Python annotations for type hints, you can use :class:`typing.Annotated`
to specify the Python type and the D-Bus signature at the same time like this::

from dbus_fast.annotations import DBusSignature, DBusStr, DBusUInt32
Originally, D-Bus signature strings were used directly in the annotations::

@dbus_method()
def echo(self, val: DBusStr) -> DBusStr:
def echo(self, val: 's') -> 's':
return val

@dbus_method()
def echo_two(
self, val1: DBusStr, val2: DBusUInt32
) -> Annotated[tuple[str, int], DBusSignature("su")]:
def echo_two(self, val1: 's', val2: 'u') -> 'su':
return val1, val2

Such usage is now deprecated and support will be removed in the future.

.. versionchanged:: v4.1.0
String annotations are deprecated and will raise a warning. Use
:class:`typing.Annotated` with the appropriate annotation from
:mod:`dbus_fast.annotations` instead.

.. versionchanged:: v4.0.0
:class:`typing.Annotated` can now be used to provide type hints and the
D-Bus signature at the same time. Older versions require D-Bus signature
Expand Down Expand Up @@ -375,31 +378,34 @@ def dbus_property(
clients.
:type disabled: bool

:example:

::
:class:`typing.Annotated` is used to specify the Python type and the D-Bus
signature at the same time like this::

@dbus_property()
def string_prop(self) -> 's':
def string_prop(self) -> DBusStr:
return self._string_prop

@string_prop.setter
def string_prop(self, val: 's'):
def string_prop(self, val: DBusStr):
self._string_prop = val

If you use Python annotations for type hints, you can use :class:`typing.Annotated`
to specify the Python type and the D-Bus signature at the same time like this::

from dbus_fast.annotations import DBusStr
Originally, D-Bus signature strings were used directly in the annotations::

@dbus_property()
def string_prop(self) -> DBusStr:
def string_prop(self) -> 's':
return self._string_prop

@string_prop.setter
def string_prop(self, val: DBusStr):
def string_prop(self, val: 's'):
self._string_prop = val

Such usage is now deprecated and support will be removed in the future.

.. versionchanged:: v4.1.0
String annotations are deprecated and will raise a warning. Use
:class:`typing.Annotated` with the appropriate annotation from
:mod:`dbus_fast.annotations` instead.

.. versionchanged:: v4.0.0
:class:`typing.Annotated` can now be used to provide type hints and the
D-Bus signature at the same time. Older versions require D-Bus signature
Expand Down
17 changes: 15 additions & 2 deletions tests/client/test_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import sys
from logging.handlers import QueueHandler
from queue import SimpleQueue
from typing import Annotated, no_type_check
from typing import Annotated, Any, no_type_check

import pytest

Expand All @@ -22,6 +22,19 @@
has_gi = check_gi_repository()


def deprecated_dbus_method():
inner_wrapper = dbus_method()

def outer_wrapper(*args: Any) -> Any:
with pytest.warns(
DeprecationWarning,
match=r"String annotations are deprecated.*Use typing\.Annotated.*instead.",
):
return inner_wrapper(*args)

return outer_wrapper


class ExampleInterface(ServiceInterface):
def __init__(self):
super().__init__("test.interface")
Expand All @@ -36,7 +49,7 @@ def EchoInt64(self, what: DBusInt64) -> DBusInt64:

# This one intentionally keeps string-style annotations for coverage purposes.
@no_type_check
@dbus_method()
@deprecated_dbus_method()
def EchoString(self, what: "s") -> "s":
return what

Expand Down
17 changes: 15 additions & 2 deletions tests/service/test_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from __future__ import annotations

import asyncio
from typing import Annotated, no_type_check
from typing import Annotated, Any, no_type_check

import pytest

Expand All @@ -21,6 +21,19 @@
from dbus_fast.service import ServiceInterface, dbus_method


def deprecated_dbus_method():
inner_wrapper = dbus_method()

def outer_wrapper(*args: Any) -> Any:
with pytest.warns(
DeprecationWarning,
match=r"String annotations are deprecated.*Use typing\.Annotated.*instead.",
):
return inner_wrapper(*args)

return outer_wrapper

Comment on lines 24 to 35
Copy link

Copilot AI Feb 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This helper is duplicated in multiple test files. Consider moving it to a shared test utility (e.g., tests/util.py) or a pytest fixture to avoid repeating the warning message and logic in multiple places.

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will be removed eventually and is not very big, so the duplication was intentional.


class ExampleInterface(ServiceInterface):
def __init__(self, name: str) -> None:
super().__init__(name)
Expand All @@ -32,7 +45,7 @@ def echo(self, what: DBusStr) -> DBusStr:

# This one intentionally keeps string-style annotations for coverage purposes.
@no_type_check
@dbus_method()
@deprecated_dbus_method()
def echo_multiple(self, what1: "s", what2: "s") -> "ss": # noqa: UP037
assert type(self) is ExampleInterface
return what1, what2
Expand Down
Loading