summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt62
-rw-r--r--TODO.txt3
-rw-r--r--docs/api/request.rst7
-rw-r--r--docs/narr/hooks.rst86
-rw-r--r--docs/narr/viewconfig.rst54
-rw-r--r--docs/tutorials/wiki/background.rst2
-rw-r--r--docs/tutorials/wiki2/background.rst2
-rw-r--r--docs/whatsnew-1.1.rst56
-rw-r--r--pyramid/config.py99
-rw-r--r--pyramid/events.py3
-rw-r--r--pyramid/request.py7
-rw-r--r--pyramid/tests/test_config.py125
-rw-r--r--pyramid/tests/test_request.py20
-rw-r--r--pyramid/view.py5
14 files changed, 474 insertions, 57 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 224860b47..0eb02baad 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,3 +1,65 @@
+Next release
+============
+
+Features
+--------
+
+- New request attribute: ``json``. If the request's ``content_type`` is
+ ``application/json``, this attribute will contain the JSON-decoded
+ variant of the request body. If the request's ``content_type`` is not
+ ``application/json``, this attribute will be ``None``.
+
+- A new value ``http_cache`` can be used as a view configuration
+ parameter.
+
+ When you supply an ``http_cache`` value to a view configuration, the
+ ``Expires`` and ``Cache-Control`` headers of a response generated by the
+ associated view callable are modified. The value for ``http_cache`` may be
+ one of the following:
+
+ - A nonzero integer. If it's a nonzero integer, it's treated as a number
+ of seconds. This number of seconds will be used to compute the
+ ``Expires`` header and the ``Cache-Control: max-age`` parameter of
+ responses to requests which call this view. For example:
+ ``http_cache=3600`` instructs the requesting browser to 'cache this
+ response for an hour, please'.
+
+ - A ``datetime.timedelta`` instance. If it's a ``datetime.timedelta``
+ instance, it will be converted into a number of seconds, and that number
+ of seconds will be used to compute the ``Expires`` header and the
+ ``Cache-Control: max-age`` parameter of responses to requests which call
+ this view. For example: ``http_cache=datetime.timedelta(days=1)``
+ instructs the requesting browser to 'cache this response for a day,
+ please'.
+
+ - Zero (``0``). If the value is zero, the ``Cache-Control`` and
+ ``Expires`` headers present in all responses from this view will be
+ composed such that client browser cache (and any intermediate caches) are
+ instructed to never cache the response.
+
+ - A two-tuple. If it's a two tuple (e.g. ``http_cache=(1,
+ {'public':True})``), the first value in the tuple may be a nonzero
+ integer or a ``datetime.timedelta`` instance; in either case this value
+ will be used as the number of seconds to cache the response. The second
+ value in the tuple must be a dictionary. The values present in the
+ dictionary will be used as input to the ``Cache-Control`` response
+ header. For example: ``http_cache=(3600, {'public':True})`` means 'cache
+ for an hour, and add ``public`` to the Cache-Control header of the
+ response'. All keys and values supported by the
+ ``webob.cachecontrol.CacheControl`` interface may be added to the
+ dictionary. Supplying ``{'public':True}`` is equivalent to calling
+ ``response.cache_control.public = True``.
+
+ Providing a non-tuple value as ``http_cache`` is equivalent to calling
+ ``response.cache_expires(value)`` within your view's body.
+
+ Providing a two-tuple value as ``http_cache`` is equivalent to calling
+ ``response.cache_expires(value[0], **value[1])`` within your view's body.
+
+ If you wish to avoid influencing, the ``Expires`` header, and instead wish
+ to only influence ``Cache-Control`` headers, pass a tuple as ``http_cache``
+ with the first element of ``None``, e.g.: ``(None, {'public':True})``.
+
1.1a4 (2011-07-01)
==================
diff --git a/TODO.txt b/TODO.txt
index 0b83e01a6..fc64163e1 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -9,6 +9,9 @@ Must-Have
Should-Have
-----------
+- Make "localizer" a property of request (instead of requiring
+ "get_localizer(request)"?
+
- Investigate mod_wsgi tutorial to make sure it still works (2 reports say
no; application package not found).
diff --git a/docs/api/request.rst b/docs/api/request.rst
index 27ce395ac..5dfb2ae9a 100644
--- a/docs/api/request.rst
+++ b/docs/api/request.rst
@@ -180,6 +180,13 @@
object (exposed to view code as ``request.response``) to influence
rendered response behavior.
+ .. attribute:: json
+
+ If the request's ``content_type`` is ``application/json``, this
+ attribute will contain the JSON-decoded variant of the request body.
+ If the request's ``content_type`` is not ``application/json``, this
+ attribute will be ``None``.
+
.. note::
For information about the API of a :term:`multidict` structure (such as
diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst
index 56c566a4c..94701c9f9 100644
--- a/docs/narr/hooks.rst
+++ b/docs/narr/hooks.rst
@@ -176,12 +176,54 @@ already constructed a :term:`configurator` it can also be registered via the
config.set_request_factory(MyRequest)
.. index::
+ single: before render event
+ single: adding renderer globals
+
+.. _beforerender_event:
+
+Using The Before Render Event
+-----------------------------
+
+Subscribers to the :class:`pyramid.events.BeforeRender` event may introspect
+and modify the set of :term:`renderer globals` before they are passed to a
+:term:`renderer`. This event object iself has a dictionary-like interface
+that can be used for this purpose. For example:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.events import subscriber
+ from pyramid.events import BeforeRender
+
+ @subscriber(BeforeRender)
+ def add_global(event):
+ event['mykey'] = 'foo'
+
+An object of this type is sent as an event just before a :term:`renderer` is
+invoked (but *after* the application-level renderer globals factory added via
+:class:`~pyramid.config.Configurator.set_renderer_globals_factory`, if any,
+has injected its own keys into the renderer globals dictionary).
+
+If a subscriber attempts to add a key that already exist in the renderer
+globals dictionary, a :exc:`KeyError` is raised. This limitation is enforced
+because event subscribers do not possess any relative ordering. The set of
+keys added to the renderer globals dictionary by all
+:class:`pyramid.events.BeforeRender` subscribers and renderer globals
+factories must be unique.
+
+See the API documentation for the :class:`~pyramid.events.BeforeRender` event
+interface at :class:`pyramid.interfaces.IBeforeRender`.
+
+Another (deprecated) mechanism which allows event subscribers more control
+when adding renderer global values exists in :ref:`adding_renderer_globals`.
+
+.. index::
single: renderer globals
.. _adding_renderer_globals:
-Adding Renderer Globals
------------------------
+Adding Renderer Globals (Deprecated)
+------------------------------------
.. warning:: this feature is deprecated as of Pyramid 1.1. A non-deprecated
mechanism which allows event subscribers to add renderer global values
@@ -231,46 +273,6 @@ already constructed a :term:`configurator` it can also be registered via the
config = Configurator()
config.set_renderer_globals_factory(renderer_globals_factory)
-.. index::
- single: before render event
-
-.. _beforerender_event:
-
-Using The Before Render Event
------------------------------
-
-Subscribers to the :class:`pyramid.events.BeforeRender` event may introspect
-and modify the set of :term:`renderer globals` before they are passed to a
-:term:`renderer`. This event object iself has a dictionary-like interface
-that can be used for this purpose. For example:
-
-.. code-block:: python
- :linenos:
-
- from pyramid.events import subscriber
- from pyramid.events import BeforeRender
-
- @subscriber(BeforeRender)
- def add_global(event):
- event['mykey'] = 'foo'
-
-An object of this type is sent as an event just before a :term:`renderer` is
-invoked (but *after* the application-level renderer globals factory added via
-:class:`~pyramid.config.Configurator.set_renderer_globals_factory`, if any,
-has injected its own keys into the renderer globals dictionary).
-
-If a subscriber attempts to add a key that already exist in the renderer
-globals dictionary, a :exc:`KeyError` is raised. This limitation is enforced
-because event subscribers do not possess any relative ordering. The set of
-keys added to the renderer globals dictionary by all
-:class:`pyramid.events.BeforeRender` subscribers and renderer globals
-factories must be unique.
-
-See the API documentation for the :class:`~pyramid.events.BeforeRender` event
-interface at :class:`pyramid.interfaces.IBeforeRender`.
-
-Another mechanism which allows event subscribers more control when adding
-renderer global values exists in :ref:`adding_renderer_globals`.
.. index::
single: response callback
diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst
index 5640800a2..ec42446ff 100644
--- a/docs/narr/viewconfig.rst
+++ b/docs/narr/viewconfig.rst
@@ -160,6 +160,55 @@ Non-Predicate Arguments
view callable itself returns a :term:`response` (see :ref:`the_response`),
the specified renderer implementation is never called.
+``http_cache``
+ When you supply an ``http_cache`` value to a view configuration, the
+ ``Expires`` and ``Cache-Control`` headers of a response generated by the
+ associated view callable are modified. The value for ``http_cache`` may be
+ one of the following:
+
+ - A nonzero integer. If it's a nonzero integer, it's treated as a number
+ of seconds. This number of seconds will be used to compute the
+ ``Expires`` header and the ``Cache-Control: max-age`` parameter of
+ responses to requests which call this view. For example:
+ ``http_cache=3600`` instructs the requesting browser to 'cache this
+ response for an hour, please'.
+
+ - A ``datetime.timedelta`` instance. If it's a ``datetime.timedelta``
+ instance, it will be converted into a number of seconds, and that number
+ of seconds will be used to compute the ``Expires`` header and the
+ ``Cache-Control: max-age`` parameter of responses to requests which call
+ this view. For example: ``http_cache=datetime.timedelta(days=1)``
+ instructs the requesting browser to 'cache this response for a day,
+ please'.
+
+ - Zero (``0``). If the value is zero, the ``Cache-Control`` and
+ ``Expires`` headers present in all responses from this view will be
+ composed such that client browser cache (and any intermediate caches) are
+ instructed to never cache the response.
+
+ - A two-tuple. If it's a two tuple (e.g. ``http_cache=(1,
+ {'public':True})``), the first value in the tuple may be a nonzero
+ integer or a ``datetime.timedelta`` instance; in either case this value
+ will be used as the number of seconds to cache the response. The second
+ value in the tuple must be a dictionary. The values present in the
+ dictionary will be used as input to the ``Cache-Control`` response
+ header. For example: ``http_cache=(3600, {'public':True})`` means 'cache
+ for an hour, and add ``public`` to the Cache-Control header of the
+ response'. All keys and values supported by the
+ ``webob.cachecontrol.CacheControl`` interface may be added to the
+ dictionary. Supplying ``{'public':True}`` is equivalent to calling
+ ``response.cache_control.public = True``.
+
+ Providing a non-tuple value as ``http_cache`` is equivalent to calling
+ ``response.cache_expires(value)`` within your view's body.
+
+ Providing a two-tuple value as ``http_cache`` is equivalent to calling
+ ``response.cache_expires(value[0], **value[1])`` within your view's body.
+
+ If you wish to avoid influencing, the ``Expires`` header, and instead wish
+ to only influence ``Cache-Control`` headers, pass a tuple as ``http_cache``
+ with the first element of ``None``, e.g.: ``(None, {'public':True})``.
+
``wrapper``
The :term:`view name` of a different :term:`view configuration` which will
receive the response body of this view as the ``request.wrapped_body``
@@ -400,8 +449,9 @@ configuration stanza:
.. code-block:: python
:linenos:
- config.add_view('mypackage.views.my_view', name='my_view', request_method='POST',
- context=MyResource, permission='read')
+ config.add_view('mypackage.views.my_view', name='my_view',
+ request_method='POST', context=MyResource,
+ permission='read')
All arguments to ``view_config`` may be omitted. For example:
diff --git a/docs/tutorials/wiki/background.rst b/docs/tutorials/wiki/background.rst
index e49407b70..eda2fe825 100644
--- a/docs/tutorials/wiki/background.rst
+++ b/docs/tutorials/wiki/background.rst
@@ -11,7 +11,7 @@ Python web framework experience.
To code along with this tutorial, the developer will need a UNIX
machine with development tools (Mac OS X with XCode, any Linux or BSD
-variant, etc) *or* he will need a Windows system of any kind.
+variant, etc) *or* a Windows system of any kind.
This tutorial targets :app:`Pyramid` version 1.0.
diff --git a/docs/tutorials/wiki2/background.rst b/docs/tutorials/wiki2/background.rst
index 880b5b219..79b39914b 100644
--- a/docs/tutorials/wiki2/background.rst
+++ b/docs/tutorials/wiki2/background.rst
@@ -10,7 +10,7 @@ people without any prior Python web framework experience.
To code along with this tutorial, the developer will need a UNIX
machine with development tools (Mac OS X with XCode, any Linux or BSD
-variant, etc) *or* he will need a Windows system of any kind.
+variant, etc) *or* a Windows system of any kind.
This tutorial is targeted at :app:`Pyramid` version 1.0.
diff --git a/docs/whatsnew-1.1.rst b/docs/whatsnew-1.1.rst
index d83582dee..fdf3b1c74 100644
--- a/docs/whatsnew-1.1.rst
+++ b/docs/whatsnew-1.1.rst
@@ -94,6 +94,62 @@ Default HTTP Exception View
Minor Feature Additions
-----------------------
+- New request attribute: ``json``. If the request's ``content_type`` is
+ ``application/json``, this attribute will contain the JSON-decoded
+ variant of the request body. If the request's ``content_type`` is not
+ ``application/json``, this attribute will be ``None``.
+
+- A new value ``http_cache`` can be used as a :term:`view configuration`
+ parameter.
+
+ When you supply an ``http_cache`` value to a view configuration, the
+ ``Expires`` and ``Cache-Control`` headers of a response generated by the
+ associated view callable are modified. The value for ``http_cache`` may be
+ one of the following:
+
+ - A nonzero integer. If it's a nonzero integer, it's treated as a number
+ of seconds. This number of seconds will be used to compute the
+ ``Expires`` header and the ``Cache-Control: max-age`` parameter of
+ responses to requests which call this view. For example:
+ ``http_cache=3600`` instructs the requesting browser to 'cache this
+ response for an hour, please'.
+
+ - A ``datetime.timedelta`` instance. If it's a ``datetime.timedelta``
+ instance, it will be converted into a number of seconds, and that number
+ of seconds will be used to compute the ``Expires`` header and the
+ ``Cache-Control: max-age`` parameter of responses to requests which call
+ this view. For example: ``http_cache=datetime.timedelta(days=1)``
+ instructs the requesting browser to 'cache this response for a day,
+ please'.
+
+ - Zero (``0``). If the value is zero, the ``Cache-Control`` and
+ ``Expires`` headers present in all responses from this view will be
+ composed such that client browser cache (and any intermediate caches) are
+ instructed to never cache the response.
+
+ - A two-tuple. If it's a two tuple (e.g. ``http_cache=(1,
+ {'public':True})``), the first value in the tuple may be a nonzero
+ integer or a ``datetime.timedelta`` instance; in either case this value
+ will be used as the number of seconds to cache the response. The second
+ value in the tuple must be a dictionary. The values present in the
+ dictionary will be used as input to the ``Cache-Control`` response
+ header. For example: ``http_cache=(3600, {'public':True})`` means 'cache
+ for an hour, and add ``public`` to the Cache-Control header of the
+ response'. All keys and values supported by the
+ ``webob.cachecontrol.CacheControl`` interface may be added to the
+ dictionary. Supplying ``{'public':True}`` is equivalent to calling
+ ``response.cache_control.public = True``.
+
+ Providing a non-tuple value as ``http_cache`` is equivalent to calling
+ ``response.cache_expires(value)`` within your view's body.
+
+ Providing a two-tuple value as ``http_cache`` is equivalent to calling
+ ``response.cache_expires(value[0], **value[1])`` within your view's body.
+
+ If you wish to avoid influencing, the ``Expires`` header, and instead wish
+ to only influence ``Cache-Control`` headers, pass a tuple as ``http_cache``
+ with the first element of ``None``, e.g.: ``(None, {'public':True})``.
+
- A `JSONP <http://en.wikipedia.org/wiki/JSONP>`_ renderer. See
:ref:`jsonp_renderer` for more details.
diff --git a/pyramid/config.py b/pyramid/config.py
index 732d2c521..61463eb86 100644
--- a/pyramid/config.py
+++ b/pyramid/config.py
@@ -293,6 +293,7 @@ class Configurator(object):
default_view_mapper=None,
autocommit=False,
exceptionresponse_view=default_exceptionresponse_view,
+ root_route_name=None,
):
if package is None:
package = caller_package()
@@ -302,6 +303,7 @@ class Configurator(object):
self.package = name_resolver.package
self.registry = registry
self.autocommit = autocommit
+ self.root_route_name = root_route_name
if registry is None:
registry = Registry(self.package_name)
self.registry = registry
@@ -373,7 +375,7 @@ class Configurator(object):
attr=None, renderer=None, wrapper_viewname=None,
viewname=None, accept=None, order=MAX_ORDER,
phash=DEFAULT_PHASH, decorator=None,
- mapper=None):
+ mapper=None, http_cache=None):
view = self.maybe_dotted(view)
mapper = self.maybe_dotted(mapper)
if isinstance(renderer, basestring):
@@ -398,7 +400,8 @@ class Configurator(object):
phash=phash,
package=self.package,
mapper=mapper,
- decorator=decorator)
+ decorator=decorator,
+ http_cache=http_cache)
return deriver(view)
@@ -1035,7 +1038,7 @@ class Configurator(object):
request_param=None, containment=None, attr=None,
renderer=None, wrapper=None, xhr=False, accept=None,
header=None, path_info=None, custom_predicates=(),
- context=None, decorator=None, mapper=None):
+ context=None, decorator=None, mapper=None, http_cache=None):
""" Add a :term:`view configuration` to the current
configuration state. Arguments to ``add_view`` are broken
down below into *predicate* arguments and *non-predicate*
@@ -1123,7 +1126,60 @@ class Configurator(object):
The ``renderer`` attribute is optional. If it is not
defined, the "null" renderer is assumed (no rendering is
performed and the value is passed back to the upstream
- :app:`Pyramid` machinery unmolested).
+ :app:`Pyramid` machinery unmodified).
+
+ http_cache
+
+ When you supply an ``http_cache`` value to a view configuration,
+ the ``Expires`` and ``Cache-Control`` headers of a response
+ generated by the associated view callable are modified. The value
+ for ``http_cache`` may be one of the following:
+
+ - A nonzero integer. If it's a nonzero integer, it's treated as a
+ number of seconds. This number of seconds will be used to
+ compute the ``Expires`` header and the ``Cache-Control:
+ max-age`` parameter of responses to requests which call this view.
+ For example: ``http_cache=3600`` instructs the requesting browser
+ to 'cache this response for an hour, please'.
+
+ - A ``datetime.timedelta`` instance. If it's a
+ ``datetime.timedelta`` instance, it will be converted into a
+ number of seconds, and that number of seconds will be used to
+ compute the ``Expires`` header and the ``Cache-Control:
+ max-age`` parameter of responses to requests which call this view.
+ For example: ``http_cache=datetime.timedelta(days=1)`` instructs
+ the requesting browser to 'cache this response for a day, please'.
+
+ - Zero (``0``). If the value is zero, the ``Cache-Control`` and
+ ``Expires`` headers present in all responses from this view will
+ be composed such that client browser cache (and any intermediate
+ caches) are instructed to never cache the response.
+
+ - A two-tuple. If it's a two tuple (e.g. ``http_cache=(1,
+ {'public':True})``), the first value in the tuple may be a
+ nonzero integer or a ``datetime.timedelta`` instance; in either
+ case this value will be used as the number of seconds to cache
+ the response. The second value in the tuple must be a
+ dictionary. The values present in the dictionary will be used as
+ input to the ``Cache-Control`` response header. For example:
+ ``http_cache=(3600, {'public':True})`` means 'cache for an hour,
+ and add ``public`` to the Cache-Control header of the response'.
+ All keys and values supported by the
+ ``webob.cachecontrol.CacheControl`` interface may be added to the
+ dictionary. Supplying ``{'public':True}`` is equivalent to
+ calling ``response.cache_control.public = True``.
+
+ Providing a non-tuple value as ``http_cache`` is equivalent to
+ calling ``response.cache_expires(value)`` within your view's body.
+
+ Providing a two-tuple value as ``http_cache`` is equivalent to
+ calling ``response.cache_expires(value[0], **value[1])`` within your
+ view's body.
+
+ If you wish to avoid influencing, the ``Expires`` header, and
+ instead wish to only influence ``Cache-Control`` headers, pass a
+ tuple as ``http_cache`` with the first element of ``None``, e.g.:
+ ``(None, {'public':True})``.
wrapper
@@ -1340,7 +1396,7 @@ class Configurator(object):
renderer=renderer, wrapper=wrapper, xhr=xhr, accept=accept,
header=header, path_info=path_info,
custom_predicates=custom_predicates, context=context,
- mapper = mapper,
+ mapper = mapper, http_cache = http_cache,
)
view_info = deferred_views.setdefault(route_name, [])
view_info.append(info)
@@ -1390,7 +1446,8 @@ class Configurator(object):
phash=phash,
package=self.package,
mapper=mapper,
- decorator=decorator)
+ decorator=decorator,
+ http_cache=http_cache)
derived_view = deriver(view)
registered = self.registry.adapters.registered
@@ -2918,8 +2975,9 @@ class ViewDeriver(object):
self.secured_view(
self.owrapped_view(
self.decorated_view(
- self.rendered_view(
- self.mapped_view(view))))))))
+ self.http_cached_view(
+ self.rendered_view(
+ self.mapped_view(view)))))))))
@wraps_view
def mapped_view(self, view):
@@ -2955,6 +3013,31 @@ class ViewDeriver(object):
return _owrapped_view
@wraps_view
+ def http_cached_view(self, view):
+ seconds = self.kw.get('http_cache')
+ options = {}
+
+ if seconds is None:
+ return view
+
+ if isinstance(seconds, (tuple, list)):
+ try:
+ seconds, options = seconds
+ except ValueError:
+ raise ConfigurationError(
+ 'If http_cache parameter is a tuple or list, it must be '
+ 'in the form (seconds, options); not %s' % (seconds,))
+
+ def wrapper(context, request):
+ response = view(context, request)
+ cache_expires = getattr(response, 'cache_expires', None)
+ if cache_expires is not None:
+ cache_expires(seconds, **options)
+ return response
+
+ return wrapper
+
+ @wraps_view
def secured_view(self, view):
permission = self.kw.get('permission')
if permission == '__no_permission_required__':
diff --git a/pyramid/events.py b/pyramid/events.py
index 68e400550..22cbf0cb2 100644
--- a/pyramid/events.py
+++ b/pyramid/events.py
@@ -24,7 +24,8 @@ class subscriber(object):
def mysubscriber(event):
event.request.foo = 1
- More than one event type can be passed as a construtor argument:
+ More than one event type can be passed as a construtor argument. The
+ decorated subscriber will be called for each event type.
.. code-block:: python
diff --git a/pyramid/request.py b/pyramid/request.py
index cc5137869..a3848461f 100644
--- a/pyramid/request.py
+++ b/pyramid/request.py
@@ -10,6 +10,7 @@ from pyramid.interfaces import IResponse
from pyramid.interfaces import ISessionFactory
from pyramid.interfaces import IResponseFactory
+from pyramid.compat import json
from pyramid.exceptions import ConfigurationError
from pyramid.decorator import reify
from pyramid.response import Response
@@ -489,6 +490,12 @@ class Request(BaseRequest, DeprecatedRequestMethods):
return False
return adapted is ob
+ @property
+ def json(self):
+ if self.content_type == 'application/json':
+ return json.loads(self.body, encoding=self.charset)
+
+
def route_request_iface(name, bases=()):
iface = InterfaceClass('%s_IRequest' % name, bases=bases)
# for exception view lookups
diff --git a/pyramid/tests/test_config.py b/pyramid/tests/test_config.py
index 63644da2d..3423b9784 100644
--- a/pyramid/tests/test_config.py
+++ b/pyramid/tests/test_config.py
@@ -823,6 +823,27 @@ class ConfiguratorTests(unittest.TestCase):
result = wrapper(None, None)
self.assertEqual(result, 'OK')
+ def test_add_view_with_http_cache(self):
+ import datetime
+ from pyramid.response import Response
+ response = Response('OK')
+ def view(request):
+ """ ABC """
+ return response
+ config = self._makeOne(autocommit=True)
+ config.add_view(view=view, http_cache=(86400, {'public':True}))
+ wrapper = self._getViewCallable(config)
+ self.assertFalse(wrapper is view)
+ self.assertEqual(wrapper.__doc__, view.__doc__)
+ request = testing.DummyRequest()
+ when = datetime.datetime.utcnow() + datetime.timedelta(days=1)
+ result = wrapper(None, request)
+ self.assertEqual(result, response)
+ headers = dict(response.headerlist)
+ self.assertEqual(headers['Cache-Control'], 'max-age=86400, public')
+ expires = parse_httpdate(headers['Expires'])
+ assert_similar_datetime(expires, when)
+
def test_add_view_as_instance(self):
class AView:
def __call__(self, context, request):
@@ -4199,6 +4220,101 @@ class TestViewDeriver(unittest.TestCase):
result = deriver(view)
self.assertNotEqual(result, view)
+ def test_http_cached_view_integer(self):
+ import datetime
+ from webob import Response
+ response = Response('OK')
+ def inner_view(context, request):
+ return response
+ deriver = self._makeOne(http_cache=3600)
+ result = deriver(inner_view)
+ self.assertFalse(result is inner_view)
+ self.assertEqual(inner_view.__module__, result.__module__)
+ self.assertEqual(inner_view.__doc__, result.__doc__)
+ request = self._makeRequest()
+ when = datetime.datetime.utcnow() + datetime.timedelta(hours=1)
+ result = result(None, request)
+ self.assertEqual(result, response)
+ headers = dict(result.headerlist)
+ expires = parse_httpdate(headers['Expires'])
+ assert_similar_datetime(expires, when)
+ self.assertEqual(headers['Cache-Control'], 'max-age=3600')
+
+ def test_http_cached_view_timedelta(self):
+ import datetime
+ from webob import Response
+ response = Response('OK')
+ def inner_view(context, request):
+ return response
+ deriver = self._makeOne(http_cache=datetime.timedelta(hours=1))
+ result = deriver(inner_view)
+ self.assertFalse(result is inner_view)
+ self.assertEqual(inner_view.__module__, result.__module__)
+ self.assertEqual(inner_view.__doc__, result.__doc__)
+ request = self._makeRequest()
+ when = datetime.datetime.utcnow() + datetime.timedelta(hours=1)
+ result = result(None, request)
+ self.assertEqual(result, response)
+ headers = dict(result.headerlist)
+ expires = parse_httpdate(headers['Expires'])
+ assert_similar_datetime(expires, when)
+ self.assertEqual(headers['Cache-Control'], 'max-age=3600')
+
+ def test_http_cached_view_tuple(self):
+ import datetime
+ from webob import Response
+ response = Response('OK')
+ def inner_view(context, request):
+ return response
+ deriver = self._makeOne(http_cache=(3600, {'public':True}))
+ result = deriver(inner_view)
+ self.assertFalse(result is inner_view)
+ self.assertEqual(inner_view.__module__, result.__module__)
+ self.assertEqual(inner_view.__doc__, result.__doc__)
+ request = self._makeRequest()
+ when = datetime.datetime.utcnow() + datetime.timedelta(hours=1)
+ result = result(None, request)
+ self.assertEqual(result, response)
+ headers = dict(result.headerlist)
+ expires = parse_httpdate(headers['Expires'])
+ assert_similar_datetime(expires, when)
+ self.assertEqual(headers['Cache-Control'], 'max-age=3600, public')
+
+ def test_http_cached_view_tuple_seconds_None(self):
+ from webob import Response
+ response = Response('OK')
+ def inner_view(context, request):
+ return response
+ deriver = self._makeOne(http_cache=(None, {'public':True}))
+ result = deriver(inner_view)
+ self.assertFalse(result is inner_view)
+ self.assertEqual(inner_view.__module__, result.__module__)
+ self.assertEqual(inner_view.__doc__, result.__doc__)
+ request = self._makeRequest()
+ result = result(None, request)
+ self.assertEqual(result, response)
+ headers = dict(result.headerlist)
+ self.assertFalse('Expires' in headers)
+ self.assertEqual(headers['Cache-Control'], 'public')
+
+ def test_http_cached_view_nonresponse_object_returned_downstream(self):
+ def inner_view(context, request):
+ return None
+ deriver = self._makeOne(http_cache=3600)
+ result = deriver(inner_view)
+ self.assertFalse(result is inner_view)
+ self.assertEqual(inner_view.__module__, result.__module__)
+ self.assertEqual(inner_view.__doc__, result.__doc__)
+ request = self._makeRequest()
+ result = result(None, request)
+ self.assertEqual(result, None) # doesn't blow up
+
+ def test_http_cached_view_bad_tuple(self):
+ from pyramid.exceptions import ConfigurationError
+ deriver = self._makeOne(http_cache=(None,))
+ def view(request): pass
+ self.assertRaises(ConfigurationError, deriver, view)
+
class TestDefaultViewMapper(unittest.TestCase):
def setUp(self):
self.config = testing.setUp()
@@ -5302,3 +5418,12 @@ class DummyRegistry(object):
self.adapters.append((arg, kw))
def queryAdapter(self, *arg, **kw):
return self.adaptation
+
+def parse_httpdate(s):
+ import datetime
+ return datetime.datetime.strptime(s, "%a, %d %b %Y %H:%M:%S %Z")
+
+def assert_similar_datetime(one, two):
+ for attr in ('year', 'month', 'day', 'hour', 'minute'):
+ assert(getattr(one, attr) == getattr(two, attr))
+
diff --git a/pyramid/tests/test_request.py b/pyramid/tests/test_request.py
index 76426b8a8..0c1c78721 100644
--- a/pyramid/tests/test_request.py
+++ b/pyramid/tests/test_request.py
@@ -233,6 +233,26 @@ class TestRequest(unittest.TestCase):
request.registry.registerAdapter(adapter, (Foo,), IResponse)
self.assertEqual(request.is_response(foo), True)
+ def test_json_incorrect_mimetype(self):
+ request = self._makeOne({})
+ self.assertEqual(request.json, None)
+
+ def test_json_correct_mimetype(self):
+ request = self._makeOne({})
+ request.content_type = 'application/json'
+ request.body = '{"a":1}'
+ self.assertEqual(request.json, {'a':1})
+
+ def test_json_alternate_charset(self):
+ from pyramid.compat import json
+ request = self._makeOne({})
+ request.content_type = 'application/json'
+ request.charset = 'latin-1'
+ la = unicode('La Pe\xc3\xb1a', 'utf-8')
+ body = json.dumps({'a':la}, encoding='latin-1')
+ request.body = body
+ self.assertEqual(request.json, {'a':la})
+
class TestRequestDeprecatedMethods(unittest.TestCase):
def setUp(self):
self.config = testing.setUp()
diff --git a/pyramid/view.py b/pyramid/view.py
index afa10fd0f..ea20a19c2 100644
--- a/pyramid/view.py
+++ b/pyramid/view.py
@@ -166,7 +166,7 @@ class view_config(object):
:class:`pyramid.view.view_config`: ``context``, ``permission``, ``name``,
``request_type``, ``route_name``, ``request_method``, ``request_param``,
``containment``, ``xhr``, ``accept``, ``header``, ``path_info``,
- ``custom_predicates``, ``decorator``, and ``mapper``.
+ ``custom_predicates``, ``decorator``, ``mapper``, and ``http_cache``.
The meanings of these arguments are the same as the arguments passed to
:meth:`pyramid.config.Configurator.add_view`.
@@ -181,7 +181,7 @@ class view_config(object):
containment=None, attr=None, renderer=None, wrapper=None,
xhr=False, accept=None, header=None, path_info=None,
custom_predicates=(), context=None, decorator=None,
- mapper=None):
+ mapper=None, http_cache=None):
self.name = name
self.request_type = request_type
self.context = context or for_
@@ -200,6 +200,7 @@ class view_config(object):
self.custom_predicates = custom_predicates
self.decorator = decorator
self.mapper = mapper
+ self.http_cache = http_cache
def __call__(self, wrapped):
settings = self.__dict__.copy()