diff options
| author | Chris McDonough <chrism@plope.com> | 2011-06-13 06:17:00 -0400 |
|---|---|---|
| committer | Chris McDonough <chrism@plope.com> | 2011-06-13 06:17:00 -0400 |
| commit | d868fff7597c5a05acd1f5c024fc45dde9880413 (patch) | |
| tree | 603a17606938ac748e96dd12bb8904cbf4f2be2d | |
| parent | f0d77e8f3cec1ff90a2029fe143580fd42cf81aa (diff) | |
| download | pyramid-d868fff7597c5a05acd1f5c024fc45dde9880413.tar.gz pyramid-d868fff7597c5a05acd1f5c024fc45dde9880413.tar.bz2 pyramid-d868fff7597c5a05acd1f5c024fc45dde9880413.zip | |
- Remove IResponder abstraction in favor of more general IResponse
abstraction.
- It is now possible to return an arbitrary object from a Pyramid view
callable even if a renderer is not used, as long as a suitable adapter to
``pyramid.interfaces.IResponse`` is registered for the type of the returned
object. See the section in the Hooks chapter of the documentation entitled
"Changing How Pyramid Treats View Responses".
- The Pyramid router now, by default, expects response objects returned from
view callables to implement the ``pyramid.interfaces.IResponse`` interface.
Unlike the Pyramid 1.0 version of this interface, objects which implement
IResponse now must define a ``__call__`` method that accepts ``environ``
and ``start_response``, and which returns an ``app_iter`` iterable, among
other things. Previously, it was possible to return any object which had
the three WebOb ``app_iter``, ``headerlist``, and ``status`` attributes as
a response, so this is a backwards incompatibility. It is possible to get
backwards compatibility back by registering an adapter to IResponse from
the type of object you're now returning from view callables. See the
section in the Hooks chapter of the documentation entitled "Changing How
Pyramid Treats View Responses".
- The ``pyramid.interfaces.IResponse`` interface is now much more extensive.
Previously it defined only ``app_iter``, ``status`` and ``headerlist``; now
it is basically intended to directly mirror the ``webob.Response`` API,
which has many methods and attributes.
- Documentation changes to support above.
| -rw-r--r-- | CHANGES.txt | 42 | ||||
| -rw-r--r-- | TODO.txt | 19 | ||||
| -rw-r--r-- | docs/api/interfaces.rst | 2 | ||||
| -rw-r--r-- | docs/api/request.rst | 33 | ||||
| -rw-r--r-- | docs/designdefense.rst | 4 | ||||
| -rw-r--r-- | docs/glossary.rst | 12 | ||||
| -rw-r--r-- | docs/narr/assets.rst | 2 | ||||
| -rw-r--r-- | docs/narr/hooks.rst | 134 | ||||
| -rw-r--r-- | docs/narr/renderers.rst | 2 | ||||
| -rw-r--r-- | docs/narr/router.rst | 6 | ||||
| -rw-r--r-- | docs/narr/views.rst | 36 | ||||
| -rw-r--r-- | docs/narr/webob.rst | 66 | ||||
| -rw-r--r-- | docs/tutorials/wiki/definingviews.rst | 10 | ||||
| -rw-r--r-- | docs/tutorials/wiki2/definingviews.rst | 3 | ||||
| -rw-r--r-- | pyramid/config.py | 39 | ||||
| -rw-r--r-- | pyramid/interfaces.py | 8 | ||||
| -rw-r--r-- | pyramid/registry.py | 19 | ||||
| -rw-r--r-- | pyramid/renderers.py | 4 | ||||
| -rw-r--r-- | pyramid/request.py | 2 | ||||
| -rw-r--r-- | pyramid/response.py | 2 | ||||
| -rw-r--r-- | pyramid/router.py | 47 | ||||
| -rw-r--r-- | pyramid/session.py | 14 | ||||
| -rw-r--r-- | pyramid/tests/test_config.py | 78 | ||||
| -rw-r--r-- | pyramid/tests/test_router.py | 122 | ||||
| -rw-r--r-- | pyramid/tests/test_session.py | 15 | ||||
| -rw-r--r-- | pyramid/tests/test_view.py | 23 | ||||
| -rw-r--r-- | pyramid/view.py | 6 |
27 files changed, 411 insertions, 339 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index e413f0657..5e8df1a0b 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -127,11 +127,11 @@ Features - The ``pyramid.request.Response`` class now has a ``RequestClass`` interface which points at ``pyramid.response.Request``. -- It is now possible to control how the Pyramid router calls the WSGI - ``start_response`` callable and obtains the WSGI ``app_iter`` based on - adapting the response object to the new ``pyramid.interfaces.IResponder`` - interface. See the section in the Hooks chapter of the documentation - entitled "Changing How Pyramid Treats Response Objects". +- It is now possible to return an arbitrary object from a Pyramid view + callable even if a renderer is not used, as long as a suitable adapter to + ``pyramid.interfaces.IResponse`` is registered for the type of the returned + object. See the section in the Hooks chapter of the documentation entitled + "Changing How Pyramid Treats View Responses". - The Pyramid router will now, by default, call the ``__call__`` method of WebOb response objects when returning a WSGI response. This means that, @@ -306,22 +306,26 @@ Behavior Changes ``webob.response.Response`` (in order to directly implement the ``pyramid.interfaces.IResponse`` interface). -- The ``pyramid.interfaces.IResponse`` interface now includes a ``__call__`` - method which has the WSGI application call signature (and which expects an - iterable as a result). +Backwards Incompatibilities +--------------------------- - The Pyramid router now, by default, expects response objects returned from - views to implement the WSGI application interface (a ``__call__`` method - that accepts ``environ`` and ``start_response``, and which returns an - ``app_iter`` iterable). If such a method exists, Pyramid will now call it - in order to satisfy the WSGI request. Backwards compatibility code in the - default responder exists which will fall back to the older behavior, but - Pyramid will raise a deprecation warning if it is reached. See the section - in the Hooks chapter of the documentation entitled "Changing How Pyramid - Treats Response Objects" to default back to the older behavior, where the - ``app_iter``, ``headerlist``, and ``status`` attributes of the object were - consulted directly (without any indirection through ``__call__``) to - silence the deprecation warnings. + view callables to implement the ``pyramid.interfaces.IResponse`` interface. + Unlike the Pyramid 1.0 version of this interface, objects which implement + IResponse now must define a ``__call__`` method that accepts ``environ`` + and ``start_response``, and which returns an ``app_iter`` iterable, among + other things. Previously, it was possible to return any object which had + the three WebOb ``app_iter``, ``headerlist``, and ``status`` attributes as + a response, so this is a backwards incompatibility. It is possible to get + backwards compatibility back by registering an adapter to IResponse from + the type of object you're now returning from view callables. See the + section in the Hooks chapter of the documentation entitled "Changing How + Pyramid Treats View Responses". + +- The ``pyramid.interfaces.IResponse`` interface is now much more extensive. + Previously it defined only ``app_iter``, ``status`` and ``headerlist``; now + it is basically intended to directly mirror the ``webob.Response`` API, + which has many methods and attributes. Dependencies ------------ @@ -6,14 +6,27 @@ Must-Have - To subclass or not subclass http exceptions. -- Depend on only __call__ interface or only 3-attr interface in builtin code - that deals with response objects. +- Flesh out IResponse interface. Attributes Used internally: unicode_body / + body / content_type / charset / cache_expires / headers/ + default_content_type / set_cookie / headerlist / app_iter / status / + __call__. -- Figure out what to do with ``is_response``. +- Deprecate view.is_response? + +- Move is_response to response.py? + +- Make sure registering IResponse adapter for webob.Response doesn't make it + impossible to register an IResponse adapter for an interface that a + webob.Response happens to implement. + +- Run whatsitdoing tests. - Docs mention ``exception.args[0]`` as a way to get messages; check that this works. +- Deprecate response_foo attrs on request at attribute set time rather than + lookup time. + Should-Have ----------- diff --git a/docs/api/interfaces.rst b/docs/api/interfaces.rst index 3a60fa4dc..51a1963b5 100644 --- a/docs/api/interfaces.rst +++ b/docs/api/interfaces.rst @@ -57,6 +57,6 @@ Other Interfaces .. autointerface:: IMultiDict :members: - .. autointerface:: IResponder + .. autointerface:: IResponse :members: diff --git a/docs/api/request.rst b/docs/api/request.rst index 8cb424658..27ce395ac 100644 --- a/docs/api/request.rst +++ b/docs/api/request.rst @@ -107,7 +107,9 @@ return {'text':'Value that will be used by the renderer'} Mutations to this response object will be preserved in the response sent - to the client after rendering. + to the client after rendering. For more information about using + ``request.response`` in conjunction with a renderer, see + :ref:`request_response_attr`. Non-renderer code can also make use of request.response instead of creating a response "by hand". For example, in view code:: @@ -162,20 +164,21 @@ .. attribute:: response_* - .. warning:: As of Pyramid 1.1, assignment to ``response_*`` attrs are - deprecated. Assigning to one will cause a deprecation warning to be - emitted. Instead of assigning ``response_*`` attributes to the - request, use API of the the :attr:`pyramid.request.Request.response` - object (exposed to view code as ``request.response``) to influence - response behavior. - - You can set attributes on a :class:`pyramid.request.Request` which will - influence the behavor of *rendered* responses (views which use a - :term:`renderer` and which don't directly return a response). These - attributes begin with ``response_``, such as ``response_headerlist``. If - you need to influence response values from a view that uses a renderer - (such as the status code, a header, the content type, etc) see, - :ref:`response_prefixed_attrs`. + In Pyramid 1.0, you could set attributes on a + :class:`pyramid.request.Request` which influenced the behavor of + *rendered* responses (views which use a :term:`renderer` and which + don't directly return a response). These attributes began with + ``response_``, such as ``response_headerlist``. If you needed to + influence response values from a view that uses a renderer (such as the + status code, a header, the content type, etc) you would set these + attributes. See :ref:`response_prefixed_attrs` for further discussion. + As of Pyramid 1.1, assignment to ``response_*`` attrs are deprecated. + Assigning to one is still supported but will cause a deprecation + warning to be emitted, and eventually the feature will be removed. For + new code, instead of assigning ``response_*`` attributes to the + request, use API of the the :attr:`pyramid.request.Request.response` + object (exposed to view code as ``request.response``) to influence + rendered response behavior. .. note:: diff --git a/docs/designdefense.rst b/docs/designdefense.rst index 136b9c5de..de6c0af33 100644 --- a/docs/designdefense.rst +++ b/docs/designdefense.rst @@ -428,7 +428,7 @@ allowing people to define "custom" view predicates: :linenos: from pyramid.view import view_config - from webob import Response + from pyramid.response import Response def subpath(context, request): return request.subpath and request.subpath[0] == 'abc' @@ -1497,7 +1497,7 @@ comments take into account what we've discussed in the .. code-block:: python :linenos: - from webob import Response # explicit response objects, no TL + from pyramid.response import Response # explicit response objects, no TL from paste.httpserver import serve # explicitly WSGI def hello_world(request): # accepts a request; no request thread local reqd diff --git a/docs/glossary.rst b/docs/glossary.rst index 079a069b4..d3ba9a545 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -16,12 +16,12 @@ Glossary positional argument, returns a ``WebOb`` compatible request. response - An object that has three attributes: ``app_iter`` (representing an - iterable body), ``headerlist`` (representing the http headers sent - to the user agent), and ``status`` (representing the http status - string sent to the user agent). This is the interface defined for - ``WebOb`` response objects. See :ref:`webob_chapter` for - information about response objects. + An object returned by a :term:`view callable` that represents response + data returned to the requesting user agent. It must implements the + :class:`pyramid.interfaces.IResponse` interface. A response object is + typically an instance of the :class:`pyramid.response.Response` class or + a subclass such as :class:`pyramid.httpexceptions.HTTPFound`. See + :ref:`webob_chapter` for information about response objects. Repoze "Repoze" is essentially a "brand" of software developed by `Agendaless diff --git a/docs/narr/assets.rst b/docs/narr/assets.rst index 8d0e7058c..0d50b0106 100644 --- a/docs/narr/assets.rst +++ b/docs/narr/assets.rst @@ -358,7 +358,7 @@ do so, do things "by hand". First define the view callable. :linenos: import os - from webob import Response + from pyramid.response import Response def favicon_view(request): here = os.path.dirname(__file__) diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index b6a781417..0db8ce5e0 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -523,41 +523,103 @@ The default context URL generator is available for perusal as the class :term:`Pylons` GitHub Pyramid repository. .. index:: - single: IResponder - -.. _using_iresponder: - -Changing How Pyramid Treats Response Objects --------------------------------------------- - -It is possible to control how the Pyramid :term:`router` calls the WSGI -``start_response`` callable and obtains the WSGI ``app_iter`` based on -adapting the response object to the :class: `pyramid.interfaces.IResponder` -interface. The default responder uses the ``__call__`` method of a response -object, passing it the WSGI environ and the WSGI ``start_response`` callable -(the response is assumed to be a WSGI application). To override the -responder:: - - from pyramid.interfaces import IResponder - from pyramid.response import Response - from myapp import MyResponder - - config.registry.registerAdapter(MyResponder, (Response,), - IResponder, name='') - -Overriding makes it possible to reuse response object implementations which -have, for example, the ``app_iter``, ``headerlist`` and ``status`` attributes -of an object returned as a response instead of trying to use the object's -``__call__`` method:: - - class MyResponder(object): - def __init__(self, response): - """ Obtain a reference to the response """ - self.response = response - def __call__(self, request, start_response): - """ Call start_response and return an app_iter """ - start_response(self.response.status, self.response.headerlist) - return self.response.app_iter + single: IResponse + +.. _using_iresponse: + +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 +:class:`pyramid.interfaces.IResponse`. + +.. note:: This feature is new as of Pyramid 1.1. + +Pyramid, in various places, adapts the result of calling a view callable to +the :class:`~pyramid.interfaces.IResponse` interface to ensure that the +object returned by the view callable is a "true" response object. The vast +majority of time, the result of this adaptation is the result object itself, +as view callables written by "civilians" who read the narrative documentation +contained in this manual will always return something that implements the +:class:`~pyramid.interfaces.IResponse` interface. Most typically, this will +be an instance of the :class:`pyramid.response.Response` class or a subclass. +If a civilian returns a non-Response object from a view callable that isn't +configured to use a :term:`renderer`, he will typically expect the router to +raise an error. However, you can hook Pyramid in such a way that users can +return arbitrary values from a view callable by providing an adapter which +converts the arbitrary return value into something that implements +:class:`~pyramid.interfaces.IResponse`. + +For example, if you'd like to allow view callables to return bare string +objects (without requiring a a :term:`renderer` to convert a string to a +response object), you can register an adapter which converts the string to a +Response: + +.. code-block:: python + :linenos: + + from pyramid.interfaces import IResponse + from pyramid.response import Response + + def string_response_adapter(s): + response = Response(s) + return response + + # config is an instance of pyramid.config.Configurator + + config.registry.registerAdapter(string_response_adapter, (str,), + IResponse) + +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: + +.. code-block:: python + :linenos: + + from pyramid.interfaces import IResponse + from pyramid.response import Response + + class SimpleResponse(object): + def __init__(self, body): + self.body = body + + def simple_response_adapter(simple_response): + response = Response(simple_response.body) + return response + + # config is an instance of pyramid.config.Configurator + + config.registry.registerAdapter(simple_response_adapter, + (SimpleResponse,), + IResponse) + +If you want to implement your own Response object instead of using the +:class:`pyramid.response.Response` object in any capacity at all, you'll have +to make sure the object implements every attribute and method outlined in +:class:`pyramid.interfaces.IResponse` *and* you'll have to ensure that it's +marked up with ``zope.interface.implements(IResponse)``: + + from pyramid.interfaces import IResponse + from zope.interface import implements + + class MyResponse(object): + implements(IResponse) + # ... an implementation of every method and attribute + # documented in IResponse should follow ... + +When an alternate response object implementation is returned by a view +callable, if that object asserts that it implements +:class:`~pyramid.interfaces.IResponse` (via +``zope.interface.implements(IResponse)``) , an adapter needn't be registered +for the object; Pyramid will use it directly. + +An IResponse adapter for ``webob.Response`` (as opposed to +:class:`pyramid.response.Response`) is registered by Pyramid by default at +startup time, as by their nature, instances of this class (and instances of +subclasses of the class) will natively provide IResponse. The adapter +registered for ``webob.Response`` simply returns the response object. .. index:: single: view mapper @@ -628,7 +690,7 @@ A user might make use of these framework components like so: # user application - from webob import Response + from pyramid.response import Response from pyramid.config import Configurator import pyramid_handlers from paste.httpserver import serve diff --git a/docs/narr/renderers.rst b/docs/narr/renderers.rst index 99ee14908..c4a37c23d 100644 --- a/docs/narr/renderers.rst +++ b/docs/narr/renderers.rst @@ -416,7 +416,7 @@ effect, you must return ``request.response``: For more information on attributes of the request, see the API documentation in :ref:`request_module`. For more information on the API of -``request.response``, see :class:`pyramid.response.Response`. +``request.response``, see :attr:`pyramid.request.Request.response`. .. _response_prefixed_attrs: diff --git a/docs/narr/router.rst b/docs/narr/router.rst index 30d54767e..0812f7ec7 100644 --- a/docs/narr/router.rst +++ b/docs/narr/router.rst @@ -115,9 +115,9 @@ processing? any :term:`response callback` functions attached via :meth:`~pyramid.request.Request.add_response_callback`. A :class:`~pyramid.events.NewResponse` :term:`event` is then sent to any - subscribers. The response object's ``app_iter``, ``status``, and - ``headerlist`` attributes are then used to generate a WSGI response. The - response is sent back to the upstream WSGI server. + subscribers. The response object's ``__call__`` method is then used to + generate a WSGI response. The response is sent back to the upstream WSGI + server. #. :app:`Pyramid` will attempt to execute any :term:`finished callback` functions attached via diff --git a/docs/narr/views.rst b/docs/narr/views.rst index 990828f80..e3d0a37e5 100644 --- a/docs/narr/views.rst +++ b/docs/narr/views.rst @@ -230,29 +230,19 @@ implements the :term:`Response` interface is to return a def view(request): return Response('OK') -You don't need to use :class:`~pyramid.response.Response` to represent a -response. A view can actually return any object that has a ``__call__`` -method that implements the :term:`WSGI` application call interface. For -example, an instance of the following class could be successfully returned by -a view callable as a response object: - -.. code-block:: python - :linenos: - - class SimpleResponse(object): - def __call__(self, environ, start_response): - """ Call the ``start_response`` callback and return - an iterable """ - body = 'Hello World!' - headers = [('Content-Type', 'text/plain'), - ('Content-Length', str(len(body)))] - start_response('200 OK', headers) - return [body] - -:app:`Pyramid` provides a range of different "exception" classes which can -act as response objects too. For example, an instance of the class -:class:`pyramid.httpexceptions.HTTPFound` is also a valid response object -(see :ref:`http_exceptions` and ref:`http_redirect`). +:app:`Pyramid` provides a range of different "exception" classes which +inherit from :class:`pyramid.response.Response`. For example, an instance of +the class :class:`pyramid.httpexceptions.HTTPFound` is also a valid response +object because it inherits from :class:`~pyramid.response.Response`. For +examples, see :ref:`http_exceptions` and ref:`http_redirect`. + +You can also return objects from view callables that aren't instances of (or +instances of classes which are subclasses of) +:class:`pyramid.response.Response` in various circumstances. This can be +helpful when writing tests and when attempting to share code between view +callables. See :ref:`renderers_chapter` for the common way to allow for +this. A much less common way to allow for view callables to return +non-Response objects is documented in :ref:`using_iresponse`. .. index:: single: view exceptions diff --git a/docs/narr/webob.rst b/docs/narr/webob.rst index 70ab5eea8..0ff8e1de7 100644 --- a/docs/narr/webob.rst +++ b/docs/narr/webob.rst @@ -10,15 +10,15 @@ Request and Response Objects .. note:: This chapter is adapted from a portion of the :term:`WebOb` documentation, originally written by Ian Bicking. -:app:`Pyramid` uses the :term:`WebOb` package to supply +:app:`Pyramid` uses the :term:`WebOb` package as a basis for its :term:`request` and :term:`response` object implementations. The -:term:`request` object that is passed to a :app:`Pyramid` -:term:`view` is an instance of the :class:`pyramid.request.Request` -class, which is a subclass of :class:`webob.Request`. The -:term:`response` returned from a :app:`Pyramid` :term:`view` -:term:`renderer` is an instance of the :mod:`webob.Response` class. -Users can also return an instance of :mod:`webob.Response` directly -from a view as necessary. +:term:`request` object that is passed to a :app:`Pyramid` :term:`view` is an +instance of the :class:`pyramid.request.Request` class, which is a subclass +of :class:`webob.Request`. The :term:`response` returned from a +:app:`Pyramid` :term:`view` :term:`renderer` is an instance of the +:mod:`pyramid.response.Response` class, which is a subclass of the +:class:`webob.Response` class. Users can also return an instance of +:class:`pyramid.response.Response` directly from a view as necessary. WebOb is a project separate from :app:`Pyramid` with a separate set of authors and a fully separate `set of documentation @@ -26,16 +26,15 @@ authors and a fully separate `set of documentation standard WebOb request, which is documented in the :ref:`request_module` API documentation. -WebOb provides objects for HTTP requests and responses. Specifically -it does this by wrapping the `WSGI <http://wsgi.org>`_ request -environment and response status/headers/app_iter (body). +WebOb provides objects for HTTP requests and responses. Specifically it does +this by wrapping the `WSGI <http://wsgi.org>`_ request environment and +response status, header list, and app_iter (body) values. -WebOb request and response objects provide many conveniences for -parsing WSGI requests and forming WSGI responses. WebOb is a nice way -to represent "raw" WSGI requests and responses; however, we won't -cover that use case in this document, as users of :app:`Pyramid` -don't typically need to use the WSGI-related features of WebOb -directly. The `reference documentation +WebOb request and response objects provide many conveniences for parsing WSGI +requests and forming WSGI responses. WebOb is a nice way to represent "raw" +WSGI requests and responses; however, we won't cover that use case in this +document, as users of :app:`Pyramid` don't typically need to use the +WSGI-related features of WebOb directly. The `reference documentation <http://pythonpaste.org/webob/reference.html>`_ shows many examples of creating requests and using response objects in this manner, however. @@ -170,9 +169,9 @@ of the request. I'll show various values for an example URL Methods +++++++ -There are `several methods -<http://pythonpaste.org/webob/class-webob.Request.html#__init__>`_ but -only a few you'll use often: +There are methods of request objects documented in +:class:`pyramid.request.Request` but you'll find that you won't use very many +of them. Here are a couple that might be useful: ``Request.blank(base_url)``: Creates a new request with blank information, based at the given @@ -183,9 +182,9 @@ only a few you'll use often: subrequests). ``req.get_response(wsgi_application)``: - This method calls the given WSGI application with this request, - and returns a `Response`_ object. You can also use this for - subrequests, or testing. + This method calls the given WSGI application with this request, and + returns a :class:`pyramid.response.Response` object. You can also use + this for subrequests, or testing. .. index:: single: request (and unicode) @@ -259,8 +258,10 @@ Response ~~~~~~~~ The :app:`Pyramid` response object can be imported as -:class:`pyramid.response.Response`. This import location is merely a facade -for its original location: ``webob.Response``. +:class:`pyramid.response.Response`. This class is a subclass of the +``webob.Response`` class. The subclass does not add or change any +functionality, so the WebOb Response documentation will be completely +relevant for this class as well. A response object has three fundamental parts: @@ -283,8 +284,8 @@ A response object has three fundamental parts: ``response.body_file`` (a file-like object; writing to it appends to ``app_iter``). -Everything else in the object derives from this underlying state. -Here's the highlights: +Everything else in the object typically derives from this underlying state. +Here are some highlights: ``response.content_type`` The content type *not* including the ``charset`` parameter. @@ -359,11 +360,12 @@ Exception Responses +++++++++++++++++++ To facilitate error responses like ``404 Not Found``, the module -:mod:`webob.exc` contains classes for each kind of error response. These -include boring, but appropriate error bodies. The exceptions exposed by this -module, when used under :app:`Pyramid`, should be imported from the -:mod:`pyramid.httpexceptions` module. This import location contains -subclasses and replacements that mirror those in the original ``webob.exc``. +:mod:`pyramid.httpexceptions` contains classes for each kind of error +response. These include boring, but appropriate error bodies. The +exceptions exposed by this module, when used under :app:`Pyramid`, should be +imported from the :mod:`pyramid.httpexceptions` module. This import location +contains subclasses and replacements that mirror those in the ``webob.exc`` +module. Each class is named ``pyramid.httpexceptions.HTTP*``, where ``*`` is the reason for the error. For instance, diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst index b6c083bbf..92a3da09c 100644 --- a/docs/tutorials/wiki/definingviews.rst +++ b/docs/tutorials/wiki/definingviews.rst @@ -84,10 +84,12 @@ No renderer is necessary when a view returns a response object. The ``view_wiki`` view callable always redirects to the URL of a Page resource named "FrontPage". To do so, it returns an instance of the :class:`pyramid.httpexceptions.HTTPFound` class (instances of which implement -the WebOb :term:`response` interface). The :func:`pyramid.url.resource_url` -API. :func:`pyramid.url.resource_url` constructs a URL to the ``FrontPage`` -page resource (e.g. ``http://localhost:6543/FrontPage``), and uses it as the -"location" of the HTTPFound response, forming an HTTP redirect. +the :class:`pyramid.interfaces.IResponse` interface like +:class:`pyramid.response.Response` does). The +:func:`pyramid.url.resource_url` API. :func:`pyramid.url.resource_url` +constructs a URL to the ``FrontPage`` page resource +(e.g. ``http://localhost:6543/FrontPage``), and uses it as the "location" of +the HTTPFound response, forming an HTTP redirect. The ``view_page`` view function ------------------------------- diff --git a/docs/tutorials/wiki2/definingviews.rst b/docs/tutorials/wiki2/definingviews.rst index 832f90b92..43cbc3483 100644 --- a/docs/tutorials/wiki2/definingviews.rst +++ b/docs/tutorials/wiki2/definingviews.rst @@ -91,7 +91,8 @@ path to our "FrontPage". The ``view_wiki`` function returns an instance of the :class:`pyramid.httpexceptions.HTTPFound` class (instances of which implement -the WebOb :term:`response` interface), It will use the +the :class:`pyramid.interfaces.IResponse` interface like +:class:`pyramid.response.Response` does), It will use the :func:`pyramid.url.route_url` API to construct a URL to the ``FrontPage`` page (e.g. ``http://localhost:6543/FrontPage``), and will use it as the "location" of the HTTPFound response, forming an HTTP redirect. diff --git a/pyramid/config.py b/pyramid/config.py index 91ba414b3..fab75f56d 100644 --- a/pyramid/config.py +++ b/pyramid/config.py @@ -19,6 +19,7 @@ from zope.interface import implementedBy from zope.interface.interfaces import IInterface from zope.interface import implements from zope.interface import classProvides +from zope.interface import providedBy from pyramid.interfaces import IAuthenticationPolicy from pyramid.interfaces import IAuthorizationPolicy @@ -36,6 +37,7 @@ from pyramid.interfaces import IRendererFactory from pyramid.interfaces import IRendererGlobalsFactory from pyramid.interfaces import IRequest from pyramid.interfaces import IRequestFactory +from pyramid.interfaces import IResponse from pyramid.interfaces import IRootFactory from pyramid.interfaces import IRouteRequest from pyramid.interfaces import IRoutesMapper @@ -82,7 +84,6 @@ from pyramid.traversal import traversal_path from pyramid.urldispatch import RoutesMapper from pyramid.util import DottedNameResolver from pyramid.view import render_view_to_response -from pyramid.view import is_response DEFAULT_RENDERERS = ( ('.mak', mako_renderer_factory), @@ -417,7 +418,8 @@ class Configurator(object): def _fix_registry(self): """ Fix up a ZCA component registry that is not a pyramid.registry.Registry by adding analogues of ``has_listeners``, - and ``notify`` through monkey-patching.""" + ``notify``, ``queryAdapterOrSelf``, and ``registerSelfAdapter`` + through monkey-patching.""" _registry = self.registry @@ -429,6 +431,24 @@ class Configurator(object): if not hasattr(_registry, 'has_listeners'): _registry.has_listeners = True + if not hasattr(_registry, 'queryAdapterOrSelf'): + def queryAdapterOrSelf(object, interface, name=u'', default=None): + provides = providedBy(object) + if not interface in provides: + return _registry.queryAdapter(object, interface, name=name, + default=default) + return object + _registry.queryAdapterOrSelf = queryAdapterOrSelf + + if not hasattr(_registry, 'registerSelfAdapter'): + def registerSelfAdapter(required=None, provided=None, + name=u'', info=u'', event=True): + return _registry.registerAdapter(lambda x: x, + required=required, + provided=provided, name=name, + info=info, event=event) + _registry.registerSelfAdapter = registerSelfAdapter + def _make_context(self, autocommit=False): context = PyramidConfigurationMachine() registerCommonDirectives(context) @@ -697,6 +717,9 @@ class Configurator(object): self._fix_registry() self._set_settings(settings) self._set_root_factory(root_factory) + # cope with WebOb response objects that aren't decorated with IResponse + from webob import Response as WebobResponse + registry.registerSelfAdapter((WebobResponse,), IResponse) debug_logger = self.maybe_dotted(debug_logger) if debug_logger is None: debug_logger = make_stream_logger('pyramid.debug', sys.stderr) @@ -2942,22 +2965,24 @@ class ViewDeriver(object): def _rendered_view(context, request): renderer = static_renderer - response = wrapped_view(context, request) - if not is_response(response): + result = wrapped_view(context, request) + registry = self.kw['registry'] + response = registry.queryAdapterOrSelf(result, IResponse) + if response is None: attrs = getattr(request, '__dict__', {}) if 'override_renderer' in attrs: # renderer overridden by newrequest event or other renderer_name = attrs.pop('override_renderer') renderer = RendererHelper(name=renderer_name, package=self.kw.get('package'), - registry = self.kw['registry']) + registry = registry) if '__view__' in attrs: view_inst = attrs.pop('__view__') else: view_inst = getattr(wrapped_view, '__original_view__', wrapped_view) - return renderer.render_view(request, response, view_inst, - context) + response = renderer.render_view(request, result, view_inst, + context) return response return _rendered_view diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index b8ff2d4c9..dea7174fb 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -233,14 +233,6 @@ class IMultiDict(Interface): # docs-only interface dictionary. This is similar to the kind of dictionary often used to represent the variables in a web request. """ -class IResponder(Interface): - """ Adapter from IResponse to an IResponder. See :ref:`using_iresponder` - for usage details. New in Pyramid 1.1. - """ - def __call__(self, request, start_response): - """ Call the WSGI ``start_response`` callable passed as - ``start_response`` and return an ``app_iter``.""" - # internal interfaces class IRequest(Interface): diff --git a/pyramid/registry.py b/pyramid/registry.py index 37e230dc3..26f84d493 100644 --- a/pyramid/registry.py +++ b/pyramid/registry.py @@ -1,4 +1,5 @@ from zope.component.registry import Components +from zope.interface import providedBy from pyramid.interfaces import ISettings @@ -28,6 +29,24 @@ class Registry(Components, dict): self.has_listeners = True return result + def registerSelfAdapter(self, required=None, provided=None, name=u'', + info=u'', event=True): + # registerAdapter analogue which always returns the object itself + # when required is matched + return self.registerAdapter(lambda x: x, required=required, + provided=provided, name=name, + info=info, event=event) + + def queryAdapterOrSelf(self, object, interface, name=u'', default=None): + # queryAdapter analogue which returns the object if it implements + # the interface, otherwise it will return an adaptation to the + # interface + provides = providedBy(object) + if not interface in provides: + return self.queryAdapter(object, interface, name=name, + default=default) + return object + def registerHandler(self, *arg, **kw): result = Components.registerHandler(self, *arg, **kw) self.has_listeners = True diff --git a/pyramid/renderers.py b/pyramid/renderers.py index a6dce9b3a..6865067dd 100644 --- a/pyramid/renderers.py +++ b/pyramid/renderers.py @@ -316,9 +316,7 @@ class RendererHelper(object): 'context':context, 'request':request } - return self.render_to_response(response, system, - request=request) - + return self.render_to_response(response, system, request=request) def render(self, value, system_values, request=None): renderer = self.renderer diff --git a/pyramid/request.py b/pyramid/request.py index d387a0b2f..b69440ac6 100644 --- a/pyramid/request.py +++ b/pyramid/request.py @@ -5,12 +5,14 @@ from zope.interface.interface import InterfaceClass from webob import BaseRequest from pyramid.interfaces import IRequest +from pyramid.interfaces import IResponse from pyramid.interfaces import ISessionFactory from pyramid.interfaces import IResponseFactory from pyramid.exceptions import ConfigurationError from pyramid.decorator import reify from pyramid.response import Response +from pyramid.threadlocal import get_current_registry from pyramid.url import resource_url from pyramid.url import route_url from pyramid.url import static_url diff --git a/pyramid/response.py b/pyramid/response.py index 1d2ef296f..68496e386 100644 --- a/pyramid/response.py +++ b/pyramid/response.py @@ -4,4 +4,4 @@ from pyramid.interfaces import IResponse class Response(_Response): implements(IResponse) - + diff --git a/pyramid/router.py b/pyramid/router.py index 4d2750efb..48640b39d 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -1,5 +1,3 @@ -import warnings - from zope.interface import implements from zope.interface import providedBy @@ -14,7 +12,7 @@ from pyramid.interfaces import IRoutesMapper from pyramid.interfaces import ITraverser from pyramid.interfaces import IView from pyramid.interfaces import IViewClassifier -from pyramid.interfaces import IResponder +from pyramid.interfaces import IResponse from pyramid.events import ContextFound from pyramid.events import NewRequest @@ -61,7 +59,6 @@ class Router(object): logger = self.logger manager = self.threadlocal_manager request = None - responder = default_responder threadlocals = {'registry':registry, 'request':request} manager.push(threadlocals) @@ -159,7 +156,7 @@ class Router(object): msg = request.path_info raise HTTPNotFound(msg) else: - response = view_callable(context, request) + result = view_callable(context, request) # handle exceptions raised during root finding and view-exec except Exception, why: @@ -181,52 +178,26 @@ class Router(object): # repoze.bfg.message docs-deprecated in Pyramid 1.0 environ['repoze.bfg.message'] = msg - response = view_callable(why, request) + result = view_callable(why, request) # process the response + response = registry.queryAdapterOrSelf(result, IResponse) + if response is None: + raise ValueError( + 'Could not convert view return value "%s" into a ' + 'response' % (result,)) has_listeners and registry.notify(NewResponse(request,response)) if request.response_callbacks: request._process_response_callbacks(response) - responder = adapters.queryAdapter(response, IResponder) - if responder is None: - responder = default_responder(response) - finally: if request is not None and request.finished_callbacks: request._process_finished_callbacks() - return responder(request, start_response) + return response(request.environ, start_response) finally: manager.pop() -def default_responder(response): - def inner(request, start_response): - # __call__ is default 1.1 response API - call = getattr(response, '__call__', None) - if call is not None: - return call(request.environ, start_response) - # start 1.0 bw compat (use headerlist, app_iter, status) - try: - headers = response.headerlist - app_iter = response.app_iter - status = response.status - except AttributeError: - raise ValueError( - 'Non-response object returned from view ' - '(and no renderer): %r' % (response)) - start_response(status, headers) - warnings.warn( - 'As of Pyramid 1.1, an object used as a response object is ' - 'required to have a "__call__" method if an IResponder adapter is ' - 'not registered for its type. See "Deprecations" in "What\'s New ' - 'in Pyramid 1.1" within the general Pyramid documentation for ' - 'further details.', - DeprecationWarning, - 3) - return app_iter - return inner - diff --git a/pyramid/session.py b/pyramid/session.py index 5772c80d0..bb3226a1e 100644 --- a/pyramid/session.py +++ b/pyramid/session.py @@ -8,8 +8,6 @@ try: except ImportError: # pragma: no cover import pickle -from webob import Response - import base64 import binascii import hmac @@ -213,17 +211,7 @@ def UnencryptedCookieSessionFactoryConfig( 'Cookie value is too long to store (%s bytes)' % len(cookieval) ) - if hasattr(response, 'set_cookie'): - # ``response`` is a "real" webob response - set_cookie = response.set_cookie - else: - # ``response`` is not a "real" webob response, cope - def set_cookie(*arg, **kw): - tmp_response = Response() - tmp_response.set_cookie(*arg, **kw) - response.headerlist.append( - tmp_response.headerlist[-1]) - set_cookie( + response.set_cookie( self._cookie_name, value=cookieval, max_age = self._cookie_max_age, diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py index 703a2577c..49bfab396 100644 --- a/pyramid/tests/test_config.py +++ b/pyramid/tests/test_config.py @@ -289,27 +289,58 @@ class ConfiguratorTests(unittest.TestCase): result = config.absolute_asset_spec('templates') self.assertEqual(result, 'pyramid.tests:templates') - def test_setup_registry_fixed(self): - class DummyRegistry(object): - def subscribers(self, events, name): - self.events = events - return events - def registerUtility(self, *arg, **kw): - pass + def test__fix_registry_has_listeners(self): reg = DummyRegistry() config = self._makeOne(reg) - config.add_view = lambda *arg, **kw: False - config.setup_registry() + config._fix_registry() self.assertEqual(reg.has_listeners, True) + + def test__fix_registry_notify(self): + reg = DummyRegistry() + config = self._makeOne(reg) + config._fix_registry() self.assertEqual(reg.notify(1), None) self.assertEqual(reg.events, (1,)) + def test__fix_registry_queryAdapterOrSelf(self): + from zope.interface import Interface + class IFoo(Interface): + pass + class Foo(object): + implements(IFoo) + class Bar(object): + pass + adaptation = () + foo = Foo() + bar = Bar() + reg = DummyRegistry(adaptation) + config = self._makeOne(reg) + config._fix_registry() + self.assertTrue(reg.queryAdapterOrSelf(foo, IFoo) is foo) + self.assertTrue(reg.queryAdapterOrSelf(bar, IFoo) is adaptation) + + def test__fix_registry_registerSelfAdapter(self): + reg = DummyRegistry() + config = self._makeOne(reg) + config._fix_registry() + reg.registerSelfAdapter('required', 'provided', name='abc') + self.assertEqual(len(reg.adapters), 1) + args, kw = reg.adapters[0] + self.assertEqual(args[0]('abc'), 'abc') + self.assertEqual(kw, + {'info': u'', 'provided': 'provided', + 'required': 'required', 'name': 'abc', 'event': True}) + + def test_setup_registry_calls_fix_registry(self): + reg = DummyRegistry() + config = self._makeOne(reg) + config.add_view = lambda *arg, **kw: False + config.setup_registry() + self.assertEqual(reg.has_listeners, True) + def test_setup_registry_registers_default_exceptionresponse_view(self): from pyramid.interfaces import IExceptionResponse from pyramid.view import default_exceptionresponse_view - class DummyRegistry(object): - def registerUtility(self, *arg, **kw): - pass reg = DummyRegistry() config = self._makeOne(reg) views = [] @@ -318,6 +349,15 @@ class ConfiguratorTests(unittest.TestCase): self.assertEqual(views[0], ((default_exceptionresponse_view,), {'context':IExceptionResponse})) + def test_setup_registry_registers_default_webob_iresponse_adapter(self): + from webob import Response + from pyramid.interfaces import IResponse + config = self._makeOne() + config.setup_registry() + response = Response() + self.assertTrue( + config.registry.queryAdapter(response, IResponse) is response) + def test_setup_registry_explicit_notfound_trumps_iexceptionresponse(self): from zope.interface import implementedBy from pyramid.interfaces import IRequest @@ -5134,3 +5174,17 @@ def dummy_extend(config, discrim): def dummy_extend2(config, discrim): config.action(discrim, None, config.registry) +class DummyRegistry(object): + def __init__(self, adaptation=None): + self.utilities = [] + self.adapters = [] + self.adaptation = adaptation + def subscribers(self, events, name): + self.events = events + return events + def registerUtility(self, *arg, **kw): + self.utilities.append((arg, kw)) + def registerAdapter(self, *arg, **kw): + self.adapters.append((arg, kw)) + def queryAdapter(self, *arg, **kw): + return self.adaptation diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py index 765a26751..5fd2cf01e 100644 --- a/pyramid/tests/test_router.py +++ b/pyramid/tests/test_router.py @@ -2,19 +2,6 @@ import unittest from pyramid import testing -def hide_warnings(wrapped): - import warnings - def wrapper(*arg, **kw): - warnings.filterwarnings('ignore') - try: - wrapped(*arg, **kw) - finally: - warnings.resetwarnings() - wrapper.__name__ = wrapped.__name__ - wrapper.__doc__ = wrapped.__doc__ - return wrapper - - class TestRouter(unittest.TestCase): def setUp(self): testing.setUp() @@ -249,7 +236,7 @@ class TestRouter(unittest.TestCase): self.assertTrue("view_name: ''" in message) self.assertTrue("subpath: []" in message) - def test_call_view_returns_nonresponse(self): + def test_call_view_returns_non_iresponse(self): from pyramid.interfaces import IViewClassifier context = DummyContext() self._registerTraverserFactory(context) @@ -260,6 +247,24 @@ class TestRouter(unittest.TestCase): start_response = DummyStartResponse() self.assertRaises(ValueError, router, environ, start_response) + def test_call_view_returns_adapted_response(self): + from pyramid.response import Response + from pyramid.interfaces import IViewClassifier + from pyramid.interfaces import IResponse + context = DummyContext() + self._registerTraverserFactory(context) + environ = self._makeEnviron() + view = DummyView('abc') + self._registerView(view, '', IViewClassifier, None, None) + router = self._makeOne() + start_response = DummyStartResponse() + def make_response(s): + return Response(s) + router.registry.registerAdapter(make_response, (str,), IResponse) + app_iter = router(environ, start_response) + self.assertEqual(app_iter, ['abc']) + self.assertEqual(start_response.status, '200 OK') + def test_call_view_registered_nonspecific_default_path(self): from pyramid.interfaces import IViewClassifier context = DummyContext() @@ -464,37 +469,6 @@ class TestRouter(unittest.TestCase): exc_raised(NotImplementedError, router, environ, start_response) self.assertEqual(environ['called_back'], True) - def test_call_with_overridden_iresponder_factory(self): - from zope.interface import Interface - from zope.interface import directlyProvides - from pyramid.interfaces import IRequest - from pyramid.interfaces import IViewClassifier - from pyramid.interfaces import IResponder - context = DummyContext() - class IFoo(Interface): - pass - directlyProvides(context, IFoo) - self._registerTraverserFactory(context, subpath=['']) - class DummyResponder(object): - def __init__(self, response): - self.response = response - def __call__(self, request, start_response): - self.response.responder_used = True - return '123' - self.registry.registerAdapter(DummyResponder, (None,), - IResponder, name='') - response = DummyResponse('200 OK') - directlyProvides(response, IFoo) - def view(context, request): - return response - environ = self._makeEnviron() - self._registerView(view, '', IViewClassifier, IRequest, Interface) - router = self._makeOne() - start_response = DummyStartResponse() - result = router(environ, start_response) - self.assertTrue(response.responder_used) - self.assertEqual(result, '123') - def test_call_request_factory_raises(self): # making sure finally doesnt barf when a request cannot be created environ = self._makeEnviron() @@ -875,7 +849,7 @@ class TestRouter(unittest.TestCase): result = router(environ, start_response) self.assertEqual(result, ["Hello, world"]) - def test_exception_view_returns_non_response(self): + def test_exception_view_returns_non_iresponse(self): from pyramid.interfaces import IRequest from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IExceptionViewClassifier @@ -1072,52 +1046,6 @@ class TestRouter(unittest.TestCase): start_response = DummyStartResponse() self.assertRaises(RuntimeError, router, environ, start_response) -class Test_default_responder(unittest.TestCase): - def _makeOne(self, response): - from pyramid.router import default_responder - return default_responder(response) - - def test_has_call(self): - response = DummyResponse() - response.app_iter = ['123'] - response.headerlist = [('a', '1')] - responder = self._makeOne(response) - request = DummyRequest({'a':'1'}) - start_response = DummyStartResponse() - app_iter = responder(request, start_response) - self.assertEqual(app_iter, response.app_iter) - self.assertEqual(start_response.status, response.status) - self.assertEqual(start_response.headers, response.headerlist) - self.assertEqual(response.environ, request.environ) - - @hide_warnings - def test_without_call_success(self): - response = DummyResponseWithoutCall() - response.app_iter = ['123'] - response.headerlist = [('a', '1')] - responder = self._makeOne(response) - request = DummyRequest({'a':'1'}) - start_response = DummyStartResponse() - app_iter = responder(request, start_response) - self.assertEqual(app_iter, response.app_iter) - self.assertEqual(start_response.status, response.status) - self.assertEqual(start_response.headers, response.headerlist) - - @hide_warnings - def test_without_call_exception(self): - response = DummyResponseWithoutCall() - del response.status - responder = self._makeOne(response) - request = DummyRequest({'a':'1'}) - start_response = DummyStartResponse() - self.assertRaises(ValueError, responder, request, start_response) - - -class DummyRequest(object): - def __init__(self, environ=None): - if environ is None: environ = {} - self.environ = environ - class DummyContext: pass @@ -1147,14 +1075,16 @@ class DummyStartResponse: self.status = status self.headers = headers -class DummyResponseWithoutCall: +from pyramid.interfaces import IResponse +from zope.interface import implements + +class DummyResponse(object): + implements(IResponse) headerlist = () app_iter = () + environ = None def __init__(self, status='200 OK'): self.status = status - -class DummyResponse(DummyResponseWithoutCall): - environ = None def __call__(self, environ, start_response): self.environ = environ diff --git a/pyramid/tests/test_session.py b/pyramid/tests/test_session.py index 17a437af6..5c6454a38 100644 --- a/pyramid/tests/test_session.py +++ b/pyramid/tests/test_session.py @@ -90,16 +90,8 @@ class TestUnencryptedCookieSession(unittest.TestCase): self.assertEqual(session._set_cookie(response), True) self.assertEqual(response.headerlist[-1][0], 'Set-Cookie') - def test__set_cookie_other_kind_of_response(self): - request = testing.DummyRequest() - request.exception = None - session = self._makeOne(request) - session['abc'] = 'x' - response = DummyResponse() - self.assertEqual(session._set_cookie(response), True) - self.assertEqual(len(response.headerlist), 1) - def test__set_cookie_options(self): + from pyramid.response import Response request = testing.DummyRequest() request.exception = None session = self._makeOne(request, @@ -110,10 +102,9 @@ class TestUnencryptedCookieSession(unittest.TestCase): cookie_httponly = True, ) session['abc'] = 'x' - response = DummyResponse() + response = Response() self.assertEqual(session._set_cookie(response), True) - self.assertEqual(len(response.headerlist), 1) - cookieval= response.headerlist[0][1] + cookieval= response.headerlist[-1][1] val, domain, path, secure, httponly = [x.strip() for x in cookieval.split(';')] self.assertTrue(val.startswith('abc=')) diff --git a/pyramid/tests/test_view.py b/pyramid/tests/test_view.py index b1d48b98b..0ea9a11a0 100644 --- a/pyramid/tests/test_view.py +++ b/pyramid/tests/test_view.py @@ -120,6 +120,19 @@ class RenderViewToIterableTests(BaseTest, unittest.TestCase): secure=True) self.assertEqual(iterable, ()) + def test_call_view_returns_iresponse_adaptable(self): + from pyramid.response import Response + request = self._makeRequest() + context = self._makeContext() + view = make_view('123') + self._registerView(request.registry, view, 'registered') + def str_response(s): + return Response(s) + request.registry.registerAdapter(str_response, (str,), IResponse) + iterable = self._callFUT(context, request, name='registered', + secure=True) + self.assertEqual(iterable, ['123']) + def test_call_view_registered_insecure_no_call_permissive(self): context = self._makeContext() request = self._makeRequest() @@ -536,9 +549,15 @@ def make_view(response): class DummyRequest: exception = None -class DummyResponse: - status = '200 OK' +from pyramid.interfaces import IResponse +from zope.interface import implements + +class DummyResponse(object): + implements(IResponse) headerlist = () + app_iter = () + status = '200 OK' + environ = None def __init__(self, body=None): if body is None: self.app_iter = () diff --git a/pyramid/view.py b/pyramid/view.py index 9a4be7580..a89df8859 100644 --- a/pyramid/view.py +++ b/pyramid/view.py @@ -4,6 +4,7 @@ import venusian from zope.interface import providedBy from zope.deprecation import deprecated +from pyramid.interfaces import IResponse from pyramid.interfaces import IRoutesMapper from pyramid.interfaces import IView from pyramid.interfaces import IViewClassifier @@ -100,6 +101,11 @@ def render_view_to_iterable(context, request, name='', secure=True): response = render_view_to_response(context, request, name, secure) if response is None: return None + try: + reg = request.registry + except AttributeError: + reg = get_current_registry() + response = reg.queryAdapterOrSelf(response, IResponse) return response.app_iter def render_view(context, request, name='', secure=True): |
