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