Skip to content

Commit a7758c3

Browse files
author
Anders Hellerup Madsen
committed
support signatures in dbus_property decorator
The builtin @decorator property recreates itself when the .setter, .getter or .deleter child-decorators are used. This meant that the introspection meant for the getter was also being applied to the setter. I added a guard for this situation to exit early, and instead copy the already introspected properties over from the getter.
1 parent 93bbd7c commit a7758c3

2 files changed

Lines changed: 57 additions & 16 deletions

File tree

dbus_next/service.py

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -213,18 +213,25 @@ def set_options(self, options):
213213
self.__dict__['__DBUS_PROPERTY'] = True
214214

215215
def __init__(self, fn, *args, **kwargs):
216+
if args:
217+
# this is a call to prop.setter - all we need to do call super
218+
return super().__init__(fn, *args, **kwargs)
219+
216220
self.prop_getter = fn
217221
self.prop_setter = None
218222

219223
inspection = inspect.signature(fn)
224+
220225
if len(inspection.parameters) != 1:
221226
raise ValueError('the property must only have the "self" input parameter')
222227

223-
return_annotation = parse_annotation(inspection.return_annotation)
228+
return_annotation = kwargs.pop('signature', None)
229+
if return_annotation is None:
230+
return_annotation = parse_annotation(inspection.return_annotation)
224231

225232
if not return_annotation:
226233
raise ValueError(
227-
'the property must specify the dbus type string as a return annotation string')
234+
'the property must specify the dbus type string as a return annotation string or with the signature option')
228235

229236
self.signature = return_annotation
230237
tree = SignatureTree._get(return_annotation)
@@ -234,26 +241,40 @@ def __init__(self, fn, *args, **kwargs):
234241

235242
self.type = tree.types[0]
236243

237-
if 'options' in kwargs:
238-
options = kwargs['options']
244+
options = kwargs.pop('options', None)
245+
if options is not None:
239246
self.set_options(options)
240-
del kwargs['options']
241247

242248
super().__init__(fn, *args, **kwargs)
243249

244250
def setter(self, fn, **kwargs):
245251
# XXX The setter decorator seems to be recreating the class in the list
246252
# of class members and clobbering the options so we need to reset them.
247253
# Why does it do that?
254+
#
255+
# The default implementation of setter basically looks like this:
256+
#
257+
# def setter(self, fset):
258+
# return type(self)(self.fget, fset, self.fdel)
259+
#
260+
# That is it creates a new instance, with the new setter, carrying
261+
# the getter and deleter over from the the existing instance.
262+
#
263+
# In this case, we need to carry all the private properties from the
264+
# old instance and reset the options on the new instance.
248265
result = super().setter(fn, **kwargs)
266+
result.prop_getter = self.prop_getter
249267
result.prop_setter = fn
268+
result.signature = self.signature
269+
result.type = self.type
250270
result.set_options(self.options)
251271
return result
252272

253273

254274
def dbus_property(access: PropertyAccess = PropertyAccess.READWRITE,
255275
name: str = None,
256-
disabled: bool = False):
276+
disabled: bool = False,
277+
signature: Optional[str] = None):
257278
"""A decorator to mark a class method of a :class:`ServiceInterface` to be a DBus property.
258279
259280
The class method must be a Python getter method with a return annotation
@@ -301,7 +322,7 @@ def string_prop(self, val: 's'):
301322
@no_type_check_decorator
302323
def decorator(fn):
303324
options = {'name': name, 'access': access, 'disabled': disabled}
304-
return _Property(fn, options=options)
325+
return _Property(fn, options=options, signature=signature)
305326

306327
return decorator
307328

test/service/test_decorators.py

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ def __init__(self):
1010
self._some_prop = 55
1111
self._another_prop = 101
1212
self._weird_prop = 500
13+
self._foo_prop = 17
1314

1415
@method()
1516
def some_method(self, one: 's', two: 's') -> 's':
@@ -19,10 +20,6 @@ def some_method(self, one: 's', two: 's') -> 's':
1920
def another_method(self, eight: 'o', six: 't'):
2021
pass
2122

22-
@method(in_signature="sasu", out_signature="i")
23-
def a_third_method(self, one: str, two: List[str], three) -> int:
24-
return 42
25-
2623
@signal()
2724
def some_signal(self) -> 'as':
2825
return ['result']
@@ -53,6 +50,18 @@ def weird_prop(self) -> 't':
5350
def setter_for_weird_prop(self, val: 't'):
5451
self._weird_prop = val
5552

53+
@method(in_signature="sasu", out_signature="i")
54+
def a_third_method(self, one: str, two: List[str], three) -> int:
55+
return 42
56+
57+
@dbus_property(signature='u')
58+
def foo_prop(self) -> int:
59+
return self._foo_prop
60+
61+
@foo_prop.setter
62+
def foo_prop(self, val: int):
63+
self._foo_prop = val
64+
5665

5766
def test_method_decorator():
5867
interface = ExampleInterface()
@@ -101,7 +110,7 @@ def test_method_decorator():
101110
assert not signal.disabled
102111
assert type(signal.introspection) is intr.Signal
103112

104-
assert len(properties) == 3
113+
assert len(properties) == 4
105114

106115
renamed_readonly_prop = properties[0]
107116
assert renamed_readonly_prop.name == 'renamed_readonly_property'
@@ -110,7 +119,18 @@ def test_method_decorator():
110119
assert renamed_readonly_prop.disabled
111120
assert type(renamed_readonly_prop.introspection) is intr.Property
112121

113-
weird_prop = properties[1]
122+
foo_prop = properties[1]
123+
assert foo_prop.name == 'foo_prop'
124+
assert foo_prop.access == PropertyAccess.READWRITE
125+
assert foo_prop.signature == 'u'
126+
assert not foo_prop.disabled
127+
assert foo_prop.prop_getter is not None
128+
assert foo_prop.prop_getter.__name__ == 'foo_prop'
129+
assert foo_prop.prop_setter is not None
130+
assert foo_prop.prop_setter.__name__ == 'foo_prop'
131+
assert type(foo_prop.introspection) is intr.Property
132+
133+
weird_prop = properties[2]
114134
assert weird_prop.name == 'weird_prop'
115135
assert weird_prop.access == PropertyAccess.READWRITE
116136
assert weird_prop.signature == 't'
@@ -121,7 +141,7 @@ def test_method_decorator():
121141
assert weird_prop.prop_setter.__name__ == 'setter_for_weird_prop'
122142
assert type(weird_prop.introspection) is intr.Property
123143

124-
prop = properties[2]
144+
prop = properties[3]
125145
assert prop.name == 'some_prop'
126146
assert prop.access == PropertyAccess.READWRITE
127147
assert prop.signature == 'u'
@@ -157,7 +177,7 @@ def test_interface_introspection():
157177
signals = xml.findall('signal')
158178
properties = xml.findall('property')
159179

160-
assert len(xml) == 5
180+
assert len(xml) == 6
161181
assert len(methods) == 2
162182
assert len(signals) == 1
163-
assert len(properties) == 2
183+
assert len(properties) == 3

0 commit comments

Comments
 (0)