summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@agendaless.com>2010-04-25 21:53:04 +0000
committerChris McDonough <chrism@agendaless.com>2010-04-25 21:53:04 +0000
commit8cc399ae9291f34166bc8a8fc1983a9941b57e97 (patch)
tree7c11a39f2926593e85d158ba9d7a056a235c7eb5
parent30b89889183acb9243f13cc141fae08a22b2d317 (diff)
downloadpyramid-8cc399ae9291f34166bc8a8fc1983a9941b57e97.tar.gz
pyramid-8cc399ae9291f34166bc8a8fc1983a9941b57e97.tar.bz2
pyramid-8cc399ae9291f34166bc8a8fc1983a9941b57e97.zip
- Add a new method of the Configurator named ``derive_view`` which can
be used to generate a BFG view callable from a user-supplied function, instance, or class. This useful for external framework and plugin authors wishing to wrap callables supplied by their users which follow the same calling conventions and response conventions as objects that can be supplied directly to BFG as a view callable. See the ``derive_view`` method in the ``repoze.bfg.configuration.Configurator`` docs.
-rw-r--r--CHANGES.txt9
-rw-r--r--TODO.txt3
-rw-r--r--docs/api/configuration.rst2
-rw-r--r--repoze/bfg/configuration.py70
-rw-r--r--repoze/bfg/tests/test_configuration.py42
5 files changed, 123 insertions, 3 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 144c2cb10..17e597de3 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -77,6 +77,15 @@ Features
more helpful debugging information. This mode is on by default in
all new projects.
+- Add a new method of the Configurator named ``derive_view`` which can
+ be used to generate a BFG view callable from a user-supplied
+ function, instance, or class. This useful for external framework and
+ plugin authors wishing to wrap callables supplied by their users
+ which follow the same calling conventions and response conventions
+ as objects that can be supplied directly to BFG as a view callable.
+ See the ``derive_view`` method in the
+ ``repoze.bfg.configuration.Configurator`` docs.
+
ZCML
----
diff --git a/TODO.txt b/TODO.txt
index fd60e6523..3fcbd4588 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -5,9 +5,6 @@
- Basic WSGI documentation (pipeline / app / server).
-- Figure out a way to expose some of the functionality of
- ``Configurator._derive_view`` as an API.
-
- Provide a webob.Response class facade for forward compat.
- Fix message catalog extraction / compilation documentation.
diff --git a/docs/api/configuration.rst b/docs/api/configuration.rst
index 7b411b92d..9d391dd1f 100644
--- a/docs/api/configuration.rst
+++ b/docs/api/configuration.rst
@@ -36,6 +36,8 @@
.. automethod:: add_view
+ .. automethod:: derive_view
+
.. automethod:: load_zcml(spec)
.. automethod:: make_wsgi_app()
diff --git a/repoze/bfg/configuration.py b/repoze/bfg/configuration.py
index fdc259934..9fa3fe8b2 100644
--- a/repoze/bfg/configuration.py
+++ b/repoze/bfg/configuration.py
@@ -221,6 +221,76 @@ class Configurator(object):
def _split_spec(self, path_or_spec):
return resolve_resource_spec(path_or_spec, self.package.__name__)
+ def derive_view(self, view, attr=None, renderer=None):
+ """
+ Create a :term:`view callable` using the function, instance,
+ or class provided as ``view`` object.
+
+ This is API is useful to framework extenders who create
+ pluggable systems which need to register 'proxy' view
+ callables for functions, instances, or classes which meet the
+ requirements of being a :mod:`repoze.bfg` view callable. For
+ example, a ``some_other_framework`` function in another
+ framework may want to allow a user to supply a view callable,
+ but he may want to wrap the view callable in his own before
+ registering the wrapper as a :mod:`repoze.bfg` view callable.
+ Because a :mod:`repoze.bfg` view callable can be any of a
+ number of valid objects, the framework extender will not know
+ how to call the user-supplied object. Running it through
+ ``derive_view`` normalizes it to a callable which accepts two
+ arguments: ``context`` and ``request``.
+
+ For example:
+
+ .. code-block:: python
+
+ def some_other_framework(user_supplied_view):
+ config = Configurator(reg)
+ proxy_view = config.derive_view(user_supplied_view)
+ def my_wrapper(context, request):
+ do_something_that_mutates(request)
+ return proxy_view(context, request)
+ config.add_view(my_wrapper)
+
+ The ``view`` object provided should be one of the following:
+
+ - A function or another non-class callable object that accepts
+ a :term:`request` as a single positional argument and which
+ returns a :term:`response` object.
+
+ - A function or other non-class callable object that accepts
+ two positional arguments, ``context, request`` and which
+ returns a :term:`response` object.
+
+ - A class which accepts a single positional argument in its
+ constructor named ``request``, and which has a ``__call__``
+ method that accepts no arguments that returns a
+ :term:`response` object.
+
+ - A class which accepts two positional arguments named
+ ``context, request``, and which has a ``__call__`` method
+ that accepts no arguments that returns a :term:`response`
+ object.
+
+ This API returns a callable which accepts the arguments
+ ``context, request`` and which returns the result of calling
+ the provided ``view`` object.
+
+ The ``attr`` keyword argument is most useful when the view
+ object is a class. It names the method that should be used as
+ the callable. If ``attr`` is not provided, the attribute
+ effectively defaults to ``__call__``. See
+ :ref:`class_as_view` for more information.
+
+ The ``renderer`` keyword argument, if supplies, causes the
+ returned callable to use a :term:`renderer` to convert the
+ user-supplied view result to a :term:`response` object. If a
+ ``renderer`` argument is not supplied, the user-supplied view
+ must itself return a :term:`response` object.
+ """
+
+ return self._derive_view(view, attr=attr, renderer_name=renderer)
+
def _derive_view(self, view, permission=None, predicates=(),
attr=None, renderer_name=None, wrapper_viewname=None,
viewname=None, accept=None, order=MAX_ORDER,
diff --git a/repoze/bfg/tests/test_configuration.py b/repoze/bfg/tests/test_configuration.py
index fa18b75cb..0a443720f 100644
--- a/repoze/bfg/tests/test_configuration.py
+++ b/repoze/bfg/tests/test_configuration.py
@@ -1879,6 +1879,48 @@ class ConfiguratorTests(unittest.TestCase):
result = config._renderer_from_name(None)
self.assertEqual(result, 'OK')
+ def test_derive_view_function(self):
+ def view(request):
+ return 'OK'
+ config = self._makeOne()
+ result = config.derive_view(view)
+ self.failIf(result is view)
+ self.assertEqual(result(None, None), 'OK')
+
+ def test_derive_view_with_renderer(self):
+ def view(request):
+ return 'OK'
+ config = self._makeOne()
+ class moo(object):
+ def __init__(self, *arg, **kw):
+ pass
+ def __call__(self, *arg, **kw):
+ return 'moo'
+ config.add_renderer('moo', moo)
+ result = config.derive_view(view, renderer='moo')
+ self.failIf(result is view)
+ self.assertEqual(result(None, None).body, 'moo')
+
+ def test_derive_view_class_without_attr(self):
+ class View(object):
+ def __init__(self, request):
+ pass
+ def __call__(self):
+ return 'OK'
+ config = self._makeOne()
+ result = config.derive_view(View)
+ self.assertEqual(result(None, None), 'OK')
+
+ def test_derive_view_class_with_attr(self):
+ class View(object):
+ def __init__(self, request):
+ pass
+ def another(self):
+ return 'OK'
+ config = self._makeOne()
+ result = config.derive_view(View, attr='another')
+ self.assertEqual(result(None, None), 'OK')
+
def test__derive_view_as_function_context_and_request(self):
def view(context, request):
return 'OK'