diff --git a/dbus_next/aio/message_bus.py b/dbus_next/aio/message_bus.py index 8193a68..b57b63e 100644 --- a/dbus_next/aio/message_bus.py +++ b/dbus_next/aio/message_bus.py @@ -33,7 +33,6 @@ class MessageBus(BaseMessageBus): be :class:`None` until the message bus connects. :vartype unique_name: str """ - def __init__(self, bus_address: str = None, bus_type: BusType = BusType.SESSION): super().__init__(bus_address, bus_type, ProxyObject) self._loop = asyncio.get_event_loop() diff --git a/dbus_next/aio/proxy_object.py b/dbus_next/aio/proxy_object.py index 9c85d89..d9f071e 100644 --- a/dbus_next/aio/proxy_object.py +++ b/dbus_next/aio/proxy_object.py @@ -65,7 +65,6 @@ class ProxyInterface(BaseProxyInterface): If the service returns an error for a DBus call, a :class:`DBusError ` will be raised with information about the error. """ - def _add_method(self, intr_method): async def method_fn(*args): msg = await self.bus.call( @@ -128,7 +127,6 @@ class ProxyObject(BaseProxyObject): For more information, see the :class:`BaseProxyObject `. """ - def __init__(self, bus_name: str, path: str, introspection: Union[intr.Node, str, ET.Element], bus: BaseMessageBus): super().__init__(bus_name, path, introspection, bus, ProxyInterface) diff --git a/dbus_next/glib/message_bus.py b/dbus_next/glib/message_bus.py index 1c94cb0..c1a95f2 100644 --- a/dbus_next/glib/message_bus.py +++ b/dbus_next/glib/message_bus.py @@ -140,7 +140,6 @@ class MessageBus(BaseMessageBus): be :class:`None` until the message bus connects. :vartype unique_name: str """ - def __init__(self, bus_address: str = None, bus_type: BusType = BusType.SESSION): if _import_error: raise _import_error diff --git a/dbus_next/glib/proxy_object.py b/dbus_next/glib/proxy_object.py index 12d350f..bfeab59 100644 --- a/dbus_next/glib/proxy_object.py +++ b/dbus_next/glib/proxy_object.py @@ -102,7 +102,6 @@ def set_callback(error: Exception) :class:`DBusError ` will be raised with information about the error. """ - def _add_method(self, intr_method): in_len = len(intr_method.in_args) out_len = len(intr_method.out_args) @@ -270,7 +269,6 @@ class ProxyObject(BaseProxyObject): For more information, see the :class:`BaseProxyObject `. """ - def __init__(self, bus_name: str, path: str, introspection: Union[intr.Node, str, ET.Element], bus: BaseMessageBus): super().__init__(bus_name, path, introspection, bus, ProxyInterface) diff --git a/dbus_next/introspection.py b/dbus_next/introspection.py index 22d8873..76f7d77 100644 --- a/dbus_next/introspection.py +++ b/dbus_next/introspection.py @@ -28,7 +28,6 @@ class Arg: - :class:`InvalidSignatureError ` - If the signature is not valid. - :class:`InvalidIntrospectionError ` - If the signature is not a single complete type. """ - def __init__(self, signature: Union[SignatureType, str], direction: List[ArgDirection] = None, @@ -101,7 +100,6 @@ class Signal: :raises: - :class:`InvalidMemberNameError ` - If the name of the signal is not a valid member name. """ - def __init__(self, name: str, args: List[Arg] = None): if name is not None: assert_member_name_valid(name) @@ -165,7 +163,6 @@ class Method: :raises: - :class:`InvalidMemberNameError ` - If the name of this method is not valid. """ - def __init__(self, name: str, in_args: List[Arg] = [], out_args: List[Arg] = []): assert_member_name_valid(name) @@ -238,7 +235,6 @@ class Property: - :class `InvalidSignatureError ` - If the given signature is not valid. - :class: `InvalidMemberNameError ` - If the member name is not valid. """ - def __init__(self, name: str, signature: str, access: PropertyAccess = PropertyAccess.READWRITE): assert_member_name_valid(name) @@ -303,7 +299,6 @@ class Interface: :raises: - :class:`InvalidInterfaceNameError ` - If the name is not a valid interface name. """ - def __init__(self, name: str, methods: List[Method] = None, @@ -387,7 +382,6 @@ class Node: :raises: - :class:`InvalidIntrospectionError ` - If the name is not a valid node name. """ - def __init__(self, name: str = None, interfaces: List[Interface] = None, is_root: bool = True): if not is_root and not name: raise InvalidIntrospectionError('child nodes must have a "name" attribute') @@ -488,6 +482,7 @@ def default(name: str = None) -> 'Node': * ``org.freedesktop.DBus.Introspectable`` * ``org.freedesktop.DBus.Peer`` * ``org.freedesktop.DBus.Properties`` + * ``org.freedesktop.DBus.ObjectManager`` """ return Node(name, is_root=True, @@ -530,5 +525,26 @@ def default(name: str = None) -> 'Node': Arg('as', ArgDirection.OUT, 'invalidated_properties') ]) - ]) + ]), + Interface('org.freedesktop.DBus.ObjectManager', + methods=[ + Method('GetManagedObjects', + out_args=[ + Arg('a{oa{sa{sv}}}', ArgDirection.OUT, + 'objpath_interfaces_and_properties') + ]), + ], + signals=[ + Signal('InterfacesAdded', + args=[ + Arg('o', ArgDirection.OUT, 'object_path'), + Arg('a{sa{sv}}', ArgDirection.OUT, + 'interfaces_and_properties'), + ]), + Signal('InterfacesRemoved', + args=[ + Arg('o', ArgDirection.OUT, 'object_path'), + Arg('as', ArgDirection.OUT, 'interfaces'), + ]) + ]), ]) diff --git a/dbus_next/message.py b/dbus_next/message.py index 1f6d643..4dd3844 100644 --- a/dbus_next/message.py +++ b/dbus_next/message.py @@ -54,7 +54,6 @@ class Message: - :class:`InvalidMemberNameError` - If ``member`` is not a valid member name. - :class:`InvalidInterfaceNameError` - If ``error_name`` or ``interface`` is not a valid interface name. """ - def __init__(self, destination: str = None, path: str = None, diff --git a/dbus_next/message_bus.py b/dbus_next/message_bus.py index 3b51608..d20e371 100644 --- a/dbus_next/message_bus.py +++ b/dbus_next/message_bus.py @@ -43,7 +43,6 @@ class BaseMessageBus: be :class:`None` until the message bus connects. :vartype unique_name: str """ - def __init__(self, bus_address: Optional[str] = None, bus_type: BusType = BusType.SESSION, @@ -586,6 +585,9 @@ def _find_message_handler(self, msg): handler = self._default_ping_handler elif msg._matches(member='GetMachineId', signature=''): handler = self._default_get_machine_id_handler + elif msg._matches(interface='org.freedesktop.DBus.ObjectManager', + member='GetManagedObjects'): + handler = self._default_object_manager else: for interface in self._path_exports.get(msg.path, []): @@ -633,6 +635,19 @@ def reply_handler(reply, err): interface='org.freedesktop.DBus.Peer', member='GetMachineId'), reply_handler) + def _default_object_manager(self, msg): + result = {} + + for node in self._path_exports: + if not node.startswith(msg.path + '/') and msg.path != '/': + continue + + result[node] = {} + for interface in self._path_exports[node]: + result[node][interface.name] = self._get_all_properties(interface) + + return Message.new_method_return(msg, 'a{oa{sa{sv}}}', [result]) + def _default_properties_handler(self, msg): methods = {'Get': 'ss', 'Set': 'ssv', 'GetAll': 's'} if msg.member not in methods or methods[msg.member] != msg.signature: @@ -686,13 +701,18 @@ def _default_properties_handler(self, msg): return Message.new_method_return(msg) elif msg.member == 'GetAll': - result = {} - for prop in ServiceInterface._get_properties(interface): - if prop.disabled or not prop.access.readable(): - continue - result[prop.name] = Variant(prop.signature, - getattr(interface, prop.prop_getter.__name__)) - + result = self._get_all_properties(interface) return Message.new_method_return(msg, 'a{sv}', [result]) else: assert False + + def _get_all_properties(self, interface): + result = {} + + for prop in ServiceInterface._get_properties(interface): + if prop.disabled or not prop.access.readable(): + continue + result[prop.name] = Variant(prop.signature, getattr(interface, + prop.prop_getter.__name__)) + + return result diff --git a/dbus_next/proxy_object.py b/dbus_next/proxy_object.py index cf211d9..ff17a2f 100644 --- a/dbus_next/proxy_object.py +++ b/dbus_next/proxy_object.py @@ -37,7 +37,6 @@ class BaseProxyInterface: :ivar bus: The message bus this proxy interface is connected to. :vartype bus: :class:`BaseMessageBus ` """ - def __init__(self, bus_name, path, introspection, bus): self.bus_name = bus_name @@ -104,7 +103,6 @@ class BaseProxyObject: - :class:`InvalidObjectPathError ` - If the given object path is not valid. - :class:`InvalidIntrospectionError ` - If the introspection data for the node is not valid. """ - def __init__(self, bus_name: str, path: str, introspection: Union[intr.Node, str, ET.Element], bus: 'message_bus.BaseMessageBus', ProxyInterface: Type[BaseProxyInterface]): assert_object_path_valid(path) diff --git a/dbus_next/service.py b/dbus_next/service.py index c447f63..9971f1c 100644 --- a/dbus_next/service.py +++ b/dbus_next/service.py @@ -317,7 +317,6 @@ class ServiceInterface: valid interface name. :vartype name: str """ - def __init__(self, name: str): # TODO cannot be overridden by a dbus member self.name = name diff --git a/dbus_next/signature.py b/dbus_next/signature.py index c3b96f9..adedc36 100644 --- a/dbus_next/signature.py +++ b/dbus_next/signature.py @@ -291,7 +291,6 @@ class SignatureTree: :raises: :class:`InvalidSignatureError` if the given signature is not valid. """ - def __init__(self, signature: str = ''): self.signature = signature @@ -353,7 +352,6 @@ class Variant: :class:`InvalidSignatureError` if the signature is not valid. :class:`SignatureBodyMismatchError` if the signature does not match the body. """ - def __init__(self, signature: Union[str, SignatureTree, SignatureType], value: Any): signature_str = '' signature_tree = None diff --git a/test/service/test_export.py b/test/service/test_export.py index b7b37d5..dbed007 100644 --- a/test/service/test_export.py +++ b/test/service/test_export.py @@ -110,4 +110,3 @@ async def test_export_introspection(): root = bus._introspect_export_path('/') assert len(root.nodes) == 1 - diff --git a/test/service/test_standard_interfaces.py b/test/service/test_standard_interfaces.py index 90f253b..12aa682 100644 --- a/test/service/test_standard_interfaces.py +++ b/test/service/test_standard_interfaces.py @@ -1,4 +1,5 @@ -from dbus_next.service import ServiceInterface +from dbus_next.service import ServiceInterface, dbus_property, PropertyAccess +from dbus_next.signature import Variant from dbus_next.aio import MessageBus from dbus_next import Message, MessageType, introspection as intr @@ -12,6 +13,21 @@ def __init__(self, name): super().__init__(name) +class ExampleComplexInterface(ServiceInterface): + def __init__(self, name): + self._foo = 42 + self._bar = 'str' + super().__init__(name) + + @dbus_property(access=PropertyAccess.READ) + def Foo(self) -> 'y': + return self._foo + + @dbus_property(access=PropertyAccess.READ) + def Bar(self) -> 's': + return self._bar + + @pytest.mark.asyncio async def test_introspectable_interface(): bus1 = await MessageBus().connect() @@ -74,3 +90,62 @@ async def test_peer_interface(): assert reply.message_type == MessageType.METHOD_RETURN, reply.body[0] assert reply.signature == 's' + + +@pytest.mark.asyncio +async def test_object_manager(): + expected_reply = { + '/test/path/deeper': { + 'test.interface2': { + 'Bar': Variant('s', 'str'), + 'Foo': Variant('y', 42) + } + } + } + reply_ext = { + '/test/path': { + 'test.interface1': {}, + 'test.interface2': { + 'Bar': Variant('s', 'str'), + 'Foo': Variant('y', 42) + } + } + } + + bus1 = await MessageBus().connect() + bus2 = await MessageBus().connect() + + interface = ExampleInterface('test.interface1') + interface2 = ExampleComplexInterface('test.interface2') + + export_path = '/test/path' + bus1.export(export_path, interface) + bus1.export(export_path, interface2) + bus1.export(export_path + '/deeper', interface2) + + reply_root = await bus2.call( + Message(destination=bus1.unique_name, + path='/', + interface='org.freedesktop.DBus.ObjectManager', + member='GetManagedObjects')) + + reply_level1 = await bus2.call( + Message(destination=bus1.unique_name, + path=export_path, + interface='org.freedesktop.DBus.ObjectManager', + member='GetManagedObjects')) + + reply_level2 = await bus2.call( + Message(destination=bus1.unique_name, + path=export_path + '/deeper', + interface='org.freedesktop.DBus.ObjectManager', + member='GetManagedObjects')) + + assert reply_root.signature == 'a{oa{sa{sv}}}' + assert reply_level1.signature == 'a{oa{sa{sv}}}' + assert reply_level2.signature == 'a{oa{sa{sv}}}' + + assert reply_level2.body == [{}] + assert reply_level1.body == [expected_reply] + expected_reply.update(reply_ext) + assert reply_root.body == [expected_reply]