Saved and restored logging._handlerList at the same time as saving/restoring logging...
[python.git] / Lib / DocXMLRPCServer.py
blob259fb18b8631ad38ca47d84402c5d8da8d55bada
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.
231 response = self.server.generate_html_documentation()
232 self.send_response(200)
233 self.send_header("Content-type", "text/html")
234 self.send_header("Content-length", str(len(response)))
235 self.end_headers()
236 self.wfile.write(response)
238 # shut down the connection
239 self.wfile.flush()
240 self.connection.shutdown(1)
242 class DocXMLRPCServer( SimpleXMLRPCServer,
243 XMLRPCDocGenerator):
244 """XML-RPC and HTML documentation server.
246 Adds the ability to serve server documentation to the capabilities
247 of SimpleXMLRPCServer.
250 def __init__(self, addr, requestHandler=DocXMLRPCRequestHandler,
251 logRequests=1):
252 SimpleXMLRPCServer.__init__(self, addr, requestHandler, logRequests)
253 XMLRPCDocGenerator.__init__(self)
255 class DocCGIXMLRPCRequestHandler( CGIXMLRPCRequestHandler,
256 XMLRPCDocGenerator):
257 """Handler for XML-RPC data and documentation requests passed through
258 CGI"""
260 def handle_get(self):
261 """Handles the HTTP GET request.
263 Interpret all HTTP GET requests as requests for server
264 documentation.
267 response = self.generate_html_documentation()
269 print 'Content-Type: text/html'
270 print 'Content-Length: %d' % len(response)
271 print
272 sys.stdout.write(response)
274 def __init__(self):
275 CGIXMLRPCRequestHandler.__init__(self)
276 XMLRPCDocGenerator.__init__(self)
278 if __name__ == '__main__':
279 def deg_to_rad(deg):
280 """deg_to_rad(90) => 1.5707963267948966
282 Converts an angle in degrees to an angle in radians"""
283 import math
284 return deg * math.pi / 180
286 server = DocXMLRPCServer(("localhost", 8000))
288 server.set_server_title("Math Server")
289 server.set_server_name("Math XML-RPC Server")
290 server.set_server_documentation("""This server supports various mathematical functions.
292 You can use it from Python as follows:
294 >>> from xmlrpclib import ServerProxy
295 >>> s = ServerProxy("http://localhost:8000")
296 >>> s.deg_to_rad(90.0)
297 1.5707963267948966""")
299 server.register_function(deg_to_rad)
300 server.register_introspection_functions()
302 server.serve_forever()