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

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
1# util/langhelpers.py
2# Copyright (C) 2005-2020 the SQLAlchemy authors and contributors
3# <see AUTHORS file>
4#
5# This module is part of SQLAlchemy and is released under
6# the MIT License: http://www.opensource.org/licenses/mit-license.php
8"""Routines to help with the creation, loading and introspection of
9modules, classes, hierarchies, attributes, functions, and methods.
11"""
12from functools import update_wrapper
13import hashlib
14import inspect
15import itertools
16import operator
17import re
18import sys
19import textwrap
20import types
21import warnings
23from . import _collections
24from . import compat
25from .. import exc
28def md5_hex(x):
29 if compat.py3k:
30 x = x.encode("utf-8")
31 m = hashlib.md5()
32 m.update(x)
33 return m.hexdigest()
36class safe_reraise(object):
37 """Reraise an exception after invoking some
38 handler code.
40 Stores the existing exception info before
41 invoking so that it is maintained across a potential
42 coroutine context switch.
44 e.g.::
46 try:
47 sess.commit()
48 except:
49 with safe_reraise():
50 sess.rollback()
52 """
54 __slots__ = ("warn_only", "_exc_info")
56 def __init__(self, warn_only=False):
57 self.warn_only = warn_only
59 def __enter__(self):
60 self._exc_info = sys.exc_info()
62 def __exit__(self, type_, value, traceback):
63 # see #2703 for notes
64 if type_ is None:
65 exc_type, exc_value, exc_tb = self._exc_info
66 self._exc_info = None # remove potential circular references
67 if not self.warn_only:
68 compat.raise_(
69 exc_value, with_traceback=exc_tb,
70 )
71 else:
72 if not compat.py3k and self._exc_info and self._exc_info[1]:
73 # emulate Py3K's behavior of telling us when an exception
74 # occurs in an exception handler.
75 warn(
76 "An exception has occurred during handling of a "
77 "previous exception. The previous exception "
78 "is:\n %s %s\n" % (self._exc_info[0], self._exc_info[1])
79 )
80 self._exc_info = None # remove potential circular references
81 compat.raise_(value, with_traceback=traceback)
84def clsname_as_plain_name(cls):
85 return " ".join(
86 n.lower() for n in re.findall(r"([A-Z][a-z]+)", cls.__name__)
87 )
90def decode_slice(slc):
91 """decode a slice object as sent to __getitem__.
93 takes into account the 2.5 __index__() method, basically.
95 """
96 ret = []
97 for x in slc.start, slc.stop, slc.step:
98 if hasattr(x, "__index__"):
99 x = x.__index__()
100 ret.append(x)
101 return tuple(ret)
104def _unique_symbols(used, *bases):
105 used = set(used)
106 for base in bases:
107 pool = itertools.chain(
108 (base,),
109 compat.itertools_imap(lambda i: base + str(i), range(1000)),
110 )
111 for sym in pool:
112 if sym not in used:
113 used.add(sym)
114 yield sym
115 break
116 else:
117 raise NameError("exhausted namespace for symbol base %s" % base)
120def map_bits(fn, n):
121 """Call the given function given each nonzero bit from n."""
123 while n:
124 b = n & (~n + 1)
125 yield fn(b)
126 n ^= b
129def decorator(target):
130 """A signature-matching decorator factory."""
132 def decorate(fn):
133 if not inspect.isfunction(fn) and not inspect.ismethod(fn):
134 raise Exception("not a decoratable function")
136 spec = compat.inspect_getfullargspec(fn)
137 names = tuple(spec[0]) + spec[1:3] + (fn.__name__,)
138 targ_name, fn_name = _unique_symbols(names, "target", "fn")
140 metadata = dict(target=targ_name, fn=fn_name)
141 metadata.update(format_argspec_plus(spec, grouped=False))
142 metadata["name"] = fn.__name__
143 code = (
144 """\
145def %(name)s(%(args)s):
146 return %(target)s(%(fn)s, %(apply_kw)s)
147"""
148 % metadata
149 )
150 decorated = _exec_code_in_env(
151 code, {targ_name: target, fn_name: fn}, fn.__name__
152 )
153 decorated.__defaults__ = getattr(fn, "im_func", fn).__defaults__
154 decorated.__wrapped__ = fn
155 return update_wrapper(decorated, fn)
157 return update_wrapper(decorate, target)
160def _exec_code_in_env(code, env, fn_name):
161 exec(code, env)
162 return env[fn_name]
165def public_factory(target, location, class_location=None):
166 """Produce a wrapping function for the given cls or classmethod.
168 Rationale here is so that the __init__ method of the
169 class can serve as documentation for the function.
171 """
172 if isinstance(target, type):
173 fn = target.__init__
174 callable_ = target
175 doc = (
176 "Construct a new :class:`.%s` object. \n\n"
177 "This constructor is mirrored as a public API function; "
178 "see :func:`sqlalchemy%s` "
179 "for a full usage and argument description."
180 % (target.__name__, location)
181 )
182 else:
183 fn = callable_ = target
184 doc = (
185 "This function is mirrored; see :func:`sqlalchemy%s` "
186 "for a description of arguments." % location
187 )
189 location_name = location.split(".")[-1]
190 spec = compat.inspect_getfullargspec(fn)
191 del spec[0][0]
192 metadata = format_argspec_plus(spec, grouped=False)
193 metadata["name"] = location_name
194 code = (
195 """\
196def %(name)s(%(args)s):
197 return cls(%(apply_kw)s)
198"""
199 % metadata
200 )
201 env = {"cls": callable_, "symbol": symbol}
202 exec(code, env)
203 decorated = env[location_name]
204 if hasattr(fn, "_linked_to"):
205 linked_to, linked_to_location = fn._linked_to
206 linked_to_doc = linked_to.__doc__
207 if class_location is None:
208 class_location = "%s.%s" % (target.__module__, target.__name__)
210 linked_to_doc = inject_docstring_text(
211 linked_to_doc,
212 ".. container:: inherited_member\n\n "
213 "Inherited from :func:`sqlalchemy%s`; this constructor "
214 "creates a :class:`%s` object"
215 % (linked_to_location, class_location),
216 1,
217 )
218 decorated.__doc__ = linked_to_doc
219 else:
220 decorated.__doc__ = fn.__doc__
222 decorated.__module__ = "sqlalchemy" + location.rsplit(".", 1)[0]
223 if decorated.__module__ not in sys.modules:
224 raise ImportError(
225 "public_factory location %s is not in sys.modules"
226 % (decorated.__module__,)
227 )
228 if compat.py2k or hasattr(fn, "__func__"):
229 fn.__func__.__doc__ = doc
230 if not hasattr(fn.__func__, "_linked_to"):
231 fn.__func__._linked_to = (decorated, location)
232 else:
233 fn.__doc__ = doc
234 if not hasattr(fn, "_linked_to"):
235 fn._linked_to = (decorated, location)
236 return decorated
239class PluginLoader(object):
240 def __init__(self, group, auto_fn=None):
241 self.group = group
242 self.impls = {}
243 self.auto_fn = auto_fn
245 def clear(self):
246 self.impls.clear()
248 def load(self, name):
249 if name in self.impls:
250 return self.impls[name]()
252 if self.auto_fn:
253 loader = self.auto_fn(name)
254 if loader:
255 self.impls[name] = loader
256 return loader()
258 try:
259 import pkg_resources
260 except ImportError:
261 pass
262 else:
263 for impl in pkg_resources.iter_entry_points(self.group, name):
264 self.impls[name] = impl.load
265 return impl.load()
267 raise exc.NoSuchModuleError(
268 "Can't load plugin: %s:%s" % (self.group, name)
269 )
271 def register(self, name, modulepath, objname):
272 def load():
273 mod = compat.import_(modulepath)
274 for token in modulepath.split(".")[1:]:
275 mod = getattr(mod, token)
276 return getattr(mod, objname)
278 self.impls[name] = load
281def _inspect_func_args(fn):
282 try:
283 co_varkeywords = inspect.CO_VARKEYWORDS
284 except AttributeError:
285 # https://docs.python.org/3/library/inspect.html
286 # The flags are specific to CPython, and may not be defined in other
287 # Python implementations. Furthermore, the flags are an implementation
288 # detail, and can be removed or deprecated in future Python releases.
289 spec = compat.inspect_getfullargspec(fn)
290 return spec[0], bool(spec[2])
291 else:
292 # use fn.__code__ plus flags to reduce method call overhead
293 co = fn.__code__
294 nargs = co.co_argcount
295 return (
296 list(co.co_varnames[:nargs]),
297 bool(co.co_flags & co_varkeywords),
298 )
301def get_cls_kwargs(cls, _set=None):
302 r"""Return the full set of inherited kwargs for the given `cls`.
304 Probes a class's __init__ method, collecting all named arguments. If the
305 __init__ defines a \**kwargs catch-all, then the constructor is presumed
306 to pass along unrecognized keywords to its base classes, and the
307 collection process is repeated recursively on each of the bases.
309 Uses a subset of inspect.getfullargspec() to cut down on method overhead,
310 as this is used within the Core typing system to create copies of type
311 objects which is a performance-sensitive operation.
313 No anonymous tuple arguments please !
315 """
316 toplevel = _set is None
317 if toplevel:
318 _set = set()
320 ctr = cls.__dict__.get("__init__", False)
322 has_init = (
323 ctr
324 and isinstance(ctr, types.FunctionType)
325 and isinstance(ctr.__code__, types.CodeType)
326 )
328 if has_init:
329 names, has_kw = _inspect_func_args(ctr)
330 _set.update(names)
332 if not has_kw and not toplevel:
333 return None
335 if not has_init or has_kw:
336 for c in cls.__bases__:
337 if get_cls_kwargs(c, _set) is None:
338 break
340 _set.discard("self")
341 return _set
344def get_func_kwargs(func):
345 """Return the set of legal kwargs for the given `func`.
347 Uses getargspec so is safe to call for methods, functions,
348 etc.
350 """
352 return compat.inspect_getfullargspec(func)[0]
355def get_callable_argspec(fn, no_self=False, _is_init=False):
356 """Return the argument signature for any callable.
358 All pure-Python callables are accepted, including
359 functions, methods, classes, objects with __call__;
360 builtins and other edge cases like functools.partial() objects
361 raise a TypeError.
363 """
364 if inspect.isbuiltin(fn):
365 raise TypeError("Can't inspect builtin: %s" % fn)
366 elif inspect.isfunction(fn):
367 if _is_init and no_self:
368 spec = compat.inspect_getfullargspec(fn)
369 return compat.FullArgSpec(
370 spec.args[1:],
371 spec.varargs,
372 spec.varkw,
373 spec.defaults,
374 spec.kwonlyargs,
375 spec.kwonlydefaults,
376 spec.annotations,
377 )
378 else:
379 return compat.inspect_getfullargspec(fn)
380 elif inspect.ismethod(fn):
381 if no_self and (_is_init or fn.__self__):
382 spec = compat.inspect_getfullargspec(fn.__func__)
383 return compat.FullArgSpec(
384 spec.args[1:],
385 spec.varargs,
386 spec.varkw,
387 spec.defaults,
388 spec.kwonlyargs,
389 spec.kwonlydefaults,
390 spec.annotations,
391 )
392 else:
393 return compat.inspect_getfullargspec(fn.__func__)
394 elif inspect.isclass(fn):
395 return get_callable_argspec(
396 fn.__init__, no_self=no_self, _is_init=True
397 )
398 elif hasattr(fn, "__func__"):
399 return compat.inspect_getfullargspec(fn.__func__)
400 elif hasattr(fn, "__call__"):
401 if inspect.ismethod(fn.__call__):
402 return get_callable_argspec(fn.__call__, no_self=no_self)
403 else:
404 raise TypeError("Can't inspect callable: %s" % fn)
405 else:
406 raise TypeError("Can't inspect callable: %s" % fn)
409def format_argspec_plus(fn, grouped=True):
410 """Returns a dictionary of formatted, introspected function arguments.
412 A enhanced variant of inspect.formatargspec to support code generation.
414 fn
415 An inspectable callable or tuple of inspect getargspec() results.
416 grouped
417 Defaults to True; include (parens, around, argument) lists
419 Returns:
421 args
422 Full inspect.formatargspec for fn
423 self_arg
424 The name of the first positional argument, varargs[0], or None
425 if the function defines no positional arguments.
426 apply_pos
427 args, re-written in calling rather than receiving syntax. Arguments are
428 passed positionally.
429 apply_kw
430 Like apply_pos, except keyword-ish args are passed as keywords.
432 Example::
434 >>> format_argspec_plus(lambda self, a, b, c=3, **d: 123)
435 {'args': '(self, a, b, c=3, **d)',
436 'self_arg': 'self',
437 'apply_kw': '(self, a, b, c=c, **d)',
438 'apply_pos': '(self, a, b, c, **d)'}
440 """
441 if compat.callable(fn):
442 spec = compat.inspect_getfullargspec(fn)
443 else:
444 spec = fn
446 args = compat.inspect_formatargspec(*spec)
447 if spec[0]:
448 self_arg = spec[0][0]
449 elif spec[1]:
450 self_arg = "%s[0]" % spec[1]
451 else:
452 self_arg = None
454 apply_pos = compat.inspect_formatargspec(
455 spec[0], spec[1], spec[2], None, spec[4]
456 )
457 num_defaults = 0
458 if spec[3]:
459 num_defaults += len(spec[3])
460 if spec[4]:
461 num_defaults += len(spec[4])
462 name_args = spec[0] + spec[4]
464 if num_defaults:
465 defaulted_vals = name_args[0 - num_defaults :]
466 else:
467 defaulted_vals = ()
469 apply_kw = compat.inspect_formatargspec(
470 name_args,
471 spec[1],
472 spec[2],
473 defaulted_vals,
474 formatvalue=lambda x: "=" + x,
475 )
476 if grouped:
477 return dict(
478 args=args,
479 self_arg=self_arg,
480 apply_pos=apply_pos,
481 apply_kw=apply_kw,
482 )
483 else:
484 return dict(
485 args=args[1:-1],
486 self_arg=self_arg,
487 apply_pos=apply_pos[1:-1],
488 apply_kw=apply_kw[1:-1],
489 )
492def format_argspec_init(method, grouped=True):
493 """format_argspec_plus with considerations for typical __init__ methods
495 Wraps format_argspec_plus with error handling strategies for typical
496 __init__ cases::
498 object.__init__ -> (self)
499 other unreflectable (usually C) -> (self, *args, **kwargs)
501 """
502 if method is object.__init__:
503 args = grouped and "(self)" or "self"
504 else:
505 try:
506 return format_argspec_plus(method, grouped=grouped)
507 except TypeError:
508 args = (
509 grouped
510 and "(self, *args, **kwargs)"
511 or "self, *args, **kwargs"
512 )
513 return dict(self_arg="self", args=args, apply_pos=args, apply_kw=args)
516def getargspec_init(method):
517 """inspect.getargspec with considerations for typical __init__ methods
519 Wraps inspect.getargspec with error handling for typical __init__ cases::
521 object.__init__ -> (self)
522 other unreflectable (usually C) -> (self, *args, **kwargs)
524 """
525 try:
526 return compat.inspect_getfullargspec(method)
527 except TypeError:
528 if method is object.__init__:
529 return (["self"], None, None, None)
530 else:
531 return (["self"], "args", "kwargs", None)
534def unbound_method_to_callable(func_or_cls):
535 """Adjust the incoming callable such that a 'self' argument is not
536 required.
538 """
540 if isinstance(func_or_cls, types.MethodType) and not func_or_cls.__self__:
541 return func_or_cls.__func__
542 else:
543 return func_or_cls
546def generic_repr(obj, additional_kw=(), to_inspect=None, omit_kwarg=()):
547 """Produce a __repr__() based on direct association of the __init__()
548 specification vs. same-named attributes present.
550 """
551 if to_inspect is None:
552 to_inspect = [obj]
553 else:
554 to_inspect = _collections.to_list(to_inspect)
556 missing = object()
558 pos_args = []
559 kw_args = _collections.OrderedDict()
560 vargs = None
561 for i, insp in enumerate(to_inspect):
562 try:
563 spec = compat.inspect_getfullargspec(insp.__init__)
564 except TypeError:
565 continue
566 else:
567 default_len = spec.defaults and len(spec.defaults) or 0
568 if i == 0:
569 if spec.varargs:
570 vargs = spec.varargs
571 if default_len:
572 pos_args.extend(spec.args[1:-default_len])
573 else:
574 pos_args.extend(spec.args[1:])
575 else:
576 kw_args.update(
577 [(arg, missing) for arg in spec.args[1:-default_len]]
578 )
580 if default_len:
581 kw_args.update(
582 [
583 (arg, default)
584 for arg, default in zip(
585 spec.args[-default_len:], spec.defaults
586 )
587 ]
588 )
589 output = []
591 output.extend(repr(getattr(obj, arg, None)) for arg in pos_args)
593 if vargs is not None and hasattr(obj, vargs):
594 output.extend([repr(val) for val in getattr(obj, vargs)])
596 for arg, defval in kw_args.items():
597 if arg in omit_kwarg:
598 continue
599 try:
600 val = getattr(obj, arg, missing)
601 if val is not missing and val != defval:
602 output.append("%s=%r" % (arg, val))
603 except Exception:
604 pass
606 if additional_kw:
607 for arg, defval in additional_kw:
608 try:
609 val = getattr(obj, arg, missing)
610 if val is not missing and val != defval:
611 output.append("%s=%r" % (arg, val))
612 except Exception:
613 pass
615 return "%s(%s)" % (obj.__class__.__name__, ", ".join(output))
618class portable_instancemethod(object):
619 """Turn an instancemethod into a (parent, name) pair
620 to produce a serializable callable.
622 """
624 __slots__ = "target", "name", "kwargs", "__weakref__"
626 def __getstate__(self):
627 return {
628 "target": self.target,
629 "name": self.name,
630 "kwargs": self.kwargs,
631 }
633 def __setstate__(self, state):
634 self.target = state["target"]
635 self.name = state["name"]
636 self.kwargs = state.get("kwargs", ())
638 def __init__(self, meth, kwargs=()):
639 self.target = meth.__self__
640 self.name = meth.__name__
641 self.kwargs = kwargs
643 def __call__(self, *arg, **kw):
644 kw.update(self.kwargs)
645 return getattr(self.target, self.name)(*arg, **kw)
648def class_hierarchy(cls):
649 """Return an unordered sequence of all classes related to cls.
651 Traverses diamond hierarchies.
653 Fibs slightly: subclasses of builtin types are not returned. Thus
654 class_hierarchy(class A(object)) returns (A, object), not A plus every
655 class systemwide that derives from object.
657 Old-style classes are discarded and hierarchies rooted on them
658 will not be descended.
660 """
661 if compat.py2k:
662 if isinstance(cls, types.ClassType):
663 return list()
665 hier = {cls}
666 process = list(cls.__mro__)
667 while process:
668 c = process.pop()
669 if compat.py2k:
670 if isinstance(c, types.ClassType):
671 continue
672 bases = (
673 _
674 for _ in c.__bases__
675 if _ not in hier and not isinstance(_, types.ClassType)
676 )
677 else:
678 bases = (_ for _ in c.__bases__ if _ not in hier)
680 for b in bases:
681 process.append(b)
682 hier.add(b)
684 if compat.py3k:
685 if c.__module__ == "builtins" or not hasattr(c, "__subclasses__"):
686 continue
687 else:
688 if c.__module__ == "__builtin__" or not hasattr(
689 c, "__subclasses__"
690 ):
691 continue
693 for s in [_ for _ in c.__subclasses__() if _ not in hier]:
694 process.append(s)
695 hier.add(s)
696 return list(hier)
699def iterate_attributes(cls):
700 """iterate all the keys and attributes associated
701 with a class, without using getattr().
703 Does not use getattr() so that class-sensitive
704 descriptors (i.e. property.__get__()) are not called.
706 """
707 keys = dir(cls)
708 for key in keys:
709 for c in cls.__mro__:
710 if key in c.__dict__:
711 yield (key, c.__dict__[key])
712 break
715def monkeypatch_proxied_specials(
716 into_cls,
717 from_cls,
718 skip=None,
719 only=None,
720 name="self.proxy",
721 from_instance=None,
722):
723 """Automates delegation of __specials__ for a proxying type."""
725 if only:
726 dunders = only
727 else:
728 if skip is None:
729 skip = (
730 "__slots__",
731 "__del__",
732 "__getattribute__",
733 "__metaclass__",
734 "__getstate__",
735 "__setstate__",
736 )
737 dunders = [
738 m
739 for m in dir(from_cls)
740 if (
741 m.startswith("__")
742 and m.endswith("__")
743 and not hasattr(into_cls, m)
744 and m not in skip
745 )
746 ]
748 for method in dunders:
749 try:
750 fn = getattr(from_cls, method)
751 if not hasattr(fn, "__call__"):
752 continue
753 fn = getattr(fn, "im_func", fn)
754 except AttributeError:
755 continue
756 try:
757 spec = compat.inspect_getfullargspec(fn)
758 fn_args = compat.inspect_formatargspec(spec[0])
759 d_args = compat.inspect_formatargspec(spec[0][1:])
760 except TypeError:
761 fn_args = "(self, *args, **kw)"
762 d_args = "(*args, **kw)"
764 py = (
765 "def %(method)s%(fn_args)s: "
766 "return %(name)s.%(method)s%(d_args)s" % locals()
767 )
769 env = from_instance is not None and {name: from_instance} or {}
770 compat.exec_(py, env)
771 try:
772 env[method].__defaults__ = fn.__defaults__
773 except AttributeError:
774 pass
775 setattr(into_cls, method, env[method])
778def methods_equivalent(meth1, meth2):
779 """Return True if the two methods are the same implementation."""
781 return getattr(meth1, "__func__", meth1) is getattr(
782 meth2, "__func__", meth2
783 )
786def as_interface(obj, cls=None, methods=None, required=None):
787 """Ensure basic interface compliance for an instance or dict of callables.
789 Checks that ``obj`` implements public methods of ``cls`` or has members
790 listed in ``methods``. If ``required`` is not supplied, implementing at
791 least one interface method is sufficient. Methods present on ``obj`` that
792 are not in the interface are ignored.
794 If ``obj`` is a dict and ``dict`` does not meet the interface
795 requirements, the keys of the dictionary are inspected. Keys present in
796 ``obj`` that are not in the interface will raise TypeErrors.
798 Raises TypeError if ``obj`` does not meet the interface criteria.
800 In all passing cases, an object with callable members is returned. In the
801 simple case, ``obj`` is returned as-is; if dict processing kicks in then
802 an anonymous class is returned.
804 obj
805 A type, instance, or dictionary of callables.
806 cls
807 Optional, a type. All public methods of cls are considered the
808 interface. An ``obj`` instance of cls will always pass, ignoring
809 ``required``..
810 methods
811 Optional, a sequence of method names to consider as the interface.
812 required
813 Optional, a sequence of mandatory implementations. If omitted, an
814 ``obj`` that provides at least one interface method is considered
815 sufficient. As a convenience, required may be a type, in which case
816 all public methods of the type are required.
818 """
819 if not cls and not methods:
820 raise TypeError("a class or collection of method names are required")
822 if isinstance(cls, type) and isinstance(obj, cls):
823 return obj
825 interface = set(methods or [m for m in dir(cls) if not m.startswith("_")])
826 implemented = set(dir(obj))
828 complies = operator.ge
829 if isinstance(required, type):
830 required = interface
831 elif not required:
832 required = set()
833 complies = operator.gt
834 else:
835 required = set(required)
837 if complies(implemented.intersection(interface), required):
838 return obj
840 # No dict duck typing here.
841 if not isinstance(obj, dict):
842 qualifier = complies is operator.gt and "any of" or "all of"
843 raise TypeError(
844 "%r does not implement %s: %s"
845 % (obj, qualifier, ", ".join(interface))
846 )
848 class AnonymousInterface(object):
849 """A callable-holding shell."""
851 if cls:
852 AnonymousInterface.__name__ = "Anonymous" + cls.__name__
853 found = set()
855 for method, impl in dictlike_iteritems(obj):
856 if method not in interface:
857 raise TypeError("%r: unknown in this interface" % method)
858 if not compat.callable(impl):
859 raise TypeError("%r=%r is not callable" % (method, impl))
860 setattr(AnonymousInterface, method, staticmethod(impl))
861 found.add(method)
863 if complies(found, required):
864 return AnonymousInterface
866 raise TypeError(
867 "dictionary does not contain required keys %s"
868 % ", ".join(required - found)
869 )
872class memoized_property(object):
873 """A read-only @property that is only evaluated once."""
875 def __init__(self, fget, doc=None):
876 self.fget = fget
877 self.__doc__ = doc or fget.__doc__
878 self.__name__ = fget.__name__
880 def __get__(self, obj, cls):
881 if obj is None:
882 return self
883 obj.__dict__[self.__name__] = result = self.fget(obj)
884 return result
886 def _reset(self, obj):
887 memoized_property.reset(obj, self.__name__)
889 @classmethod
890 def reset(cls, obj, name):
891 obj.__dict__.pop(name, None)
894def memoized_instancemethod(fn):
895 """Decorate a method memoize its return value.
897 Best applied to no-arg methods: memoization is not sensitive to
898 argument values, and will always return the same value even when
899 called with different arguments.
901 """
903 def oneshot(self, *args, **kw):
904 result = fn(self, *args, **kw)
906 def memo(*a, **kw):
907 return result
909 memo.__name__ = fn.__name__
910 memo.__doc__ = fn.__doc__
911 self.__dict__[fn.__name__] = memo
912 return result
914 return update_wrapper(oneshot, fn)
917class group_expirable_memoized_property(object):
918 """A family of @memoized_properties that can be expired in tandem."""
920 def __init__(self, attributes=()):
921 self.attributes = []
922 if attributes:
923 self.attributes.extend(attributes)
925 def expire_instance(self, instance):
926 """Expire all memoized properties for *instance*."""
927 stash = instance.__dict__
928 for attribute in self.attributes:
929 stash.pop(attribute, None)
931 def __call__(self, fn):
932 self.attributes.append(fn.__name__)
933 return memoized_property(fn)
935 def method(self, fn):
936 self.attributes.append(fn.__name__)
937 return memoized_instancemethod(fn)
940class MemoizedSlots(object):
941 """Apply memoized items to an object using a __getattr__ scheme.
943 This allows the functionality of memoized_property and
944 memoized_instancemethod to be available to a class using __slots__.
946 """
948 __slots__ = ()
950 def _fallback_getattr(self, key):
951 raise AttributeError(key)
953 def __getattr__(self, key):
954 if key.startswith("_memoized"):
955 raise AttributeError(key)
956 elif hasattr(self, "_memoized_attr_%s" % key):
957 value = getattr(self, "_memoized_attr_%s" % key)()
958 setattr(self, key, value)
959 return value
960 elif hasattr(self, "_memoized_method_%s" % key):
961 fn = getattr(self, "_memoized_method_%s" % key)
963 def oneshot(*args, **kw):
964 result = fn(*args, **kw)
966 def memo(*a, **kw):
967 return result
969 memo.__name__ = fn.__name__
970 memo.__doc__ = fn.__doc__
971 setattr(self, key, memo)
972 return result
974 oneshot.__doc__ = fn.__doc__
975 return oneshot
976 else:
977 return self._fallback_getattr(key)
980def dependency_for(modulename, add_to_all=False):
981 def decorate(obj):
982 tokens = modulename.split(".")
983 mod = compat.import_(
984 ".".join(tokens[0:-1]), globals(), locals(), [tokens[-1]]
985 )
986 mod = getattr(mod, tokens[-1])
987 setattr(mod, obj.__name__, obj)
988 if add_to_all and hasattr(mod, "__all__"):
989 mod.__all__.append(obj.__name__)
990 return obj
992 return decorate
995class dependencies(object):
996 """Apply imported dependencies as arguments to a function.
998 E.g.::
1000 @util.dependencies(
1001 "sqlalchemy.sql.widget",
1002 "sqlalchemy.engine.default"
1003 );
1004 def some_func(self, widget, default, arg1, arg2, **kw):
1005 # ...
1007 Rationale is so that the impact of a dependency cycle can be
1008 associated directly with the few functions that cause the cycle,
1009 and not pollute the module-level namespace.
1011 """
1013 def __init__(self, *deps):
1014 self.import_deps = []
1015 for dep in deps:
1016 tokens = dep.split(".")
1017 self.import_deps.append(
1018 dependencies._importlater(".".join(tokens[0:-1]), tokens[-1])
1019 )
1021 def __call__(self, fn):
1022 import_deps = self.import_deps
1023 spec = compat.inspect_getfullargspec(fn)
1025 spec_zero = list(spec[0])
1026 hasself = spec_zero[0] in ("self", "cls")
1028 for i in range(len(import_deps)):
1029 spec[0][i + (1 if hasself else 0)] = "import_deps[%r]" % i
1031 inner_spec = format_argspec_plus(spec, grouped=False)
1033 for impname in import_deps:
1034 del spec_zero[1 if hasself else 0]
1035 spec[0][:] = spec_zero
1037 outer_spec = format_argspec_plus(spec, grouped=False)
1039 code = "lambda %(args)s: fn(%(apply_kw)s)" % {
1040 "args": outer_spec["args"],
1041 "apply_kw": inner_spec["apply_kw"],
1042 }
1044 decorated = eval(code, locals())
1045 decorated.__defaults__ = getattr(fn, "im_func", fn).__defaults__
1046 return update_wrapper(decorated, fn)
1048 @classmethod
1049 def resolve_all(cls, path):
1050 for m in list(dependencies._unresolved):
1051 if m._full_path.startswith(path):
1052 m._resolve()
1054 _unresolved = set()
1055 _by_key = {}
1057 class _importlater(object):
1058 _unresolved = set()
1060 _by_key = {}
1062 def __new__(cls, path, addtl):
1063 key = path + "." + addtl
1064 if key in dependencies._by_key:
1065 return dependencies._by_key[key]
1066 else:
1067 dependencies._by_key[key] = imp = object.__new__(cls)
1068 return imp
1070 def __init__(self, path, addtl):
1071 self._il_path = path
1072 self._il_addtl = addtl
1073 dependencies._unresolved.add(self)
1075 @property
1076 def _full_path(self):
1077 return self._il_path + "." + self._il_addtl
1079 @memoized_property
1080 def module(self):
1081 if self in dependencies._unresolved:
1082 raise ImportError(
1083 "importlater.resolve_all() hasn't "
1084 "been called (this is %s %s)"
1085 % (self._il_path, self._il_addtl)
1086 )
1088 return getattr(self._initial_import, self._il_addtl)
1090 def _resolve(self):
1091 dependencies._unresolved.discard(self)
1092 self._initial_import = compat.import_(
1093 self._il_path, globals(), locals(), [self._il_addtl]
1094 )
1096 def __getattr__(self, key):
1097 if key == "module":
1098 raise ImportError(
1099 "Could not resolve module %s" % self._full_path
1100 )
1101 try:
1102 attr = getattr(self.module, key)
1103 except AttributeError:
1104 raise AttributeError(
1105 "Module %s has no attribute '%s'" % (self._full_path, key)
1106 )
1107 self.__dict__[key] = attr
1108 return attr
1111# from paste.deploy.converters
1112def asbool(obj):
1113 if isinstance(obj, compat.string_types):
1114 obj = obj.strip().lower()
1115 if obj in ["true", "yes", "on", "y", "t", "1"]:
1116 return True
1117 elif obj in ["false", "no", "off", "n", "f", "0"]:
1118 return False
1119 else:
1120 raise ValueError("String is not true/false: %r" % obj)
1121 return bool(obj)
1124def bool_or_str(*text):
1125 """Return a callable that will evaluate a string as
1126 boolean, or one of a set of "alternate" string values.
1128 """
1130 def bool_or_value(obj):
1131 if obj in text:
1132 return obj
1133 else:
1134 return asbool(obj)
1136 return bool_or_value
1139def asint(value):
1140 """Coerce to integer."""
1142 if value is None:
1143 return value
1144 return int(value)
1147def coerce_kw_type(kw, key, type_, flexi_bool=True, dest=None):
1148 r"""If 'key' is present in dict 'kw', coerce its value to type 'type\_' if
1149 necessary. If 'flexi_bool' is True, the string '0' is considered false
1150 when coercing to boolean.
1151 """
1153 if dest is None:
1154 dest = kw
1156 if (
1157 key in kw
1158 and (not isinstance(type_, type) or not isinstance(kw[key], type_))
1159 and kw[key] is not None
1160 ):
1161 if type_ is bool and flexi_bool:
1162 dest[key] = asbool(kw[key])
1163 else:
1164 dest[key] = type_(kw[key])
1167def constructor_copy(obj, cls, *args, **kw):
1168 """Instantiate cls using the __dict__ of obj as constructor arguments.
1170 Uses inspect to match the named arguments of ``cls``.
1172 """
1174 names = get_cls_kwargs(cls)
1175 kw.update(
1176 (k, obj.__dict__[k]) for k in names.difference(kw) if k in obj.__dict__
1177 )
1178 return cls(*args, **kw)
1181def counter():
1182 """Return a threadsafe counter function."""
1184 lock = compat.threading.Lock()
1185 counter = itertools.count(1)
1187 # avoid the 2to3 "next" transformation...
1188 def _next():
1189 lock.acquire()
1190 try:
1191 return next(counter)
1192 finally:
1193 lock.release()
1195 return _next
1198def duck_type_collection(specimen, default=None):
1199 """Given an instance or class, guess if it is or is acting as one of
1200 the basic collection types: list, set and dict. If the __emulates__
1201 property is present, return that preferentially.
1202 """
1204 if hasattr(specimen, "__emulates__"):
1205 # canonicalize set vs sets.Set to a standard: the builtin set
1206 if specimen.__emulates__ is not None and issubclass(
1207 specimen.__emulates__, set
1208 ):
1209 return set
1210 else:
1211 return specimen.__emulates__
1213 isa = isinstance(specimen, type) and issubclass or isinstance
1214 if isa(specimen, list):
1215 return list
1216 elif isa(specimen, set):
1217 return set
1218 elif isa(specimen, dict):
1219 return dict
1221 if hasattr(specimen, "append"):
1222 return list
1223 elif hasattr(specimen, "add"):
1224 return set
1225 elif hasattr(specimen, "set"):
1226 return dict
1227 else:
1228 return default
1231def assert_arg_type(arg, argtype, name):
1232 if isinstance(arg, argtype):
1233 return arg
1234 else:
1235 if isinstance(argtype, tuple):
1236 raise exc.ArgumentError(
1237 "Argument '%s' is expected to be one of type %s, got '%s'"
1238 % (name, " or ".join("'%s'" % a for a in argtype), type(arg))
1239 )
1240 else:
1241 raise exc.ArgumentError(
1242 "Argument '%s' is expected to be of type '%s', got '%s'"
1243 % (name, argtype, type(arg))
1244 )
1247def dictlike_iteritems(dictlike):
1248 """Return a (key, value) iterator for almost any dict-like object."""
1250 if compat.py3k:
1251 if hasattr(dictlike, "items"):
1252 return list(dictlike.items())
1253 else:
1254 if hasattr(dictlike, "iteritems"):
1255 return dictlike.iteritems()
1256 elif hasattr(dictlike, "items"):
1257 return iter(dictlike.items())
1259 getter = getattr(dictlike, "__getitem__", getattr(dictlike, "get", None))
1260 if getter is None:
1261 raise TypeError("Object '%r' is not dict-like" % dictlike)
1263 if hasattr(dictlike, "iterkeys"):
1265 def iterator():
1266 for key in dictlike.iterkeys():
1267 yield key, getter(key)
1269 return iterator()
1270 elif hasattr(dictlike, "keys"):
1271 return iter((key, getter(key)) for key in dictlike.keys())
1272 else:
1273 raise TypeError("Object '%r' is not dict-like" % dictlike)
1276class classproperty(property):
1277 """A decorator that behaves like @property except that operates
1278 on classes rather than instances.
1280 The decorator is currently special when using the declarative
1281 module, but note that the
1282 :class:`~.sqlalchemy.ext.declarative.declared_attr`
1283 decorator should be used for this purpose with declarative.
1285 """
1287 def __init__(self, fget, *arg, **kw):
1288 super(classproperty, self).__init__(fget, *arg, **kw)
1289 self.__doc__ = fget.__doc__
1291 def __get__(desc, self, cls):
1292 return desc.fget(cls)
1295class hybridproperty(object):
1296 def __init__(self, func):
1297 self.func = func
1299 def __get__(self, instance, owner):
1300 if instance is None:
1301 clsval = self.func(owner)
1302 clsval.__doc__ = self.func.__doc__
1303 return clsval
1304 else:
1305 return self.func(instance)
1308class hybridmethod(object):
1309 """Decorate a function as cls- or instance- level."""
1311 def __init__(self, func):
1312 self.func = func
1314 def __get__(self, instance, owner):
1315 if instance is None:
1316 return self.func.__get__(owner, owner.__class__)
1317 else:
1318 return self.func.__get__(instance, owner)
1321class _symbol(int):
1322 def __new__(self, name, doc=None, canonical=None):
1323 """Construct a new named symbol."""
1324 assert isinstance(name, compat.string_types)
1325 if canonical is None:
1326 canonical = hash(name)
1327 v = int.__new__(_symbol, canonical)
1328 v.name = name
1329 if doc:
1330 v.__doc__ = doc
1331 return v
1333 def __reduce__(self):
1334 return symbol, (self.name, "x", int(self))
1336 def __str__(self):
1337 return repr(self)
1339 def __repr__(self):
1340 return "symbol(%r)" % self.name
1343_symbol.__name__ = "symbol"
1346class symbol(object):
1347 """A constant symbol.
1349 >>> symbol('foo') is symbol('foo')
1350 True
1351 >>> symbol('foo')
1352 <symbol 'foo>
1354 A slight refinement of the MAGICCOOKIE=object() pattern. The primary
1355 advantage of symbol() is its repr(). They are also singletons.
1357 Repeated calls of symbol('name') will all return the same instance.
1359 The optional ``doc`` argument assigns to ``__doc__``. This
1360 is strictly so that Sphinx autoattr picks up the docstring we want
1361 (it doesn't appear to pick up the in-module docstring if the datamember
1362 is in a different module - autoattribute also blows up completely).
1363 If Sphinx fixes/improves this then we would no longer need
1364 ``doc`` here.
1366 """
1368 symbols = {}
1369 _lock = compat.threading.Lock()
1371 def __new__(cls, name, doc=None, canonical=None):
1372 cls._lock.acquire()
1373 try:
1374 sym = cls.symbols.get(name)
1375 if sym is None:
1376 cls.symbols[name] = sym = _symbol(name, doc, canonical)
1377 return sym
1378 finally:
1379 symbol._lock.release()
1381 @classmethod
1382 def parse_user_argument(
1383 cls, arg, choices, name, resolve_symbol_names=False
1384 ):
1385 """Given a user parameter, parse the parameter into a chosen symbol.
1387 The user argument can be a string name that matches the name of a
1388 symbol, or the symbol object itself, or any number of alternate choices
1389 such as True/False/ None etc.
1391 :param arg: the user argument.
1392 :param choices: dictionary of symbol object to list of possible
1393 entries.
1394 :param name: name of the argument. Used in an :class:`.ArgumentError`
1395 that is raised if the parameter doesn't match any available argument.
1396 :param resolve_symbol_names: include the name of each symbol as a valid
1397 entry.
1399 """
1400 # note using hash lookup is tricky here because symbol's `__hash__`
1401 # is its int value which we don't want included in the lookup
1402 # explicitly, so we iterate and compare each.
1403 for sym, choice in choices.items():
1404 if arg is sym:
1405 return sym
1406 elif resolve_symbol_names and arg == sym.name:
1407 return sym
1408 elif arg in choice:
1409 return sym
1411 if arg is None:
1412 return None
1414 raise exc.ArgumentError("Invalid value for '%s': %r" % (name, arg))
1417_creation_order = 1
1420def set_creation_order(instance):
1421 """Assign a '_creation_order' sequence to the given instance.
1423 This allows multiple instances to be sorted in order of creation
1424 (typically within a single thread; the counter is not particularly
1425 threadsafe).
1427 """
1428 global _creation_order
1429 instance._creation_order = _creation_order
1430 _creation_order += 1
1433def warn_exception(func, *args, **kwargs):
1434 """executes the given function, catches all exceptions and converts to
1435 a warning.
1437 """
1438 try:
1439 return func(*args, **kwargs)
1440 except Exception:
1441 warn("%s('%s') ignored" % sys.exc_info()[0:2])
1444def ellipses_string(value, len_=25):
1445 try:
1446 if len(value) > len_:
1447 return "%s..." % value[0:len_]
1448 else:
1449 return value
1450 except TypeError:
1451 return value
1454class _hash_limit_string(compat.text_type):
1455 """A string subclass that can only be hashed on a maximum amount
1456 of unique values.
1458 This is used for warnings so that we can send out parameterized warnings
1459 without the __warningregistry__ of the module, or the non-overridable
1460 "once" registry within warnings.py, overloading memory,
1463 """
1465 def __new__(cls, value, num, args):
1466 interpolated = (value % args) + (
1467 " (this warning may be suppressed after %d occurrences)" % num
1468 )
1469 self = super(_hash_limit_string, cls).__new__(cls, interpolated)
1470 self._hash = hash("%s_%d" % (value, hash(interpolated) % num))
1471 return self
1473 def __hash__(self):
1474 return self._hash
1476 def __eq__(self, other):
1477 return hash(self) == hash(other)
1480def warn(msg):
1481 """Issue a warning.
1483 If msg is a string, :class:`.exc.SAWarning` is used as
1484 the category.
1486 """
1487 warnings.warn(msg, exc.SAWarning, stacklevel=2)
1490def warn_limited(msg, args):
1491 """Issue a warning with a parameterized string, limiting the number
1492 of registrations.
1494 """
1495 if args:
1496 msg = _hash_limit_string(msg, 10, args)
1497 warnings.warn(msg, exc.SAWarning, stacklevel=2)
1500def only_once(fn, retry_on_exception):
1501 """Decorate the given function to be a no-op after it is called exactly
1502 once."""
1504 once = [fn]
1506 def go(*arg, **kw):
1507 # strong reference fn so that it isn't garbage collected,
1508 # which interferes with the event system's expectations
1509 strong_fn = fn # noqa
1510 if once:
1511 once_fn = once.pop()
1512 try:
1513 return once_fn(*arg, **kw)
1514 except:
1515 if retry_on_exception:
1516 once.insert(0, once_fn)
1517 raise
1519 return go
1522_SQLA_RE = re.compile(r"sqlalchemy/([a-z_]+/){0,2}[a-z_]+\.py")
1523_UNITTEST_RE = re.compile(r"unit(?:2|test2?/)")
1526def chop_traceback(tb, exclude_prefix=_UNITTEST_RE, exclude_suffix=_SQLA_RE):
1527 """Chop extraneous lines off beginning and end of a traceback.
1529 :param tb:
1530 a list of traceback lines as returned by ``traceback.format_stack()``
1532 :param exclude_prefix:
1533 a regular expression object matching lines to skip at beginning of
1534 ``tb``
1536 :param exclude_suffix:
1537 a regular expression object matching lines to skip at end of ``tb``
1538 """
1539 start = 0
1540 end = len(tb) - 1
1541 while start <= end and exclude_prefix.search(tb[start]):
1542 start += 1
1543 while start <= end and exclude_suffix.search(tb[end]):
1544 end -= 1
1545 return tb[start : end + 1]
1548NoneType = type(None)
1551def attrsetter(attrname):
1552 code = "def set(obj, value):" " obj.%s = value" % attrname
1553 env = locals().copy()
1554 exec(code, env)
1555 return env["set"]
1558class EnsureKWArgType(type):
1559 r"""Apply translation of functions to accept \**kw arguments if they
1560 don't already.
1562 """
1564 def __init__(cls, clsname, bases, clsdict):
1565 fn_reg = cls.ensure_kwarg
1566 if fn_reg:
1567 for key in clsdict:
1568 m = re.match(fn_reg, key)
1569 if m:
1570 fn = clsdict[key]
1571 spec = compat.inspect_getfullargspec(fn)
1572 if not spec.varkw:
1573 clsdict[key] = wrapped = cls._wrap_w_kw(fn)
1574 setattr(cls, key, wrapped)
1575 super(EnsureKWArgType, cls).__init__(clsname, bases, clsdict)
1577 def _wrap_w_kw(self, fn):
1578 def wrap(*arg, **kw):
1579 return fn(*arg)
1581 return update_wrapper(wrap, fn)
1584def wrap_callable(wrapper, fn):
1585 """Augment functools.update_wrapper() to work with objects with
1586 a ``__call__()`` method.
1588 :param fn:
1589 object with __call__ method
1591 """
1592 if hasattr(fn, "__name__"):
1593 return update_wrapper(wrapper, fn)
1594 else:
1595 _f = wrapper
1596 _f.__name__ = fn.__class__.__name__
1597 if hasattr(fn, "__module__"):
1598 _f.__module__ = fn.__module__
1600 if hasattr(fn.__call__, "__doc__") and fn.__call__.__doc__:
1601 _f.__doc__ = fn.__call__.__doc__
1602 elif fn.__doc__:
1603 _f.__doc__ = fn.__doc__
1605 return _f
1608def quoted_token_parser(value):
1609 """Parse a dotted identifier with accommodation for quoted names.
1611 Includes support for SQL-style double quotes as a literal character.
1613 E.g.::
1615 >>> quoted_token_parser("name")
1616 ["name"]
1617 >>> quoted_token_parser("schema.name")
1618 ["schema", "name"]
1619 >>> quoted_token_parser('"Schema"."Name"')
1620 ['Schema', 'Name']
1621 >>> quoted_token_parser('"Schema"."Name""Foo"')
1622 ['Schema', 'Name""Foo']
1624 """
1626 if '"' not in value:
1627 return value.split(".")
1629 # 0 = outside of quotes
1630 # 1 = inside of quotes
1631 state = 0
1632 result = [[]]
1633 idx = 0
1634 lv = len(value)
1635 while idx < lv:
1636 char = value[idx]
1637 if char == '"':
1638 if state == 1 and idx < lv - 1 and value[idx + 1] == '"':
1639 result[-1].append('"')
1640 idx += 1
1641 else:
1642 state ^= 1
1643 elif char == "." and state == 0:
1644 result.append([])
1645 else:
1646 result[-1].append(char)
1647 idx += 1
1649 return ["".join(token) for token in result]
1652def add_parameter_text(params, text):
1653 params = _collections.to_list(params)
1655 def decorate(fn):
1656 doc = fn.__doc__ is not None and fn.__doc__ or ""
1657 if doc:
1658 doc = inject_param_text(doc, {param: text for param in params})
1659 fn.__doc__ = doc
1660 return fn
1662 return decorate
1665def _dedent_docstring(text):
1666 split_text = text.split("\n", 1)
1667 if len(split_text) == 1:
1668 return text
1669 else:
1670 firstline, remaining = split_text
1671 if not firstline.startswith(" "):
1672 return firstline + "\n" + textwrap.dedent(remaining)
1673 else:
1674 return textwrap.dedent(text)
1677def inject_docstring_text(doctext, injecttext, pos):
1678 doctext = _dedent_docstring(doctext or "")
1679 lines = doctext.split("\n")
1680 if len(lines) == 1:
1681 lines.append("")
1682 injectlines = textwrap.dedent(injecttext).split("\n")
1683 if injectlines[0]:
1684 injectlines.insert(0, "")
1686 blanks = [num for num, line in enumerate(lines) if not line.strip()]
1687 blanks.insert(0, 0)
1689 inject_pos = blanks[min(pos, len(blanks) - 1)]
1691 lines = lines[0:inject_pos] + injectlines + lines[inject_pos:]
1692 return "\n".join(lines)
1695def inject_param_text(doctext, inject_params):
1696 doclines = doctext.splitlines()
1697 lines = []
1699 to_inject = None
1700 while doclines:
1701 line = doclines.pop(0)
1702 if to_inject is None:
1703 m = re.match(r"(\s+):param (?:\\\*\*?)?(.+?):", line)
1704 if m:
1705 param = m.group(2)
1706 if param in inject_params:
1707 # default indent to that of :param: plus one
1708 indent = " " * len(m.group(1)) + " "
1710 # but if the next line has text, use that line's
1711 # indentntation
1712 if doclines:
1713 m2 = re.match(r"(\s+)\S", doclines[0])
1714 if m2:
1715 indent = " " * len(m2.group(1))
1717 to_inject = indent + inject_params[param]
1718 elif line.lstrip().startswith(":param "):
1719 lines.append("\n")
1720 lines.append(to_inject)
1721 lines.append("\n")
1722 to_inject = None
1723 elif not line.rstrip():
1724 lines.append(line)
1725 lines.append(to_inject)
1726 lines.append("\n")
1727 to_inject = None
1728 elif line.endswith("::"):
1729 # TODO: this still wont cover if the code example itself has blank
1730 # lines in it, need to detect those via indentation.
1731 lines.append(line)
1732 lines.append(
1733 doclines.pop(0)
1734 ) # the blank line following a code example
1735 continue
1736 lines.append(line)
1738 return "\n".join(lines)