summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/api/response.rst5
-rw-r--r--docs/narr/hooks.rst17
-rw-r--r--pyramid/response.py56
-rw-r--r--pyramid/tests/test_response.py64
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')])