diff options
author | Georg Brandl <georg@python.org> | 2008-05-26 11:14:17 (GMT) |
---|---|---|
committer | Georg Brandl <georg@python.org> | 2008-05-26 11:14:17 (GMT) |
commit | 38eceaaf0c207709cfbb44c7a1b9715699622848 (patch) | |
tree | 874f496e0fbaf8e6f3c86e86cd0c95fe51845dde /Lib | |
parent | 7f986acb0142574d30e5c5460df02fdbb00760e9 (diff) | |
download | cpython-38eceaaf0c207709cfbb44c7a1b9715699622848.zip cpython-38eceaaf0c207709cfbb44c7a1b9715699622848.tar.gz cpython-38eceaaf0c207709cfbb44c7a1b9715699622848.tar.bz2 |
Create xmlrpc package. Issue #2886.
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/DocXMLRPCServer.py | 283 | ||||
-rw-r--r-- | Lib/test/test_anydbm.py | 147 | ||||
-rw-r--r-- | Lib/test/test_docxmlrpc.py | 3 | ||||
-rw-r--r-- | Lib/test/test_xmlrpc.py | 34 | ||||
-rw-r--r-- | Lib/test/test_xmlrpc_net.py | 2 | ||||
-rw-r--r-- | Lib/xmlrpc/__init__.py | 1 | ||||
-rw-r--r-- | Lib/xmlrpc/client.py (renamed from Lib/xmlrpclib.py) | 43 | ||||
-rw-r--r-- | Lib/xmlrpc/server.py (renamed from Lib/SimpleXMLRPCServer.py) | 295 |
8 files changed, 313 insertions, 495 deletions
diff --git a/Lib/DocXMLRPCServer.py b/Lib/DocXMLRPCServer.py deleted file mode 100644 index 246fd74..0000000 --- a/Lib/DocXMLRPCServer.py +++ /dev/null @@ -1,283 +0,0 @@ -"""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 -server. - -This module is built upon the pydoc and SimpleXMLRPCServer -modules. -""" - -import pydoc -import inspect -import re -import sys - -from SimpleXMLRPCServer import (SimpleXMLRPCServer, - SimpleXMLRPCRequestHandler, - CGIXMLRPCRequestHandler, - 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 - results = [] - here = 0 - - # XXX Note that this regular expression 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'RFC[- ]?(\d+)|' - r'PEP[- ]?(\d+)|' - r'(self\.)?((?:\w|\.)+))\b') - while 1: - match = pattern.search(text, here) - if not match: break - start, end = match.span() - results.append(escape(text[here:start])) - - all, scheme, rfc, pep, selfdot, name = match.groups() - if scheme: - url = escape(all).replace('"', '"') - results.append('<a href="%s">%s</a>' % (url, url)) - elif rfc: - url = 'http://www.rfc-editor.org/rfc/rfc%d.txt' % int(rfc) - results.append('<a href="%s">%s</a>' % (url, escape(all))) - elif pep: - url = 'http://www.python.org/dev/peps/pep-%04d/' % 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)) - elif selfdot: - results.append('self.<strong>%s</strong>' % name) - else: - results.append(self.namelink(name, classes)) - here = end - results.append(escape(text[here:])) - return ''.join(results) - - def docroutine(self, object, name, mod=None, - funcs={}, classes={}, methods={}, cl=None): - """Produce HTML documentation for a function or method object.""" - - anchor = (cl and cl.__name__ or '') + '-' + name - note = '' - - title = '<a name="%s"><strong>%s</strong></a>' % ( - self.escape(anchor), self.escape(name)) - - if inspect.ismethod(object): - args, varargs, varkw, defaults = inspect.getargspec(object) - # exclude the argument bound to the instance, it will be - # confusing to the non-Python user - argspec = inspect.formatargspec ( - args[1:], - varargs, - varkw, - defaults, - formatvalue=self.formatvalue - ) - elif inspect.isfunction(object): - args, varargs, varkw, defaults = inspect.getargspec(object) - argspec = inspect.formatargspec( - args, varargs, varkw, defaults, formatvalue=self.formatvalue) - else: - argspec = '(...)' - - if isinstance(object, tuple): - argspec = object[0] or argspec - docstring = object[1] or "" - else: - docstring = pydoc.getdoc(object) - - decl = title + argspec + (note and self.grey( - '<font face="helvetica, arial">%s</font>' % note)) - - doc = self.markup( - 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.""" - - fdict = {} - for key, value in methods.items(): - fdict[key] = '#-' + key - fdict[value] = fdict[key] - - server_name = self.escape(server_name) - 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 - - contents = [] - method_items = sorted(methods.items()) - for key, value in method_items: - contents.append(self.docroutine(value, key, funcs=fdict)) - result = result + self.bigsection( - 'Methods', '#ffffff', '#eeaa77', ''.join(contents)) - - return result - -class XMLRPCDocGenerator: - """Generates documentation for an XML-RPC server. - - This class is designed as mix-in and should not - be constructed directly. - """ - - def __init__(self): - # 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 "\ - "protocol." - 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 - in the documentation.""" - - methods = {} - - for method_name in self.system_listMethods(): - if method_name in self.funcs: - 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): - method = method_info - elif not hasattr(self.instance, '_dispatch'): - try: - method = resolve_dotted_attribute( - self.instance, - method_name - ) - except AttributeError: - method = method_info - else: - method = method_info - else: - assert 0, "Could not find method in self.functions and no "\ - "instance installed" - - methods[method_name] = method - - documenter = ServerHTMLDoc() - documentation = documenter.docserver( - self.server_name, - self.server_documentation, - methods - ) - - 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 - XML-RPC requests. - - Handles all HTTP GET requests and interprets them as requests - for documentation. - """ - - def do_GET(self): - """Handles the HTTP GET request. - - Interpret all HTTP GET requests as requests for server - documentation. - """ - # Check that the path is legal - if not self.is_rpc_path_valid(): - self.report_404() - return - - response = self.server.generate_html_documentation() - self.send_response(200) - self.send_header("Content-type", "text/html") - self.send_header("Content-length", str(len(response))) - self.end_headers() - self.wfile.write(response.encode()) - - # shut down the connection - self.wfile.flush() - self.connection.shutdown(1) - -class DocXMLRPCServer( SimpleXMLRPCServer, - XMLRPCDocGenerator): - """XML-RPC and HTML documentation server. - - Adds the ability to serve server documentation to the capabilities - of SimpleXMLRPCServer. - """ - - def __init__(self, addr, requestHandler=DocXMLRPCRequestHandler, - logRequests=1, allow_none=False, encoding=None, - bind_and_activate=True): - SimpleXMLRPCServer.__init__(self, addr, requestHandler, logRequests, - allow_none, encoding, bind_and_activate) - XMLRPCDocGenerator.__init__(self) - -class DocCGIXMLRPCRequestHandler( CGIXMLRPCRequestHandler, - XMLRPCDocGenerator): - """Handler for XML-RPC data and documentation requests passed through - CGI""" - - def handle_get(self): - """Handles the HTTP GET request. - - Interpret all HTTP GET requests as requests for server - documentation. - """ - - response = self.generate_html_documentation() - - print('Content-Type: text/html') - print('Content-Length: %d' % len(response)) - print() - sys.stdout.write(response) - - def __init__(self): - CGIXMLRPCRequestHandler.__init__(self) - XMLRPCDocGenerator.__init__(self) diff --git a/Lib/test/test_anydbm.py b/Lib/test/test_anydbm.py deleted file mode 100644 index aab1388..0000000 --- a/Lib/test/test_anydbm.py +++ /dev/null @@ -1,147 +0,0 @@ -#! /usr/bin/env python -"""Test script for the dbm.open function based on testdumbdbm.py""" - -import os -import unittest -import dbm -import glob -import test.support - -_fname = test.support.TESTFN - -# -# Iterates over every database module supported by dbm currently available, -# setting dbm to use each in turn, and yielding that module -# -def dbm_iterator(): - old_default = dbm._defaultmod - for module in dbm._modules.values(): - dbm._defaultmod = module - yield module - dbm._defaultmod = old_default - -# -# Clean up all scratch databases we might have created during testing -# -def delete_files(): - # we don't know the precise name the underlying database uses - # so we use glob to locate all names - for f in glob.glob(_fname + "*"): - test.support.unlink(f) - - -class AnyDBMTestCase(unittest.TestCase): - _dict = {'0': b'', - 'a': b'Python:', - 'b': b'Programming', - 'c': b'the', - 'd': b'way', - 'f': b'Guido', - 'g': b'intended', - } - - def __init__(self, *args): - unittest.TestCase.__init__(self, *args) - - def test_anydbm_creation(self): - f = dbm.open(_fname, 'c') - self.assertEqual(list(f.keys()), []) - for key in self._dict: - f[key.encode("ascii")] = self._dict[key] - self.read_helper(f) - f.close() - - def test_anydbm_modification(self): - self.init_db() - f = dbm.open(_fname, 'c') - self._dict['g'] = f[b'g'] = b"indented" - self.read_helper(f) - f.close() - - def test_anydbm_read(self): - self.init_db() - f = dbm.open(_fname, 'r') - self.read_helper(f) - f.close() - - def test_anydbm_keys(self): - self.init_db() - f = dbm.open(_fname, 'r') - keys = self.keys_helper(f) - f.close() - - def test_anydbm_access(self): - self.init_db() - f = dbm.open(_fname, 'r') - key = "a".encode("ascii") - assert(key in f) - assert(f[key] == b"Python:") - f.close() - - def read_helper(self, f): - keys = self.keys_helper(f) - for key in self._dict: - self.assertEqual(self._dict[key], f[key.encode("ascii")]) - - def init_db(self): - f = dbm.open(_fname, 'n') - for k in self._dict: - f[k.encode("ascii")] = self._dict[k] - f.close() - - def keys_helper(self, f): - keys = sorted(k.decode("ascii") for k in f.keys()) - dkeys = sorted(self._dict.keys()) - self.assertEqual(keys, dkeys) - return keys - - def tearDown(self): - delete_files() - - def setUp(self): - delete_files() - - -class WhichDBTestCase(unittest.TestCase): - # Actual test methods are added to namespace after class definition. - def __init__(self, *args): - unittest.TestCase.__init__(self, *args) - - def test_whichdb(self): - for module in dbm_iterator(): - # Check whether whichdb correctly guesses module name - # for databases opened with "module" module. - # Try with empty files first - name = module.__name__ - if name == 'dbm.dumb': - continue # whichdb can't support dbm.dumb - test.support.unlink(_fname) - f = module.open(_fname, 'c') - f.close() - self.assertEqual(name, dbm.whichdb(_fname)) - # Now add a key - f = module.open(_fname, 'w') - f[b"1"] = b"1" - # and test that we can find it - self.assertTrue(b"1" in f) - # and read it - self.assertTrue(f[b"1"] == b"1") - f.close() - self.assertEqual(name, dbm.whichdb(_fname)) - - def tearDown(self): - delete_files() - - def setUp(self): - delete_files() - - -def test_main(): - try: - for module in dbm_iterator(): - test.support.run_unittest(AnyDBMTestCase, WhichDBTestCase) - finally: - delete_files() - -if __name__ == "__main__": - test_main() diff --git a/Lib/test/test_docxmlrpc.py b/Lib/test/test_docxmlrpc.py index 2af2071..9cb9ffb 100644 --- a/Lib/test/test_docxmlrpc.py +++ b/Lib/test/test_docxmlrpc.py @@ -1,10 +1,9 @@ -from DocXMLRPCServer import DocXMLRPCServer +from xmlrpc.server import DocXMLRPCServer import httplib from test import support import threading import time import unittest -import xmlrpclib PORT = None diff --git a/Lib/test/test_xmlrpc.py b/Lib/test/test_xmlrpc.py index cad2b9d..25a9c9d 100644 --- a/Lib/test/test_xmlrpc.py +++ b/Lib/test/test_xmlrpc.py @@ -3,8 +3,8 @@ import datetime import sys import time import unittest -import xmlrpclib -import SimpleXMLRPCServer +import xmlrpc.client as xmlrpclib +import xmlrpc.server import threading import mimetools import httplib @@ -160,9 +160,9 @@ class FaultTestCase(unittest.TestCase): # this will raise AttirebuteError because code don't want us to use # private methods self.assertRaises(AttributeError, - SimpleXMLRPCServer.resolve_dotted_attribute, str, '__add') + xmlrpc.server.resolve_dotted_attribute, str, '__add') - self.assert_(SimpleXMLRPCServer.resolve_dotted_attribute(str, 'title')) + self.assert_(xmlrpc.server.resolve_dotted_attribute(str, 'title')) class DateTimeTestCase(unittest.TestCase): def test_default(self): @@ -249,7 +249,7 @@ def http_server(evt, numrequests): '''This is my function''' return True - class MyXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer): + class MyXMLRPCServer(xmlrpc.server.SimpleXMLRPCServer): def get_request(self): # Ensure the socket is always non-blocking. On Linux, socket # attributes are not inherited like they are on *BSD and Windows. @@ -306,7 +306,7 @@ def is_unavailable_exception(e): class SimpleServerTestCase(unittest.TestCase): def setUp(self): # enable traceback reporting - SimpleXMLRPCServer.SimpleXMLRPCServer._send_traceback_header = True + xmlrpc.server.SimpleXMLRPCServer._send_traceback_header = True self.evt = threading.Event() # start server thread to handle requests @@ -326,7 +326,7 @@ class SimpleServerTestCase(unittest.TestCase): raise RuntimeError("timeout reached, test has failed") # disable traceback reporting - SimpleXMLRPCServer.SimpleXMLRPCServer._send_traceback_header = False + xmlrpc.server.SimpleXMLRPCServer._send_traceback_header = False def test_simple1(self): try: @@ -443,9 +443,9 @@ class SimpleServerTestCase(unittest.TestCase): def test_dotted_attribute(self): # Raises an AttributeError because private methods are not allowed. self.assertRaises(AttributeError, - SimpleXMLRPCServer.resolve_dotted_attribute, str, '__add') + xmlrpc.server.resolve_dotted_attribute, str, '__add') - self.assert_(SimpleXMLRPCServer.resolve_dotted_attribute(str, 'title')) + self.assert_(xmlrpc.server.resolve_dotted_attribute(str, 'title')) # Get the test to run faster by sending a request with test_simple1. # This avoids waiting for the socket timeout. self.test_simple1() @@ -475,17 +475,17 @@ class FailingServerTestCase(unittest.TestCase): # wait on the server thread to terminate self.evt.wait() # reset flag - SimpleXMLRPCServer.SimpleXMLRPCServer._send_traceback_header = False + xmlrpc.server.SimpleXMLRPCServer._send_traceback_header = False # reset message class - SimpleXMLRPCServer.SimpleXMLRPCRequestHandler.MessageClass = mimetools.Message + xmlrpc.server.SimpleXMLRPCRequestHandler.MessageClass = mimetools.Message def test_basic(self): # check that flag is false by default - flagval = SimpleXMLRPCServer.SimpleXMLRPCServer._send_traceback_header + flagval = xmlrpc.server.SimpleXMLRPCServer._send_traceback_header self.assertEqual(flagval, False) # enable traceback reporting - SimpleXMLRPCServer.SimpleXMLRPCServer._send_traceback_header = True + xmlrpc.server.SimpleXMLRPCServer._send_traceback_header = True # test a call that shouldn't fail just as a smoke test try: @@ -499,7 +499,7 @@ class FailingServerTestCase(unittest.TestCase): def test_fail_no_info(self): # use the broken message class - SimpleXMLRPCServer.SimpleXMLRPCRequestHandler.MessageClass = FailingMessageClass + xmlrpc.server.SimpleXMLRPCRequestHandler.MessageClass = FailingMessageClass try: p = xmlrpclib.ServerProxy('http://localhost:%d' % PORT) @@ -515,11 +515,11 @@ class FailingServerTestCase(unittest.TestCase): def test_fail_with_info(self): # use the broken message class - SimpleXMLRPCServer.SimpleXMLRPCRequestHandler.MessageClass = FailingMessageClass + xmlrpc.server.SimpleXMLRPCRequestHandler.MessageClass = FailingMessageClass # Check that errors in the server send back exception/traceback # info when flag is set - SimpleXMLRPCServer.SimpleXMLRPCServer._send_traceback_header = True + xmlrpc.server.SimpleXMLRPCServer._send_traceback_header = True try: p = xmlrpclib.ServerProxy('http://localhost:%d' % PORT) @@ -536,7 +536,7 @@ class FailingServerTestCase(unittest.TestCase): class CGIHandlerTestCase(unittest.TestCase): def setUp(self): - self.cgi = SimpleXMLRPCServer.CGIXMLRPCRequestHandler() + self.cgi = xmlrpc.server.CGIXMLRPCRequestHandler() def tearDown(self): self.cgi = None diff --git a/Lib/test/test_xmlrpc_net.py b/Lib/test/test_xmlrpc_net.py index 1f8dd5d..2525254 100644 --- a/Lib/test/test_xmlrpc_net.py +++ b/Lib/test/test_xmlrpc_net.py @@ -6,7 +6,7 @@ import sys import unittest from test import support -import xmlrpclib +import xmlrpclib.client as xmlrpclib class CurrentTimeTest(unittest.TestCase): diff --git a/Lib/xmlrpc/__init__.py b/Lib/xmlrpc/__init__.py new file mode 100644 index 0000000..196d378 --- /dev/null +++ b/Lib/xmlrpc/__init__.py @@ -0,0 +1 @@ +# This directory is a Python package. diff --git a/Lib/xmlrpclib.py b/Lib/xmlrpc/client.py index 522df4d..138d86d 100644 --- a/Lib/xmlrpclib.py +++ b/Lib/xmlrpc/client.py @@ -108,7 +108,6 @@ Exported classes: ServerProxy Represents a logical connection to an XML-RPC server MultiCall Executor of boxcared xmlrpc requests - Boolean boolean wrapper to generate a "boolean" XML-RPC value DateTime dateTime wrapper for an ISO 8601 string or time tuple or localtime integer value to generate a "dateTime.iso8601" XML-RPC value @@ -127,7 +126,6 @@ Exported constants: Exported functions: - boolean Convert any Python value to an XML-RPC boolean getparser Create instance of the fastest available parser & attach to an unmarshalling object dumps Convert an argument tuple or a Fault instance to an XML-RPC @@ -147,11 +145,6 @@ try: except ImportError: datetime = None -try: - _bool_is_builtin = False.__class__.__name__ == "bool" -except NameError: - _bool_is_builtin = 0 - def _decode(data, encoding, is8bit=re.compile("[\x80-\xff]").search): # decode non-ascii string (if possible) if encoding and is8bit(data): @@ -265,12 +258,7 @@ class Fault(Error): # Special values ## -# Wrapper for XML-RPC boolean values. Use the xmlrpclib.True and -# xmlrpclib.False constants, or the xmlrpclib.boolean() function, to -# generate boolean XML-RPC values. -# -# @param value A boolean value. Any true value is interpreted as True, -# all other values are interpreted as False. +# Backwards compatibility boolean = Boolean = bool @@ -451,26 +439,10 @@ def _binary(data): return value WRAPPERS = (DateTime, Binary) -if not _bool_is_builtin: - WRAPPERS = WRAPPERS + (Boolean,) # -------------------------------------------------------------------- # XML parsers -try: - # optional xmlrpclib accelerator - import _xmlrpclib - FastParser = _xmlrpclib.Parser - FastUnmarshaller = _xmlrpclib.Unmarshaller -except (AttributeError, ImportError): - FastParser = FastUnmarshaller = None - -try: - import _xmlrpclib - FastMarshaller = _xmlrpclib.Marshaller -except (AttributeError, ImportError): - FastMarshaller = None - # # the SGMLOP parser is about 15x faster than Python's builtin # XML parser. SGMLOP sources can be downloaded from: @@ -640,12 +612,11 @@ class Marshaller: write("</int></value>\n") #dispatch[int] = dump_int - if _bool_is_builtin: - def dump_bool(self, value, write): - write("<value><boolean>") - write(value and "1" or "0") - write("</boolean></value>\n") - dispatch[bool] = dump_bool + def dump_bool(self, value, write): + write("<value><boolean>") + write(value and "1" or "0") + write("</boolean></value>\n") + dispatch[bool] = dump_bool def dump_long(self, value, write): if value > MAXINT or value < MININT: @@ -968,6 +939,8 @@ class MultiCall: # -------------------------------------------------------------------- # convenience functions +FastMarshaller = FastParser = FastUnmarshaller = None + ## # Create a parser object, and connect it to an unmarshalling instance. # This function picks the fastest available XML parser. diff --git a/Lib/SimpleXMLRPCServer.py b/Lib/xmlrpc/server.py index a985153..9668c8c 100644 --- a/Lib/SimpleXMLRPCServer.py +++ b/Lib/xmlrpc/server.py @@ -1,4 +1,4 @@ -"""Simple XML-RPC Server. +"""XML-RPC Servers. This module can be used to create simple XML-RPC servers by creating a server and either installing functions, a @@ -8,6 +8,12 @@ class. It can also be used to handle XML-RPC requests in a CGI environment using CGIXMLRPCRequestHandler. +The Doc* classes 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 +server. + A list of possible usage patterns follows: 1. Install functions: @@ -98,12 +104,14 @@ server.handle_request() # Written by Brian Quinlan (brian@sweetapp.com). # Based on code written by Fredrik Lundh. -import xmlrpclib -from xmlrpclib import Fault +from xmlrpc.client import Fault, dumps, loads import socketserver import BaseHTTPServer import sys import os +import re +import pydoc +import inspect import traceback try: import fcntl @@ -235,7 +243,7 @@ class SimpleXMLRPCDispatcher: """ try: - params, method = xmlrpclib.loads(data) + params, method = loads(data) # generate response if dispatch_method is not None: @@ -244,16 +252,16 @@ class SimpleXMLRPCDispatcher: response = self._dispatch(method, params) # wrap response in a singleton tuple response = (response,) - response = xmlrpclib.dumps(response, methodresponse=1, - allow_none=self.allow_none, encoding=self.encoding) + response = dumps(response, methodresponse=1, + allow_none=self.allow_none, encoding=self.encoding) except Fault as fault: - response = xmlrpclib.dumps(fault, allow_none=self.allow_none, - encoding=self.encoding) + response = dumps(fault, allow_none=self.allow_none, + encoding=self.encoding) except: # report exception back to server exc_type, exc_value, exc_tb = sys.exc_info() - response = xmlrpclib.dumps( - xmlrpclib.Fault(1, "%s:%s" % (exc_type, exc_value)), + response = dumps( + Fault(1, "%s:%s" % (exc_type, exc_value)), encoding=self.encoding, allow_none=self.allow_none, ) @@ -585,6 +593,273 @@ class CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher): self.handle_xmlrpc(request_text) + +# ----------------------------------------------------------------------------- +# Self documenting XML-RPC Server. + +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 + results = [] + here = 0 + + # XXX Note that this regular expression 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'RFC[- ]?(\d+)|' + r'PEP[- ]?(\d+)|' + r'(self\.)?((?:\w|\.)+))\b') + while 1: + match = pattern.search(text, here) + if not match: break + start, end = match.span() + results.append(escape(text[here:start])) + + all, scheme, rfc, pep, selfdot, name = match.groups() + if scheme: + url = escape(all).replace('"', '"') + results.append('<a href="%s">%s</a>' % (url, url)) + elif rfc: + url = 'http://www.rfc-editor.org/rfc/rfc%d.txt' % int(rfc) + results.append('<a href="%s">%s</a>' % (url, escape(all))) + elif pep: + url = 'http://www.python.org/dev/peps/pep-%04d/' % 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)) + elif selfdot: + results.append('self.<strong>%s</strong>' % name) + else: + results.append(self.namelink(name, classes)) + here = end + results.append(escape(text[here:])) + return ''.join(results) + + def docroutine(self, object, name, mod=None, + funcs={}, classes={}, methods={}, cl=None): + """Produce HTML documentation for a function or method object.""" + + anchor = (cl and cl.__name__ or '') + '-' + name + note = '' + + title = '<a name="%s"><strong>%s</strong></a>' % ( + self.escape(anchor), self.escape(name)) + + if inspect.ismethod(object): + args, varargs, varkw, defaults = inspect.getargspec(object) + # exclude the argument bound to the instance, it will be + # confusing to the non-Python user + argspec = inspect.formatargspec ( + args[1:], + varargs, + varkw, + defaults, + formatvalue=self.formatvalue + ) + elif inspect.isfunction(object): + args, varargs, varkw, defaults = inspect.getargspec(object) + argspec = inspect.formatargspec( + args, varargs, varkw, defaults, formatvalue=self.formatvalue) + else: + argspec = '(...)' + + if isinstance(object, tuple): + argspec = object[0] or argspec + docstring = object[1] or "" + else: + docstring = pydoc.getdoc(object) + + decl = title + argspec + (note and self.grey( + '<font face="helvetica, arial">%s</font>' % note)) + + doc = self.markup( + 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.""" + + fdict = {} + for key, value in methods.items(): + fdict[key] = '#-' + key + fdict[value] = fdict[key] + + server_name = self.escape(server_name) + 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 + + contents = [] + method_items = sorted(methods.items()) + for key, value in method_items: + contents.append(self.docroutine(value, key, funcs=fdict)) + result = result + self.bigsection( + 'Methods', '#ffffff', '#eeaa77', ''.join(contents)) + + return result + +class XMLRPCDocGenerator: + """Generates documentation for an XML-RPC server. + + This class is designed as mix-in and should not + be constructed directly. + """ + + def __init__(self): + # 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 "\ + "protocol." + 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 + in the documentation.""" + + methods = {} + + for method_name in self.system_listMethods(): + if method_name in self.funcs: + 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): + method = method_info + elif not hasattr(self.instance, '_dispatch'): + try: + method = resolve_dotted_attribute( + self.instance, + method_name + ) + except AttributeError: + method = method_info + else: + method = method_info + else: + assert 0, "Could not find method in self.functions and no "\ + "instance installed" + + methods[method_name] = method + + documenter = ServerHTMLDoc() + documentation = documenter.docserver( + self.server_name, + self.server_documentation, + methods + ) + + 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 + XML-RPC requests. + + Handles all HTTP GET requests and interprets them as requests + for documentation. + """ + + def do_GET(self): + """Handles the HTTP GET request. + + Interpret all HTTP GET requests as requests for server + documentation. + """ + # Check that the path is legal + if not self.is_rpc_path_valid(): + self.report_404() + return + + response = self.server.generate_html_documentation() + self.send_response(200) + self.send_header("Content-type", "text/html") + self.send_header("Content-length", str(len(response))) + self.end_headers() + self.wfile.write(response.encode()) + + # shut down the connection + self.wfile.flush() + self.connection.shutdown(1) + +class DocXMLRPCServer( SimpleXMLRPCServer, + XMLRPCDocGenerator): + """XML-RPC and HTML documentation server. + + Adds the ability to serve server documentation to the capabilities + of SimpleXMLRPCServer. + """ + + def __init__(self, addr, requestHandler=DocXMLRPCRequestHandler, + logRequests=1, allow_none=False, encoding=None, + bind_and_activate=True): + SimpleXMLRPCServer.__init__(self, addr, requestHandler, logRequests, + allow_none, encoding, bind_and_activate) + XMLRPCDocGenerator.__init__(self) + +class DocCGIXMLRPCRequestHandler( CGIXMLRPCRequestHandler, + XMLRPCDocGenerator): + """Handler for XML-RPC data and documentation requests passed through + CGI""" + + def handle_get(self): + """Handles the HTTP GET request. + + Interpret all HTTP GET requests as requests for server + documentation. + """ + + response = self.generate_html_documentation() + + print('Content-Type: text/html') + print('Content-Length: %d' % len(response)) + print() + sys.stdout.write(response) + + def __init__(self): + CGIXMLRPCRequestHandler.__init__(self) + XMLRPCDocGenerator.__init__(self) + + if __name__ == '__main__': print('Running XML-RPC server on port 8000') server = SimpleXMLRPCServer(("localhost", 8000)) |