##// END OF EJS Templates
demandimport: add 'nt' to ignore list (issue5373)...
Yuya Nishihara -
r30021:16a09ae3 default
parent child Browse files
Show More
@@ -1,319 +1,320 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 contextlib
29 import contextlib
30 import os
30 import os
31 import sys
31 import sys
32
32
33 # __builtin__ in Python 2, builtins in Python 3.
33 # __builtin__ in Python 2, builtins in Python 3.
34 try:
34 try:
35 import __builtin__ as builtins
35 import __builtin__ as builtins
36 except ImportError:
36 except ImportError:
37 import builtins
37 import builtins
38
38
39 contextmanager = contextlib.contextmanager
39 contextmanager = contextlib.contextmanager
40
40
41 _origimport = __import__
41 _origimport = __import__
42
42
43 nothing = object()
43 nothing = object()
44
44
45 # Python 3 doesn't have relative imports nor level -1.
45 # Python 3 doesn't have relative imports nor level -1.
46 level = -1
46 level = -1
47 if sys.version_info[0] >= 3:
47 if sys.version_info[0] >= 3:
48 level = 0
48 level = 0
49 _import = _origimport
49 _import = _origimport
50
50
51 def _hgextimport(importfunc, name, globals, *args, **kwargs):
51 def _hgextimport(importfunc, name, globals, *args, **kwargs):
52 try:
52 try:
53 return importfunc(name, globals, *args, **kwargs)
53 return importfunc(name, globals, *args, **kwargs)
54 except ImportError:
54 except ImportError:
55 if not globals:
55 if not globals:
56 raise
56 raise
57 # extensions are loaded with "hgext_" prefix
57 # extensions are loaded with "hgext_" prefix
58 hgextname = 'hgext_%s' % name
58 hgextname = 'hgext_%s' % name
59 nameroot = hgextname.split('.', 1)[0]
59 nameroot = hgextname.split('.', 1)[0]
60 contextroot = globals.get('__name__', '').split('.', 1)[0]
60 contextroot = globals.get('__name__', '').split('.', 1)[0]
61 if nameroot != contextroot:
61 if nameroot != contextroot:
62 raise
62 raise
63 # retry to import with "hgext_" prefix
63 # retry to import with "hgext_" prefix
64 return importfunc(hgextname, globals, *args, **kwargs)
64 return importfunc(hgextname, globals, *args, **kwargs)
65
65
66 class _demandmod(object):
66 class _demandmod(object):
67 """module demand-loader and proxy
67 """module demand-loader and proxy
68
68
69 Specify 1 as 'level' argument at construction, to import module
69 Specify 1 as 'level' argument at construction, to import module
70 relatively.
70 relatively.
71 """
71 """
72 def __init__(self, name, globals, locals, level):
72 def __init__(self, name, globals, locals, level):
73 if '.' in name:
73 if '.' in name:
74 head, rest = name.split('.', 1)
74 head, rest = name.split('.', 1)
75 after = [rest]
75 after = [rest]
76 else:
76 else:
77 head = name
77 head = name
78 after = []
78 after = []
79 object.__setattr__(self, "_data",
79 object.__setattr__(self, "_data",
80 (head, globals, locals, after, level, set()))
80 (head, globals, locals, after, level, set()))
81 object.__setattr__(self, "_module", None)
81 object.__setattr__(self, "_module", None)
82 def _extend(self, name):
82 def _extend(self, name):
83 """add to the list of submodules to load"""
83 """add to the list of submodules to load"""
84 self._data[3].append(name)
84 self._data[3].append(name)
85
85
86 def _addref(self, name):
86 def _addref(self, name):
87 """Record that the named module ``name`` imports this module.
87 """Record that the named module ``name`` imports this module.
88
88
89 References to this proxy class having the name of this module will be
89 References to this proxy class having the name of this module will be
90 replaced at module load time. We assume the symbol inside the importing
90 replaced at module load time. We assume the symbol inside the importing
91 module is identical to the "head" name of this module. We don't
91 module is identical to the "head" name of this module. We don't
92 actually know if "as X" syntax is being used to change the symbol name
92 actually know if "as X" syntax is being used to change the symbol name
93 because this information isn't exposed to __import__.
93 because this information isn't exposed to __import__.
94 """
94 """
95 self._data[5].add(name)
95 self._data[5].add(name)
96
96
97 def _load(self):
97 def _load(self):
98 if not self._module:
98 if not self._module:
99 head, globals, locals, after, level, modrefs = self._data
99 head, globals, locals, after, level, modrefs = self._data
100 mod = _hgextimport(_import, head, globals, locals, None, level)
100 mod = _hgextimport(_import, head, globals, locals, None, level)
101 if mod is self:
101 if mod is self:
102 # In this case, _hgextimport() above should imply
102 # In this case, _hgextimport() above should imply
103 # _demandimport(). Otherwise, _hgextimport() never
103 # _demandimport(). Otherwise, _hgextimport() never
104 # returns _demandmod. This isn't intentional behavior,
104 # returns _demandmod. This isn't intentional behavior,
105 # in fact. (see also issue5304 for detail)
105 # in fact. (see also issue5304 for detail)
106 #
106 #
107 # If self._module is already bound at this point, self
107 # If self._module is already bound at this point, self
108 # should be already _load()-ed while _hgextimport().
108 # should be already _load()-ed while _hgextimport().
109 # Otherwise, there is no way to import actual module
109 # Otherwise, there is no way to import actual module
110 # as expected, because (re-)invoking _hgextimport()
110 # as expected, because (re-)invoking _hgextimport()
111 # should cause same result.
111 # should cause same result.
112 # This is reason why _load() returns without any more
112 # This is reason why _load() returns without any more
113 # setup but assumes self to be already bound.
113 # setup but assumes self to be already bound.
114 mod = self._module
114 mod = self._module
115 assert mod and mod is not self, "%s, %s" % (self, mod)
115 assert mod and mod is not self, "%s, %s" % (self, mod)
116 return
116 return
117
117
118 # load submodules
118 # load submodules
119 def subload(mod, p):
119 def subload(mod, p):
120 h, t = p, None
120 h, t = p, None
121 if '.' in p:
121 if '.' in p:
122 h, t = p.split('.', 1)
122 h, t = p.split('.', 1)
123 if getattr(mod, h, nothing) is nothing:
123 if getattr(mod, h, nothing) is nothing:
124 setattr(mod, h, _demandmod(p, mod.__dict__, mod.__dict__,
124 setattr(mod, h, _demandmod(p, mod.__dict__, mod.__dict__,
125 level=1))
125 level=1))
126 elif t:
126 elif t:
127 subload(getattr(mod, h), t)
127 subload(getattr(mod, h), t)
128
128
129 for x in after:
129 for x in after:
130 subload(mod, x)
130 subload(mod, x)
131
131
132 # Replace references to this proxy instance with the actual module.
132 # Replace references to this proxy instance with the actual module.
133 if locals and locals.get(head) == self:
133 if locals and locals.get(head) == self:
134 locals[head] = mod
134 locals[head] = mod
135
135
136 for modname in modrefs:
136 for modname in modrefs:
137 modref = sys.modules.get(modname, None)
137 modref = sys.modules.get(modname, None)
138 if modref and getattr(modref, head, None) == self:
138 if modref and getattr(modref, head, None) == self:
139 setattr(modref, head, mod)
139 setattr(modref, head, mod)
140
140
141 object.__setattr__(self, "_module", mod)
141 object.__setattr__(self, "_module", mod)
142
142
143 def __repr__(self):
143 def __repr__(self):
144 if self._module:
144 if self._module:
145 return "<proxied module '%s'>" % self._data[0]
145 return "<proxied module '%s'>" % self._data[0]
146 return "<unloaded module '%s'>" % self._data[0]
146 return "<unloaded module '%s'>" % self._data[0]
147 def __call__(self, *args, **kwargs):
147 def __call__(self, *args, **kwargs):
148 raise TypeError("%s object is not callable" % repr(self))
148 raise TypeError("%s object is not callable" % repr(self))
149 def __getattribute__(self, attr):
149 def __getattribute__(self, attr):
150 if attr in ('_data', '_extend', '_load', '_module', '_addref'):
150 if attr in ('_data', '_extend', '_load', '_module', '_addref'):
151 return object.__getattribute__(self, attr)
151 return object.__getattribute__(self, attr)
152 self._load()
152 self._load()
153 return getattr(self._module, attr)
153 return getattr(self._module, attr)
154 def __setattr__(self, attr, val):
154 def __setattr__(self, attr, val):
155 self._load()
155 self._load()
156 setattr(self._module, attr, val)
156 setattr(self._module, attr, val)
157
157
158 _pypy = '__pypy__' in sys.builtin_module_names
158 _pypy = '__pypy__' in sys.builtin_module_names
159
159
160 def _demandimport(name, globals=None, locals=None, fromlist=None, level=level):
160 def _demandimport(name, globals=None, locals=None, fromlist=None, level=level):
161 if not locals or name in ignore or fromlist == ('*',):
161 if not locals or name in ignore or fromlist == ('*',):
162 # these cases we can't really delay
162 # these cases we can't really delay
163 return _hgextimport(_import, name, globals, locals, fromlist, level)
163 return _hgextimport(_import, name, globals, locals, fromlist, level)
164 elif not fromlist:
164 elif not fromlist:
165 # import a [as b]
165 # import a [as b]
166 if '.' in name: # a.b
166 if '.' in name: # a.b
167 base, rest = name.split('.', 1)
167 base, rest = name.split('.', 1)
168 # email.__init__ loading email.mime
168 # email.__init__ loading email.mime
169 if globals and globals.get('__name__', None) == base:
169 if globals and globals.get('__name__', None) == base:
170 return _import(name, globals, locals, fromlist, level)
170 return _import(name, globals, locals, fromlist, level)
171 # if a is already demand-loaded, add b to its submodule list
171 # if a is already demand-loaded, add b to its submodule list
172 if base in locals:
172 if base in locals:
173 if isinstance(locals[base], _demandmod):
173 if isinstance(locals[base], _demandmod):
174 locals[base]._extend(rest)
174 locals[base]._extend(rest)
175 return locals[base]
175 return locals[base]
176 return _demandmod(name, globals, locals, level)
176 return _demandmod(name, globals, locals, level)
177 else:
177 else:
178 # There is a fromlist.
178 # There is a fromlist.
179 # from a import b,c,d
179 # from a import b,c,d
180 # from . import b,c,d
180 # from . import b,c,d
181 # from .a import b,c,d
181 # from .a import b,c,d
182
182
183 # level == -1: relative and absolute attempted (Python 2 only).
183 # level == -1: relative and absolute attempted (Python 2 only).
184 # level >= 0: absolute only (Python 2 w/ absolute_import and Python 3).
184 # level >= 0: absolute only (Python 2 w/ absolute_import and Python 3).
185 # The modern Mercurial convention is to use absolute_import everywhere,
185 # The modern Mercurial convention is to use absolute_import everywhere,
186 # so modern Mercurial code will have level >= 0.
186 # so modern Mercurial code will have level >= 0.
187
187
188 # The name of the module the import statement is located in.
188 # The name of the module the import statement is located in.
189 globalname = globals.get('__name__')
189 globalname = globals.get('__name__')
190
190
191 def processfromitem(mod, attr):
191 def processfromitem(mod, attr):
192 """Process an imported symbol in the import statement.
192 """Process an imported symbol in the import statement.
193
193
194 If the symbol doesn't exist in the parent module, it must be a
194 If the symbol doesn't exist in the parent module, it must be a
195 module. We set missing modules up as _demandmod instances.
195 module. We set missing modules up as _demandmod instances.
196 """
196 """
197 symbol = getattr(mod, attr, nothing)
197 symbol = getattr(mod, attr, nothing)
198 if symbol is nothing:
198 if symbol is nothing:
199 mn = '%s.%s' % (mod.__name__, attr)
199 mn = '%s.%s' % (mod.__name__, attr)
200 if mn in ignore:
200 if mn in ignore:
201 importfunc = _origimport
201 importfunc = _origimport
202 else:
202 else:
203 importfunc = _demandmod
203 importfunc = _demandmod
204 symbol = importfunc(attr, mod.__dict__, locals, level=1)
204 symbol = importfunc(attr, mod.__dict__, locals, level=1)
205 setattr(mod, attr, symbol)
205 setattr(mod, attr, symbol)
206
206
207 # Record the importing module references this symbol so we can
207 # Record the importing module references this symbol so we can
208 # replace the symbol with the actual module instance at load
208 # replace the symbol with the actual module instance at load
209 # time.
209 # time.
210 if globalname and isinstance(symbol, _demandmod):
210 if globalname and isinstance(symbol, _demandmod):
211 symbol._addref(globalname)
211 symbol._addref(globalname)
212
212
213 def chainmodules(rootmod, modname):
213 def chainmodules(rootmod, modname):
214 # recurse down the module chain, and return the leaf module
214 # recurse down the module chain, and return the leaf module
215 mod = rootmod
215 mod = rootmod
216 for comp in modname.split('.')[1:]:
216 for comp in modname.split('.')[1:]:
217 if getattr(mod, comp, nothing) is nothing:
217 if getattr(mod, comp, nothing) is nothing:
218 setattr(mod, comp, _demandmod(comp, mod.__dict__,
218 setattr(mod, comp, _demandmod(comp, mod.__dict__,
219 mod.__dict__, level=1))
219 mod.__dict__, level=1))
220 mod = getattr(mod, comp)
220 mod = getattr(mod, comp)
221 return mod
221 return mod
222
222
223 if level >= 0:
223 if level >= 0:
224 if name:
224 if name:
225 # "from a import b" or "from .a import b" style
225 # "from a import b" or "from .a import b" style
226 rootmod = _hgextimport(_origimport, name, globals, locals,
226 rootmod = _hgextimport(_origimport, name, globals, locals,
227 level=level)
227 level=level)
228 mod = chainmodules(rootmod, name)
228 mod = chainmodules(rootmod, name)
229 elif _pypy:
229 elif _pypy:
230 # PyPy's __import__ throws an exception if invoked
230 # PyPy's __import__ throws an exception if invoked
231 # with an empty name and no fromlist. Recreate the
231 # with an empty name and no fromlist. Recreate the
232 # desired behaviour by hand.
232 # desired behaviour by hand.
233 mn = globalname
233 mn = globalname
234 mod = sys.modules[mn]
234 mod = sys.modules[mn]
235 if getattr(mod, '__path__', nothing) is nothing:
235 if getattr(mod, '__path__', nothing) is nothing:
236 mn = mn.rsplit('.', 1)[0]
236 mn = mn.rsplit('.', 1)[0]
237 mod = sys.modules[mn]
237 mod = sys.modules[mn]
238 if level > 1:
238 if level > 1:
239 mn = mn.rsplit('.', level - 1)[0]
239 mn = mn.rsplit('.', level - 1)[0]
240 mod = sys.modules[mn]
240 mod = sys.modules[mn]
241 else:
241 else:
242 mod = _hgextimport(_origimport, name, globals, locals,
242 mod = _hgextimport(_origimport, name, globals, locals,
243 level=level)
243 level=level)
244
244
245 for x in fromlist:
245 for x in fromlist:
246 processfromitem(mod, x)
246 processfromitem(mod, x)
247
247
248 return mod
248 return mod
249
249
250 # But, we still need to support lazy loading of standard library and 3rd
250 # But, we still need to support lazy loading of standard library and 3rd
251 # party modules. So handle level == -1.
251 # party modules. So handle level == -1.
252 mod = _hgextimport(_origimport, name, globals, locals)
252 mod = _hgextimport(_origimport, name, globals, locals)
253 mod = chainmodules(mod, name)
253 mod = chainmodules(mod, name)
254
254
255 for x in fromlist:
255 for x in fromlist:
256 processfromitem(mod, x)
256 processfromitem(mod, x)
257
257
258 return mod
258 return mod
259
259
260 ignore = [
260 ignore = [
261 '__future__',
261 '__future__',
262 '_hashlib',
262 '_hashlib',
263 # ImportError during pkg_resources/__init__.py:fixup_namespace_package
263 # ImportError during pkg_resources/__init__.py:fixup_namespace_package
264 '_imp',
264 '_imp',
265 '_xmlplus',
265 '_xmlplus',
266 'fcntl',
266 'fcntl',
267 'nt', # pathlib2 tests the existence of built-in 'nt' module
267 'win32com.gen_py',
268 'win32com.gen_py',
268 '_winreg', # 2.7 mimetypes needs immediate ImportError
269 '_winreg', # 2.7 mimetypes needs immediate ImportError
269 'pythoncom',
270 'pythoncom',
270 # imported by tarfile, not available under Windows
271 # imported by tarfile, not available under Windows
271 'pwd',
272 'pwd',
272 'grp',
273 'grp',
273 # imported by profile, itself imported by hotshot.stats,
274 # imported by profile, itself imported by hotshot.stats,
274 # not available under Windows
275 # not available under Windows
275 'resource',
276 'resource',
276 # this trips up many extension authors
277 # this trips up many extension authors
277 'gtk',
278 'gtk',
278 # setuptools' pkg_resources.py expects "from __main__ import x" to
279 # setuptools' pkg_resources.py expects "from __main__ import x" to
279 # raise ImportError if x not defined
280 # raise ImportError if x not defined
280 '__main__',
281 '__main__',
281 '_ssl', # conditional imports in the stdlib, issue1964
282 '_ssl', # conditional imports in the stdlib, issue1964
282 '_sre', # issue4920
283 '_sre', # issue4920
283 'rfc822',
284 'rfc822',
284 'mimetools',
285 'mimetools',
285 'sqlalchemy.events', # has import-time side effects (issue5085)
286 'sqlalchemy.events', # has import-time side effects (issue5085)
286 # setuptools 8 expects this module to explode early when not on windows
287 # setuptools 8 expects this module to explode early when not on windows
287 'distutils.msvc9compiler',
288 'distutils.msvc9compiler',
288 ]
289 ]
289
290
290 if _pypy:
291 if _pypy:
291 ignore.extend([
292 ignore.extend([
292 # _ctypes.pointer is shadowed by "from ... import pointer" (PyPy 5)
293 # _ctypes.pointer is shadowed by "from ... import pointer" (PyPy 5)
293 '_ctypes.pointer',
294 '_ctypes.pointer',
294 ])
295 ])
295
296
296 def isenabled():
297 def isenabled():
297 return builtins.__import__ == _demandimport
298 return builtins.__import__ == _demandimport
298
299
299 def enable():
300 def enable():
300 "enable global demand-loading of modules"
301 "enable global demand-loading of modules"
301 if os.environ.get('HGDEMANDIMPORT') != 'disable':
302 if os.environ.get('HGDEMANDIMPORT') != 'disable':
302 builtins.__import__ = _demandimport
303 builtins.__import__ = _demandimport
303
304
304 def disable():
305 def disable():
305 "disable global demand-loading of modules"
306 "disable global demand-loading of modules"
306 builtins.__import__ = _origimport
307 builtins.__import__ = _origimport
307
308
308 @contextmanager
309 @contextmanager
309 def deactivated():
310 def deactivated():
310 "context manager for disabling demandimport in 'with' blocks"
311 "context manager for disabling demandimport in 'with' blocks"
311 demandenabled = isenabled()
312 demandenabled = isenabled()
312 if demandenabled:
313 if demandenabled:
313 disable()
314 disable()
314
315
315 try:
316 try:
316 yield
317 yield
317 finally:
318 finally:
318 if demandenabled:
319 if demandenabled:
319 enable()
320 enable()
General Comments 0
You need to be logged in to leave comments. Login now