1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354
|
Asyncio quick start
+++++++++++++++++++++
.. py:currentmodule:: sdbus
Interface classes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Python-sdbus works by declaring interface classes.
Interface classes for async IO should be derived from :py:class:`DbusInterfaceCommonAsync`.
The class constructor takes ``interface_name`` keyword to determine the D-Bus interface name for all
D-Bus elements declared in the class body.
Example: ::
from sdbus import DbusInterfaceCommonAsync
class ExampleInterface(DbusInterfaceCommonAsync,
interface_name='org.example.myinterface'
):
...
Interface class body should contain the definitions of methods, properties and
signals using decorators such as
:py:func:`dbus_method_async`, :py:func:`dbus_property_async` and
:py:func:`dbus_signal_async`.
Example: ::
from sdbus import (DbusInterfaceCommonAsync, dbus_method_async,
dbus_property_async, dbus_signal_async)
class ExampleInterface(DbusInterfaceCommonAsync,
interface_name='org.example.myinterface'
):
# Method that takes an integer and multiplies it by 2
@dbus_method_async('i', 'i')
async def double_int(self, an_int: int) -> None:
return an_int * 2
# Read only property of str
@dbus_property_async('s')
def read_string(self) -> int:
return 'Test'
# Signal with a list of strings
@dbus_signal_async('as')
def str_signal(self) -> list[str]:
raise NotImplementedError
Initiating proxy
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
:py:class:`DbusInterfaceCommonAsync` provides two methods for proxying remote objects.
:py:meth:`DbusInterfaceCommonAsync.new_proxy` class method bypasses the class ``__init__`` and returns proxy object.
:py:meth:`DbusInterfaceCommonAsync._proxify` should be used inside the ``__init__`` methods if your class is a proxy only.
Recommended to create proxy classes that a subclass of the interface: ::
from sdbus import DbusInterfaceCommonAsync
class ExampleInterface(...):
# Some interface class
...
class ExampleClient(ExampleInterface):
def __init__(self) -> None:
# Your client init can proxy to any object based on passed arguments.
self._proxify('org.example.test', '/')
.. note:: Successfully initiating a proxy object does NOT guarantee that the D-Bus object exists.
Serving objects
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
:py:meth:`DbusInterfaceCommonAsync.export_to_dbus` method
will export the object to the D-Bus. After calling it the object
becomes visible on D-Bus for other processes to call.
Example using ExampleInterface from before: ::
from sdbus import request_default_bus_name_async
loop = get_event_loop()
i = ExampleInterface()
async def start() -> None:
# Acquire a name on the bus
await request_default_bus_name_async('org.example.test')
# Start serving at / path
i.export_to_dbus('/')
loop.run_until_complete(start())
loop.run_forever()
Connection transparency
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The interface objects are designed to be transparent to their connection
status. This means if the object not proxied to remote the calls to decorated
methods will still work in the local scope.
This is the call to local object: ::
i = ExampleInterface()
async def test() -> None:
print(await i.double_int(5)) # Will print 10
This is a call to remote object at ``'org.example.test'`` service name
and ``'/'`` path: ::
i = ExampleInterface.new_proxy('org.example.test', '/')
async def test() -> None:
print(await i.double_int(5)) # Will print 10
Methods
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Methods are async function calls wrapped with :py:func:`dbus_method_async` decorator. (see the API reference for decorator parameters)
Methods have to be async function, otherwise :py:exc:`AssertionError` will be raised.
While method calls are async there is a inherit timeout timer for any method call.
To return an error to caller you need to raise exception which has a :py:exc:`.DbusFailedError` as base.
Regular exceptions will not propagate.
See :doc:`/exceptions`.
Example: ::
from sdbus import DbusInterfaceCommonAsync, dbus_method_async
class ExampleInterface(...):
...
# Body of some class
# Method that takes a string
# and returns uppercase of that string
@dbus_method_async(
input_signature='s',
result_signature='s',
result_args_names=('uppercased', ) # This is optional but
# makes arguments have names in
# introspection data.
)
async def upper(self, str_to_up: str) -> str:
return str_to_up.upper()
Methods behave exact same way as Python methods would: ::
print(await example_object.upper('test')) # prints TEST
Properties
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Properties are a single value that can be read and write.
To declare a read only property you need to decorate a regular function with
:py:func:`dbus_property_async` decorator.
Example: ::
from sdbus import DbusInterfaceCommonAsync, dbus_property_async
class ExampleInterface(...):
...
# Body of some class
# Read only property. No setter defined.
@dbus_property_async('i')
def read_only_number(self) -> int:
return 10
To create a read/write property you need to decorate the setter function with
the :py:obj:`setter` attribute of your getter function.
Example: ::
from sdbus import DbusInterfaceCommonAsync, dbus_property_async
class ExampleInterface(...):
...
# Body of some class
# Read/write property. First define getter.
@dbus_property_async('s')
def read_write_str(self) -> str:
return self.s
# Now create setter. Method name does not matter.
@read_write_str.setter # Use the property setter method as decorator
def read_write_str_setter(self, new_str: str) -> None:
self.s = new_str
Properties are supposed to be lightweight. Make sure you don't block event loop with getter or setter.
Async properties do not behave the same way as :py:func:`property` decorator does.
To get the value of the property you can either directly ``await`` on property
or use :py:meth:`get_async` method. (also need to be awaited)
To set property use :py:meth:`set_async` method.
Example: ::
...
# Somewhere in async function
# Assume we have example_object of class defined above
print(await example_object.read_write_str) # Print the value of read_write_str
...
# Set read_write_str to new value
await example_object.read_write_str.set_async('test')
Signals
^^^^^^^^^^^^^^^^^^^^^^^^^^^
To define a D-Bus signal wrap a function with :py:func:`dbus_signal_async` decorator.
The function is only used for type hints information. It is recommended
to just put ``raise NotImplementedError`` in to the body of the function.
Example: ::
from sdbus import DbusInterfaceCommonAsync, dbus_signal_async
class ExampleInterface(...):
...
# Body of some class
@dbus_signal_async('s')
def name_changed(self) -> str:
raise NotImplementedError
To catch a signal use ``async for`` loop: ::
async for x in example_object.name_changed:
print(x)
.. warning:: If you are creating an asyncio task to listen on signals
make sure to bind it to a variable and keep it referenced otherwise
garbage collector will destroy your task.
A signal can be emitted with :py:meth:`emit <DbusSignalAsync.emit>` method.
Example::
example_object.name_changed.emit('test')
Signals can also be caught from multiple D-Bus objects using
:py:meth:`catch_anywhere <DbusSignalAsync.catch_anywhere>` method. The async
iterator will yield the path of the object that emitted the signal and the signal data.
:py:meth:`catch_anywhere <DbusSignalAsync.catch_anywhere>` can be called from
class but in such case the service name must be provided.
Example::
async for path, x in ExampleInterface.name_changed.catch_anywhere('org.example.test'):
print(f"On {path} caught: {x}")
Subclass Overrides
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If you define a subclass which overrides a declared D-Bus method or property
you need to use :py:func:`dbus_method_async_override` and :py:func:`dbus_property_async_override`
decorators. Overridden property can decorate a new setter.
Overridden methods should take same number and type of arguments.
Example: ::
from sdbus import (dbus_method_async_override,
dbus_property_async_override)
# Some subclass
class SubclassInterface(...):
...
@dbus_method_async_override()
async def upper(self, str_to_up: str) -> str:
return 'Upper: ' + str_to_up.upper()
@dbus_property_async_override()
def str_prop(self) -> str:
return 'Test property' + self.s
# Setter needs to be decorated again to override
@str_prop.setter
def str_prop_setter(self, new_s: str) -> None:
self.s = new_s.upper()
Multiple interfaces
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
A D-Bus object can have multiple interfaces with different methods and properties.
To implement this define multiple interface classes and do a
multiple inheritance on all interfaces the object has.
Example: ::
from sdbus import DbusInterfaceCommonAsync
class ExampleInterface(DbusInterfaceCommonAsync,
interface_name='org.example.myinterface'
):
@dbus_method_async('i', 'i')
async def double_int(self, an_int: int) -> None:
return an_int * 2
class TestInterface(DbusInterfaceCommonAsync,
interface_name='org.example.test'
):
@dbus_method_async('as', 's')
async def join_str(self, str_array: list[str]) -> str:
return ''.join(str_array)
class MultipleInterfaces(TestInterface, ExampleInterface):
...
``MultipleInterfaces`` class will have both ``test_method`` and ``example_method``
that will be wired to correct interface names. (``org.example.myinterface``
and ``org.example.test`` respectively)
|