Guido van Rossum | e7e578f | 1995-08-04 04:00:20 +0000 | [diff] [blame] | 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 | (rimport.py), implementing a hierarchical module namespace (newimp.py) |
| 12 | or restricted execution (rexec.py). |
| 13 | |
| 14 | This module defines three new concepts: |
| 15 | |
| 16 | (1) A "file system hooks" class provides an interface to a filesystem. |
| 17 | |
| 18 | One hooks class is defined (Hooks), which uses the interface provided |
| 19 | by standard modules os and os.path. It should be used as the base |
| 20 | class for other hooks classes. |
| 21 | |
| 22 | (2) A "module loader" class provides an interface to to search for a |
| 23 | module in a search path and to load it. It defines a method which |
| 24 | searches for a module in a single directory; by overriding this method |
| 25 | one can redefine the details of the search. If the directory is None, |
| 26 | built-in and frozen modules are searched instead. |
| 27 | |
| 28 | Two module loader class are defined, both implementing the search |
| 29 | strategy used by the built-in __import__ function: ModuleLoader uses |
| 30 | the imp module's find_module interface, while HookableModuleLoader |
| 31 | uses a file system hooks class to interact with the file system. Both |
| 32 | use the imp module's load_* interfaces to actually load the module. |
| 33 | |
| 34 | (3) A "module importer" class provides an interface to import a |
| 35 | module, as well as interfaces to reload and unload a module. It also |
| 36 | provides interfaces to install and uninstall itself instead of the |
| 37 | default __import__ and reload (and unload) functions. |
| 38 | |
| 39 | One module importer class is defined (ModuleImporter), which uses a |
| 40 | module loader instance passed in (by default HookableModuleLoader is |
| 41 | instantiated). |
| 42 | |
| 43 | The classes defined here should be used as base classes for extended |
| 44 | functionality along those lines. |
| 45 | |
| 46 | If a module mporter class supports dotted names, its import_module() |
| 47 | must return a different value depending on whether it is called on |
| 48 | behalf of a "from ... import ..." statement or not. (This is caused |
| 49 | by the way the __import__ hook is used by the Python interpreter.) It |
| 50 | would also do wise to install a different version of reload(). |
| 51 | |
| 52 | XXX Should the imp.load_* functions also be called via the hooks |
| 53 | instance? |
| 54 | |
| 55 | """ |
| 56 | |
| 57 | |
| 58 | import __builtin__ |
| 59 | import imp |
| 60 | import os |
| 61 | import sys |
| 62 | |
| 63 | |
| 64 | from imp import C_EXTENSION, PY_SOURCE, PY_COMPILED |
| 65 | BUILTIN_MODULE = 32 |
| 66 | FROZEN_MODULE = 33 |
| 67 | |
| 68 | |
| 69 | class _Verbose: |
| 70 | |
| 71 | def __init__(self, verbose = 0): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 72 | self.verbose = verbose |
Guido van Rossum | e7e578f | 1995-08-04 04:00:20 +0000 | [diff] [blame] | 73 | |
| 74 | def get_verbose(self): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 75 | return self.verbose |
Guido van Rossum | e7e578f | 1995-08-04 04:00:20 +0000 | [diff] [blame] | 76 | |
| 77 | def set_verbose(self, verbose): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 78 | self.verbose = verbose |
Guido van Rossum | e7e578f | 1995-08-04 04:00:20 +0000 | [diff] [blame] | 79 | |
| 80 | # XXX The following is an experimental interface |
| 81 | |
| 82 | def note(self, *args): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 83 | if self.verbose: |
| 84 | apply(self.message, args) |
Guido van Rossum | e7e578f | 1995-08-04 04:00:20 +0000 | [diff] [blame] | 85 | |
| 86 | def message(self, format, *args): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 87 | print format%args |
Guido van Rossum | e7e578f | 1995-08-04 04:00:20 +0000 | [diff] [blame] | 88 | |
| 89 | |
| 90 | class BasicModuleLoader(_Verbose): |
| 91 | |
| 92 | """Basic module loader. |
| 93 | |
| 94 | This provides the same functionality as built-in import. It |
| 95 | doesn't deal with checking sys.modules -- all it provides is |
| 96 | find_module() and a load_module(), as well as find_module_in_dir() |
| 97 | which searches just one directory, and can be overridden by a |
| 98 | derived class to change the module search algorithm when the basic |
| 99 | dependency on sys.path is unchanged. |
| 100 | |
| 101 | The interface is a little more convenient than imp's: |
| 102 | find_module(name, [path]) returns None or 'stuff', and |
| 103 | load_module(name, stuff) loads the module. |
| 104 | |
| 105 | """ |
| 106 | |
| 107 | def find_module(self, name, path = None): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 108 | if path is None: |
| 109 | path = [None] + self.default_path() |
| 110 | for dir in path: |
| 111 | stuff = self.find_module_in_dir(name, dir) |
| 112 | if stuff: return stuff |
| 113 | return None |
Guido van Rossum | e7e578f | 1995-08-04 04:00:20 +0000 | [diff] [blame] | 114 | |
| 115 | def default_path(self): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 116 | return sys.path |
Guido van Rossum | e7e578f | 1995-08-04 04:00:20 +0000 | [diff] [blame] | 117 | |
| 118 | def find_module_in_dir(self, name, dir): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 119 | if dir is None: |
| 120 | return self.find_builtin_module(name) |
| 121 | else: |
| 122 | try: |
| 123 | return imp.find_module(name, [dir]) |
| 124 | except ImportError: |
| 125 | return None |
Guido van Rossum | e7e578f | 1995-08-04 04:00:20 +0000 | [diff] [blame] | 126 | |
| 127 | def find_builtin_module(self, name): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 128 | if imp.is_builtin(name): |
| 129 | return None, '', ('', '', BUILTIN_MODULE) |
| 130 | if imp.is_frozen(name): |
| 131 | return None, '', ('', '', FROZEN_MODULE) |
| 132 | return None |
Guido van Rossum | e7e578f | 1995-08-04 04:00:20 +0000 | [diff] [blame] | 133 | |
| 134 | def load_module(self, name, stuff): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 135 | file, filename, (suff, mode, type) = stuff |
| 136 | try: |
| 137 | if type == BUILTIN_MODULE: |
| 138 | return imp.init_builtin(name) |
| 139 | if type == FROZEN_MODULE: |
| 140 | return imp.init_frozen(name) |
| 141 | if type == C_EXTENSION: |
| 142 | return imp.load_dynamic(name, filename, file) |
| 143 | if type == PY_SOURCE: |
| 144 | return imp.load_source(name, filename, file) |
| 145 | if type == PY_COMPILED: |
| 146 | return imp.load_compiled(name, filename, file) |
| 147 | finally: |
| 148 | if file: file.close() |
| 149 | raise ImportError, "Unrecognized module type (%s) for %s" % \ |
| 150 | (`type`, name) |
Guido van Rossum | e7e578f | 1995-08-04 04:00:20 +0000 | [diff] [blame] | 151 | |
| 152 | |
| 153 | class Hooks(_Verbose): |
| 154 | |
| 155 | """Hooks into the filesystem and interpreter. |
| 156 | |
| 157 | By deriving a subclass you can redefine your filesystem interface, |
| 158 | e.g. to merge it with the URL space. |
| 159 | |
| 160 | This base class behaves just like the native filesystem. |
| 161 | |
| 162 | """ |
| 163 | |
| 164 | # imp interface |
| 165 | def get_suffixes(self): return imp.get_suffixes() |
| 166 | def new_module(self, name): return imp.new_module(name) |
| 167 | def is_builtin(self, name): return imp.is_builtin(name) |
| 168 | def init_builtin(self, name): return imp.init_builtin(name) |
| 169 | def is_frozen(self, name): return imp.is_frozen(name) |
| 170 | def init_frozen(self, name): return imp.init_frozen(name) |
| 171 | def get_frozen_object(self, name): return imp.get_frozen_object(name) |
| 172 | def load_source(self, name, filename, file=None): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 173 | return imp.load_source(name, filename, file) |
Guido van Rossum | e7e578f | 1995-08-04 04:00:20 +0000 | [diff] [blame] | 174 | def load_compiled(self, name, filename, file=None): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 175 | return imp.load_compiled(name, filename, file) |
Guido van Rossum | e7e578f | 1995-08-04 04:00:20 +0000 | [diff] [blame] | 176 | def load_dynamic(self, name, filename, file=None): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 177 | return imp.load_dynamic(name, filename, file) |
Guido van Rossum | e7e578f | 1995-08-04 04:00:20 +0000 | [diff] [blame] | 178 | |
| 179 | def add_module(self, name): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 180 | d = self.modules_dict() |
| 181 | if d.has_key(name): return d[name] |
| 182 | d[name] = m = self.new_module(name) |
| 183 | return m |
Guido van Rossum | e7e578f | 1995-08-04 04:00:20 +0000 | [diff] [blame] | 184 | |
| 185 | # sys interface |
| 186 | def modules_dict(self): return sys.modules |
| 187 | def default_path(self): return sys.path |
| 188 | |
| 189 | def path_split(self, x): return os.path.split(x) |
| 190 | def path_join(self, x, y): return os.path.join(x, y) |
| 191 | def path_isabs(self, x): return os.path.isabs(x) |
| 192 | # etc. |
| 193 | |
| 194 | def path_exists(self, x): return os.path.exists(x) |
| 195 | def path_isdir(self, x): return os.path.isdir(x) |
| 196 | def path_isfile(self, x): return os.path.isfile(x) |
| 197 | def path_islink(self, x): return os.path.islink(x) |
| 198 | # etc. |
| 199 | |
| 200 | def openfile(self, *x): return apply(open, x) |
| 201 | openfile_error = IOError |
| 202 | def listdir(self, x): return os.listdir(x) |
| 203 | listdir_error = os.error |
| 204 | # etc. |
| 205 | |
| 206 | |
| 207 | class ModuleLoader(BasicModuleLoader): |
| 208 | |
| 209 | """Default module loader; uses file system hooks. |
| 210 | |
| 211 | By defining suitable hooks, you might be able to load modules from |
| 212 | other sources than the file system, e.g. from compressed or |
| 213 | encrypted files, tar files or (if you're brave!) URLs. |
| 214 | |
| 215 | """ |
| 216 | |
| 217 | def __init__(self, hooks = None, verbose = 0): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 218 | BasicModuleLoader.__init__(self, verbose) |
| 219 | self.hooks = hooks or Hooks(verbose) |
Guido van Rossum | e7e578f | 1995-08-04 04:00:20 +0000 | [diff] [blame] | 220 | |
| 221 | def default_path(self): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 222 | return self.hooks.default_path() |
Guido van Rossum | e7e578f | 1995-08-04 04:00:20 +0000 | [diff] [blame] | 223 | |
| 224 | def modules_dict(self): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 225 | return self.hooks.modules_dict() |
Guido van Rossum | e7e578f | 1995-08-04 04:00:20 +0000 | [diff] [blame] | 226 | |
| 227 | def get_hooks(self): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 228 | return self.hooks |
Guido van Rossum | e7e578f | 1995-08-04 04:00:20 +0000 | [diff] [blame] | 229 | |
| 230 | def set_hooks(self, hooks): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 231 | self.hooks = hooks |
Guido van Rossum | e7e578f | 1995-08-04 04:00:20 +0000 | [diff] [blame] | 232 | |
| 233 | def find_builtin_module(self, name): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 234 | if self.hooks.is_builtin(name): |
| 235 | return None, '', ('', '', BUILTIN_MODULE) |
| 236 | if self.hooks.is_frozen(name): |
| 237 | return None, '', ('', '', FROZEN_MODULE) |
| 238 | return None |
Guido van Rossum | e7e578f | 1995-08-04 04:00:20 +0000 | [diff] [blame] | 239 | |
| 240 | def find_module_in_dir(self, name, dir): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 241 | if dir is None: |
| 242 | return self.find_builtin_module(name) |
| 243 | for info in self.hooks.get_suffixes(): |
| 244 | suff, mode, type = info |
| 245 | fullname = self.hooks.path_join(dir, name+suff) |
| 246 | try: |
| 247 | fp = self.hooks.openfile(fullname, mode) |
| 248 | return fp, fullname, info |
| 249 | except self.hooks.openfile_error: |
| 250 | pass |
| 251 | return None |
Guido van Rossum | e7e578f | 1995-08-04 04:00:20 +0000 | [diff] [blame] | 252 | |
| 253 | def load_module(self, name, stuff): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 254 | file, filename, (suff, mode, type) = stuff |
| 255 | try: |
| 256 | if type == BUILTIN_MODULE: |
| 257 | return self.hooks.init_builtin(name) |
| 258 | if type == FROZEN_MODULE: |
| 259 | return self.hooks.init_frozen(name) |
| 260 | if type == C_EXTENSION: |
| 261 | m = self.hooks.load_dynamic(name, filename, file) |
| 262 | elif type == PY_SOURCE: |
| 263 | m = self.hooks.load_source(name, filename, file) |
| 264 | elif type == PY_COMPILED: |
| 265 | m = self.hooks.load_compiled(name, filename, file) |
| 266 | else: |
| 267 | raise ImportError, "Unrecognized module type (%s) for %s" % \ |
| 268 | (`type`, name) |
| 269 | finally: |
| 270 | if file: file.close() |
| 271 | m.__file__ = filename |
| 272 | return m |
Guido van Rossum | e7e578f | 1995-08-04 04:00:20 +0000 | [diff] [blame] | 273 | |
| 274 | |
| 275 | class FancyModuleLoader(ModuleLoader): |
| 276 | |
| 277 | """Fancy module loader -- parses and execs the code itself.""" |
| 278 | |
| 279 | def load_module(self, name, stuff): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 280 | file, filename, (suff, mode, type) = stuff |
| 281 | if type == FROZEN_MODULE: |
| 282 | code = self.hooks.get_frozen_object(name) |
| 283 | elif type == PY_COMPILED: |
| 284 | import marshal |
| 285 | file.seek(8) |
| 286 | code = marshal.load(file) |
| 287 | elif type == PY_SOURCE: |
| 288 | data = file.read() |
| 289 | code = compile(data, filename, 'exec') |
| 290 | else: |
| 291 | return ModuleLoader.load_module(self, name, stuff) |
| 292 | m = self.hooks.add_module(name) |
| 293 | m.__file__ = filename |
| 294 | exec code in m.__dict__ |
| 295 | return m |
Guido van Rossum | e7e578f | 1995-08-04 04:00:20 +0000 | [diff] [blame] | 296 | |
| 297 | |
| 298 | class ModuleImporter(_Verbose): |
| 299 | |
| 300 | """Default module importer; uses module loader. |
| 301 | |
| 302 | This provides the same functionality as built-in import, when |
| 303 | combined with ModuleLoader. |
| 304 | |
| 305 | """ |
| 306 | |
| 307 | def __init__(self, loader = None, verbose = 0): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 308 | _Verbose.__init__(self, verbose) |
| 309 | self.loader = loader or ModuleLoader(None, verbose) |
| 310 | self.modules = self.loader.modules_dict() |
Guido van Rossum | e7e578f | 1995-08-04 04:00:20 +0000 | [diff] [blame] | 311 | |
| 312 | def get_loader(self): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 313 | return self.loader |
Guido van Rossum | e7e578f | 1995-08-04 04:00:20 +0000 | [diff] [blame] | 314 | |
| 315 | def set_loader(self, loader): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 316 | self.loader = loader |
Guido van Rossum | e7e578f | 1995-08-04 04:00:20 +0000 | [diff] [blame] | 317 | |
| 318 | def get_hooks(self): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 319 | return self.loader.get_hooks() |
Guido van Rossum | e7e578f | 1995-08-04 04:00:20 +0000 | [diff] [blame] | 320 | |
| 321 | def set_hooks(self, hooks): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 322 | return self.loader.set_hooks(hooks) |
Guido van Rossum | e7e578f | 1995-08-04 04:00:20 +0000 | [diff] [blame] | 323 | |
| 324 | def import_module(self, name, globals={}, locals={}, fromlist=[]): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 325 | if self.modules.has_key(name): |
| 326 | return self.modules[name] # Fast path |
| 327 | stuff = self.loader.find_module(name) |
| 328 | if not stuff: |
| 329 | raise ImportError, "No module named %s" % name |
| 330 | return self.loader.load_module(name, stuff) |
Guido van Rossum | e7e578f | 1995-08-04 04:00:20 +0000 | [diff] [blame] | 331 | |
| 332 | def reload(self, module, path = None): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 333 | name = module.__name__ |
| 334 | stuff = self.loader.find_module(name, path) |
| 335 | if not stuff: |
| 336 | raise ImportError, "Module %s not found for reload" % name |
| 337 | return self.loader.load_module(name, stuff) |
Guido van Rossum | e7e578f | 1995-08-04 04:00:20 +0000 | [diff] [blame] | 338 | |
| 339 | def unload(self, module): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 340 | del self.modules[module.__name__] |
| 341 | # XXX Should this try to clear the module's namespace? |
Guido van Rossum | e7e578f | 1995-08-04 04:00:20 +0000 | [diff] [blame] | 342 | |
| 343 | def install(self): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 344 | self.save_import_module = __builtin__.__import__ |
| 345 | self.save_reload = __builtin__.reload |
| 346 | if not hasattr(__builtin__, 'unload'): |
| 347 | __builtin__.unload = None |
| 348 | self.save_unload = __builtin__.unload |
| 349 | __builtin__.__import__ = self.import_module |
| 350 | __builtin__.reload = self.reload |
| 351 | __builtin__.unload = self.unload |
Guido van Rossum | e7e578f | 1995-08-04 04:00:20 +0000 | [diff] [blame] | 352 | |
| 353 | def uninstall(self): |
Guido van Rossum | 45e2fbc | 1998-03-26 21:13:24 +0000 | [diff] [blame] | 354 | __builtin__.__import__ = self.save_import_module |
| 355 | __builtin__.reload = self.save_reload |
| 356 | __builtin__.unload = self.save_unload |
| 357 | if not __builtin__.unload: |
| 358 | del __builtin__.unload |
Guido van Rossum | e7e578f | 1995-08-04 04:00:20 +0000 | [diff] [blame] | 359 | |
| 360 | |
Guido van Rossum | e7e578f | 1995-08-04 04:00:20 +0000 | [diff] [blame] | 361 | default_importer = None |
| 362 | current_importer = None |
| 363 | |
| 364 | def install(importer = None): |
| 365 | global current_importer |
| 366 | current_importer = importer or default_importer or ModuleImporter() |
| 367 | current_importer.install() |
| 368 | |
| 369 | def uninstall(): |
| 370 | global current_importer |
| 371 | current_importer.uninstall() |