Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/pyramid/util.py : 54%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from contextlib import contextmanager
2import functools
4try:
5 # py2.7.7+ and py3.3+ have native comparison support
6 from hmac import compare_digest
7except ImportError: # pragma: no cover
8 compare_digest = None
9import inspect
10import weakref
12from pyramid.exceptions import ConfigurationError, CyclicDependencyError
14from pyramid.compat import (
15 getargspec,
16 im_func,
17 is_nonstr_iter,
18 integer_types,
19 string_types,
20 bytes_,
21 text_,
22 PY2,
23 native_,
24)
26from pyramid.path import DottedNameResolver as _DottedNameResolver
28_marker = object()
31class DottedNameResolver(_DottedNameResolver):
32 def __init__(
33 self, package=None
34 ): # default to package = None for bw compat
35 _DottedNameResolver.__init__(self, package)
38def is_string_or_iterable(v):
39 if isinstance(v, string_types):
40 return True
41 if hasattr(v, '__iter__'):
42 return True
45def as_sorted_tuple(val):
46 if not is_nonstr_iter(val):
47 val = (val,)
48 val = tuple(sorted(val))
49 return val
52class InstancePropertyHelper(object):
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.
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.
61 """
63 def __init__(self):
64 self.properties = {}
66 @classmethod
67 def make_property(cls, callable, name=None, reify=False):
68 """ Convert a callable into one suitable for adding to the
69 instance. This will return a 2-tuple containing the computed
70 (name, property) pair.
71 """
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)
82 fn.__name__ = get_callable_name(name)
83 fn.__doc__ = callable.__doc__
84 else:
85 name = callable.__name__
86 fn = callable
87 if reify:
88 import pyramid.decorator # avoid circular import
90 fn = pyramid.decorator.reify(fn)
91 elif not is_property:
92 fn = property(fn)
94 return name, fn
96 @classmethod
97 def apply_properties(cls, target, properties):
98 """Accept a list or dict of ``properties`` generated from
99 :meth:`.make_property` and apply them to a ``target`` object.
100 """
101 attrs = dict(properties)
102 if attrs:
103 parent = target.__class__
104 # fix the module name so it appears to still be the parent
105 # e.g. pyramid.request instead of pyramid.util
106 attrs.setdefault('__module__', parent.__module__)
107 newcls = type(parent.__name__, (parent, object), attrs)
108 # We assign __provides__ and __implemented__ below to prevent a
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
112 # newly-created class as an argument. Because the newly-created
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
118 # instead. See GitHub issues #1212, #1529 and #1568 for more
119 # information.
120 for name in ('__implemented__', '__provides__'):
121 # we assign these attributes conditionally to make it possible
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:
126 setattr(newcls, name, val)
127 target.__class__ = newcls
129 @classmethod
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])
135 def add_property(self, callable, name=None, reify=False):
136 """Add a new property configuration.
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
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)
150class 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 """
156 def set_property(self, callable, name=None, reify=False):
157 """ Add a callable or a property descriptor to the instance.
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.
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
167 cached. Using this method, the value of the property is only
168 computed once for the lifetime of the object.
170 ``callable`` can either be a callable that accepts the instance
171 as its single positional parameter, or it can be a property
172 descriptor.
174 If the ``callable`` is a property descriptor, the ``name``
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``.
179 If ``name`` is None, the name of the property will be computed
180 from the name of the ``callable``.
182 .. code-block:: python
183 :linenos:
185 class Foo(InstancePropertyMixin):
186 _x = 1
188 def _get_x(self):
189 return _x
191 def _set_x(self, value):
192 self._x = value
194 foo = Foo()
195 foo.set_property(property(_get_x, _set_x), name='x')
196 foo.set_property(_get_x, name='y', reify=True)
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 """
208 InstancePropertyHelper.set_property(
209 self, callable, name=name, reify=reify
210 )
213class WeakOrderedSet(object):
214 """ Maintain a set of items.
216 Each item is stored as a weakref to avoid extending their lifetime.
218 The values may be iterated over or the last item added may be
219 accessed via the ``last`` property.
221 If items are added more than once, the most recent addition will
222 be remembered in the order:
224 order = WeakOrderedSet()
225 order.add('1')
226 order.add('2')
227 order.add('1')
229 list(order) == ['2', '1']
230 order.last == '1'
231 """
233 def __init__(self):
234 self._items = {}
235 self._order = []
237 def add(self, item):
238 """ Add an item to the set."""
239 oid = id(item)
240 if oid in self._items:
241 self._order.remove(oid)
242 self._order.append(oid)
243 return
244 ref = weakref.ref(item, lambda x: self._remove_by_id(oid))
245 self._items[oid] = ref
246 self._order.append(oid)
248 def _remove_by_id(self, oid):
249 """ Remove an item from the set."""
250 if oid in self._items:
251 del self._items[oid]
252 self._order.remove(oid)
254 def remove(self, item):
255 """ Remove an item from the set."""
256 self._remove_by_id(id(item))
258 def empty(self):
259 """ Clear all objects from the set."""
260 self._items = {}
261 self._order = []
263 def __len__(self):
264 return len(self._order)
266 def __contains__(self, item):
267 oid = id(item)
268 return oid in self._items
270 def __iter__(self):
271 return (self._items[oid]() for oid in self._order)
273 @property
274 def last(self):
275 if self._order:
276 oid = self._order[-1]
277 return self._items[oid]()
280def strings_differ(string1, string2, compare_digest=compare_digest):
281 """Check whether two strings differ while avoiding timing attacks.
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:
288 http://seb.dbzteam.org/crypto/python-oauth-timing-hmac.pdf
290 .. versionchanged:: 1.6
291 Support :func:`hmac.compare_digest` if it is available (Python 2.7.7+
292 and Python 3.3+).
294 """
295 len_eq = len(string1) == len(string2)
296 if len_eq:
297 invalid_bits = 0
298 left = string1
299 else:
300 invalid_bits = 1
301 left = string2
302 right = string2
304 if compare_digest is not None:
305 invalid_bits += not compare_digest(left, right)
306 else:
307 for a, b in zip(left, right):
308 invalid_bits += a != b
309 return invalid_bits != 0
312def object_description(object):
313 """ Produce a human-consumable text description of ``object``,
314 usually involving a Python dotted name. For example:
316 >>> object_description(None)
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'
326 If this method cannot identify the type of the object, a generic
327 description ala ``object <object.__name__>`` will be returned.
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):
334 return text_(object)
335 if isinstance(object, integer_types):
336 return text_(str(object))
337 if isinstance(object, (bool, float, type(None))):
338 return text_(str(object))
339 if isinstance(object, set):
340 if PY2:
341 return shortrepr(object, ')')
342 else:
343 return shortrepr(object, '}')
344 if isinstance(object, tuple):
345 return shortrepr(object, ')')
346 if isinstance(object, list):
347 return shortrepr(object, ']')
348 if isinstance(object, dict):
349 return shortrepr(object, '}')
350 module = inspect.getmodule(object)
351 if module is None:
352 return text_('object %s' % str(object))
353 modulename = module.__name__
354 if inspect.ismodule(object):
355 return text_('module %s' % modulename)
356 if inspect.ismethod(object):
357 oself = getattr(object, '__self__', None)
358 if oself is None: # pragma: no cover
359 oself = getattr(object, 'im_self', None)
360 return text_(
361 'method %s of class %s.%s'
362 % (object.__name__, modulename, oself.__class__.__name__)
363 )
365 if inspect.isclass(object):
366 dottedname = '%s.%s' % (modulename, object.__name__)
367 return text_('class %s' % dottedname)
368 if inspect.isfunction(object):
369 dottedname = '%s.%s' % (modulename, object.__name__)
370 return text_('function %s' % dottedname)
371 return text_('object %s' % str(object))
374def shortrepr(object, closer):
375 r = str(object)
376 if len(r) > 100:
377 r = r[:100] + ' ... %s' % closer
378 return r
381class Sentinel(object):
382 def __init__(self, repr):
383 self.repr = repr
385 def __repr__(self):
386 return self.repr
389FIRST = Sentinel('FIRST')
390LAST = Sentinel('LAST')
393class TopologicalSorter(object):
394 """ A utility class which can be used to perform topological sorts against
395 tuple-like data."""
397 def __init__(
398 self, default_before=LAST, default_after=None, first=FIRST, last=LAST
399 ):
400 self.names = []
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
412 def values(self):
413 return self.name2val.values()
415 def remove(self, name):
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))
430 def add(self, name, val, after=None, before=None):
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::
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})
446 sorter.sorted() # will be {'c':3}, {'b':2}, {'a':1}
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)
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)
477 for a, b in self.order:
478 order.append((a, b))
480 def add_node(node):
481 if node not in graph:
482 roots.append(node)
483 graph[node] = [0] # 0 = number of arcs coming into this node
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)
491 for name in names:
492 add_node(name)
494 has_before, has_after = set(), set()
495 for a, b in order:
496 if a in names and b in names: # deal with missing dependencies
497 add_arc(a, b)
498 has_before.add(a)
499 has_after.add(b)
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 )
512 sorted_names = []
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
521 graph[child][0] = arcs
522 if arcs == 0:
523 roots.insert(0, child)
524 del graph[root]
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)
533 result = []
535 for name in sorted_names:
536 if name in self.names:
537 result.append((name, self.name2val[name]))
539 return result
542def 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)
557@contextmanager
558def hide_attrs(obj, *attrs):
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]
577def 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
588 pattern = pattern.lower()
589 return (
590 pattern[0] == "."
591 and (host.endswith(pattern) or host == pattern[1:])
592 or pattern == host
593 )
596def make_contextmanager(fn):
597 if inspect.isgeneratorfunction(fn):
598 return contextmanager(fn)
600 if fn is None:
601 fn = lambda *a, **kw: None
603 @contextmanager
604 @functools.wraps(fn)
605 def wrapper(*a, **kw):
606 yield fn(*a, **kw)
608 return wrapper
611def 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
629 try:
630 argspec = getargspec(fn)
631 except TypeError:
632 return False
634 args = argspec[0]
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:]
642 if not args:
643 return False
645 if len(args) == 1:
646 return True
648 if argname:
650 defaults = argspec[3]
651 if defaults is None:
652 defaults = ()
654 if args[0] == argname:
655 if len(args) - len(defaults) == 1:
656 return True
658 return False
661class SimpleSerializer(object):
662 def loads(self, bstruct):
663 return native_(bstruct)
665 def dumps(self, appstruct):
666 return bytes_(appstruct)