##// END OF EJS Templates
demandimport: consolidate code for processing items in fromlist...
Gregory Szorc -
r26455:f2bf76d3 default
parent child Browse files
Show More
@@ -1,230 +1,237 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):
147 """Process an imported symbol in the import statement.
148
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.
151 """
152 if getattr(mod, attr, nothing) is nothing:
153 setattr(mod, attr,
154 _demandmod(attr, mod.__dict__, locals, **kwargs))
155
146 if level >= 0:
156 if level >= 0:
147 # Mercurial's enforced import style does not use
157 # Mercurial's enforced import style does not use
148 # "from a import b,c,d" or "from .a import b,c,d" syntax. In
158 # "from a import b,c,d" or "from .a import b,c,d" syntax. In
149 # addition, this appears to be giving errors with some modules
159 # addition, this appears to be giving errors with some modules
150 # for unknown reasons. Since we shouldn't be using this syntax
160 # for unknown reasons. Since we shouldn't be using this syntax
151 # much, work around the problems.
161 # much, work around the problems.
152 if name:
162 if name:
153 return _hgextimport(_origimport, name, globals, locals,
163 return _hgextimport(_origimport, name, globals, locals,
154 fromlist, level)
164 fromlist, level)
155
165
156 mod = _hgextimport(_origimport, name, globals, locals, level=level)
166 mod = _hgextimport(_origimport, name, globals, locals, level=level)
167
157 for x in fromlist:
168 for x in fromlist:
158 # Missing symbols mean they weren't defined in the module
169 processfromitem(mod, x, level=level)
159 # itself which means they are sub-modules.
160 if getattr(mod, x, nothing) is nothing:
161 setattr(mod, x,
162 _demandmod(x, mod.__dict__, locals, level=level))
163
170
164 return mod
171 return mod
165
172
166 # But, we still need to support lazy loading of standard library and 3rd
173 # But, we still need to support lazy loading of standard library and 3rd
167 # party modules. So handle level == -1.
174 # party modules. So handle level == -1.
168 mod = _hgextimport(_origimport, name, globals, locals)
175 mod = _hgextimport(_origimport, name, globals, locals)
169 # recurse down the module chain
176 # recurse down the module chain
170 for comp in name.split('.')[1:]:
177 for comp in name.split('.')[1:]:
171 if getattr(mod, comp, nothing) is nothing:
178 if getattr(mod, comp, nothing) is nothing:
172 setattr(mod, comp,
179 setattr(mod, comp,
173 _demandmod(comp, mod.__dict__, mod.__dict__))
180 _demandmod(comp, mod.__dict__, mod.__dict__))
174 mod = getattr(mod, comp)
181 mod = getattr(mod, comp)
182
175 for x in fromlist:
183 for x in fromlist:
176 # set requested submodules for demand load
184 processfromitem(mod, x)
177 if getattr(mod, x, nothing) is nothing:
185
178 setattr(mod, x, _demandmod(x, mod.__dict__, locals))
179 return mod
186 return mod
180
187
181 ignore = [
188 ignore = [
182 '__future__',
189 '__future__',
183 '_hashlib',
190 '_hashlib',
184 '_xmlplus',
191 '_xmlplus',
185 'fcntl',
192 'fcntl',
186 'win32com.gen_py',
193 'win32com.gen_py',
187 '_winreg', # 2.7 mimetypes needs immediate ImportError
194 '_winreg', # 2.7 mimetypes needs immediate ImportError
188 'pythoncom',
195 'pythoncom',
189 # imported by tarfile, not available under Windows
196 # imported by tarfile, not available under Windows
190 'pwd',
197 'pwd',
191 'grp',
198 'grp',
192 # imported by profile, itself imported by hotshot.stats,
199 # imported by profile, itself imported by hotshot.stats,
193 # not available under Windows
200 # not available under Windows
194 'resource',
201 'resource',
195 # this trips up many extension authors
202 # this trips up many extension authors
196 'gtk',
203 'gtk',
197 # setuptools' pkg_resources.py expects "from __main__ import x" to
204 # setuptools' pkg_resources.py expects "from __main__ import x" to
198 # raise ImportError if x not defined
205 # raise ImportError if x not defined
199 '__main__',
206 '__main__',
200 '_ssl', # conditional imports in the stdlib, issue1964
207 '_ssl', # conditional imports in the stdlib, issue1964
201 'rfc822',
208 'rfc822',
202 'mimetools',
209 'mimetools',
203 # setuptools 8 expects this module to explode early when not on windows
210 # setuptools 8 expects this module to explode early when not on windows
204 'distutils.msvc9compiler'
211 'distutils.msvc9compiler'
205 ]
212 ]
206
213
207 def isenabled():
214 def isenabled():
208 return builtins.__import__ == _demandimport
215 return builtins.__import__ == _demandimport
209
216
210 def enable():
217 def enable():
211 "enable global demand-loading of modules"
218 "enable global demand-loading of modules"
212 if os.environ.get('HGDEMANDIMPORT') != 'disable':
219 if os.environ.get('HGDEMANDIMPORT') != 'disable':
213 builtins.__import__ = _demandimport
220 builtins.__import__ = _demandimport
214
221
215 def disable():
222 def disable():
216 "disable global demand-loading of modules"
223 "disable global demand-loading of modules"
217 builtins.__import__ = _origimport
224 builtins.__import__ = _origimport
218
225
219 @contextmanager
226 @contextmanager
220 def deactivated():
227 def deactivated():
221 "context manager for disabling demandimport in 'with' blocks"
228 "context manager for disabling demandimport in 'with' blocks"
222 demandenabled = isenabled()
229 demandenabled = isenabled()
223 if demandenabled:
230 if demandenabled:
224 disable()
231 disable()
225
232
226 try:
233 try:
227 yield
234 yield
228 finally:
235 finally:
229 if demandenabled:
236 if demandenabled:
230 enable()
237 enable()
General Comments 0
You need to be logged in to leave comments. Login now