Patch by Jeremy Katz (SF #1609407)
[python.git] / Lib / DocXMLRPCServer.py
blob86ed32b6fc193248bb791c68e01ac95fd0e745bc
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
7 server.
9 This module is built upon the pydoc and SimpleXMLRPCServer
10 modules.
11 """
13 import pydoc
14 import inspect
15 import re
16 import sys
18 from SimpleXMLRPCServer import (SimpleXMLRPCServer,
19 SimpleXMLRPCRequestHandler,
20 CGIXMLRPCRequestHandler,
21 resolve_dotted_attribute)
23 class ServerHTMLDoc(pydoc.HTMLDoc):
24 """Class used to generate pydoc HTML document for a server"""
26 def markup(self, text, escape=None, funcs={}, classes={}, methods={}):
27 """Mark up some plain text, given a context of symbols to look for.
28 Each context dictionary maps object names to anchor names."""
29 escape = escape or self.escape
30 results = []
31 here = 0
33 # XXX Note that this regular expressions does not allow for the
34 # hyperlinking of arbitrary strings being used as method
35 # names. Only methods with names consisting of word characters
36 # and '.'s are hyperlinked.
37 pattern = re.compile(r'\b((http|ftp)://\S+[\w/]|'
38 r'RFC[- ]?(\d+)|'
39 r'PEP[- ]?(\d+)|'
40 r'(self\.)?((?:\w|\.)+))\b')
41 while 1:
42 match = pattern.search(text, here)
43 if not match: break
44 start, end = match.span()
45 results.append(escape(text[here:start]))
47 all, scheme, rfc, pep, selfdot, name = match.groups()
48 if scheme:
49 url = escape(all).replace('"', '"')
50 results.append('<a href="%s">%s</a>' % (url, url))
51 elif rfc:
52 url = 'http://www.rfc-editor.org/rfc/rfc%d.txt' % int(rfc)
53 results.append('<a href="%s">%s</a>' % (url, escape(all)))
54 elif pep:
55 url = 'http://www.python.org/peps/pep-%04d.html' % int(pep)
56 results.append('<a href="%s">%s</a>' % (url, escape(all)))
57 elif text[end:end+1] == '(':
58 results.append(self.namelink(name, methods, funcs, classes))
59 elif selfdot:
60 results.append('self.<strong>%s</strong>' % name)
61 else:
62 results.append(self.namelink(name, classes))
63 here = end
64 results.append(escape(text[here:]))
65 return ''.join(results)
67 def docroutine(self, object, name=None, mod=None,
68 funcs={}, classes={}, methods={}, cl=None):
69 """Produce HTML documentation for a function or method object."""
71 anchor = (cl and cl.__name__ or '') + '-' + name
72 note = ''
74 title = '<a name="%s"><strong>%s</strong></a>' % (anchor, name)
76 if inspect.ismethod(object):
77 args, varargs, varkw, defaults = inspect.getargspec(object.im_func)
78 # exclude the argument bound to the instance, it will be
79 # confusing to the non-Python user
80 argspec = inspect.formatargspec (
81 args[1:],
82 varargs,
83 varkw,
84 defaults,
85 formatvalue=self.formatvalue
87 elif inspect.isfunction(object):
88 args, varargs, varkw, defaults = inspect.getargspec(object)
89 argspec = inspect.formatargspec(
90 args, varargs, varkw, defaults, formatvalue=self.formatvalue)
91 else:
92 argspec = '(...)'
94 if isinstance(object, tuple):
95 argspec = object[0] or argspec
96 docstring = object[1] or ""
97 else:
98 docstring = pydoc.getdoc(object)
100 decl = title + argspec + (note and self.grey(
101 '<font face="helvetica, arial">%s</font>' % note))
103 doc = self.markup(
104 docstring, self.preformat, funcs, classes, methods)
105 doc = doc and '<dd><tt>%s</tt></dd>' % doc
106 return '<dl><dt>%s</dt>%s</dl>\n' % (decl, doc)
108 def docserver(self, server_name, package_documentation, methods):
109 """Produce HTML documentation for an XML-RPC server."""
111 fdict = {}
112 for key, value in methods.items():
113 fdict[key] = '#-' + key
114 fdict[value] = fdict[key]
116 head = '<big><big><strong>%s</strong></big></big>' % server_name
117 result = self.heading(head, '#ffffff', '#7799ee')
119 doc = self.markup(package_documentation, self.preformat, fdict)
120 doc = doc and '<tt>%s</tt>' % doc
121 result = result + '<p>%s</p>\n' % doc
123 contents = []
124 method_items = methods.items()
125 method_items.sort()
126 for key, value in method_items:
127 contents.append(self.docroutine(value, key, funcs=fdict))
128 result = result + self.bigsection(
129 'Methods', '#ffffff', '#eeaa77', pydoc.join(contents))
131 return result
133 class XMLRPCDocGenerator:
134 """Generates documentation for an XML-RPC server.
136 This class is designed as mix-in and should not
137 be constructed directly.
140 def __init__(self):
141 # setup variables used for HTML documentation
142 self.server_name = 'XML-RPC Server Documentation'
143 self.server_documentation = \
144 "This server exports the following methods through the XML-RPC "\
145 "protocol."
146 self.server_title = 'XML-RPC Server Documentation'
148 def set_server_title(self, server_title):
149 """Set the HTML title of the generated server documentation"""
151 self.server_title = server_title
153 def set_server_name(self, server_name):
154 """Set the name of the generated HTML server documentation"""
156 self.server_name = server_name
158 def set_server_documentation(self, server_documentation):
159 """Set the documentation string for the entire server."""
161 self.server_documentation = server_documentation
163 def generate_html_documentation(self):
164 """generate_html_documentation() => html documentation for the server
166 Generates HTML documentation for the server using introspection for
167 installed functions and instances that do not implement the
168 _dispatch method. Alternatively, instances can choose to implement
169 the _get_method_argstring(method_name) method to provide the
170 argument string used in the documentation and the
171 _methodHelp(method_name) method to provide the help text used
172 in the documentation."""
174 methods = {}
176 for method_name in self.system_listMethods():
177 if self.funcs.has_key(method_name):
178 method = self.funcs[method_name]
179 elif self.instance is not None:
180 method_info = [None, None] # argspec, documentation
181 if hasattr(self.instance, '_get_method_argstring'):
182 method_info[0] = self.instance._get_method_argstring(method_name)
183 if hasattr(self.instance, '_methodHelp'):
184 method_info[1] = self.instance._methodHelp(method_name)
186 method_info = tuple(method_info)
187 if method_info != (None, None):
188 method = method_info
189 elif not hasattr(self.instance, '_dispatch'):
190 try:
191 method = resolve_dotted_attribute(
192 self.instance,
193 method_name
195 except AttributeError:
196 method = method_info
197 else:
198 method = method_info
199 else:
200 assert 0, "Could not find method in self.functions and no "\
201 "instance installed"
203 methods[method_name] = method
205 documenter = ServerHTMLDoc()
206 documentation = documenter.docserver(
207 self.server_name,
208 self.server_documentation,
209 methods
212 return documenter.page(self.server_title, documentation)
214 class DocXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
215 """XML-RPC and documentation request handler class.
217 Handles all HTTP POST requests and attempts to decode them as
218 XML-RPC requests.
220 Handles all HTTP GET requests and interprets them as requests
221 for documentation.
224 def do_GET(self):
225 """Handles the HTTP GET request.
227 Interpret all HTTP GET requests as requests for server
228 documentation.
230 # Check that the path is legal
231 if not self.is_rpc_path_valid():
232 self.report_404()
233 return
235 response = self.server.generate_html_documentation()
236 self.send_response(200)
237 self.send_header("Content-type", "text/html")
238 self.send_header("Content-length", str(len(response)))
239 self.end_headers()
240 self.wfile.write(response)
242 # shut down the connection
243 self.wfile.flush()
244 self.connection.shutdown(1)
246 class DocXMLRPCServer( SimpleXMLRPCServer,
247 XMLRPCDocGenerator):
248 """XML-RPC and HTML documentation server.
250 Adds the ability to serve server documentation to the capabilities
251 of SimpleXMLRPCServer.
254 def __init__(self, addr, requestHandler=DocXMLRPCRequestHandler,
255 logRequests=1):
256 SimpleXMLRPCServer.__init__(self, addr, requestHandler, logRequests)
257 XMLRPCDocGenerator.__init__(self)
259 class DocCGIXMLRPCRequestHandler( CGIXMLRPCRequestHandler,
260 XMLRPCDocGenerator):
261 """Handler for XML-RPC data and documentation requests passed through
262 CGI"""
264 def handle_get(self):
265 """Handles the HTTP GET request.
267 Interpret all HTTP GET requests as requests for server
268 documentation.
271 response = self.generate_html_documentation()
273 print 'Content-Type: text/html'
274 print 'Content-Length: %d' % len(response)
275 print
276 sys.stdout.write(response)
278 def __init__(self):
279 CGIXMLRPCRequestHandler.__init__(self)
280 XMLRPCDocGenerator.__init__(self)
282 if __name__ == '__main__':
283 def deg_to_rad(deg):
284 """deg_to_rad(90) => 1.5707963267948966
286 Converts an angle in degrees to an angle in radians"""
287 import math
288 return deg * math.pi / 180
290 server = DocXMLRPCServer(("localhost", 8000))
292 server.set_server_title("Math Server")
293 server.set_server_name("Math XML-RPC Server")
294 server.set_server_documentation("""This server supports various mathematical functions.
296 You can use it from Python as follows:
298 >>> from xmlrpclib import ServerProxy
299 >>> s = ServerProxy("http://localhost:8000")
300 >>> s.deg_to_rad(90.0)
301 1.5707963267948966""")
303 server.register_function(deg_to_rad)
304 server.register_introspection_functions()
306 server.serve_forever()