Merge lp:~verterok/ubuntuone-client/dbus-iface-docs into lp:ubuntuone-client
- dbus-iface-docs
- Merge into trunk
Proposed by
Guillermo Gonzalez
Status: | Merged |
---|---|
Approved by: | Rick McBride |
Approved revision: | not available |
Merged at revision: | not available |
Proposed branch: | lp:~verterok/ubuntuone-client/dbus-iface-docs |
Merge into: | lp:ubuntuone-client |
Diff against target: |
408 lines (+305/-32) 6 files modified
.bzrignore (+1/-0) Makefile.am (+4/-0) contrib/dbus-docs (+193/-0) contrib/dbus_util.py (+72/-0) contrib/test (+4/-32) ubuntuone/syncdaemon/dbus_interface.py (+31/-0) |
To merge this branch: | bzr merge lp:~verterok/ubuntuone-client/dbus-iface-docs |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Rick McBride (community) | Approve | ||
Eric Casteleijn (community) | Approve | ||
Review via email: mp+16259@code.launchpad.net |
Commit message
export method and signal docstrings via DBus Introspect, add script to generate a simple text file with SyncDaemon DBus API
Description of the change
To post a comment you must log in.
Revision history for this message
Guillermo Gonzalez (verterok) wrote : | # |
- 292. By Guillermo Gonzalez
-
remove commented code
Revision history for this message
Eric Casteleijn (thisfred) wrote : | # |
Looks good, tests pass.
review:
Approve
Revision history for this message
Rick McBride (rmcbride) wrote : | # |
Very nice! neat diagram too.
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file '.bzrignore' |
2 | --- .bzrignore 2009-07-15 21:47:57 +0000 |
3 | +++ .bzrignore 2009-12-16 20:33:18 +0000 |
4 | @@ -42,3 +42,4 @@ |
5 | icons |
6 | storageprotocol |
7 | clientdefs.py |
8 | +docs/syncdaemon_dbus_api.txt |
9 | |
10 | === modified file 'Makefile.am' |
11 | --- Makefile.am 2009-09-25 18:48:03 +0000 |
12 | +++ Makefile.am 2009-12-16 20:33:18 +0000 |
13 | @@ -62,6 +62,10 @@ |
14 | fi |
15 | rm -rf _trial_temp |
16 | |
17 | +docs: protocol Makefile |
18 | + PYTHONPATH="$(PYTHONPATH)" $(PYTHON) $(srcdir)/contrib/dbus-docs |
19 | + |
20 | + |
21 | protocol: ubuntuone Makefile |
22 | PROTOCOL="ubuntuone/storageprotocol"; \ |
23 | if [ ! -d $(USP_PATH) ]; then \ |
24 | |
25 | === added file 'contrib/dbus-docs' |
26 | --- contrib/dbus-docs 1970-01-01 00:00:00 +0000 |
27 | +++ contrib/dbus-docs 2009-12-16 20:33:18 +0000 |
28 | @@ -0,0 +1,193 @@ |
29 | +#!/usr/bin/env python |
30 | +# |
31 | +# Author: Guillermo Gonzalez <guillermo.gonzalez@canonical.com> |
32 | +# |
33 | +# Copyright 2009 Canonical Ltd. |
34 | +# |
35 | +# This program is free software: you can redistribute it and/or modify it |
36 | +# under the terms of the GNU General Public License version 3, as published |
37 | +# by the Free Software Foundation. |
38 | +# |
39 | +# This program is distributed in the hope that it will be useful, but |
40 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
41 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
42 | +# PURPOSE. See the GNU General Public License for more details. |
43 | +# |
44 | +# You should have received a copy of the GNU General Public License along |
45 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
46 | + |
47 | +from twisted.internet import glib2reactor |
48 | +glib2reactor.install() |
49 | + |
50 | +import sys |
51 | +import os |
52 | +import signal |
53 | +import dbus |
54 | +import subprocess |
55 | +import shutil |
56 | + |
57 | +from xml.etree import ElementTree |
58 | + |
59 | +sys.path.insert(0, os.path.abspath(".")) |
60 | + |
61 | +from contrib.dbus_util import DBusRunner |
62 | +from contrib.testing.testcase import ( |
63 | + FakeMain, |
64 | + DBusGMainLoop, |
65 | + DBusInterface, |
66 | + FakeNetworkManager, |
67 | +) |
68 | + |
69 | +from ubuntuone.syncdaemon.dbus_interface import ( |
70 | + DBUS_IFACE_SYNC_NAME, |
71 | + DBUS_IFACE_STATUS_NAME, |
72 | + DBUS_IFACE_EVENTS_NAME, |
73 | + DBUS_IFACE_FS_NAME, |
74 | + DBUS_IFACE_SHARES_NAME, |
75 | + DBUS_IFACE_CONFIG_NAME, |
76 | +) |
77 | +from ubuntuone.syncdaemon.tools import DBusClient |
78 | +from twisted.internet import reactor, defer |
79 | + |
80 | + |
81 | +iface_path = ((DBUS_IFACE_SYNC_NAME, '/'), (DBUS_IFACE_CONFIG_NAME, '/config'), |
82 | + (DBUS_IFACE_EVENTS_NAME, '/events'), |
83 | + (DBUS_IFACE_FS_NAME, '/filesystem'), |
84 | + (DBUS_IFACE_SHARES_NAME, '/shares'), |
85 | + (DBUS_IFACE_STATUS_NAME, '/status')) |
86 | + |
87 | + |
88 | +def parse_introspect_data(xml): |
89 | + """Parse the xml returned by Introspect and returns a dict""" |
90 | + info = dict() |
91 | + e = ElementTree.fromstring(xml) |
92 | + for c in e.findall('interface'): |
93 | + # ignore other interfaces |
94 | + if not c.attrib['name'].startswith('com.ubuntuone'): |
95 | + continue |
96 | + iface_name = c.attrib['name'] |
97 | + info[iface_name] = dict() |
98 | + # methods |
99 | + methods = dict() |
100 | + for method in c.findall('method'): |
101 | + meth_name = method.attrib['name'] |
102 | + args = [] |
103 | + for arg in method.findall('arg'): |
104 | + dir = arg.attrib['direction'] |
105 | + type = arg.attrib['type'] |
106 | + if 'name' in arg.attrib: |
107 | + name = arg.attrib['name'] |
108 | + args.append((type, dir, name)) |
109 | + else: |
110 | + args.append((type, dir)) |
111 | + docstrings = method.findall('docstring') |
112 | + docstring = docstrings[0].text if docstrings else 'No docstring' |
113 | + methods[meth_name] = dict(args=args, docstring=docstring) |
114 | + info[iface_name]['methods'] = methods |
115 | + # signals |
116 | + signals = dict() |
117 | + for signal in c.findall('signal'): |
118 | + sig_name = signal.attrib['name'] |
119 | + args = [] |
120 | + for arg in signal.findall('arg'): |
121 | + type = arg.attrib['type'] |
122 | + name = arg.attrib['name'] |
123 | + args.append((type, name)) |
124 | + docstrings = signal.findall('docstring') |
125 | + docstring = docstrings[0].text if docstrings else 'No docstring' |
126 | + signals[sig_name] = dict(args=args, docstring=docstring) |
127 | + info[iface_name]['signals'] = signals |
128 | + return info |
129 | + |
130 | + |
131 | +def get_info(path): |
132 | + """Get all the introspectable info from 'path'""" |
133 | + d = defer.Deferred() |
134 | + client = DBusClient(bus, path, 'org.freedesktop.DBus.Introspectable') |
135 | + client.call_method('Introspect', reply_handler=d.callback, |
136 | + error_handler=d.errback) |
137 | + return d |
138 | + |
139 | + |
140 | +def dump_to_stream(info_by_path, stream): |
141 | + print >>stream, "SyncDaemon DBus API\n" |
142 | + for path, interfaces in info_by_path.items(): |
143 | + print >>stream, "Object path: %s" % path |
144 | + for iface_name, kinds in interfaces.items(): |
145 | + print >>stream, " Interface: %s" % iface_name |
146 | + print >>stream, " Methods:" |
147 | + for meth_name, val in kinds['methods'].items(): |
148 | + in_args = ','.join([arg[2] + '=' + arg[0] for arg in |
149 | + val['args'] if arg[1] == 'in']) |
150 | + out_args = ','.join([arg[0] for arg in val['args'] |
151 | + if arg[1] == 'out']) |
152 | + if out_args and in_args: |
153 | + print >>stream, " %s(%s) -> %s" % (meth_name, in_args, |
154 | + out_args) |
155 | + elif in_args: |
156 | + print >>stream, " %s(%s)" % (meth_name, in_args) |
157 | + else: |
158 | + print >>stream, " %s()" % meth_name |
159 | + print >>stream, " %s\n" % val['docstring'] |
160 | + print >>stream, " Signals:" |
161 | + for signal_name, val in kinds['signals'].items(): |
162 | + in_args = ','.join([arg[1] + '=' + arg[0] \ |
163 | + for arg in val['args']]) |
164 | + if in_args: |
165 | + print >>stream, " %s(%s)" % (signal_name, in_args) |
166 | + else: |
167 | + print >>stream, " %s(%s)" % signal_name |
168 | + print >>stream, " %s\n" % val['docstring'] |
169 | + |
170 | + |
171 | +@defer.inlineCallbacks |
172 | +def main(bus): |
173 | + """Entry point""" |
174 | + info_by_path = dict() |
175 | + for iface, path in iface_path: |
176 | + xml = yield get_info(path) |
177 | + obj_info = parse_introspect_data(xml) |
178 | + info_by_path[path] = obj_info |
179 | + dest_file = os.path.join(os.getcwd(), 'docs', 'syncdaemon_dbus_api.txt') |
180 | + with open(dest_file, 'w') as f: |
181 | + dump_to_stream(info_by_path, f) |
182 | + reactor.stop() |
183 | + |
184 | + |
185 | +def start_syncdaemon(tmp_dir): |
186 | + """Starts a syncdaemon instance just like the one used in the test suite""" |
187 | + xdg_cache = os.path.join(tmp_dir, 'xdg_cache') |
188 | + data_dir = os.path.join(xdg_cache, 'data') |
189 | + partials_dir = os.path.join(xdg_cache, 'partials') |
190 | + root_dir = os.path.join(tmp_dir, 'root') |
191 | + shares_dir = os.path.join(tmp_dir, 'shares') |
192 | + main = FakeMain(root_dir, shares_dir, data_dir, partials_dir) |
193 | + loop = DBusGMainLoop(set_as_default=True) |
194 | + bus = dbus.bus.BusConnection(mainloop=loop) |
195 | + nm = FakeNetworkManager(bus) |
196 | + dbus_iface = DBusInterface(bus, main, system_bus=bus) |
197 | + main.dbus_iface = dbus_iface |
198 | + return main, bus |
199 | + |
200 | + |
201 | +if __name__ == '__main__': |
202 | + dbus_runner = DBusRunner() |
203 | + dbus_runner.startDBus() |
204 | + tmp_dir = os.path.join(os.getcwd(), 'tmp') |
205 | + try: |
206 | + |
207 | + m, bus = start_syncdaemon(tmp_dir) |
208 | + try: |
209 | + reactor.callWhenRunning(main, bus) |
210 | + reactor.run() |
211 | + finally: |
212 | + m.quit() |
213 | + finally: |
214 | + dbus_runner.stopDBus() |
215 | + # remove the tmp dir |
216 | + os.chmod(tmp_dir, 0777) |
217 | + for dirpath, dirs, _ in os.walk(tmp_dir): |
218 | + for dir in dirs: |
219 | + os.chmod(os.path.join(dirpath, dir), 0777) |
220 | + shutil.rmtree(tmp_dir) |
221 | + |
222 | |
223 | === added file 'contrib/dbus_util.py' |
224 | --- contrib/dbus_util.py 1970-01-01 00:00:00 +0000 |
225 | +++ contrib/dbus_util.py 2009-12-16 20:33:18 +0000 |
226 | @@ -0,0 +1,72 @@ |
227 | +# |
228 | +# Author: Guillermo Gonzalez <guillermo.gonzalez@canonical.com> |
229 | +# |
230 | +# Copyright 2009 Canonical Ltd. |
231 | +# |
232 | +# This program is free software: you can redistribute it and/or modify it |
233 | +# under the terms of the GNU General Public License version 3, as published |
234 | +# by the Free Software Foundation. |
235 | +# |
236 | +# This program is distributed in the hope that it will be useful, but |
237 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
238 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
239 | +# PURPOSE. See the GNU General Public License for more details. |
240 | +# |
241 | +# You should have received a copy of the GNU General Public License along |
242 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
243 | + |
244 | +import os |
245 | +import signal |
246 | +import subprocess |
247 | + |
248 | +from distutils.spawn import find_executable |
249 | + |
250 | + |
251 | +class DBusLaunchError(Exception): |
252 | + """Error while launching dbus-daemon""" |
253 | + pass |
254 | + |
255 | +class NotFoundError(Exception): |
256 | + """Not found error""" |
257 | + pass |
258 | + |
259 | + |
260 | +class DBusRunner(object): |
261 | + |
262 | + def __init__(self): |
263 | + self.dbus_address = None |
264 | + self.dbus_pid = None |
265 | + self.running = False |
266 | + |
267 | + def startDBus(self): |
268 | + """Start our own session bus daemon for testing.""" |
269 | + dbus = find_executable("dbus-daemon") |
270 | + if not dbus: |
271 | + raise NotFoundError("dbus-daemon was not found.") |
272 | + |
273 | + config_file = os.path.join(os.getcwd(), "contrib", "testing", |
274 | + "dbus-session.conf") |
275 | + dbus_args = ["--fork", |
276 | + "--config-file=" + config_file, |
277 | + "--print-address=1", |
278 | + "--print-pid=2"] |
279 | + p = subprocess.Popen([dbus] + dbus_args, |
280 | + bufsize=4096, stdout=subprocess.PIPE, |
281 | + stderr=subprocess.PIPE) |
282 | + |
283 | + self.dbus_address = "".join(p.stdout.readlines()).strip() |
284 | + self.dbus_pid = int("".join(p.stderr.readlines()).strip()) |
285 | + |
286 | + if self.dbus_address != "": |
287 | + os.environ["DBUS_SESSION_BUS_ADDRESS"] = self.dbus_address |
288 | + else: |
289 | + os.kill(self.dbus_pid, signal.SIGKILL) |
290 | + raise DBusLaunchError("There was a problem launching dbus-daemon.") |
291 | + self.running = True |
292 | + |
293 | + def stopDBus(self): |
294 | + """Stop our DBus session bus daemon.""" |
295 | + del os.environ["DBUS_SESSION_BUS_ADDRESS"] |
296 | + os.kill(self.dbus_pid, signal.SIGKILL) |
297 | + self.running = False |
298 | + |
299 | |
300 | === modified file 'contrib/test' |
301 | --- contrib/test 2009-11-06 19:31:17 +0000 |
302 | +++ contrib/test 2009-12-16 20:33:18 +0000 |
303 | @@ -66,36 +66,6 @@ |
304 | suite.addTests(module_suite) |
305 | return suite |
306 | |
307 | - def startDBus(self): |
308 | - """Start our own session bus daemon for testing.""" |
309 | - dbus = find_executable("dbus-daemon") |
310 | - if not dbus: |
311 | - raise NotFoundError("dbus-daemon was not found.") |
312 | - |
313 | - config_file = os.path.join(os.getcwd(), "contrib", "testing", |
314 | - "dbus-session.conf") |
315 | - dbus_args = ["--fork", |
316 | - "--config-file=" + config_file, |
317 | - "--print-address=1", |
318 | - "--print-pid=2"] |
319 | - p = subprocess.Popen([dbus] + dbus_args, |
320 | - bufsize=4096, stdout=subprocess.PIPE, |
321 | - stderr=subprocess.PIPE) |
322 | - |
323 | - self.dbus_address = "".join(p.stdout.readlines()).strip() |
324 | - self.dbus_pid = int("".join(p.stderr.readlines()).strip()) |
325 | - |
326 | - if self.dbus_address != "": |
327 | - os.environ["DBUS_SESSION_BUS_ADDRESS"] = self.dbus_address |
328 | - else: |
329 | - os.kill(self.dbus_pid, signal.SIGKILL) |
330 | - raise DBusLaunchError("There was a problem launching dbus-daemon.") |
331 | - |
332 | - def stopDBus(self): |
333 | - """Stop our DBus session bus daemon.""" |
334 | - del os.environ["DBUS_SESSION_BUS_ADDRESS"] |
335 | - os.kill(self.dbus_pid, signal.SIGKILL) |
336 | - |
337 | def run(self, testpath, test_pattern=None): |
338 | """run the tests. """ |
339 | # install the glib2reactor before any import of the reactor to avoid |
340 | @@ -106,7 +76,9 @@ |
341 | from twisted.trial.reporter import TreeReporter |
342 | from twisted.trial.runner import TrialRunner |
343 | |
344 | - self.startDBus() |
345 | + from contrib.dbus_util import DBusRunner |
346 | + dbus_runner = DBusRunner() |
347 | + dbus_runner.startDBus() |
348 | |
349 | workingDirectory = os.path.join(os.getcwd(), "_trial_temp", "tmp") |
350 | runner = TrialRunner(reporterFactory=TreeReporter, realTimeErrors=True, |
351 | @@ -125,7 +97,7 @@ |
352 | result = runner.run(suite) |
353 | success = result.wasSuccessful() |
354 | finally: |
355 | - self.stopDBus() |
356 | + dbus_runner.stopDBus() |
357 | if not success: |
358 | sys.exit(1) |
359 | else: |
360 | |
361 | === modified file 'ubuntuone/syncdaemon/dbus_interface.py' |
362 | --- ubuntuone/syncdaemon/dbus_interface.py 2009-12-07 20:49:54 +0000 |
363 | +++ ubuntuone/syncdaemon/dbus_interface.py 2009-12-16 20:33:18 +0000 |
364 | @@ -22,6 +22,7 @@ |
365 | |
366 | from dbus import DBusException |
367 | from itertools import groupby, chain |
368 | +from xml.etree import ElementTree |
369 | |
370 | from twisted.internet import defer |
371 | from twisted.python.failure import Failure |
372 | @@ -90,6 +91,36 @@ |
373 | """ emit's a Error signal. """ |
374 | self.SignalError(signal, extra_args) |
375 | |
376 | + @classmethod |
377 | + def _add_docstring(cls, func, reflection_data): |
378 | + """add <docstring> tag to reflection_data if func.__doc__ isn't None.""" |
379 | + # add docstring element |
380 | + if getattr(func, '__doc__', None) is not None: |
381 | + |
382 | + element = ElementTree.fromstring(reflection_data) |
383 | + doc = element.makeelement('docstring', dict()) |
384 | + data = '<![CDATA[' + func.__doc__ + ']]>' |
385 | + doc.text = '%s' |
386 | + element.insert(0, doc) |
387 | + return ElementTree.tostring(element) % data |
388 | + else: |
389 | + return reflection_data |
390 | + |
391 | + @classmethod |
392 | + def _reflect_on_method(cls, func): |
393 | + """override _reflect_on_method to provide an extra <docstring> element |
394 | + in the xml. |
395 | + """ |
396 | + reflection_data = dbus.service.Object._reflect_on_method(func) |
397 | + reflection_data = cls._add_docstring(func, reflection_data) |
398 | + return reflection_data |
399 | + |
400 | + @classmethod |
401 | + def _reflect_on_signal(cls, func): |
402 | + reflection_data = dbus.service.Object._reflect_on_signal(func) |
403 | + reflection_data = cls._add_docstring(func, reflection_data) |
404 | + return reflection_data |
405 | + |
406 | |
407 | class Status(DBusExposedObject): |
408 | """ Represent the status of the syncdaemon """ |
This branch adds a script to generate simple text file with the syncdaemon DBus API description.
In order to include the method docstrings I overrided a dbus-python magic method in the parent class of the DBus exposed objects to add an extra <docstring> tag to the Introspect xml, so we can get all the information directly from DBus
Also adds a new make target: 'docs'.