Merge lp:~laney/python-dbusmock/introspection-properties into lp:~pitti/python-dbusmock/old-bzr-trunk

Proposed by Iain Lane
Status: Merged
Merged at revision: 164
Proposed branch: lp:~laney/python-dbusmock/introspection-properties
Merge into: lp:~pitti/python-dbusmock/old-bzr-trunk
Diff against target: 115 lines (+49/-16)
2 files modified
dbusmock/mockobject.py (+32/-0)
tests/test_api.py (+17/-16)
To merge this branch: bzr merge lp:~laney/python-dbusmock/introspection-properties
Reviewer Review Type Date Requested Status
Martin Pitt Approve
Review via email: mp+198906@code.launchpad.net

Description of the change

The introspection XML currently doesn't include <property> nodes, breaking some things which rely on it such as Qt's property support.

I found an old-ish dbus-python bug about adding this there but it doesn't seem to have gained much traction:

  https://bugs.freedesktop.org/show_bug.cgi?id=26903

Since we already override Introspect(), I figure it's not too bad to add this there even if it is a bit ugly.

One wart:

+ # copy.copy removes one level of variant-ness, which means that the
+ # types get exported in introspection data correctly, but we can't do
+ # this for container types.
+ if not (isinstance(value, dbus.Dictionary) or isinstance(value, dbus.Array)):
+ value = copy.copy(value)

While the support works for properties added via the python library (e.g. in templates) without this, dynamic proeprties added using AddProperty over dbus don't. If you remove this and run the testsuite you'll see what I mean. AddPropert{y,ies} get type 'v' when introspected. We probably want them to have the actual types, so this tries to unpack the variant by one level (you can't just decrement variant_level as the object is immutable; copying it doesn't preseve this). It breaks for container types though, so we don't do this there—again, remove that bit and see what happens to the NM tests. I'd be glad to replace this with something better if you can think of an improved way around.

The test changes are because we now take the XML through a parser which changes the order of the arguments. Shouldn't be anything clients need to worry about.

To post a comment you must log in.
Revision history for this message
Martin Pitt (pitti) wrote :

Thanks a lot for this! That's indeed a long-standing wart. I merged it with some stylistic changes and changing properties to be readwrite.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'dbusmock/mockobject.py'
2--- dbusmock/mockobject.py 2013-11-29 09:22:03 +0000
3+++ dbusmock/mockobject.py 2013-12-13 10:44:10 +0000
4@@ -12,10 +12,12 @@
5 __copyright__ = '(c) 2012 Canonical Ltd.'
6 __license__ = 'LGPL 3+'
7
8+import copy
9 import time
10 import sys
11 import types
12 import importlib
13+import xml.etree.ElementTree as ET
14
15 # we do not use this ourselves, but mock methods often want to use this
16 import os
17@@ -367,6 +369,13 @@
18 except KeyError:
19 # this is what we expect
20 pass
21+
22+ # copy.copy removes one level of variant-ness, which means that the
23+ # types get exported in introspection data correctly, but we can't do
24+ # this for container types.
25+ if not (isinstance(value, dbus.Dictionary) or isinstance(value, dbus.Array)):
26+ value = copy.copy(value)
27+
28 self.props.setdefault(interface, {})[name] = value
29
30 @dbus.service.method(MOCK_IFACE,
31@@ -613,6 +622,29 @@
32
33 xml = dbus.service.Object.Introspect(self, object_path, connection)
34
35+ tree = ET.fromstring(xml)
36+
37+ for name in self.props:
38+ # We might have properties for new interfaces we don't know about
39+ # yet. Try to find an existing <interface> node named after our
40+ # interface to append to, and create one if we can't.
41+ interface = tree.find(".//interface[@name='%s']" % name)
42+ if interface is None:
43+ interface = ET.Element("interface", {"name": name})
44+ tree.append(interface)
45+
46+ for prop in self.props[name]:
47+ elem = ET.Element("property", {
48+ "name": prop,
49+ # We don't store the signature anywhere, so guess it.
50+ "type": dbus.lowlevel.Message.guess_signature(self.props[name][prop]),
51+ # Assume read-only.
52+ "access": "read"})
53+
54+ interface.append(elem)
55+
56+ xml = ET.tostring(tree, encoding='utf8', method='xml').decode('utf8')
57+
58 # restore original class table
59 self._dbus_class_table[cls] = orig_interfaces
60
61
62=== modified file 'tests/test_api.py'
63--- tests/test_api.py 2013-12-12 08:14:35 +0000
64+++ tests/test_api.py 2013-12-13 10:44:10 +0000
65@@ -305,24 +305,25 @@
66 self.assertFalse(xml_empty == xml_method, 'No change from empty XML')
67 self.assertTrue('<interface name="org.freedesktop.Test.Main">' in xml_method, xml_method)
68 self.assertTrue('''<method name="Do">
69- <arg direction="in" type="s" name="arg1" />
70- <arg direction="in" type="ai" name="arg2" />
71- <arg direction="in" type="v" name="arg3" />
72+ <arg direction="in" name="arg1" type="s" />
73+ <arg direction="in" name="arg2" type="ai" />
74+ <arg direction="in" name="arg3" type="v" />
75 <arg direction="out" type="i" />
76 </method>''' in xml_method, xml_method)
77
78 # properties in introspection are not supported by dbus-python right now
79- #def test_introspection_properties(self):
80- # '''dynamically added properties appear in introspection'''
81-
82- # self.dbus_mock.AddProperty('', 'Color', 'yellow')
83- # self.dbus_mock.AddProperty('org.freedesktop.Test.Sub', 'Count', 5)
84-
85- # xml = self.obj_test.Introspect(dbus_interface=dbus.INTROSPECTABLE_IFACE)
86- # self.assertTrue('<interface name="org.freedesktop.Test.Main">' in xml, xml)
87- # self.assertTrue('<interface name="org.freedesktop.Test.Sub">' in xml, xml)
88- # self.assertTrue('<property name="Color" type="s" access="read" />' in xml, xml)
89- # self.assertTrue('<property name="Count" type="i" access="read" />' in xml, xml)
90+ def test_introspection_properties(self):
91+ '''dynamically added properties appear in introspection'''
92+
93+ self.dbus_mock.AddProperty('', 'Color', 'yellow')
94+ self.dbus_mock.AddProperty('org.freedesktop.Test.Sub', 'Count', 5)
95+
96+ xml = self.obj_test.Introspect()
97+
98+ self.assertTrue('<interface name="org.freedesktop.Test.Main">' in xml, xml)
99+ self.assertTrue('<interface name="org.freedesktop.Test.Sub">' in xml, xml)
100+ self.assertTrue('<property access="read" name="Color" type="s" />' in xml, xml)
101+ self.assertTrue('<property access="read" name="Count" type="i" />' in xml, xml)
102
103 def test_objects_map(self):
104 '''access global objects map'''
105@@ -635,8 +636,8 @@
106 xml = dbus_objmgr.Introspect()
107 self.assertIn('<interface name="org.freedesktop.DBus.ObjectManager">', xml)
108 self.assertIn('<method name="GetManagedObjects">', xml)
109- self.assertIn('<node name="Thing1"/>', xml)
110- self.assertIn('<node name="Thing2"/>', xml)
111+ self.assertIn('<node name="Thing1" />', xml)
112+ self.assertIn('<node name="Thing2" />', xml)
113
114 def test_reset(self):
115 '''Reset() puts the template back to pristine state'''

Subscribers

People subscribed via source and target branches

to all changes: