"""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
This module is built upon the pydoc and SimpleXMLRPCServer
from SimpleXMLRPCServer
import (SimpleXMLRPCServer
,
SimpleXMLRPCRequestHandler
,
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
# 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
'(self\.)?((?:\w|\.)+))\b')
match
= pattern
.search(text
, here
)
start
, end
= match
.span()
results
.append(escape(text
[here
:start
]))
all
, scheme
, rfc
, pep
, selfdot
, name
= match
.groups()
url
= escape(all
).replace('"', '"')
results
.append('<a href="%s">%s</a>' % (url
, url
))
url
= 'http://www.rfc-editor.org/rfc/rfc%d.txt' % int(rfc
)
results
.append('<a href="%s">%s</a>' % (url
, escape(all
)))
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
))
results
.append('self.<strong>%s</strong>' % name
)
results
.append(self
.namelink(name
, classes
))
results
.append(escape(text
[here
:]))
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
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 (
formatvalue
=self
.formatvalue
elif inspect
.isfunction(object):
args
, varargs
, varkw
, defaults
= inspect
.getargspec(object)
argspec
= inspect
.formatargspec(
args
, varargs
, varkw
, defaults
, formatvalue
=self
.formatvalue
)
if isinstance(object, types
.TupleType
):
argspec
= object[0] or argspec
docstring
= object[1] or ""
docstring
= pydoc
.getdoc(object)
decl
= title
+ argspec
+ (note
and self
.grey(
'<font face="helvetica, arial">%s</font>' % note
))
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."""
for key
, value
in methods
.items():
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
method_items
= methods
.items()
for key
, value
in method_items
:
contents
.append(self
.docroutine(value
, key
, funcs
=fdict
))
result
= result
+ self
.bigsection(
'Methods', '#ffffff', '#eeaa77', pydoc
.join(contents
))
class XMLRPCDocGenerator
:
"""Generates documentation for an XML-RPC server.
This class is designed as mix-in and should not
# 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 "\
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
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):
elif not hasattr(self
.instance
, '_dispatch'):
method
= resolve_dotted_attribute(
assert 0, "Could not find method in self.functions and no "\
methods
[method_name
] = method
documenter
= ServerHTMLDoc()
documentation
= documenter
.docserver(
self
.server_documentation
,
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
Handles all HTTP GET requests and interprets them as requests
"""Handles the HTTP GET request.
Interpret all HTTP GET requests as requests for server
response
= self
.server
.generate_html_documentation()
self
.send_header("Content-type", "text/html")
self
.send_header("Content-length", str(len(response
)))
self
.wfile
.write(response
)
# shut down the connection
self
.connection
.shutdown(1)
class DocXMLRPCServer( SimpleXMLRPCServer
,
"""XML-RPC and HTML documentation server.
Adds the ability to serve server documentation to the capabilities
def __init__(self
, addr
, requestHandler
=DocXMLRPCRequestHandler
,
SimpleXMLRPCServer
.__init
__(self
, addr
, requestHandler
, logRequests
)
XMLRPCDocGenerator
.__init
__(self
)
class DocCGIXMLRPCRequestHandler( CGIXMLRPCRequestHandler
,
"""Handler for XML-RPC data and documentation requests passed through
"""Handles the HTTP GET request.
Interpret all HTTP GET requests as requests for server
response
= self
.generate_html_documentation()
print 'Content-Type: text/html'
print 'Content-Length: %d' % len(response
)
sys
.stdout
.write(response
)
CGIXMLRPCRequestHandler
.__init
__(self
)
XMLRPCDocGenerator
.__init
__(self
)
if __name__
== '__main__':
"""deg_to_rad(90) => 1.5707963267948966
Converts an angle in degrees to an angle in radians"""
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")
server
.register_function(deg_to_rad
)
server
.register_introspection_functions()