summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2012-08-29 16:06:40 -0400
committerChris McDonough <chrism@plope.com>2012-08-29 16:06:40 -0400
commita7051f17a6f352193b4f4b7f1bcc635dff452001 (patch)
tree33b3a6d6994137b6423f2f4d9ac2dc1a0e3ab717
parent8b55a68adb54783895a91a9e1af800a7f8f22c07 (diff)
parenta9289d95036eb23e973815e529d3db3fea235046 (diff)
downloadpyramid-a7051f17a6f352193b4f4b7f1bcc635dff452001.tar.gz
pyramid-a7051f17a6f352193b4f4b7f1bcc635dff452001.tar.bz2
pyramid-a7051f17a6f352193b4f4b7f1bcc635dff452001.zip
Merge branch 'master' of github.com:Pylons/pyramid
-rw-r--r--CHANGES.txt37
-rw-r--r--CONTRIBUTORS.txt4
-rw-r--r--TODO.txt27
-rw-r--r--docs/glossary.rst3
-rw-r--r--docs/narr/firstapp.rst10
-rw-r--r--docs/narr/helloworld.py15
-rw-r--r--docs/narr/hooks.rst122
-rw-r--r--docs/narr/views.rst2
-rw-r--r--pyramid/config/__init__.py29
-rw-r--r--pyramid/config/adapters.py115
-rw-r--r--pyramid/config/routes.py41
-rw-r--r--pyramid/config/util.py9
-rw-r--r--pyramid/config/views.py44
-rw-r--r--pyramid/events.py17
-rw-r--r--pyramid/mako_templating.py2
-rw-r--r--pyramid/session.py106
-rw-r--r--pyramid/tests/test_config/__init__.py8
-rw-r--r--pyramid/tests/test_config/test_adapters.py127
-rw-r--r--pyramid/tests/test_config/test_factories.py6
-rw-r--r--pyramid/tests/test_config/test_init.py26
-rw-r--r--pyramid/tests/test_config/test_init.py:TestConfigurator_add_directive0
-rw-r--r--pyramid/tests/test_config/test_views.py43
-rw-r--r--pyramid/tests/test_events.py21
-rw-r--r--pyramid/tests/test_mako_templating.py10
-rw-r--r--pyramid/tests/test_session.py42
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
diff --git a/TODO.txt b/TODO.txt
index 4b4f48499..d1209f325 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -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