# Copyright 2009-2012 Gentoo Foundation # Distributed under the terms of the GNU General Public License v2 __all__ = ['lazyimport'] import sys import types try: import threading except ImportError: import dummy_threading as threading from portage.proxy.objectproxy import ObjectProxy if sys.hexversion >= 0x3000000: basestring = str _module_proxies = {} _module_proxies_lock = threading.RLock() def _preload_portage_submodules(): """ Load lazily referenced portage submodules into memory, so imports won't fail during portage upgrade/downgrade. Note that this recursively loads only the modules that are lazily referenced by currently imported modules, so some portage submodules may still remain unimported after this function is called. """ imported = set() while True: remaining = False for name in list(_module_proxies): if name.startswith('portage.') or name.startswith('_emerge.'): if name in imported: continue imported.add(name) remaining = True __import__(name) _unregister_module_proxy(name) if not remaining: break def _register_module_proxy(name, proxy): _module_proxies_lock.acquire() try: proxy_list = _module_proxies.get(name) if proxy_list is None: proxy_list = [] _module_proxies[name] = proxy_list proxy_list.append(proxy) finally: _module_proxies_lock.release() def _unregister_module_proxy(name): """ Destroy all proxies that reference the give module name. Also, check for other proxies referenced by modules that have been imported and destroy those proxies too. This way, destruction of a single proxy can trigger destruction of all the rest. If a target module appears to be partially imported (indicated when an AttributeError is caught), this function will leave in place proxies that reference it. """ _module_proxies_lock.acquire() try: if name in _module_proxies: modules = sys.modules for name, proxy_list in list(_module_proxies.items()): if name not in modules: continue # First delete this name from the dict so that # if this same thread reenters below, it won't # enter this path again. del _module_proxies[name] try: while proxy_list: proxy = proxy_list.pop() object.__getattribute__(proxy, '_get_target')() except AttributeError: # Apparently the target module is only partially # imported, so proxies that reference it cannot # be destroyed yet. proxy_list.append(proxy) _module_proxies[name] = proxy_list finally: _module_proxies_lock.release() class _LazyImport(ObjectProxy): __slots__ = ('_scope', '_alias', '_name', '_target') def __init__(self, scope, alias, name): ObjectProxy.__init__(self) object.__setattr__(self, '_scope', scope) object.__setattr__(self, '_alias', alias) object.__setattr__(self, '_name', name) _register_module_proxy(name, self) def _get_target(self): try: return object.__getattribute__(self, '_target') except AttributeError: pass name = object.__getattribute__(self, '_name') __import__(name) target = sys.modules[name] object.__setattr__(self, '_target', target) object.__getattribute__(self, '_scope')[ object.__getattribute__(self, '_alias')] = target _unregister_module_proxy(name) return target class _LazyImportFrom(_LazyImport): __slots__ = ('_attr_name',) def __init__(self, scope, name, attr_name, alias): object.__setattr__(self, '_attr_name', attr_name) _LazyImport.__init__(self, scope, alias, name) def _get_target(self): try: return object.__getattribute__(self, '_target') except AttributeError: pass name = object.__getattribute__(self, '_name') attr_name = object.__getattribute__(self, '_attr_name') __import__(name) # If called by _unregister_module_proxy() and the target module is # partially imported, then the following getattr call may raise an # AttributeError for _unregister_module_proxy() to handle. target = getattr(sys.modules[name], attr_name) object.__setattr__(self, '_target', target) object.__getattribute__(self, '_scope')[ object.__getattribute__(self, '_alias')] = target _unregister_module_proxy(name) return target def lazyimport(scope, *args): """ Create a proxy in the given scope in order to performa a lazy import. Syntax Result foo import foo foo:bar,baz from foo import bar, baz foo:bar@baz from foo import bar as baz @param scope: the scope in which to place the import, typically globals() @type myfilename: dict @param args: module names to import @type args: strings """ modules = sys.modules for s in args: parts = s.split(':', 1) if len(parts) == 1: name = s if not name or not isinstance(name, basestring): raise ValueError(name) components = name.split('.') parent_scope = scope for i in range(len(components)): alias = components[i] if i < len(components) - 1: parent_name = ".".join(components[:i+1]) __import__(parent_name) mod = modules.get(parent_name) if not isinstance(mod, types.ModuleType): # raise an exception __import__(name) parent_scope[alias] = mod parent_scope = mod.__dict__ continue already_imported = modules.get(name) if already_imported is not None: parent_scope[alias] = already_imported else: parent_scope[alias] = \ _LazyImport(parent_scope, alias, name) else: name, fromlist = parts already_imported = modules.get(name) fromlist = fromlist.split(',') for s in fromlist: if not s: # This happens if there's an extra comma in fromlist. raise ValueError('Empty module attribute name') alias = s.split('@', 1) if len(alias) == 1: alias = alias[0] attr_name = alias else: attr_name, alias = alias if already_imported is not None: try: scope[alias] = getattr(already_imported, attr_name) except AttributeError: # Apparently the target module is only partially # imported, so create a proxy. already_imported = None scope[alias] = \ _LazyImportFrom(scope, name, attr_name, alias) else: scope[alias] = \ _LazyImportFrom(scope, name, attr_name, alias)