diff options
Diffstat (limited to 'lib/sqlalchemy/util/langhelpers.py')
| -rw-r--r-- | lib/sqlalchemy/util/langhelpers.py | 161 |
1 files changed, 40 insertions, 121 deletions
diff --git a/lib/sqlalchemy/util/langhelpers.py b/lib/sqlalchemy/util/langhelpers.py index 09aa94bf2..bac95c7e3 100644 --- a/lib/sqlalchemy/util/langhelpers.py +++ b/lib/sqlalchemy/util/langhelpers.py @@ -987,135 +987,54 @@ class MemoizedSlots(object): return self._fallback_getattr(key) -def dependency_for(modulename, add_to_all=False): - def decorate(obj): - tokens = modulename.split(".") - mod = compat.import_( - ".".join(tokens[0:-1]), globals(), locals(), [tokens[-1]] - ) - mod = getattr(mod, tokens[-1]) - setattr(mod, obj.__name__, obj) - if add_to_all and hasattr(mod, "__all__"): - mod.__all__.append(obj.__name__) - return obj - - 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. - +class _ModuleRegistry: + """Registry of modules to load in a package init file. + + To avoid potential thread safety issues for imports that are deferred + in a function, like https://bugs.python.org/issue38884, these modules + are added to the system module cache by importing them after the packages + has finished initialization. + + A global instance is provided under the name :attr:`.preloaded`. Use + the function :func:`.preload_module` to register modules to load and + :meth:`.import_prefix` to load all the modules that start with the + given path. + + While the modules are loaded in the global module cache, it's advisable + to access them using :attr:`.preloaded` to ensure that it was actually + registered. Each registered module is added to the instance ``__dict__`` + in the form `<package>_<module>`, omitting ``sqlalchemy`` from the package + name. Example: ``sqlalchemy.sql.util`` becomes ``preloaded.sql_util``. """ - 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) + def __init__(self, prefix="sqlalchemy"): + self.module_registry = set() - for impname in import_deps: - del spec_zero[1 if hasself else 0] - spec[0][:] = spec_zero + def preload_module(self, *deps): + """Adds the specified modules to the list to load. - 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 = {} + This method can be used both as a normal function and as a decorator. + No change is performed to the decorated object. + """ + self.module_registry.update(deps) + return lambda fn: fn - 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) + def import_prefix(self, path): + """Resolve all the modules in the registry that start with the + specified path. + """ + for module in self.module_registry: + key = module.split("sqlalchemy.")[-1].replace(".", "_") + if module.startswith(path) and key not in self.__dict__: + tokens = module.split(".") + compat.import_( + ".".join(tokens[0:-1]), globals(), locals(), [tokens[-1]] ) + self.__dict__[key] = sys.modules[module] - 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 +preloaded = _ModuleRegistry() +preload_module = preloaded.preload_module # from paste.deploy.converters |
