diff options
| -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): |
