|
1 # demandimport.py - global demand-loading of modules for Mercurial |
|
2 # |
|
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com> |
|
4 # |
|
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. |
|
7 |
|
8 ''' |
|
9 demandimport - automatic demandloading of modules |
|
10 |
|
11 To enable this module, do: |
|
12 |
|
13 import demandimport; demandimport.enable() |
|
14 |
|
15 Imports of the following forms will be demand-loaded: |
|
16 |
|
17 import a, b.c |
|
18 import a.b as c |
|
19 from a import b,c # a will be loaded immediately |
|
20 |
|
21 These imports will not be delayed: |
|
22 |
|
23 from a import * |
|
24 b = __import__(a) |
|
25 ''' |
|
26 |
|
27 import __builtin__ |
|
28 _origimport = __import__ |
|
29 |
|
30 class _demandmod(object): |
|
31 """module demand-loader and proxy""" |
|
32 def __init__(self, name, globals, locals): |
|
33 if '.' in name: |
|
34 head, rest = name.split('.', 1) |
|
35 after = [rest] |
|
36 else: |
|
37 head = name |
|
38 after = [] |
|
39 object.__setattr__(self, "_data", (head, globals, locals, after)) |
|
40 object.__setattr__(self, "_module", None) |
|
41 def _extend(self, name): |
|
42 """add to the list of submodules to load""" |
|
43 self._data[3].append(name) |
|
44 def _load(self): |
|
45 if not self._module: |
|
46 head, globals, locals, after = self._data |
|
47 mod = _origimport(head, globals, locals) |
|
48 # load submodules |
|
49 def subload(mod, p): |
|
50 h, t = p, None |
|
51 if '.' in p: |
|
52 h, t = p.split('.', 1) |
|
53 if not hasattr(mod, h): |
|
54 setattr(mod, h, _demandmod(p, mod.__dict__, mod.__dict__)) |
|
55 elif t: |
|
56 subload(getattr(mod, h), t) |
|
57 |
|
58 for x in after: |
|
59 subload(mod, x) |
|
60 |
|
61 # are we in the locals dictionary still? |
|
62 if locals and locals.get(head) == self: |
|
63 locals[head] = mod |
|
64 object.__setattr__(self, "_module", mod) |
|
65 |
|
66 def __repr__(self): |
|
67 if self._module: |
|
68 return "<proxied module '%s'>" % self._data[0] |
|
69 return "<unloaded module '%s'>" % self._data[0] |
|
70 def __call__(self, *args, **kwargs): |
|
71 raise TypeError("%s object is not callable" % repr(self)) |
|
72 def __getattribute__(self, attr): |
|
73 if attr in ('_data', '_extend', '_load', '_module'): |
|
74 return object.__getattribute__(self, attr) |
|
75 self._load() |
|
76 return getattr(self._module, attr) |
|
77 def __setattr__(self, attr, val): |
|
78 self._load() |
|
79 setattr(self._module, attr, val) |
|
80 |
|
81 def _demandimport(name, globals=None, locals=None, fromlist=None, level=None): |
|
82 if not locals or name in ignore or fromlist == ('*',): |
|
83 # these cases we can't really delay |
|
84 if level is None: |
|
85 return _origimport(name, globals, locals, fromlist) |
|
86 else: |
|
87 return _origimport(name, globals, locals, fromlist, level) |
|
88 elif not fromlist: |
|
89 # import a [as b] |
|
90 if '.' in name: # a.b |
|
91 base, rest = name.split('.', 1) |
|
92 # email.__init__ loading email.mime |
|
93 if globals and globals.get('__name__', None) == base: |
|
94 return _origimport(name, globals, locals, fromlist) |
|
95 # if a is already demand-loaded, add b to its submodule list |
|
96 if base in locals: |
|
97 if isinstance(locals[base], _demandmod): |
|
98 locals[base]._extend(rest) |
|
99 return locals[base] |
|
100 return _demandmod(name, globals, locals) |
|
101 else: |
|
102 if level is not None: |
|
103 # from . import b,c,d or from .a import b,c,d |
|
104 return _origimport(name, globals, locals, fromlist, level) |
|
105 # from a import b,c,d |
|
106 mod = _origimport(name, globals, locals) |
|
107 # recurse down the module chain |
|
108 for comp in name.split('.')[1:]: |
|
109 if not hasattr(mod, comp): |
|
110 setattr(mod, comp, _demandmod(comp, mod.__dict__, mod.__dict__)) |
|
111 mod = getattr(mod, comp) |
|
112 for x in fromlist: |
|
113 # set requested submodules for demand load |
|
114 if not(hasattr(mod, x)): |
|
115 setattr(mod, x, _demandmod(x, mod.__dict__, locals)) |
|
116 return mod |
|
117 |
|
118 ignore = [ |
|
119 '_hashlib', |
|
120 '_xmlplus', |
|
121 'fcntl', |
|
122 'win32com.gen_py', |
|
123 '_winreg', # 2.7 mimetypes needs immediate ImportError |
|
124 'pythoncom', |
|
125 # imported by tarfile, not available under Windows |
|
126 'pwd', |
|
127 'grp', |
|
128 # imported by profile, itself imported by hotshot.stats, |
|
129 # not available under Windows |
|
130 'resource', |
|
131 # this trips up many extension authors |
|
132 'gtk', |
|
133 # setuptools' pkg_resources.py expects "from __main__ import x" to |
|
134 # raise ImportError if x not defined |
|
135 '__main__', |
|
136 '_ssl', # conditional imports in the stdlib, issue1964 |
|
137 ] |
|
138 |
|
139 def enable(): |
|
140 "enable global demand-loading of modules" |
|
141 __builtin__.__import__ = _demandimport |
|
142 |
|
143 def disable(): |
|
144 "disable global demand-loading of modules" |
|
145 __builtin__.__import__ = _origimport |
|
146 |