Skip to content

Support automatic XML introspection generation #29

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 7 additions & 14 deletions doc/tutorial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -238,30 +238,23 @@ To prepare a class for exporting on the Bus, provide the dbus introspection XML
in a ''dbus'' class property or in its ''docstring''. For example::

from pydbus.generic import signal
from pydbus.strong_typing import typed_method, typed_property
from pydbus.xml_generator import interface, emits_changed_signal, attach_introspection_xml

@attach_introspection_xml
@interface("net.lew21.pydbus.TutorialExample")
class Example(object):
"""
<node>
<interface name='net.lew21.pydbus.TutorialExample'>
<method name='EchoString'>
<arg type='s' name='a' direction='in'/>
<arg type='s' name='response' direction='out'/>
</method>
<property name="SomeProperty" type="s" access="readwrite">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
</property>
</interface>
</node>
"""

@typed_method(("s", ), "s")
def EchoString(self, s):
"""returns whatever is passed to it"""
return s

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

@property
@emits_changed_signal
@typed_property("s")
def SomeProperty(self):
return self._someProperty

Expand Down
10 changes: 8 additions & 2 deletions pydbus/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,16 @@ class A:
- they will be forwarded to all subscribed callbacks.
"""

def __init__(self):
def __init__(self, method=None):
# if used as a decorator, method is defined
self.method = method
self.map = {}
self.__qualname__ = "<anonymous signal>" # function uses <lambda> ;)
self.__doc__ = "Signal."
if method is None:
self.__qualname__ = "<anonymous signal>" # function uses <lambda> ;)
else:
self.__qualname__ = "signal '{}'".format(method.__name__)
self.__name__ = method.__name__

def connect(self, object, callback):
"""Subscribe to the signal."""
Expand Down
36 changes: 36 additions & 0 deletions pydbus/strong_typing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""Decorators for methods and properties to strongly typed the values."""
import inspect

from pydbus.xml_generator import get_arguments


def typed_property(value_type):
"""
Decorate a function as a dbus property getter.

It alreay makes the method a property so another `@property` decorator may
not be used.
"""
def decorate(func):
func.prop_type = value_type
return property(func)
return decorate


def typed_method(argument_types, return_type):
"""
Decorate a function as a dbus method.

Parameters
----------
argument_types : tuple
Required argument types for each argument except the first
return_type : string
Type of the returned value, must be None if it returns nothing
"""
def decorate(func):
func.arg_types = argument_types
func.ret_type = return_type
get_arguments(func)
return func
return decorate
47 changes: 47 additions & 0 deletions pydbus/tests/strong_typing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from pydbus.generic import signal
from pydbus.strong_typing import typed_method, typed_property


def test_signal():
@signal
@typed_method(("s", ), None)
def dummy(self, parameter):
pass

assert hasattr(dummy, 'method')
assert dummy.method.arg_types == ("s", )
assert dummy.method.ret_type is None


def test_count_off():
"""Test what happens if to many or to few types are defined in methods."""
try:
@typed_method(("s", "i", "o"), None)
def dummy(self, parameter):
pass

assert False
except ValueError as e:
assert str(e) == "Number of argument types (3) differs from the number of parameters (1) in function 'dummy'"

try:
@typed_method(("s", "i"), "o")
def dummy(self, parameter):
pass

assert False
except ValueError as e:
assert str(e) == "Number of argument types (2) differs from the number of parameters (1) in function 'dummy'"

try:
@typed_method(tuple(), None)
def dummy(self, parameter):
pass

assert False
except ValueError as e:
assert str(e) == "Number of argument types (0) differs from the number of parameters (1) in function 'dummy'"


test_signal()
test_count_off()
192 changes: 192 additions & 0 deletions pydbus/tests/xml_generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
from sys import version_info

from pydbus import xml_generator
from pydbus.generic import signal
from pydbus.strong_typing import typed_method, typed_property


@xml_generator.attach_introspection_xml
@xml_generator.interface("net.lvht.Foo1")
class Example(object):

def __init__(self):
self._rw = 42

@typed_method(("s", ), "i")
def OneParamReturn(self, parameter):
return 42

@typed_method(("s", ), None)
def OneParamNoReturn(self, parameter):
pass

@typed_property("i")
def ReadProperty(self):
return 42

@xml_generator.emits_changed_signal
@typed_property("i")
def RwProperty(self):
return self._rw

@RwProperty.setter
def RwProperty(self, value):
self._rw = value


@xml_generator.attach_introspection_xml
@xml_generator.interface("net.lvht.Foolback")
class MultiInterface(object):

def MethodFoolback(self):
pass

@xml_generator.interface("net.lvht.Barface")
def MethodBarface(self):
pass

@signal
def SignalFoolback(self):
pass

@xml_generator.interface("net.lvht.Barface")
@signal
def SignalBarface(self):
pass


def test_get_arguments():
def nothing(self):
pass

def arguments(self, arg1, arg2):
pass

def ctx_argument(self, arg, dbus_context):
pass

@typed_method(tuple(), None)
def typed_nothing(self):
pass

@typed_method(("s", "i"), None)
def typed_arguments(self, arg1, arg2):
pass

assert xml_generator.get_arguments(nothing) == (tuple(), None)
assert xml_generator.get_arguments(arguments) == ((("arg1", None), ("arg2", None)), None)
assert xml_generator.get_arguments(ctx_argument) == ((("arg", None), ), None)

assert xml_generator.get_arguments(typed_nothing) == (tuple(), None)
assert xml_generator.get_arguments(typed_arguments) == ((("arg1", "s"), ("arg2", "i")), None)


def test_valid():
assert not hasattr(Example.OneParamReturn, "dbus_interface")
assert Example.OneParamReturn.arg_types == ("s", )
assert Example.OneParamReturn.ret_type == "i"

assert not hasattr(Example.OneParamNoReturn, "dbus_interface")
assert Example.OneParamNoReturn.arg_types == ("s", )
assert Example.OneParamNoReturn.ret_type is None

assert not hasattr(Example.ReadProperty, "dbus_interface")
assert isinstance(Example.ReadProperty, property)
assert Example.ReadProperty.fget.prop_type == "i"
assert Example.ReadProperty.fset is None

assert not hasattr(Example.RwProperty, "dbus_interface")
assert isinstance(Example.RwProperty, property)
assert Example.RwProperty.fget.causes_signal is True
assert Example.RwProperty.fget.prop_type == "i"
assert Example.RwProperty.fset is not None

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>'


def test_multiple_interfaces():
assert not hasattr(MultiInterface.MethodFoolback, "dbus_interface")
assert MultiInterface.MethodBarface.dbus_interface == "net.lvht.Barface"
assert not hasattr(MultiInterface.SignalFoolback, "dbus_interface")
assert MultiInterface.SignalBarface.dbus_interface == "net.lvht.Barface"

assert MultiInterface.dbus == b'<node><interface name="net.lvht.Barface"><method name="MethodBarface" /><signal name="SignalBarface" /></interface><interface name="net.lvht.Foolback"><method name="MethodFoolback" /><signal name="SignalFoolback" /></interface></node>'


def test_invalid_function():
"""Test what happens if to many or to few types are defined in methods."""
def Dummy(self, param=None):
pass

try:
xml_generator.get_arguments(Dummy)
assert False
except ValueError as e:
assert str(e) == "Default values are not allowed for method 'Dummy'"

if version_info[0] == 2:
E_NO_VARGS = (
"Variable arguments arguments are not allowed for method 'Dummy'")
else:
E_NO_VARGS = E_NO_KWARGS = (
"Variable arguments or keyword only arguments are not allowed for "
"method 'Dummy'")

def Dummy(self, *vargs):
pass

try:
xml_generator.get_arguments(Dummy)
assert False
except ValueError as e:
assert str(e) == E_NO_VARGS

def Dummy(self, **kwargs):
pass

try:
xml_generator.get_arguments(Dummy)
assert False
except ValueError as e:
assert str(e) == E_NO_VARGS


def test_require_strong_typing():
try:
@xml_generator.attach_introspection_xml(True)
@xml_generator.interface("net.lvht.Foo1")
class Example(object):

def Dummy(self, param):
pass
except ValueError as e:
assert str(e) == "No argument types defined for method 'Dummy'"

@xml_generator.attach_introspection_xml(True)
@xml_generator.interface("net.lvht.Foo1")
class RequiredExample(object):

@typed_method(("s", ), None)
def Dummy(self, param):
pass

assert RequiredExample.Dummy.arg_types == ("s", )
assert RequiredExample.Dummy.ret_type is None

@xml_generator.attach_introspection_xml(False)
@xml_generator.interface("net.lvht.Foo1")
class OptionalExample(object):

@typed_method(("s", ), None)
def Dummy(self, param):
pass

assert OptionalExample.dbus == RequiredExample.dbus
assert OptionalExample is not RequiredExample


test_get_arguments()
test_valid()
test_multiple_interfaces()
test_invalid_function()
test_require_strong_typing()
Loading