diff options
author | Martin v. Löwis <martin@v.loewis.de> | 2003-04-18 21:04:39 (GMT) |
---|---|---|
committer | Martin v. Löwis <martin@v.loewis.de> | 2003-04-18 21:04:39 (GMT) |
commit | 281b2c62117c9cb26f8dec55765f9f211282f298 (patch) | |
tree | 7c61c1d796ad8ed63be73935bc60d8eab1005d7f /Lib | |
parent | b5c4b7be3fc0c04f30535b456a22baef4d9b324e (diff) | |
download | cpython-281b2c62117c9cb26f8dec55765f9f211282f298.zip cpython-281b2c62117c9cb26f8dec55765f9f211282f298.tar.gz cpython-281b2c62117c9cb26f8dec55765f9f211282f298.tar.bz2 |
Patch #536883: SimpleXMLRPCServer auto-docing subclass.
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/DocXMLRPCServer.py | 302 |
1 files changed, 302 insertions, 0 deletions
diff --git a/Lib/DocXMLRPCServer.py b/Lib/DocXMLRPCServer.py new file mode 100644 index 0000000..adb8a93 --- /dev/null +++ b/Lib/DocXMLRPCServer.py @@ -0,0 +1,302 @@ +"""Self documenting XML-RPC Server. + +This module can be used to create XML-RPC servers that +serve pydoc-style documentation in response to HTTP +GET requests. This documentation is dynamically generated +based on the functions and methods registered with the +server. + +This module is built upon the pydoc and SimpleXMLRPCServer +modules. +""" + +import pydoc +import inspect +import types +import re + +from SimpleXMLRPCServer import SimpleXMLRPCServer,\ + SimpleXMLRPCRequestHandler,\ + CGIXMLRPCRequestHandler,\ + resolve_dotted_attribute + +class ServerHTMLDoc(pydoc.HTMLDoc): + """Class used to generate pydoc HTML document for a server""" + + def markup(self, text, escape=None, funcs={}, classes={}, methods={}): + """Mark up some plain text, given a context of symbols to look for. + Each context dictionary maps object names to anchor names.""" + escape = escape or self.escape + results = [] + here = 0 + + # XXX Note that this regular expressions does not allow for the + # hyperlinking of arbitrary strings being used as method + # names. Only methods with names consisting of word characters + # and '.'s are hyperlinked. + pattern = re.compile(r'\b((http|ftp)://\S+[\w/]|' + r'RFC[- ]?(\d+)|' + r'PEP[- ]?(\d+)|' + r'(self\.)?((?:\w|\.)+))\b') + while 1: + match = pattern.search(text, here) + if not match: break + start, end = match.span() + results.append(escape(text[here:start])) + + all, scheme, rfc, pep, selfdot, name = match.groups() + if scheme: + url = escape(all).replace('"', '"') + results.append('<a href="%s">%s</a>' % (url, url)) + elif rfc: + url = 'http://www.rfc-editor.org/rfc/rfc%d.txt' % int(rfc) + results.append('<a href="%s">%s</a>' % (url, escape(all))) + elif pep: + url = 'http://www.python.org/peps/pep-%04d.html' % int(pep) + results.append('<a href="%s">%s</a>' % (url, escape(all))) + elif text[end:end+1] == '(': + results.append(self.namelink(name, methods, funcs, classes)) + elif selfdot: + results.append('self.<strong>%s</strong>' % name) + else: + results.append(self.namelink(name, classes)) + here = end + results.append(escape(text[here:])) + return ''.join(results) + + def docroutine(self, object, name=None, mod=None, + funcs={}, classes={}, methods={}, cl=None): + """Produce HTML documentation for a function or method object.""" + + anchor = (cl and cl.__name__ or '') + '-' + name + note = '' + + title = '<a name="%s"><strong>%s</strong></a>' % (anchor, name) + + if inspect.ismethod(object): + args, varargs, varkw, defaults = inspect.getargspec(object.im_func) + # exclude the argument bound to the instance, it will be + # confusing to the non-Python user + argspec = inspect.formatargspec ( + args[1:], + varargs, + varkw, + defaults, + formatvalue=self.formatvalue + ) + elif inspect.isfunction(object): + args, varargs, varkw, defaults = inspect.getargspec(object) + argspec = inspect.formatargspec( + args, varargs, varkw, defaults, formatvalue=self.formatvalue) + else: + argspec = '(...)' + + if isinstance(object, types.TupleType): + argspec = object[0] or argspec + docstring = object[1] or "" + else: + docstring = pydoc.getdoc(object) + + decl = title + argspec + (note and self.grey( + '<font face="helvetica, arial">%s</font>' % note)) + + doc = self.markup( + docstring, self.preformat, funcs, classes, methods) + doc = doc and '<dd><tt>%s</tt></dd>' % doc + return '<dl><dt>%s</dt>%s</dl>\n' % (decl, doc) + + def docserver(self, server_name, package_documentation, methods): + """Produce HTML documentation for an XML-RPC server.""" + + fdict = {} + for key, value in methods.items(): + fdict[key] = '#-' + key + fdict[value] = fdict[key] + + head = '<big><big><strong>%s</strong></big></big>' % server_name + result = self.heading(head, '#ffffff', '#7799ee') + + doc = self.markup(package_documentation, self.preformat, fdict) + doc = doc and '<tt>%s</tt>' % doc + result = result + '<p>%s</p>\n' % doc + + contents = [] + method_items = methods.items() + method_items.sort() + for key, value in method_items: + contents.append(self.docroutine(value, key, funcs=fdict)) + result = result + self.bigsection( + 'Methods', '#ffffff', '#eeaa77', pydoc.join(contents)) + + return result + +class XMLRPCDocGenerator: + """Generates documentation for an XML-RPC server. + + This class is designed as mix-in and should not + be constructed directly. + """ + + def __init__(self): + # setup variables used for HTML documentation + self.server_name = 'XML-RPC Server Documentation' + self.server_documentation = \ + "This server exports the following methods through the XML-RPC "\ + "protocol." + self.server_title = 'XML-RPC Server Documentation' + + def set_server_title(self, server_title): + """Set the HTML title of the generated server documentation""" + + self.server_title = server_title + + def set_server_name(self, server_name): + """Set the name of the generated HTML server documentation""" + + self.server_name = server_name + + def set_server_documentation(self, server_documentation): + """Set the documentation string for the entire server.""" + + self.server_documentation = server_documentation + + def generate_html_documentation(self): + """generate_html_documentation() => html documentation for the server + + Generates HTML documentation for the server using introspection for + installed functions and instances that do not implement the + _dispatch method. Alternatively, instances can choose to implement + the _get_method_argstring(method_name) method to provide the + argument string used in the documentation and the + _methodHelp(method_name) method to provide the help text used + in the documentation.""" + + methods = {} + + for method_name in self.system_listMethods(): + if self.funcs.has_key(method_name): + method = self.funcs[method_name] + elif self.instance is not None: + method_info = [None, None] # argspec, documentation + if hasattr(self.instance, '_get_method_argstring'): + method_info[0] = self.instance._get_method_argstring(method_name) + if hasattr(self.instance, '_methodHelp'): + method_info[1] = self.instance._methodHelp(method_name) + + method_info = tuple(method_info) + if method_info != (None, None): + method = method_info + elif not hasattr(self.instance, '_dispatch'): + try: + method = resolve_dotted_attribute( + self.instance, + method_name + ) + except AttributeError: + method = method_info + else: + method = method_info + else: + assert 0, "Could not find method in self.functions and no "\ + "instance installed" + + methods[method_name] = method + + documenter = ServerHTMLDoc() + documentation = documenter.docserver( + self.server_name, + self.server_documentation, + methods + ) + + return documenter.page(self.server_title, documentation) + +class DocXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): + """XML-RPC and documentation request handler class. + + Handles all HTTP POST requests and attempts to decode them as + XML-RPC requests. + + Handles all HTTP GET requests and interprets them as requests + for documentation. + """ + + def do_GET(self): + """Handles the HTTP GET request. + + Interpret all HTTP GET requests as requests for server + documentation. + """ + + response = self.server.generate_html_documentation() + self.send_response(200) + self.send_header("Content-type", "text/html") + self.send_header("Content-length", str(len(response))) + self.end_headers() + self.wfile.write(response) + + # shut down the connection + self.wfile.flush() + self.connection.shutdown(1) + +class DocXMLRPCServer( SimpleXMLRPCServer, + XMLRPCDocGenerator): + """XML-RPC and HTML documentation server. + + Adds the ability to serve server documentation to the capabilities + of SimpleXMLRPCServer. + """ + + def __init__(self, addr, requestHandler=DocXMLRPCRequestHandler, + logRequests=1): + SimpleXMLRPCServer.__init__(self, addr, requestHandler, logRequests) + XMLRPCDocGenerator.__init__(self) + +class DocCGIXMLRPCRequestHandler( CGIXMLRPCRequestHandler, + XMLRPCDocGenerator): + """Handler for XML-RPC data and documentation requests passed through + CGI""" + + def handle_get(self): + """Handles the HTTP GET request. + + Interpret all HTTP GET requests as requests for server + documentation. + """ + + response = self.generate_html_documentation() + + print 'Content-Type: text/html' + print 'Content-Length: %d' % len(response) + print + print response + + def __init__(self): + CGIXMLRPCRequestHandler.__init__(self) + XMLRPCDocGenerator.__init__(self) + +if __name__ == '__main__': + def deg_to_rad(deg): + """deg_to_rad(90) => 1.5707963267948966 + + Converts an angle in degrees to an angle in radians""" + import math + return deg * math.pi / 180 + + server = DocXMLRPCServer(("localhost", 8000)) + + server.set_server_title("Math Server") + server.set_server_name("Math XML-RPC Server") + server.set_server_documentation("""This server supports various mathematical functions. + +You can use it from Python as follows: + +>>> from xmlrpclib import ServerProxy +>>> s = ServerProxy("http://localhost:8000") +>>> s.deg_to_rad(90.0) +1.5707963267948966""") + + server.register_function(deg_to_rad) + server.register_introspection_functions() + + server.serve_forever()
\ No newline at end of file |