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