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