Skip to content

Commit c8de073

Browse files
committed
Add type decorators for XML generation
The decorators add information about the types a method or property can accept or return. The XML generator can optionally require that every method and property has the information defined. Alternatively it adds the possibility to use annotations instead, but that is a Python 3 only feature.
1 parent 2816403 commit c8de073

File tree

6 files changed

+334
-20
lines changed

6 files changed

+334
-20
lines changed

doc/tutorial.rst

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -238,24 +238,27 @@ To prepare a class for exporting on the Bus, provide the dbus introspection XML
238238
in a ''dbus'' class property or in its ''docstring''. For example::
239239

240240
from pydbus.generic import signal
241-
from pydbus.xml_generator import interface, signalled, attach_introspection_xml
241+
from pydbus.strong_typing import typed_method, typed_property
242+
from pydbus.xml_generator import interface, emits_changed_signal, attach_introspection_xml
242243

243244
@attach_introspection_xml
244245
@interface("net.lew21.pydbus.TutorialExample")
245246
class Example(object):
246247

248+
@typed_method(("s", ), "s")
247249
def EchoString(self, s):
248250
"""returns whatever is passed to it"""
249251
return s
250252

251253
def __init__(self):
252254
self._someProperty = "initial value"
253255

254-
@property
256+
@emits_changed_signal
257+
@typed_property("s")
255258
def SomeProperty(self):
256259
return self._someProperty
257260

258-
@signalled(SomeProperty)
261+
@SomeProperty.setter
259262
def SomeProperty(self, value):
260263
self._someProperty = value
261264
self.PropertiesChanged("net.lew21.pydbus.TutorialExample", {"SomeProperty": self.SomeProperty}, [])

pydbus/strong_typing.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""Decorators for methods and properties to strongly typed the values."""
2+
import inspect
3+
4+
from pydbus.xml_generator import get_arguments
5+
6+
7+
def typed_property(value_type):
8+
"""
9+
Decorate a function as a dbus property getter.
10+
11+
It alreay makes the method a property so another `@property` decorator may
12+
not be used.
13+
"""
14+
def decorate(func):
15+
func.prop_type = value_type
16+
return property(func)
17+
return decorate
18+
19+
20+
def typed_method(argument_types, return_type):
21+
"""
22+
Decorate a function as a dbus method.
23+
24+
Parameters
25+
----------
26+
argument_types : tuple
27+
Required argument types for each argument except the first
28+
return_type : string
29+
Type of the returned value, must be None if it returns nothing
30+
"""
31+
def decorate(func):
32+
func.arg_types = argument_types
33+
func.ret_type = return_type
34+
get_arguments(func)
35+
return func
36+
return decorate

pydbus/tests/strong_typing.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from pydbus.generic import signal
2+
from pydbus.strong_typing import typed_method, typed_property
3+
4+
5+
def test_signal():
6+
@signal
7+
@typed_method(("s", ), None)
8+
def dummy(self, parameter):
9+
pass
10+
11+
assert hasattr(dummy, 'method')
12+
assert dummy.method.arg_types == ("s", )
13+
assert dummy.method.ret_type is None
14+
15+
16+
def test_count_off():
17+
"""Test what happens if to many or to few types are defined in methods."""
18+
try:
19+
@typed_method(("s", "i", "o"), None)
20+
def dummy(self, parameter):
21+
pass
22+
23+
assert False
24+
except ValueError as e:
25+
assert str(e) == "Number of argument types (3) differs from the number of parameters (1) in function 'dummy'"
26+
27+
try:
28+
@typed_method(("s", "i"), "o")
29+
def dummy(self, parameter):
30+
pass
31+
32+
assert False
33+
except ValueError as e:
34+
assert str(e) == "Number of argument types (2) differs from the number of parameters (1) in function 'dummy'"
35+
36+
try:
37+
@typed_method(tuple(), None)
38+
def dummy(self, parameter):
39+
pass
40+
41+
assert False
42+
except ValueError as e:
43+
assert str(e) == "Number of argument types (0) differs from the number of parameters (1) in function 'dummy'"
44+
45+
46+
test_signal()
47+
test_count_off()

pydbus/tests/xml_generator.py

Lines changed: 81 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
from sys import version_info
2+
13
from pydbus import xml_generator
24
from pydbus.generic import signal
5+
from pydbus.strong_typing import typed_method, typed_property
36

47

58
@xml_generator.attach_introspection_xml
@@ -9,11 +12,20 @@ class Example(object):
912
def __init__(self):
1013
self._rw = 42
1114

15+
@typed_method(("s", ), "i")
1216
def OneParamReturn(self, parameter):
1317
return 42
1418

19+
@typed_method(("s", ), None)
20+
def OneParamNoReturn(self, parameter):
21+
pass
22+
23+
@typed_property("i")
24+
def ReadProperty(self):
25+
return 42
26+
1527
@xml_generator.emits_changed_signal
16-
@property
28+
@typed_property("i")
1729
def RwProperty(self):
1830
return self._rw
1931

@@ -53,20 +65,43 @@ def arguments(self, arg1, arg2):
5365
def ctx_argument(self, arg, dbus_context):
5466
pass
5567

56-
assert xml_generator.get_arguments(nothing) == []
57-
assert xml_generator.get_arguments(arguments) == ["arg1", "arg2"]
58-
assert xml_generator.get_arguments(ctx_argument) == ["arg"]
68+
@typed_method(tuple(), None)
69+
def typed_nothing(self):
70+
pass
71+
72+
@typed_method(("s", "i"), None)
73+
def typed_arguments(self, arg1, arg2):
74+
pass
75+
76+
assert xml_generator.get_arguments(nothing) == (tuple(), None)
77+
assert xml_generator.get_arguments(arguments) == ((("arg1", None), ("arg2", None)), None)
78+
assert xml_generator.get_arguments(ctx_argument) == ((("arg", None), ), None)
79+
80+
assert xml_generator.get_arguments(typed_nothing) == (tuple(), None)
81+
assert xml_generator.get_arguments(typed_arguments) == ((("arg1", "s"), ("arg2", "i")), None)
5982

6083

6184
def test_valid():
6285
assert not hasattr(Example.OneParamReturn, "dbus_interface")
86+
assert Example.OneParamReturn.arg_types == ("s", )
87+
assert Example.OneParamReturn.ret_type == "i"
88+
89+
assert not hasattr(Example.OneParamNoReturn, "dbus_interface")
90+
assert Example.OneParamNoReturn.arg_types == ("s", )
91+
assert Example.OneParamNoReturn.ret_type is None
92+
93+
assert not hasattr(Example.ReadProperty, "dbus_interface")
94+
assert isinstance(Example.ReadProperty, property)
95+
assert Example.ReadProperty.fget.prop_type == "i"
96+
assert Example.ReadProperty.fset is None
6397

6498
assert not hasattr(Example.RwProperty, "dbus_interface")
6599
assert isinstance(Example.RwProperty, property)
66100
assert Example.RwProperty.fget.causes_signal is True
101+
assert Example.RwProperty.fget.prop_type == "i"
67102
assert Example.RwProperty.fset is not None
68103

69-
assert Example.dbus == b'<node><interface name="net.lvht.Foo1"><method name="OneParamReturn"><arg direction="in" name="parameter" /></method><property access="readwrite" name="RwProperty"><annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true" /></property></interface></node>'
104+
assert Example.dbus == b'<node><interface name="net.lvht.Foo1"><method name="OneParamNoReturn"><arg direction="in" name="parameter" type="s" /></method><method name="OneParamReturn"><arg direction="in" name="parameter" type="s" /><arg direction="out" name="return" type="i" /></method><property access="read" name="ReadProperty" type="i" /><property access="readwrite" name="RwProperty" type="i"><annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true" /></property></interface></node>'
70105

71106

72107
def test_multiple_interfaces():
@@ -89,8 +124,13 @@ def Dummy(self, param=None):
89124
except ValueError as e:
90125
assert str(e) == "Default values are not allowed for method 'Dummy'"
91126

92-
E_NO_VARGS = (
127+
if version_info[0] == 2:
128+
E_NO_VARGS = (
93129
"Variable arguments arguments are not allowed for method 'Dummy'")
130+
else:
131+
E_NO_VARGS = E_NO_KWARGS = (
132+
"Variable arguments or keyword only arguments are not allowed for "
133+
"method 'Dummy'")
94134

95135
def Dummy(self, *vargs):
96136
pass
@@ -111,7 +151,42 @@ def Dummy(self, **kwargs):
111151
assert str(e) == E_NO_VARGS
112152

113153

154+
def test_require_strong_typing():
155+
try:
156+
@xml_generator.attach_introspection_xml(True)
157+
@xml_generator.interface("net.lvht.Foo1")
158+
class Example(object):
159+
160+
def Dummy(self, param):
161+
pass
162+
except ValueError as e:
163+
assert str(e) == "No argument types defined for method 'Dummy'"
164+
165+
@xml_generator.attach_introspection_xml(True)
166+
@xml_generator.interface("net.lvht.Foo1")
167+
class RequiredExample(object):
168+
169+
@typed_method(("s", ), None)
170+
def Dummy(self, param):
171+
pass
172+
173+
assert RequiredExample.Dummy.arg_types == ("s", )
174+
assert RequiredExample.Dummy.ret_type is None
175+
176+
@xml_generator.attach_introspection_xml(False)
177+
@xml_generator.interface("net.lvht.Foo1")
178+
class OptionalExample(object):
179+
180+
@typed_method(("s", ), None)
181+
def Dummy(self, param):
182+
pass
183+
184+
assert OptionalExample.dbus == RequiredExample.dbus
185+
assert OptionalExample is not RequiredExample
186+
187+
114188
test_get_arguments()
115189
test_valid()
116190
test_multiple_interfaces()
117191
test_invalid_function()
192+
test_require_strong_typing()

0 commit comments

Comments
 (0)