Home | History | Annotate | Download | only in python2.7
      1 """Self documenting XML-RPC Server.
      2 
      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.
      8 
      9 This module is built upon the pydoc and SimpleXMLRPCServer
     10 modules.
     11 """
     12 
     13 import pydoc
     14 import inspect
     15 import re
     16 import sys
     17 
     18 from SimpleXMLRPCServer import (SimpleXMLRPCServer,
     19             SimpleXMLRPCRequestHandler,
     20             CGIXMLRPCRequestHandler,
     21             resolve_dotted_attribute)
     22 
     23 class ServerHTMLDoc(pydoc.HTMLDoc):
     24     """Class used to generate pydoc HTML document for a server"""
     25 
     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
     32 
     33         # XXX Note that this regular expression 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]))
     46 
     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/dev/peps/pep-%04d/' % 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)
     66 
     67     def docroutine(self, object, name, mod=None,
     68                    funcs={}, classes={}, methods={}, cl=None):
     69         """Produce HTML documentation for a function or method object."""
     70 
     71         anchor = (cl and cl.__name__ or '') + '-' + name
     72         note = ''
     73 
     74         title = '<a name="%s"><strong>%s</strong></a>' % (
     75             self.escape(anchor), self.escape(name))
     76 
     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 (
     82                     args[1:],
     83                     varargs,
     84                     varkw,
     85                     defaults,
     86                     formatvalue=self.formatvalue
     87                 )
     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)
     92         else:
     93             argspec = '(...)'
     94 
     95         if isinstance(object, tuple):
     96             argspec = object[0] or argspec
     97             docstring = object[1] or ""
     98         else:
     99             docstring = pydoc.getdoc(object)
    100 
    101         decl = title + argspec + (note and self.grey(
    102                '<font face="helvetica, arial">%s</font>' % note))
    103 
    104         doc = self.markup(
    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)
    108 
    109     def docserver(self, server_name, package_documentation, methods):
    110         """Produce HTML documentation for an XML-RPC server."""
    111 
    112         fdict = {}
    113         for key, value in methods.items():
    114             fdict[key] = '#-' + key
    115             fdict[value] = fdict[key]
    116 
    117         server_name = self.escape(server_name)
    118         head = '<big><big><strong>%s</strong></big></big>' % server_name
    119         result = self.heading(head, '#ffffff', '#7799ee')
    120 
    121         doc = self.markup(package_documentation, self.preformat, fdict)
    122         doc = doc and '<tt>%s</tt>' % doc
    123         result = result + '<p>%s</p>\n' % doc
    124 
    125         contents = []
    126         method_items = sorted(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))
    131 
    132         return result
    133 
    134 class XMLRPCDocGenerator:
    135     """Generates documentation for an XML-RPC server.
    136 
    137     This class is designed as mix-in and should not
    138     be constructed directly.
    139     """
    140 
    141     def __init__(self):
    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 "\
    146             "protocol."
    147         self.server_title = 'XML-RPC Server Documentation'
    148 
    149     def set_server_title(self, server_title):
    150         """Set the HTML title of the generated server documentation"""
    151 
    152         self.server_title = server_title
    153 
    154     def set_server_name(self, server_name):
    155         """Set the name of the generated HTML server documentation"""
    156 
    157         self.server_name = server_name
    158 
    159     def set_server_documentation(self, server_documentation):
    160         """Set the documentation string for the entire server."""
    161 
    162         self.server_documentation = server_documentation
    163 
    164     def generate_html_documentation(self):
    165         """generate_html_documentation() => html documentation for the server
    166 
    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."""
    174 
    175         methods = {}
    176 
    177         for method_name in self.system_listMethods():
    178             if method_name in self.funcs:
    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)
    186 
    187                 method_info = tuple(method_info)
    188                 if method_info != (None, None):
    189                     method = method_info
    190                 elif not hasattr(self.instance, '_dispatch'):
    191                     try:
    192                         method = resolve_dotted_attribute(
    193                                     self.instance,
    194                                     method_name
    195                                     )
    196                     except AttributeError:
    197                         method = method_info
    198                 else:
    199                     method = method_info
    200             else:
    201                 assert 0, "Could not find method in self.functions and no "\
    202                           "instance installed"
    203 
    204             methods[method_name] = method
    205 
    206         documenter = ServerHTMLDoc()
    207         documentation = documenter.docserver(
    208                                 self.server_name,
    209                                 self.server_documentation,
    210                                 methods
    211                             )
    212 
    213         return documenter.page(self.server_title, documentation)
    214 
    215 class DocXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
    216     """XML-RPC and documentation request handler class.
    217 
    218     Handles all HTTP POST requests and attempts to decode them as
    219     XML-RPC requests.
    220 
    221     Handles all HTTP GET requests and interprets them as requests
    222     for documentation.
    223     """
    224 
    225     def do_GET(self):
    226         """Handles the HTTP GET request.
    227 
    228         Interpret all HTTP GET requests as requests for server
    229         documentation.
    230         """
    231         # Check that the path is legal
    232         if not self.is_rpc_path_valid():
    233             self.report_404()
    234             return
    235 
    236         response = self.server.generate_html_documentation()
    237         self.send_response(200)
    238         self.send_header("Content-type", "text/html")
    239         self.send_header("Content-length", str(len(response)))
    240         self.end_headers()
    241         self.wfile.write(response)
    242 
    243 class DocXMLRPCServer(  SimpleXMLRPCServer,
    244                         XMLRPCDocGenerator):
    245     """XML-RPC and HTML documentation server.
    246 
    247     Adds the ability to serve server documentation to the capabilities
    248     of SimpleXMLRPCServer.
    249     """
    250 
    251     def __init__(self, addr, requestHandler=DocXMLRPCRequestHandler,
    252                  logRequests=1, allow_none=False, encoding=None,
    253                  bind_and_activate=True):
    254         SimpleXMLRPCServer.__init__(self, addr, requestHandler, logRequests,
    255                                     allow_none, encoding, bind_and_activate)
    256         XMLRPCDocGenerator.__init__(self)
    257 
    258 class DocCGIXMLRPCRequestHandler(   CGIXMLRPCRequestHandler,
    259                                     XMLRPCDocGenerator):
    260     """Handler for XML-RPC data and documentation requests passed through
    261     CGI"""
    262 
    263     def handle_get(self):
    264         """Handles the HTTP GET request.
    265 
    266         Interpret all HTTP GET requests as requests for server
    267         documentation.
    268         """
    269 
    270         response = self.generate_html_documentation()
    271 
    272         print 'Content-Type: text/html'
    273         print 'Content-Length: %d' % len(response)
    274         print
    275         sys.stdout.write(response)
    276 
    277     def __init__(self):
    278         CGIXMLRPCRequestHandler.__init__(self)
    279         XMLRPCDocGenerator.__init__(self)
    280