##// END OF EJS Templates
demandimport: move HGDEMANDIMPORT test to __init__.py...
Jun Wu -
r33861:8fb52126 default
parent child Browse files
Show More
@@ -1,73 +1,77 b''
1 # hgdemandimport - global demand-loading of modules for Mercurial
1 # hgdemandimport - global demand-loading of modules for Mercurial
2 #
2 #
3 # Copyright 2017 Facebook Inc.
3 # Copyright 2017 Facebook Inc.
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''demandimport - automatic demand-loading of modules'''
8 '''demandimport - automatic demand-loading of modules'''
9
9
10 # This is in a separate package from mercurial because in Python 3,
10 # This is in a separate package from mercurial because in Python 3,
11 # demand loading is per-package. Keeping demandimport in the mercurial package
11 # demand loading is per-package. Keeping demandimport in the mercurial package
12 # would disable demand loading for any modules in mercurial.
12 # would disable demand loading for any modules in mercurial.
13
13
14 from __future__ import absolute_import
14 from __future__ import absolute_import
15
15
16 import os
16 import sys
17 import sys
17
18
18 if sys.version_info[0] >= 3:
19 if sys.version_info[0] >= 3:
19 from . import demandimportpy3 as demandimport
20 from . import demandimportpy3 as demandimport
20 else:
21 else:
21 from . import demandimportpy2 as demandimport
22 from . import demandimportpy2 as demandimport
22
23
23 # Extensions can add to this list if necessary.
24 # Extensions can add to this list if necessary.
24 ignore = [
25 ignore = [
25 '__future__',
26 '__future__',
26 '_hashlib',
27 '_hashlib',
27 # ImportError during pkg_resources/__init__.py:fixup_namespace_package
28 # ImportError during pkg_resources/__init__.py:fixup_namespace_package
28 '_imp',
29 '_imp',
29 '_xmlplus',
30 '_xmlplus',
30 'fcntl',
31 'fcntl',
31 'nt', # pathlib2 tests the existence of built-in 'nt' module
32 'nt', # pathlib2 tests the existence of built-in 'nt' module
32 'win32com.gen_py',
33 'win32com.gen_py',
33 'win32com.shell', # 'appdirs' tries to import win32com.shell
34 'win32com.shell', # 'appdirs' tries to import win32com.shell
34 '_winreg', # 2.7 mimetypes needs immediate ImportError
35 '_winreg', # 2.7 mimetypes needs immediate ImportError
35 'pythoncom',
36 'pythoncom',
36 # imported by tarfile, not available under Windows
37 # imported by tarfile, not available under Windows
37 'pwd',
38 'pwd',
38 'grp',
39 'grp',
39 # imported by profile, itself imported by hotshot.stats,
40 # imported by profile, itself imported by hotshot.stats,
40 # not available under Windows
41 # not available under Windows
41 'resource',
42 'resource',
42 # this trips up many extension authors
43 # this trips up many extension authors
43 'gtk',
44 'gtk',
44 # setuptools' pkg_resources.py expects "from __main__ import x" to
45 # setuptools' pkg_resources.py expects "from __main__ import x" to
45 # raise ImportError if x not defined
46 # raise ImportError if x not defined
46 '__main__',
47 '__main__',
47 '_ssl', # conditional imports in the stdlib, issue1964
48 '_ssl', # conditional imports in the stdlib, issue1964
48 '_sre', # issue4920
49 '_sre', # issue4920
49 'rfc822',
50 'rfc822',
50 'mimetools',
51 'mimetools',
51 'sqlalchemy.events', # has import-time side effects (issue5085)
52 'sqlalchemy.events', # has import-time side effects (issue5085)
52 # setuptools 8 expects this module to explode early when not on windows
53 # setuptools 8 expects this module to explode early when not on windows
53 'distutils.msvc9compiler',
54 'distutils.msvc9compiler',
54 '__builtin__',
55 '__builtin__',
55 'builtins',
56 'builtins',
56 'urwid.command_map', # for pudb
57 'urwid.command_map', # for pudb
57 ]
58 ]
58
59
59 _pypy = '__pypy__' in sys.builtin_module_names
60 _pypy = '__pypy__' in sys.builtin_module_names
60
61
61 if _pypy:
62 if _pypy:
62 ignore.extend([
63 ignore.extend([
63 # _ctypes.pointer is shadowed by "from ... import pointer" (PyPy 5)
64 # _ctypes.pointer is shadowed by "from ... import pointer" (PyPy 5)
64 '_ctypes.pointer',
65 '_ctypes.pointer',
65 ])
66 ])
66
67
67 demandimport.init(ignore)
68 demandimport.init(ignore)
68
69
69 # Re-export.
70 # Re-export.
70 isenabled = demandimport.isenabled
71 isenabled = demandimport.isenabled
71 enable = demandimport.enable
72 disable = demandimport.disable
72 disable = demandimport.disable
73 deactivated = demandimport.deactivated
73 deactivated = demandimport.deactivated
74
75 def enable():
76 if os.environ.get('HGDEMANDIMPORT') != 'disable':
77 demandimport.enable()
@@ -1,306 +1,304 b''
1 # demandimport.py - global demand-loading of modules for Mercurial
1 # demandimport.py - global demand-loading of modules for Mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''
8 '''
9 demandimport - automatic demandloading of modules
9 demandimport - automatic demandloading of modules
10
10
11 To enable this module, do:
11 To enable this module, do:
12
12
13 import demandimport; demandimport.enable()
13 import demandimport; demandimport.enable()
14
14
15 Imports of the following forms will be demand-loaded:
15 Imports of the following forms will be demand-loaded:
16
16
17 import a, b.c
17 import a, b.c
18 import a.b as c
18 import a.b as c
19 from a import b,c # a will be loaded immediately
19 from a import b,c # a will be loaded immediately
20
20
21 These imports will not be delayed:
21 These imports will not be delayed:
22
22
23 from a import *
23 from a import *
24 b = __import__(a)
24 b = __import__(a)
25 '''
25 '''
26
26
27 from __future__ import absolute_import
27 from __future__ import absolute_import
28
28
29 import __builtin__ as builtins
29 import __builtin__ as builtins
30 import contextlib
30 import contextlib
31 import os
32 import sys
31 import sys
33
32
34 contextmanager = contextlib.contextmanager
33 contextmanager = contextlib.contextmanager
35
34
36 _origimport = __import__
35 _origimport = __import__
37
36
38 nothing = object()
37 nothing = object()
39
38
40 def _hgextimport(importfunc, name, globals, *args, **kwargs):
39 def _hgextimport(importfunc, name, globals, *args, **kwargs):
41 try:
40 try:
42 return importfunc(name, globals, *args, **kwargs)
41 return importfunc(name, globals, *args, **kwargs)
43 except ImportError:
42 except ImportError:
44 if not globals:
43 if not globals:
45 raise
44 raise
46 # extensions are loaded with "hgext_" prefix
45 # extensions are loaded with "hgext_" prefix
47 hgextname = 'hgext_%s' % name
46 hgextname = 'hgext_%s' % name
48 nameroot = hgextname.split('.', 1)[0]
47 nameroot = hgextname.split('.', 1)[0]
49 contextroot = globals.get('__name__', '').split('.', 1)[0]
48 contextroot = globals.get('__name__', '').split('.', 1)[0]
50 if nameroot != contextroot:
49 if nameroot != contextroot:
51 raise
50 raise
52 # retry to import with "hgext_" prefix
51 # retry to import with "hgext_" prefix
53 return importfunc(hgextname, globals, *args, **kwargs)
52 return importfunc(hgextname, globals, *args, **kwargs)
54
53
55 class _demandmod(object):
54 class _demandmod(object):
56 """module demand-loader and proxy
55 """module demand-loader and proxy
57
56
58 Specify 1 as 'level' argument at construction, to import module
57 Specify 1 as 'level' argument at construction, to import module
59 relatively.
58 relatively.
60 """
59 """
61
60
62 def __init__(self, name, globals, locals, level):
61 def __init__(self, name, globals, locals, level):
63 if '.' in name:
62 if '.' in name:
64 head, rest = name.split('.', 1)
63 head, rest = name.split('.', 1)
65 after = [rest]
64 after = [rest]
66 else:
65 else:
67 head = name
66 head = name
68 after = []
67 after = []
69 object.__setattr__(self, r"_data",
68 object.__setattr__(self, r"_data",
70 (head, globals, locals, after, level, set()))
69 (head, globals, locals, after, level, set()))
71 object.__setattr__(self, r"_module", None)
70 object.__setattr__(self, r"_module", None)
72
71
73 def _extend(self, name):
72 def _extend(self, name):
74 """add to the list of submodules to load"""
73 """add to the list of submodules to load"""
75 self._data[3].append(name)
74 self._data[3].append(name)
76
75
77 def _addref(self, name):
76 def _addref(self, name):
78 """Record that the named module ``name`` imports this module.
77 """Record that the named module ``name`` imports this module.
79
78
80 References to this proxy class having the name of this module will be
79 References to this proxy class having the name of this module will be
81 replaced at module load time. We assume the symbol inside the importing
80 replaced at module load time. We assume the symbol inside the importing
82 module is identical to the "head" name of this module. We don't
81 module is identical to the "head" name of this module. We don't
83 actually know if "as X" syntax is being used to change the symbol name
82 actually know if "as X" syntax is being used to change the symbol name
84 because this information isn't exposed to __import__.
83 because this information isn't exposed to __import__.
85 """
84 """
86 self._data[5].add(name)
85 self._data[5].add(name)
87
86
88 def _load(self):
87 def _load(self):
89 if not self._module:
88 if not self._module:
90 head, globals, locals, after, level, modrefs = self._data
89 head, globals, locals, after, level, modrefs = self._data
91 mod = _hgextimport(_origimport, head, globals, locals, None, level)
90 mod = _hgextimport(_origimport, head, globals, locals, None, level)
92 if mod is self:
91 if mod is self:
93 # In this case, _hgextimport() above should imply
92 # In this case, _hgextimport() above should imply
94 # _demandimport(). Otherwise, _hgextimport() never
93 # _demandimport(). Otherwise, _hgextimport() never
95 # returns _demandmod. This isn't intentional behavior,
94 # returns _demandmod. This isn't intentional behavior,
96 # in fact. (see also issue5304 for detail)
95 # in fact. (see also issue5304 for detail)
97 #
96 #
98 # If self._module is already bound at this point, self
97 # If self._module is already bound at this point, self
99 # should be already _load()-ed while _hgextimport().
98 # should be already _load()-ed while _hgextimport().
100 # Otherwise, there is no way to import actual module
99 # Otherwise, there is no way to import actual module
101 # as expected, because (re-)invoking _hgextimport()
100 # as expected, because (re-)invoking _hgextimport()
102 # should cause same result.
101 # should cause same result.
103 # This is reason why _load() returns without any more
102 # This is reason why _load() returns without any more
104 # setup but assumes self to be already bound.
103 # setup but assumes self to be already bound.
105 mod = self._module
104 mod = self._module
106 assert mod and mod is not self, "%s, %s" % (self, mod)
105 assert mod and mod is not self, "%s, %s" % (self, mod)
107 return
106 return
108
107
109 # load submodules
108 # load submodules
110 def subload(mod, p):
109 def subload(mod, p):
111 h, t = p, None
110 h, t = p, None
112 if '.' in p:
111 if '.' in p:
113 h, t = p.split('.', 1)
112 h, t = p.split('.', 1)
114 if getattr(mod, h, nothing) is nothing:
113 if getattr(mod, h, nothing) is nothing:
115 setattr(mod, h, _demandmod(p, mod.__dict__, mod.__dict__,
114 setattr(mod, h, _demandmod(p, mod.__dict__, mod.__dict__,
116 level=1))
115 level=1))
117 elif t:
116 elif t:
118 subload(getattr(mod, h), t)
117 subload(getattr(mod, h), t)
119
118
120 for x in after:
119 for x in after:
121 subload(mod, x)
120 subload(mod, x)
122
121
123 # Replace references to this proxy instance with the actual module.
122 # Replace references to this proxy instance with the actual module.
124 if locals:
123 if locals:
125 if locals.get(head) is self:
124 if locals.get(head) is self:
126 locals[head] = mod
125 locals[head] = mod
127 elif locals.get(head + r'mod') is self:
126 elif locals.get(head + r'mod') is self:
128 locals[head + r'mod'] = mod
127 locals[head + r'mod'] = mod
129
128
130 for modname in modrefs:
129 for modname in modrefs:
131 modref = sys.modules.get(modname, None)
130 modref = sys.modules.get(modname, None)
132 if modref and getattr(modref, head, None) is self:
131 if modref and getattr(modref, head, None) is self:
133 setattr(modref, head, mod)
132 setattr(modref, head, mod)
134
133
135 object.__setattr__(self, r"_module", mod)
134 object.__setattr__(self, r"_module", mod)
136
135
137 def __repr__(self):
136 def __repr__(self):
138 if self._module:
137 if self._module:
139 return "<proxied module '%s'>" % self._data[0]
138 return "<proxied module '%s'>" % self._data[0]
140 return "<unloaded module '%s'>" % self._data[0]
139 return "<unloaded module '%s'>" % self._data[0]
141
140
142 def __call__(self, *args, **kwargs):
141 def __call__(self, *args, **kwargs):
143 raise TypeError("%s object is not callable" % repr(self))
142 raise TypeError("%s object is not callable" % repr(self))
144
143
145 def __getattr__(self, attr):
144 def __getattr__(self, attr):
146 self._load()
145 self._load()
147 return getattr(self._module, attr)
146 return getattr(self._module, attr)
148
147
149 def __setattr__(self, attr, val):
148 def __setattr__(self, attr, val):
150 self._load()
149 self._load()
151 setattr(self._module, attr, val)
150 setattr(self._module, attr, val)
152
151
153 @property
152 @property
154 def __dict__(self):
153 def __dict__(self):
155 self._load()
154 self._load()
156 return self._module.__dict__
155 return self._module.__dict__
157
156
158 @property
157 @property
159 def __doc__(self):
158 def __doc__(self):
160 self._load()
159 self._load()
161 return self._module.__doc__
160 return self._module.__doc__
162
161
163 _pypy = '__pypy__' in sys.builtin_module_names
162 _pypy = '__pypy__' in sys.builtin_module_names
164
163
165 def _demandimport(name, globals=None, locals=None, fromlist=None, level=-1):
164 def _demandimport(name, globals=None, locals=None, fromlist=None, level=-1):
166 if locals is None or name in ignore or fromlist == ('*',):
165 if locals is None or name in ignore or fromlist == ('*',):
167 # these cases we can't really delay
166 # these cases we can't really delay
168 return _hgextimport(_origimport, name, globals, locals, fromlist, level)
167 return _hgextimport(_origimport, name, globals, locals, fromlist, level)
169 elif not fromlist:
168 elif not fromlist:
170 # import a [as b]
169 # import a [as b]
171 if '.' in name: # a.b
170 if '.' in name: # a.b
172 base, rest = name.split('.', 1)
171 base, rest = name.split('.', 1)
173 # email.__init__ loading email.mime
172 # email.__init__ loading email.mime
174 if globals and globals.get('__name__', None) == base:
173 if globals and globals.get('__name__', None) == base:
175 return _origimport(name, globals, locals, fromlist, level)
174 return _origimport(name, globals, locals, fromlist, level)
176 # if a is already demand-loaded, add b to its submodule list
175 # if a is already demand-loaded, add b to its submodule list
177 if base in locals:
176 if base in locals:
178 if isinstance(locals[base], _demandmod):
177 if isinstance(locals[base], _demandmod):
179 locals[base]._extend(rest)
178 locals[base]._extend(rest)
180 return locals[base]
179 return locals[base]
181 return _demandmod(name, globals, locals, level)
180 return _demandmod(name, globals, locals, level)
182 else:
181 else:
183 # There is a fromlist.
182 # There is a fromlist.
184 # from a import b,c,d
183 # from a import b,c,d
185 # from . import b,c,d
184 # from . import b,c,d
186 # from .a import b,c,d
185 # from .a import b,c,d
187
186
188 # level == -1: relative and absolute attempted (Python 2 only).
187 # level == -1: relative and absolute attempted (Python 2 only).
189 # level >= 0: absolute only (Python 2 w/ absolute_import and Python 3).
188 # level >= 0: absolute only (Python 2 w/ absolute_import and Python 3).
190 # The modern Mercurial convention is to use absolute_import everywhere,
189 # The modern Mercurial convention is to use absolute_import everywhere,
191 # so modern Mercurial code will have level >= 0.
190 # so modern Mercurial code will have level >= 0.
192
191
193 # The name of the module the import statement is located in.
192 # The name of the module the import statement is located in.
194 globalname = globals.get('__name__')
193 globalname = globals.get('__name__')
195
194
196 def processfromitem(mod, attr):
195 def processfromitem(mod, attr):
197 """Process an imported symbol in the import statement.
196 """Process an imported symbol in the import statement.
198
197
199 If the symbol doesn't exist in the parent module, and if the
198 If the symbol doesn't exist in the parent module, and if the
200 parent module is a package, it must be a module. We set missing
199 parent module is a package, it must be a module. We set missing
201 modules up as _demandmod instances.
200 modules up as _demandmod instances.
202 """
201 """
203 symbol = getattr(mod, attr, nothing)
202 symbol = getattr(mod, attr, nothing)
204 nonpkg = getattr(mod, '__path__', nothing) is nothing
203 nonpkg = getattr(mod, '__path__', nothing) is nothing
205 if symbol is nothing:
204 if symbol is nothing:
206 if nonpkg:
205 if nonpkg:
207 # do not try relative import, which would raise ValueError,
206 # do not try relative import, which would raise ValueError,
208 # and leave unknown attribute as the default __import__()
207 # and leave unknown attribute as the default __import__()
209 # would do. the missing attribute will be detected later
208 # would do. the missing attribute will be detected later
210 # while processing the import statement.
209 # while processing the import statement.
211 return
210 return
212 mn = '%s.%s' % (mod.__name__, attr)
211 mn = '%s.%s' % (mod.__name__, attr)
213 if mn in ignore:
212 if mn in ignore:
214 importfunc = _origimport
213 importfunc = _origimport
215 else:
214 else:
216 importfunc = _demandmod
215 importfunc = _demandmod
217 symbol = importfunc(attr, mod.__dict__, locals, level=1)
216 symbol = importfunc(attr, mod.__dict__, locals, level=1)
218 setattr(mod, attr, symbol)
217 setattr(mod, attr, symbol)
219
218
220 # Record the importing module references this symbol so we can
219 # Record the importing module references this symbol so we can
221 # replace the symbol with the actual module instance at load
220 # replace the symbol with the actual module instance at load
222 # time.
221 # time.
223 if globalname and isinstance(symbol, _demandmod):
222 if globalname and isinstance(symbol, _demandmod):
224 symbol._addref(globalname)
223 symbol._addref(globalname)
225
224
226 def chainmodules(rootmod, modname):
225 def chainmodules(rootmod, modname):
227 # recurse down the module chain, and return the leaf module
226 # recurse down the module chain, and return the leaf module
228 mod = rootmod
227 mod = rootmod
229 for comp in modname.split('.')[1:]:
228 for comp in modname.split('.')[1:]:
230 obj = getattr(mod, comp, nothing)
229 obj = getattr(mod, comp, nothing)
231 if obj is nothing:
230 if obj is nothing:
232 obj = _demandmod(comp, mod.__dict__, mod.__dict__, level=1)
231 obj = _demandmod(comp, mod.__dict__, mod.__dict__, level=1)
233 setattr(mod, comp, obj)
232 setattr(mod, comp, obj)
234 elif mod.__name__ + '.' + comp in sys.modules:
233 elif mod.__name__ + '.' + comp in sys.modules:
235 # prefer loaded module over attribute (issue5617)
234 # prefer loaded module over attribute (issue5617)
236 obj = sys.modules[mod.__name__ + '.' + comp]
235 obj = sys.modules[mod.__name__ + '.' + comp]
237 mod = obj
236 mod = obj
238 return mod
237 return mod
239
238
240 if level >= 0:
239 if level >= 0:
241 if name:
240 if name:
242 # "from a import b" or "from .a import b" style
241 # "from a import b" or "from .a import b" style
243 rootmod = _hgextimport(_origimport, name, globals, locals,
242 rootmod = _hgextimport(_origimport, name, globals, locals,
244 level=level)
243 level=level)
245 mod = chainmodules(rootmod, name)
244 mod = chainmodules(rootmod, name)
246 elif _pypy:
245 elif _pypy:
247 # PyPy's __import__ throws an exception if invoked
246 # PyPy's __import__ throws an exception if invoked
248 # with an empty name and no fromlist. Recreate the
247 # with an empty name and no fromlist. Recreate the
249 # desired behaviour by hand.
248 # desired behaviour by hand.
250 mn = globalname
249 mn = globalname
251 mod = sys.modules[mn]
250 mod = sys.modules[mn]
252 if getattr(mod, '__path__', nothing) is nothing:
251 if getattr(mod, '__path__', nothing) is nothing:
253 mn = mn.rsplit('.', 1)[0]
252 mn = mn.rsplit('.', 1)[0]
254 mod = sys.modules[mn]
253 mod = sys.modules[mn]
255 if level > 1:
254 if level > 1:
256 mn = mn.rsplit('.', level - 1)[0]
255 mn = mn.rsplit('.', level - 1)[0]
257 mod = sys.modules[mn]
256 mod = sys.modules[mn]
258 else:
257 else:
259 mod = _hgextimport(_origimport, name, globals, locals,
258 mod = _hgextimport(_origimport, name, globals, locals,
260 level=level)
259 level=level)
261
260
262 for x in fromlist:
261 for x in fromlist:
263 processfromitem(mod, x)
262 processfromitem(mod, x)
264
263
265 return mod
264 return mod
266
265
267 # But, we still need to support lazy loading of standard library and 3rd
266 # But, we still need to support lazy loading of standard library and 3rd
268 # party modules. So handle level == -1.
267 # party modules. So handle level == -1.
269 mod = _hgextimport(_origimport, name, globals, locals)
268 mod = _hgextimport(_origimport, name, globals, locals)
270 mod = chainmodules(mod, name)
269 mod = chainmodules(mod, name)
271
270
272 for x in fromlist:
271 for x in fromlist:
273 processfromitem(mod, x)
272 processfromitem(mod, x)
274
273
275 return mod
274 return mod
276
275
277 ignore = []
276 ignore = []
278
277
279 def init(ignorelist):
278 def init(ignorelist):
280 global ignore
279 global ignore
281 ignore = ignorelist
280 ignore = ignorelist
282
281
283 def isenabled():
282 def isenabled():
284 return builtins.__import__ == _demandimport
283 return builtins.__import__ == _demandimport
285
284
286 def enable():
285 def enable():
287 "enable global demand-loading of modules"
286 "enable global demand-loading of modules"
288 if os.environ.get('HGDEMANDIMPORT') != 'disable':
289 builtins.__import__ = _demandimport
287 builtins.__import__ = _demandimport
290
288
291 def disable():
289 def disable():
292 "disable global demand-loading of modules"
290 "disable global demand-loading of modules"
293 builtins.__import__ = _origimport
291 builtins.__import__ = _origimport
294
292
295 @contextmanager
293 @contextmanager
296 def deactivated():
294 def deactivated():
297 "context manager for disabling demandimport in 'with' blocks"
295 "context manager for disabling demandimport in 'with' blocks"
298 demandenabled = isenabled()
296 demandenabled = isenabled()
299 if demandenabled:
297 if demandenabled:
300 disable()
298 disable()
301
299
302 try:
300 try:
303 yield
301 yield
304 finally:
302 finally:
305 if demandenabled:
303 if demandenabled:
306 enable()
304 enable()
@@ -1,112 +1,110 b''
1 # demandimportpy3 - global demand-loading of modules for Mercurial
1 # demandimportpy3 - global demand-loading of modules for Mercurial
2 #
2 #
3 # Copyright 2017 Facebook Inc.
3 # Copyright 2017 Facebook Inc.
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 """Lazy loading for Python 3.6 and above.
8 """Lazy loading for Python 3.6 and above.
9
9
10 This uses the new importlib finder/loader functionality available in Python 3.5
10 This uses the new importlib finder/loader functionality available in Python 3.5
11 and up. The code reuses most of the mechanics implemented inside importlib.util,
11 and up. The code reuses most of the mechanics implemented inside importlib.util,
12 but with a few additions:
12 but with a few additions:
13
13
14 * Allow excluding certain modules from lazy imports.
14 * Allow excluding certain modules from lazy imports.
15 * Expose an interface that's substantially the same as demandimport for
15 * Expose an interface that's substantially the same as demandimport for
16 Python 2.
16 Python 2.
17
17
18 This also has some limitations compared to the Python 2 implementation:
18 This also has some limitations compared to the Python 2 implementation:
19
19
20 * Much of the logic is per-package, not per-module, so any packages loaded
20 * Much of the logic is per-package, not per-module, so any packages loaded
21 before demandimport is enabled will not be lazily imported in the future. In
21 before demandimport is enabled will not be lazily imported in the future. In
22 practice, we only expect builtins to be loaded before demandimport is
22 practice, we only expect builtins to be loaded before demandimport is
23 enabled.
23 enabled.
24 """
24 """
25
25
26 # This line is unnecessary, but it satisfies test-check-py3-compat.t.
26 # This line is unnecessary, but it satisfies test-check-py3-compat.t.
27 from __future__ import absolute_import
27 from __future__ import absolute_import
28
28
29 import contextlib
29 import contextlib
30 import os
31 import sys
30 import sys
32
31
33 import importlib.abc
32 import importlib.abc
34 import importlib.machinery
33 import importlib.machinery
35 import importlib.util
34 import importlib.util
36
35
37 _deactivated = False
36 _deactivated = False
38
37
39 class _lazyloaderex(importlib.util.LazyLoader):
38 class _lazyloaderex(importlib.util.LazyLoader):
40 """This is a LazyLoader except it also follows the _deactivated global and
39 """This is a LazyLoader except it also follows the _deactivated global and
41 the ignore list.
40 the ignore list.
42 """
41 """
43 def exec_module(self, module):
42 def exec_module(self, module):
44 """Make the module load lazily."""
43 """Make the module load lazily."""
45 if _deactivated or module.__name__ in ignore:
44 if _deactivated or module.__name__ in ignore:
46 self.loader.exec_module(module)
45 self.loader.exec_module(module)
47 else:
46 else:
48 super().exec_module(module)
47 super().exec_module(module)
49
48
50 # This is 3.6+ because with Python 3.5 it isn't possible to lazily load
49 # This is 3.6+ because with Python 3.5 it isn't possible to lazily load
51 # extensions. See the discussion in https://python.org/sf/26186 for more.
50 # extensions. See the discussion in https://python.org/sf/26186 for more.
52 _extensions_loader = _lazyloaderex.factory(
51 _extensions_loader = _lazyloaderex.factory(
53 importlib.machinery.ExtensionFileLoader)
52 importlib.machinery.ExtensionFileLoader)
54 _bytecode_loader = _lazyloaderex.factory(
53 _bytecode_loader = _lazyloaderex.factory(
55 importlib.machinery.SourcelessFileLoader)
54 importlib.machinery.SourcelessFileLoader)
56 _source_loader = _lazyloaderex.factory(importlib.machinery.SourceFileLoader)
55 _source_loader = _lazyloaderex.factory(importlib.machinery.SourceFileLoader)
57
56
58 def _makefinder(path):
57 def _makefinder(path):
59 return importlib.machinery.FileFinder(
58 return importlib.machinery.FileFinder(
60 path,
59 path,
61 # This is the order in which loaders are passed in in core Python.
60 # This is the order in which loaders are passed in in core Python.
62 (_extensions_loader, importlib.machinery.EXTENSION_SUFFIXES),
61 (_extensions_loader, importlib.machinery.EXTENSION_SUFFIXES),
63 (_source_loader, importlib.machinery.SOURCE_SUFFIXES),
62 (_source_loader, importlib.machinery.SOURCE_SUFFIXES),
64 (_bytecode_loader, importlib.machinery.BYTECODE_SUFFIXES),
63 (_bytecode_loader, importlib.machinery.BYTECODE_SUFFIXES),
65 )
64 )
66
65
67 ignore = []
66 ignore = []
68
67
69 def init(ignorelist):
68 def init(ignorelist):
70 global ignore
69 global ignore
71 ignore = ignorelist
70 ignore = ignorelist
72
71
73 def isenabled():
72 def isenabled():
74 return _makefinder in sys.path_hooks and not _deactivated
73 return _makefinder in sys.path_hooks and not _deactivated
75
74
76 def disable():
75 def disable():
77 try:
76 try:
78 while True:
77 while True:
79 sys.path_hooks.remove(_makefinder)
78 sys.path_hooks.remove(_makefinder)
80 except ValueError:
79 except ValueError:
81 pass
80 pass
82
81
83 def enable():
82 def enable():
84 if os.environ.get('HGDEMANDIMPORT') != 'disable':
85 sys.path_hooks.insert(0, _makefinder)
83 sys.path_hooks.insert(0, _makefinder)
86
84
87 @contextlib.contextmanager
85 @contextlib.contextmanager
88 def deactivated():
86 def deactivated():
89 # This implementation is a bit different from Python 2's. Python 3
87 # This implementation is a bit different from Python 2's. Python 3
90 # maintains a per-package finder cache in sys.path_importer_cache (see
88 # maintains a per-package finder cache in sys.path_importer_cache (see
91 # PEP 302). This means that we can't just call disable + enable.
89 # PEP 302). This means that we can't just call disable + enable.
92 # If we do that, in situations like:
90 # If we do that, in situations like:
93 #
91 #
94 # demandimport.enable()
92 # demandimport.enable()
95 # ...
93 # ...
96 # from foo.bar import mod1
94 # from foo.bar import mod1
97 # with demandimport.deactivated():
95 # with demandimport.deactivated():
98 # from foo.bar import mod2
96 # from foo.bar import mod2
99 #
97 #
100 # mod2 will be imported lazily. (The converse also holds -- whatever finder
98 # mod2 will be imported lazily. (The converse also holds -- whatever finder
101 # first gets cached will be used.)
99 # first gets cached will be used.)
102 #
100 #
103 # Instead, have a global flag the LazyLoader can use.
101 # Instead, have a global flag the LazyLoader can use.
104 global _deactivated
102 global _deactivated
105 demandenabled = isenabled()
103 demandenabled = isenabled()
106 if demandenabled:
104 if demandenabled:
107 _deactivated = True
105 _deactivated = True
108 try:
106 try:
109 yield
107 yield
110 finally:
108 finally:
111 if demandenabled:
109 if demandenabled:
112 _deactivated = False
110 _deactivated = False
General Comments 0
You need to be logged in to leave comments. Login now