summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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'