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