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