From 281b2c62117c9cb26f8dec55765f9f211282f298 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20v=2E=20L=C3=B6wis?= Date: Fri, 18 Apr 2003 21:04:39 +0000 Subject: Patch #536883: SimpleXMLRPCServer auto-docing subclass. --- Doc/lib/lib.tex | 1 + Doc/lib/libdocxmlrpc.tex | 105 +++++++++++++++ Doc/whatsnew/whatsnew23.tex | 8 ++ Lib/DocXMLRPCServer.py | 302 ++++++++++++++++++++++++++++++++++++++++++++ Misc/NEWS | 2 + 5 files changed, 418 insertions(+) create mode 100644 Doc/lib/libdocxmlrpc.tex create mode 100644 Lib/DocXMLRPCServer.py diff --git a/Doc/lib/lib.tex b/Doc/lib/lib.tex index 5faeedc..3843d46 100644 --- a/Doc/lib/lib.tex +++ b/Doc/lib/lib.tex @@ -231,6 +231,7 @@ and how to embed it in other applications. \input{libcookie} \input{libxmlrpclib} \input{libsimplexmlrpc} +\input{libdocxmlrpc} \input{libasyncore} \input{libasynchat} diff --git a/Doc/lib/libdocxmlrpc.tex b/Doc/lib/libdocxmlrpc.tex new file mode 100644 index 0000000..fc69031 --- /dev/null +++ b/Doc/lib/libdocxmlrpc.tex @@ -0,0 +1,105 @@ +\section{\module{DocXMLRPCServer} --- + Self-documenting XML-RPC server} + +\declaremodule{standard}{DocXMLRPCServer} +\modulesynopsis{Self-documenting XML-RPC server implementation.} +\moduleauthor{Brian Quinlan}{brianq@activestate.com} +\sectionauthor{Brian Quinlan}{brianq@activestate.com} + +\versionadded{2.3} + +The \module{DocXMLRPCServer} module extends the classes found in +\module{SimpleXMLRPCServer} to serve HTML documentation in response to +HTTP GET requests. Servers can either be free standing, using +\class{DocXMLRPCServer}, or embedded in a CGI environment, using +\class{DocCGIXMLRPCRequestHandler}. + +\begin{classdesc}{DocXMLRPCServer}{addr\optional{, + requestHandler\optional{, logRequests}}} + +Create a new server instance. All parameters have the same meaning as +for \class{SimpleXMLRPCServer.SimpleXMLRPCServer}; +\var{requestHandler} defaults to \class{DocXMLRPCRequestHandler}. + +\end{classdesc} + +\begin{classdesc}{DocCGIXMLRPCRequestHandler}{} + +Create a new instance to handle XML-RPC requests in a CGI environment. + +\end{classdesc} + +\begin{classdesc}{DocXMLRPCRequestHandler}{} + +Create a new request handler instance. This request handler supports +XML-RPC POST requests, documentation GET requests, and modifies +logging so that the \var{logRequests} parameter to the +\class{DocXMLRPCServer} constructor parameter is honored. + +\end{classdesc} + +\subsection{DocXMLRPCServer Objects \label{doc-xmlrpc-servers}} + +The \class{DocXMLRPCServer} class is derived from +\class{SimpleXMLRPCServer.SimpleXMLRPCServer} and provides a means of +creating self-documenting, stand alone XML-RPC servers. HTTP POST +requests are handled as XML-RPC method calls. HTTP GET requests are +handled by generating pydoc-style HTML documentation. This allows a +server to provide its own web-based documentation. + +\begin{methoddesc}{set_server_title}{server_title} + +Set the title used in the generated HTML documentation. This title +will be used inside the HTML "title" element. + +\end{methoddesc} + +\begin{set_server_name}{server_name} + +Set the name used in the generated HTML documentation. This name will +appear at the top of the generated documentation inside a "h1" +element. + +\end{methoddesc} + + +\begin{set_server_documentation}{server_documentation} + +Set the description used in the generated HTML documentation. This +description will appear as a paragraph, below the server name, in the +documentation. + +\end{methoddesc} + +\subsection{DocCGIXMLRPCRequestHandler} + +The \class{DocCGIXMLRPCRequestHandler} class is derived from +\class{SimpleXMLRPCServer.CGIXMLRPCRequestHandler} and provides a means +of creating self-documenting, XML-RPC CGI scripts. HTTP POST requests +are handled as XML-RPC method calls. HTTP GET requests are handled by +generating pydoc-style HTML documentation. This allows a server to +provide its own web-based documentation. + +\begin{methoddesc}{set_server_title}{server_title} + +Set the title used in the generated HTML documentation. This title +will be used inside the HTML "title" element. + +\end{methoddesc} + +\begin{methoddesc}{set_server_name}{server_name} + +Set the name used in the generated HTML documentation. This name will +appear at the top of the generated documentation inside a "h1" +element. + +\end{methoddesc} + + +\begin{methoddesc}{set_server_documentation}{server_documentation} + +Set the description used in the generated HTML documentation. This +description will appear as a paragraph, below the server name, in the +documentation. + +\end{methoddesc} diff --git a/Doc/whatsnew/whatsnew23.tex b/Doc/whatsnew/whatsnew23.tex index 38669b2..f447ed4 100644 --- a/Doc/whatsnew/whatsnew23.tex +++ b/Doc/whatsnew/whatsnew23.tex @@ -1812,6 +1812,14 @@ of the URL is ASCII only. To implement this change, the module \module{stringprep}, the tool \code{mkstringprep} and the \code{punycode} encoding have been added. +\item The new \module{DocXMLRPCServer} allows to write +self-documenting XML-RPC servers. Run it in demo mode (as a program) +to see it in action: Pointing the Web browser to the RPC server +produces pydoc-style documentation; pointing xmlrpclib to the +server allows to invoke the actual methods. + +Contributed by Brian Quinlan. + \end{itemize} 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('%s' % (url, url)) + elif rfc: + url = 'http://www.rfc-editor.org/rfc/rfc%d.txt' % int(rfc) + results.append('%s' % (url, escape(all))) + elif pep: + url = 'http://www.python.org/peps/pep-%04d.html' % int(pep) + results.append('%s' % (url, escape(all))) + elif text[end:end+1] == '(': + results.append(self.namelink(name, methods, funcs, classes)) + elif selfdot: + results.append('self.%s' % 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 = '%s' % (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( + '%s' % note)) + + doc = self.markup( + docstring, self.preformat, funcs, classes, methods) + doc = doc and '
%s
' % doc + return '
%s
%s
\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 = '%s' % server_name + result = self.heading(head, '#ffffff', '#7799ee') + + doc = self.markup(package_documentation, self.preformat, fdict) + doc = doc and '%s' % doc + result = result + '

%s

\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 diff --git a/Misc/NEWS b/Misc/NEWS index 07a9bc6..cf3b5f7 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -118,6 +118,8 @@ Extension modules Library ------- +- The self-documenting XML server library DocXMLRPCServer was added. + - Support for internationalized domain names has been added through the 'idna' and 'punycode' encodings, the 'stringprep' module, the 'mkstringprep' tool, and enhancements to the socket and httplib -- cgit v0.12