|
1 """Import hook support. |
|
2 |
|
3 Consistent use of this module will make it possible to change the |
|
4 different mechanisms involved in loading modules independently. |
|
5 |
|
6 While the built-in module imp exports interfaces to the built-in |
|
7 module searching and loading algorithm, and it is possible to replace |
|
8 the built-in function __import__ in order to change the semantics of |
|
9 the import statement, until now it has been difficult to combine the |
|
10 effect of different __import__ hacks, like loading modules from URLs |
|
11 by rimport.py, or restricted execution by rexec.py. |
|
12 |
|
13 This module defines three new concepts: |
|
14 |
|
15 1) A "file system hooks" class provides an interface to a filesystem. |
|
16 |
|
17 One hooks class is defined (Hooks), which uses the interface provided |
|
18 by standard modules os and os.path. It should be used as the base |
|
19 class for other hooks classes. |
|
20 |
|
21 2) A "module loader" class provides an interface to search for a |
|
22 module in a search path and to load it. It defines a method which |
|
23 searches for a module in a single directory; by overriding this method |
|
24 one can redefine the details of the search. If the directory is None, |
|
25 built-in and frozen modules are searched instead. |
|
26 |
|
27 Two module loader class are defined, both implementing the search |
|
28 strategy used by the built-in __import__ function: ModuleLoader uses |
|
29 the imp module's find_module interface, while HookableModuleLoader |
|
30 uses a file system hooks class to interact with the file system. Both |
|
31 use the imp module's load_* interfaces to actually load the module. |
|
32 |
|
33 3) A "module importer" class provides an interface to import a |
|
34 module, as well as interfaces to reload and unload a module. It also |
|
35 provides interfaces to install and uninstall itself instead of the |
|
36 default __import__ and reload (and unload) functions. |
|
37 |
|
38 One module importer class is defined (ModuleImporter), which uses a |
|
39 module loader instance passed in (by default HookableModuleLoader is |
|
40 instantiated). |
|
41 |
|
42 The classes defined here should be used as base classes for extended |
|
43 functionality along those lines. |
|
44 |
|
45 If a module importer class supports dotted names, its import_module() |
|
46 must return a different value depending on whether it is called on |
|
47 behalf of a "from ... import ..." statement or not. (This is caused |
|
48 by the way the __import__ hook is used by the Python interpreter.) It |
|
49 would also do wise to install a different version of reload(). |
|
50 |
|
51 """ |
|
52 |
|
53 |
|
54 import __builtin__ |
|
55 import imp |
|
56 import os |
|
57 import sys |
|
58 |
|
59 __all__ = ["BasicModuleLoader","Hooks","ModuleLoader","FancyModuleLoader", |
|
60 "BasicModuleImporter","ModuleImporter","install","uninstall"] |
|
61 |
|
62 VERBOSE = 0 |
|
63 |
|
64 |
|
65 from imp import C_EXTENSION, PY_SOURCE, PY_COMPILED |
|
66 from imp import C_BUILTIN, PY_FROZEN, PKG_DIRECTORY |
|
67 BUILTIN_MODULE = C_BUILTIN |
|
68 FROZEN_MODULE = PY_FROZEN |
|
69 |
|
70 |
|
71 class _Verbose: |
|
72 |
|
73 def __init__(self, verbose = VERBOSE): |
|
74 self.verbose = verbose |
|
75 |
|
76 def get_verbose(self): |
|
77 return self.verbose |
|
78 |
|
79 def set_verbose(self, verbose): |
|
80 self.verbose = verbose |
|
81 |
|
82 # XXX The following is an experimental interface |
|
83 |
|
84 def note(self, *args): |
|
85 if self.verbose: |
|
86 self.message(*args) |
|
87 |
|
88 def message(self, format, *args): |
|
89 if args: |
|
90 print format%args |
|
91 else: |
|
92 print format |
|
93 |
|
94 |
|
95 class BasicModuleLoader(_Verbose): |
|
96 |
|
97 """Basic module loader. |
|
98 |
|
99 This provides the same functionality as built-in import. It |
|
100 doesn't deal with checking sys.modules -- all it provides is |
|
101 find_module() and a load_module(), as well as find_module_in_dir() |
|
102 which searches just one directory, and can be overridden by a |
|
103 derived class to change the module search algorithm when the basic |
|
104 dependency on sys.path is unchanged. |
|
105 |
|
106 The interface is a little more convenient than imp's: |
|
107 find_module(name, [path]) returns None or 'stuff', and |
|
108 load_module(name, stuff) loads the module. |
|
109 |
|
110 """ |
|
111 |
|
112 def find_module(self, name, path = None): |
|
113 if path is None: |
|
114 path = [None] + self.default_path() |
|
115 for dir in path: |
|
116 stuff = self.find_module_in_dir(name, dir) |
|
117 if stuff: return stuff |
|
118 return None |
|
119 |
|
120 def default_path(self): |
|
121 return sys.path |
|
122 |
|
123 def find_module_in_dir(self, name, dir): |
|
124 if dir is None: |
|
125 return self.find_builtin_module(name) |
|
126 else: |
|
127 try: |
|
128 return imp.find_module(name, [dir]) |
|
129 except ImportError: |
|
130 return None |
|
131 |
|
132 def find_builtin_module(self, name): |
|
133 # XXX frozen packages? |
|
134 if imp.is_builtin(name): |
|
135 return None, '', ('', '', BUILTIN_MODULE) |
|
136 if imp.is_frozen(name): |
|
137 return None, '', ('', '', FROZEN_MODULE) |
|
138 return None |
|
139 |
|
140 def load_module(self, name, stuff): |
|
141 file, filename, info = stuff |
|
142 try: |
|
143 return imp.load_module(name, file, filename, info) |
|
144 finally: |
|
145 if file: file.close() |
|
146 |
|
147 |
|
148 class Hooks(_Verbose): |
|
149 |
|
150 """Hooks into the filesystem and interpreter. |
|
151 |
|
152 By deriving a subclass you can redefine your filesystem interface, |
|
153 e.g. to merge it with the URL space. |
|
154 |
|
155 This base class behaves just like the native filesystem. |
|
156 |
|
157 """ |
|
158 |
|
159 # imp interface |
|
160 def get_suffixes(self): return imp.get_suffixes() |
|
161 def new_module(self, name): return imp.new_module(name) |
|
162 def is_builtin(self, name): return imp.is_builtin(name) |
|
163 def init_builtin(self, name): return imp.init_builtin(name) |
|
164 def is_frozen(self, name): return imp.is_frozen(name) |
|
165 def init_frozen(self, name): return imp.init_frozen(name) |
|
166 def get_frozen_object(self, name): return imp.get_frozen_object(name) |
|
167 def load_source(self, name, filename, file=None): |
|
168 return imp.load_source(name, filename, file) |
|
169 def load_compiled(self, name, filename, file=None): |
|
170 return imp.load_compiled(name, filename, file) |
|
171 def load_dynamic(self, name, filename, file=None): |
|
172 return imp.load_dynamic(name, filename, file) |
|
173 def load_package(self, name, filename, file=None): |
|
174 return imp.load_module(name, file, filename, ("", "", PKG_DIRECTORY)) |
|
175 |
|
176 def add_module(self, name): |
|
177 d = self.modules_dict() |
|
178 if name in d: return d[name] |
|
179 d[name] = m = self.new_module(name) |
|
180 return m |
|
181 |
|
182 # sys interface |
|
183 def modules_dict(self): return sys.modules |
|
184 def default_path(self): return sys.path |
|
185 |
|
186 def path_split(self, x): return os.path.split(x) |
|
187 def path_join(self, x, y): return os.path.join(x, y) |
|
188 def path_isabs(self, x): return os.path.isabs(x) |
|
189 # etc. |
|
190 |
|
191 def path_exists(self, x): return os.path.exists(x) |
|
192 def path_isdir(self, x): return os.path.isdir(x) |
|
193 def path_isfile(self, x): return os.path.isfile(x) |
|
194 def path_islink(self, x): return os.path.islink(x) |
|
195 # etc. |
|
196 |
|
197 def openfile(self, *x): return open(*x) |
|
198 openfile_error = IOError |
|
199 def listdir(self, x): return os.listdir(x) |
|
200 listdir_error = os.error |
|
201 # etc. |
|
202 |
|
203 |
|
204 class ModuleLoader(BasicModuleLoader): |
|
205 |
|
206 """Default module loader; uses file system hooks. |
|
207 |
|
208 By defining suitable hooks, you might be able to load modules from |
|
209 other sources than the file system, e.g. from compressed or |
|
210 encrypted files, tar files or (if you're brave!) URLs. |
|
211 |
|
212 """ |
|
213 |
|
214 def __init__(self, hooks = None, verbose = VERBOSE): |
|
215 BasicModuleLoader.__init__(self, verbose) |
|
216 self.hooks = hooks or Hooks(verbose) |
|
217 |
|
218 def default_path(self): |
|
219 return self.hooks.default_path() |
|
220 |
|
221 def modules_dict(self): |
|
222 return self.hooks.modules_dict() |
|
223 |
|
224 def get_hooks(self): |
|
225 return self.hooks |
|
226 |
|
227 def set_hooks(self, hooks): |
|
228 self.hooks = hooks |
|
229 |
|
230 def find_builtin_module(self, name): |
|
231 # XXX frozen packages? |
|
232 if self.hooks.is_builtin(name): |
|
233 return None, '', ('', '', BUILTIN_MODULE) |
|
234 if self.hooks.is_frozen(name): |
|
235 return None, '', ('', '', FROZEN_MODULE) |
|
236 return None |
|
237 |
|
238 def find_module_in_dir(self, name, dir, allow_packages=1): |
|
239 if dir is None: |
|
240 return self.find_builtin_module(name) |
|
241 if allow_packages: |
|
242 fullname = self.hooks.path_join(dir, name) |
|
243 if self.hooks.path_isdir(fullname): |
|
244 stuff = self.find_module_in_dir("__init__", fullname, 0) |
|
245 if stuff: |
|
246 file = stuff[0] |
|
247 if file: file.close() |
|
248 return None, fullname, ('', '', PKG_DIRECTORY) |
|
249 for info in self.hooks.get_suffixes(): |
|
250 suff, mode, type = info |
|
251 fullname = self.hooks.path_join(dir, name+suff) |
|
252 try: |
|
253 fp = self.hooks.openfile(fullname, mode) |
|
254 return fp, fullname, info |
|
255 except self.hooks.openfile_error: |
|
256 pass |
|
257 return None |
|
258 |
|
259 def load_module(self, name, stuff): |
|
260 file, filename, info = stuff |
|
261 (suff, mode, type) = info |
|
262 try: |
|
263 if type == BUILTIN_MODULE: |
|
264 return self.hooks.init_builtin(name) |
|
265 if type == FROZEN_MODULE: |
|
266 return self.hooks.init_frozen(name) |
|
267 if type == C_EXTENSION: |
|
268 m = self.hooks.load_dynamic(name, filename, file) |
|
269 elif type == PY_SOURCE: |
|
270 m = self.hooks.load_source(name, filename, file) |
|
271 elif type == PY_COMPILED: |
|
272 m = self.hooks.load_compiled(name, filename, file) |
|
273 elif type == PKG_DIRECTORY: |
|
274 m = self.hooks.load_package(name, filename, file) |
|
275 else: |
|
276 raise ImportError, "Unrecognized module type (%r) for %s" % \ |
|
277 (type, name) |
|
278 finally: |
|
279 if file: file.close() |
|
280 m.__file__ = filename |
|
281 return m |
|
282 |
|
283 |
|
284 class FancyModuleLoader(ModuleLoader): |
|
285 |
|
286 """Fancy module loader -- parses and execs the code itself.""" |
|
287 |
|
288 def load_module(self, name, stuff): |
|
289 file, filename, (suff, mode, type) = stuff |
|
290 realfilename = filename |
|
291 path = None |
|
292 |
|
293 if type == PKG_DIRECTORY: |
|
294 initstuff = self.find_module_in_dir("__init__", filename, 0) |
|
295 if not initstuff: |
|
296 raise ImportError, "No __init__ module in package %s" % name |
|
297 initfile, initfilename, initinfo = initstuff |
|
298 initsuff, initmode, inittype = initinfo |
|
299 if inittype not in (PY_COMPILED, PY_SOURCE): |
|
300 if initfile: initfile.close() |
|
301 raise ImportError, \ |
|
302 "Bad type (%r) for __init__ module in package %s" % ( |
|
303 inittype, name) |
|
304 path = [filename] |
|
305 file = initfile |
|
306 realfilename = initfilename |
|
307 type = inittype |
|
308 |
|
309 if type == FROZEN_MODULE: |
|
310 code = self.hooks.get_frozen_object(name) |
|
311 elif type == PY_COMPILED: |
|
312 import marshal |
|
313 file.seek(8) |
|
314 code = marshal.load(file) |
|
315 elif type == PY_SOURCE: |
|
316 data = file.read() |
|
317 code = compile(data, realfilename, 'exec') |
|
318 else: |
|
319 return ModuleLoader.load_module(self, name, stuff) |
|
320 |
|
321 m = self.hooks.add_module(name) |
|
322 if path: |
|
323 m.__path__ = path |
|
324 m.__file__ = filename |
|
325 try: |
|
326 exec code in m.__dict__ |
|
327 except: |
|
328 d = self.hooks.modules_dict() |
|
329 if name in d: |
|
330 del d[name] |
|
331 raise |
|
332 return m |
|
333 |
|
334 |
|
335 class BasicModuleImporter(_Verbose): |
|
336 |
|
337 """Basic module importer; uses module loader. |
|
338 |
|
339 This provides basic import facilities but no package imports. |
|
340 |
|
341 """ |
|
342 |
|
343 def __init__(self, loader = None, verbose = VERBOSE): |
|
344 _Verbose.__init__(self, verbose) |
|
345 self.loader = loader or ModuleLoader(None, verbose) |
|
346 self.modules = self.loader.modules_dict() |
|
347 |
|
348 def get_loader(self): |
|
349 return self.loader |
|
350 |
|
351 def set_loader(self, loader): |
|
352 self.loader = loader |
|
353 |
|
354 def get_hooks(self): |
|
355 return self.loader.get_hooks() |
|
356 |
|
357 def set_hooks(self, hooks): |
|
358 return self.loader.set_hooks(hooks) |
|
359 |
|
360 def import_module(self, name, globals={}, locals={}, fromlist=[]): |
|
361 name = str(name) |
|
362 if name in self.modules: |
|
363 return self.modules[name] # Fast path |
|
364 stuff = self.loader.find_module(name) |
|
365 if not stuff: |
|
366 raise ImportError, "No module named %s" % name |
|
367 return self.loader.load_module(name, stuff) |
|
368 |
|
369 def reload(self, module, path = None): |
|
370 name = str(module.__name__) |
|
371 stuff = self.loader.find_module(name, path) |
|
372 if not stuff: |
|
373 raise ImportError, "Module %s not found for reload" % name |
|
374 return self.loader.load_module(name, stuff) |
|
375 |
|
376 def unload(self, module): |
|
377 del self.modules[str(module.__name__)] |
|
378 # XXX Should this try to clear the module's namespace? |
|
379 |
|
380 def install(self): |
|
381 self.save_import_module = __builtin__.__import__ |
|
382 self.save_reload = __builtin__.reload |
|
383 if not hasattr(__builtin__, 'unload'): |
|
384 __builtin__.unload = None |
|
385 self.save_unload = __builtin__.unload |
|
386 __builtin__.__import__ = self.import_module |
|
387 __builtin__.reload = self.reload |
|
388 __builtin__.unload = self.unload |
|
389 |
|
390 def uninstall(self): |
|
391 __builtin__.__import__ = self.save_import_module |
|
392 __builtin__.reload = self.save_reload |
|
393 __builtin__.unload = self.save_unload |
|
394 if not __builtin__.unload: |
|
395 del __builtin__.unload |
|
396 |
|
397 |
|
398 class ModuleImporter(BasicModuleImporter): |
|
399 |
|
400 """A module importer that supports packages.""" |
|
401 |
|
402 def import_module(self, name, globals=None, locals=None, fromlist=None): |
|
403 parent = self.determine_parent(globals) |
|
404 q, tail = self.find_head_package(parent, str(name)) |
|
405 m = self.load_tail(q, tail) |
|
406 if not fromlist: |
|
407 return q |
|
408 if hasattr(m, "__path__"): |
|
409 self.ensure_fromlist(m, fromlist) |
|
410 return m |
|
411 |
|
412 def determine_parent(self, globals): |
|
413 if not globals or not "__name__" in globals: |
|
414 return None |
|
415 pname = globals['__name__'] |
|
416 if "__path__" in globals: |
|
417 parent = self.modules[pname] |
|
418 assert globals is parent.__dict__ |
|
419 return parent |
|
420 if '.' in pname: |
|
421 i = pname.rfind('.') |
|
422 pname = pname[:i] |
|
423 parent = self.modules[pname] |
|
424 assert parent.__name__ == pname |
|
425 return parent |
|
426 return None |
|
427 |
|
428 def find_head_package(self, parent, name): |
|
429 if '.' in name: |
|
430 i = name.find('.') |
|
431 head = name[:i] |
|
432 tail = name[i+1:] |
|
433 else: |
|
434 head = name |
|
435 tail = "" |
|
436 if parent: |
|
437 qname = "%s.%s" % (parent.__name__, head) |
|
438 else: |
|
439 qname = head |
|
440 q = self.import_it(head, qname, parent) |
|
441 if q: return q, tail |
|
442 if parent: |
|
443 qname = head |
|
444 parent = None |
|
445 q = self.import_it(head, qname, parent) |
|
446 if q: return q, tail |
|
447 raise ImportError, "No module named " + qname |
|
448 |
|
449 def load_tail(self, q, tail): |
|
450 m = q |
|
451 while tail: |
|
452 i = tail.find('.') |
|
453 if i < 0: i = len(tail) |
|
454 head, tail = tail[:i], tail[i+1:] |
|
455 mname = "%s.%s" % (m.__name__, head) |
|
456 m = self.import_it(head, mname, m) |
|
457 if not m: |
|
458 raise ImportError, "No module named " + mname |
|
459 return m |
|
460 |
|
461 def ensure_fromlist(self, m, fromlist, recursive=0): |
|
462 for sub in fromlist: |
|
463 if sub == "*": |
|
464 if not recursive: |
|
465 try: |
|
466 all = m.__all__ |
|
467 except AttributeError: |
|
468 pass |
|
469 else: |
|
470 self.ensure_fromlist(m, all, 1) |
|
471 continue |
|
472 if sub != "*" and not hasattr(m, sub): |
|
473 subname = "%s.%s" % (m.__name__, sub) |
|
474 submod = self.import_it(sub, subname, m) |
|
475 if not submod: |
|
476 raise ImportError, "No module named " + subname |
|
477 |
|
478 def import_it(self, partname, fqname, parent, force_load=0): |
|
479 if not partname: |
|
480 raise ValueError, "Empty module name" |
|
481 if not force_load: |
|
482 try: |
|
483 return self.modules[fqname] |
|
484 except KeyError: |
|
485 pass |
|
486 try: |
|
487 path = parent and parent.__path__ |
|
488 except AttributeError: |
|
489 return None |
|
490 partname = str(partname) |
|
491 stuff = self.loader.find_module(partname, path) |
|
492 if not stuff: |
|
493 return None |
|
494 fqname = str(fqname) |
|
495 m = self.loader.load_module(fqname, stuff) |
|
496 if parent: |
|
497 setattr(parent, partname, m) |
|
498 return m |
|
499 |
|
500 def reload(self, module): |
|
501 name = str(module.__name__) |
|
502 if '.' not in name: |
|
503 return self.import_it(name, name, None, force_load=1) |
|
504 i = name.rfind('.') |
|
505 pname = name[:i] |
|
506 parent = self.modules[pname] |
|
507 return self.import_it(name[i+1:], name, parent, force_load=1) |
|
508 |
|
509 |
|
510 default_importer = None |
|
511 current_importer = None |
|
512 |
|
513 def install(importer = None): |
|
514 global current_importer |
|
515 current_importer = importer or default_importer or ModuleImporter() |
|
516 current_importer.install() |
|
517 |
|
518 def uninstall(): |
|
519 global current_importer |
|
520 current_importer.uninstall() |