1 '''Demand load modules when used, not when imported.''' |
|
2 |
|
3 __author__ = '''Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>. |
|
4 This software may be used and distributed according to the terms |
|
5 of the GNU General Public License, incorporated herein by reference.''' |
|
6 |
|
7 # this is based on matt's original demandload module. it is a |
|
8 # complete rewrite. some time, we may need to support syntax of |
|
9 # "import foo as bar". |
|
10 |
|
11 class _importer(object): |
|
12 '''import a module. it is not imported until needed, and is |
|
13 imported at most once per scope.''' |
|
14 |
|
15 def __init__(self, scope, modname, fromlist): |
|
16 '''scope is context (globals() or locals()) in which import |
|
17 should be made. modname is name of module to import. |
|
18 fromlist is list of modules for "from foo import ..." |
|
19 emulation.''' |
|
20 |
|
21 self.scope = scope |
|
22 self.modname = modname |
|
23 self.fromlist = fromlist |
|
24 self.mod = None |
|
25 |
|
26 def module(self): |
|
27 '''import the module if needed, and return.''' |
|
28 if self.mod is None: |
|
29 self.mod = __import__(self.modname, self.scope, self.scope, |
|
30 self.fromlist) |
|
31 del self.modname, self.fromlist |
|
32 return self.mod |
|
33 |
|
34 class _replacer(object): |
|
35 '''placeholder for a demand loaded module. demandload puts this in |
|
36 a target scope. when an attribute of this object is looked up, |
|
37 this object is replaced in the target scope with the actual |
|
38 module. |
|
39 |
|
40 we use __getattribute__ to avoid namespace clashes between |
|
41 placeholder object and real module.''' |
|
42 |
|
43 def __init__(self, importer, target): |
|
44 self.importer = importer |
|
45 self.target = target |
|
46 # consider case where we do this: |
|
47 # demandload(globals(), 'foo.bar foo.quux') |
|
48 # foo will already exist in target scope when we get to |
|
49 # foo.quux. so we remember that we will need to demandload |
|
50 # quux into foo's scope when we really load it. |
|
51 self.later = [] |
|
52 |
|
53 def module(self): |
|
54 return object.__getattribute__(self, 'importer').module() |
|
55 |
|
56 def __getattribute__(self, key): |
|
57 '''look up an attribute in a module and return it. replace the |
|
58 name of the module in the caller\'s dict with the actual |
|
59 module.''' |
|
60 |
|
61 module = object.__getattribute__(self, 'module')() |
|
62 target = object.__getattribute__(self, 'target') |
|
63 importer = object.__getattribute__(self, 'importer') |
|
64 later = object.__getattribute__(self, 'later') |
|
65 |
|
66 if later: |
|
67 demandload(module.__dict__, ' '.join(later)) |
|
68 |
|
69 importer.scope[target] = module |
|
70 |
|
71 return getattr(module, key) |
|
72 |
|
73 class _replacer_from(_replacer): |
|
74 '''placeholder for a demand loaded module. used for "from foo |
|
75 import ..." emulation. semantics of this are different than |
|
76 regular import, so different implementation needed.''' |
|
77 |
|
78 def module(self): |
|
79 importer = object.__getattribute__(self, 'importer') |
|
80 target = object.__getattribute__(self, 'target') |
|
81 |
|
82 return getattr(importer.module(), target) |
|
83 |
|
84 def __call__(self, *args, **kwargs): |
|
85 target = object.__getattribute__(self, 'module')() |
|
86 return target(*args, **kwargs) |
|
87 |
|
88 def demandload(scope, modules): |
|
89 '''import modules into scope when each is first used. |
|
90 |
|
91 scope should be the value of globals() in the module calling this |
|
92 function, or locals() in the calling function. |
|
93 |
|
94 modules is a string listing module names, separated by white |
|
95 space. names are handled like this: |
|
96 |
|
97 foo import foo |
|
98 foo bar import foo, bar |
|
99 foo@bar import foo as bar |
|
100 foo.bar import foo.bar |
|
101 foo:bar from foo import bar |
|
102 foo:bar,quux from foo import bar, quux |
|
103 foo.bar:quux from foo.bar import quux''' |
|
104 |
|
105 for mod in modules.split(): |
|
106 col = mod.find(':') |
|
107 if col >= 0: |
|
108 fromlist = mod[col+1:].split(',') |
|
109 mod = mod[:col] |
|
110 else: |
|
111 fromlist = [] |
|
112 as_ = None |
|
113 if '@' in mod: |
|
114 mod, as_ = mod.split("@") |
|
115 importer = _importer(scope, mod, fromlist) |
|
116 if fromlist: |
|
117 for name in fromlist: |
|
118 scope[name] = _replacer_from(importer, name) |
|
119 else: |
|
120 dot = mod.find('.') |
|
121 if dot >= 0: |
|
122 basemod = mod[:dot] |
|
123 val = scope.get(basemod) |
|
124 # if base module has already been demandload()ed, |
|
125 # remember to load this submodule into its namespace |
|
126 # when needed. |
|
127 if isinstance(val, _replacer): |
|
128 later = object.__getattribute__(val, 'later') |
|
129 later.append(mod[dot+1:]) |
|
130 continue |
|
131 else: |
|
132 basemod = mod |
|
133 if not as_: |
|
134 as_ = basemod |
|
135 scope[as_] = _replacer(importer, as_) |
|