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