From 944e9029006792f0052d9870100d2f7c801942f1 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 21 Sep 2011 17:00:44 -0400 Subject: [PATCH] patch from 0.7: Adjusted the "importlater" mechanism, which is used internally to resolve import cycles, such that the usage of __import__ is completed when the import of sqlalchemy or sqlalchemy.orm is done, thereby avoiding any usage of __import__ after the application starts new threads, fixes [ticket:2279]. --- CHANGES | 9 +++++ lib/sqlalchemy/__init__.py | 3 ++ lib/sqlalchemy/orm/__init__.py | 3 ++ lib/sqlalchemy/sql/__init__.py | 1 + lib/sqlalchemy/util.py | 60 +++++++++++++++++++++++++--------- 5 files changed, 61 insertions(+), 15 deletions(-) diff --git a/CHANGES b/CHANGES index a7bb9a59fd..98a34e9d26 100644 --- a/CHANGES +++ b/CHANGES @@ -5,6 +5,15 @@ CHANGES ======= 0.6.9 ===== +- general + - Adjusted the "importlater" mechanism, which is + used internally to resolve import cycles, + such that the usage of __import__ is completed + when the import of sqlalchemy or sqlalchemy.orm + is done, thereby avoiding any usage of __import__ + after the application starts new threads, + fixes [ticket:2279]. + - orm - Fixed bug whereby the source clause used by query.join() would be inconsistent diff --git a/lib/sqlalchemy/__init__.py b/lib/sqlalchemy/__init__.py index 8d4e2a57d3..6af9bce750 100644 --- a/lib/sqlalchemy/__init__.py +++ b/lib/sqlalchemy/__init__.py @@ -118,3 +118,6 @@ __all__ = sorted(name for name, obj in locals().items() __version__ = '0.6.9' del inspect, sys + +from sqlalchemy import util as _sa_util +_sa_util.importlater.resolve_all() \ No newline at end of file diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py index 1e9959939b..3fdbd8795d 100644 --- a/lib/sqlalchemy/orm/__init__.py +++ b/lib/sqlalchemy/orm/__init__.py @@ -1315,3 +1315,6 @@ def undefer_group(name): """ return strategies.UndeferGroupOption(name) + +from sqlalchemy import util as _sa_util +_sa_util.importlater.resolve_all() \ No newline at end of file diff --git a/lib/sqlalchemy/sql/__init__.py b/lib/sqlalchemy/sql/__init__.py index 80aa769284..822c139099 100644 --- a/lib/sqlalchemy/sql/__init__.py +++ b/lib/sqlalchemy/sql/__init__.py @@ -63,3 +63,4 @@ from sqlalchemy.sql.visitors import ClauseVisitor __tmp = locals().keys() __all__ = sorted([i for i in __tmp if not i.startswith('__')]) + diff --git a/lib/sqlalchemy/util.py b/lib/sqlalchemy/util.py index 68cd185dd5..634c7d5191 100644 --- a/lib/sqlalchemy/util.py +++ b/lib/sqlalchemy/util.py @@ -1584,37 +1584,67 @@ class importlater(object): from mypackage.somemodule import somesubmod except evaluted upon attribute access to "somesubmod". + + importlater() currently requires that resolve_all() be + called, typically at the bottom of a package's __init__.py. + This is so that __import__ still called only at + module import time, and not potentially within + a non-main thread later on. """ + + _unresolved = set() + def __init__(self, path, addtl=None): self._il_path = path self._il_addtl = addtl + importlater._unresolved.add(self) + + @classmethod + def resolve_all(cls): + for m in list(importlater._unresolved): + m._resolve() + + @property + def _full_path(self): + if self._il_addtl: + return self._il_path + "." + self._il_addtl + else: + return self._il_path @memoized_property - def _il_module(self): + def module(self): + if self in importlater._unresolved: + raise ImportError( + "importlater.resolve_all() hasn't been called") + + m = self._initial_import if self._il_addtl: - m = __import__(self._il_path, globals(), locals(), - [self._il_addtl]) - try: - return getattr(m, self._il_addtl) - except AttributeError: - raise ImportError( - "Module %s has no attribute '%s'" % - (self._il_path, self._il_addtl) - ) + m = getattr(m, self._il_addtl) else: - m = __import__(self._il_path) for token in self._il_path.split(".")[1:]: m = getattr(m, token) - return m + return m + + def _resolve(self): + importlater._unresolved.discard(self) + if self._il_addtl: + self._initial_import = __import__( + self._il_path, globals(), locals(), + [self._il_addtl]) + else: + self._initial_import = __import__(self._il_path) def __getattr__(self, key): + if key == 'module': + raise ImportError("Could not resolve module %s" + % self._full_path) try: - attr = getattr(self._il_module, key) + attr = getattr(self.module, key) except AttributeError: raise AttributeError( - "Module %s has no attribute '%s'" % - (self._il_path, key) + "Module %s has no attribute '%s'" % + (self._full_path, key) ) self.__dict__[key] = attr return attr -- 2.47.3