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