diff options
| -rw-r--r-- | CHANGES.txt | 9 | ||||
| -rw-r--r-- | TODO.txt | 3 | ||||
| -rw-r--r-- | docs/api/configuration.rst | 2 | ||||
| -rw-r--r-- | repoze/bfg/configuration.py | 70 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_configuration.py | 42 |
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 ---- @@ -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' |
