xibus

experimental pure python async D-Bus library
git clone https://git.ce9e.org/xibus.git

commit
fdb1861c6b484addabb09b48d5de29649687e565
parent
2ef55b7378b764d11297fa7be4e570f086fc7ac6
Author
Tobias Bengfort <tobias.bengfort@posteo.de>
Date
2026-02-16 08:47
add high-level client

Diffstat

A xibus/__init__.py 11 +++++++++++
A xibus/client.py 27 +++++++++++++++++++++++++++
A xibus/schema.py 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

3 files changed, 108 insertions, 0 deletions


diff --git a/xibus/__init__.py b/xibus/__init__.py

@@ -0,0 +1,11 @@
   -1     1 import contextlib
   -1     2 
   -1     3 from .client import Client
   -1     4 from .connection import DBusError  # noqa
   -1     5 from .connection import get_connection
   -1     6 
   -1     7 
   -1     8 @contextlib.asynccontextmanager
   -1     9 async def get_client(bus):
   -1    10     async with get_connection(bus) as con:
   -1    11         yield Client(con)

diff --git a/xibus/client.py b/xibus/client.py

@@ -0,0 +1,27 @@
   -1     1 from .schema import Schema
   -1     2 
   -1     3 
   -1     4 class Client:
   -1     5     def __init__(self, con):
   -1     6         self.con = con
   -1     7         self.introspect_cache = {}
   -1     8 
   -1     9     async def introspect(self, name, path):
   -1    10         key = f'{name}{path}'
   -1    11         if key not in self.introspect_cache:
   -1    12             iface = 'org.freedesktop.DBus.Introspectable'
   -1    13             (xml,) = await self.con.call(name, path, iface, 'Introspect', [], '')
   -1    14             self.introspect_cache[key] = Schema.from_xml(xml)
   -1    15         return self.introspect_cache[key]
   -1    16 
   -1    17     async def call(self, name, path, iface, method, params=(), sig=None):
   -1    18         schema = await self.introspect(name, path)
   -1    19         m = schema.interfaces[iface].methods[method]
   -1    20         if sig is None:
   -1    21             sig = ''.join([v for _, v in m.args])
   -1    22 
   -1    23         result = await self.con.call(name, path, iface, method, params, sig)
   -1    24         if len(m.returns) == 1:
   -1    25             return result[0]
   -1    26         elif len(m.returns) > 1:
   -1    27             return result

diff --git a/xibus/schema.py b/xibus/schema.py

@@ -0,0 +1,70 @@
   -1     1 import xml.etree.ElementTree as ET
   -1     2 from collections import namedtuple
   -1     3 
   -1     4 _Property = namedtuple('Property', ['type', 'access'])
   -1     5 _Method = namedtuple('Method', ['args', 'returns'])
   -1     6 _Signal = namedtuple('Signal', ['args'])
   -1     7 _Interface = namedtuple('Interface', ['methods', 'properties', 'signals'])
   -1     8 
   -1     9 
   -1    10 def get_all_ordered(node, tag, parse):
   -1    11     return [(n.get('name'), parse(n)) for n in node.findall(tag)]
   -1    12 
   -1    13 
   -1    14 def get_all(node, tag, parse):
   -1    15     return dict(get_all_ordered(node, tag, parse))
   -1    16 
   -1    17 
   -1    18 def parse_arg(node):
   -1    19     return node.get('type')
   -1    20 
   -1    21 
   -1    22 class Method(_Method):
   -1    23     @classmethod
   -1    24     def parse(cls, node):
   -1    25         # inout args must be included in both
   -1    26         return cls(
   -1    27             args=get_all_ordered(node, './/arg[@direction!="out"]', parse_arg),
   -1    28             returns=get_all_ordered(node, './/arg[@direction!="in"]', parse_arg),
   -1    29         )
   -1    30 
   -1    31 
   -1    32 class Property(_Property):
   -1    33     @classmethod
   -1    34     def parse(cls, node):
   -1    35         return cls(
   -1    36             type=node.get('type'),
   -1    37             access=node.get('access'),
   -1    38         )
   -1    39 
   -1    40 
   -1    41 class Signal(_Signal):
   -1    42     @classmethod
   -1    43     def parse(cls, node):
   -1    44         return cls(
   -1    45             args=get_all_ordered(node, 'arg', parse_arg),
   -1    46         )
   -1    47 
   -1    48 
   -1    49 class Interface(_Interface):
   -1    50     @classmethod
   -1    51     def parse(cls, node):
   -1    52         return cls(
   -1    53             methods=get_all(node, 'method', Method.parse),
   -1    54             properties=get_all(node, 'property', Property.parse),
   -1    55             signals=get_all(node, 'signal', Signal.parse),
   -1    56         )
   -1    57 
   -1    58 
   -1    59 class Schema:
   -1    60     def __init__(self, interfaces=None, nodes=None):
   -1    61         self.interfaces = interfaces or {}
   -1    62         self.nodes = nodes or []
   -1    63 
   -1    64     @classmethod
   -1    65     def from_xml(cls, s):
   -1    66         tree = ET.fromstring(s)
   -1    67         return cls(
   -1    68             interfaces=get_all(tree, 'interface', Interface.parse),
   -1    69             nodes=[n.get('name') for n in tree.findall('node')],
   -1    70         )