diff options
| -rw-r--r-- | docs/api/response.rst | 5 | ||||
| -rw-r--r-- | docs/narr/hooks.rst | 17 | ||||
| -rw-r--r-- | pyramid/response.py | 56 | ||||
| -rw-r--r-- | pyramid/tests/test_response.py | 64 |
4 files changed, 141 insertions, 1 deletions
diff --git a/docs/api/response.rst b/docs/api/response.rst index e67b15568..8020b629a 100644 --- a/docs/api/response.rst +++ b/docs/api/response.rst @@ -9,3 +9,8 @@ :members: :inherited-members: +Functions +~~~~~~~~~ + +.. autofunction:: response_adapter + diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index 94701c9f9..0dcbcd371 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -536,7 +536,8 @@ Changing How Pyramid Treats View Responses It is possible to control how Pyramid treats the result of calling a view callable on a per-type basis by using a hook involving -:meth:`pyramid.config.Configurator.add_response_adapter`. +:meth:`pyramid.config.Configurator.add_response_adapter` or the +:class:`~pyramid.response.response_adapter` decorator. .. note:: This feature is new as of Pyramid 1.1. @@ -573,6 +574,20 @@ Response: config.add_response_adapter(string_response_adapter, str) +The above example using the :class:`~pyramid.response.response_adapter` +decorator: + +.. code-block:: python + :linenos: + + from pyramid.response import Response + from pyramid.response import response_adapter + + @response_adapter(str) + def string_response_adapter(s): + response = Response(s) + return response + Likewise, if you want to be able to return a simplified kind of response object from view callables, you can use the IResponse hook to register an adapter to the more complex IResponse interface: diff --git a/pyramid/response.py b/pyramid/response.py index 68496e386..60666bd03 100644 --- a/pyramid/response.py +++ b/pyramid/response.py @@ -1,3 +1,5 @@ +import venusian + from webob import Response as _Response from zope.interface import implements from pyramid.interfaces import IResponse @@ -5,3 +7,57 @@ from pyramid.interfaces import IResponse class Response(_Response): implements(IResponse) + +class response_adapter(object): + """ Decorator activated via a :term:`scan` which treats the + function being decorated as a response adapter for the set of types or + interfaces passed as ``*types_or_ifaces`` to the decorator constructor. + + For example: + + .. code-block:: python + + from pyramid.response import Response + from pyramid.response import response_adapter + + @response_adapter(int) + def myadapter(i): + return Response(status=i) + + More than one type or interface can be passed as a constructor argument. + The decorated response adapter will be called for each type or interface. + + .. code-block:: python + + import json + + from pyramid.response import Response + from pyramid.response import response_adapter + + @response_adapter(dict, list) + def myadapter(ob): + return Response(json.dumps(ob)) + + This method will have no effect until a :term:`scan` is performed + agains the package or module which contains it, ala: + + .. code-block:: python + + from pyramid.config import Configurator + config = Configurator() + config.scan('somepackage_containing_adapters') + + """ + venusian = venusian # for unit testing + + def __init__(self, *types_or_ifaces): + self.types_or_ifaces = types_or_ifaces + + def register(self, scanner, name, wrapped): + config = scanner.config + for type_or_iface in self.types_or_ifaces: + config.add_response_adapter(wrapped, type_or_iface) + + def __call__(self, wrapped): + self.venusian.attach(wrapped, self.register, category='pyramid') + return wrapped diff --git a/pyramid/tests/test_response.py b/pyramid/tests/test_response.py index 6cb2fd6f4..39360c0af 100644 --- a/pyramid/tests/test_response.py +++ b/pyramid/tests/test_response.py @@ -1,4 +1,5 @@ import unittest +from pyramid import testing class TestResponse(unittest.TestCase): def _getTargetClass(self): @@ -15,3 +16,66 @@ class TestResponse(unittest.TestCase): inst = self._getTargetClass()() self.assertTrue(IResponse.providedBy(inst)) +class Dummy(object): + pass + +class DummyConfigurator(object): + def __init__(self): + self.adapters = [] + + def add_response_adapter(self, wrapped, type_or_iface): + self.adapters.append((wrapped, type_or_iface)) + +class DummyVenusian(object): + def __init__(self): + self.attached = [] + + def attach(self, wrapped, fn, category=None): + self.attached.append((wrapped, fn, category)) + +class TestResponseAdapter(unittest.TestCase): + def setUp(self): + registry = Dummy() + self.config = testing.setUp(registry=registry) + self.config.begin() + + def tearDown(self): + self.config.end() + + def _makeOne(self, *types_or_ifaces): + from pyramid.response import response_adapter + return response_adapter(*types_or_ifaces) + + def test_register_single(self): + from zope.interface import Interface + class IFoo(Interface): pass + dec = self._makeOne(IFoo) + def foo(): pass + config = DummyConfigurator() + scanner = Dummy() + scanner.config = config + dec.register(scanner, None, foo) + self.assertEqual(config.adapters, [(foo, IFoo)]) + + def test_register_multi(self): + from zope.interface import Interface + class IFoo(Interface): pass + class IBar(Interface): pass + dec = self._makeOne(IFoo, IBar) + def foo(): pass + config = DummyConfigurator() + scanner = Dummy() + scanner.config = config + dec.register(scanner, None, foo) + self.assertEqual(config.adapters, [(foo, IFoo), (foo, IBar)]) + + def test___call__(self): + from zope.interface import Interface + class IFoo(Interface): pass + dec = self._makeOne(IFoo) + dummy_venusian = DummyVenusian() + dec.venusian = dummy_venusian + def foo(): pass + dec(foo) + self.assertEqual(dummy_venusian.attached, + [(foo, dec.register, 'pyramid')]) |
