From: Mike Bayer Date: Wed, 7 Oct 2020 01:46:03 +0000 (-0400) Subject: Use preloaded for sql.util import in exc X-Git-Tag: rel_1_3_20~8 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=4ea9f4ba745ebea8bd4d8a7bc01403dfe50589af;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Use preloaded for sql.util import in exc Repaired a function-level import that was not using SQLAlchemy's standard late-import system within the sqlalchemy.exc module. Moves 1.3 dependencies() to sqlalchemy.util.preloaded so that it does not depend on langhelpers which depends on exc. Fixes: #5632 Change-Id: I61b7ce9cd461071ce543714739f67aa5aeb47fd6 (cherry picked with major changes from commit 2f28ba5513fc7da4efb315e1a03d50ebe40ca566) --- diff --git a/doc/build/changelog/unreleased_13/5632.rst b/doc/build/changelog/unreleased_13/5632.rst new file mode 100644 index 0000000000..fb906944ee --- /dev/null +++ b/doc/build/changelog/unreleased_13/5632.rst @@ -0,0 +1,7 @@ +.. change:: + :tags: bug, engine + :tickets: 5632 + + Repaired a function-level import that was not using SQLAlchemy's standard + late-import system within the sqlalchemy.exc module. + diff --git a/lib/sqlalchemy/exc.py b/lib/sqlalchemy/exc.py index a03fd8bb52..5694c6ae3e 100644 --- a/lib/sqlalchemy/exc.py +++ b/lib/sqlalchemy/exc.py @@ -13,6 +13,7 @@ raised as a result of DBAPI exceptions are all subclasses of """ +from .util import _preloaded from .util import compat _version_token = None @@ -384,8 +385,8 @@ class StatementError(SQLAlchemyError): ), ) - def _sql_message(self, as_unicode): - from sqlalchemy.sql import util + @_preloaded.dependencies("sqlalchemy.sql.util") + def _sql_message(self, util, as_unicode): details = [self._message(as_unicode=as_unicode)] if self.statement: diff --git a/lib/sqlalchemy/util/__init__.py b/lib/sqlalchemy/util/__init__.py index 9c08dfeaa0..77840ffa1c 100644 --- a/lib/sqlalchemy/util/__init__.py +++ b/lib/sqlalchemy/util/__init__.py @@ -43,6 +43,7 @@ from ._collections import UniqueAppender # noqa from ._collections import update_copy # noqa from ._collections import WeakPopulateDict # noqa from ._collections import WeakSequence # noqa +from ._preloaded import dependencies # noqa from .compat import arm # noqa from .compat import b # noqa from .compat import b64decode # noqa @@ -113,7 +114,6 @@ from .langhelpers import constructor_copy # noqa from .langhelpers import counter # noqa from .langhelpers import decode_slice # noqa from .langhelpers import decorator # noqa -from .langhelpers import dependencies # noqa from .langhelpers import dictlike_iteritems # noqa from .langhelpers import duck_type_collection # noqa from .langhelpers import ellipses_string # noqa diff --git a/lib/sqlalchemy/util/_preloaded.py b/lib/sqlalchemy/util/_preloaded.py new file mode 100644 index 0000000000..ecd0e9e17c --- /dev/null +++ b/lib/sqlalchemy/util/_preloaded.py @@ -0,0 +1,208 @@ +# util/_preloaded.py +# Copyright (C) 2005-2020 the SQLAlchemy authors and contributors +# +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +"""Legacy routines to resolve circular module imports at runtime. + +These routines are replaced in 1.4. + +""" + +from functools import update_wrapper + +from . import compat + + +class _memoized_property(object): + """vendored version of langhelpers.memoized_property. + + not needed in the 1.4 version of preloaded. + + """ + + def __init__(self, fget, doc=None): + self.fget = fget + self.__doc__ = doc or fget.__doc__ + self.__name__ = fget.__name__ + + def __get__(self, obj, cls): + if obj is None: + return self + obj.__dict__[self.__name__] = result = self.fget(obj) + return result + + +def _format_argspec_plus(fn, grouped=True): + """vendored version of langhelpers._format_argspec_plus. + + not needed in the 1.4 version of preloaded. + + """ + if compat.callable(fn): + spec = compat.inspect_getfullargspec(fn) + else: + spec = fn + + args = compat.inspect_formatargspec(*spec) + if spec[0]: + self_arg = spec[0][0] + elif spec[1]: + self_arg = "%s[0]" % spec[1] + else: + self_arg = None + + apply_pos = compat.inspect_formatargspec( + spec[0], spec[1], spec[2], None, spec[4] + ) + num_defaults = 0 + if spec[3]: + num_defaults += len(spec[3]) + if spec[4]: + num_defaults += len(spec[4]) + name_args = spec[0] + spec[4] + + if num_defaults: + defaulted_vals = name_args[0 - num_defaults :] + else: + defaulted_vals = () + + apply_kw = compat.inspect_formatargspec( + name_args, + spec[1], + spec[2], + defaulted_vals, + formatvalue=lambda x: "=" + x, + ) + if grouped: + return dict( + args=args, + self_arg=self_arg, + apply_pos=apply_pos, + apply_kw=apply_kw, + ) + else: + return dict( + args=args[1:-1], + self_arg=self_arg, + apply_pos=apply_pos[1:-1], + apply_kw=apply_kw[1:-1], + ) + + +class dependencies(object): + """Apply imported dependencies as arguments to a function. + + E.g.:: + + @util.dependencies( + "sqlalchemy.sql.widget", + "sqlalchemy.engine.default" + ); + def some_func(self, widget, default, arg1, arg2, **kw): + # ... + + Rationale is so that the impact of a dependency cycle can be + associated directly with the few functions that cause the cycle, + and not pollute the module-level namespace. + + """ + + def __init__(self, *deps): + self.import_deps = [] + for dep in deps: + tokens = dep.split(".") + self.import_deps.append( + dependencies._importlater(".".join(tokens[0:-1]), tokens[-1]) + ) + + def __call__(self, fn): + import_deps = self.import_deps + spec = compat.inspect_getfullargspec(fn) + + spec_zero = list(spec[0]) + hasself = spec_zero[0] in ("self", "cls") + + for i in range(len(import_deps)): + spec[0][i + (1 if hasself else 0)] = "import_deps[%r]" % i + + inner_spec = _format_argspec_plus(spec, grouped=False) + + for impname in import_deps: + del spec_zero[1 if hasself else 0] + spec[0][:] = spec_zero + + outer_spec = _format_argspec_plus(spec, grouped=False) + + code = "lambda %(args)s: fn(%(apply_kw)s)" % { + "args": outer_spec["args"], + "apply_kw": inner_spec["apply_kw"], + } + + decorated = eval(code, locals()) + decorated.__defaults__ = getattr(fn, "im_func", fn).__defaults__ + return update_wrapper(decorated, fn) + + @classmethod + def resolve_all(cls, path): + for m in list(dependencies._unresolved): + if m._full_path.startswith(path): + m._resolve() + + _unresolved = set() + _by_key = {} + + class _importlater(object): + _unresolved = set() + + _by_key = {} + + def __new__(cls, path, addtl): + key = path + "." + addtl + if key in dependencies._by_key: + return dependencies._by_key[key] + else: + dependencies._by_key[key] = imp = object.__new__(cls) + return imp + + def __init__(self, path, addtl): + self._il_path = path + self._il_addtl = addtl + dependencies._unresolved.add(self) + + @property + def _full_path(self): + return self._il_path + "." + self._il_addtl + + @_memoized_property + def module(self): + if self in dependencies._unresolved: + raise ImportError( + "importlater.resolve_all() hasn't " + "been called (this is %s %s)" + % (self._il_path, self._il_addtl) + ) + + return getattr(self._initial_import, self._il_addtl) + + def _resolve(self): + dependencies._unresolved.discard(self) + self._initial_import = compat.import_( + self._il_path, globals(), locals(), [self._il_addtl] + ) + + def __getattr__(self, key): + if key == "module": + raise ImportError( + "Could not resolve module %s" % self._full_path + ) + try: + attr = getattr(self.module, key) + except AttributeError: + raise AttributeError( + "Module %s has no attribute '%s'" % (self._full_path, key) + ) + self.__dict__[key] = attr + return attr diff --git a/lib/sqlalchemy/util/langhelpers.py b/lib/sqlalchemy/util/langhelpers.py index ffa5e03958..7de16bcdfd 100644 --- a/lib/sqlalchemy/util/langhelpers.py +++ b/lib/sqlalchemy/util/langhelpers.py @@ -993,123 +993,6 @@ def dependency_for(modulename, add_to_all=False): return decorate -class dependencies(object): - """Apply imported dependencies as arguments to a function. - - E.g.:: - - @util.dependencies( - "sqlalchemy.sql.widget", - "sqlalchemy.engine.default" - ); - def some_func(self, widget, default, arg1, arg2, **kw): - # ... - - Rationale is so that the impact of a dependency cycle can be - associated directly with the few functions that cause the cycle, - and not pollute the module-level namespace. - - """ - - def __init__(self, *deps): - self.import_deps = [] - for dep in deps: - tokens = dep.split(".") - self.import_deps.append( - dependencies._importlater(".".join(tokens[0:-1]), tokens[-1]) - ) - - def __call__(self, fn): - import_deps = self.import_deps - spec = compat.inspect_getfullargspec(fn) - - spec_zero = list(spec[0]) - hasself = spec_zero[0] in ("self", "cls") - - for i in range(len(import_deps)): - spec[0][i + (1 if hasself else 0)] = "import_deps[%r]" % i - - inner_spec = format_argspec_plus(spec, grouped=False) - - for impname in import_deps: - del spec_zero[1 if hasself else 0] - spec[0][:] = spec_zero - - outer_spec = format_argspec_plus(spec, grouped=False) - - code = "lambda %(args)s: fn(%(apply_kw)s)" % { - "args": outer_spec["args"], - "apply_kw": inner_spec["apply_kw"], - } - - decorated = eval(code, locals()) - decorated.__defaults__ = getattr(fn, "im_func", fn).__defaults__ - return update_wrapper(decorated, fn) - - @classmethod - def resolve_all(cls, path): - for m in list(dependencies._unresolved): - if m._full_path.startswith(path): - m._resolve() - - _unresolved = set() - _by_key = {} - - class _importlater(object): - _unresolved = set() - - _by_key = {} - - def __new__(cls, path, addtl): - key = path + "." + addtl - if key in dependencies._by_key: - return dependencies._by_key[key] - else: - dependencies._by_key[key] = imp = object.__new__(cls) - return imp - - def __init__(self, path, addtl): - self._il_path = path - self._il_addtl = addtl - dependencies._unresolved.add(self) - - @property - def _full_path(self): - return self._il_path + "." + self._il_addtl - - @memoized_property - def module(self): - if self in dependencies._unresolved: - raise ImportError( - "importlater.resolve_all() hasn't " - "been called (this is %s %s)" - % (self._il_path, self._il_addtl) - ) - - return getattr(self._initial_import, self._il_addtl) - - def _resolve(self): - dependencies._unresolved.discard(self) - self._initial_import = compat.import_( - self._il_path, globals(), locals(), [self._il_addtl] - ) - - def __getattr__(self, key): - if key == "module": - raise ImportError( - "Could not resolve module %s" % self._full_path - ) - try: - attr = getattr(self.module, key) - except AttributeError: - raise AttributeError( - "Module %s has no attribute '%s'" % (self._full_path, key) - ) - self.__dict__[key] = attr - return attr - - -# from paste.deploy.converters def asbool(obj): if isinstance(obj, compat.string_types): obj = obj.strip().lower()