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