commit | author | age
|
19016b
|
1 |
import contextlib |
d8d3a9
|
2 |
import functools |
716a20
|
3 |
try: |
MM |
4 |
# py2.7.7+ and py3.3+ have native comparison support |
|
5 |
from hmac import compare_digest |
af01a5
|
6 |
except ImportError: # pragma: no cover |
716a20
|
7 |
compare_digest = None |
8b6f09
|
8 |
import inspect |
d8d3a9
|
9 |
import traceback |
91cd7e
|
10 |
import weakref |
d8d3a9
|
11 |
|
CM |
12 |
from zope.interface import implementer |
a9f17c
|
13 |
|
66fe1d
|
14 |
from pyramid.exceptions import ( |
CM |
15 |
ConfigurationError, |
|
16 |
CyclicDependencyError, |
|
17 |
) |
|
18 |
|
8a4c36
|
19 |
from pyramid.compat import ( |
417320
|
20 |
getargspec, |
MM |
21 |
im_func, |
66fe1d
|
22 |
is_nonstr_iter, |
8a4c36
|
23 |
integer_types, |
CM |
24 |
string_types, |
e49638
|
25 |
text_, |
bc37a5
|
26 |
PY2, |
1e0d64
|
27 |
native_ |
8a4c36
|
28 |
) |
CM |
29 |
|
d8d3a9
|
30 |
from pyramid.interfaces import IActionInfo |
078859
|
31 |
from pyramid.path import DottedNameResolver as _DottedNameResolver |
a9f17c
|
32 |
|
c7974f
|
33 |
_marker = object() |
MM |
34 |
|
50a8a0
|
35 |
|
078859
|
36 |
class DottedNameResolver(_DottedNameResolver): |
CM |
37 |
def __init__(self, package=None): # default to package = None for bw compat |
0f2a11
|
38 |
_DottedNameResolver.__init__(self, package) |
a9f17c
|
39 |
|
c7974f
|
40 |
def is_string_or_iterable(v): |
MM |
41 |
if isinstance(v, string_types): |
|
42 |
return True |
|
43 |
if hasattr(v, '__iter__'): |
|
44 |
return True |
f58977
|
45 |
|
c7974f
|
46 |
def as_sorted_tuple(val): |
MM |
47 |
if not is_nonstr_iter(val): |
|
48 |
val = (val,) |
|
49 |
val = tuple(sorted(val)) |
|
50 |
return val |
a9f17c
|
51 |
|
04cc91
|
52 |
class InstancePropertyHelper(object): |
MM |
53 |
"""A helper object for assigning properties and descriptors to instances. |
|
54 |
It is not normally possible to do this because descriptors must be |
|
55 |
defined on the class itself. |
|
56 |
|
|
57 |
This class is optimized for adding multiple properties at once to an |
|
58 |
instance. This is done by calling :meth:`.add_property` once |
|
59 |
per-property and then invoking :meth:`.apply` on target objects. |
|
60 |
|
577db9
|
61 |
""" |
04cc91
|
62 |
def __init__(self): |
MM |
63 |
self.properties = {} |
577db9
|
64 |
|
6d2187
|
65 |
@classmethod |
04cc91
|
66 |
def make_property(cls, callable, name=None, reify=False): |
5473f0
|
67 |
""" Convert a callable into one suitable for adding to the |
MM |
68 |
instance. This will return a 2-tuple containing the computed |
|
69 |
(name, property) pair. |
|
70 |
""" |
|
71 |
|
|
72 |
is_property = isinstance(callable, property) |
|
73 |
if is_property: |
|
74 |
fn = callable |
|
75 |
if name is None: |
|
76 |
raise ValueError('must specify "name" for a property') |
|
77 |
if reify: |
|
78 |
raise ValueError('cannot reify a property') |
|
79 |
elif name is not None: |
|
80 |
fn = lambda this: callable(this) |
1e0d64
|
81 |
fn.__name__ = get_callable_name(name) |
5473f0
|
82 |
fn.__doc__ = callable.__doc__ |
MM |
83 |
else: |
|
84 |
name = callable.__name__ |
|
85 |
fn = callable |
|
86 |
if reify: |
|
87 |
import pyramid.decorator # avoid circular import |
|
88 |
fn = pyramid.decorator.reify(fn) |
|
89 |
elif not is_property: |
|
90 |
fn = property(fn) |
|
91 |
|
|
92 |
return name, fn |
|
93 |
|
04cc91
|
94 |
@classmethod |
MM |
95 |
def apply_properties(cls, target, properties): |
2f0ba0
|
96 |
"""Accept a list or dict of ``properties`` generated from |
MM |
97 |
:meth:`.make_property` and apply them to a ``target`` object. |
5473f0
|
98 |
""" |
MM |
99 |
attrs = dict(properties) |
0cd88b
|
100 |
if attrs: |
04cc91
|
101 |
parent = target.__class__ |
MM |
102 |
newcls = type(parent.__name__, (parent, object), attrs) |
c600ab
|
103 |
# We assign __provides__ and __implemented__ below to prevent a |
BJR |
104 |
# memory leak that results from from the usage of this instance's |
|
105 |
# eventual use in an adapter lookup. Adapter lookup results in |
|
106 |
# ``zope.interface.implementedBy`` being called with the |
f58977
|
107 |
# newly-created class as an argument. Because the newly-created |
CM |
108 |
# class has no interface specification data of its own, lookup |
|
109 |
# causes new ClassProvides and Implements instances related to our |
|
110 |
# just-generated class to be created and set into the newly-created |
|
111 |
# class' __dict__. We don't want these instances to be created; we |
|
112 |
# want this new class to behave exactly like it is the parent class |
32e969
|
113 |
# instead. See GitHub issues #1212, #1529 and #1568 for more |
CM |
114 |
# information. |
c600ab
|
115 |
for name in ('__implemented__', '__provides__'): |
f58977
|
116 |
# we assign these attributes conditionally to make it possible |
CM |
117 |
# to test this class in isolation without having any interfaces |
|
118 |
# attached to it |
|
119 |
val = getattr(parent, name, _marker) |
|
120 |
if val is not _marker: |
04cc91
|
121 |
setattr(newcls, name, val) |
MM |
122 |
target.__class__ = newcls |
5473f0
|
123 |
|
04cc91
|
124 |
@classmethod |
MM |
125 |
def set_property(cls, target, callable, name=None, reify=False): |
|
126 |
"""A helper method to apply a single property to an instance.""" |
|
127 |
prop = cls.make_property(callable, name=name, reify=reify) |
|
128 |
cls.apply_properties(target, [prop]) |
9e7248
|
129 |
|
04cc91
|
130 |
def add_property(self, callable, name=None, reify=False): |
MM |
131 |
"""Add a new property configuration. |
|
132 |
|
|
133 |
This should be used in combination with :meth:`.apply` as a |
|
134 |
more efficient version of :meth:`.set_property`. |
|
135 |
""" |
|
136 |
name, fn = self.make_property(callable, name=name, reify=reify) |
|
137 |
self.properties[name] = fn |
|
138 |
|
|
139 |
def apply(self, target): |
|
140 |
""" Apply all configured properties to the ``target`` instance.""" |
|
141 |
if self.properties: |
|
142 |
self.apply_properties(target, self.properties) |
|
143 |
|
|
144 |
class InstancePropertyMixin(object): |
|
145 |
""" Mixin that will allow an instance to add properties at |
|
146 |
run-time as if they had been defined via @property or @reify |
|
147 |
on the class itself. |
|
148 |
""" |
735987
|
149 |
|
1b113f
|
150 |
def set_property(self, callable, name=None, reify=False): |
577db9
|
151 |
""" Add a callable or a property descriptor to the instance. |
MM |
152 |
|
|
153 |
Properties, unlike attributes, are lazily evaluated by executing |
|
154 |
an underlying callable when accessed. They can be useful for |
|
155 |
adding features to an object without any cost if those features |
|
156 |
go unused. |
|
157 |
|
|
158 |
A property may also be reified via the |
|
159 |
:class:`pyramid.decorator.reify` decorator by setting |
|
160 |
``reify=True``, allowing the result of the evaluation to be |
5473f0
|
161 |
cached. Using this method, the value of the property is only |
MM |
162 |
computed once for the lifetime of the object. |
577db9
|
163 |
|
1b113f
|
164 |
``callable`` can either be a callable that accepts the instance |
5473f0
|
165 |
as its single positional parameter, or it can be a property |
577db9
|
166 |
descriptor. |
MM |
167 |
|
1b113f
|
168 |
If the ``callable`` is a property descriptor, the ``name`` |
MM |
169 |
parameter must be supplied or a ``ValueError`` will be raised. |
|
170 |
Also note that a property descriptor cannot be reified, so |
|
171 |
``reify`` must be ``False``. |
577db9
|
172 |
|
MM |
173 |
If ``name`` is None, the name of the property will be computed |
1b113f
|
174 |
from the name of the ``callable``. |
577db9
|
175 |
|
MM |
176 |
.. code-block:: python |
|
177 |
:linenos: |
|
178 |
|
|
179 |
class Foo(InstancePropertyMixin): |
|
180 |
_x = 1 |
|
181 |
|
|
182 |
def _get_x(self): |
|
183 |
return _x |
|
184 |
|
|
185 |
def _set_x(self, value): |
|
186 |
self._x = value |
|
187 |
|
|
188 |
foo = Foo() |
|
189 |
foo.set_property(property(_get_x, _set_x), name='x') |
|
190 |
foo.set_property(_get_x, name='y', reify=True) |
|
191 |
|
|
192 |
>>> foo.x |
|
193 |
1 |
|
194 |
>>> foo.y |
|
195 |
1 |
|
196 |
>>> foo.x = 5 |
|
197 |
>>> foo.x |
|
198 |
5 |
|
199 |
>>> foo.y # notice y keeps the original value |
|
200 |
1 |
|
201 |
""" |
04cc91
|
202 |
InstancePropertyHelper.set_property( |
MM |
203 |
self, callable, name=name, reify=reify) |
577db9
|
204 |
|
91cd7e
|
205 |
class WeakOrderedSet(object): |
MM |
206 |
""" Maintain a set of items. |
a9f17c
|
207 |
|
91cd7e
|
208 |
Each item is stored as a weakref to avoid extending their lifetime. |
MM |
209 |
|
|
210 |
The values may be iterated over or the last item added may be |
|
211 |
accessed via the ``last`` property. |
01c4f3
|
212 |
|
MM |
213 |
If items are added more than once, the most recent addition will |
|
214 |
be remembered in the order: |
|
215 |
|
|
216 |
order = WeakOrderedSet() |
|
217 |
order.add('1') |
|
218 |
order.add('2') |
|
219 |
order.add('1') |
|
220 |
|
|
221 |
list(order) == ['2', '1'] |
|
222 |
order.last == '1' |
91cd7e
|
223 |
""" |
MM |
224 |
|
|
225 |
def __init__(self): |
|
226 |
self._items = {} |
|
227 |
self._order = [] |
|
228 |
|
|
229 |
def add(self, item): |
eff1cb
|
230 |
""" Add an item to the set.""" |
91cd7e
|
231 |
oid = id(item) |
MM |
232 |
if oid in self._items: |
01c4f3
|
233 |
self._order.remove(oid) |
MM |
234 |
self._order.append(oid) |
91cd7e
|
235 |
return |
9e94bb
|
236 |
ref = weakref.ref(item, lambda x: self._remove_by_id(oid)) |
91cd7e
|
237 |
self._items[oid] = ref |
MM |
238 |
self._order.append(oid) |
|
239 |
|
9e94bb
|
240 |
def _remove_by_id(self, oid): |
eff1cb
|
241 |
""" Remove an item from the set.""" |
MM |
242 |
if oid in self._items: |
|
243 |
del self._items[oid] |
|
244 |
self._order.remove(oid) |
|
245 |
|
9e94bb
|
246 |
def remove(self, item): |
KK |
247 |
""" Remove an item from the set.""" |
|
248 |
self._remove_by_id(id(item)) |
|
249 |
|
eff1cb
|
250 |
def empty(self): |
MM |
251 |
""" Clear all objects from the set.""" |
|
252 |
self._items = {} |
|
253 |
self._order = [] |
|
254 |
|
91cd7e
|
255 |
def __len__(self): |
MM |
256 |
return len(self._order) |
|
257 |
|
|
258 |
def __contains__(self, item): |
|
259 |
oid = id(item) |
|
260 |
return oid in self._items |
|
261 |
|
|
262 |
def __iter__(self): |
|
263 |
return (self._items[oid]() for oid in self._order) |
|
264 |
|
|
265 |
@property |
|
266 |
def last(self): |
|
267 |
if self._order: |
|
268 |
oid = self._order[-1] |
|
269 |
return self._items[oid]() |
654a67
|
270 |
|
716a20
|
271 |
def strings_differ(string1, string2, compare_digest=compare_digest): |
13906d
|
272 |
"""Check whether two strings differ while avoiding timing attacks. |
RK |
273 |
|
|
274 |
This function returns True if the given strings differ and False |
|
275 |
if they are equal. It's careful not to leak information about *where* |
|
276 |
they differ as a result of its running time, which can be very important |
|
277 |
to avoid certain timing-related crypto attacks: |
|
278 |
|
|
279 |
http://seb.dbzteam.org/crypto/python-oauth-timing-hmac.pdf |
|
280 |
|
716a20
|
281 |
.. versionchanged:: 1.6 |
MM |
282 |
Support :func:`hmac.compare_digest` if it is available (Python 2.7.7+ |
|
283 |
and Python 3.3+). |
|
284 |
|
13906d
|
285 |
""" |
716a20
|
286 |
len_eq = len(string1) == len(string2) |
MM |
287 |
if len_eq: |
|
288 |
invalid_bits = 0 |
|
289 |
left = string1 |
|
290 |
else: |
|
291 |
invalid_bits = 1 |
|
292 |
left = string2 |
|
293 |
right = string2 |
13906d
|
294 |
|
716a20
|
295 |
if compare_digest is not None: |
MM |
296 |
invalid_bits += not compare_digest(left, right) |
|
297 |
else: |
|
298 |
for a, b in zip(left, right): |
|
299 |
invalid_bits += a != b |
13906d
|
300 |
return invalid_bits != 0 |
RK |
301 |
|
8b6f09
|
302 |
def object_description(object): |
e49638
|
303 |
""" Produce a human-consumable text description of ``object``, |
CM |
304 |
usually involving a Python dotted name. For example: |
8b6f09
|
305 |
|
88cafd
|
306 |
>>> object_description(None) |
TL |
307 |
u'None' |
|
308 |
>>> from xml.dom import minidom |
|
309 |
>>> object_description(minidom) |
|
310 |
u'module xml.dom.minidom' |
|
311 |
>>> object_description(minidom.Attr) |
|
312 |
u'class xml.dom.minidom.Attr' |
|
313 |
>>> object_description(minidom.Attr.appendChild) |
|
314 |
u'method appendChild of class xml.dom.minidom.Attr' |
8b6f09
|
315 |
|
CM |
316 |
If this method cannot identify the type of the object, a generic |
|
317 |
description ala ``object <object.__name__>`` will be returned. |
|
318 |
|
|
319 |
If the object passed is already a string, it is simply returned. If it |
|
320 |
is a boolean, an integer, a list, a tuple, a set, or ``None``, a |
|
321 |
(possibly shortened) string representation is returned. |
|
322 |
""" |
|
323 |
if isinstance(object, string_types): |
e49638
|
324 |
return text_(object) |
8a4c36
|
325 |
if isinstance(object, integer_types): |
e49638
|
326 |
return text_(str(object)) |
8a4c36
|
327 |
if isinstance(object, (bool, float, type(None))): |
e49638
|
328 |
return text_(str(object)) |
82ba10
|
329 |
if isinstance(object, set): |
bc37a5
|
330 |
if PY2: |
82ba10
|
331 |
return shortrepr(object, ')') |
bc37a5
|
332 |
else: |
MM |
333 |
return shortrepr(object, '}') |
82ba10
|
334 |
if isinstance(object, tuple): |
8b6f09
|
335 |
return shortrepr(object, ')') |
CM |
336 |
if isinstance(object, list): |
|
337 |
return shortrepr(object, ']') |
|
338 |
if isinstance(object, dict): |
|
339 |
return shortrepr(object, '}') |
|
340 |
module = inspect.getmodule(object) |
7f72f8
|
341 |
if module is None: |
e49638
|
342 |
return text_('object %s' % str(object)) |
8b6f09
|
343 |
modulename = module.__name__ |
CM |
344 |
if inspect.ismodule(object): |
e49638
|
345 |
return text_('module %s' % modulename) |
8b6f09
|
346 |
if inspect.ismethod(object): |
CM |
347 |
oself = getattr(object, '__self__', None) |
e49638
|
348 |
if oself is None: # pragma: no cover |
8b6f09
|
349 |
oself = getattr(object, 'im_self', None) |
e49638
|
350 |
return text_('method %s of class %s.%s' % |
CM |
351 |
(object.__name__, modulename, |
|
352 |
oself.__class__.__name__)) |
1b113f
|
353 |
|
8b6f09
|
354 |
if inspect.isclass(object): |
CM |
355 |
dottedname = '%s.%s' % (modulename, object.__name__) |
e49638
|
356 |
return text_('class %s' % dottedname) |
8b6f09
|
357 |
if inspect.isfunction(object): |
CM |
358 |
dottedname = '%s.%s' % (modulename, object.__name__) |
e49638
|
359 |
return text_('function %s' % dottedname) |
CM |
360 |
return text_('object %s' % str(object)) |
8b6f09
|
361 |
|
CM |
362 |
def shortrepr(object, closer): |
|
363 |
r = str(object) |
|
364 |
if len(r) > 100: |
e49638
|
365 |
r = r[:100] + ' ... %s' % closer |
8b6f09
|
366 |
return r |
d98612
|
367 |
|
66fe1d
|
368 |
class Sentinel(object): |
CM |
369 |
def __init__(self, repr): |
|
370 |
self.repr = repr |
|
371 |
|
|
372 |
def __repr__(self): |
|
373 |
return self.repr |
|
374 |
|
|
375 |
FIRST = Sentinel('FIRST') |
|
376 |
LAST = Sentinel('LAST') |
|
377 |
|
|
378 |
class TopologicalSorter(object): |
|
379 |
""" A utility class which can be used to perform topological sorts against |
|
380 |
tuple-like data.""" |
|
381 |
def __init__( |
|
382 |
self, |
|
383 |
default_before=LAST, |
|
384 |
default_after=None, |
|
385 |
first=FIRST, |
|
386 |
last=LAST, |
|
387 |
): |
|
388 |
self.names = [] |
|
389 |
self.req_before = set() |
|
390 |
self.req_after = set() |
|
391 |
self.name2before = {} |
|
392 |
self.name2after = {} |
|
393 |
self.name2val = {} |
|
394 |
self.order = [] |
|
395 |
self.default_before = default_before |
|
396 |
self.default_after = default_after |
|
397 |
self.first = first |
|
398 |
self.last = last |
|
399 |
|
e4b931
|
400 |
def values(self): |
MM |
401 |
return self.name2val.values() |
|
402 |
|
66fe1d
|
403 |
def remove(self, name): |
CM |
404 |
""" Remove a node from the sort input """ |
|
405 |
self.names.remove(name) |
|
406 |
del self.name2val[name] |
|
407 |
after = self.name2after.pop(name, []) |
|
408 |
if after: |
|
409 |
self.req_after.remove(name) |
|
410 |
for u in after: |
|
411 |
self.order.remove((u, name)) |
|
412 |
before = self.name2before.pop(name, []) |
|
413 |
if before: |
|
414 |
self.req_before.remove(name) |
|
415 |
for u in before: |
|
416 |
self.order.remove((name, u)) |
|
417 |
|
|
418 |
def add(self, name, val, after=None, before=None): |
|
419 |
""" Add a node to the sort input. The ``name`` should be a string or |
|
420 |
any other hashable object, the ``val`` should be the sortable (doesn't |
|
421 |
need to be hashable). ``after`` and ``before`` represents the name of |
|
422 |
one of the other sortables (or a sequence of such named) or one of the |
|
423 |
special sentinel values :attr:`pyramid.util.FIRST`` or |
|
424 |
:attr:`pyramid.util.LAST` representing the first or last positions |
|
425 |
respectively. ``FIRST`` and ``LAST`` can also be part of a sequence |
|
426 |
passed as ``before`` or ``after``. A sortable should not be added |
|
427 |
after LAST or before FIRST. An example:: |
|
428 |
|
|
429 |
sorter = TopologicalSorter() |
|
430 |
sorter.add('a', {'a':1}, before=LAST, after='b') |
|
431 |
sorter.add('b', {'b':2}, before=LAST, after='c') |
|
432 |
sorter.add('c', {'c':3}) |
|
433 |
|
|
434 |
sorter.sorted() # will be {'c':3}, {'b':2}, {'a':1} |
|
435 |
|
|
436 |
""" |
|
437 |
if name in self.names: |
|
438 |
self.remove(name) |
|
439 |
self.names.append(name) |
|
440 |
self.name2val[name] = val |
|
441 |
if after is None and before is None: |
|
442 |
before = self.default_before |
|
443 |
after = self.default_after |
|
444 |
if after is not None: |
|
445 |
if not is_nonstr_iter(after): |
|
446 |
after = (after,) |
|
447 |
self.name2after[name] = after |
|
448 |
self.order += [(u, name) for u in after] |
|
449 |
self.req_after.add(name) |
|
450 |
if before is not None: |
|
451 |
if not is_nonstr_iter(before): |
|
452 |
before = (before,) |
|
453 |
self.name2before[name] = before |
|
454 |
self.order += [(name, o) for o in before] |
|
455 |
self.req_before.add(name) |
|
456 |
|
|
457 |
|
|
458 |
def sorted(self): |
|
459 |
""" Returns the sort input values in topologically sorted order""" |
|
460 |
order = [(self.first, self.last)] |
|
461 |
roots = [] |
|
462 |
graph = {} |
|
463 |
names = [self.first, self.last] |
|
464 |
names.extend(self.names) |
|
465 |
|
|
466 |
for a, b in self.order: |
|
467 |
order.append((a, b)) |
|
468 |
|
|
469 |
def add_node(node): |
25c64c
|
470 |
if node not in graph: |
66fe1d
|
471 |
roots.append(node) |
CM |
472 |
graph[node] = [0] # 0 = number of arcs coming into this node |
|
473 |
|
|
474 |
def add_arc(fromnode, tonode): |
|
475 |
graph[fromnode].append(tonode) |
|
476 |
graph[tonode][0] += 1 |
|
477 |
if tonode in roots: |
|
478 |
roots.remove(tonode) |
|
479 |
|
|
480 |
for name in names: |
|
481 |
add_node(name) |
|
482 |
|
|
483 |
has_before, has_after = set(), set() |
|
484 |
for a, b in order: |
|
485 |
if a in names and b in names: # deal with missing dependencies |
|
486 |
add_arc(a, b) |
|
487 |
has_before.add(a) |
|
488 |
has_after.add(b) |
|
489 |
|
|
490 |
if not self.req_before.issubset(has_before): |
|
491 |
raise ConfigurationError( |
|
492 |
'Unsatisfied before dependencies: %s' |
|
493 |
% (', '.join(sorted(self.req_before - has_before))) |
|
494 |
) |
|
495 |
if not self.req_after.issubset(has_after): |
|
496 |
raise ConfigurationError( |
|
497 |
'Unsatisfied after dependencies: %s' |
|
498 |
% (', '.join(sorted(self.req_after - has_after))) |
|
499 |
) |
|
500 |
|
|
501 |
sorted_names = [] |
|
502 |
|
|
503 |
while roots: |
|
504 |
root = roots.pop(0) |
|
505 |
sorted_names.append(root) |
|
506 |
children = graph[root][1:] |
|
507 |
for child in children: |
|
508 |
arcs = graph[child][0] |
|
509 |
arcs -= 1 |
|
510 |
graph[child][0] = arcs |
|
511 |
if arcs == 0: |
|
512 |
roots.insert(0, child) |
|
513 |
del graph[root] |
|
514 |
|
|
515 |
if graph: |
|
516 |
# loop in input |
|
517 |
cycledeps = {} |
|
518 |
for k, v in graph.items(): |
|
519 |
cycledeps[k] = v[1:] |
|
520 |
raise CyclicDependencyError(cycledeps) |
|
521 |
|
|
522 |
result = [] |
|
523 |
|
|
524 |
for name in sorted_names: |
|
525 |
if name in self.names: |
|
526 |
result.append((name, self.name2val[name])) |
|
527 |
|
|
528 |
return result |
|
529 |
|
d8d3a9
|
530 |
def viewdefaults(wrapped): |
CM |
531 |
""" Decorator for add_view-like methods which takes into account |
|
532 |
__view_defaults__ attached to view it is passed. Not a documented API but |
|
533 |
used by some external systems.""" |
|
534 |
def wrapper(self, *arg, **kw): |
|
535 |
defaults = {} |
|
536 |
if arg: |
|
537 |
view = arg[0] |
|
538 |
else: |
|
539 |
view = kw.get('view') |
|
540 |
view = self.maybe_dotted(view) |
|
541 |
if inspect.isclass(view): |
|
542 |
defaults = getattr(view, '__view_defaults__', {}).copy() |
25c64c
|
543 |
if '_backframes' not in kw: |
0920ac
|
544 |
kw['_backframes'] = 1 # for action_method |
d8d3a9
|
545 |
defaults.update(kw) |
CM |
546 |
return wrapped(self, *arg, **defaults) |
|
547 |
return functools.wraps(wrapped)(wrapper) |
|
548 |
|
|
549 |
@implementer(IActionInfo) |
|
550 |
class ActionInfo(object): |
|
551 |
def __init__(self, file, line, function, src): |
|
552 |
self.file = file |
|
553 |
self.line = line |
|
554 |
self.function = function |
|
555 |
self.src = src |
|
556 |
|
|
557 |
def __str__(self): |
|
558 |
srclines = self.src.split('\n') |
|
559 |
src = '\n'.join(' %s' % x for x in srclines) |
|
560 |
return 'Line %s of file %s:\n%s' % (self.line, self.file, src) |
|
561 |
|
|
562 |
def action_method(wrapped): |
|
563 |
""" Wrapper to provide the right conflict info report data when a method |
|
564 |
that calls Configurator.action calls another that does the same. Not a |
|
565 |
documented API but used by some external systems.""" |
|
566 |
def wrapper(self, *arg, **kw): |
|
567 |
if self._ainfo is None: |
|
568 |
self._ainfo = [] |
|
569 |
info = kw.pop('_info', None) |
|
570 |
# backframes for outer decorators to actionmethods |
0920ac
|
571 |
backframes = kw.pop('_backframes', 0) + 2 |
d8d3a9
|
572 |
if is_nonstr_iter(info) and len(info) == 4: |
CM |
573 |
# _info permitted as extract_stack tuple |
|
574 |
info = ActionInfo(*info) |
|
575 |
if info is None: |
|
576 |
try: |
ce042d
|
577 |
f = traceback.extract_stack(limit=4) |
BJR |
578 |
|
|
579 |
# Work around a Python 3.5 issue whereby it would insert an |
|
580 |
# extra stack frame. This should no longer be necessary in |
|
581 |
# Python 3.5.1 |
|
582 |
last_frame = ActionInfo(*f[-1]) |
601393
|
583 |
if last_frame.function == 'extract_stack': # pragma: no cover |
ce042d
|
584 |
f.pop() |
d8d3a9
|
585 |
info = ActionInfo(*f[-backframes]) |
c0ca03
|
586 |
except Exception: # pragma: no cover |
d8d3a9
|
587 |
info = ActionInfo(None, 0, '', '') |
CM |
588 |
self._ainfo.append(info) |
|
589 |
try: |
|
590 |
result = wrapped(self, *arg, **kw) |
|
591 |
finally: |
|
592 |
self._ainfo.pop() |
|
593 |
return result |
|
594 |
|
|
595 |
if hasattr(wrapped, '__name__'): |
|
596 |
functools.update_wrapper(wrapper, wrapped) |
|
597 |
wrapper.__docobj__ = wrapped |
|
598 |
return wrapper |
1e0d64
|
599 |
|
JA |
600 |
|
|
601 |
def get_callable_name(name): |
|
602 |
""" |
|
603 |
Verifies that the ``name`` is ascii and will raise a ``ConfigurationError`` |
|
604 |
if it is not. |
|
605 |
""" |
|
606 |
try: |
|
607 |
return native_(name, 'ascii') |
|
608 |
except (UnicodeEncodeError, UnicodeDecodeError): |
|
609 |
msg = ( |
|
610 |
'`name="%s"` is invalid. `name` must be ascii because it is ' |
|
611 |
'used on __name__ of the method' |
|
612 |
) |
|
613 |
raise ConfigurationError(msg % name) |
19016b
|
614 |
|
MM |
615 |
@contextlib.contextmanager |
|
616 |
def hide_attrs(obj, *attrs): |
|
617 |
""" |
|
618 |
Temporarily delete object attrs and restore afterward. |
|
619 |
""" |
|
620 |
obj_vals = obj.__dict__ if obj is not None else {} |
|
621 |
saved_vals = {} |
|
622 |
for name in attrs: |
|
623 |
saved_vals[name] = obj_vals.pop(name, _marker) |
|
624 |
try: |
|
625 |
yield |
|
626 |
finally: |
|
627 |
for name in attrs: |
|
628 |
saved_val = saved_vals[name] |
|
629 |
if saved_val is not _marker: |
|
630 |
obj_vals[name] = saved_val |
|
631 |
elif name in obj_vals: |
|
632 |
del obj_vals[name] |
65dee6
|
633 |
|
DS |
634 |
|
|
635 |
def is_same_domain(host, pattern): |
|
636 |
""" |
|
637 |
Return ``True`` if the host is either an exact match or a match |
|
638 |
to the wildcard pattern. |
|
639 |
Any pattern beginning with a period matches a domain and all of its |
|
640 |
subdomains. (e.g. ``.example.com`` matches ``example.com`` and |
|
641 |
``foo.example.com``). Anything else is an exact string match. |
|
642 |
""" |
|
643 |
if not pattern: |
|
644 |
return False |
|
645 |
|
|
646 |
pattern = pattern.lower() |
|
647 |
return (pattern[0] == "." and |
|
648 |
(host.endswith(pattern) or host == pattern[1:]) or |
|
649 |
pattern == host) |
417320
|
650 |
|
MM |
651 |
|
|
652 |
def takes_one_arg(callee, attr=None, argname=None): |
|
653 |
ismethod = False |
|
654 |
if attr is None: |
|
655 |
attr = '__call__' |
|
656 |
if inspect.isroutine(callee): |
|
657 |
fn = callee |
|
658 |
elif inspect.isclass(callee): |
|
659 |
try: |
|
660 |
fn = callee.__init__ |
|
661 |
except AttributeError: |
|
662 |
return False |
|
663 |
ismethod = hasattr(fn, '__call__') |
|
664 |
else: |
|
665 |
try: |
|
666 |
fn = getattr(callee, attr) |
|
667 |
except AttributeError: |
|
668 |
return False |
|
669 |
|
|
670 |
try: |
|
671 |
argspec = getargspec(fn) |
|
672 |
except TypeError: |
|
673 |
return False |
|
674 |
|
|
675 |
args = argspec[0] |
|
676 |
|
|
677 |
if hasattr(fn, im_func) or ismethod: |
|
678 |
# it's an instance method (or unbound method on py2) |
|
679 |
if not args: |
|
680 |
return False |
|
681 |
args = args[1:] |
|
682 |
|
|
683 |
if not args: |
|
684 |
return False |
|
685 |
|
|
686 |
if len(args) == 1: |
|
687 |
return True |
|
688 |
|
|
689 |
if argname: |
|
690 |
|
|
691 |
defaults = argspec[3] |
|
692 |
if defaults is None: |
|
693 |
defaults = () |
|
694 |
|
|
695 |
if args[0] == argname: |
|
696 |
if len(args) - len(defaults) == 1: |
|
697 |
return True |
|
698 |
|
|
699 |
return False |