##// END OF EJS Templates
demandimport: enforce ignore list while processing modules in fromlist...
Yuya Nishihara -
r28175:c25e3fd3 default
parent child Browse files
Show More
@@ -1,280 +1,285
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 symbol = _demandmod(attr, mod.__dict__, locals, level=1)
177 mn = '%s.%s' % (mod.__name__, attr)
178 if mn in ignore:
179 importfunc = _origimport
180 else:
181 importfunc = _demandmod
182 symbol = importfunc(attr, mod.__dict__, locals, level=1)
178 183 setattr(mod, attr, symbol)
179 184
180 185 # Record the importing module references this symbol so we can
181 186 # replace the symbol with the actual module instance at load
182 187 # time.
183 188 if globalname and isinstance(symbol, _demandmod):
184 189 symbol._addref(globalname)
185 190
186 191 if level >= 0:
187 192 # The "from a import b,c,d" or "from .a import b,c,d"
188 193 # syntax gives errors with some modules for unknown
189 194 # reasons. Work around the problem.
190 195 if name:
191 196 return _hgextimport(_origimport, name, globals, locals,
192 197 fromlist, level)
193 198
194 199 if _pypy:
195 200 # PyPy's __import__ throws an exception if invoked
196 201 # with an empty name and no fromlist. Recreate the
197 202 # desired behaviour by hand.
198 203 mn = globalname
199 204 mod = sys.modules[mn]
200 205 if getattr(mod, '__path__', nothing) is nothing:
201 206 mn = mn.rsplit('.', 1)[0]
202 207 mod = sys.modules[mn]
203 208 if level > 1:
204 209 mn = mn.rsplit('.', level - 1)[0]
205 210 mod = sys.modules[mn]
206 211 else:
207 212 mod = _hgextimport(_origimport, name, globals, locals,
208 213 level=level)
209 214
210 215 for x in fromlist:
211 216 processfromitem(mod, x)
212 217
213 218 return mod
214 219
215 220 # But, we still need to support lazy loading of standard library and 3rd
216 221 # party modules. So handle level == -1.
217 222 mod = _hgextimport(_origimport, name, globals, locals)
218 223 # recurse down the module chain
219 224 for comp in name.split('.')[1:]:
220 225 if getattr(mod, comp, nothing) is nothing:
221 226 setattr(mod, comp,
222 227 _demandmod(comp, mod.__dict__, mod.__dict__))
223 228 mod = getattr(mod, comp)
224 229
225 230 for x in fromlist:
226 231 processfromitem(mod, x)
227 232
228 233 return mod
229 234
230 235 ignore = [
231 236 '__future__',
232 237 '_hashlib',
233 238 '_xmlplus',
234 239 'fcntl',
235 240 'win32com.gen_py',
236 241 '_winreg', # 2.7 mimetypes needs immediate ImportError
237 242 'pythoncom',
238 243 # imported by tarfile, not available under Windows
239 244 'pwd',
240 245 'grp',
241 246 # imported by profile, itself imported by hotshot.stats,
242 247 # not available under Windows
243 248 'resource',
244 249 # this trips up many extension authors
245 250 'gtk',
246 251 # setuptools' pkg_resources.py expects "from __main__ import x" to
247 252 # raise ImportError if x not defined
248 253 '__main__',
249 254 '_ssl', # conditional imports in the stdlib, issue1964
250 255 '_sre', # issue4920
251 256 'rfc822',
252 257 'mimetools',
253 258 # setuptools 8 expects this module to explode early when not on windows
254 259 'distutils.msvc9compiler'
255 260 ]
256 261
257 262 def isenabled():
258 263 return builtins.__import__ == _demandimport
259 264
260 265 def enable():
261 266 "enable global demand-loading of modules"
262 267 if os.environ.get('HGDEMANDIMPORT') != 'disable':
263 268 builtins.__import__ = _demandimport
264 269
265 270 def disable():
266 271 "disable global demand-loading of modules"
267 272 builtins.__import__ = _origimport
268 273
269 274 @contextmanager
270 275 def deactivated():
271 276 "context manager for disabling demandimport in 'with' blocks"
272 277 demandenabled = isenabled()
273 278 if demandenabled:
274 279 disable()
275 280
276 281 try:
277 282 yield
278 283 finally:
279 284 if demandenabled:
280 285 enable()
General Comments 0
You need to be logged in to leave comments. Login now