demandimport.py
280 lines
| 9.4 KiB
| text/x-python
|
PythonLexer
/ mercurial / demandimport.py
|
r3877 | # demandimport.py - global demand-loading of modules for Mercurial | ||
# | ||||
|
r4635 | # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com> | ||
|
r3877 | # | ||
|
r8225 | # This software may be used and distributed according to the terms of the | ||
|
r10263 | # GNU General Public License version 2 or any later version. | ||
|
r3877 | |||
''' | ||||
demandimport - automatic demandloading of modules | ||||
To enable this module, do: | ||||
import demandimport; demandimport.enable() | ||||
Imports of the following forms will be demand-loaded: | ||||
import a, b.c | ||||
import a.b as c | ||||
from a import b,c # a will be loaded immediately | ||||
These imports will not be delayed: | ||||
from a import * | ||||
b = __import__(a) | ||||
''' | ||||
|
r25943 | from __future__ import absolute_import | ||
import contextlib | ||||
import os | ||||
import sys | ||||
|
r25674 | |||
# __builtin__ in Python 2, builtins in Python 3. | ||||
try: | ||||
import __builtin__ as builtins | ||||
except ImportError: | ||||
import builtins | ||||
|
r25327 | |||
|
r25943 | contextmanager = contextlib.contextmanager | ||
|
r3877 | _origimport = __import__ | ||
|
r14977 | nothing = object() | ||
|
r25933 | # Python 3 doesn't have relative imports nor level -1. | ||
level = -1 | ||||
if sys.version_info[0] >= 3: | ||||
level = 0 | ||||
_import = _origimport | ||||
|
r15096 | |||
|
r25936 | def _hgextimport(importfunc, name, globals, *args, **kwargs): | ||
|
r19933 | try: | ||
|
r25936 | return importfunc(name, globals, *args, **kwargs) | ||
|
r19933 | except ImportError: | ||
if not globals: | ||||
raise | ||||
# extensions are loaded with "hgext_" prefix | ||||
hgextname = 'hgext_%s' % name | ||||
nameroot = hgextname.split('.', 1)[0] | ||||
contextroot = globals.get('__name__', '').split('.', 1)[0] | ||||
if nameroot != contextroot: | ||||
raise | ||||
# retry to import with "hgext_" prefix | ||||
|
r25936 | return importfunc(hgextname, globals, *args, **kwargs) | ||
|
r19933 | |||
|
r3877 | class _demandmod(object): | ||
"""module demand-loader and proxy""" | ||||
|
r21290 | def __init__(self, name, globals, locals, level=level): | ||
|
r3877 | if '.' in name: | ||
head, rest = name.split('.', 1) | ||||
after = [rest] | ||||
else: | ||||
head = name | ||||
after = [] | ||||
|
r19932 | object.__setattr__(self, "_data", | ||
|
r26457 | (head, globals, locals, after, level, set())) | ||
|
r3896 | object.__setattr__(self, "_module", None) | ||
|
r3877 | def _extend(self, name): | ||
"""add to the list of submodules to load""" | ||||
self._data[3].append(name) | ||||
|
r26457 | |||
def _addref(self, name): | ||||
"""Record that the named module ``name`` imports this module. | ||||
References to this proxy class having the name of this module will be | ||||
replaced at module load time. We assume the symbol inside the importing | ||||
module is identical to the "head" name of this module. We don't | ||||
actually know if "as X" syntax is being used to change the symbol name | ||||
because this information isn't exposed to __import__. | ||||
""" | ||||
self._data[5].add(name) | ||||
|
r3877 | def _load(self): | ||
if not self._module: | ||||
|
r26457 | head, globals, locals, after, level, modrefs = self._data | ||
|
r19933 | mod = _hgextimport(_import, head, globals, locals, None, level) | ||
|
r3877 | # load submodules | ||
|
r3921 | def subload(mod, p): | ||
h, t = p, None | ||||
if '.' in p: | ||||
h, t = p.split('.', 1) | ||||
|
r14977 | if getattr(mod, h, nothing) is nothing: | ||
|
r12894 | setattr(mod, h, _demandmod(p, mod.__dict__, mod.__dict__)) | ||
|
r3926 | elif t: | ||
|
r3921 | subload(getattr(mod, h), t) | ||
|
r3877 | for x in after: | ||
|
r3921 | subload(mod, x) | ||
|
r26457 | # Replace references to this proxy instance with the actual module. | ||
|
r3877 | if locals and locals.get(head) == self: | ||
locals[head] = mod | ||||
|
r26457 | |||
for modname in modrefs: | ||||
modref = sys.modules.get(modname, None) | ||||
if modref and getattr(modref, head, None) == self: | ||||
setattr(modref, head, mod) | ||||
|
r3896 | object.__setattr__(self, "_module", mod) | ||
|
r4631 | |||
|
r3877 | def __repr__(self): | ||
|
r4631 | if self._module: | ||
return "<proxied module '%s'>" % self._data[0] | ||||
|
r3877 | return "<unloaded module '%s'>" % self._data[0] | ||
def __call__(self, *args, **kwargs): | ||||
|
r5639 | raise TypeError("%s object is not callable" % repr(self)) | ||
|
r3903 | def __getattribute__(self, attr): | ||
|
r26457 | if attr in ('_data', '_extend', '_load', '_module', '_addref'): | ||
|
r3903 | return object.__getattribute__(self, attr) | ||
|
r3877 | self._load() | ||
return getattr(self._module, attr) | ||||
def __setattr__(self, attr, val): | ||||
self._load() | ||||
setattr(self._module, attr, val) | ||||
|
r27536 | _pypy = '__pypy__' in sys.builtin_module_names | ||
|
r21290 | def _demandimport(name, globals=None, locals=None, fromlist=None, level=level): | ||
|
r3877 | if not locals or name in ignore or fromlist == ('*',): | ||
# these cases we can't really delay | ||||
|
r19933 | return _hgextimport(_import, name, globals, locals, fromlist, level) | ||
|
r3877 | elif not fromlist: | ||
# import a [as b] | ||||
if '.' in name: # a.b | ||||
base, rest = name.split('.', 1) | ||||
|
r3903 | # email.__init__ loading email.mime | ||
if globals and globals.get('__name__', None) == base: | ||||
|
r15096 | return _import(name, globals, locals, fromlist, level) | ||
|
r3877 | # if a is already demand-loaded, add b to its submodule list | ||
if base in locals: | ||||
if isinstance(locals[base], _demandmod): | ||||
locals[base]._extend(rest) | ||||
return locals[base] | ||||
|
r19932 | return _demandmod(name, globals, locals, level) | ||
|
r3877 | else: | ||
|
r25935 | # There is a fromlist. | ||
# from a import b,c,d | ||||
# from . import b,c,d | ||||
# from .a import b,c,d | ||||
# level == -1: relative and absolute attempted (Python 2 only). | ||||
# level >= 0: absolute only (Python 2 w/ absolute_import and Python 3). | ||||
# The modern Mercurial convention is to use absolute_import everywhere, | ||||
# so modern Mercurial code will have level >= 0. | ||||
|
r26457 | # The name of the module the import statement is located in. | ||
globalname = globals.get('__name__') | ||||
|
r26873 | def processfromitem(mod, attr): | ||
|
r26455 | """Process an imported symbol in the import statement. | ||
If the symbol doesn't exist in the parent module, it must be a | ||||
module. We set missing modules up as _demandmod instances. | ||||
""" | ||||
|
r26456 | symbol = getattr(mod, attr, nothing) | ||
if symbol is nothing: | ||||
|
r26873 | symbol = _demandmod(attr, mod.__dict__, locals, level=1) | ||
|
r26456 | setattr(mod, attr, symbol) | ||
|
r26455 | |||
|
r26457 | # Record the importing module references this symbol so we can | ||
# replace the symbol with the actual module instance at load | ||||
# time. | ||||
if globalname and isinstance(symbol, _demandmod): | ||||
symbol._addref(globalname) | ||||
|
r25935 | if level >= 0: | ||
|
r27537 | # The "from a import b,c,d" or "from .a import b,c,d" | ||
# syntax gives errors with some modules for unknown | ||||
# reasons. Work around the problem. | ||||
|
r25937 | if name: | ||
return _hgextimport(_origimport, name, globals, locals, | ||||
fromlist, level) | ||||
|
r27536 | if _pypy: | ||
# PyPy's __import__ throws an exception if invoked | ||||
# with an empty name and no fromlist. Recreate the | ||||
# desired behaviour by hand. | ||||
mn = globalname | ||||
mod = sys.modules[mn] | ||||
if getattr(mod, '__path__', nothing) is nothing: | ||||
mn = mn.rsplit('.', 1)[0] | ||||
mod = sys.modules[mn] | ||||
if level > 1: | ||||
mn = mn.rsplit('.', level - 1)[0] | ||||
mod = sys.modules[mn] | ||||
else: | ||||
mod = _hgextimport(_origimport, name, globals, locals, | ||||
level=level) | ||||
|
r26455 | |||
|
r25937 | for x in fromlist: | ||
|
r26873 | processfromitem(mod, x) | ||
|
r25937 | |||
return mod | ||||
|
r25935 | |||
# But, we still need to support lazy loading of standard library and 3rd | ||||
# party modules. So handle level == -1. | ||||
|
r19933 | mod = _hgextimport(_origimport, name, globals, locals) | ||
|
r3877 | # recurse down the module chain | ||
for comp in name.split('.')[1:]: | ||||
|
r14977 | if getattr(mod, comp, nothing) is nothing: | ||
|
r25935 | setattr(mod, comp, | ||
_demandmod(comp, mod.__dict__, mod.__dict__)) | ||||
|
r3877 | mod = getattr(mod, comp) | ||
|
r26455 | |||
|
r3877 | for x in fromlist: | ||
|
r26455 | processfromitem(mod, x) | ||
|
r3877 | return mod | ||
|
r5098 | ignore = [ | ||
|
r25934 | '__future__', | ||
|
r5098 | '_hashlib', | ||
'_xmlplus', | ||||
'fcntl', | ||||
'win32com.gen_py', | ||||
|
r10242 | '_winreg', # 2.7 mimetypes needs immediate ImportError | ||
|
r7861 | 'pythoncom', | ||
|
r5098 | # imported by tarfile, not available under Windows | ||
'pwd', | ||||
'grp', | ||||
# imported by profile, itself imported by hotshot.stats, | ||||
# not available under Windows | ||||
'resource', | ||||
|
r9458 | # this trips up many extension authors | ||
'gtk', | ||||
|
r10598 | # setuptools' pkg_resources.py expects "from __main__ import x" to | ||
# raise ImportError if x not defined | ||||
'__main__', | ||||
|
r10612 | '_ssl', # conditional imports in the stdlib, issue1964 | ||
|
r26830 | '_sre', # issue4920 | ||
|
r14976 | 'rfc822', | ||
'mimetools', | ||||
|
r23643 | # setuptools 8 expects this module to explode early when not on windows | ||
'distutils.msvc9compiler' | ||||
|
r5098 | ] | ||
|
r3877 | |||
|
r20422 | def isenabled(): | ||
|
r25673 | return builtins.__import__ == _demandimport | ||
|
r20422 | |||
|
r3877 | def enable(): | ||
"enable global demand-loading of modules" | ||||
|
r21025 | if os.environ.get('HGDEMANDIMPORT') != 'disable': | ||
|
r25673 | builtins.__import__ = _demandimport | ||
|
r3877 | |||
def disable(): | ||||
"disable global demand-loading of modules" | ||||
|
r25673 | builtins.__import__ = _origimport | ||
|
r25327 | |||
@contextmanager | ||||
def deactivated(): | ||||
"context manager for disabling demandimport in 'with' blocks" | ||||
demandenabled = isenabled() | ||||
if demandenabled: | ||||
disable() | ||||
try: | ||||
yield | ||||
finally: | ||||
if demandenabled: | ||||
enable() | ||||