Skip to content

Commit f158687

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 ffb374c commit f158687

2 files changed

Lines changed: 58 additions & 16 deletions

File tree

dbus_next/service.py

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -228,18 +228,26 @@ def set_options(self, options):
228228
self.__dict__['__DBUS_PROPERTY'] = True
229229

230230
def __init__(self, fn, *args, **kwargs):
231+
if args:
232+
# this is a call to prop.setter - all we need to do call super
233+
return super().__init__(fn, *args, **kwargs)
234+
231235
self.prop_getter = fn
232236
self.prop_setter = None
233237

234238
inspection = inspect.signature(fn)
239+
235240
if len(inspection.parameters) != 1:
236241
raise ValueError('the property must only have the "self" input parameter')
237242

238-
return_annotation = parse_annotation(inspection.return_annotation)
243+
return_annotation = kwargs.pop('signature', None)
244+
if return_annotation is None:
245+
return_annotation = parse_annotation(inspection.return_annotation)
239246

240247
if not return_annotation:
241248
raise ValueError(
242-
'the property must specify the dbus type string as a return annotation string')
249+
'the property must specify the dbus type string as a return annotation string or with the signature option'
250+
)
243251

244252
self.signature = return_annotation
245253
tree = SignatureTree._get(return_annotation)
@@ -249,26 +257,40 @@ def __init__(self, fn, *args, **kwargs):
249257

250258
self.type = tree.types[0]
251259

252-
if 'options' in kwargs:
253-
options = kwargs['options']
260+
options = kwargs.pop('options', None)
261+
if options is not None:
254262
self.set_options(options)
255-
del kwargs['options']
256263

257264
super().__init__(fn, *args, **kwargs)
258265

259266
def setter(self, fn, **kwargs):
260267
# XXX The setter decorator seems to be recreating the class in the list
261268
# of class members and clobbering the options so we need to reset them.
262269
# Why does it do that?
270+
#
271+
# The default implementation of setter basically looks like this:
272+
#
273+
# def setter(self, fset):
274+
# return type(self)(self.fget, fset, self.fdel)
275+
#
276+
# That is it creates a new instance, with the new setter, carrying
277+
# the getter and deleter over from the the existing instance.
278+
#
279+
# In this case, we need to carry all the private properties from the
280+
# old instance and reset the options on the new instance.
263281
result = super().setter(fn, **kwargs)
282+
result.prop_getter = self.prop_getter
264283
result.prop_setter = fn
284+
result.signature = self.signature
285+
result.type = self.type
265286
result.set_options(self.options)
266287
return result
267288

268289

269290
def dbus_property(access: PropertyAccess = PropertyAccess.READWRITE,
270291
name: str = None,
271-
disabled: bool = False):
292+
disabled: bool = False,
293+
signature: Optional[str] = None):
272294
"""A decorator to mark a class method of a :class:`ServiceInterface` to be a DBus property.
273295
274296
The class method must be a Python getter method with a return annotation
@@ -316,7 +338,7 @@ def string_prop(self, val: 's'):
316338
@no_type_check_decorator
317339
def decorator(fn):
318340
options = {'name': name, 'access': access, 'disabled': disabled}
319-
return _Property(fn, options=options)
341+
return _Property(fn, options=options, signature=signature)
320342

321343
return decorator
322344

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)