Skip to content

Commit b44d565

Browse files
committed
New export_to_dbus algorithm using metadata
Instead of iterating over all members to find D-Bus elements use the `_dbus_iter_interfaces_meta` to iterate over the metadata and then `getattr` the exact Python attributes containing the D-Bus elements. This makes the algorithm faster as it won't have to go through all members as well as allows for D-Bus interfaces without any members to be exported. Raise an error if no interfaces got exported to prevent unexpected errors except for ObjectManager where libsystemd allow exporting without any extra interfaces.
1 parent 4dac6ca commit b44d565

File tree

4 files changed

+36
-36
lines changed

4 files changed

+36
-36
lines changed

src/sdbus/dbus_proxy_async_interface_base.py

Lines changed: 18 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121

2222
from collections.abc import Callable
2323
from copy import copy
24-
from inspect import getmembers
2524
from itertools import chain
2625
from types import MethodType
2726
from typing import TYPE_CHECKING, Any, cast
@@ -52,7 +51,6 @@
5251
from collections.abc import Iterable, Iterator
5352
from typing import Optional, TypeVar, Union
5453

55-
from .dbus_common_elements import DbusBoundAsync
5654
from .sd_bus_internals import SdBus, SdBusSlot
5755

5856
T = TypeVar('T')
@@ -312,12 +310,14 @@ async def start_serving(self,
312310
DeprecationWarning)
313311
self.export_to_dbus(object_path, bus)
314312

313+
def _dbus_on_no_members_exported(self) -> None:
314+
raise ValueError("No D-Bus interfaces were exported")
315+
315316
def export_to_dbus(
316317
self,
317318
object_path: str,
318319
bus: Optional[SdBus] = None,
319320
) -> DbusExportHandle:
320-
321321
local_object_meta = self._dbus
322322
if isinstance(local_object_meta, DbusRemoteObjectMeta):
323323
raise RuntimeError("Cannot export D-Bus proxies.")
@@ -334,38 +334,17 @@ def export_to_dbus(
334334

335335
local_object_meta.attached_bus = bus
336336
local_object_meta.serving_object_path = object_path
337-
# TODO: can be optimized with a single loop
338-
interface_map: dict[str, list[DbusBoundAsync]] = {}
339-
340-
for key, value in getmembers(self):
341-
assert not isinstance(value, DbusMemberAsync)
342-
343-
if isinstance(value, DbusLocalMethodAsync):
344-
interface_name = value.dbus_method.interface_name
345-
if not value.dbus_method.serving_enabled:
346-
continue
347-
elif isinstance(value, DbusLocalPropertyAsync):
348-
interface_name = value.dbus_property.interface_name
349-
if not value.dbus_property.serving_enabled:
350-
continue
351-
elif isinstance(value, DbusLocalSignalAsync):
352-
interface_name = value.dbus_signal.interface_name
353-
if not value.dbus_signal.serving_enabled:
354-
continue
355-
else:
356-
continue
357-
358-
try:
359-
interface_member_list = interface_map[interface_name]
360-
except KeyError:
361-
interface_member_list = []
362-
interface_map[interface_name] = interface_member_list
363337

364-
interface_member_list.append(value)
338+
for interface_name, meta in self._dbus_iter_interfaces_meta():
339+
if not meta.serving_enabled:
340+
continue
365341

366-
for interface_name, member_list in interface_map.items():
367342
new_interface = SdBusInterface()
368-
for dbus_something in member_list:
343+
344+
for python_attr, dbus_member in (
345+
meta.python_attr_to_dbus_member.items()
346+
):
347+
dbus_something = getattr(self, python_attr)
369348
if isinstance(dbus_something, DbusLocalMethodAsync):
370349
new_interface.add_method(
371350
dbus_something.dbus_method.method_name,
@@ -404,12 +383,16 @@ def export_to_dbus(
404383
dbus_something.dbus_signal.flags,
405384
)
406385
else:
407-
raise TypeError
386+
raise TypeError(
387+
"Expected D-Bus element, got: {dbus_something!r}"
388+
)
408389

409-
bus.add_interface(new_interface, object_path,
410-
interface_name)
390+
bus.add_interface(new_interface, object_path, interface_name)
411391
local_object_meta.activated_interfaces.append(new_interface)
412392

393+
if not local_object_meta.activated_interfaces:
394+
self._dbus_on_no_members_exported()
395+
413396
return DbusExportHandle(local_object_meta)
414397

415398
def _connect(

src/sdbus/dbus_proxy_async_object_manager.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ def interfaces_added(self) -> tuple[str, dict[str, dict[str, Any]]]:
7676
def interfaces_removed(self) -> tuple[str, list[str]]:
7777
raise NotImplementedError
7878

79+
def _dbus_on_no_members_exported(self) -> None:
80+
... # Object manager is allowed to be exported empty
81+
7982
def export_to_dbus(
8083
self,
8184
object_path: str,

test/test_sdbus_async.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1017,3 +1017,17 @@ async def test_python_exc(self) -> None:
10171017

10181018
with self.assertRaisesRegex(ValueError, "Test!"):
10191019
await test_object_connection.raise_python_exc()
1020+
1021+
async def test_empty_dbus_interface(self) -> None:
1022+
class Empty(
1023+
DbusInterfaceCommonAsync,
1024+
interface_name="org.empty",
1025+
):
1026+
...
1027+
1028+
empty_local = Empty()
1029+
empty_local.export_to_dbus("/")
1030+
empty_proxy = Empty.new_proxy(TEST_SERVICE_NAME, "/")
1031+
1032+
intro = await empty_proxy.dbus_introspect()
1033+
self.assertIn('<interface name="org.empty">', intro)

test/test_sdbus_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ def test_inspect_dbus_path_async_proxy(self) -> None:
212212
inspect_dbus_path(proxy, new_bus)
213213

214214
def test_inspect_dbus_path_async_local(self) -> None:
215-
local_obj = DbusInterfaceCommonAsync()
215+
local_obj = FooBarAsync()
216216

217217
with self.assertRaisesRegex(
218218
LookupError, "is not exported to any D-Bus",

0 commit comments

Comments
 (0)