Michael Merickel
2017-07-08 417320a5391e7f3b551f8f10e28aae9cd00ae27e
commit | author | age
d8d3a9 1 from hashlib import md5
5bf23f 2
ee117e 3 from pyramid.compat import (
CM 4     bytes_,
9c120b 5     is_nonstr_iter
ee117e 6     )
CM 7
5bf23f 8 from pyramid.exceptions import ConfigurationError
d98612 9 from pyramid.registry import predvalseq
CM 10
d8d3a9 11 from pyramid.util import (
CM 12     TopologicalSorter,
13     action_method,
14     ActionInfo,
417320 15     takes_one_arg,
d8d3a9 16     )
417320 17
MM 18 from pyramid.viewderivers import (
19     MAX_ORDER,
20     DEFAULT_PHASH,
21 )
d8d3a9 22
CM 23 action_method = action_method # support bw compat imports
24 ActionInfo = ActionInfo # support bw compat imports
5bf23f 25
417320 26 MAX_ORDER = MAX_ORDER  # support bw compat imports
MM 27 DEFAULT_PHASH = DEFAULT_PHASH  # support bw compat imports
28
29 takes_one_arg = takes_one_arg  # support bw compat imports
561591 30
32333e 31 class not_(object):
49f7c3 32     """
CM 33
34     You can invert the meaning of any predicate value by wrapping it in a call
35     to :class:`pyramid.config.not_`.
36
37     .. code-block:: python
38        :linenos:
39
40        from pyramid.config import not_
41
42        config.add_view(
43            'mypackage.views.my_view',
44            route_name='ok', 
45            request_method=not_('POST')
46            )
47
48     The above example will ensure that the view is called if the request method
49     is *not* ``POST``, at least if no other view is more specific.
50
51     This technique of wrapping a predicate value in ``not_`` can be used
52     anywhere predicate values are accepted:
53
54     - :meth:`pyramid.config.Configurator.add_view`
55
56     - :meth:`pyramid.config.Configurator.add_route`
57
58     - :meth:`pyramid.config.Configurator.add_subscriber`
59
60     - :meth:`pyramid.view.view_config`
61
62     - :meth:`pyramid.events.subscriber`
63
64     .. versionadded:: 1.5
65     """
32333e 66     def __init__(self, value):
CM 67         self.value = value
68
69 class Notted(object):
70     def __init__(self, predicate):
71         self.predicate = predicate
72
73     def _notted_text(self, val):
74         # if the underlying predicate doesnt return a value, it's not really
75         # a predicate, it's just something pretending to be a predicate,
76         # so dont update the hash
77         if val: 
78             val = '!' + val
79         return val
80
81     def text(self):
82         return self._notted_text(self.predicate.text())
83
84     def phash(self):
85         return self._notted_text(self.predicate.phash())
86
87     def __call__(self, context, request):
88         result = self.predicate(context, request)
89         phash = self.phash()
90         if phash:
91             result = not result
92         return result
93
fc3f23 94 # under = after
CM 95 # over = before
a00621 96
CM 97 class PredicateList(object):
9c8ec5 98     
a00621 99     def __init__(self):
CM 100         self.sorter = TopologicalSorter()
9c8ec5 101         self.last_added = None
a00621 102
CM 103     def add(self, name, factory, weighs_more_than=None, weighs_less_than=None):
9c8ec5 104         # Predicates should be added to a predicate list in (presumed)
CM 105         # computation expense order.
106         ## if weighs_more_than is None and weighs_less_than is None:
107         ##     weighs_more_than = self.last_added or FIRST
108         ##     weighs_less_than = LAST
109         self.last_added = name
0ccdc2 110         self.sorter.add(
CM 111             name,
112             factory,
113             after=weighs_more_than,
114             before=weighs_less_than,
115             )
a00621 116
51ee34 117     def names(self):
BJR 118         # Return the list of valid predicate names.
119         return self.sorter.names
120
9c8ec5 121     def make(self, config, **kw):
CM 122         # Given a configurator and a list of keywords, a predicate list is
123         # computed.  Elsewhere in the code, we evaluate predicates using a
124         # generator expression.  All predicates associated with a view or
125         # route must evaluate true for the view or route to "match" during a
126         # request.  The fastest predicate should be evaluated first, then the
127         # next fastest, and so on, as if one returns false, the remainder of
128         # the predicates won't need to be evaluated.
129         #
130         # While we compute predicates, we also compute a predicate hash (aka
131         # phash) that can be used by a caller to identify identical predicate
132         # lists.
a00621 133         ordered = self.sorter.sorted()
CM 134         phash = md5()
135         weights = []
9c8ec5 136         preds = []
CM 137         for n, (name, predicate_factory) in enumerate(ordered):
a00621 138             vals = kw.pop(name, None)
9c8ec5 139             if vals is None: # XXX should this be a sentinel other than None?
a00621 140                 continue
9c8ec5 141             if not isinstance(vals, predvalseq):
a00621 142                 vals = (vals,)
CM 143             for val in vals:
32333e 144                 realval = val
CM 145                 notted = False
146                 if isinstance(val, not_):
147                     realval = val.value
148                     notted = True
149                 pred = predicate_factory(realval, config)
150                 if notted:
151                     pred = Notted(pred)
9c8ec5 152                 hashes = pred.phash()
a00621 153                 if not is_nonstr_iter(hashes):
CM 154                     hashes = [hashes]
155                 for h in hashes:
156                     phash.update(bytes_(h))
25c64c 157                 weights.append(1 << n + 1)
9c8ec5 158                 preds.append(pred)
a00621 159         if kw:
CM 160             raise ConfigurationError('Unknown predicate values: %r' % (kw,))
9c8ec5 161         # A "order" is computed for the predicate list.  An order is
CM 162         # a scoring.
163         #
164         # Each predicate is associated with a weight value.  The weight of a
165         # predicate symbolizes the relative potential "importance" of the
166         # predicate to all other predicates.  A larger weight indicates
167         # greater importance.
168         #
169         # All weights for a given predicate list are bitwise ORed together
170         # to create a "score"; this score is then subtracted from
171         # MAX_ORDER and divided by an integer representing the number of
172         # predicates+1 to determine the order.
173         #
174         # For views, the order represents the ordering in which a "multiview"
175         # ( a collection of views that share the same context/request/name
176         # triad but differ in other ways via predicates) will attempt to call
177         # its set of views.  Views with lower orders will be tried first.
178         # The intent is to a) ensure that views with more predicates are
179         # always evaluated before views with fewer predicates and b) to
180         # ensure a stable call ordering of views that share the same number
181         # of predicates.  Views which do not have any predicates get an order
182         # of MAX_ORDER, meaning that they will be tried very last.
a00621 183         score = 0
CM 184         for bit in weights:
185             score = score | bit
9c8ec5 186         order = (MAX_ORDER - score) / (len(preds) + 1)
CM 187         return order, preds, phash.hexdigest()