Skip to content
Browse files

use a predicate list for routes, introduce the concept of deferred di…

…scriminators, change conflict resolution to deal with deferred discriminators, make predicates take a config argument, get rid of legacy make_predicates function
  • Loading branch information...
1 parent 3642803 commit 9c8ec5c7b7f12abb741f9d4467bc85f15b893420 @mcdonc mcdonc committed Aug 5, 2012
View
159 pyramid/config/__init__.py
@@ -1,4 +1,5 @@
import inspect
+import itertools
import logging
import operator
import os
@@ -71,6 +72,7 @@
from pyramid.config.util import (
action_method,
ActionInfo,
+ Deferred,
)
from pyramid.config.views import ViewsConfiguratorMixin
from pyramid.config.zca import ZCAConfiguratorMixin
@@ -354,6 +356,7 @@ def setup_registry(self,
self.add_renderer(name, renderer)
self.add_default_view_predicates()
+ self.add_default_route_predicates()
if exceptionresponse_view is not None:
exceptionresponse_view = self.maybe_dotted(exceptionresponse_view)
@@ -549,6 +552,10 @@ def action(self, discriminator, callable=None, args=(), kw=None, order=0,
introspectables = ()
if autocommit:
+ if isinstance(discriminator, Deferred):
+ # callables can depend on the side effects of resolving a
+ # deferred discriminator
+ discriminator.resolve()
if callable is not None:
callable(*args, **kw)
for introspectable in introspectables:
@@ -1060,6 +1067,11 @@ def execute_actions(self, clear=True, introspector=None):
if clear:
del self.actions[:]
+def undefer(v):
+ if isinstance(v, Deferred):
+ v = v.resolve()
+ return v
+
# this function is licensed under the ZPL (stolen from Zope)
def resolveConflicts(actions):
"""Resolve conflicting actions
@@ -1072,73 +1084,94 @@ def resolveConflicts(actions):
other conflicting actions.
"""
- # organize actions by discriminators
- unique = {}
- output = []
- for i, action in enumerate(actions):
- if not isinstance(action, dict):
+ def orderandpos((n, v)):
+ if not isinstance(v, dict):
# old-style tuple action
- action = expand_action(*action)
+ v = expand_action(*v)
+ return (v['order'] or 0, n)
+
+ sactions = sorted(enumerate(actions), key=orderandpos)
+ def orderonly((n,v)):
+ if not isinstance(v, dict):
+ # old-style tuple action
+ v = expand_action(*v)
+ return v['order'] or 0
+
+ for order, actiongroup in itertools.groupby(sactions, orderonly):
# "order" is an integer grouping. Actions in a lower order will be
- # executed before actions in a higher order. Within an order,
- # actions are executed sequentially based on original action ordering
- # ("i").
- order = action['order'] or 0
- discriminator = action['discriminator']
-
- # "ainfo" is a tuple of (order, i, action) where "order" is a
- # user-supplied grouping, "i" is an integer expressing the relative
- # position of this action in the action list being resolved, and
- # "action" is an action dictionary. The purpose of an ainfo is to
- # associate an "order" and an "i" with a particular action; "order"
- # and "i" exist for sorting purposes after conflict resolution.
- ainfo = (order, i, action)
-
- if discriminator is None:
- # The discriminator is None, so this action can never conflict.
- # We can add it directly to the result.
+ # executed before actions in a higher order. All of the actions in
+ # one grouping will be executed (its callable, if any will be called)
+ # before any of the actions in the next.
+
+ unique = {}
+ output = []
+
+ for i, action in actiongroup:
+ # Within an order, actions are executed sequentially based on
+ # original action ordering ("i").
+
+ if not isinstance(action, dict):
+ # old-style tuple action
+ action = expand_action(*action)
+
+ # "ainfo" is a tuple of (order, i, action) where "order" is a
+ # user-supplied grouping, "i" is an integer expressing the relative
+ # position of this action in the action list being resolved, and
+ # "action" is an action dictionary. The purpose of an ainfo is to
+ # associate an "order" and an "i" with a particular action; "order"
+ # and "i" exist for sorting purposes after conflict resolution.
+ ainfo = (order, i, action)
+
+ discriminator = undefer(action['discriminator'])
+ action['discriminator'] = discriminator
+
+ if discriminator is None:
+ # The discriminator is None, so this action can never conflict.
+ # We can add it directly to the result.
+ output.append(ainfo)
+ continue
+
+ L = unique.setdefault(discriminator, [])
+ L.append(ainfo)
+
+ # Check for conflicts
+ conflicts = {}
+
+ for discriminator, ainfos in unique.items():
+ # We use (includepath, order, i) as a sort key because we need to
+ # sort the actions by the paths so that the shortest path with a
+ # given prefix comes first. The "first" action is the one with the
+ # shortest include path. We break sorting ties using "order", then
+ # "i".
+ def bypath(ainfo):
+ path, order, i = ainfo[2]['includepath'], ainfo[0], ainfo[1]
+ return path, order, i
+
+ ainfos.sort(key=bypath)
+ ainfo, rest = ainfos[0], ainfos[1:]
output.append(ainfo)
- continue
-
- L = unique.setdefault(discriminator, [])
- L.append(ainfo)
-
- # Check for conflicts
- conflicts = {}
-
- for discriminator, ainfos in unique.items():
-
- # We use (includepath, order, i) as a sort key because we need to
- # sort the actions by the paths so that the shortest path with a
- # given prefix comes first. The "first" action is the one with the
- # shortest include path. We break sorting ties using "order", then
- # "i".
- def bypath(ainfo):
- path, order, i = ainfo[2]['includepath'], ainfo[0], ainfo[1]
- return path, order, i
-
- ainfos.sort(key=bypath)
- ainfo, rest = ainfos[0], ainfos[1:]
- output.append(ainfo)
- _, _, action = ainfo
- basepath, baseinfo, discriminator = (action['includepath'],
- action['info'],
- action['discriminator'])
-
- for _, _, action in rest:
- includepath = action['includepath']
- # Test whether path is a prefix of opath
- if (includepath[:len(basepath)] != basepath # not a prefix
- or includepath == basepath):
- L = conflicts.setdefault(discriminator, [baseinfo])
- L.append(action['info'])
-
- if conflicts:
- raise ConfigurationConflictError(conflicts)
-
- # sort conflict-resolved actions by (order, i) and return them
- return [ x[2] for x in sorted(output, key=operator.itemgetter(0, 1))]
+ _, _, action = ainfo
+ basepath, baseinfo, discriminator = (
+ action['includepath'],
+ action['info'],
+ action['discriminator'],
+ )
+
+ for _, _, action in rest:
+ includepath = action['includepath']
+ # Test whether path is a prefix of opath
+ if (includepath[:len(basepath)] != basepath # not a prefix
+ or includepath == basepath):
+ L = conflicts.setdefault(discriminator, [baseinfo])
+ L.append(action['info'])
+
+ if conflicts:
+ raise ConfigurationConflictError(conflicts)
+
+ # sort conflict-resolved actions by (order, i) and yield them one by one
+ for a in [x[2] for x in sorted(output, key=operator.itemgetter(0, 1))]:
+ yield a
def expand_action(discriminator, callable=None, args=(), kw=None,
includepath=(), info=None, order=0, introspectables=()):
View
50 pyramid/config/predicates.py
@@ -11,10 +11,12 @@
from pyramid.urldispatch import _compile_route
+from pyramid.util import object_description
+
from .util import as_sorted_tuple
class XHRPredicate(object):
- def __init__(self, val):
+ def __init__(self, val, config):
self.val = bool(val)
def text(self):
@@ -26,7 +28,7 @@ def __call__(self, context, request):
return bool(request.is_xhr) is self.val
class RequestMethodPredicate(object):
- def __init__(self, val):
+ def __init__(self, val, config):
self.val = as_sorted_tuple(val)
def text(self):
@@ -38,7 +40,7 @@ def __call__(self, context, request):
return request.method in self.val
class PathInfoPredicate(object):
- def __init__(self, val):
+ def __init__(self, val, config):
self.orig = val
try:
val = re.compile(val)
@@ -55,7 +57,7 @@ def __call__(self, context, request):
return self.val.match(request.upath_info) is not None
class RequestParamPredicate(object):
- def __init__(self, val):
+ def __init__(self, val, config):
name = val
v = None
if '=' in name:
@@ -80,7 +82,7 @@ def __call__(self, context, request):
class HeaderPredicate(object):
- def __init__(self, val):
+ def __init__(self, val, config):
name = val
v = None
if ':' in name:
@@ -110,7 +112,7 @@ def __call__(self, context, request):
return self.val.match(val) is not None
class AcceptPredicate(object):
- def __init__(self, val):
+ def __init__(self, val, config):
self.val = val
def text(self):
@@ -122,8 +124,8 @@ def __call__(self, context, request):
return self.val in request.accept
class ContainmentPredicate(object):
- def __init__(self, val):
- self.val = val
+ def __init__(self, val, config):
+ self.val = config.maybe_dotted(val)
def text(self):
return 'containment = %s' % (self.val,)
@@ -135,7 +137,7 @@ def __call__(self, context, request):
return find_interface(ctx, self.val) is not None
class RequestTypePredicate(object):
- def __init__(self, val):
+ def __init__(self, val, config):
self.val = val
def text(self):
@@ -147,7 +149,7 @@ def __call__(self, context, request):
return self.val.providedBy(request)
class MatchParamPredicate(object):
- def __init__(self, val):
+ def __init__(self, val, config):
if not is_nonstr_iter(val):
val = (val,)
val = sorted(val)
@@ -169,34 +171,54 @@ def __call__(self, context, request):
return True
class CustomPredicate(object):
- def __init__(self, func):
+ def __init__(self, func, config):
self.func = func
def text(self):
- return getattr(self.func, '__text__', repr(self.func))
+ return getattr(
+ self.func,
+ '__text__',
+ 'custom predicate: %s' % object_description(self.func)
+ )
def phash(self):
+ # using hash() here rather than id() is intentional: we
+ # want to allow custom predicates that are part of
+ # frameworks to be able to define custom __hash__
+ # functions for custom predicates, so that the hash output
+ # of predicate instances which are "logically the same"
+ # may compare equal.
return 'custom:%r' % hash(self.func)
def __call__(self, context, request):
return self.func(context, request)
class TraversePredicate(object):
- def __init__(self, val):
+ # Can only be used as a *route* "predicate"; it adds 'traverse' to the
+ # matchdict if it's specified in the routing args. This causes the
+ # ResourceTreeTraverser to use the resolved traverse pattern as the
+ # traversal path.
+ def __init__(self, val, config):
_, self.tgenerate = _compile_route(val)
self.val = val
def text(self):
return 'traverse matchdict pseudo-predicate'
def phash(self):
+ # This isn't actually a predicate, it's just a infodict modifier that
+ # injects ``traverse`` into the matchdict. As a result, we don't
+ # need to update the hash.
return ''
def __call__(self, context, request):
if 'traverse' in context:
return True
m = context['match']
- tvalue = self.tgenerate(m)
+ tvalue = self.tgenerate(m) # tvalue will be urlquoted string
m['traverse'] = traversal_path(tvalue)
+ # This isn't actually a predicate, it's just a infodict modifier that
+ # injects ``traverse`` into the matchdict. As a result, we just
+ # return True.
return True
View
101 pyramid/config/routes.py
@@ -1,9 +1,11 @@
import warnings
from pyramid.interfaces import (
+ IPredicateList,
IRequest,
IRouteRequest,
IRoutesMapper,
+ PHASE1_CONFIG,
PHASE2_CONFIG,
)
@@ -13,10 +15,13 @@
from pyramid.config.util import (
action_method,
- make_predicates,
as_sorted_tuple,
+ PredicateList,
+ predvalseq,
)
+from pyramid.config import predicates
+
class RoutesConfiguratorMixin(object):
@action_method
def add_route(self,
@@ -28,7 +33,7 @@ def add_route(self,
factory=None,
for_=None,
header=None,
- xhr=False,
+ xhr=None,
accept=None,
path_info=None,
request_method=None,
@@ -44,7 +49,7 @@ def add_route(self,
path=None,
pregenerator=None,
static=False,
- ):
+ **other_predicates):
""" Add a :term:`route configuration` to the current
configuration state, as well as possibly a :term:`view
configuration` to be used to specify a :term:`view callable`
@@ -254,6 +259,14 @@ def add_route(self,
:ref:`custom_route_predicates` for more information about
``info``.
+ other_predicates
+
+ Pass a key/value pair here to use a third-party predicate 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 third-party predicates.
+
View-Related Arguments
.. warning::
@@ -351,17 +364,6 @@ def add_route(self,
if request_method is not None:
request_method = as_sorted_tuple(request_method)
- ignored, predicates, ignored = make_predicates(
- xhr=xhr,
- request_method=request_method,
- path_info=path_info,
- request_param=request_param,
- header=header,
- accept=accept,
- traverse=traverse,
- custom=custom_predicates
- )
-
factory = self.maybe_dotted(factory)
if pattern is None:
pattern = path
@@ -417,8 +419,24 @@ def register_route_request_iface():
request_iface, IRouteRequest, name=name)
def register_connect():
+ pvals = other_predicates
+ pvals.update(
+ dict(
+ xhr=xhr,
+ request_method=request_method,
+ path_info=path_info,
+ request_param=request_param,
+ header=header,
+ accept=accept,
+ traverse=traverse,
+ custom=predvalseq(custom_predicates),
+ )
+ )
+
+ predlist = self.route_predlist
+ _, preds, _ = predlist.make(self, **pvals)
route = mapper.connect(
- name, pattern, factory, predicates=predicates,
+ name, pattern, factory, predicates=preds,
pregenerator=pregenerator, static=static
)
intr['object'] = route
@@ -447,6 +465,59 @@ def register_connect():
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):
+ """ Adds a route predicate factory. The view predicate can later be
+ named as a keyword argument to
+ :meth:`pyramid.config.Configurator.add_route`.
+
+ ``name`` should be the name of the predicate. It must be a valid
+ Python identifier (it will be used as a keyword argument to
+ ``add_view``).
+
+ ``factory`` should be a :term:`predicate factory`.
+ """
+ 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)
+
+ def add_default_route_predicates(self):
+ for (name, factory) in (
+ ('xhr', predicates.XHRPredicate),
+ ('request_method', predicates.RequestMethodPredicate),
+ ('path_info', predicates.PathInfoPredicate),
+ ('request_param', predicates.RequestParamPredicate),
+ ('header', predicates.HeaderPredicate),
+ ('accept', predicates.AcceptPredicate),
+ ('custom', predicates.CustomPredicate),
+ ('traverse', predicates.TraversePredicate),
+ ):
+ self.add_route_predicate(name, factory)
+
def get_routes_mapper(self):
""" Return the :term:`routes mapper` object associated with
this configurator's :term:`registry`."""
View
328 pyramid/config/util.py
@@ -1,4 +1,3 @@
-import re
import traceback
from zope.interface import implementer
@@ -12,11 +11,6 @@
from pyramid.exceptions import ConfigurationError
-from pyramid.traversal import (
- find_interface,
- traversal_path,
- )
-
from hashlib import md5
MAX_ORDER = 1 << 30
@@ -64,236 +58,6 @@ def wrapper(self, *arg, **kw):
wrapper.__docobj__ = wrapped # for sphinx
return wrapper
-def make_predicates(xhr=None, request_method=None, path_info=None,
- request_param=None, match_param=None, header=None,
- accept=None, containment=None, request_type=None,
- traverse=None, custom=()):
-
- # PREDICATES
- # ----------
- #
- # Given an argument list, a predicate list is computed.
- # Predicates are added to a predicate list in (presumed)
- # computation expense order. All predicates associated with a
- # view or route must evaluate true for the view or route to
- # "match" during a request. Elsewhere in the code, we evaluate
- # predicates using a generator expression. The fastest predicate
- # should be evaluated first, then the next fastest, and so on, as
- # if one returns false, the remainder of the predicates won't need
- # to be evaluated.
- #
- # While we compute predicates, we also compute a predicate hash
- # (aka phash) that can be used by a caller to identify identical
- # predicate lists.
- #
- # ORDERING
- # --------
- #
- # A "order" is computed for the predicate list. An order is
- # a scoring.
- #
- # Each predicate is associated with a weight value, which is a
- # multiple of 2. The weight of a predicate symbolizes the
- # relative potential "importance" of the predicate to all other
- # predicates. A larger weight indicates greater importance.
- #
- # All weights for a given predicate list are bitwise ORed together
- # to create a "score"; this score is then subtracted from
- # MAX_ORDER and divided by an integer representing the number of
- # predicates+1 to determine the order.
- #
- # The order represents the ordering in which a "multiview" ( a
- # collection of views that share the same context/request/name
- # triad but differ in other ways via predicates) will attempt to
- # call its set of views. Views with lower orders will be tried
- # first. The intent is to a) ensure that views with more
- # predicates are always evaluated before views with fewer
- # predicates and b) to ensure a stable call ordering of views that
- # share the same number of predicates. Views which do not have
- # any predicates get an order of MAX_ORDER, meaning that they will
- # be tried very last.
-
- # NB: each predicate callable constructed by this function (or examined
- # by this function, in the case of custom predicates) must leave this
- # function with a ``__text__`` attribute. The subsystem which reports
- # errors when no predicates match depends upon the existence of this
- # attribute on each predicate callable.
-
- predicates = []
- weights = []
- h = md5()
-
- if xhr:
- def xhr_predicate(context, request):
- return request.is_xhr
- xhr_predicate.__text__ = "xhr = True"
- weights.append(1 << 1)
- predicates.append(xhr_predicate)
- h.update(bytes_('xhr:%r' % bool(xhr)))
-
- if request_method is not None:
- if not is_nonstr_iter(request_method):
- request_method = (request_method,)
- request_method = sorted(request_method)
- def request_method_predicate(context, request):
- return request.method in request_method
- text = "request method = %r" % request_method
- request_method_predicate.__text__ = text
- weights.append(1 << 2)
- predicates.append(request_method_predicate)
- for m in request_method:
- h.update(bytes_('request_method:%r' % m))
-
- if path_info is not None:
- try:
- path_info_val = re.compile(path_info)
- except re.error as why:
- raise ConfigurationError(why.args[0])
- def path_info_predicate(context, request):
- return path_info_val.match(request.upath_info) is not None
- text = "path_info = %s"
- path_info_predicate.__text__ = text % path_info
- weights.append(1 << 3)
- predicates.append(path_info_predicate)
- h.update(bytes_('path_info:%r' % path_info))
-
- if request_param is not None:
- request_param_val = None
- if '=' in request_param:
- request_param, request_param_val = request_param.split('=', 1)
- if request_param_val is None:
- text = "request_param %s" % request_param
- else:
- text = "request_param %s = %s" % (request_param, request_param_val)
- def request_param_predicate(context, request):
- if request_param_val is None:
- return request_param in request.params
- return request.params.get(request_param) == request_param_val
- request_param_predicate.__text__ = text
- weights.append(1 << 4)
- predicates.append(request_param_predicate)
- h.update(
- bytes_('request_param:%r=%r' % (request_param, request_param_val)))
-
- if header is not None:
- header_name = header
- header_val = None
- if ':' in header:
- header_name, header_val = header.split(':', 1)
- try:
- header_val = re.compile(header_val)
- except re.error as why:
- raise ConfigurationError(why.args[0])
- if header_val is None:
- text = "header %s" % header_name
- else:
- text = "header %s = %s" % (header_name, header_val)
- def header_predicate(context, request):
- if header_val is None:
- return header_name in request.headers
- val = request.headers.get(header_name)
- if val is None:
- return False
- return header_val.match(val) is not None
- header_predicate.__text__ = text
- weights.append(1 << 5)
- predicates.append(header_predicate)
- h.update(bytes_('header:%r=%r' % (header_name, header_val)))
-
- if accept is not None:
- def accept_predicate(context, request):
- return accept in request.accept
- accept_predicate.__text__ = "accept = %s" % accept
- weights.append(1 << 6)
- predicates.append(accept_predicate)
- h.update(bytes_('accept:%r' % accept))
-
- if containment is not None:
- def containment_predicate(context, request):
- ctx = getattr(request, 'context', context)
- return find_interface(ctx, containment) is not None
- containment_predicate.__text__ = "containment = %s" % containment
- weights.append(1 << 7)
- predicates.append(containment_predicate)
- h.update(bytes_('containment:%r' % hash(containment)))
-
- if request_type is not None:
- def request_type_predicate(context, request):
- return request_type.providedBy(request)
- text = "request_type = %s"
- request_type_predicate.__text__ = text % request_type
- weights.append(1 << 8)
- predicates.append(request_type_predicate)
- h.update(bytes_('request_type:%r' % hash(request_type)))
-
- if match_param is not None:
- if not is_nonstr_iter(match_param):
- match_param = (match_param,)
- match_param = sorted(match_param)
- text = "match_param %s" % repr(match_param)
- reqs = [p.split('=', 1) for p in match_param]
- def match_param_predicate(context, request):
- for k, v in reqs:
- if request.matchdict.get(k) != v:
- return False
- return True
- match_param_predicate.__text__ = text
- weights.append(1 << 9)
- predicates.append(match_param_predicate)
- for p in match_param:
- h.update(bytes_('match_param:%r' % p))
-
- if custom:
- for num, predicate in enumerate(custom):
- if getattr(predicate, '__text__', None) is None:
- text = '<unknown custom predicate>'
- try:
- predicate.__text__ = text
- except AttributeError:
- # if this happens the predicate is probably a classmethod
- if hasattr(predicate, '__func__'):
- predicate.__func__.__text__ = text
- else: # pragma: no cover ; 2.5 doesn't have __func__
- predicate.im_func.__text__ = text
- predicates.append(predicate)
- # using hash() here rather than id() is intentional: we
- # want to allow custom predicates that are part of
- # frameworks to be able to define custom __hash__
- # functions for custom predicates, so that the hash output
- # of predicate instances which are "logically the same"
- # may compare equal.
- h.update(bytes_('custom%s:%r' % (num, hash(predicate))))
- weights.append(1 << 10)
-
- if traverse is not None:
- # ``traverse`` can only be used as a *route* "predicate"; it
- # adds 'traverse' to the matchdict if it's specified in the
- # routing args. This causes the ResourceTreeTraverser to use
- # the resolved traverse pattern as the traversal path.
- from pyramid.urldispatch import _compile_route
- _, tgenerate = _compile_route(traverse)
- def traverse_predicate(context, request):
- if 'traverse' in context:
- return True
- m = context['match']
- tvalue = tgenerate(m) # tvalue will be urlquoted string
- m['traverse'] = traversal_path(tvalue) # will be seq of unicode
- return True
- traverse_predicate.__text__ = 'traverse matchdict pseudo-predicate'
- # This isn't actually a predicate, it's just a infodict
- # modifier that injects ``traverse`` into the matchdict. As a
- # result, the ``traverse_predicate`` function above always
- # returns True, and we don't need to update the hash or attach
- # a weight to it
- predicates.append(traverse_predicate)
-
- score = 0
- for bit in weights:
- score = score | bit
- order = (MAX_ORDER - score) / (len(predicates) + 1)
- phash = h.hexdigest()
- return order, predicates, phash
-
def as_sorted_tuple(val):
if not is_nonstr_iter(val):
val = (val,)
@@ -334,16 +98,22 @@ def __init__(
self.last = last
def remove(self, name):
- if name in self.names:
- self.names.remove(name)
- del self.name2val[name]
- for u in self.name2after.get(name, []):
+ self.names.remove(name)
+ del self.name2val[name]
+ after = self.name2after.pop(name, [])
+ if after:
+ self.req_after.remove(name)
+ for u in after:
self.order.remove((u, name))
- for u in self.name2before.get(name, []):
+ before = self.name2before.pop(name, [])
+ if before:
+ self.req_before.remove(name)
+ for u in before:
self.order.remove((name, u))
def add(self, name, val, after=None, before=None):
- self.remove(name)
+ if name in self.names:
+ self.remove(name)
self.names.append(name)
self.name2val[name] = val
if after is None and before is None:
@@ -448,41 +218,89 @@ def __str__(self):
return msg
class PredicateList(object):
+
def __init__(self):
self.sorter = TopologicalSorter()
+ self.last_added = None
def add(self, name, factory, weighs_more_than=None, weighs_less_than=None):
+ # Predicates should be added to a predicate list in (presumed)
+ # computation expense order.
+ ## if weighs_more_than is None and weighs_less_than is None:
+ ## weighs_more_than = self.last_added or FIRST
+ ## weighs_less_than = LAST
+ self.last_added = name
self.sorter.add(name, factory, after=weighs_more_than,
before=weighs_less_than)
- def make(self, **kw):
+ def make(self, config, **kw):
+ # Given a configurator and a list of keywords, a predicate list is
+ # computed. Elsewhere in the code, we evaluate predicates using a
+ # generator expression. All predicates associated with a view or
+ # route must evaluate true for the view or route to "match" during a
+ # request. The fastest predicate should be evaluated first, then the
+ # next fastest, and so on, as if one returns false, the remainder of
+ # the predicates won't need to be evaluated.
+ #
+ # While we compute predicates, we also compute a predicate hash (aka
+ # phash) that can be used by a caller to identify identical predicate
+ # lists.
ordered = self.sorter.sorted()
phash = md5()
weights = []
- predicates = []
- for order, (name, predicate_factory) in enumerate(ordered):
+ preds = []
+ for n, (name, predicate_factory) in enumerate(ordered):
vals = kw.pop(name, None)
- if vals is None:
+ if vals is None: # XXX should this be a sentinel other than None?
continue
- if not isinstance(vals, SequenceOfPredicateValues):
+ if not isinstance(vals, predvalseq):
vals = (vals,)
for val in vals:
- predicate = predicate_factory(val)
- hashes = predicate.phash()
+ pred = predicate_factory(val, config)
+ hashes = pred.phash()
if not is_nonstr_iter(hashes):
hashes = [hashes]
for h in hashes:
phash.update(bytes_(h))
- predicate = predicate_factory(val)
- weights.append(1 << order)
- predicates.append(predicate)
+ weights.append(1 << n+1)
+ preds.append(pred)
if kw:
raise ConfigurationError('Unknown predicate values: %r' % (kw,))
+ # A "order" is computed for the predicate list. An order is
+ # a scoring.
+ #
+ # Each predicate is associated with a weight value. The weight of a
+ # predicate symbolizes the relative potential "importance" of the
+ # predicate to all other predicates. A larger weight indicates
+ # greater importance.
+ #
+ # All weights for a given predicate list are bitwise ORed together
+ # to create a "score"; this score is then subtracted from
+ # MAX_ORDER and divided by an integer representing the number of
+ # predicates+1 to determine the order.
+ #
+ # For views, the order represents the ordering in which a "multiview"
+ # ( a collection of views that share the same context/request/name
+ # triad but differ in other ways via predicates) will attempt to call
+ # its set of views. Views with lower orders will be tried first.
+ # The intent is to a) ensure that views with more predicates are
+ # always evaluated before views with fewer predicates and b) to
+ # ensure a stable call ordering of views that share the same number
+ # of predicates. Views which do not have any predicates get an order
+ # of MAX_ORDER, meaning that they will be tried very last.
score = 0
for bit in weights:
score = score | bit
- order = (MAX_ORDER - score) / (len(predicates) + 1)
- return order, predicates, phash.hexdigest()
+ order = (MAX_ORDER - score) / (len(preds) + 1)
+ return order, preds, phash.hexdigest()
-class SequenceOfPredicateValues(tuple):
+class predvalseq(tuple):
pass
+
+class Deferred(object):
+ def __init__(self, func):
+ self.func = func
+
+ def resolve(self):
+ return self.func()
+
View
208 pyramid/config/views.py
@@ -69,12 +69,13 @@
from pyramid.config import predicates
from pyramid.config.util import (
+ Deferred,
DEFAULT_PHASH,
MAX_ORDER,
action_method,
as_sorted_tuple,
PredicateList,
- SequenceOfPredicateValues,
+ predvalseq,
)
urljoin = urlparse.urljoin
@@ -635,13 +636,31 @@ def wrapper(self, *arg, **kw):
class ViewsConfiguratorMixin(object):
@viewdefaults
@action_method
- def add_view(self, view=None, name="", for_=None, permission=None,
- request_type=None, route_name=None, request_method=None,
- request_param=None, containment=None, attr=None,
- renderer=None, wrapper=None, xhr=None, accept=None,
- header=None, path_info=None, custom_predicates=(),
- context=None, decorator=None, mapper=None, http_cache=None,
- match_param=None, **other_predicates):
+ def add_view(
+ self,
+ view=None,
+ name="",
+ for_=None,
+ permission=None,
+ request_type=None,
+ route_name=None,
+ request_method=None,
+ request_param=None,
+ containment=None,
+ attr=None,
+ renderer=None,
+ wrapper=None,
+ xhr=None,
+ accept=None,
+ header=None,
+ path_info=None,
+ custom_predicates=(),
+ context=None,
+ decorator=None,
+ mapper=None,
+ http_cache=None,
+ match_param=None,
+ **other_predicates):
""" Add a :term:`view configuration` to the current
configuration state. Arguments to ``add_view`` are broken
down below into *predicate* arguments and *non-predicate*
@@ -664,24 +683,27 @@ def add_view(self, view=None, name="", for_=None, permission=None,
permission
- The name of a :term:`permission` that the user must possess
- in order to invoke the :term:`view callable`. See
- :ref:`view_security_section` for more information about view
- security and permissions. If ``permission`` is omitted, a
- *default* permission may be used for this view registration
- if one was named as the
+ A :term:`permission` that the user must possess in order to invoke
+ the :term:`view callable`. See :ref:`view_security_section` for
+ more information about view security and permissions. This is
+ often a string like ``view`` or ``edit``.
+
+ If ``permission`` is omitted, a *default* permission may be used
+ for this view registration if one was named as the
:class:`pyramid.config.Configurator` constructor's
``default_permission`` argument, or if
- :meth:`pyramid.config.Configurator.set_default_permission`
- was used prior to this view registration. Pass the string
- :data:`pyramid.security.NO_PERMISSION_REQUIRED` as the
- permission argument to explicitly indicate that the view should
- always be executable by entirely anonymous users, regardless of
- the default permission, bypassing any :term:`authorization
- policy` that may be in effect.
+ :meth:`pyramid.config.Configurator.set_default_permission` was used
+ prior to this view registration. Pass the value
+ :data:`pyramid.security.NO_PERMISSION_REQUIRED` as the permission
+ argument to explicitly indicate that the view should always be
+ executable by entirely anonymous users, regardless of the default
+ permission, bypassing any :term:`authorization policy` that may be
+ in effect.
attr
+ This knob is most useful when the view definition is a class.
+
The view machinery defaults to using the ``__call__`` method
of the :term:`view callable` (or the function itself, if the
view callable is a function) to obtain a response. The
@@ -690,8 +712,7 @@ def add_view(self, view=None, name="", for_=None, permission=None,
class, and the class has a method named ``index`` and you
wanted to use this method instead of the class' ``__call__``
method to return the response, you'd say ``attr="index"`` in the
- view configuration for the view. This is
- most useful when the view definition is a class.
+ view configuration for the view.
renderer
@@ -975,9 +996,15 @@ def add_view(self, view=None, name="", for_=None, permission=None,
Each custom predicate callable should accept two arguments:
``context`` and ``request`` and should return either
``True`` or ``False`` after doing arbitrary evaluation of
- the context and/or the request. If all callables return
- ``True``, the associated view callable will be considered
- viable for a given request.
+ the context and/or the request.
+
+ other_predicates
+
+ Pass a key/value pair here to use a third-party predicate 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 third-party predicates.
"""
view = self.maybe_dotted(view)
@@ -1034,17 +1061,26 @@ def view(context, request):
containment=containment,
request_type=request_type,
match_param=match_param,
- custom=SequenceOfPredicateValues(custom_predicates),
+ custom=predvalseq(custom_predicates),
)
)
-
- discriminator = ('view', context, name, route_name, attr,
- str(sorted(pvals.items())))
+
+ def discrim_func():
+ # We need to defer the discriminator until we know what the phash
+ # is. It can't be computed any sooner because thirdparty
+ # predicates may not yet exist when add_view is called.
+ order, preds, phash = predlist.make(self, **pvals)
+ view_intr.update({'phash':phash, 'order':order, 'predicates':preds})
+ return ('view', context, name, route_name, phash)
+
+ discriminator = Deferred(discrim_func)
+
if inspect.isclass(view) and attr:
view_desc = 'method %r of %s' % (
attr, self.object_description(view))
else:
view_desc = self.object_description(view)
+
view_intr = self.introspectable('views',
discriminator,
view_desc,
@@ -1072,8 +1108,10 @@ def view(context, request):
predlist = self.view_predlist
def register(permission=permission, renderer=renderer):
- order, preds, phash = predlist.make(**pvals)
- view_intr.update({'phash':phash})
+ # the discrim_func above is guaranteed to have been called already
+ order = view_intr['order']
+ preds = view_intr['predicates']
+ phash = view_intr['phash']
request_iface = IRequest
if route_name is not None:
request_iface = self.registry.queryUtility(IRouteRequest,
@@ -1098,21 +1136,28 @@ def register(permission=permission, renderer=renderer):
# (reg'd in phase 1)
permission = self.registry.queryUtility(IDefaultPermission)
+ # added by discrim_func above during conflict resolving
+ preds = view_intr['predicates']
+ order = view_intr['order']
+ phash = view_intr['phash']
+
# __no_permission_required__ handled by _secure_view
- deriver = ViewDeriver(registry=self.registry,
- permission=permission,
- predicates=preds,
- attr=attr,
- renderer=renderer,
- wrapper_viewname=wrapper,
- viewname=name,
- accept=accept,
- order=order,
- phash=phash,
- package=self.package,
- mapper=mapper,
- decorator=decorator,
- http_cache=http_cache)
+ deriver = ViewDeriver(
+ registry=self.registry,
+ permission=permission,
+ predicates=preds,
+ attr=attr,
+ renderer=renderer,
+ wrapper_viewname=wrapper,
+ viewname=name,
+ accept=accept,
+ order=order,
+ phash=phash,
+ package=self.package,
+ mapper=mapper,
+ decorator=decorator,
+ http_cache=http_cache,
+ )
derived_view = deriver(view)
derived_view.__discriminator__ = lambda *arg: discriminator
# __discriminator__ is used by superdynamic systems
@@ -1217,28 +1262,39 @@ def regclosure():
IMultiView, name=name)
if mapper:
- mapper_intr = self.introspectable('view mappers',
- discriminator,
- 'view mapper for %s' % view_desc,
- 'view mapper')
+ mapper_intr = self.introspectable(
+ 'view mappers',
+ discriminator,
+ 'view mapper for %s' % view_desc,
+ 'view mapper'
+ )
mapper_intr['mapper'] = mapper
mapper_intr.relate('views', discriminator)
introspectables.append(mapper_intr)
if route_name:
view_intr.relate('routes', route_name) # see add_route
if renderer is not None and renderer.name and '.' in renderer.name:
- # it's a template
- tmpl_intr = self.introspectable('templates', discriminator,
- renderer.name, 'template')
+ # the renderer is a template
+ tmpl_intr = self.introspectable(
+ 'templates',
+ discriminator,
+ renderer.name,
+ 'template'
+ )
tmpl_intr.relate('views', discriminator)
tmpl_intr['name'] = renderer.name
tmpl_intr['type'] = renderer.type
tmpl_intr['renderer'] = renderer
tmpl_intr.relate('renderer factories', renderer.type)
introspectables.append(tmpl_intr)
if permission is not None:
- perm_intr = self.introspectable('permissions', permission,
- permission, 'permission')
+ # if a permission exists, register a permission introspectable
+ perm_intr = self.introspectable(
+ 'permissions',
+ permission,
+ permission,
+ 'permission'
+ )
perm_intr['value'] = permission
perm_intr.relate('views', discriminator)
introspectables.append(perm_intr)
@@ -1255,13 +1311,14 @@ def view_predlist(self):
@action_method
def add_view_predicate(self, name, factory, weighs_more_than=None,
weighs_less_than=None):
- """ Adds a view predicate factory. The view predicate can later be
- named as a keyword argument to
- :meth:`pyramid.config.Configurator.add_view`.
+ """ Adds a view predicate factory. The associated view predicate can
+ later be named as a keyword argument to
+ :meth:`pyramid.config.Configurator.add_view` in the
+ ``other_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 keyword argument to
- ``add_view``).
+ ``add_view`` by others).
``factory`` should be a :term:`predicate factory`.
"""
@@ -1283,26 +1340,19 @@ def register():
order=PHASE1_CONFIG) # must be registered before views added
def add_default_view_predicates(self):
- self.add_view_predicate(
- 'xhr', predicates.XHRPredicate)
- self.add_view_predicate(
- 'request_method', predicates.RequestMethodPredicate)
- self.add_view_predicate(
- 'path_info', predicates.PathInfoPredicate)
- self.add_view_predicate(
- 'request_param', predicates.RequestParamPredicate)
- self.add_view_predicate(
- 'header', predicates.HeaderPredicate)
- self.add_view_predicate(
- 'accept', predicates.AcceptPredicate)
- self.add_view_predicate(
- 'containment', predicates.ContainmentPredicate)
- self.add_view_predicate(
- 'request_type', predicates.RequestTypePredicate)
- self.add_view_predicate(
- 'match_param', predicates.MatchParamPredicate)
- self.add_view_predicate(
- 'custom', predicates.CustomPredicate)
+ for (name, factory) in (
+ ('xhr', predicates.XHRPredicate),
+ ('request_method', predicates.RequestMethodPredicate),
+ ('path_info', predicates.PathInfoPredicate),
+ ('request_param', predicates.RequestParamPredicate),
+ ('header', predicates.HeaderPredicate),
+ ('accept', predicates.AcceptPredicate),
+ ('containment', predicates.ContainmentPredicate),
+ ('request_type', predicates.RequestTypePredicate),
+ ('match_param', predicates.MatchParamPredicate),
+ ('custom', predicates.CustomPredicate),
+ ):
+ self.add_view_predicate(name, factory)
def derive_view(self, view, attr=None, renderer=None):
"""
View
2 pyramid/testing.py
@@ -372,6 +372,7 @@ def registerRoute(pattern, name, factory=None):
"""
reg = get_current_registry()
config = Configurator(registry=reg)
+ config.setup_registry()
result = config.add_route(name, pattern, factory=factory)
config.commit()
return result
@@ -825,6 +826,7 @@ def setUp(registry=None, request=None, hook_zca=True, autocommit=True,
# any existing renderer factory lookup system.
config.add_renderer(name, renderer)
config.add_default_view_predicates()
+ config.add_default_route_predicates()
config.commit()
global have_zca
try:
View
41 pyramid/tests/test_config/test_init.py
@@ -1396,13 +1396,9 @@ def view2(request): pass
try:
config.commit()
except ConfigurationConflictError as why:
- c1, c2, c3, c4, c5, c6 = _conflictFunctions(why)
+ c1, c2 = _conflictFunctions(why)
self.assertEqual(c1, 'test_conflict_route_with_view')
self.assertEqual(c2, 'test_conflict_route_with_view')
- self.assertEqual(c3, 'test_conflict_route_with_view')
- self.assertEqual(c4, 'test_conflict_route_with_view')
- self.assertEqual(c5, 'test_conflict_route_with_view')
- self.assertEqual(c6, 'test_conflict_route_with_view')
else: # pragma: no cover
raise AssertionError
@@ -1693,6 +1689,7 @@ def test_it_success_tuples(self):
(3, f, (3,), {}, ('y',)),
(None, f, (5,), {}, ('y',)),
])
+ result = list(result)
self.assertEqual(
result,
[{'info': None,
@@ -1754,6 +1751,7 @@ def test_it_success_dicts(self):
expand_action(3, f, (3,), {}, ('y',)),
expand_action(None, f, (5,), {}, ('y',)),
])
+ result = list(result)
self.assertEqual(
result,
[{'info': None,
@@ -1805,32 +1803,31 @@ def test_it_success_dicts(self):
def test_it_conflict(self):
from pyramid.tests.test_config import dummyfactory as f
- self.assertRaises(
- ConfigurationConflictError,
- self._callFUT, [
- (None, f),
- (1, f, (2,), {}, ('x',), 'eek'),
- (1, f, (3,), {}, ('y',), 'ack'),
- (4, f, (4,), {}, ('y',)),
- (3, f, (3,), {}, ('y',)),
- (None, f, (5,), {}, ('y',)),
- ]
- )
+ result = self._callFUT([
+ (None, f),
+ (1, f, (2,), {}, ('x',), 'eek'), # will conflict
+ (1, f, (3,), {}, ('y',), 'ack'), # will conflict
+ (4, f, (4,), {}, ('y',)),
+ (3, f, (3,), {}, ('y',)),
+ (None, f, (5,), {}, ('y',)),
+ ])
+ self.assertRaises(ConfigurationConflictError, list, result)
def test_it_with_actions_grouped_by_order(self):
from pyramid.tests.test_config import dummyfactory as f
from pyramid.config import expand_action
result = self._callFUT([
- expand_action(None, f),
- expand_action(1, f, (1,), {}, (), 'third', 10),
+ expand_action(None, f), # X
+ expand_action(1, f, (1,), {}, (), 'third', 10), # X
expand_action(1, f, (2,), {}, ('x',), 'fourth', 10),
expand_action(1, f, (3,), {}, ('y',), 'fifth', 10),
- expand_action(2, f, (1,), {}, (), 'sixth', 10),
- expand_action(3, f, (1,), {}, (), 'seventh', 10),
- expand_action(5, f, (4,), {}, ('y',), 'eighth', 99999),
- expand_action(4, f, (3,), {}, (), 'first', 5),
+ expand_action(2, f, (1,), {}, (), 'sixth', 10), # X
+ expand_action(3, f, (1,), {}, (), 'seventh', 10), # X
+ expand_action(5, f, (4,), {}, ('y',), 'eighth', 99999), # X
+ expand_action(4, f, (3,), {}, (), 'first', 5), # X
expand_action(4, f, (5,), {}, ('y',), 'second', 5),
])
+ result = list(result)
self.assertEqual(len(result), 6)
# resolved actions should be grouped by (order, i)
self.assertEqual(
View
18 pyramid/tests/test_config/test_predicates.py
@@ -5,7 +5,7 @@
class TestXHRPredicate(unittest.TestCase):
def _makeOne(self, val):
from pyramid.config.predicates import XHRPredicate
- return XHRPredicate(val)
+ return XHRPredicate(val, None)
def test___call___true(self):
inst = self._makeOne(True)
@@ -32,7 +32,7 @@ def test_phash(self):
class TestRequestMethodPredicate(unittest.TestCase):
def _makeOne(self, val):
from pyramid.config.predicates import RequestMethodPredicate
- return RequestMethodPredicate(val)
+ return RequestMethodPredicate(val, None)
def test___call___true_single(self):
inst = self._makeOne('GET')
@@ -66,7 +66,7 @@ def test_phash(self):
class TestPathInfoPredicate(unittest.TestCase):
def _makeOne(self, val):
from pyramid.config.predicates import PathInfoPredicate
- return PathInfoPredicate(val)
+ return PathInfoPredicate(val, None)
def test_ctor_compilefail(self):
from pyramid.exceptions import ConfigurationError
@@ -97,7 +97,7 @@ def test_phash(self):
class TestRequestParamPredicate(unittest.TestCase):
def _makeOne(self, val):
from pyramid.config.predicates import RequestParamPredicate
- return RequestParamPredicate(val)
+ return RequestParamPredicate(val, None)
def test___call___true_exists(self):
inst = self._makeOne('abc')
@@ -139,7 +139,7 @@ def test_phash_withval(self):
class TestMatchParamPredicate(unittest.TestCase):
def _makeOne(self, val):
from pyramid.config.predicates import MatchParamPredicate
- return MatchParamPredicate(val)
+ return MatchParamPredicate(val, None)
def test___call___true_single(self):
inst = self._makeOne('abc=1')
@@ -174,7 +174,7 @@ def test_phash(self):
class TestCustomPredicate(unittest.TestCase):
def _makeOne(self, val):
from pyramid.config.predicates import CustomPredicate
- return CustomPredicate(val)
+ return CustomPredicate(val, None)
def test___call___true(self):
def func(context, request):
@@ -203,17 +203,17 @@ def test_text_func_has___text__(self):
def test_text_func_repr(self):
pred = predicate()
inst = self._makeOne(pred)
- self.assertTrue(inst.text(), 'predicate')
+ self.assertEqual(inst.text(), u'custom predicate: object predicate')
def test_phash(self):
pred = predicate()
inst = self._makeOne(pred)
- self.assertTrue(inst.phash(), 'custom:1')
+ self.assertEqual(inst.phash(), 'custom:1')
class TestTraversePredicate(unittest.TestCase):
def _makeOne(self, val):
from pyramid.config.predicates import TraversePredicate
- return TraversePredicate(val)
+ return TraversePredicate(val, None)
def test___call__traverse_has_remainder_already(self):
inst = self._makeOne('/1/:a/:b')
View
2 pyramid/tests/test_config/test_routes.py
@@ -158,7 +158,7 @@ def pred1(context, request): pass
def pred2(context, request): pass
config.add_route('name', 'path', custom_predicates=(pred1, pred2))
route = self._assertRoute(config, 'name', 'path', 2)
- self.assertEqual(route.predicates, [pred1, pred2])
+ self.assertEqual(len(route.predicates), 2)
def test_add_route_with_header(self):
config = self._makeOne(autocommit=True)
View
95 pyramid/tests/test_config/test_util.py
@@ -1,17 +1,40 @@
import unittest
from pyramid.compat import text_
-class Test__make_predicates(unittest.TestCase):
+class TestPredicateList(unittest.TestCase):
+
+ def _makeOne(self):
+ from pyramid.config.util import PredicateList
+ from pyramid.config import predicates
+ inst = PredicateList()
+ for name, factory in (
+ ('xhr', predicates.XHRPredicate),
+ ('request_method', predicates.RequestMethodPredicate),
+ ('path_info', predicates.PathInfoPredicate),
+ ('request_param', predicates.RequestParamPredicate),
+ ('header', predicates.HeaderPredicate),
+ ('accept', predicates.AcceptPredicate),
+ ('containment', predicates.ContainmentPredicate),
+ ('request_type', predicates.RequestTypePredicate),
+ ('match_param', predicates.MatchParamPredicate),
+ ('custom', predicates.CustomPredicate),
+ ('traverse', predicates.TraversePredicate),
+ ):
+ inst.add(name, factory)
+ return inst
+
def _callFUT(self, **kw):
- from pyramid.config.util import make_predicates
- return make_predicates(**kw)
+ inst = self._makeOne()
+ config = DummyConfigurator()
+ return inst.make(config, **kw)
def test_ordering_xhr_and_request_method_trump_only_containment(self):
order1, _, _ = self._callFUT(xhr=True, request_method='GET')
order2, _, _ = self._callFUT(containment=True)
self.assertTrue(order1 < order2)
def test_ordering_number_of_predicates(self):
+ from pyramid.config.util import predvalseq
order1, _, _ = self._callFUT(
xhr='xhr',
request_method='request_method',
@@ -22,7 +45,7 @@ def test_ordering_number_of_predicates(self):
accept='accept',
containment='containment',
request_type='request_type',
- custom=(DummyCustomPredicate(),),
+ custom=predvalseq([DummyCustomPredicate()]),
)
order2, _, _ = self._callFUT(
xhr='xhr',
@@ -34,7 +57,7 @@ def test_ordering_number_of_predicates(self):
accept='accept',
containment='containment',
request_type='request_type',
- custom=(DummyCustomPredicate(),),
+ custom=predvalseq([DummyCustomPredicate()]),
)
order3, _, _ = self._callFUT(
xhr='xhr',
@@ -114,6 +137,7 @@ def test_ordering_number_of_predicates(self):
self.assertTrue(order12 > order10)
def test_ordering_importance_of_predicates(self):
+ from pyramid.config.util import predvalseq
order1, _, _ = self._callFUT(
xhr='xhr',
)
@@ -142,7 +166,7 @@ def test_ordering_importance_of_predicates(self):
match_param='foo=bar',
)
order10, _, _ = self._callFUT(
- custom=(DummyCustomPredicate(),),
+ custom=predvalseq([DummyCustomPredicate()]),
)
self.assertTrue(order1 > order2)
self.assertTrue(order2 > order3)
@@ -155,12 +179,13 @@ def test_ordering_importance_of_predicates(self):
self.assertTrue(order9 > order10)
def test_ordering_importance_and_number(self):
+ from pyramid.config.util import predvalseq
order1, _, _ = self._callFUT(
xhr='xhr',
request_method='request_method',
)
order2, _, _ = self._callFUT(
- custom=(DummyCustomPredicate(),),
+ custom=predvalseq([DummyCustomPredicate()]),
)
self.assertTrue(order1 < order2)
@@ -170,7 +195,7 @@ def test_ordering_importance_and_number(self):
)
order2, _, _ = self._callFUT(
request_method='request_method',
- custom=(DummyCustomPredicate(),),
+ custom=predvalseq([DummyCustomPredicate()]),
)
self.assertTrue(order1 > order2)
@@ -181,7 +206,7 @@ def test_ordering_importance_and_number(self):
)
order2, _, _ = self._callFUT(
request_method='request_method',
- custom=(DummyCustomPredicate(),),
+ custom=predvalseq([DummyCustomPredicate()]),
)
self.assertTrue(order1 < order2)
@@ -193,18 +218,19 @@ def test_ordering_importance_and_number(self):
order2, _, _ = self._callFUT(
xhr='xhr',
request_method='request_method',
- custom=(DummyCustomPredicate(),),
+ custom=predvalseq([DummyCustomPredicate()]),
)
self.assertTrue(order1 > order2)
def test_different_custom_predicates_with_same_hash(self):
+ from pyramid.config.util import predvalseq
class PredicateWithHash(object):
def __hash__(self):
return 1
a = PredicateWithHash()
b = PredicateWithHash()
- _, _, a_phash = self._callFUT(custom=(a,))
- _, _, b_phash = self._callFUT(custom=(b,))
+ _, _, a_phash = self._callFUT(custom=predvalseq([a]))
+ _, _, b_phash = self._callFUT(custom=predvalseq([b]))
self.assertEqual(a_phash, b_phash)
def test_traverse_has_remainder_already(self):
@@ -244,12 +270,14 @@ def test_traverse_matches_with_highorder_chars(self):
)
def test_custom_predicates_can_affect_traversal(self):
+ from pyramid.config.util import predvalseq
def custom(info, request):
m = info['match']
m['dummy'] = 'foo'
return True
- _, predicates, _ = self._callFUT(custom=(custom,),
- traverse='/1/:dummy/:a')
+ _, predicates, _ = self._callFUT(
+ custom=predvalseq([custom]),
+ traverse='/1/:dummy/:a')
self.assertEqual(len(predicates), 2)
info = {'match':{'a':'a'}}
request = DummyRequest()
@@ -259,6 +287,7 @@ def custom(info, request):
'traverse':('1', 'foo', 'a')}})
def test_predicate_text_is_correct(self):
+ from pyramid.config.util import predvalseq
_, predicates, _ = self._callFUT(
xhr='xhr',
request_method='request_method',
@@ -268,23 +297,27 @@ def test_predicate_text_is_correct(self):
accept='accept',
containment='containment',
request_type='request_type',
- custom=(DummyCustomPredicate(),
+ custom=predvalseq(
+ [
+ DummyCustomPredicate(),
DummyCustomPredicate.classmethod_predicate,
- DummyCustomPredicate.classmethod_predicate_no_text),
+ DummyCustomPredicate.classmethod_predicate_no_text,
+ ]
+ ),
match_param='foo=bar')
- self.assertEqual(predicates[0].__text__, 'xhr = True')
- self.assertEqual(predicates[1].__text__,
- "request method = ['request_method']")
- self.assertEqual(predicates[2].__text__, 'path_info = path_info')
- self.assertEqual(predicates[3].__text__, 'request_param param')
- self.assertEqual(predicates[4].__text__, 'header header')
- self.assertEqual(predicates[5].__text__, 'accept = accept')
- self.assertEqual(predicates[6].__text__, 'containment = containment')
- self.assertEqual(predicates[7].__text__, 'request_type = request_type')
- self.assertEqual(predicates[8].__text__, "match_param ['foo=bar']")
- self.assertEqual(predicates[9].__text__, 'custom predicate')
- self.assertEqual(predicates[10].__text__, 'classmethod predicate')
- self.assertEqual(predicates[11].__text__, '<unknown custom predicate>')
+ self.assertEqual(predicates[0].text(), 'xhr = True')
+ self.assertEqual(predicates[1].text(),
+ "request_method = request_method")
+ self.assertEqual(predicates[2].text(), 'path_info = path_info')
+ self.assertEqual(predicates[3].text(), 'request_param param')
+ self.assertEqual(predicates[4].text(), 'header header')
+ self.assertEqual(predicates[5].text(), 'accept = accept')
+ self.assertEqual(predicates[6].text(), 'containment = containment')
+ self.assertEqual(predicates[7].text(), 'request_type = request_type')
+ self.assertEqual(predicates[8].text(), "match_param foo=bar")
+ self.assertEqual(predicates[9].text(), 'custom predicate')
+ self.assertEqual(predicates[10].text(), 'classmethod predicate')
+ self.assertTrue(predicates[11].text().startswith('custom predicate'))
def test_match_param_from_string(self):
_, predicates, _ = self._callFUT(match_param='foo=bar')
@@ -641,3 +674,7 @@ def __init__(self, environ=None):
self.params = {}
self.cookies = {}
+class DummyConfigurator(object):
+ def maybe_dotted(self, thing):
+ return thing
+
View
9 pyramid/tests/test_config/test_views.py
@@ -118,7 +118,8 @@ def view(request): # request-only wrapper
self.assertEqual(wrapper.__module__, view.__module__)
self.assertEqual(wrapper.__name__, view.__name__)
self.assertEqual(wrapper.__doc__, view.__doc__)
- self.assertEqual(wrapper.__discriminator__(None, None)[0], 'view')
+ self.assertEqual(wrapper.__discriminator__(None, None).resolve()[0],
+ 'view')
def test_add_view_view_callable_dottedname(self):
from pyramid.renderers import null_renderer
@@ -970,8 +971,10 @@ class Bar(object):
wrapper = self._getViewCallable(config)
self.assertTrue(IMultiView.providedBy(wrapper))
request = self._makeRequest(config)
- self.assertTrue('IFoo' in wrapper.__discriminator__(foo, request)[5])
- self.assertTrue('IBar' in wrapper.__discriminator__(bar, request)[5])
+ self.assertNotEqual(
+ wrapper.__discriminator__(foo, request),
+ wrapper.__discriminator__(bar, request),
+ )
def test_add_view_with_template_renderer(self):
from pyramid.tests import test_config
View
3 pyramid/tests/test_testing.py
@@ -253,6 +253,7 @@ def subscriber(event):
class Test_registerRoute(TestBase):
def test_registerRoute(self):
+ from pyramid.config import Configurator
from pyramid.request import Request
from pyramid.interfaces import IRoutesMapper
from pyramid.testing import registerRoute
@@ -261,6 +262,8 @@ def test_registerRoute(self):
self.assertEqual(len(mapper.routelist), 1)
request = Request.blank('/')
request.registry = self.registry
+ config = Configurator(registry=self.registry)
+ config.setup_registry()
self.assertEqual(request.route_url('home', pagename='abc'),
'http://localhost/abc')

0 comments on commit 9c8ec5c

Please sign in to comment.
Something went wrong with that request. Please try again.