from hashlib import md5
import inspect
from pyramid.compat import (
bytes_,
getargspec,
is_nonstr_iter,
)
from pyramid.compat import im_func
from pyramid.exceptions import ConfigurationError
from pyramid.registry import predvalseq
from pyramid.util import (
TopologicalSorter,
action_method,
ActionInfo,
)
action_method = action_method # support bw compat imports
ActionInfo = ActionInfo # support bw compat imports
MAX_ORDER = 1 << 30
DEFAULT_PHASH = md5().hexdigest()
def as_sorted_tuple(val):
if not is_nonstr_iter(val):
val = (val,)
val = tuple(sorted(val))
return val
[docs]class not_(object):
"""
You can invert the meaning of any predicate value by wrapping it in a call
to :class:`pyramid.config.not_`.
.. code-block:: python
:linenos:
from pyramid.config import not_
config.add_view(
'mypackage.views.my_view',
route_name='ok',
request_method=not_('POST')
)
The above example will ensure that the view is called if the request method
is *not* ``POST``, at least if no other view is more specific.
This technique of wrapping a predicate value in ``not_`` can be used
anywhere predicate values are accepted:
- :meth:`pyramid.config.Configurator.add_view`
- :meth:`pyramid.config.Configurator.add_route`
- :meth:`pyramid.config.Configurator.add_subscriber`
- :meth:`pyramid.view.view_config`
- :meth:`pyramid.events.subscriber`
.. versionadded:: 1.5
"""
def __init__(self, value):
self.value = value
class Notted(object):
def __init__(self, predicate):
self.predicate = predicate
def _notted_text(self, val):
# if the underlying predicate doesnt return a value, it's not really
# a predicate, it's just something pretending to be a predicate,
# so dont update the hash
if val:
val = '!' + val
return val
def text(self):
return self._notted_text(self.predicate.text())
def phash(self):
return self._notted_text(self.predicate.phash())
def __call__(self, context, request):
result = self.predicate(context, request)
phash = self.phash()
if phash:
result = not result
return result
# under = after
# over = before
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, 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 = []
preds = []
for n, (name, predicate_factory) in enumerate(ordered):
vals = kw.pop(name, None)
if vals is None: # XXX should this be a sentinel other than None?
continue
if not isinstance(vals, predvalseq):
vals = (vals,)
for val in vals:
realval = val
notted = False
if isinstance(val, not_):
realval = val.value
notted = True
pred = predicate_factory(realval, config)
if notted:
pred = Notted(pred)
hashes = pred.phash()
if not is_nonstr_iter(hashes):
hashes = [hashes]
for h in hashes:
phash.update(bytes_(h))
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(preds) + 1)
return order, preds, phash.hexdigest()
def takes_one_arg(callee, attr=None, argname=None):
ismethod = False
if attr is None:
attr = '__call__'
if inspect.isroutine(callee):
fn = callee
elif inspect.isclass(callee):
try:
fn = callee.__init__
except AttributeError:
return False
ismethod = hasattr(fn, '__call__')
else:
try:
fn = getattr(callee, attr)
except AttributeError:
return False
try:
argspec = getargspec(fn)
except TypeError:
return False
args = argspec[0]
if hasattr(fn, im_func) or ismethod:
# it's an instance method (or unbound method on py2)
if not args:
return False
args = args[1:]
if not args:
return False
if len(args) == 1:
return True
if argname:
defaults = argspec[3]
if defaults is None:
defaults = ()
if args[0] == argname:
if len(args) - len(defaults) == 1:
return True
return False