diff options
| author | Chris McDonough <chrism@plope.com> | 2012-08-29 16:06:40 -0400 |
|---|---|---|
| committer | Chris McDonough <chrism@plope.com> | 2012-08-29 16:06:40 -0400 |
| commit | a7051f17a6f352193b4f4b7f1bcc635dff452001 (patch) | |
| tree | 33b3a6d6994137b6423f2f4d9ac2dc1a0e3ab717 | |
| parent | 8b55a68adb54783895a91a9e1af800a7f8f22c07 (diff) | |
| parent | a9289d95036eb23e973815e529d3db3fea235046 (diff) | |
| download | pyramid-a7051f17a6f352193b4f4b7f1bcc635dff452001.tar.gz pyramid-a7051f17a6f352193b4f4b7f1bcc635dff452001.tar.bz2 pyramid-a7051f17a6f352193b4f4b7f1bcc635dff452001.zip | |
Merge branch 'master' of github.com:Pylons/pyramid
| -rw-r--r-- | CHANGES.txt | 37 | ||||
| -rw-r--r-- | CONTRIBUTORS.txt | 4 | ||||
| -rw-r--r-- | TODO.txt | 27 | ||||
| -rw-r--r-- | docs/glossary.rst | 3 | ||||
| -rw-r--r-- | docs/narr/firstapp.rst | 10 | ||||
| -rw-r--r-- | docs/narr/helloworld.py | 15 | ||||
| -rw-r--r-- | docs/narr/hooks.rst | 122 | ||||
| -rw-r--r-- | docs/narr/views.rst | 2 | ||||
| -rw-r--r-- | pyramid/config/__init__.py | 29 | ||||
| -rw-r--r-- | pyramid/config/adapters.py | 115 | ||||
| -rw-r--r-- | pyramid/config/routes.py | 41 | ||||
| -rw-r--r-- | pyramid/config/util.py | 9 | ||||
| -rw-r--r-- | pyramid/config/views.py | 44 | ||||
| -rw-r--r-- | pyramid/events.py | 17 | ||||
| -rw-r--r-- | pyramid/mako_templating.py | 2 | ||||
| -rw-r--r-- | pyramid/session.py | 106 | ||||
| -rw-r--r-- | pyramid/tests/test_config/__init__.py | 8 | ||||
| -rw-r--r-- | pyramid/tests/test_config/test_adapters.py | 127 | ||||
| -rw-r--r-- | pyramid/tests/test_config/test_factories.py | 6 | ||||
| -rw-r--r-- | pyramid/tests/test_config/test_init.py | 26 | ||||
| -rw-r--r-- | pyramid/tests/test_config/test_init.py:TestConfigurator_add_directive | 0 | ||||
| -rw-r--r-- | pyramid/tests/test_config/test_views.py | 43 | ||||
| -rw-r--r-- | pyramid/tests/test_events.py | 21 | ||||
| -rw-r--r-- | pyramid/tests/test_mako_templating.py | 10 | ||||
| -rw-r--r-- | pyramid/tests/test_session.py | 42 |
25 files changed, 676 insertions, 190 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 62c37f374..bb726738c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -23,12 +23,33 @@ Bug Fixes https://github.com/Pylons/pyramid/issues/606 https://github.com/Pylons/pyramid/issues/607 +- In Mako Templates lookup, check for absolute uri (using mako directories) + when mixing up inheritance with asset specs. + https://github.com/Pylons/pyramid/issues/662 + +- HTTP Accept headers were not being normalized causing potentially + conflicting view registrations to go unnoticed. Two views that only + differ in the case ('text/html' vs. 'text/HTML') will now raise an error. + https://github.com/Pylons/pyramid/pull/620 + +- Configurator.add_directive now accepts arbitrary callables like partials or + objects implementing ``__call__`` which dont have ``__name__`` and + ``__doc__`` attributes. See https://github.com/Pylons/pyramid/issues/621 + and https://github.com/Pylons/pyramid/pull/647. + +- Forward-port from 1.3 branch: when registering multiple views with an + ``accept`` predicate in a Pyramid application runing under Python 3, you + might have received a ``TypeError: unorderable types: function() < + function()`` exception. + Features -------- -- Third-party custom view and route predicates can now be added for use by - view authors via ``pyramid.config.Configurator.add_view_predicate`` and - ``pyramid.config.Configurator.add_route_predicate``. So, for example, +- Third-party custom view, route, and subscriber predicates can now be added + for use by view authors via + ``pyramid.config.Configurator.add_view_predicate``, + ``pyramid.config.Configurator.add_route_predicate`` and + ``pyramid.config.Configurator.add_subscriber_predicate``. So, for example, doing this:: config.add_view_predicate('abc', my.package.ABCPredicate) @@ -38,8 +59,9 @@ Features @view_config(abc=1) - See "Adding A Third Party View or Route Predicate" in the Hooks chapter for - more information. + Similar features exist for ``add_route``, and ``add_subscriber``. See + "Adding A Third Party View, Route, or Subscriber Predicate" in the Hooks + chapter for more information. Note that changes made to support the above feature now means that only actions registered using the same "order" can conflict with one another. @@ -116,6 +138,11 @@ Features config = Configurator() config.add_permission('view') +- The ``UnencryptedCookieSessionFactoryConfig`` now accepts + ``signed_serialize`` and ``signed_deserialize`` hooks which may be used + to influence how the sessions are marshalled (by default this is done + with HMAC+pickle). + Deprecations ------------ diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index a2da7fbfd..264acf048 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -173,8 +173,12 @@ Contributors - Marin Rukavina, 2012/05/03 +- Lorenzo M. Catucci, 2012/06/08 + - Marc Abramowitz, 2012/06/13 - Jeff Cook, 2012/06/16 +- Ian Wilson, 2012/06/17 + - Roman Kozlovskyi, 2012/08/11 @@ -24,17 +24,6 @@ Nice-to-Have - Modify the urldispatch chapter examples to assume a scan rather than ``add_view``. -- Context manager for creating a new configurator (replacing - ``with_package``). E.g.:: - - with config.partial(package='bar') as c: - c.add_view(...) - - or:: - - with config.partial(introspection=False) as c: - c.add_view(..) - - Introspection: * ``default root factory`` category (prevent folks from needing to searh @@ -114,7 +103,7 @@ Future - 1.4: Remove ``chameleon_text`` / ``chameleon_zpt`` deprecated functions (render_*) -- 1.4: Remove ``pyramid.configuration.ConfigurationError`` (deprecated). +- 1.4: Remove ``pyramid.configuration`` (deprecated). - 1.4: Remove ``pyramid.paster.PyramidTemplate`` (deprecated). @@ -128,7 +117,7 @@ Future - 1.5: Remove ``pyramid.requests.DeprecatedRequestMethodsMixin``. -- 1.5: Maybe? deprecate set_request_property in favor of pointing people at +- 1.6: Maybe? deprecate set_request_property in favor of pointing people at set_request_method. - 1.6: Remove IContextURL and TraversalContextURL. @@ -152,3 +141,15 @@ Probably Bad Ideas - http://pythonguy.wordpress.com/2011/06/22/dynamic-variables-revisited/ instead of thread locals + +- Context manager for creating a new configurator (replacing + ``with_package``). E.g.:: + + with config.partial(package='bar') as c: + c.add_view(...) + + or:: + + with config.partial(introspection=False) as c: + c.add_view(..) + diff --git a/docs/glossary.rst b/docs/glossary.rst index ba3203f89..34cf1b078 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -997,6 +997,7 @@ Glossary predicate factory A callable which is used by a third party during the registration of a - route or view predicates to extend the view and route configuration + route, view, or subscriber predicates to extend the configuration system. See :ref:`registering_thirdparty_predicates` for more information. + diff --git a/docs/narr/firstapp.rst b/docs/narr/firstapp.rst index a86826d86..ccaa6e9e2 100644 --- a/docs/narr/firstapp.rst +++ b/docs/narr/firstapp.rst @@ -127,7 +127,7 @@ defined imports and function definitions, placed within the confines of an .. literalinclude:: helloworld.py :linenos: - :lines: 8-13 + :lines: 9-15 Let's break this down piece-by-piece. @@ -136,7 +136,7 @@ Configurator Construction .. literalinclude:: helloworld.py :linenos: - :lines: 8-9 + :lines: 9-10 The ``if __name__ == '__main__':`` line in the code sample above represents a Python idiom: the code inside this if clause is not invoked unless the script @@ -169,7 +169,7 @@ Adding Configuration .. ignore-next-block .. literalinclude:: helloworld.py :linenos: - :lines: 10-11 + :lines: 11-12 First line above calls the :meth:`pyramid.config.Configurator.add_route` method, which registers a :term:`route` to match any URL path that begins @@ -189,7 +189,7 @@ WSGI Application Creation .. ignore-next-block .. literalinclude:: helloworld.py :linenos: - :lines: 12 + :lines: 13 After configuring views and ending configuration, the script creates a WSGI *application* via the :meth:`pyramid.config.Configurator.make_wsgi_app` @@ -218,7 +218,7 @@ WSGI Application Serving .. ignore-next-block .. literalinclude:: helloworld.py :linenos: - :lines: 13 + :lines: 14-15 Finally, we actually serve the application to requestors by starting up a WSGI server. We happen to use the :mod:`wsgiref` ``make_server`` server diff --git a/docs/narr/helloworld.py b/docs/narr/helloworld.py index 7c26c8cdc..c01329af9 100644 --- a/docs/narr/helloworld.py +++ b/docs/narr/helloworld.py @@ -2,14 +2,15 @@ from wsgiref.simple_server import make_server from pyramid.config import Configurator from pyramid.response import Response + def hello_world(request): - return Response('Hello %(name)s!' % request.matchdict) + return Response('Hello %(name)s!' % request.matchdict) if __name__ == '__main__': - config = Configurator() - config.add_route('hello', '/hello/{name}') - config.add_view(hello_world, route_name='hello') - app = config.make_wsgi_app() - server = make_server('0.0.0.0', 8080, app) - server.serve_forever() + config = Configurator() + config.add_route('hello', '/hello/{name}') + config.add_view(hello_world, route_name='hello') + app = config.make_wsgi_app() + server = make_server('0.0.0.0', 8080, app) + server.serve_forever() diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index 2c15cd690..96fa77a07 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -1235,17 +1235,23 @@ implict and explicit tween chains used by an application. See .. _registering_thirdparty_predicates: -Adding A Third Party View or Route Predicate --------------------------------------------- +Adding A Third Party View, Route, or Subscriber Predicate +--------------------------------------------------------- .. note:: - Third-party predicates are a feature new as of Pyramid 1.4. + Third-party view, route, and subscriber predicates are a feature new as of + Pyramid 1.4. -View and route predicates used during view configuration allow you to narrow -the set of circumstances under which a view or route will match. For -example, the ``request_method`` view predicate can be used to ensure a view -callable is only invoked when the request's method is ``POST``: +.. _view_and_route_predicates: + +View and Route Predicates +~~~~~~~~~~~~~~~~~~~~~~~~~ + +View and route predicates used during configuration allow you to narrow the +set of circumstances under which a view or route will match. For example, +the ``request_method`` view predicate can be used to ensure a view callable +is only invoked when the request's method is ``POST``: .. code-block:: python @@ -1286,9 +1292,9 @@ The first argument to :meth:`pyramid.config.Configurator.add_view_predicate`, the name, is a string representing the name that is expected to be passed to ``view_config`` (or its imperative analogue ``add_view``). -The second argument is a predicate factory. A predicate factory is most -often a class with a constructor (``__init__``), a ``text`` method, a -``phash`` method and a ``__call__`` method. For example: +The second argument is a view or route predicate factory. A view or route +predicate factory is most often a class with a constructor (``__init__``), a +``text`` method, a ``phash`` method and a ``__call__`` method. For example: .. code-block:: python :linenos: @@ -1330,3 +1336,99 @@ You can use the same predicate factory as both a view predicate and as a route predicate, but you'll need to call ``add_view_predicate`` and ``add_route_predicate`` separately with the same factory. +.. _subscriber_predicates: + +Subscriber Predicates +~~~~~~~~~~~~~~~~~~~~~ + +Subscriber predicates work almost exactly like view and route predicates. +They narrow the set of circumstances in which a subscriber will be called. +There are several minor differences between a subscriber predicate and a +view/route predicate: + +- There are no default subscriber predicates. You must register one to use + one. + +- The ``__call__`` method of a subscriber predicate accepts a single + ``event`` object instead of a ``context`` and a ``request``. + +- Not every subscriber predicate can be used with every event type. Some + subscriber predicates will assume a certain event type. + +Here's an example of a subscriber predicate that can be used in conjunction +with a subscriber that subscribes to the :class:`pyramid.events.NewReqest` +event type. + +.. code-block:: python + :linenos: + + class RequestPathStartsWith(object): + def __init__(self, val, config): + self.val = val + + def text(self): + return 'path_startswith = %s' % (self.val,) + + phash = text + + def __call__(self, event): + return event.request.path.startswith(self.val) + +Once you've created a subscriber predicate, it may registered via +:meth:`pyramid.config.Configurator.add_subscriber_predicate`. For example: + +.. code-block:: python + + config.add_subscriber_predicate( + 'request_path_startswith', RequestPathStartsWith) + +Once a subscriber predicate is registered, you can use it in a call to +:meth:`pyramid.config.Configurator.add_subscriber` or to +:class:`pyramid.events.subscriber`. Here's an example of using the +previously registered ``request_path_startswith`` predicate in a call to +:meth:`~pyramid.config.Configurator.add_subscriber`: + +.. code-block:: python + :linenos: + + # define a subscriber in your code + + def yosubscriber(event): + event.request.yo = 'YO!' + + # and at configuration time + + config.add_subscriber(yosubscriber, NewRequest, + request_path_startswith='/add_yo') + +Here's the same subscriber/predicate/event-type combination used via +:class:`~pyramid.events.subscriber`. + +.. code-block:: python + :linenos: + + from pyramid.events import subscriber + + @subscriber(NewRequest, request_path_startswith='/add_yo') + def yosubscriber(event): + event.request.yo = 'YO!' + +In either of the above configurations, the ``yosubscriber`` callable will +only be called if the request path starts with ``/add_yo``. Otherwise the +event subscriber will not be called. + +Note that the ``request_path_startswith`` subscriber you defined can be used +with events that have a ``request`` attribute, but not ones that do not. So, +for example, the predicate can be used with subscribers registered for +:class:`pyramid.events.NewRequest` and :class:`pyramid.events.ContextFound` +events, but it cannot be used with subscribers registered for +:class:`pyramid.events.ApplicationCreated` because the latter type of event +has no ``request`` attribute. The point being: unlike route and view +predicates, not every type of subscriber predicate will necessarily be +applicable for use in every subscriber registration. It is not the +responsibility of the predicate author to make every predicate make sense for +every event type; it is the responsibility of the predicate consumer to use +predicates that make sense for a particular event type registration. + + + diff --git a/docs/narr/views.rst b/docs/narr/views.rst index f6ee9a8d5..9e41464a6 100644 --- a/docs/narr/views.rst +++ b/docs/narr/views.rst @@ -177,7 +177,7 @@ HTTP Exceptions ~~~~~~~~~~~~~~~ All classes documented in the :mod:`pyramid.httpexceptions` module documented -as inheriting from the :class:`pryamid.httpexceptions.HTTPException` are +as inheriting from the :class:`pyramid.httpexceptions.HTTPException` are :term:`http exception` objects. Instances of an HTTP exception object may either be *returned* or *raised* from within view code. In either case (return or raise) the instance will be used as as the view's response. diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py index 6010740ca..1dc438597 100644 --- a/pyramid/config/__init__.py +++ b/pyramid/config/__init__.py @@ -12,6 +12,8 @@ from webob.exc import WSGIHTTPException as WebobWSGIHTTPException from pyramid.interfaces import ( IDebugLogger, IExceptionResponse, + IPredicateList, + PHASE1_CONFIG, ) from pyramid.asset import resolve_asset_spec @@ -71,6 +73,7 @@ from pyramid.config.tweens import TweensConfiguratorMixin from pyramid.config.util import ( action_method, ActionInfo, + PredicateList, ) from pyramid.config.views import ViewsConfiguratorMixin from pyramid.config.zca import ZCAConfiguratorMixin @@ -489,6 +492,32 @@ class Configurator( _get_introspector, _set_introspector, _del_introspector ) + def get_predlist(self, name): + predlist = self.registry.queryUtility(IPredicateList, name=name) + if predlist is None: + predlist = PredicateList() + self.registry.registerUtility(predlist, IPredicateList, name=name) + return predlist + + def _add_predicate(self, type, name, factory, weighs_more_than=None, + weighs_less_than=None): + discriminator = ('%s predicate' % type, name) + intr = self.introspectable( + '%s predicates' % type, + discriminator, + '%s predicate named %s' % (type, name), + '%s predicate' % type) + intr['name'] = name + intr['factory'] = factory + intr['weighs_more_than'] = weighs_more_than + intr['weighs_less_than'] = weighs_less_than + def register(): + predlist = self.get_predlist(type) + predlist.add(name, factory, weighs_more_than=weighs_more_than, + weighs_less_than=weighs_less_than) + self.action(discriminator, register, introspectables=(intr,), + order=PHASE1_CONFIG) # must be registered early + @property def action_info(self): info = self.info # usually a ZCML action (ParserInfo) if self.info diff --git a/pyramid/config/adapters.py b/pyramid/config/adapters.py index 5f15f2e46..12c4de660 100644 --- a/pyramid/config/adapters.py +++ b/pyramid/config/adapters.py @@ -1,3 +1,5 @@ +from functools import update_wrapper + from zope.interface import Interface from pyramid.interfaces import ( @@ -6,40 +8,116 @@ from pyramid.interfaces import ( IResourceURL, ) -from pyramid.config.util import action_method +from pyramid.config.util import ( + action_method, + ) + class AdaptersConfiguratorMixin(object): @action_method - def add_subscriber(self, subscriber, iface=None): + def add_subscriber(self, subscriber, iface=None, **predicates): """Add an event :term:`subscriber` for the event stream - implied by the supplied ``iface`` interface. The - ``subscriber`` argument represents a callable object (or a - :term:`dotted Python name` which identifies a callable); it - will be called with a single object ``event`` whenever - :app:`Pyramid` emits an :term:`event` associated with the - ``iface``, which may be an :term:`interface` or a class or a - :term:`dotted Python name` to a global object representing an - interface or a class. Using the default ``iface`` value, - ``None`` will cause the subscriber to be registered for all - event types. See :ref:`events_chapter` for more information - about events and subscribers.""" + implied by the supplied ``iface`` interface. + + The ``subscriber`` argument represents a callable object (or a + :term:`dotted Python name` which identifies a callable); it will be + called with a single object ``event`` whenever :app:`Pyramid` emits + an :term:`event` associated with the ``iface``, which may be an + :term:`interface` or a class or a :term:`dotted Python name` to a + global object representing an interface or a class. + + Using the default ``iface`` value, ``None`` will cause the subscriber + to be registered for all event types. See :ref:`events_chapter` for + more information about events and subscribers. + + Any number of predicate keyword arguments may be passed in + ``**predicates``. Each predicate named will narrow the set of + circumstances that the subscriber will be invoked. Each named + predicate must have been registered via + :meth:`pyramid.config.Configurator.add_subscriber_predicate` before it + can be used. See :ref:`subscriber_predicates` for more information. + + .. note:: + + THe ``**predicates`` argument is new as of Pyramid 1.4. + """ dotted = self.maybe_dotted subscriber, iface = dotted(subscriber), dotted(iface) if iface is None: iface = (Interface,) if not isinstance(iface, (tuple, list)): iface = (iface,) + def register(): - self.registry.registerHandler(subscriber, iface) - intr = self.introspectable('subscribers', - id(subscriber), - self.object_description(subscriber), - 'subscriber') + predlist = self.get_predlist('subscriber') + order, preds, phash = predlist.make(self, **predicates) + intr.update({'phash':phash, 'order':order, 'predicates':preds}) + derived_subscriber = self._derive_subscriber(subscriber, preds) + self.registry.registerHandler(derived_subscriber, iface) + + intr = self.introspectable( + 'subscribers', + id(subscriber), + self.object_description(subscriber), + 'subscriber' + ) + intr['subscriber'] = subscriber intr['interfaces'] = iface + self.action(None, register, introspectables=(intr,)) return subscriber + def _derive_subscriber(self, subscriber, predicates): + if not predicates: + return subscriber + def subscriber_wrapper(*arg): + # We need to accept *arg and pass it along because zope + # subscribers are designed poorly. Notification will always call + # an associated subscriber with all of the objects involved in + # the subscription lookup, despite the fact that the event sender + # always has the option to attach those objects to the event + # object itself (and usually does). It would be much saner if the + # registry just used extra args passed to notify to do the lookup + # but only called event subscribers with the actual event object, + # or if we had been smart enough early on to always wrap + # subscribers in something that threw away the extra args, but + # c'est la vie. + if all((predicate(*arg) for predicate in predicates)): + return subscriber(*arg) + if hasattr(subscriber, '__name__'): + update_wrapper(subscriber_wrapper, subscriber) + return subscriber_wrapper + + @action_method + def add_subscriber_predicate(self, name, factory, weighs_more_than=None, + weighs_less_than=None): + """ + Adds a subscriber predicate factory. The associated subscriber + predicate can later be named as a keyword argument to + :meth:`pyramid.config.Configurator.add_subscriber` in the + ``**predicates`` anonyous keyword argument dictionary. + + ``name`` should be the name of the predicate. It must be a valid + Python identifier (it will be used as a ``**predicates`` keyword + argument to :meth:`~pyramid.config.Configurator.add_subscriber`). + + ``factory`` should be a :term:`predicate factory`. + + See :ref:`subscriber_predicates` for more information. + + .. note:: + + This method is new as of Pyramid 1.4. + """ + self._add_predicate( + 'subscriber', + name, + factory, + weighs_more_than=weighs_more_than, + weighs_less_than=weighs_less_than + ) + @action_method def add_response_adapter(self, adapter, type_or_iface): """ When an object of type (or interface) ``type_or_iface`` is @@ -203,4 +281,3 @@ class AdaptersConfiguratorMixin(object): intr['resource_iface'] = resource_iface self.action(discriminator, register, introspectables=(intr,)) - diff --git a/pyramid/config/routes.py b/pyramid/config/routes.py index 18fe39e45..1a7fdfac9 100644 --- a/pyramid/config/routes.py +++ b/pyramid/config/routes.py @@ -1,7 +1,6 @@ import warnings from pyramid.interfaces import ( - IPredicateList, IRequest, IRouteRequest, IRoutesMapper, @@ -17,7 +16,6 @@ from pyramid.urldispatch import RoutesMapper from pyramid.config.util import ( action_method, as_sorted_tuple, - PredicateList, ) import pyramid.config.predicates @@ -265,7 +263,7 @@ class RoutesConfiguratorMixin(object): registered via :meth:`pyramid.config.Configurator.add_view_predicate`. More than one key/value pair can be used at the same time. See - :ref:`registering_thirdparty_predicates` for more information about + :ref:`view_and_route_predicates` for more information about third-party predicates. This argument is new as of Pyramid 1.4. View-Related Arguments @@ -434,7 +432,7 @@ class RoutesConfiguratorMixin(object): ) ) - predlist = self.route_predlist + predlist = self.get_predlist('route') _, preds, _ = predlist.make(self, **pvals) route = mapper.connect( name, pattern, factory, predicates=preds, @@ -466,15 +464,6 @@ class RoutesConfiguratorMixin(object): attr=view_attr, ) - @property - def route_predlist(self): - predlist = self.registry.queryUtility(IPredicateList, name='route') - if predlist is None: - predlist = PredicateList() - self.registry.registerUtility(predlist, IPredicateList, - name='route') - return predlist - @action_method def add_route_predicate(self, name, factory, weighs_more_than=None, weighs_less_than=None): @@ -488,29 +477,19 @@ class RoutesConfiguratorMixin(object): ``factory`` should be a :term:`predicate factory`. - See :ref:`registering_thirdparty_predicates` for more information. + See :ref:`view_and_route_predicates` for more information. .. note:: This method is new as of Pyramid 1.4. """ - discriminator = ('route predicate', name) - intr = self.introspectable( - 'route predicates', - discriminator, - 'route predicate named %s' % name, - 'route predicate') - intr['name'] = name - intr['factory'] = factory - intr['weighs_more_than'] = weighs_more_than - intr['weighs_less_than'] = weighs_less_than - def register(): - predlist = self.route_predlist - predlist.add(name, factory, weighs_more_than=weighs_more_than, - weighs_less_than=weighs_less_than) - # must be registered before routes connected - self.action(discriminator, register, introspectables=(intr,), - order=PHASE1_CONFIG) + self._add_predicate( + 'route', + name, + factory, + weighs_more_than=weighs_more_than, + weighs_less_than=weighs_less_than + ) def add_default_route_predicates(self): p = pyramid.config.predicates diff --git a/pyramid/config/util.py b/pyramid/config/util.py index cabcab649..a4df44408 100644 --- a/pyramid/config/util.py +++ b/pyramid/config/util.py @@ -1,5 +1,7 @@ import traceback +from functools import update_wrapper + from zope.interface import implementer from pyramid.interfaces import IActionInfo @@ -55,9 +57,10 @@ def action_method(wrapped): finally: self._ainfo.pop() return result - wrapper.__name__ = wrapped.__name__ - wrapper.__doc__ = wrapped.__doc__ - wrapper.__docobj__ = wrapped # for sphinx + + if hasattr(wrapped, '__name__'): + update_wrapper(wrapper, wrapped) + wrapper.__docobj__ = wrapped return wrapper def as_sorted_tuple(val): diff --git a/pyramid/config/views.py b/pyramid/config/views.py index 1c4e20dd6..36896a17e 100644 --- a/pyramid/config/views.py +++ b/pyramid/config/views.py @@ -20,7 +20,6 @@ from pyramid.interfaces import ( IException, IExceptionViewClassifier, IMultiView, - IPredicateList, IRendererFactory, IRequest, IResponse, @@ -79,7 +78,6 @@ from pyramid.config.util import ( DEFAULT_PHASH, MAX_ORDER, action_method, - PredicateList, ) urljoin = urlparse.urljoin @@ -575,7 +573,7 @@ class MultiView(object): return else: subset.append((order, view, phash)) - subset.sort() + subset.sort(key=operator.itemgetter(0)) accepts = set(self.accepts) accepts.add(accept) self.accepts = list(accepts) # dedupe @@ -1008,7 +1006,7 @@ class ViewsConfiguratorMixin(object): registered via :meth:`pyramid.config.Configurator.add_view_predicate`. More than one key/value pair can be used at the same time. See - :ref:`registering_thirdparty_predicates` for more information about + :ref:`view_and_route_predicates` for more information about third-party predicates. This argument is new as of Pyramid 1.4. """ @@ -1047,6 +1045,9 @@ class ViewsConfiguratorMixin(object): name=renderer, package=self.package, registry = self.registry) + if accept is not None: + accept = accept.lower() + introspectables = [] pvals = predicates.copy() pvals.update( @@ -1104,7 +1105,7 @@ class ViewsConfiguratorMixin(object): ) view_intr.update(**predicates) introspectables.append(view_intr) - predlist = self.view_predlist + predlist = self.get_predlist('view') def register(permission=permission, renderer=renderer): # the discrim_func above is guaranteed to have been called already @@ -1299,14 +1300,6 @@ class ViewsConfiguratorMixin(object): introspectables.append(perm_intr) self.action(discriminator, register, introspectables=introspectables) - @property - def view_predlist(self): - predlist = self.registry.queryUtility(IPredicateList, name='view') - if predlist is None: - predlist = PredicateList() - self.registry.registerUtility(predlist, IPredicateList, name='view') - return predlist - @action_method def add_view_predicate(self, name, factory, weighs_more_than=None, weighs_less_than=None): @@ -1321,28 +1314,19 @@ class ViewsConfiguratorMixin(object): ``factory`` should be a :term:`predicate factory`. - See :ref:`registering_thirdparty_predicates` for more information. + See :ref:`view_and_route_predicates` for more information. .. note:: This method is new as of Pyramid 1.4. """ - discriminator = ('view predicate', name) - intr = self.introspectable( - 'view predicates', - discriminator, - 'view predicate named %s' % name, - 'view predicate') - intr['name'] = name - intr['factory'] = factory - intr['weighs_more_than'] = weighs_more_than - intr['weighs_less_than'] = weighs_less_than - def register(): - predlist = self.view_predlist - predlist.add(name, factory, weighs_more_than=weighs_more_than, - weighs_less_than=weighs_less_than) - self.action(discriminator, register, introspectables=(intr,), - order=PHASE1_CONFIG) # must be registered before views added + self._add_predicate( + 'view', + name, + factory, + weighs_more_than=weighs_more_than, + weighs_less_than=weighs_less_than + ) def add_default_view_predicates(self): p = pyramid.config.predicates diff --git a/pyramid/events.py b/pyramid/events.py index db274823c..836466ba2 100644 --- a/pyramid/events.py +++ b/pyramid/events.py @@ -14,9 +14,10 @@ from pyramid.interfaces import ( ) class subscriber(object): - """ Decorator activated via a :term:`scan` which treats the - function being decorated as an event subscriber for the set of - interfaces passed as ``*ifaces`` to the decorator constructor. + """ Decorator activated via a :term:`scan` which treats the function + being decorated as an event subscriber for the set of interfaces passed + as ``*ifaces`` and the set of predicate terms passed as ``**predicates`` + to the decorator constructor. For example: @@ -61,16 +62,22 @@ class subscriber(object): config = Configurator() config.scan('somepackage_containing_subscribers') + Any ``**predicate`` arguments will be passed along to + :meth:`pyramid.config.Configurator.add_subscriber`. See + :ref:`subscriber_predicates` for a description of how predicates can + narrow the set of circumstances in which a subscriber will be called. + """ venusian = venusian # for unit testing - def __init__(self, *ifaces): + def __init__(self, *ifaces, **predicates): self.ifaces = ifaces + self.predicates = predicates def register(self, scanner, name, wrapped): config = scanner.config for iface in self.ifaces or (Interface,): - config.add_subscriber(wrapped, iface) + config.add_subscriber(wrapped, iface, **self.predicates) def __call__(self, wrapped): self.venusian.attach(wrapped, self.register, category='pyramid') diff --git a/pyramid/mako_templating.py b/pyramid/mako_templating.py index 489c1f11a..2b09e8d45 100644 --- a/pyramid/mako_templating.py +++ b/pyramid/mako_templating.py @@ -43,6 +43,8 @@ class PkgResourceTemplateLookup(TemplateLookup): if relativeto is not None: relativeto = relativeto.replace('$', ':') if not(':' in uri) and (':' in relativeto): + if uri.startswith('/'): + return uri pkg, relto = relativeto.split(':') _uri = posixpath.join(posixpath.dirname(relto), uri) return '{0}:{1}'.format(pkg, _uri) diff --git a/pyramid/session.py b/pyramid/session.py index 76b2b30b1..40e21ddbc 100644 --- a/pyramid/session.py +++ b/pyramid/session.py @@ -33,6 +33,53 @@ def manage_accessed(wrapped): accessed.__doc__ = wrapped.__doc__ return accessed +def signed_serialize(data, secret): + """ Serialize any pickleable structure (``data``) and sign it + using the ``secret`` (must be a string). Return the + serialization, which includes the signature as its first 40 bytes. + The ``signed_deserialize`` method will deserialize such a value. + + This function is useful for creating signed cookies. For example: + + .. code-block:: python + + cookieval = signed_serialize({'a':1}, 'secret') + response.set_cookie('signed_cookie', cookieval) + """ + pickled = pickle.dumps(data, pickle.HIGHEST_PROTOCOL) + sig = hmac.new(bytes_(secret), pickled, sha1).hexdigest() + return sig + native_(base64.b64encode(pickled)) + +def signed_deserialize(serialized, secret, hmac=hmac): + """ Deserialize the value returned from ``signed_serialize``. If + the value cannot be deserialized for any reason, a + :exc:`ValueError` exception will be raised. + + This function is useful for deserializing a signed cookie value + created by ``signed_serialize``. For example: + + .. code-block:: python + + cookieval = request.cookies['signed_cookie'] + data = signed_deserialize(cookieval, 'secret') + """ + # hmac parameterized only for unit tests + try: + input_sig, pickled = (serialized[:40], + base64.b64decode(bytes_(serialized[40:]))) + except (binascii.Error, TypeError) as e: + # Badly formed data can make base64 die + raise ValueError('Badly formed base64 data: %s' % e) + + sig = hmac.new(bytes_(secret), pickled, sha1).hexdigest() + + # Avoid timing attacks (see + # http://seb.dbzteam.org/crypto/python-oauth-timing-hmac.pdf) + if strings_differ(sig, input_sig): + raise ValueError('Invalid signature') + + return pickle.loads(pickled) + def UnencryptedCookieSessionFactoryConfig( secret, timeout=1200, @@ -43,6 +90,8 @@ def UnencryptedCookieSessionFactoryConfig( cookie_secure=False, cookie_httponly=False, cookie_on_exception=True, + signed_serialize=signed_serialize, + signed_deserialize=signed_deserialize, ): """ Configure a :term:`session factory` which will provide unencrypted @@ -89,6 +138,15 @@ def UnencryptedCookieSessionFactoryConfig( If ``True``, set a session cookie even if an exception occurs while rendering a view. Default: ``True``. + ``signed_serialize`` + A callable which takes more or less arbitrary python data structure and + a secret and returns a signed serialization in bytes. + Default: ``signed_serialize`` (using pickle). + + ``signed_deserialize`` + A callable which takes a signed and serialized data structure in bytes + and a secret and returns the original data structure if the signature + is valid. Default: ``signed_deserialize`` (using pickle). """ @implementer(ISession) @@ -225,51 +283,3 @@ def UnencryptedCookieSessionFactoryConfig( return True return UnencryptedCookieSessionFactory - -def signed_serialize(data, secret): - """ Serialize any pickleable structure (``data``) and sign it - using the ``secret`` (must be a string). Return the - serialization, which includes the signature as its first 40 bytes. - The ``signed_deserialize`` method will deserialize such a value. - - This function is useful for creating signed cookies. For example: - - .. code-block:: python - - cookieval = signed_serialize({'a':1}, 'secret') - response.set_cookie('signed_cookie', cookieval) - """ - pickled = pickle.dumps(data, pickle.HIGHEST_PROTOCOL) - sig = hmac.new(bytes_(secret), pickled, sha1).hexdigest() - return sig + native_(base64.b64encode(pickled)) - -def signed_deserialize(serialized, secret, hmac=hmac): - """ Deserialize the value returned from ``signed_serialize``. If - the value cannot be deserialized for any reason, a - :exc:`ValueError` exception will be raised. - - This function is useful for deserializing a signed cookie value - created by ``signed_serialize``. For example: - - .. code-block:: python - - cookieval = request.cookies['signed_cookie'] - data = signed_deserialize(cookieval, 'secret') - """ - # hmac parameterized only for unit tests - try: - input_sig, pickled = (serialized[:40], - base64.b64decode(bytes_(serialized[40:]))) - except (binascii.Error, TypeError) as e: - # Badly formed data can make base64 die - raise ValueError('Badly formed base64 data: %s' % e) - - sig = hmac.new(bytes_(secret), pickled, sha1).hexdigest() - - # Avoid timing attacks (see - # http://seb.dbzteam.org/crypto/python-oauth-timing-hmac.pdf) - if strings_differ(sig, input_sig): - raise ValueError('Invalid signature') - - return pickle.loads(pickled) - diff --git a/pyramid/tests/test_config/__init__.py b/pyramid/tests/test_config/__init__.py index 5b40a8c09..81d9f4965 100644 --- a/pyramid/tests/test_config/__init__.py +++ b/pyramid/tests/test_config/__init__.py @@ -43,3 +43,11 @@ def dummy_extend(config, discrim): def dummy_extend2(config, discrim): config.action(discrim, None, config.registry) +from functools import partial +dummy_partial = partial(dummy_extend, discrim='partial') + +class DummyCallable(object): + def __call__(self, config, discrim): + config.action(discrim, None, config.package) +dummy_callable = DummyCallable() + diff --git a/pyramid/tests/test_config/test_adapters.py b/pyramid/tests/test_config/test_adapters.py index 83ea0f05b..d47e012dc 100644 --- a/pyramid/tests/test_config/test_adapters.py +++ b/pyramid/tests/test_config/test_adapters.py @@ -81,6 +81,121 @@ class AdaptersConfiguratorMixinTests(unittest.TestCase): config.registry.subscribers((event.object, IDummy), None) self.assertEqual(len(L), 1) + def test_add_subscriber_with_specific_type_and_predicates_True(self): + from zope.interface import implementer + from zope.interface import Interface + class IEvent(Interface): + pass + @implementer(IEvent) + class Event: + pass + L = [] + def subscriber(event): + L.append(event) + config = self._makeOne(autocommit=True) + predlist = config.get_predlist('subscriber') + jam_predicate = predicate_maker('jam') + jim_predicate = predicate_maker('jim') + predlist.add('jam', jam_predicate) + predlist.add('jim', jim_predicate) + config.add_subscriber(subscriber, IEvent, jam=True, jim=True) + event = Event() + event.jam = True + event.jim = True + config.registry.notify(event) + self.assertEqual(len(L), 1) + self.assertEqual(L[0], event) + config.registry.notify(object()) + self.assertEqual(len(L), 1) + + def test_add_subscriber_with_default_type_predicates_True(self): + from zope.interface import implementer + from zope.interface import Interface + class IEvent(Interface): + pass + @implementer(IEvent) + class Event: + pass + L = [] + def subscriber(event): + L.append(event) + config = self._makeOne(autocommit=True) + predlist = config.get_predlist('subscriber') + jam_predicate = predicate_maker('jam') + jim_predicate = predicate_maker('jim') + predlist.add('jam', jam_predicate) + predlist.add('jim', jim_predicate) + config.add_subscriber(subscriber, jam=True, jim=True) + event = Event() + event.jam = True + event.jim = True + config.registry.notify(event) + self.assertEqual(len(L), 1) + self.assertEqual(L[0], event) + config.registry.notify(object()) + self.assertEqual(len(L), 1) + + def test_add_subscriber_with_specific_type_and_predicates_False(self): + from zope.interface import implementer + from zope.interface import Interface + class IEvent(Interface): + pass + @implementer(IEvent) + class Event: + pass + L = [] + def subscriber(event): L.append(event) + config = self._makeOne(autocommit=True) + predlist = config.get_predlist('subscriber') + jam_predicate = predicate_maker('jam') + jim_predicate = predicate_maker('jim') + predlist.add('jam', jam_predicate) + predlist.add('jim', jim_predicate) + config.add_subscriber(subscriber, IEvent, jam=True, jim=True) + event = Event() + event.jam = True + event.jim = False + config.registry.notify(event) + self.assertEqual(len(L), 0) + + def test_add_subscriber_with_default_type_predicates_False(self): + from zope.interface import implementer + from zope.interface import Interface + class IEvent(Interface): + pass + @implementer(IEvent) + class Event: + pass + L = [] + def subscriber(event): L.append(event) + config = self._makeOne(autocommit=True) + predlist = config.get_predlist('subscriber') + jam_predicate = predicate_maker('jam') + jim_predicate = predicate_maker('jim') + predlist.add('jam', jam_predicate) + predlist.add('jim', jim_predicate) + config.add_subscriber(subscriber, jam=True, jim=True) + event = Event() + event.jam = False + event.jim = True + config.registry.notify(event) + self.assertEqual(len(L), 0) + + def test_add_subscriber_predicate(self): + config = self._makeOne() + L = [] + def add_predicate(type, name, factory, weighs_less_than=None, + weighs_more_than=None): + self.assertEqual(type, 'subscriber') + self.assertEqual(name, 'name') + self.assertEqual(factory, 'factory') + self.assertEqual(weighs_more_than, 1) + self.assertEqual(weighs_less_than, 2) + L.append(1) + config._add_predicate = add_predicate + config.add_subscriber_predicate('name', 'factory', 1, 2) + self.assertTrue(L) + def test_add_response_adapter(self): from pyramid.interfaces import IResponse config = self._makeOne(autocommit=True) @@ -228,4 +343,14 @@ class DummyResourceURL(object): self.resource = resource self.request = request - +def predicate_maker(name): + class Predicate(object): + def __init__(self, val, config): + self.val = val + def phash(self): + return 'phash' + text = phash + def __call__(self, event): + return getattr(event, name, None) == self.val + return Predicate + diff --git a/pyramid/tests/test_config/test_factories.py b/pyramid/tests/test_config/test_factories.py index a95326772..38e80416f 100644 --- a/pyramid/tests/test_config/test_factories.py +++ b/pyramid/tests/test_config/test_factories.py @@ -125,9 +125,9 @@ class TestFactoriesMixin(unittest.TestCase): request = DummyRequest(config.registry) event = Event() config.registry.notify(event) - exts = event.request.extensions - self.assertTrue('foo' in exts[0]) - self.assertTrue('bar' in exts[1]) + exts = list(sorted(event.request.extensions)) + self.assertEqual('bar', exts[0]) + self.assertEqual('foo', exts[1]) def test_set_request_method_subscriber(self): from zope.interface import implementer diff --git a/pyramid/tests/test_config/test_init.py b/pyramid/tests/test_config/test_init.py index abe22400b..f39906dd9 100644 --- a/pyramid/tests/test_config/test_init.py +++ b/pyramid/tests/test_config/test_init.py @@ -1421,6 +1421,32 @@ class TestConfigurator_add_directive(unittest.TestCase): self.assertEqual(action['callable'], None) self.assertEqual(action['args'], test_config) + def test_add_directive_with_partial(self): + from pyramid.tests import test_config + config = self.config + config.add_directive( + 'dummy_partial', 'pyramid.tests.test_config.dummy_partial') + self.assertTrue(hasattr(config, 'dummy_partial')) + config.dummy_partial() + after = config.action_state + action = after.actions[-1] + self.assertEqual(action['discriminator'], 'partial') + self.assertEqual(action['callable'], None) + self.assertEqual(action['args'], test_config) + + def test_add_directive_with_custom_callable(self): + from pyramid.tests import test_config + config = self.config + config.add_directive( + 'dummy_callable', 'pyramid.tests.test_config.dummy_callable') + self.assertTrue(hasattr(config, 'dummy_callable')) + config.dummy_callable('discrim') + after = config.action_state + action = after.actions[-1] + self.assertEqual(action['discriminator'], 'discrim') + self.assertEqual(action['callable'], None) + self.assertEqual(action['args'], test_config) + def test_extend_with_python_callable(self): from pyramid.tests import test_config config = self.config diff --git a/pyramid/tests/test_config/test_init.py:TestConfigurator_add_directive b/pyramid/tests/test_config/test_init.py:TestConfigurator_add_directive new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/pyramid/tests/test_config/test_init.py:TestConfigurator_add_directive diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py index 38f60d79b..575d8c738 100644 --- a/pyramid/tests/test_config/test_views.py +++ b/pyramid/tests/test_config/test_views.py @@ -646,6 +646,24 @@ class TestViewsConfigurationMixin(unittest.TestCase): request.accept = DummyAccept('text/html', 'text/html') self.assertEqual(wrapper(None, request), 'OK2') + def test_add_view_mixed_case_replaces_existing_view(self): + from pyramid.renderers import null_renderer + def view(context, request): return 'OK' + def view2(context, request): return 'OK2' + def view3(context, request): return 'OK3' + config = self._makeOne(autocommit=True) + config.add_view(view=view, renderer=null_renderer) + config.add_view(view=view2, accept='text/html', renderer=null_renderer) + config.add_view(view=view3, accept='text/HTML', renderer=null_renderer) + wrapper = self._getViewCallable(config) + self.assertTrue(IMultiView.providedBy(wrapper)) + self.assertEqual(len(wrapper.media_views.items()),1) + self.assertFalse('text/HTML' in wrapper.media_views) + self.assertEqual(wrapper(None, None), 'OK') + request = DummyRequest() + request.accept = DummyAccept('text/html', 'text/html') + self.assertEqual(wrapper(None, request), 'OK3') + def test_add_views_with_accept_multiview_replaces_existing(self): from pyramid.renderers import null_renderer def view(context, request): return 'OK' @@ -2122,11 +2140,28 @@ class TestMultiView(unittest.TestCase): def test_add_with_phash_override_accept(self): mv = self._makeOne() - mv.add('view2', 100, accept='text/html', phash='abc') - mv.add('view3', 100, accept='text/html', phash='abc') - mv.add('view4', 99, accept='text/html', phash='def') + def view1(): pass + def view2(): pass + def view3(): pass + mv.add(view1, 100, accept='text/html', phash='abc') + mv.add(view2, 100, accept='text/html', phash='abc') + mv.add(view3, 99, accept='text/html', phash='def') + self.assertEqual(mv.media_views['text/html'], + [(99, view3, 'def'), (100, view2, 'abc')]) + + def test_add_with_phash_override_accept2(self): + mv = self._makeOne() + def view1(): pass + def view2(): pass + def view3(): pass + mv.add(view1, 100, accept='text/html', phash='abc') + mv.add(view2, 100, accept='text/html', phash='def') + mv.add(view3, 99, accept='text/html', phash='ghi') self.assertEqual(mv.media_views['text/html'], - [(99, 'view4', 'def'), (100, 'view3', 'abc')]) + [(99, view3, 'ghi'), + (100, view1, 'abc'), + (100, view2, 'def')] + ) def test_multiple_with_functions_as_views(self): # this failed on py3 at one point, because functions aren't orderable diff --git a/pyramid/tests/test_events.py b/pyramid/tests/test_events.py index 3e9c959d9..2c72c07e8 100644 --- a/pyramid/tests/test_events.py +++ b/pyramid/tests/test_events.py @@ -131,9 +131,9 @@ class TestSubscriber(unittest.TestCase): def tearDown(self): testing.tearDown() - def _makeOne(self, *ifaces): + def _makeOne(self, *ifaces, **predicates): from pyramid.events import subscriber - return subscriber(*ifaces) + return subscriber(*ifaces, **predicates) def test_register_single(self): from zope.interface import Interface @@ -190,6 +190,16 @@ class TestSubscriber(unittest.TestCase): self.assertEqual(dummy_venusian.attached, [(foo, dec.register, 'pyramid')]) + def test_regsister_with_predicates(self): + from zope.interface import Interface + dec = self._makeOne(a=1) + def foo(): pass + config = DummyConfigurator() + scanner = Dummy() + scanner.config = config + dec.register(scanner, None, foo) + self.assertEqual(config.subscribed, [(foo, Interface, {'a':1})]) + class TestBeforeRender(unittest.TestCase): def _makeOne(self, system, val=None): from pyramid.events import BeforeRender @@ -264,8 +274,11 @@ class DummyConfigurator(object): def __init__(self): self.subscribed = [] - def add_subscriber(self, wrapped, ifaces): - self.subscribed.append((wrapped, ifaces)) + def add_subscriber(self, wrapped, ifaces, **predicates): + if not predicates: + self.subscribed.append((wrapped, ifaces)) + else: + self.subscribed.append((wrapped, ifaces, predicates)) class DummyRegistry(object): pass diff --git a/pyramid/tests/test_mako_templating.py b/pyramid/tests/test_mako_templating.py index aced6c586..97b2c679b 100644 --- a/pyramid/tests/test_mako_templating.py +++ b/pyramid/tests/test_mako_templating.py @@ -499,6 +499,16 @@ class TestPkgResourceTemplateLookup(unittest.TestCase): result = inst.adjust_uri('b', '../a') self.assertEqual(result, '../b') + def test_adjust_uri_not_asset_spec_abs_with_relativeto_asset_spec(self): + inst = self._makeOne() + result = inst.adjust_uri('/c', 'a:b') + self.assertEqual(result, '/c') + + def test_adjust_uri_asset_spec_with_relativeto_not_asset_spec_abs(self): + inst = self._makeOne() + result = inst.adjust_uri('a:b', '/c') + self.assertEqual(result, 'a:b') + def test_get_template_not_asset_spec(self): fixturedir = self.get_fixturedir() inst = self._makeOne(directories=[fixturedir]) diff --git a/pyramid/tests/test_session.py b/pyramid/tests/test_session.py index 6d75c7950..5143b7a95 100644 --- a/pyramid/tests/test_session.py +++ b/pyramid/tests/test_session.py @@ -205,6 +205,48 @@ class TestUnencryptedCookieSession(unittest.TestCase): self.assertTrue(token) self.assertTrue('_csrft_' in session) + def test_serialize_option(self): + from pyramid.response import Response + secret = 'secret' + request = testing.DummyRequest() + session = self._makeOne(request, + signed_serialize=dummy_signed_serialize) + session['key'] = 'value' + response = Response() + self.assertEqual(session._set_cookie(response), True) + cookie = response.headerlist[-1][1] + expected_cookieval = dummy_signed_serialize( + (session.accessed, session.created, {'key': 'value'}), secret) + response = Response() + response.set_cookie('session', expected_cookieval) + expected_cookie = response.headerlist[-1][1] + self.assertEqual(cookie, expected_cookie) + + def test_deserialize_option(self): + import time + secret = 'secret' + request = testing.DummyRequest() + accessed = time.time() + state = {'key': 'value'} + cookieval = dummy_signed_serialize((accessed, accessed, state), secret) + request.cookies['session'] = cookieval + session = self._makeOne(request, + signed_deserialize=dummy_signed_deserialize) + self.assertEqual(dict(session), state) + +def dummy_signed_serialize(data, secret): + import base64 + from pyramid.compat import pickle, bytes_ + pickled = pickle.dumps(data) + return base64.b64encode(bytes_(secret)) + base64.b64encode(pickled) + +def dummy_signed_deserialize(serialized, secret): + import base64 + from pyramid.compat import pickle, bytes_ + serialized_data = base64.b64decode( + serialized[len(base64.b64encode(bytes_(secret))):]) + return pickle.loads(serialized_data) + class Test_manage_accessed(unittest.TestCase): def _makeOne(self, wrapped): from pyramid.session import manage_accessed |
