0001 """Self documenting XML-RPC Server. 0002 0003 This module can be used to create XML-RPC servers that 0004 serve pydoc-style documentation in response to HTTP 0005 GET requests. This documentation is dynamically generated 0006 based on the functions and methods registered with the 0007 server. 0008 0009 This module is built upon the pydoc and SimpleXMLRPCServer 0010 modules. 0011 """ 0012 0013 import pydoc 0014 import inspect 0015 import types 0016 import re 0017 import sys 0018 0019 from SimpleXMLRPCServer import (SimpleXMLRPCServer, 0020 SimpleXMLRPCRequestHandler, 0021 CGIXMLRPCRequestHandler, 0022 resolve_dotted_attribute) 0023 0024 class ServerHTMLDoc(pydoc.HTMLDoc): 0025 """Class used to generate pydoc HTML document for a server""" 0026 0027 def markup(self, text, escape=None, funcs={}, classes={}, methods={}): 0028 """Mark up some plain text, given a context of symbols to look for. 0029 Each context dictionary maps object names to anchor names.""" 0030 escape = escape or self.escape 0031 results = [] 0032 here = 0 0033 0034 # XXX Note that this regular expressions does not allow for the 0035 # hyperlinking of arbitrary strings being used as method 0036 # names. Only methods with names consisting of word characters 0037 # and '.'s are hyperlinked. 0038 pattern = re.compile(r'\b((http|ftp)://\S+[\w/]|' 0039 r'RFC[- ]?(\d+)|' 0040 r'PEP[- ]?(\d+)|' 0041 r'(self\.)?((?:\w|\.)+))\b') 0042 while 1: 0043 match = pattern.search(text, here) 0044 if not match: break 0045 start, end = match.span() 0046 results.append(escape(text[here:start])) 0047 0048 all, scheme, rfc, pep, selfdot, name = match.groups() 0049 if scheme: 0050 url = escape(all).replace('"', '"') 0051 results.append('<a href="%s">%s</a>' % (url, url)) 0052 elif rfc: 0053 url = 'http://www.rfc-editor.org/rfc/rfc%d.txt' % int(rfc) 0054 results.append('<a href="%s">%s</a>' % (url, escape(all))) 0055 elif pep: 0056 url = 'http://www.python.org/peps/pep-%04d.html' % int(pep) 0057 results.append('<a href="%s">%s</a>' % (url, escape(all))) 0058 elif text[end:end+1] == '(': 0059 results.append(self.namelink(name, methods, funcs, classes)) 0060 elif selfdot: 0061 results.append('self.<strong>%s</strong>' % name) 0062 else: 0063 results.append(self.namelink(name, classes)) 0064 here = end 0065 results.append(escape(text[here:])) 0066 return ''.join(results) 0067 0068 def docroutine(self, object, name=None, mod=None, 0069 funcs={}, classes={}, methods={}, cl=None): 0070 """Produce HTML documentation for a function or method object.""" 0071 0072 anchor = (cl and cl.__name__ or '') + '-' + name 0073 note = '' 0074 0075 title = '<a name="%s"><strong>%s</strong></a>' % (anchor, name) 0076 0077 if inspect.ismethod(object): 0078 args, varargs, varkw, defaults = inspect.getargspec(object.im_func) 0079 # exclude the argument bound to the instance, it will be 0080 # confusing to the non-Python user 0081 argspec = inspect.formatargspec ( 0082 args[1:], 0083 varargs, 0084 varkw, 0085 defaults, 0086 formatvalue=self.formatvalue 0087 ) 0088 elif inspect.isfunction(object): 0089 args, varargs, varkw, defaults = inspect.getargspec(object) 0090 argspec = inspect.formatargspec( 0091 args, varargs, varkw, defaults, formatvalue=self.formatvalue) 0092 else: 0093 argspec = '(...)' 0094 0095 if isinstance(object, types.TupleType): 0096 argspec = object[0] or argspec 0097 docstring = object[1] or "" 0098 else: 0099 docstring = pydoc.getdoc(object) 0100 0101 decl = title + argspec + (note and self.grey( 0102 '<font face="helvetica, arial">%s</font>' % note)) 0103 0104 doc = self.markup( 0105 docstring, self.preformat, funcs, classes, methods) 0106 doc = doc and '<dd><tt>%s</tt></dd>' % doc 0107 return '<dl><dt>%s</dt>%s</dl>\n' % (decl, doc) 0108 0109 def docserver(self, server_name, package_documentation, methods): 0110 """Produce HTML documentation for an XML-RPC server.""" 0111 0112 fdict = {} 0113 for key, value in methods.items(): 0114 fdict[key] = '#-' + key 0115 fdict[value] = fdict[key] 0116 0117 head = '<big><big><strong>%s</strong></big></big>' % server_name 0118 result = self.heading(head, '#ffffff', '#7799ee') 0119 0120 doc = self.markup(package_documentation, self.preformat, fdict) 0121 doc = doc and '<tt>%s</tt>' % doc 0122 result = result + '<p>%s</p>\n' % doc 0123 0124 contents = [] 0125 method_items = methods.items() 0126 method_items.sort() 0127 for key, value in method_items: 0128 contents.append(self.docroutine(value, key, funcs=fdict)) 0129 result = result + self.bigsection( 0130 'Methods', '#ffffff', '#eeaa77', pydoc.join(contents)) 0131 0132 return result 0133 0134 class XMLRPCDocGenerator: 0135 """Generates documentation for an XML-RPC server. 0136 0137 This class is designed as mix-in and should not 0138 be constructed directly. 0139 """ 0140 0141 def __init__(self): 0142 # setup variables used for HTML documentation 0143 self.server_name = 'XML-RPC Server Documentation' 0144 self.server_documentation = \ 0145 "This server exports the following methods through the XML-RPC "\ 0146 "protocol." 0147 self.server_title = 'XML-RPC Server Documentation' 0148 0149 def set_server_title(self, server_title): 0150 """Set the HTML title of the generated server documentation""" 0151 0152 self.server_title = server_title 0153 0154 def set_server_name(self, server_name): 0155 """Set the name of the generated HTML server documentation""" 0156 0157 self.server_name = server_name 0158 0159 def set_server_documentation(self, server_documentation): 0160 """Set the documentation string for the entire server.""" 0161 0162 self.server_documentation = server_documentation 0163 0164 def generate_html_documentation(self): 0165 """generate_html_documentation() => html documentation for the server 0166 0167 Generates HTML documentation for the server using introspection for 0168 installed functions and instances that do not implement the 0169 _dispatch method. Alternatively, instances can choose to implement 0170 the _get_method_argstring(method_name) method to provide the 0171 argument string used in the documentation and the 0172 _methodHelp(method_name) method to provide the help text used 0173 in the documentation.""" 0174 0175 methods = {} 0176 0177 for method_name in self.system_listMethods(): 0178 if self.funcs.has_key(method_name): 0179 method = self.funcs[method_name] 0180 elif self.instance is not None: 0181 method_info = [None, None] # argspec, documentation 0182 if hasattr(self.instance, '_get_method_argstring'): 0183 method_info[0] = self.instance._get_method_argstring(method_name) 0184 if hasattr(self.instance, '_methodHelp'): 0185 method_info[1] = self.instance._methodHelp(method_name) 0186 0187 method_info = tuple(method_info) 0188 if method_info != (None, None): 0189 method = method_info 0190 elif not hasattr(self.instance, '_dispatch'): 0191 try: 0192 method = resolve_dotted_attribute( 0193 self.instance, 0194 method_name 0195 ) 0196 except AttributeError: 0197 method = method_info 0198 else: 0199 method = method_info 0200 else: 0201 assert 0, "Could not find method in self.functions and no "\ 0202 "instance installed" 0203 0204 methods[method_name] = method 0205 0206 documenter = ServerHTMLDoc() 0207 documentation = documenter.docserver( 0208 self.server_name, 0209 self.server_documentation, 0210 methods 0211 ) 0212 0213 return documenter.page(self.server_title, documentation) 0214 0215 class DocXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): 0216 """XML-RPC and documentation request handler class. 0217 0218 Handles all HTTP POST requests and attempts to decode them as 0219 XML-RPC requests. 0220 0221 Handles all HTTP GET requests and interprets them as requests 0222 for documentation. 0223 """ 0224 0225 def do_GET(self): 0226 """Handles the HTTP GET request. 0227 0228 Interpret all HTTP GET requests as requests for server 0229 documentation. 0230 """ 0231 0232 response = self.server.generate_html_documentation() 0233 self.send_response(200) 0234 self.send_header("Content-type", "text/html") 0235 self.send_header("Content-length", str(len(response))) 0236 self.end_headers() 0237 self.wfile.write(response) 0238 0239 # shut down the connection 0240 self.wfile.flush() 0241 self.connection.shutdown(1) 0242 0243 class DocXMLRPCServer( SimpleXMLRPCServer, 0244 XMLRPCDocGenerator): 0245 """XML-RPC and HTML documentation server. 0246 0247 Adds the ability to serve server documentation to the capabilities 0248 of SimpleXMLRPCServer. 0249 """ 0250 0251 def __init__(self, addr, requestHandler=DocXMLRPCRequestHandler, 0252 logRequests=1): 0253 SimpleXMLRPCServer.__init__(self, addr, requestHandler, logRequests) 0254 XMLRPCDocGenerator.__init__(self) 0255 0256 class DocCGIXMLRPCRequestHandler( CGIXMLRPCRequestHandler, 0257 XMLRPCDocGenerator): 0258 """Handler for XML-RPC data and documentation requests passed through 0259 CGI""" 0260 0261 def handle_get(self): 0262 """Handles the HTTP GET request. 0263 0264 Interpret all HTTP GET requests as requests for server 0265 documentation. 0266 """ 0267 0268 response = self.generate_html_documentation() 0269 0270 print 'Content-Type: text/html' 0271 print 'Content-Length: %d' % len(response) 0272 print 0273 sys.stdout.write(response) 0274 0275 def __init__(self): 0276 CGIXMLRPCRequestHandler.__init__(self) 0277 XMLRPCDocGenerator.__init__(self) 0278 0279 if __name__ == '__main__': 0280 def deg_to_rad(deg): 0281 """deg_to_rad(90) => 1.5707963267948966 0282 0283 Converts an angle in degrees to an angle in radians""" 0284 import math 0285 return deg * math.pi / 180 0286 0287 server = DocXMLRPCServer(("localhost", 8000)) 0288 0289 server.set_server_title("Math Server") 0290 server.set_server_name("Math XML-RPC Server") 0291 server.set_server_documentation("""This server supports various mathematical functions. 0292 0293 You can use it from Python as follows: 0294 0295 >>> from xmlrpclib import ServerProxy 0296 >>> s = ServerProxy("http://localhost:8000") 0297 >>> s.deg_to_rad(90.0) 0298 1.5707963267948966""") 0299 0300 server.register_function(deg_to_rad) 0301 server.register_introspection_functions() 0302 0303 server.serve_forever() 0304
Generated by PyXR 0.9.4