Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 36 additions & 4 deletions src/sdbus/dbus_common_elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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):
...
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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):
...


Expand Down
122 changes: 38 additions & 84 deletions src/sdbus/dbus_proxy_async_interface_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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:
Expand All @@ -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")
Expand Down Expand Up @@ -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}")

Expand Down Expand Up @@ -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

Expand All @@ -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,
Expand Down Expand Up @@ -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:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why the close method is needed. The context manager is already implemented for DbusExportHandle.

https://docs.python.org/3/library/contextlib.html#contextlib.closing

Most types managing resources support the context manager protocol, which closes thing on leaving the with statement. As such, closing() is most useful for third party types that don’t support context managers

Also close might be misleading because the bus objects use it to close the connections to D-Bus but here it does not actually close the D-Bus.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • I made the DbusExportHandle a bit more generic; you can insert any type into it, which has the standard Python's close() method. Which fits nicely with the SdBus slot, which also implements this.
  • Makes it more Pythonic in my opinion, as most Python types implement both the context manager protocol and the close() method (so it can be easily cleaned-up manually by calling the "close" method, instead of calling __(a)exit__() if not bound to a context)
  • Not sure how close() might mislead user to thinking it closes the connection, when the method is being on an "export handle".

If you don't like it, or think the change is not worth it, I am ok with removing it. I can live without it, but it still seems to me as an improvement to the code.

while self._items:
self._items.pop().close()

stop = close # for backwards compatibility

async def __aenter__(self) -> DbusExportHandle:
return self
Expand All @@ -495,16 +453,12 @@ def __exit__(
exc_value: Any,
traceback: Any,
) -> None:
self.stop()
self.close()

async def __aexit__(
self,
exc_type: Any,
exc_value: Any,
traceback: Any,
) -> None:
self.stop()

def stop(self) -> None:
for slot in self._dbus_slots:
slot.close()
self.close()
48 changes: 40 additions & 8 deletions src/sdbus/dbus_proxy_async_method.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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__
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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 = "",
Expand Down
Loading