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