| # Copyright 2009 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.'): |
| 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) |