Skip to content

Commit

Permalink
Merge pull request #1499 from sontek/expose_response_class
Browse files Browse the repository at this point in the history
Expose response class
  • Loading branch information
mmerickel committed Jan 2, 2015
2 parents 568b583 + 303abc8 commit 0414b7c
Show file tree
Hide file tree
Showing 16 changed files with 179 additions and 30 deletions.
5 changes: 5 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ Features
via ``request.static_url('myapp:static/foo.png')``.
See https://github.com/Pylons/pyramid/issues/1252

- Added ``pyramid.config.Configurator.set_response_factory`` and the
``response_factory`` keyword argument to the ``Configurator`` for defining
a factory that will return a custom ``Response`` class.
See https://github.com/Pylons/pyramid/pull/1499

Bug Fixes
---------

Expand Down
4 changes: 4 additions & 0 deletions docs/glossary.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ Glossary
An object which, provided a :term:`WSGI` environment as a single
positional argument, returns a Pyramid-compatible request.

response factory
An object which, provided a :term:`request` as a single positional
argument, returns a Pyramid-compatible response.

response
An object returned by a :term:`view callable` that represents response
data returned to the requesting user agent. It must implement the
Expand Down
47 changes: 47 additions & 0 deletions docs/narr/hooks.rst
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,53 @@ We attach and cache an object named ``extra`` to the ``request`` object.

.. _beforerender_event:

.. index::
single: response factory

.. _changing_the_response_factory:

Changing the Response Factory
-------------------------------

.. versionadded:: 1.6

Whenever :app:`Pyramid` returns a response from a view it creates a
:term:`response` object. By default, an instance of the
:class:`pyramid.response.Response` class is created to represent the response
object.

The factory that :app:`Pyramid` uses to create a response object instance can be
changed by passing a ``response_factory`` argument to the constructor of the
:term:`configurator`. This argument can be either a callable or a
:term:`dotted Python name` representing a callable.

.. code-block:: python
:linenos:
from pyramid.response import Response
class MyResponse(Response):
pass
config = Configurator(response_factory=lambda r: MyResponse())
If you're doing imperative configuration, and you'd rather do it after you've
already constructed a :term:`configurator` it can also be registered via the
:meth:`pyramid.config.Configurator.set_response_factory` method:

.. code-block:: python
:linenos:
from pyramid.config import Configurator
from pyramid.response import Response
class MyResponse(Response):
pass
config = Configurator()
config.set_response_factory(lambda r: MyResponse())
Using The Before Render Event
-----------------------------

Expand Down
16 changes: 14 additions & 2 deletions pyramid/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,11 @@ class Configurator(
See :ref:`changing_the_request_factory`. By default it is ``None``,
which means use the default request factory.
If ``response_factory`` is passed, it should be a :term:`response
factory` implementation or a :term:`dotted Python name` to the same.
See :ref:`changing_the_response_factory`. By default it is ``None``,
which means use the default response factory.
If ``default_permission`` is passed, it should be a
:term:`permission` string to be used as the default permission for
all view configuration registrations performed against this
Expand All @@ -190,7 +195,7 @@ class Configurator(
configurations which do not explicitly declare a permission will
always be executable by entirely anonymous users (any
authorization policy in effect is ignored).
.. seealso::
See also :ref:`setting_a_default_permission`.
Expand Down Expand Up @@ -254,6 +259,7 @@ class Configurator(
.. versionadded:: 1.6
The ``root_package`` argument.
The ``response_factory`` argument.
"""
manager = manager # for testing injection
venusian = venusian # for testing injection
Expand All @@ -276,6 +282,7 @@ def __init__(self,
debug_logger=None,
locale_negotiator=None,
request_factory=None,
response_factory=None,
default_permission=None,
session_factory=None,
default_view_mapper=None,
Expand Down Expand Up @@ -310,6 +317,7 @@ def __init__(self,
debug_logger=debug_logger,
locale_negotiator=locale_negotiator,
request_factory=request_factory,
response_factory=response_factory,
default_permission=default_permission,
session_factory=session_factory,
default_view_mapper=default_view_mapper,
Expand All @@ -325,6 +333,7 @@ def setup_registry(self,
debug_logger=None,
locale_negotiator=None,
request_factory=None,
response_factory=None,
default_permission=None,
session_factory=None,
default_view_mapper=None,
Expand Down Expand Up @@ -412,6 +421,9 @@ def setup_registry(self,
if request_factory:
self.set_request_factory(request_factory)

if response_factory:
self.set_response_factory(response_factory)

if default_permission:
self.set_default_permission(default_permission)

Expand Down Expand Up @@ -469,7 +481,7 @@ def registerSelfAdapter(required=None, provided=None,
_registry.registerSelfAdapter = registerSelfAdapter

# API

def _get_introspector(self):
introspector = getattr(self.registry, 'introspector', _marker)
if introspector is _marker:
Expand Down
27 changes: 27 additions & 0 deletions pyramid/config/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from pyramid.interfaces import (
IDefaultRootFactory,
IRequestFactory,
IResponseFactory,
IRequestExtensions,
IRootFactory,
ISessionFactory,
Expand Down Expand Up @@ -96,6 +97,32 @@ def register():
intr['factory'] = factory
self.action(IRequestFactory, register, introspectables=(intr,))

@action_method
def set_response_factory(self, factory):
""" The object passed as ``factory`` should be an object (or a
:term:`dotted Python name` which refers to an object) which
will be used by the :app:`Pyramid` as the default response
objects. This factory object must have the same
methods and attributes as the
:class:`pyramid.request.Response` class.
.. note::
Using the ``response_factory`` argument to the
:class:`pyramid.config.Configurator` constructor
can be used to achieve the same purpose.
"""
factory = self.maybe_dotted(factory)

def register():
self.registry.registerUtility(factory, IResponseFactory)

intr = self.introspectable('response factory', None,
self.object_description(factory),
'response factory')
intr['factory'] = factory
self.action(IResponseFactory, register, introspectables=(intr,))

@action_method
def add_request_method(self,
callable=None,
Expand Down
9 changes: 3 additions & 6 deletions pyramid/renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from pyramid.interfaces import (
IJSONAdapter,
IRendererFactory,
IResponseFactory,
IRendererInfo,
)

Expand All @@ -25,7 +24,7 @@

from pyramid.path import caller_package

from pyramid.response import Response
from pyramid.response import Response, _get_response_factory
from pyramid.threadlocal import get_current_registry

# API
Expand Down Expand Up @@ -448,10 +447,8 @@ def _make_response(self, result, request):
if response is None:
# request is None or request is not a pyramid.response.Response
registry = self.registry
response_factory = registry.queryUtility(IResponseFactory,
default=Response)

response = response_factory()
response_factory = _get_response_factory(registry)
response = response_factory(request)

if result is not None:
if isinstance(result, text_type):
Expand Down
9 changes: 3 additions & 6 deletions pyramid/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
IRequest,
IResponse,
ISessionFactory,
IResponseFactory,
)

from pyramid.compat import (
Expand All @@ -21,7 +20,7 @@

from pyramid.decorator import reify
from pyramid.i18n import LocalizerRequestMixin
from pyramid.response import Response
from pyramid.response import Response, _get_response_factory
from pyramid.security import (
AuthenticationAPIMixin,
AuthorizationAPIMixin,
Expand Down Expand Up @@ -214,10 +213,8 @@ def response(self):
right" attributes (e.g. by calling ``request.response.set_cookie()``)
within a view that uses a renderer. Mutations to this response object
will be preserved in the response sent to the client."""
registry = self.registry
response_factory = registry.queryUtility(IResponseFactory,
default=Response)
return response_factory()
response_factory = _get_response_factory(self.registry)
return response_factory(self)

def is_response(self, ob):
""" Return ``True`` if the object passed as ``ob`` is a valid
Expand Down
17 changes: 15 additions & 2 deletions pyramid/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@

from webob import Response as _Response
from zope.interface import implementer
from pyramid.interfaces import IResponse
from pyramid.interfaces import IResponse, IResponseFactory


def init_mimetypes(mimetypes):
# this is a function so it can be unittested
Expand Down Expand Up @@ -143,7 +144,7 @@ def myadapter(i):
@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:
Expand All @@ -167,3 +168,15 @@ def register(self, scanner, name, wrapped):
def __call__(self, wrapped):
self.venusian.attach(wrapped, self.register, category='pyramid')
return wrapped


def _get_response_factory(registry):
""" Obtain a :class: `pyramid.response.Response` using the
`pyramid.interfaces.IResponseFactory`.
"""
response_factory = registry.queryUtility(
IResponseFactory,
default=lambda r: Response()
)

return response_factory
8 changes: 4 additions & 4 deletions pyramid/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

from pyramid.interfaces import (
IRequest,
IResponseFactory,
ISession,
)

Expand All @@ -22,7 +21,7 @@
from pyramid.config import Configurator
from pyramid.decorator import reify
from pyramid.path import caller_package
from pyramid.response import Response
from pyramid.response import Response, _get_response_factory
from pyramid.registry import Registry

from pyramid.security import (
Expand All @@ -42,6 +41,7 @@
from pyramid.url import URLMethodsMixin
from pyramid.util import InstancePropertyMixin


_marker = object()

class DummyRootFactory(object):
Expand Down Expand Up @@ -383,8 +383,8 @@ def _del_registry(self):

@reify
def response(self):
f = self.registry.queryUtility(IResponseFactory, default=Response)
return f()
f = _get_response_factory(self.registry)
return f(self)

have_zca = True

Expand Down
15 changes: 15 additions & 0 deletions pyramid/tests/test_config/test_factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,21 @@ def test_set_request_factory_dottedname(self):
self.assertEqual(config.registry.getUtility(IRequestFactory),
dummyfactory)

def test_set_response_factory(self):
from pyramid.interfaces import IResponseFactory
config = self._makeOne(autocommit=True)
factory = lambda r: object()
config.set_response_factory(factory)
self.assertEqual(config.registry.getUtility(IResponseFactory), factory)

def test_set_response_factory_dottedname(self):
from pyramid.interfaces import IResponseFactory
config = self._makeOne(autocommit=True)
config.set_response_factory(
'pyramid.tests.test_config.dummyfactory')
self.assertEqual(config.registry.getUtility(IResponseFactory),
dummyfactory)

def test_set_root_factory(self):
from pyramid.interfaces import IRootFactory
config = self._makeOne()
Expand Down
12 changes: 12 additions & 0 deletions pyramid/tests/test_config/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,18 @@ def test_setup_registry_request_factory(self):
utility = reg.getUtility(IRequestFactory)
self.assertEqual(utility, factory)

def test_setup_registry_response_factory(self):
from pyramid.registry import Registry
from pyramid.interfaces import IResponseFactory
reg = Registry()
config = self._makeOne(reg)
factory = lambda r: object()
config.setup_registry(response_factory=factory)
self.assertEqual(reg.queryUtility(IResponseFactory), None)
config.commit()
utility = reg.getUtility(IResponseFactory)
self.assertEqual(utility, factory)

def test_setup_registry_request_factory_dottedname(self):
from pyramid.registry import Registry
from pyramid.interfaces import IRequestFactory
Expand Down
9 changes: 7 additions & 2 deletions pyramid/tests/test_renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,10 @@ def _registerResponseFactory(self):
from pyramid.interfaces import IResponseFactory
class ResponseFactory(object):
pass
self.config.registry.registerUtility(ResponseFactory, IResponseFactory)

self.config.registry.registerUtility(
lambda r: ResponseFactory(), IResponseFactory
)

def test_render_to_response(self):
self._registerRendererFactory()
Expand Down Expand Up @@ -310,7 +313,9 @@ def test_with_alternate_response_factory(self):
class ResponseFactory(object):
def __init__(self):
pass
self.config.registry.registerUtility(ResponseFactory, IResponseFactory)
self.config.registry.registerUtility(
lambda r: ResponseFactory(), IResponseFactory
)
request = testing.DummyRequest()
helper = self._makeOne('loo.foo')
response = helper._make_response(b'abc', request)
Expand Down
Loading

0 comments on commit 0414b7c

Please sign in to comment.