Merge lp:~verterok/ubuntuone-client/dbus-iface-docs into lp:ubuntuone-client

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

To post a comment you must log in.
Revision history for this message
Guillermo Gonzalez (verterok) wrote :

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

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
=== modified file '.bzrignore'
--- .bzrignore 2009-07-15 21:47:57 +0000
+++ .bzrignore 2009-12-16 20:33:18 +0000
@@ -42,3 +42,4 @@
42icons42icons
43storageprotocol43storageprotocol
44clientdefs.py44clientdefs.py
45docs/syncdaemon_dbus_api.txt
4546
=== modified file 'Makefile.am'
--- Makefile.am 2009-09-25 18:48:03 +0000
+++ Makefile.am 2009-12-16 20:33:18 +0000
@@ -62,6 +62,10 @@
62 fi62 fi
63 rm -rf _trial_temp63 rm -rf _trial_temp
6464
65docs: protocol Makefile
66 PYTHONPATH="$(PYTHONPATH)" $(PYTHON) $(srcdir)/contrib/dbus-docs
67
68
65protocol: ubuntuone Makefile69protocol: ubuntuone Makefile
66 PROTOCOL="ubuntuone/storageprotocol"; \70 PROTOCOL="ubuntuone/storageprotocol"; \
67 if [ ! -d $(USP_PATH) ]; then \71 if [ ! -d $(USP_PATH) ]; then \
6872
=== added file 'contrib/dbus-docs'
--- contrib/dbus-docs 1970-01-01 00:00:00 +0000
+++ contrib/dbus-docs 2009-12-16 20:33:18 +0000
@@ -0,0 +1,193 @@
1#!/usr/bin/env python
2#
3# Author: Guillermo Gonzalez <guillermo.gonzalez@canonical.com>
4#
5# Copyright 2009 Canonical Ltd.
6#
7# This program is free software: you can redistribute it and/or modify it
8# under the terms of the GNU General Public License version 3, as published
9# by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful, but
12# WITHOUT ANY WARRANTY; without even the implied warranties of
13# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
14# PURPOSE. See the GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program. If not, see <http://www.gnu.org/licenses/>.
18
19from twisted.internet import glib2reactor
20glib2reactor.install()
21
22import sys
23import os
24import signal
25import dbus
26import subprocess
27import shutil
28
29from xml.etree import ElementTree
30
31sys.path.insert(0, os.path.abspath("."))
32
33from contrib.dbus_util import DBusRunner
34from contrib.testing.testcase import (
35 FakeMain,
36 DBusGMainLoop,
37 DBusInterface,
38 FakeNetworkManager,
39)
40
41from ubuntuone.syncdaemon.dbus_interface import (
42 DBUS_IFACE_SYNC_NAME,
43 DBUS_IFACE_STATUS_NAME,
44 DBUS_IFACE_EVENTS_NAME,
45 DBUS_IFACE_FS_NAME,
46 DBUS_IFACE_SHARES_NAME,
47 DBUS_IFACE_CONFIG_NAME,
48)
49from ubuntuone.syncdaemon.tools import DBusClient
50from twisted.internet import reactor, defer
51
52
53iface_path = ((DBUS_IFACE_SYNC_NAME, '/'), (DBUS_IFACE_CONFIG_NAME, '/config'),
54 (DBUS_IFACE_EVENTS_NAME, '/events'),
55 (DBUS_IFACE_FS_NAME, '/filesystem'),
56 (DBUS_IFACE_SHARES_NAME, '/shares'),
57 (DBUS_IFACE_STATUS_NAME, '/status'))
58
59
60def parse_introspect_data(xml):
61 """Parse the xml returned by Introspect and returns a dict"""
62 info = dict()
63 e = ElementTree.fromstring(xml)
64 for c in e.findall('interface'):
65 # ignore other interfaces
66 if not c.attrib['name'].startswith('com.ubuntuone'):
67 continue
68 iface_name = c.attrib['name']
69 info[iface_name] = dict()
70 # methods
71 methods = dict()
72 for method in c.findall('method'):
73 meth_name = method.attrib['name']
74 args = []
75 for arg in method.findall('arg'):
76 dir = arg.attrib['direction']
77 type = arg.attrib['type']
78 if 'name' in arg.attrib:
79 name = arg.attrib['name']
80 args.append((type, dir, name))
81 else:
82 args.append((type, dir))
83 docstrings = method.findall('docstring')
84 docstring = docstrings[0].text if docstrings else 'No docstring'
85 methods[meth_name] = dict(args=args, docstring=docstring)
86 info[iface_name]['methods'] = methods
87 # signals
88 signals = dict()
89 for signal in c.findall('signal'):
90 sig_name = signal.attrib['name']
91 args = []
92 for arg in signal.findall('arg'):
93 type = arg.attrib['type']
94 name = arg.attrib['name']
95 args.append((type, name))
96 docstrings = signal.findall('docstring')
97 docstring = docstrings[0].text if docstrings else 'No docstring'
98 signals[sig_name] = dict(args=args, docstring=docstring)
99 info[iface_name]['signals'] = signals
100 return info
101
102
103def get_info(path):
104 """Get all the introspectable info from 'path'"""
105 d = defer.Deferred()
106 client = DBusClient(bus, path, 'org.freedesktop.DBus.Introspectable')
107 client.call_method('Introspect', reply_handler=d.callback,
108 error_handler=d.errback)
109 return d
110
111
112def dump_to_stream(info_by_path, stream):
113 print >>stream, "SyncDaemon DBus API\n"
114 for path, interfaces in info_by_path.items():
115 print >>stream, "Object path: %s" % path
116 for iface_name, kinds in interfaces.items():
117 print >>stream, " Interface: %s" % iface_name
118 print >>stream, " Methods:"
119 for meth_name, val in kinds['methods'].items():
120 in_args = ','.join([arg[2] + '=' + arg[0] for arg in
121 val['args'] if arg[1] == 'in'])
122 out_args = ','.join([arg[0] for arg in val['args']
123 if arg[1] == 'out'])
124 if out_args and in_args:
125 print >>stream, " %s(%s) -> %s" % (meth_name, in_args,
126 out_args)
127 elif in_args:
128 print >>stream, " %s(%s)" % (meth_name, in_args)
129 else:
130 print >>stream, " %s()" % meth_name
131 print >>stream, " %s\n" % val['docstring']
132 print >>stream, " Signals:"
133 for signal_name, val in kinds['signals'].items():
134 in_args = ','.join([arg[1] + '=' + arg[0] \
135 for arg in val['args']])
136 if in_args:
137 print >>stream, " %s(%s)" % (signal_name, in_args)
138 else:
139 print >>stream, " %s(%s)" % signal_name
140 print >>stream, " %s\n" % val['docstring']
141
142
143@defer.inlineCallbacks
144def main(bus):
145 """Entry point"""
146 info_by_path = dict()
147 for iface, path in iface_path:
148 xml = yield get_info(path)
149 obj_info = parse_introspect_data(xml)
150 info_by_path[path] = obj_info
151 dest_file = os.path.join(os.getcwd(), 'docs', 'syncdaemon_dbus_api.txt')
152 with open(dest_file, 'w') as f:
153 dump_to_stream(info_by_path, f)
154 reactor.stop()
155
156
157def start_syncdaemon(tmp_dir):
158 """Starts a syncdaemon instance just like the one used in the test suite"""
159 xdg_cache = os.path.join(tmp_dir, 'xdg_cache')
160 data_dir = os.path.join(xdg_cache, 'data')
161 partials_dir = os.path.join(xdg_cache, 'partials')
162 root_dir = os.path.join(tmp_dir, 'root')
163 shares_dir = os.path.join(tmp_dir, 'shares')
164 main = FakeMain(root_dir, shares_dir, data_dir, partials_dir)
165 loop = DBusGMainLoop(set_as_default=True)
166 bus = dbus.bus.BusConnection(mainloop=loop)
167 nm = FakeNetworkManager(bus)
168 dbus_iface = DBusInterface(bus, main, system_bus=bus)
169 main.dbus_iface = dbus_iface
170 return main, bus
171
172
173if __name__ == '__main__':
174 dbus_runner = DBusRunner()
175 dbus_runner.startDBus()
176 tmp_dir = os.path.join(os.getcwd(), 'tmp')
177 try:
178
179 m, bus = start_syncdaemon(tmp_dir)
180 try:
181 reactor.callWhenRunning(main, bus)
182 reactor.run()
183 finally:
184 m.quit()
185 finally:
186 dbus_runner.stopDBus()
187 # remove the tmp dir
188 os.chmod(tmp_dir, 0777)
189 for dirpath, dirs, _ in os.walk(tmp_dir):
190 for dir in dirs:
191 os.chmod(os.path.join(dirpath, dir), 0777)
192 shutil.rmtree(tmp_dir)
193
0194
=== added file 'contrib/dbus_util.py'
--- contrib/dbus_util.py 1970-01-01 00:00:00 +0000
+++ contrib/dbus_util.py 2009-12-16 20:33:18 +0000
@@ -0,0 +1,72 @@
1#
2# Author: Guillermo Gonzalez <guillermo.gonzalez@canonical.com>
3#
4# Copyright 2009 Canonical Ltd.
5#
6# This program is free software: you can redistribute it and/or modify it
7# under the terms of the GNU General Public License version 3, as published
8# by the Free Software Foundation.
9#
10# This program is distributed in the hope that it will be useful, but
11# WITHOUT ANY WARRANTY; without even the implied warranties of
12# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
13# PURPOSE. See the GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License along
16# with this program. If not, see <http://www.gnu.org/licenses/>.
17
18import os
19import signal
20import subprocess
21
22from distutils.spawn import find_executable
23
24
25class DBusLaunchError(Exception):
26 """Error while launching dbus-daemon"""
27 pass
28
29class NotFoundError(Exception):
30 """Not found error"""
31 pass
32
33
34class DBusRunner(object):
35
36 def __init__(self):
37 self.dbus_address = None
38 self.dbus_pid = None
39 self.running = False
40
41 def startDBus(self):
42 """Start our own session bus daemon for testing."""
43 dbus = find_executable("dbus-daemon")
44 if not dbus:
45 raise NotFoundError("dbus-daemon was not found.")
46
47 config_file = os.path.join(os.getcwd(), "contrib", "testing",
48 "dbus-session.conf")
49 dbus_args = ["--fork",
50 "--config-file=" + config_file,
51 "--print-address=1",
52 "--print-pid=2"]
53 p = subprocess.Popen([dbus] + dbus_args,
54 bufsize=4096, stdout=subprocess.PIPE,
55 stderr=subprocess.PIPE)
56
57 self.dbus_address = "".join(p.stdout.readlines()).strip()
58 self.dbus_pid = int("".join(p.stderr.readlines()).strip())
59
60 if self.dbus_address != "":
61 os.environ["DBUS_SESSION_BUS_ADDRESS"] = self.dbus_address
62 else:
63 os.kill(self.dbus_pid, signal.SIGKILL)
64 raise DBusLaunchError("There was a problem launching dbus-daemon.")
65 self.running = True
66
67 def stopDBus(self):
68 """Stop our DBus session bus daemon."""
69 del os.environ["DBUS_SESSION_BUS_ADDRESS"]
70 os.kill(self.dbus_pid, signal.SIGKILL)
71 self.running = False
72
073
=== modified file 'contrib/test'
--- contrib/test 2009-11-06 19:31:17 +0000
+++ contrib/test 2009-12-16 20:33:18 +0000
@@ -66,36 +66,6 @@
66 suite.addTests(module_suite)66 suite.addTests(module_suite)
67 return suite67 return suite
6868
69 def startDBus(self):
70 """Start our own session bus daemon for testing."""
71 dbus = find_executable("dbus-daemon")
72 if not dbus:
73 raise NotFoundError("dbus-daemon was not found.")
74
75 config_file = os.path.join(os.getcwd(), "contrib", "testing",
76 "dbus-session.conf")
77 dbus_args = ["--fork",
78 "--config-file=" + config_file,
79 "--print-address=1",
80 "--print-pid=2"]
81 p = subprocess.Popen([dbus] + dbus_args,
82 bufsize=4096, stdout=subprocess.PIPE,
83 stderr=subprocess.PIPE)
84
85 self.dbus_address = "".join(p.stdout.readlines()).strip()
86 self.dbus_pid = int("".join(p.stderr.readlines()).strip())
87
88 if self.dbus_address != "":
89 os.environ["DBUS_SESSION_BUS_ADDRESS"] = self.dbus_address
90 else:
91 os.kill(self.dbus_pid, signal.SIGKILL)
92 raise DBusLaunchError("There was a problem launching dbus-daemon.")
93
94 def stopDBus(self):
95 """Stop our DBus session bus daemon."""
96 del os.environ["DBUS_SESSION_BUS_ADDRESS"]
97 os.kill(self.dbus_pid, signal.SIGKILL)
98
99 def run(self, testpath, test_pattern=None):69 def run(self, testpath, test_pattern=None):
100 """run the tests. """70 """run the tests. """
101 # install the glib2reactor before any import of the reactor to avoid71 # install the glib2reactor before any import of the reactor to avoid
@@ -106,7 +76,9 @@
106 from twisted.trial.reporter import TreeReporter76 from twisted.trial.reporter import TreeReporter
107 from twisted.trial.runner import TrialRunner77 from twisted.trial.runner import TrialRunner
10878
109 self.startDBus()79 from contrib.dbus_util import DBusRunner
80 dbus_runner = DBusRunner()
81 dbus_runner.startDBus()
11082
111 workingDirectory = os.path.join(os.getcwd(), "_trial_temp", "tmp")83 workingDirectory = os.path.join(os.getcwd(), "_trial_temp", "tmp")
112 runner = TrialRunner(reporterFactory=TreeReporter, realTimeErrors=True,84 runner = TrialRunner(reporterFactory=TreeReporter, realTimeErrors=True,
@@ -125,7 +97,7 @@
125 result = runner.run(suite)97 result = runner.run(suite)
126 success = result.wasSuccessful()98 success = result.wasSuccessful()
127 finally:99 finally:
128 self.stopDBus()100 dbus_runner.stopDBus()
129 if not success:101 if not success:
130 sys.exit(1)102 sys.exit(1)
131 else:103 else:
132104
=== modified file 'ubuntuone/syncdaemon/dbus_interface.py'
--- ubuntuone/syncdaemon/dbus_interface.py 2009-12-07 20:49:54 +0000
+++ ubuntuone/syncdaemon/dbus_interface.py 2009-12-16 20:33:18 +0000
@@ -22,6 +22,7 @@
2222
23from dbus import DBusException23from dbus import DBusException
24from itertools import groupby, chain24from itertools import groupby, chain
25from xml.etree import ElementTree
2526
26from twisted.internet import defer27from twisted.internet import defer
27from twisted.python.failure import Failure28from twisted.python.failure import Failure
@@ -90,6 +91,36 @@
90 """ emit's a Error signal. """91 """ emit's a Error signal. """
91 self.SignalError(signal, extra_args)92 self.SignalError(signal, extra_args)
9293
94 @classmethod
95 def _add_docstring(cls, func, reflection_data):
96 """add <docstring> tag to reflection_data if func.__doc__ isn't None."""
97 # add docstring element
98 if getattr(func, '__doc__', None) is not None:
99
100 element = ElementTree.fromstring(reflection_data)
101 doc = element.makeelement('docstring', dict())
102 data = '<![CDATA[' + func.__doc__ + ']]>'
103 doc.text = '%s'
104 element.insert(0, doc)
105 return ElementTree.tostring(element) % data
106 else:
107 return reflection_data
108
109 @classmethod
110 def _reflect_on_method(cls, func):
111 """override _reflect_on_method to provide an extra <docstring> element
112 in the xml.
113 """
114 reflection_data = dbus.service.Object._reflect_on_method(func)
115 reflection_data = cls._add_docstring(func, reflection_data)
116 return reflection_data
117
118 @classmethod
119 def _reflect_on_signal(cls, func):
120 reflection_data = dbus.service.Object._reflect_on_signal(func)
121 reflection_data = cls._add_docstring(func, reflection_data)
122 return reflection_data
123
93124
94class Status(DBusExposedObject):125class Status(DBusExposedObject):
95 """ Represent the status of the syncdaemon """126 """ Represent the status of the syncdaemon """

Subscribers

People subscribed via source and target branches