Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

merge from branch:

Features:
- implement, document and test base view class that provides multi-method XML-RPC support
Docs:
- add intersphinx for linking to bfg docs
- get version from package info (needs pkginfo installed!) to stop info duplication
- tweak existing docs
Metadata:
- alpha marker is pointless
- bump version to 0.4dev



git-svn-id: http://svn.repoze.org/repoze.bfg.xmlrpc/trunk@9583 8f1d8bf8-68d2-4fbe-a113-2afb08c80ed9
  • Loading branch information...
commit 88e46ffcf5a3d4db9279b42b0e61203f082d689c 1 parent 8509a4c
Chris Withers chris@simplistix.co.uk authored
View
18 docs/conf.py
@@ -12,7 +12,7 @@
# All configuration values have a default value; values that are commented
# out serve to show the default value.
-import sys, os
+import sys, os, pkginfo, datetime
# If your extensions are in another directory, add it here. If the directory
# is relative to the documentation root, use os.path.abspath to make it
@@ -31,9 +31,18 @@
# General configuration
# ---------------------
+pkg_info = pkginfo.Develop(os.path.join(os.path.dirname(__file__),'..'))
+
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = ['sphinx.ext.autodoc']
+extensions = [
+ 'sphinx.ext.autodoc',
+ 'sphinx.ext.intersphinx',
+ ]
+
+
+# Looks for bfg's objects
+intersphinx_mapping = {'http://docs.repoze.org/bfg/current': None}
# Add any paths that contain templates here, relative to this directory.
templates_path = ['.templates']
@@ -46,15 +55,14 @@
# General substitutions.
project = 'repoze.bfg.xmlrpc'
-copyright = '2008, Repoze Developers <repoze-dev@lists.repoze.org>'
+copyright = '2008-%s, Repoze Developers <repoze-dev@lists.repoze.org>' % datetime.datetime.now().year
# The default replacements for |version| and |release|, also used in various
# other places throughout the built documents.
#
# The short X.Y version.
-version = '0.3'
+version = release = pkg_info.version
# The full version, including alpha/beta/rc tags.
-release = '0.3'
# There are two options for replacing |today|: either, you set today to
# some non-false value, then it is used:
View
72 docs/index.rst
@@ -13,12 +13,20 @@ XML-RPC support for the :mod:`repoze.bfg` web framework.
easy_install -i http://dist.repoze.org/bfgsite/simple repoze.bfg.xmlrpc
Or obtain the packge via `http://svn.repoze.org/repoze.bfg.xmlrpc
-<http://svn.repoze.org/repoze.bfg.xmlrpc>`_ and use ``setup.py
+<http://pypi.python.org/pypi/repoze.bfg.xmlrpc>`_ and use ``setup.py
install``.
:mod:`repoze.bfg.xmlrpc` Usage
------------------------------
+XML-RPC allows you to expose one or more methods at a particular URL.
+:mod:`repoze.bfg.xmlrpc` has a simple usage pattern for exposing a
+single method at a particular url, and a more complicated one for
+when you want to expose multiple methods at a particular URL.
+
+Exposing a single method
+~~~~~~~~~~~~~~~~~~~~~~~~
+
Create a function in the form below. The function will be meant to be
called with positional parameters from an XML-RPC request.
@@ -28,7 +36,7 @@ called with positional parameters from an XML-RPC request.
def say_hello(context, name):
return 'Hello, %s' % name
-Then add the ``@xmlrpc_view`` decorator to the function.
+Then add the :func:`~repoze.bfg.xmlrpc.xmlrpc_view` decorator to the function.
.. code-block:: python
:linenos:
@@ -63,6 +71,63 @@ the view URL; traversal doesn't work from there.
>>> s('Chris')
Hello, Chris
+Exposing multiple methods
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you have multiple methods to expose at a particular url, you should
+group them together in a class. Think of this class in the same way
+as you would when you implement a normal :mod:`repoze.bfg` view as a
+class rather than a function.
+
+The methods of the class will be those exposed as methods to XML-RPC.
+Not that XML-RPC only supports positional parameters.
+
+To make this view class handle incoming XML-RPC requests, you simply
+need to subclass the :class:`~repoze.bfg.xmlrpc.XMLRPCView` class.
+:class:`~repoze.bfg.xmlrpc.XMLRPCView` provides a
+:meth:`~repoze.bfg.xmlrpc.XMLRPCView.__call__` method, so make sure
+that your class doesn't provide one!
+
+For example:
+
+.. code-block:: python
+ :linenos:
+
+ from repoze.bfg.xmlrpc import XMLRPCView
+
+ class MyXMLRPCStuff(XMLRPCView)
+
+ def say_hello(self):
+ return 'Hello, %s' % name
+
+ def say_goobye(self):
+ return 'Goodbye, cruel world'
+
+This class can then be registered with :mod:`repoze.bfg` as a
+normal view:
+
+.. code-block:: xml
+ :linenos:
+
+ <bfg:view
+ name="RPC2"
+ view=".views.MyXMLRPCStuff"
+ for="*"
+ />
+
+The methods exposed by this view can now be used by any XML-RPC
+client:
+
+.. code-block:: python
+ :linenos:
+
+ >>> from xmlrpclib import ServerProxy
+ >>> s = ServerProxy('http://localhost:6543/RPC2')
+ >>> s.say_hello('Chris')
+ Hello, Chris
+ >>> s.say_goodbye()
+ Goodbye, cruel world
+
.. _api:
API Documentation for :mod:`repoze.bfg.xmlrpc`
@@ -72,6 +137,9 @@ API Documentation for :mod:`repoze.bfg.xmlrpc`
.. autofunction:: xmlrpc_view
+ .. autoclass:: XMLRPCView
+ :members: __call__
+
.. autofunction:: xmlrpc_marshal
.. autofunction:: xmlrpc_response
View
43 repoze/bfg/xmlrpc/__init__.py
@@ -34,12 +34,10 @@ def parse_xmlrpc_request(request):
return params, method
def xmlrpc_view(wrapped):
- """ Function meant to be used as a decorator. The ``xmlrpc_view``
- function turns functions which accept params and return Python
- structures into functions suitable for use as bfg views which an
- XML-RPC response. The decorated function must accept a
- ``context`` argument and zero or more positional arguments
- (conventionally named ``*params``).
+ """ This decorator turns functions which accept params and return Python
+ structures into functions suitable for use as bfg views that speak XML-RPC.
+ The decorated function must accept a ``context`` argument and zero or
+ more positional arguments (conventionally named ``*params``).
E.g.::
@@ -67,9 +65,9 @@ def say(context, what):
else:
return {'say':'Goodbye!'}
- Note that if you use ``repoze.bfg.convention``, you must decorate your
- view function in the following order for it to be recognized by the
- convention machinery as a view::
+ Note that if you use :class:`~repoze.bfg.view.bfg_view`, you must
+ decorate your view function in the following order for it to be
+ recognized by the convention machinery as a view::
@bfg_view(name='say')
@xmlrpc_view
@@ -79,8 +77,8 @@ def say(context, what):
else:
return {'say':'Goodbye!'}
- In other words do *not* decorate it in ``xmlrpc_view``, then
- ``bfg_view order``; it won't work.
+ In other words do *not* decorate it in :func:`~repoze.bfg.xmlrpc.xmlrpc_view`,
+ then :class:`~repoze.bfg.view.bfg_view`; it won't work.
"""
def _curried(context, request):
@@ -92,4 +90,27 @@ def _curried(context, request):
return _curried
+class XMLRPCView:
+ """A base class for a view that serves multiple methods by XML-RPC.
+ Subclass and add your methods as described in the documentation.
+ """
+
+ def __init__(self,context,request):
+ self.context = context
+ self.request = request
+
+ def __call__(self):
+ """
+ This method de-serializes the XML-RPC request and
+ dispatches the resulting method call to the correct
+ method on the :class:`~repoze.bfg.xmlrpc.XMLRPCView`
+ subclass instance.
+
+ .. warning::
+ Do not override this method in any subclass if you
+ want XML-RPC to continute to work!
+
+ """
+ params, method = parse_xmlrpc_request(self.request)
+ return xmlrpc_response(getattr(self,method)(*params))
View
32 repoze/bfg/xmlrpc/tests.py
@@ -55,7 +55,7 @@ def test_toobig(self):
request.content_length = 1 << 24
self.assertRaises(ValueError, self._callFUT, request)
-class TestXMLRPCView(unittest.TestCase):
+class TestDecorator(unittest.TestCase):
def _callFUT(self, unwrapped):
from repoze.bfg.xmlrpc import xmlrpc_view
return xmlrpc_view(unwrapped)
@@ -77,3 +77,33 @@ def unwrapped(context, what):
response = wrapped(context, request)
self.assertEqual(response.body, xmlrpclib.dumps((param,),
methodresponse=True))
+
+class TestBaseClass(unittest.TestCase):
+
+ def test_normal(self):
+
+ from repoze.bfg.xmlrpc import XMLRPCView
+ class Test(XMLRPCView):
+ def a_method(self,param):
+ return param
+
+ # set up a request
+ param = 'what'
+ import xmlrpclib
+ packet = xmlrpclib.dumps((param,), methodname='a_method')
+ request = testing.DummyRequest()
+ request.body = packet
+ request.content_length = len(packet)
+
+ # instantiate the view
+ context = testing.DummyModel()
+ instance = Test(context,request)
+
+ # these are fair game for the methods to use if they want
+ self.failUnless(instance.context is context)
+ self.failUnless(instance.request is request)
+
+ # exercise it
+ response = instance()
+ self.assertEqual(response.body, xmlrpclib.dumps((param,),
+ methodresponse=True))
View
3  setup.py
@@ -12,7 +12,7 @@
#
##############################################################################
-__version__ = '0.3'
+__version__ = '0.4dev'
import os
@@ -30,7 +30,6 @@
description='XML-RPC support for repoze.bfg',
long_description=README + '\n\n' + CHANGES,
classifiers=[
- "Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"Programming Language :: Python",
"Topic :: Internet :: WWW/HTTP",
Please sign in to comment.
Something went wrong with that request. Please try again.