Skip to content

Commit 04e7c04

Browse files
author
Tony Crisci
committed
service: async properties
fixes #86
1 parent 797edbc commit 04e7c04

8 files changed

Lines changed: 333 additions & 105 deletions

File tree

dbus_next/_private/util.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from typing import List, Any, Union
2+
import inspect
23
from ..signature import SignatureTree, Variant
4+
import ast
35

46

57
def signature_contains_type(signature: Union[str, SignatureTree], body: List[Any],
@@ -91,6 +93,33 @@ def _replace(idx):
9193
return body
9294

9395

96+
def parse_annotation(annotation: str) -> str:
97+
'''
98+
Because of PEP 563, if `from __future__ import annotations` is used in code
99+
or on Python version >=3.10 where this is the default, return annotations
100+
from the `inspect` module will return annotations as "forward definitions".
101+
In this case, we must eval the result which we do only when given a string
102+
constant.
103+
'''
104+
def raise_value_error():
105+
raise ValueError(f'service annotations must be a string constant (got {annotation})')
106+
107+
if not annotation or annotation is inspect.Signature.empty:
108+
return ''
109+
if type(annotation) is not str:
110+
raise_value_error()
111+
try:
112+
body = ast.parse(annotation).body
113+
if len(body) == 1 and type(body[0].value) is ast.Constant:
114+
if type(body[0].value.value) is not str:
115+
raise_value_error()
116+
return body[0].value.value
117+
except SyntaxError:
118+
pass
119+
120+
return annotation
121+
122+
94123
def _replace_fds(body_obj: List[Any], children, replace_fn):
95124
'''Replace any type 'h' with the value returned by replace_fn() given the
96125
value of the fd field. This is used by the high level interfaces which

dbus_next/aio/message_bus.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -341,8 +341,7 @@ async def wait_for_disconnect(self):
341341
"""
342342
return await self._disconnect_future
343343

344-
@classmethod
345-
def _make_method_handler(cls, interface, method):
344+
def _make_method_handler(self, interface, method):
346345
if not asyncio.iscoroutinefunction(method.fn):
347346
return super()._make_method_handler(interface, method)
348347

dbus_next/message_bus.py

Lines changed: 103 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from .signature import Variant
99
from .proxy_object import BaseProxyObject
1010
from . import introspection as intr
11-
from contextlib import suppress
1211

1312
import inspect
1413
import socket
@@ -222,20 +221,27 @@ def _emit_interface_added(self, path, interface):
222221
if self._disconnected:
223222
return
224223

225-
body = {interface.name: {}}
226-
properties = interface._get_properties(interface)
224+
def get_properties_callback(interface, result, user_data, e):
225+
if e is not None:
226+
try:
227+
raise e
228+
except Exception:
229+
logging.error(
230+
'An exception ocurred when emitting ObjectManager.InterfacesAdded for %s. '
231+
'Some properties will not be included in the signal.',
232+
interface.name,
233+
exc_info=True)
227234

228-
for prop in properties:
229-
with suppress(Exception):
230-
body[interface.name][prop.name] = Variant(prop.signature,
231-
prop.prop_getter(interface))
235+
body = {interface.name: result}
232236

233-
self.send(
234-
Message.new_signal(path=path,
235-
interface='org.freedesktop.DBus.ObjectManager',
236-
member='InterfacesAdded',
237-
signature='oa{sa{sv}}',
238-
body=[path, body]))
237+
self.send(
238+
Message.new_signal(path=path,
239+
interface='org.freedesktop.DBus.ObjectManager',
240+
member='InterfacesAdded',
241+
signature='oa{sa{sv}}',
242+
body=[path, body]))
243+
244+
ServiceInterface._get_all_property_values(interface, get_properties_callback)
239245

240246
def _emit_interface_removed(self, path, removed_interfaces):
241247
"""Emit the ``org.freedesktop.DBus.ObjectManager.InterfacesRemoved` signal.
@@ -630,7 +636,7 @@ def __call__(self, reply):
630636

631637
bus.send(reply)
632638

633-
def __exit__(self, exc_type, exc_value, tb):
639+
def _exit(self, exc_type, exc_value, tb):
634640
if exc_type is None:
635641
return
636642

@@ -646,6 +652,12 @@ def __exit__(self, exc_type, exc_value, tb):
646652
))
647653
return True
648654

655+
def __exit__(self, exc_type, exc_value, tb):
656+
self._exit(exc_type, exc_value, tb)
657+
658+
def send_error(self, exc):
659+
self._exit(exc.__class__, exc, exc.__traceback__)
660+
649661
return SendReply()
650662

651663
def _process_message(self, msg):
@@ -713,8 +725,7 @@ def _process_message(self, msg):
713725
handler(msg, None)
714726
del self._method_return_handlers[msg.reply_serial]
715727

716-
@classmethod
717-
def _make_method_handler(cls, interface, method):
728+
def _make_method_handler(self, interface, method):
718729
def handler(msg, send_reply):
719730
args = ServiceInterface._msg_body_to_args(msg)
720731
result = method.fn(interface, *args)
@@ -792,16 +803,51 @@ def reply_handler(reply, err):
792803

793804
def _default_get_managed_objects_handler(self, msg, send_reply):
794805
result = {}
795-
796-
for node in self._path_exports:
797-
if not node.startswith(msg.path + '/') and msg.path != '/':
798-
continue
799-
806+
result_signature = 'a{oa{sa{sv}}}'
807+
error_handled = False
808+
809+
def is_result_complete():
810+
if not result:
811+
return True
812+
for n, interfaces in result.items():
813+
for value in interfaces.values():
814+
if value is None:
815+
return False
816+
817+
return True
818+
819+
nodes = [
820+
node for node in self._path_exports
821+
if msg.path == '/' or node.startswith(msg.path + '/')
822+
]
823+
824+
# first build up the result object to know when it's complete
825+
for node in nodes:
800826
result[node] = {}
801827
for interface in self._path_exports[node]:
802-
result[node][interface.name] = self._get_all_properties(interface)
828+
result[node][interface.name] = None
829+
830+
if is_result_complete():
831+
send_reply(Message.new_method_return(msg, result_signature, [result]))
832+
return
833+
834+
def get_all_properties_callback(interface, values, node, err):
835+
nonlocal error_handled
836+
if err is not None:
837+
if not error_handled:
838+
error_handled = True
839+
send_reply.send_error(err)
840+
return
803841

804-
send_reply(Message.new_method_return(msg, 'a{oa{sa{sv}}}', [result]))
842+
result[node][interface.name] = values
843+
844+
if is_result_complete():
845+
send_reply(Message.new_method_return(msg, result_signature, [result]))
846+
847+
for node in nodes:
848+
for interface in self._path_exports[node]:
849+
ServiceInterface._get_all_property_values(interface, get_all_properties_callback,
850+
node)
805851

806852
def _default_properties_handler(self, msg, send_reply):
807853
methods = {'Get': 'ss', 'Set': 'ssv', 'GetAll': 's'}
@@ -858,14 +904,24 @@ def _default_properties_handler(self, msg, send_reply):
858904
if not prop.access.readable():
859905
raise DBusError(ErrorType.UNKNOWN_PROPERTY,
860906
'the property does not have read access')
861-
prop_value = getattr(interface, prop.prop_getter.__name__)
862907

863-
body, unix_fds = replace_fds_with_idx(prop.signature, [prop_value])
908+
def get_property_callback(interface, prop, prop_value, err):
909+
try:
910+
if err is not None:
911+
send_reply.send_error(err)
912+
return
913+
914+
body, unix_fds = replace_fds_with_idx(prop.signature, [prop_value])
915+
916+
send_reply(
917+
Message.new_method_return(msg,
918+
'v', [Variant(prop.signature, body[0])],
919+
unix_fds=unix_fds))
920+
except Exception as e:
921+
send_reply.send_error(e)
922+
923+
ServiceInterface._get_property_value(interface, prop, get_property_callback)
864924

865-
send_reply(
866-
Message.new_method_return(msg,
867-
'v', [Variant(prop.signature, body[0])],
868-
unix_fds=unix_fds))
869925
elif msg.member == 'Set':
870926
if not prop.access.writable():
871927
raise DBusError(ErrorType.PROPERTY_READ_ONLY, 'the property is readonly')
@@ -874,27 +930,30 @@ def _default_properties_handler(self, msg, send_reply):
874930
raise DBusError(ErrorType.INVALID_SIGNATURE,
875931
f'wrong signature for property. expected "{prop.signature}"')
876932
assert prop.prop_setter
933+
934+
def set_property_callback(interface, prop, err):
935+
if err is not None:
936+
send_reply.send_error(err)
937+
return
938+
send_reply(Message.new_method_return(msg))
939+
877940
body = replace_idx_with_fds(value.signature, [value.value], msg.unix_fds)
878-
setattr(interface, prop.prop_setter.__name__, body[0])
879-
send_reply(Message.new_method_return(msg))
941+
ServiceInterface._set_property_value(interface, prop, body[0],
942+
set_property_callback)
880943

881944
elif msg.member == 'GetAll':
882-
body, unix_fds = replace_fds_with_idx('a{sv}', [self._get_all_properties(interface)])
883-
send_reply(Message.new_method_return(msg, 'a{sv}', body, unix_fds=unix_fds))
884945

885-
else:
886-
assert False
887-
888-
def _get_all_properties(self, interface):
889-
result = {}
946+
def get_all_properties_callback(interface, values, user_data, err):
947+
if err is not None:
948+
send_reply.send_error(err)
949+
return
950+
body, unix_fds = replace_fds_with_idx('a{sv}', [values])
951+
send_reply(Message.new_method_return(msg, 'a{sv}', body, unix_fds=unix_fds))
890952

891-
for prop in ServiceInterface._get_properties(interface):
892-
if prop.disabled or not prop.access.readable():
893-
continue
894-
result[prop.name] = Variant(prop.signature, getattr(interface,
895-
prop.prop_getter.__name__))
953+
ServiceInterface._get_all_property_values(interface, get_all_properties_callback)
896954

897-
return result
955+
else:
956+
assert False
898957

899958
def _init_high_level_client(self):
900959
'''The high level client is initialized when the first proxy object is

0 commit comments

Comments
 (0)