| Name | Size |
|---|---|
| .github/workflows/main.yml | 846B |
| LICENSE | 1072B |
| README.md | 5174B |
| pyproject.toml | 464B |
| tests/test_client.py | 3202B |
| tests/test_marshal.py | 17107B |
| tests/test_schema.py | 1143B |
| xibus/__init__.py | 301B |
| xibus/client.py | 8706B |
| xibus/connection.py | 7124B |
| xibus/marshal.py | 5651B |
| xibus/message.py | 2869B |
| xibus/schema.py | 4631B |
xibus - pure python async D-Bus library
This is a pure python implementation of the D-Bus Specification. It consists of the following parts:
marshal.pyimplements the low-level wire formatmessage.pybuilds on that to define messagesconnection.pyallows to send and receive messages over a socket as well as introducing the concepts of method calls and signalsclient.pyprovides high level abstractions- properties
- introspection
- guessing the correct path and interface to reduce verbosity
- portal-style async responses (
org.freedesktop.portal.Request)
Usage
import asyncio
from xibus import get_client
async def amain():
async with get_client('session') as c:
# call a method
print(await c.call(
'org.freedesktop.portal.Desktop',
'/org/freedesktop/portal/desktop',
'org.freedesktop.portal.Settings',
'ReadOne',
('org.freedesktop.appearance', 'color-scheme'),
'ss',
))
# if path, interface, or signature are omitted,
# they will be inferred from introspection
print(await c.call(
'org.freedesktop.portal.Desktop',
None,
None,
'ReadOne',
('org.freedesktop.appearance', 'color-scheme'),
))
# get a property
print(await c.get_property(
'org.freedesktop.portal.Desktop',
None,
'org.freedesktop.portal.Settings',
'version',
))
# receive signals
async with c.subscribe_signal(
'org.freedesktop.portal.Desktop',
None,
None,
'SettingChanged',
) as queue:
async for signal in queue:
print(signal)
# desktop portals have a different mechanism for returning values,
# so there is a special way to call them
await c.portal_call(
'org.freedesktop.portal.Desktop',
None,
None,
'OpenURI',
['', 'https://example.com', {}],
)
asyncio.run(amain())
Motivation
This library was born from my frustration with dbus. I wanted to see if that frustration was caused by bad library design or if it was inherent in the protocol.
Verbosity
org.freedesktop.portal.Desktop /org/freedesktop/portal/desktop org.freedesktop.portal.Settings ReadOne is extremely verbose and redundant. An
equivalent HTTP endpoint would probably be called
desktop.portal.freedesktop.org/settings/ReadOne, which is so much better.
Sure, namespacing has its benefits. But this is just excessive.
This is mostly an issue in the protocol. Applications could make this slightly
better by choosing / as the path for their primary object, but even that is
discouraged by the spec.
GDBusProxy is also more of a workaround than a solution. The fact that it
is a proxy for interfaces rather than objects is an indicator that those two
concepts are at least partially redundant.
My workaround here is to use the first matching path / interface that can be found. This has a performance penalty and is brittle (e.g. if conflicting interfaces are added later on), so this is not a real solution either.
IMHO the real solution would be to enforce / as the primary object path and
to do away with interfaces. In practice there are not so many interfaces on the
same object that collisions are a real issue.
Signatures
In order to encode a dbus message, you need to know its type signature. In the
glib implementation, the caller must provide that signature. I added the option
to omit the signature, in which case it is received via introspection. Deriving
the signature from python types is not easily possible because it is unclear
how to differentiate INT32 from UINT64 or STRING from OBJECT_PATH.
A special case of this are variant types, where the type is only known at
runtime. I chose to represent them as simple (signature, value) tuples.
Custom wire format
The most complex part of this library is the implementation of the custom wire format. This would be much easier if dbus would reuse an existing format like MessagePack, CBOR, or even JSON. (Each of these of course come with their own pros and cons.)
Layers
That said, I really like how the different layers of this protocol are stacked on top of each other. Once you have the wire format, you can build messages on top of that. Then you can build method calls and signals on top of messages. Finally, you can build introspection and property access on top of method calls.
Links
- dbus-next and its forks dbus-fast and asyncdbus also implements D-Bus in python, but the code is much more complex.
- Talk on why systemd is moving from D-Bus to Varlink