##// END OF EJS Templates
merge with default
merge with default

File last commit:

r47575:d4ba4d51 default
r49136:a44bb185 merge 6.0rc0 stable
Show More
demandimportpy2.py
326 lines | 11.0 KiB | text/x-python | PythonLexer
Siddharth Agarwal
demandimport: move to separate package...
r32420 # demandimport.py - global demand-loading of modules for Mercurial
#
Raphaël Gomès
contributor: change mentions of mpm to olivia...
r47575 # Copyright 2006, 2007 Olivia Mackall <olivia@selenic.com>
Siddharth Agarwal
demandimport: move to separate package...
r32420 #
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
'''
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)
'''
from __future__ import absolute_import
Yuya Nishihara
demandimport: drop Py3 workarounds from Py2 implementation
r33530 import __builtin__ as builtins
Siddharth Agarwal
demandimport: move to separate package...
r32420 import contextlib
import sys
Augie Fackler
demandimport: instrument python 2 code with trace events...
r39292 from . import tracing
Siddharth Agarwal
demandimport: move to separate package...
r32420 contextmanager = contextlib.contextmanager
_origimport = __import__
nothing = object()
Augie Fackler
formatting: blacken the codebase...
r43346
Siddharth Agarwal
demandimport: move to separate package...
r32420 def _hgextimport(importfunc, name, globals, *args, **kwargs):
try:
return importfunc(name, globals, *args, **kwargs)
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
return importfunc(hgextname, globals, *args, **kwargs)
Augie Fackler
formatting: blacken the codebase...
r43346
Siddharth Agarwal
demandimport: move to separate package...
r32420 class _demandmod(object):
"""module demand-loader and proxy
Specify 1 as 'level' argument at construction, to import module
relatively.
"""
Yuya Nishihara
demandimport: insert empty line per method...
r32446
Siddharth Agarwal
demandimport: move to separate package...
r32420 def __init__(self, name, globals, locals, level):
if '.' in name:
head, rest = name.split('.', 1)
after = [rest]
else:
head = name
after = []
Augie Fackler
formatting: blacken the codebase...
r43346 object.__setattr__(
Augie Fackler
cleanup: remove pointless r-prefixes on double-quoted strings...
r43809 self, "_data", (head, globals, locals, after, level, set())
Augie Fackler
formatting: blacken the codebase...
r43346 )
Augie Fackler
cleanup: remove pointless r-prefixes on double-quoted strings...
r43809 object.__setattr__(self, "_module", None)
Yuya Nishihara
demandimport: insert empty line per method...
r32446
Siddharth Agarwal
demandimport: move to separate package...
r32420 def _extend(self, name):
"""add to the list of submodules to load"""
self._data[3].append(name)
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)
def _load(self):
if not self._module:
Augie Fackler
demandimport: instrument python 2 code with trace events...
r39292 with tracing.log('demandimport %s', self._data[0]):
head, globals, locals, after, level, modrefs = self._data
mod = _hgextimport(
Augie Fackler
formatting: blacken the codebase...
r43346 _origimport, head, globals, locals, None, level
)
Augie Fackler
demandimport: instrument python 2 code with trace events...
r39292 if mod is self:
# In this case, _hgextimport() above should imply
# _demandimport(). Otherwise, _hgextimport() never
# returns _demandmod. This isn't intentional behavior,
# in fact. (see also issue5304 for detail)
#
# If self._module is already bound at this point, self
# should be already _load()-ed while _hgextimport().
# Otherwise, there is no way to import actual module
# as expected, because (re-)invoking _hgextimport()
# should cause same result.
# This is reason why _load() returns without any more
# setup but assumes self to be already bound.
mod = self._module
assert mod and mod is not self, "%s, %s" % (self, mod)
return
Siddharth Agarwal
demandimport: move to separate package...
r32420
Augie Fackler
demandimport: instrument python 2 code with trace events...
r39292 # load submodules
def subload(mod, p):
h, t = p, None
if '.' in p:
h, t = p.split('.', 1)
if getattr(mod, h, nothing) is nothing:
Augie Fackler
formatting: blacken the codebase...
r43346 setattr(
mod,
h,
_demandmod(p, mod.__dict__, mod.__dict__, level=1),
)
Augie Fackler
demandimport: instrument python 2 code with trace events...
r39292 elif t:
subload(getattr(mod, h), t)
Siddharth Agarwal
demandimport: move to separate package...
r32420
Augie Fackler
demandimport: instrument python 2 code with trace events...
r39292 for x in after:
subload(mod, x)
Siddharth Agarwal
demandimport: move to separate package...
r32420
Augie Fackler
demandimport: instrument python 2 code with trace events...
r39292 # Replace references to this proxy instance with the
# actual module.
if locals:
if locals.get(head) is self:
locals[head] = mod
Augie Fackler
cleanup: remove pointless r-prefixes on single-quoted strings...
r43906 elif locals.get(head + 'mod') is self:
locals[head + 'mod'] = mod
Siddharth Agarwal
demandimport: move to separate package...
r32420
Augie Fackler
demandimport: instrument python 2 code with trace events...
r39292 for modname in modrefs:
modref = sys.modules.get(modname, None)
if modref and getattr(modref, head, None) is self:
setattr(modref, head, mod)
Siddharth Agarwal
demandimport: move to separate package...
r32420
Augie Fackler
cleanup: remove pointless r-prefixes on double-quoted strings...
r43809 object.__setattr__(self, "_module", mod)
Siddharth Agarwal
demandimport: move to separate package...
r32420
def __repr__(self):
if self._module:
return "<proxied module '%s'>" % self._data[0]
return "<unloaded module '%s'>" % self._data[0]
Yuya Nishihara
demandimport: insert empty line per method...
r32446
Siddharth Agarwal
demandimport: move to separate package...
r32420 def __call__(self, *args, **kwargs):
raise TypeError("%s object is not callable" % repr(self))
Yuya Nishihara
demandimport: insert empty line per method...
r32446
Yuya Nishihara
demandimport: stop overriding __getattribute__()...
r32448 def __getattr__(self, attr):
Siddharth Agarwal
demandimport: move to separate package...
r32420 self._load()
return getattr(self._module, attr)
Yuya Nishihara
demandimport: insert empty line per method...
r32446
Siddharth Agarwal
demandimport: move to separate package...
r32420 def __setattr__(self, attr, val):
self._load()
setattr(self._module, attr, val)
Yuya Nishihara
demandimport: stop overriding __getattribute__()...
r32448 @property
def __dict__(self):
self._load()
return self._module.__dict__
@property
def __doc__(self):
self._load()
return self._module.__doc__
Augie Fackler
formatting: blacken the codebase...
r43346
Siddharth Agarwal
demandimport: move to separate package...
r32420 _pypy = '__pypy__' in sys.builtin_module_names
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
demandimport: drop Py3 workarounds from Py2 implementation
r33530 def _demandimport(name, globals=None, locals=None, fromlist=None, level=-1):
Gregory Szorc
demandimport: make module ignores a set (API)...
r37862 if locals is None or name in ignores or fromlist == ('*',):
Siddharth Agarwal
demandimport: move to separate package...
r32420 # these cases we can't really delay
Yuya Nishihara
demandimport: drop hack for old Pythons which had no level argument...
r33529 return _hgextimport(_origimport, name, globals, locals, fromlist, level)
Siddharth Agarwal
demandimport: move to separate package...
r32420 elif not fromlist:
# import a [as b]
Augie Fackler
formatting: blacken the codebase...
r43346 if '.' in name: # a.b
Siddharth Agarwal
demandimport: move to separate package...
r32420 base, rest = name.split('.', 1)
# email.__init__ loading email.mime
if globals and globals.get('__name__', None) == base:
Yuya Nishihara
demandimport: drop hack for old Pythons which had no level argument...
r33529 return _origimport(name, globals, locals, fromlist, level)
Siddharth Agarwal
demandimport: move to separate package...
r32420 # 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]
return _demandmod(name, globals, locals, level)
else:
# 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.
# The name of the module the import statement is located in.
globalname = globals.get('__name__')
def processfromitem(mod, attr):
"""Process an imported symbol in the import statement.
If the symbol doesn't exist in the parent module, and if the
parent module is a package, it must be a module. We set missing
modules up as _demandmod instances.
"""
symbol = getattr(mod, attr, nothing)
nonpkg = getattr(mod, '__path__', nothing) is nothing
if symbol is nothing:
if nonpkg:
# do not try relative import, which would raise ValueError,
# and leave unknown attribute as the default __import__()
# would do. the missing attribute will be detected later
# while processing the import statement.
return
mn = '%s.%s' % (mod.__name__, attr)
Gregory Szorc
demandimport: make module ignores a set (API)...
r37862 if mn in ignores:
Siddharth Agarwal
demandimport: move to separate package...
r32420 importfunc = _origimport
else:
importfunc = _demandmod
symbol = importfunc(attr, mod.__dict__, locals, level=1)
setattr(mod, attr, symbol)
# 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)
def chainmodules(rootmod, modname):
# recurse down the module chain, and return the leaf module
mod = rootmod
for comp in modname.split('.')[1:]:
Yuya Nishihara
demandimport: prefer loaded module over package attribute (issue5617)...
r33531 obj = getattr(mod, comp, nothing)
if obj is nothing:
obj = _demandmod(comp, mod.__dict__, mod.__dict__, level=1)
setattr(mod, comp, obj)
elif mod.__name__ + '.' + comp in sys.modules:
# prefer loaded module over attribute (issue5617)
obj = sys.modules[mod.__name__ + '.' + comp]
mod = obj
Siddharth Agarwal
demandimport: move to separate package...
r32420 return mod
if level >= 0:
if name:
# "from a import b" or "from .a import b" style
Augie Fackler
formatting: blacken the codebase...
r43346 rootmod = _hgextimport(
_origimport, name, globals, locals, level=level
)
Siddharth Agarwal
demandimport: move to separate package...
r32420 mod = chainmodules(rootmod, name)
elif _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:
Augie Fackler
formatting: blacken the codebase...
r43346 mod = _hgextimport(
_origimport, name, globals, locals, level=level
)
Siddharth Agarwal
demandimport: move to separate package...
r32420
for x in fromlist:
processfromitem(mod, x)
return mod
# But, we still need to support lazy loading of standard library and 3rd
# party modules. So handle level == -1.
mod = _hgextimport(_origimport, name, globals, locals)
mod = chainmodules(mod, name)
for x in fromlist:
processfromitem(mod, x)
return mod
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
demandimport: make module ignores a set (API)...
r37862 ignores = set()
Siddharth Agarwal
demandimport: move to separate package...
r32420
Augie Fackler
formatting: blacken the codebase...
r43346
Gregory Szorc
demandimport: make module ignores a set (API)...
r37862 def init(ignoreset):
global ignores
ignores = ignoreset
Siddharth Agarwal
demandimport: move to separate package...
r32420
Augie Fackler
formatting: blacken the codebase...
r43346
Siddharth Agarwal
demandimport: move to separate package...
r32420 def isenabled():
return builtins.__import__ == _demandimport
Augie Fackler
formatting: blacken the codebase...
r43346
Siddharth Agarwal
demandimport: move to separate package...
r32420 def enable():
Matt Harbison
cleanup: fix docstring formatting...
r44226 """enable global demand-loading of modules"""
Jun Wu
demandimport: move HGDEMANDIMPORT test to __init__.py...
r33861 builtins.__import__ = _demandimport
Siddharth Agarwal
demandimport: move to separate package...
r32420
Augie Fackler
formatting: blacken the codebase...
r43346
Siddharth Agarwal
demandimport: move to separate package...
r32420 def disable():
Matt Harbison
cleanup: fix docstring formatting...
r44226 """disable global demand-loading of modules"""
Siddharth Agarwal
demandimport: move to separate package...
r32420 builtins.__import__ = _origimport
Augie Fackler
formatting: blacken the codebase...
r43346
Siddharth Agarwal
demandimport: move to separate package...
r32420 @contextmanager
def deactivated():
Matt Harbison
cleanup: fix docstring formatting...
r44226 """context manager for disabling demandimport in 'with' blocks"""
Siddharth Agarwal
demandimport: move to separate package...
r32420 demandenabled = isenabled()
if demandenabled:
disable()
try:
yield
finally:
if demandenabled:
enable()