1
"""Self documenting XML-RPC Server.
3
This module can be used to create XML-RPC servers that
4
serve pydoc-style documentation in response to HTTP
5
GET requests. This documentation is dynamically generated
6
based on the functions and methods registered with the
9
This module is built upon the pydoc and SimpleXMLRPCServer
19
from SimpleXMLRPCServer import (SimpleXMLRPCServer,
20
SimpleXMLRPCRequestHandler,
21
CGIXMLRPCRequestHandler,
22
resolve_dotted_attribute)
24
class ServerHTMLDoc(pydoc.HTMLDoc):
25
"""Class used to generate pydoc HTML document for a server"""
27
def markup(self, text, escape=None, funcs={}, classes={}, methods={}):
28
"""Mark up some plain text, given a context of symbols to look for.
29
Each context dictionary maps object names to anchor names."""
30
escape = escape or self.escape
34
# XXX Note that this regular expressions does not allow for the
35
# hyperlinking of arbitrary strings being used as method
36
# names. Only methods with names consisting of word characters
37
# and '.'s are hyperlinked.
38
pattern = re.compile(r'\b((http|ftp)://\S+[\w/]|'
41
r'(self\.)?((?:\w|\.)+))\b')
43
match = pattern.search(text, here)
45
start, end = match.span()
46
results.append(escape(text[here:start]))
48
all, scheme, rfc, pep, selfdot, name = match.groups()
50
url = escape(all).replace('"', '"')
51
results.append('<a href="%s">%s</a>' % (url, url))
53
url = 'http://www.rfc-editor.org/rfc/rfc%d.txt' % int(rfc)
54
results.append('<a href="%s">%s</a>' % (url, escape(all)))
56
url = 'http://www.python.org/peps/pep-%04d.html' % int(pep)
57
results.append('<a href="%s">%s</a>' % (url, escape(all)))
58
elif text[end:end+1] == '(':
59
results.append(self.namelink(name, methods, funcs, classes))
61
results.append('self.<strong>%s</strong>' % name)
63
results.append(self.namelink(name, classes))
65
results.append(escape(text[here:]))
66
return ''.join(results)
68
def docroutine(self, object, name=None, mod=None,
69
funcs={}, classes={}, methods={}, cl=None):
70
"""Produce HTML documentation for a function or method object."""
72
anchor = (cl and cl.__name__ or '') + '-' + name
75
title = '<a name="%s"><strong>%s</strong></a>' % (anchor, name)
77
if inspect.ismethod(object):
78
args, varargs, varkw, defaults = inspect.getargspec(object.im_func)
79
# exclude the argument bound to the instance, it will be
80
# confusing to the non-Python user
81
argspec = inspect.formatargspec (
86
formatvalue=self.formatvalue
88
elif inspect.isfunction(object):
89
args, varargs, varkw, defaults = inspect.getargspec(object)
90
argspec = inspect.formatargspec(
91
args, varargs, varkw, defaults, formatvalue=self.formatvalue)
95
if isinstance(object, types.TupleType):
96
argspec = object[0] or argspec
97
docstring = object[1] or ""
99
docstring = pydoc.getdoc(object)
101
decl = title + argspec + (note and self.grey(
102
'<font face="helvetica, arial">%s</font>' % note))
105
docstring, self.preformat, funcs, classes, methods)
106
doc = doc and '<dd><tt>%s</tt></dd>' % doc
107
return '<dl><dt>%s</dt>%s</dl>\n' % (decl, doc)
109
def docserver(self, server_name, package_documentation, methods):
110
"""Produce HTML documentation for an XML-RPC server."""
113
for key, value in methods.items():
114
fdict[key] = '#-' + key
115
fdict[value] = fdict[key]
117
head = '<big><big><strong>%s</strong></big></big>' % server_name
118
result = self.heading(head, '#ffffff', '#7799ee')
120
doc = self.markup(package_documentation, self.preformat, fdict)
121
doc = doc and '<tt>%s</tt>' % doc
122
result = result + '<p>%s</p>\n' % doc
125
method_items = methods.items()
127
for key, value in method_items:
128
contents.append(self.docroutine(value, key, funcs=fdict))
129
result = result + self.bigsection(
130
'Methods', '#ffffff', '#eeaa77', pydoc.join(contents))
134
class XMLRPCDocGenerator:
135
"""Generates documentation for an XML-RPC server.
137
This class is designed as mix-in and should not
138
be constructed directly.
142
# setup variables used for HTML documentation
143
self.server_name = 'XML-RPC Server Documentation'
144
self.server_documentation = \
145
"This server exports the following methods through the XML-RPC "\
147
self.server_title = 'XML-RPC Server Documentation'
149
def set_server_title(self, server_title):
150
"""Set the HTML title of the generated server documentation"""
152
self.server_title = server_title
154
def set_server_name(self, server_name):
155
"""Set the name of the generated HTML server documentation"""
157
self.server_name = server_name
159
def set_server_documentation(self, server_documentation):
160
"""Set the documentation string for the entire server."""
162
self.server_documentation = server_documentation
164
def generate_html_documentation(self):
165
"""generate_html_documentation() => html documentation for the server
167
Generates HTML documentation for the server using introspection for
168
installed functions and instances that do not implement the
169
_dispatch method. Alternatively, instances can choose to implement
170
the _get_method_argstring(method_name) method to provide the
171
argument string used in the documentation and the
172
_methodHelp(method_name) method to provide the help text used
173
in the documentation."""
177
for method_name in self.system_listMethods():
178
if self.funcs.has_key(method_name):
179
method = self.funcs[method_name]
180
elif self.instance is not None:
181
method_info = [None, None] # argspec, documentation
182
if hasattr(self.instance, '_get_method_argstring'):
183
method_info[0] = self.instance._get_method_argstring(method_name)
184
if hasattr(self.instance, '_methodHelp'):
185
method_info[1] = self.instance._methodHelp(method_name)
187
method_info = tuple(method_info)
188
if method_info != (None, None):
190
elif not hasattr(self.instance, '_dispatch'):
192
method = resolve_dotted_attribute(
196
except AttributeError:
201
assert 0, "Could not find method in self.functions and no "\
204
methods[method_name] = method
206
documenter = ServerHTMLDoc()
207
documentation = documenter.docserver(
209
self.server_documentation,
213
return documenter.page(self.server_title, documentation)
215
class DocXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
216
"""XML-RPC and documentation request handler class.
218
Handles all HTTP POST requests and attempts to decode them as
221
Handles all HTTP GET requests and interprets them as requests
226
"""Handles the HTTP GET request.
228
Interpret all HTTP GET requests as requests for server
232
response = self.server.generate_html_documentation()
233
self.send_response(200)
234
self.send_header("Content-type", "text/html")
235
self.send_header("Content-length", str(len(response)))
237
self.wfile.write(response)
239
# shut down the connection
241
self.connection.shutdown(1)
243
class DocXMLRPCServer( SimpleXMLRPCServer,
245
"""XML-RPC and HTML documentation server.
247
Adds the ability to serve server documentation to the capabilities
248
of SimpleXMLRPCServer.
251
def __init__(self, addr, requestHandler=DocXMLRPCRequestHandler,
253
SimpleXMLRPCServer.__init__(self, addr, requestHandler, logRequests)
254
XMLRPCDocGenerator.__init__(self)
256
class DocCGIXMLRPCRequestHandler( CGIXMLRPCRequestHandler,
258
"""Handler for XML-RPC data and documentation requests passed through
261
def handle_get(self):
262
"""Handles the HTTP GET request.
264
Interpret all HTTP GET requests as requests for server
268
response = self.generate_html_documentation()
270
print 'Content-Type: text/html'
271
print 'Content-Length: %d' % len(response)
273
sys.stdout.write(response)
276
CGIXMLRPCRequestHandler.__init__(self)
277
XMLRPCDocGenerator.__init__(self)
279
if __name__ == '__main__':
281
"""deg_to_rad(90) => 1.5707963267948966
283
Converts an angle in degrees to an angle in radians"""
285
return deg * math.pi / 180
287
server = DocXMLRPCServer(("localhost", 8000))
289
server.set_server_title("Math Server")
290
server.set_server_name("Math XML-RPC Server")
291
server.set_server_documentation("""This server supports various mathematical functions.
293
You can use it from Python as follows:
295
>>> from xmlrpclib import ServerProxy
296
>>> s = ServerProxy("http://localhost:8000")
297
>>> s.deg_to_rad(90.0)
298
1.5707963267948966""")
300
server.register_function(deg_to_rad)
301
server.register_introspection_functions()
303
server.serve_forever()