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