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() |