1 """Self documenting XML-RPC Server.
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
9 This module is built upon the pydoc and SimpleXMLRPCServer
18 from SimpleXMLRPCServer
import (SimpleXMLRPCServer
,
19 SimpleXMLRPCRequestHandler
,
20 CGIXMLRPCRequestHandler
,
21 resolve_dotted_attribute
)
23 class ServerHTMLDoc(pydoc
.HTMLDoc
):
24 """Class used to generate pydoc HTML document for a server"""
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
33 # XXX Note that this regular expressions 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/]|'
40 r
'(self\.)?((?:\w|\.)+))\b')
42 match
= pattern
.search(text
, here
)
44 start
, end
= match
.span()
45 results
.append(escape(text
[here
:start
]))
47 all
, scheme
, rfc
, pep
, selfdot
, name
= match
.groups()
49 url
= escape(all
).replace('"', '"')
50 results
.append('<a href="%s">%s</a>' % (url
, url
))
52 url
= 'http://www.rfc-editor.org/rfc/rfc%d.txt' % int(rfc
)
53 results
.append('<a href="%s">%s</a>' % (url
, escape(all
)))
55 url
= 'http://www.python.org/peps/pep-%04d.html' % 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
))
60 results
.append('self.<strong>%s</strong>' % name
)
62 results
.append(self
.namelink(name
, classes
))
64 results
.append(escape(text
[here
:]))
65 return ''.join(results
)
67 def docroutine(self
, object, name
=None, mod
=None,
68 funcs
={}, classes
={}, methods
={}, cl
=None):
69 """Produce HTML documentation for a function or method object."""
71 anchor
= (cl
and cl
.__name
__ or '') + '-' + name
74 title
= '<a name="%s"><strong>%s</strong></a>' % (anchor
, name
)
76 if inspect
.ismethod(object):
77 args
, varargs
, varkw
, defaults
= inspect
.getargspec(object.im_func
)
78 # exclude the argument bound to the instance, it will be
79 # confusing to the non-Python user
80 argspec
= inspect
.formatargspec (
85 formatvalue
=self
.formatvalue
87 elif inspect
.isfunction(object):
88 args
, varargs
, varkw
, defaults
= inspect
.getargspec(object)
89 argspec
= inspect
.formatargspec(
90 args
, varargs
, varkw
, defaults
, formatvalue
=self
.formatvalue
)
94 if isinstance(object, tuple):
95 argspec
= object[0] or argspec
96 docstring
= object[1] or ""
98 docstring
= pydoc
.getdoc(object)
100 decl
= title
+ argspec
+ (note
and self
.grey(
101 '<font face="helvetica, arial">%s</font>' % note
))
104 docstring
, self
.preformat
, funcs
, classes
, methods
)
105 doc
= doc
and '<dd><tt>%s</tt></dd>' % doc
106 return '<dl><dt>%s</dt>%s</dl>\n' % (decl
, doc
)
108 def docserver(self
, server_name
, package_documentation
, methods
):
109 """Produce HTML documentation for an XML-RPC server."""
112 for key
, value
in methods
.items():
113 fdict
[key
] = '#-' + key
114 fdict
[value
] = fdict
[key
]
116 head
= '<big><big><strong>%s</strong></big></big>' % server_name
117 result
= self
.heading(head
, '#ffffff', '#7799ee')
119 doc
= self
.markup(package_documentation
, self
.preformat
, fdict
)
120 doc
= doc
and '<tt>%s</tt>' % doc
121 result
= result
+ '<p>%s</p>\n' % doc
124 method_items
= methods
.items()
126 for key
, value
in method_items
:
127 contents
.append(self
.docroutine(value
, key
, funcs
=fdict
))
128 result
= result
+ self
.bigsection(
129 'Methods', '#ffffff', '#eeaa77', pydoc
.join(contents
))
133 class XMLRPCDocGenerator
:
134 """Generates documentation for an XML-RPC server.
136 This class is designed as mix-in and should not
137 be constructed directly.
141 # setup variables used for HTML documentation
142 self
.server_name
= 'XML-RPC Server Documentation'
143 self
.server_documentation
= \
144 "This server exports the following methods through the XML-RPC "\
146 self
.server_title
= 'XML-RPC Server Documentation'
148 def set_server_title(self
, server_title
):
149 """Set the HTML title of the generated server documentation"""
151 self
.server_title
= server_title
153 def set_server_name(self
, server_name
):
154 """Set the name of the generated HTML server documentation"""
156 self
.server_name
= server_name
158 def set_server_documentation(self
, server_documentation
):
159 """Set the documentation string for the entire server."""
161 self
.server_documentation
= server_documentation
163 def generate_html_documentation(self
):
164 """generate_html_documentation() => html documentation for the server
166 Generates HTML documentation for the server using introspection for
167 installed functions and instances that do not implement the
168 _dispatch method. Alternatively, instances can choose to implement
169 the _get_method_argstring(method_name) method to provide the
170 argument string used in the documentation and the
171 _methodHelp(method_name) method to provide the help text used
172 in the documentation."""
176 for method_name
in self
.system_listMethods():
177 if self
.funcs
.has_key(method_name
):
178 method
= self
.funcs
[method_name
]
179 elif self
.instance
is not None:
180 method_info
= [None, None] # argspec, documentation
181 if hasattr(self
.instance
, '_get_method_argstring'):
182 method_info
[0] = self
.instance
._get
_method
_argstring
(method_name
)
183 if hasattr(self
.instance
, '_methodHelp'):
184 method_info
[1] = self
.instance
._methodHelp
(method_name
)
186 method_info
= tuple(method_info
)
187 if method_info
!= (None, None):
189 elif not hasattr(self
.instance
, '_dispatch'):
191 method
= resolve_dotted_attribute(
195 except AttributeError:
200 assert 0, "Could not find method in self.functions and no "\
203 methods
[method_name
] = method
205 documenter
= ServerHTMLDoc()
206 documentation
= documenter
.docserver(
208 self
.server_documentation
,
212 return documenter
.page(self
.server_title
, documentation
)
214 class DocXMLRPCRequestHandler(SimpleXMLRPCRequestHandler
):
215 """XML-RPC and documentation request handler class.
217 Handles all HTTP POST requests and attempts to decode them as
220 Handles all HTTP GET requests and interprets them as requests
225 """Handles the HTTP GET request.
227 Interpret all HTTP GET requests as requests for server
230 # Check that the path is legal
231 if not self
.is_rpc_path_valid():
235 response
= self
.server
.generate_html_documentation()
236 self
.send_response(200)
237 self
.send_header("Content-type", "text/html")
238 self
.send_header("Content-length", str(len(response
)))
240 self
.wfile
.write(response
)
242 # shut down the connection
244 self
.connection
.shutdown(1)
246 class DocXMLRPCServer( SimpleXMLRPCServer
,
248 """XML-RPC and HTML documentation server.
250 Adds the ability to serve server documentation to the capabilities
251 of SimpleXMLRPCServer.
254 def __init__(self
, addr
, requestHandler
=DocXMLRPCRequestHandler
,
256 SimpleXMLRPCServer
.__init
__(self
, addr
, requestHandler
, logRequests
)
257 XMLRPCDocGenerator
.__init
__(self
)
259 class DocCGIXMLRPCRequestHandler( CGIXMLRPCRequestHandler
,
261 """Handler for XML-RPC data and documentation requests passed through
264 def handle_get(self
):
265 """Handles the HTTP GET request.
267 Interpret all HTTP GET requests as requests for server
271 response
= self
.generate_html_documentation()
273 print 'Content-Type: text/html'
274 print 'Content-Length: %d' % len(response
)
276 sys
.stdout
.write(response
)
279 CGIXMLRPCRequestHandler
.__init
__(self
)
280 XMLRPCDocGenerator
.__init
__(self
)
282 if __name__
== '__main__':
284 """deg_to_rad(90) => 1.5707963267948966
286 Converts an angle in degrees to an angle in radians"""
288 return deg
* math
.pi
/ 180
290 server
= DocXMLRPCServer(("localhost", 8000))
292 server
.set_server_title("Math Server")
293 server
.set_server_name("Math XML-RPC Server")
294 server
.set_server_documentation("""This server supports various mathematical functions.
296 You can use it from Python as follows:
298 >>> from xmlrpclib import ServerProxy
299 >>> s = ServerProxy("http://localhost:8000")
300 >>> s.deg_to_rad(90.0)
301 1.5707963267948966""")
303 server
.register_function(deg_to_rad
)
304 server
.register_introspection_functions()
306 server
.serve_forever()