xibus

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

commit
dbeaf690edddd3b5dc1c3084ff67278dbe53e806
parent
d423f854ed683902f690d454b67cfa74adf934eb
Author
Tobias Bengfort <tobias.bengfort@posteo.de>
Date
2026-02-17 05:46
add Schema.to_xml()

Diffstat

A tests/test_schema.py 37 +++++++++++++++++++++++++++++++++++++
M xibus/schema.py 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

2 files changed, 117 insertions, 0 deletions


diff --git a/tests/test_schema.py b/tests/test_schema.py

@@ -0,0 +1,37 @@
   -1     1 import unittest
   -1     2 
   -1     3 from xibus.schema import Schema
   -1     4 
   -1     5 SCHEMA = """<?xml version='1.0' encoding='utf-8'?>
   -1     6 <node>
   -1     7   <interface name="org.freedesktop.DBus">
   -1     8     <method name="RequestName">
   -1     9       <arg direction="in" type="s" />
   -1    10       <arg direction="in" type="u" />
   -1    11       <arg direction="out" type="u" />
   -1    12     </method>
   -1    13     <method name="ReloadConfig" />
   -1    14     <property name="Features" type="as" access="read" />
   -1    15     <signal name="NameLost">
   -1    16       <arg type="s" />
   -1    17     </signal>
   -1    18   </interface>
   -1    19   <node name="foo" />
   -1    20 </node>"""
   -1    21 
   -1    22 
   -1    23 class TestSchema(unittest.TestCase):
   -1    24     maxDiff = 1000
   -1    25 
   -1    26     def test_from_xml(self):
   -1    27         schema = Schema.from_xml(SCHEMA)
   -1    28         self.assertEqual(schema.to_xml(), SCHEMA)
   -1    29 
   -1    30     def test_construct(self):
   -1    31         schema = Schema()
   -1    32         schema.add_method('org.freedesktop.DBus', 'RequestName', ['s', 'u'], ['u'])
   -1    33         schema.add_method('org.freedesktop.DBus', 'ReloadConfig', [], [])
   -1    34         schema.add_property('org.freedesktop.DBus', 'Features', 'as', 'read')
   -1    35         schema.add_signal('org.freedesktop.DBus', 'NameLost', ['s'])
   -1    36         schema.nodes.append('foo')
   -1    37         self.assertEqual(schema.to_xml(), SCHEMA)

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

@@ -7,6 +7,14 @@ _Signal = namedtuple('Signal', ['args'])
    7     7 _Interface = namedtuple('Interface', ['methods', 'properties', 'signals'])
    8     8 
    9     9 
   -1    10 def el(tag, attrs):
   -1    11     node = ET.Element(tag)
   -1    12     for key, value in attrs.items():
   -1    13         if value is not None:
   -1    14             node.attrib[key] = value
   -1    15     return node
   -1    16 
   -1    17 
   10    18 def get_all_ordered(node, tag, parse):
   11    19     return [(n.get('name'), parse(n)) for n in node.findall(tag)]
   12    20 
@@ -15,10 +23,22 @@ def get_all(node, tag, parse):
   15    23     return dict(get_all_ordered(node, tag, parse))
   16    24 
   17    25 
   -1    26 def normalize_args(args):
   -1    27     return [(None, arg) if isinstance(arg, str) else arg for arg in args]
   -1    28 
   -1    29 
   18    30 def parse_arg(node):
   19    31     return node.get('type')
   20    32 
   21    33 
   -1    34 def unparse_arg(name, typ, direction=None):
   -1    35     return el('arg', {
   -1    36         'name': name,
   -1    37         'direction': direction,
   -1    38         'type': typ,
   -1    39     })
   -1    40 
   -1    41 
   22    42 class Method(_Method):
   23    43     @classmethod
   24    44     def parse(cls, node):
@@ -28,6 +48,14 @@ class Method(_Method):
   28    48             returns=get_all_ordered(node, './/arg[@direction!="in"]', parse_arg),
   29    49         )
   30    50 
   -1    51     def unparse(self, name):
   -1    52         node = el('method', {'name': name})
   -1    53         for _name, typ in self.args:
   -1    54             node.append(unparse_arg(_name, typ, 'in'))
   -1    55         for _name, typ in self.returns:
   -1    56             node.append(unparse_arg(_name, typ, 'out'))
   -1    57         return node
   -1    58 
   31    59 
   32    60 class Property(_Property):
   33    61     @classmethod
@@ -37,6 +65,13 @@ class Property(_Property):
   37    65             access=node.get('access'),
   38    66         )
   39    67 
   -1    68     def unparse(self, name):
   -1    69         return el('property', {
   -1    70             'name': name,
   -1    71             'type': self.type,
   -1    72             'access': self.access,
   -1    73         })
   -1    74 
   40    75 
   41    76 class Signal(_Signal):
   42    77     @classmethod
@@ -45,6 +80,12 @@ class Signal(_Signal):
   45    80             args=get_all_ordered(node, 'arg', parse_arg),
   46    81         )
   47    82 
   -1    83     def unparse(self, name):
   -1    84         node = el('signal', {'name': name})
   -1    85         for _name, typ in self.args:
   -1    86             node.append(unparse_arg(_name, typ))
   -1    87         return node
   -1    88 
   48    89 
   49    90 class Interface(_Interface):
   50    91     @classmethod
@@ -55,12 +96,42 @@ class Interface(_Interface):
   55    96             signals=get_all(node, 'signal', Signal.parse),
   56    97         )
   57    98 
   -1    99     def unparse(self, name):
   -1   100         node = el('interface', {'name': name})
   -1   101         for _name, method in self.methods.items():
   -1   102             node.append(method.unparse(_name))
   -1   103         for _name, prop in self.properties.items():
   -1   104             node.append(prop.unparse(_name))
   -1   105         for _name, signal in self.signals.items():
   -1   106             node.append(signal.unparse(_name))
   -1   107         return node
   -1   108 
   58   109 
   59   110 class Schema:
   60   111     def __init__(self, interfaces=None, nodes=None):
   61   112         self.interfaces = interfaces or {}
   62   113         self.nodes = nodes or []
   63   114 
   -1   115     def add_property(self, iface, prop, typ, access):
   -1   116         iface_data = self.interfaces.setdefault(iface, Interface({}, {}, {}))
   -1   117         iface_data.properties[prop] = Property(typ, access)
   -1   118 
   -1   119     def add_method(self, iface, method, args, returns):
   -1   120         iface_data = self.interfaces.setdefault(iface, Interface({}, {}, {}))
   -1   121         iface_data.methods[method] = Method(
   -1   122             normalize_args(args), normalize_args(returns)
   -1   123         )
   -1   124 
   -1   125     def add_signal(self, iface, signal, args):
   -1   126         iface_data = self.interfaces.setdefault(iface, Interface({}, {}, {}))
   -1   127         iface_data.signals[signal] = Signal(normalize_args(args))
   -1   128 
   -1   129     def add_defaults(self):
   -1   130         self.add_method('org.freedesktop.DBus.Introspectable', 'Introspect', [], ['s'])
   -1   131         self.add_method('org.freedesktop.DBus.Properties', 'Get', ['s', 's'], ['v'])
   -1   132         self.add_method('org.freedesktop.DBus.Properties', 'Set', ['s', 's', 'v'], [])
   -1   133         self.add_method('org.freedesktop.DBus.Properties', 'GetAll', ['s'], ['a{sv}'])
   -1   134 
   64   135     @classmethod
   65   136     def from_xml(cls, s):
   66   137         tree = ET.fromstring(s)
@@ -68,3 +139,12 @@ class Schema:
   68   139             interfaces=get_all(tree, 'interface', Interface.parse),
   69   140             nodes=[n.get('name') for n in tree.findall('node')],
   70   141         )
   -1   142 
   -1   143     def to_xml(self):
   -1   144         tree = el('node', {})
   -1   145         for _name, iface in self.interfaces.items():
   -1   146             tree.append(iface.unparse(_name))
   -1   147         for _name in self.nodes:
   -1   148             tree.append(el('node', {'name': _name}))
   -1   149         ET.indent(tree)
   -1   150         return ET.tostring(tree, encoding='unicode', xml_declaration=True)