- 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 )