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 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/]|'
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/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
))
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
, 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>' % (
75 self
.escape(anchor
), self
.escape(name
))
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 (
86 formatvalue
=self
.formatvalue
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
)
95 if isinstance(object, tuple):
96 argspec
= object[0] or argspec
97 docstring
= object[1] or ""
99 docstring
= pydoc
.getdoc(object)
101 decl
= title
+ argspec
+ (note
and self
.grey(
102 '<font face="helvetica, arial">%s</font>' % note
))
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
)
109 def docserver(self
, server_name
, package_documentation
, methods
):
110 """Produce HTML documentation for an XML-RPC server."""
113 for key
, value
in methods
.items():
114 fdict
[key
] = '#-' + key
115 fdict
[value
] = fdict
[key
]
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')
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
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
))
134 class XMLRPCDocGenerator
:
135 """Generates documentation for an XML-RPC server.
137 This class is designed as mix-in and should not
138 be constructed directly.
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 "\
147 self
.server_title
= 'XML-RPC Server Documentation'
149 def set_server_title(self
, server_title
):
150 """Set the HTML title of the generated server documentation"""
152 self
.server_title
= server_title
154 def set_server_name(self
, server_name
):
155 """Set the name of the generated HTML server documentation"""
157 self
.server_name
= server_name
159 def set_server_documentation(self
, server_documentation
):
160 """Set the documentation string for the entire server."""
162 self
.server_documentation
= server_documentation
164 def generate_html_documentation(self
):
165 """generate_html_documentation() => html documentation for the server
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."""
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
)
187 method_info
= tuple(method_info
)
188 if method_info
!= (None, None):
190 elif not hasattr(self
.instance
, '_dispatch'):
192 method
= resolve_dotted_attribute(
196 except AttributeError:
201 assert 0, "Could not find method in self.functions and no "\
204 methods
[method_name
] = method
206 documenter
= ServerHTMLDoc()
207 documentation
= documenter
.docserver(
209 self
.server_documentation
,
213 return documenter
.page(self
.server_title
, documentation
)
215 class DocXMLRPCRequestHandler(SimpleXMLRPCRequestHandler
):
216 """XML-RPC and documentation request handler class.
218 Handles all HTTP POST requests and attempts to decode them as
221 Handles all HTTP GET requests and interprets them as requests
226 """Handles the HTTP GET request.
228 Interpret all HTTP GET requests as requests for server
231 # Check that the path is legal
232 if not self
.is_rpc_path_valid():
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
)))
241 self
.wfile
.write(response
)
243 class DocXMLRPCServer( SimpleXMLRPCServer
,
245 """XML-RPC and HTML documentation server.
247 Adds the ability to serve server documentation to the capabilities
248 of SimpleXMLRPCServer.
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
)
258 class DocCGIXMLRPCRequestHandler( CGIXMLRPCRequestHandler
,
260 """Handler for XML-RPC data and documentation requests passed through
263 def handle_get(self
):
264 """Handles the HTTP GET request.
266 Interpret all HTTP GET requests as requests for server
270 response
= self
.generate_html_documentation()
272 print 'Content-Type: text/html'
273 print 'Content-Length: %d' % len(response
)
275 sys
.stdout
.write(response
)
278 CGIXMLRPCRequestHandler
.__init
__(self
)
279 XMLRPCDocGenerator
.__init
__(self
)