Chris McDonough
2011-07-28 be631daa6812a004cdf52353cfca2bd6255f7135
merge
5 files modified
144 ■■■■■ changed files
CONTRIBUTORS.txt 2 ●●●●● patch | view | raw | blame | history
docs/api/response.rst 5 ●●●●● patch | view | raw | blame | history
docs/narr/hooks.rst 17 ●●●●● patch | view | raw | blame | history
pyramid/response.py 56 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_response.py 64 ●●●●● patch | view | raw | blame | history
CONTRIBUTORS.txt
@@ -144,3 +144,5 @@
- Christoph Zwerschke, 2011/06/07
- Shane Hathaway, 2011/07/22
- Manuel Hermann, 2011/07/11
docs/api/response.rst
@@ -9,3 +9,8 @@
   :members:
   :inherited-members:
Functions
~~~~~~~~~
.. autofunction:: response_adapter
docs/narr/hooks.rst
@@ -537,7 +537,8 @@
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.
@@ -574,6 +575,20 @@
   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:
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 @@
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
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 @@
        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')])