diff --git a/src/sdbus/dbus_common_elements.py b/src/sdbus/dbus_common_elements.py index 725674e..7f1d3e6 100644 --- a/src/sdbus/dbus_common_elements.py +++ b/src/sdbus/dbus_common_elements.py @@ -19,6 +19,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA from __future__ import annotations +from abc import ABC, abstractmethod from inspect import getfullargspec from typing import TYPE_CHECKING, Generic, TypeVar @@ -44,15 +45,21 @@ SelfMeta = TypeVar('SelfMeta', bound="DbusInterfaceMetaCommon") + from .dbus_proxy_async_interface_base import DbusExportHandle from .sd_bus_internals import SdBus, SdBusInterface T = TypeVar('T') -class DbusMemberCommon: +class DbusMemberCommon(ABC): interface_name: str serving_enabled: bool + @property + @abstractmethod + def member_name(self) -> str: + ... + class DbusMemberAsync(DbusMemberCommon): ... @@ -230,6 +237,10 @@ def _rebuild_args( return new_args_list + @property + def member_name(self) -> str: + return self.method_name + class DbusPropertyCommon(DbusMemberCommon): def __init__(self, @@ -261,6 +272,10 @@ def __init__(self, self.property_signature = property_signature self.flags = flags + @property + def member_name(self) -> str: + return self.property_name + class DbusSignalCommon(DbusMemberCommon): def __init__(self, @@ -290,12 +305,29 @@ def __init__(self, self.__doc__ = original_method.__doc__ self.__annotations__ = original_method.__annotations__ + @property + def member_name(self) -> str: + return self.signal_name -class DbusBoundAsync: - ... + +class DbusBoundMember(ABC): + @property + @abstractmethod + def member(self) -> DbusMemberCommon: + ... + + +class DbusLocalMemberAsync(DbusBoundMember): + @abstractmethod + def _append_to_interface( + self, + interface: SdBusInterface, + handle: DbusExportHandle, + ) -> None: + ... -class DbusBoundSync: +class DbusProxyMemberAsync(DbusBoundMember): ... diff --git a/src/sdbus/dbus_proxy_async_interface_base.py b/src/sdbus/dbus_proxy_async_interface_base.py index 9f48121..54004d1 100644 --- a/src/sdbus/dbus_proxy_async_interface_base.py +++ b/src/sdbus/dbus_proxy_async_interface_base.py @@ -23,13 +23,14 @@ from inspect import getmembers from itertools import chain from types import MethodType -from typing import TYPE_CHECKING, Any, Callable, cast +from typing import TYPE_CHECKING, Any, Callable, Protocol, cast from warnings import warn from weakref import WeakKeyDictionary, WeakValueDictionary from .dbus_common_elements import ( DbusClassMeta, DbusInterfaceMetaCommon, + DbusLocalMemberAsync, DbusLocalObjectMeta, DbusMemberAsync, DbusMemberCommon, @@ -39,12 +40,8 @@ DbusRemoteObjectMeta, ) from .dbus_common_funcs import get_default_bus -from .dbus_proxy_async_method import DbusLocalMethodAsync, DbusMethodAsync -from .dbus_proxy_async_property import ( - DbusLocalPropertyAsync, - DbusPropertyAsync, -) -from .dbus_proxy_async_signal import DbusLocalSignalAsync, DbusSignalAsync +from .dbus_proxy_async_method import DbusMethodAsync +from .dbus_proxy_async_property import DbusPropertyAsync from .sd_bus_internals import SdBusInterface if TYPE_CHECKING: @@ -61,8 +58,7 @@ Union, ) - from .dbus_common_elements import DbusBoundAsync - from .sd_bus_internals import SdBus, SdBusSlot + from .sd_bus_internals import SdBus T = TypeVar('T') Self = TypeVar('Self', bound="DbusInterfaceBaseAsync") @@ -239,15 +235,9 @@ def _map_dbus_elements( if attr.interface_name != interface_name: return - if isinstance(attr, DbusMethodAsync): - meta.dbus_member_to_python_attr[attr.method_name] = attr_name - meta.python_attr_to_dbus_member[attr_name] = attr.method_name - elif isinstance(attr, DbusPropertyAsync): - meta.dbus_member_to_python_attr[attr.property_name] = attr_name - meta.python_attr_to_dbus_member[attr_name] = attr.property_name - elif isinstance(attr, DbusSignalAsync): - meta.dbus_member_to_python_attr[attr.signal_name] = attr_name - meta.python_attr_to_dbus_member[attr_name] = attr.signal_name + if isinstance(attr, DbusMemberAsync): + meta.dbus_member_to_python_attr[attr.member_name] = attr_name + meta.python_attr_to_dbus_member[attr_name] = attr.member_name else: raise TypeError(f"Unknown D-Bus element: {attr!r}") @@ -344,23 +334,14 @@ def export_to_dbus( local_object_meta.attached_bus = bus local_object_meta.serving_object_path = object_path # TODO: can be optimized with a single loop - interface_map: Dict[str, List[DbusBoundAsync]] = {} + interface_map: Dict[str, List[DbusLocalMemberAsync]] = {} for key, value in getmembers(self): assert not isinstance(value, DbusMemberAsync) - if isinstance(value, DbusLocalMethodAsync): - interface_name = value.dbus_method.interface_name - if not value.dbus_method.serving_enabled: - continue - elif isinstance(value, DbusLocalPropertyAsync): - interface_name = value.dbus_property.interface_name - if not value.dbus_property.serving_enabled: - continue - elif isinstance(value, DbusLocalSignalAsync): - interface_name = value.dbus_signal.interface_name - if not value.dbus_signal.serving_enabled: - continue + if isinstance(value, DbusLocalMemberAsync) and \ + value.member.serving_enabled: + interface_name = value.member.interface_name else: continue @@ -372,54 +353,21 @@ def export_to_dbus( interface_member_list.append(value) + export_handle = DbusExportHandle() + for interface_name, member_list in interface_map.items(): new_interface = SdBusInterface() for dbus_something in member_list: - if isinstance(dbus_something, DbusLocalMethodAsync): - new_interface.add_method( - dbus_something.dbus_method.method_name, - dbus_something.dbus_method.input_signature, - dbus_something.dbus_method.input_args_names, - dbus_something.dbus_method.result_signature, - dbus_something.dbus_method.result_args_names, - dbus_something.dbus_method.flags, - dbus_something._dbus_reply_call, - ) - elif isinstance(dbus_something, DbusLocalPropertyAsync): - getter = dbus_something._dbus_reply_get - dbus_property = dbus_something.dbus_property - - if ( - dbus_property.property_setter is not None - and - dbus_property.property_setter_is_public - ): - setter = dbus_something._dbus_reply_set - else: - setter = None - - new_interface.add_property( - dbus_property.property_name, - dbus_property.property_signature, - getter, - setter, - dbus_property.flags, - ) - elif isinstance(dbus_something, DbusLocalSignalAsync): - new_interface.add_signal( - dbus_something.dbus_signal.signal_name, - dbus_something.dbus_signal.signal_signature, - dbus_something.dbus_signal.args_names, - dbus_something.dbus_signal.flags, - ) - else: - raise TypeError - + dbus_something._append_to_interface(new_interface, + export_handle) bus.add_interface(new_interface, object_path, interface_name) local_object_meta.activated_interfaces.append(new_interface) - return DbusExportHandle(local_object_meta) + assert new_interface.slot is not None + export_handle.append(new_interface.slot) + + return export_handle def _connect( self, @@ -475,13 +423,23 @@ def new_proxy( return new_object +class Closeable(Protocol): + def close(self) -> None: + ... + + class DbusExportHandle: - def __init__(self, local_meta: DbusLocalObjectMeta): - self._dbus_slots: List[SdBusSlot] = [ - i.slot - for i in local_meta.activated_interfaces - if i.slot is not None - ] + def __init__(self, *items: Closeable) -> None: + self._items = list(items) + + def append(self, item: Closeable) -> None: + self._items.append(item) + + def close(self) -> None: + while self._items: + self._items.pop().close() + + stop = close # for backwards compatibility async def __aenter__(self) -> DbusExportHandle: return self @@ -495,7 +453,7 @@ def __exit__( exc_value: Any, traceback: Any, ) -> None: - self.stop() + self.close() async def __aexit__( self, @@ -503,8 +461,4 @@ async def __aexit__( exc_value: Any, traceback: Any, ) -> None: - self.stop() - - def stop(self) -> None: - for slot in self._dbus_slots: - slot.close() + self.close() diff --git a/src/sdbus/dbus_proxy_async_method.py b/src/sdbus/dbus_proxy_async_method.py index d671df6..d8fd145 100644 --- a/src/sdbus/dbus_proxy_async_method.py +++ b/src/sdbus/dbus_proxy_async_method.py @@ -26,19 +26,24 @@ from weakref import ref as weak_ref from .dbus_common_elements import ( - DbusBoundAsync, + DbusBoundMember, + DbusLocalMemberAsync, DbusMemberAsync, DbusMethodCommon, DbusMethodOverride, + DbusProxyMemberAsync, DbusRemoteObjectMeta, ) from .dbus_exceptions import DbusFailedError -from .sd_bus_internals import DbusNoReplyFlag +from .sd_bus_internals import DbusNoReplyFlag, SdBusInterface if TYPE_CHECKING: from typing import Any, Callable, Optional, Sequence, Type, TypeVar, Union - from .dbus_proxy_async_interface_base import DbusInterfaceBaseAsync + from .dbus_proxy_async_interface_base import ( + DbusExportHandle, + DbusInterfaceBaseAsync, + ) from .sd_bus_internals import SdBusMessage T = TypeVar('T') @@ -85,19 +90,25 @@ def __get__( return self -class DbusBoundMethodAsyncBase(DbusBoundAsync): +class DbusBoundMethodAsyncBase(DbusBoundMember): + def __init__(self, dbus_method: DbusMethodAsync) -> None: + self.dbus_method = dbus_method + + @property + def member(self) -> DbusMemberAsync: + return self.dbus_method def __call__(self, *args: Any, **kwargs: Any) -> Any: raise NotImplementedError -class DbusProxyMethodAsync(DbusBoundMethodAsyncBase): +class DbusProxyMethodAsync(DbusBoundMethodAsyncBase, DbusProxyMemberAsync): def __init__( self, dbus_method: DbusMethodAsync, proxy_meta: DbusRemoteObjectMeta, ): - self.dbus_method = dbus_method + super().__init__(dbus_method) self.proxy_meta = proxy_meta self.__doc__ = dbus_method.__doc__ @@ -145,17 +156,32 @@ def __call__(self, *args: Any, **kwargs: Any) -> Any: return self._dbus_async_call(new_call_message) -class DbusLocalMethodAsync(DbusBoundMethodAsyncBase): +class DbusLocalMethodAsync(DbusBoundMethodAsyncBase, DbusLocalMemberAsync): def __init__( self, dbus_method: DbusMethodAsync, local_object: DbusInterfaceBaseAsync, ): - self.dbus_method = dbus_method + super().__init__(dbus_method) self.local_object_ref = weak_ref(local_object) self.__doc__ = dbus_method.__doc__ + def _append_to_interface( + self, + interface: SdBusInterface, + handle: DbusExportHandle, + ) -> None: + interface.add_method( + self.dbus_method.method_name, + self.dbus_method.input_signature, + self.dbus_method.input_args_names, + self.dbus_method.result_signature, + self.dbus_method.result_args_names, + self.dbus_method.flags, + self._dbus_reply_call, + ) + def __call__(self, *args: Any, **kwargs: Any) -> Any: local_object = self.local_object_ref() if local_object is None: @@ -232,6 +258,12 @@ async def _dbus_reply_call( reply_message.send() +# aliases for backwards compatibility +DbusMethodAsyncBaseBind = DbusBoundMethodAsyncBase +DbusMethodAsyncLocalBind = DbusLocalMethodAsync +DbusMethodAsyncProxyBind = DbusProxyMethodAsync + + def dbus_method_async( input_signature: str = "", result_signature: str = "", diff --git a/src/sdbus/dbus_proxy_async_object_manager.py b/src/sdbus/dbus_proxy_async_object_manager.py index 33f86b1..ecaef32 100644 --- a/src/sdbus/dbus_proxy_async_object_manager.py +++ b/src/sdbus/dbus_proxy_async_object_manager.py @@ -22,7 +22,6 @@ from functools import partial from typing import TYPE_CHECKING -from .dbus_common_elements import DbusLocalObjectMeta from .dbus_common_funcs import get_default_bus from .dbus_proxy_async_interface_base import ( DbusExportHandle, @@ -38,18 +37,9 @@ from .sd_bus_internals import SdBus, SdBusSlot -class DbusObjectManagerExportHandle(DbusExportHandle): - def __init__( - self, - local_meta: DbusLocalObjectMeta, - remove_object_call: Callable[[], None], - ): - super().__init__(local_meta) - self.remove_object_call = remove_object_call - - def stop(self) -> None: - super().stop() - self.remove_object_call() +class CloseableFromCallback: + def __init__(self, callback: Callable[[], None]) -> None: + self.close = callback class DbusObjectManagerInterfaceAsync( @@ -89,7 +79,7 @@ def export_to_dbus( ) slot = bus.add_object_manager(object_path) self._object_manager_slot = slot - export_handle._dbus_slots.append(slot) + export_handle.append(slot) return export_handle def export_with_manager( @@ -97,28 +87,25 @@ def export_with_manager( object_path: str, object_to_export: DbusInterfaceBaseAsync, bus: Optional[SdBus] = None, - ) -> DbusObjectManagerExportHandle: + ) -> DbusExportHandle: if self._object_manager_slot is None: raise RuntimeError('ObjectManager not intitialized') if bus is None: bus = get_default_bus() - object_to_export.export_to_dbus( + export_handle = object_to_export.export_to_dbus( object_path, bus, ) - meta = object_to_export._dbus - if not isinstance(meta, DbusLocalObjectMeta): - raise TypeError - handle = DbusObjectManagerExportHandle( - meta, - partial(self.remove_managed_object, object_to_export), + export_handle.append( + CloseableFromCallback( + partial(self.remove_managed_object, object_to_export), + ) ) bus.emit_object_added(object_path) self._managed_object_to_path[object_to_export] = object_path - - return handle + return export_handle def remove_managed_object( self, diff --git a/src/sdbus/dbus_proxy_async_property.py b/src/sdbus/dbus_proxy_async_property.py index 9cac940..d8adfc4 100644 --- a/src/sdbus/dbus_proxy_async_property.py +++ b/src/sdbus/dbus_proxy_async_property.py @@ -25,18 +25,23 @@ from weakref import ref as weak_ref from .dbus_common_elements import ( - DbusBoundAsync, + DbusBoundMember, + DbusLocalMemberAsync, DbusMemberAsync, DbusPropertyCommon, DbusPropertyOverride, + DbusProxyMemberAsync, DbusRemoteObjectMeta, ) if TYPE_CHECKING: from typing import Any, Callable, Generator, Optional, Type, Union - from .dbus_proxy_async_interface_base import DbusInterfaceBaseAsync - from .sd_bus_internals import SdBusMessage + from .dbus_proxy_async_interface_base import ( + DbusExportHandle, + DbusInterfaceBaseAsync, + ) + from .sd_bus_internals import SdBusInterface, SdBusMessage T = TypeVar('T') @@ -126,7 +131,14 @@ def setter_private( self.property_setter_is_public = False -class DbusBoundPropertyAsyncBase(DbusBoundAsync, Awaitable[T]): +class DbusBoundPropertyAsyncBase(DbusBoundMember, Awaitable[T]): + def __init__(self, dbus_property: DbusPropertyAsync[T]) -> None: + self.dbus_property = dbus_property + + @property + def member(self) -> DbusMemberAsync: + return self.dbus_property + def __await__(self) -> Generator[Any, None, T]: return self.get_async().__await__() @@ -137,13 +149,16 @@ async def set_async(self, complete_object: T) -> None: raise NotImplementedError -class DbusProxyPropertyAsync(DbusBoundPropertyAsyncBase[T]): +class DbusProxyPropertyAsync( + DbusBoundPropertyAsyncBase[T], + DbusProxyMemberAsync, +): def __init__( self, dbus_property: DbusPropertyAsync[T], proxy_meta: DbusRemoteObjectMeta, ): - self.dbus_property = dbus_property + super().__init__(dbus_property) self.proxy_meta = proxy_meta self.__doc__ = dbus_property.__doc__ @@ -179,17 +194,46 @@ async def set_async(self, complete_object: T) -> None: await bus.call_async(new_set_message) -class DbusLocalPropertyAsync(DbusBoundPropertyAsyncBase[T]): +class DbusLocalPropertyAsync( + DbusBoundPropertyAsyncBase[T], + DbusLocalMemberAsync, +): def __init__( self, dbus_property: DbusPropertyAsync[T], local_object: DbusInterfaceBaseAsync, ): - self.dbus_property = dbus_property + super().__init__(dbus_property) self.local_object_ref = weak_ref(local_object) self.__doc__ = dbus_property.__doc__ + def _append_to_interface( + self, + interface: SdBusInterface, + handle: DbusExportHandle, + ) -> None: + getter = self._dbus_reply_get + dbus_property = self.dbus_property + + if ( + dbus_property.property_setter is not None + and + dbus_property.property_setter_is_public + ): + + setter = self._dbus_reply_set + else: + setter = None + + interface.add_property( + dbus_property.property_name, + dbus_property.property_signature, + getter, + setter, + dbus_property.flags, + ) + async def get_async(self) -> T: local_object = self.local_object_ref() if local_object is None: @@ -271,6 +315,12 @@ def _dbus_reply_set(self, message: SdBusMessage) -> None: ) +# aliases for backwards compatibility +DbusPropertyAsyncBaseBind = DbusBoundPropertyAsyncBase +DbusPropertyAsyncLocalBind = DbusLocalPropertyAsync +DbusPropertyAsyncProxyBind = DbusProxyPropertyAsync + + def dbus_property_async( property_signature: str = "", flags: int = 0, diff --git a/src/sdbus/dbus_proxy_async_signal.py b/src/sdbus/dbus_proxy_async_signal.py index 8de4c25..5bf94e2 100644 --- a/src/sdbus/dbus_proxy_async_signal.py +++ b/src/sdbus/dbus_proxy_async_signal.py @@ -34,9 +34,11 @@ from weakref import WeakSet from .dbus_common_elements import ( - DbusBoundAsync, + DbusBoundMember, + DbusLocalMemberAsync, DbusLocalObjectMeta, DbusMemberAsync, + DbusProxyMemberAsync, DbusRemoteObjectMeta, DbusSignalCommon, ) @@ -45,8 +47,16 @@ if TYPE_CHECKING: from typing import Any, Callable, Optional, Sequence, Tuple, Type, Union - from .dbus_proxy_async_interface_base import DbusInterfaceBaseAsync - from .sd_bus_internals import SdBus, SdBusMessage, SdBusSlot + from .dbus_proxy_async_interface_base import ( + DbusExportHandle, + DbusInterfaceBaseAsync, + ) + from .sd_bus_internals import ( + SdBus, + SdBusInterface, + SdBusMessage, + SdBusSlot, + ) T = TypeVar('T') @@ -131,7 +141,14 @@ async def catch_anywhere( ) -class DbusBoundSignalAsyncBase(DbusBoundAsync, AsyncIterable[T], Generic[T]): +class DbusBoundSignalAsyncBase(DbusBoundMember, AsyncIterable[T], Generic[T]): + def __init__(self, dbus_signal: DbusSignalAsync[T]) -> None: + self.dbus_signal = dbus_signal + + @property + def member(self) -> DbusMemberAsync: + return self.dbus_signal + async def catch(self) -> AsyncIterator[T]: raise NotImplementedError yield cast(T, None) @@ -150,13 +167,13 @@ def emit(self, args: T) -> None: raise NotImplementedError -class DbusProxySignalAsync(DbusBoundSignalAsyncBase[T]): +class DbusProxySignalAsync(DbusBoundSignalAsyncBase[T], DbusProxyMemberAsync): def __init__( self, dbus_signal: DbusSignalAsync[T], proxy_meta: DbusRemoteObjectMeta, ): - self.dbus_signal = dbus_signal + super().__init__(dbus_signal) self.proxy_meta = proxy_meta self.__doc__ = dbus_signal.__doc__ @@ -224,17 +241,29 @@ def emit(self, args: T) -> None: raise RuntimeError("Cannot emit signal from D-Bus proxy.") -class DbusLocalSignalAsync(DbusBoundSignalAsyncBase[T]): +class DbusLocalSignalAsync(DbusBoundSignalAsyncBase[T], DbusLocalMemberAsync): def __init__( self, dbus_signal: DbusSignalAsync[T], local_meta: DbusLocalObjectMeta, ): - self.dbus_signal = dbus_signal + super().__init__(dbus_signal) self.local_meta = local_meta self.__doc__ = dbus_signal.__doc__ + def _append_to_interface( + self, + interface: SdBusInterface, + handle: DbusExportHandle, + ) -> None: + interface.add_signal( + self.dbus_signal.signal_name, + self.dbus_signal.signal_signature, + self.dbus_signal.args_names, + self.dbus_signal.flags, + ) + async def catch(self) -> AsyncIterator[T]: new_queue: Queue[T] = Queue() diff --git a/src/sdbus/dbus_proxy_sync_method.py b/src/sdbus/dbus_proxy_sync_method.py index 82e2d71..c997a0e 100644 --- a/src/sdbus/dbus_proxy_sync_method.py +++ b/src/sdbus/dbus_proxy_sync_method.py @@ -24,7 +24,7 @@ from typing import TYPE_CHECKING, TypeVar, cast from .dbus_common_elements import ( - DbusBoundSync, + DbusBoundMember, DbusMemberSync, DbusMethodCommon, ) @@ -45,7 +45,7 @@ def __get__(self, return DbusLocalMethodSync(self, obj) -class DbusLocalMethodSync(DbusBoundSync): +class DbusLocalMethodSync(DbusBoundMember): def __init__(self, dbus_method: DbusMethodSync, interface: DbusInterfaceBase): @@ -54,6 +54,10 @@ def __init__(self, self.__doc__ = dbus_method.__doc__ + @property + def member(self) -> DbusMemberSync: + return self.dbus_method + def _call_dbus_sync(self, *args: Any) -> Any: new_call_message = ( self.interface._dbus.attached_bus.new_method_call_message(