blob: b379fc56f757f3de561d4ac64202ef8e6890d5a8 [file] [log] [blame]
Guido van Rossume7e578f1995-08-04 04:00:20 +00001"""Import hook support.
2
3Consistent use of this module will make it possible to change the
4different mechanisms involved in loading modules independently.
5
6While the built-in module imp exports interfaces to the built-in
7module searching and loading algorithm, and it is possible to replace
8the built-in function __import__ in order to change the semantics of
9the import statement, until now it has been difficult to combine the
10effect of different __import__ hacks, like loading modules from URLs
11(rimport.py), implementing a hierarchical module namespace (newimp.py)
12or restricted execution (rexec.py).
13
14This module defines three new concepts:
15
16(1) A "file system hooks" class provides an interface to a filesystem.
17
18One hooks class is defined (Hooks), which uses the interface provided
19by standard modules os and os.path. It should be used as the base
20class for other hooks classes.
21
22(2) A "module loader" class provides an interface to to search for a
23module in a search path and to load it. It defines a method which
24searches for a module in a single directory; by overriding this method
25one can redefine the details of the search. If the directory is None,
26built-in and frozen modules are searched instead.
27
28Two module loader class are defined, both implementing the search
29strategy used by the built-in __import__ function: ModuleLoader uses
30the imp module's find_module interface, while HookableModuleLoader
31uses a file system hooks class to interact with the file system. Both
32use the imp module's load_* interfaces to actually load the module.
33
34(3) A "module importer" class provides an interface to import a
35module, as well as interfaces to reload and unload a module. It also
36provides interfaces to install and uninstall itself instead of the
37default __import__ and reload (and unload) functions.
38
39One module importer class is defined (ModuleImporter), which uses a
40module loader instance passed in (by default HookableModuleLoader is
41instantiated).
42
43The classes defined here should be used as base classes for extended
44functionality along those lines.
45
46If a module mporter class supports dotted names, its import_module()
47must return a different value depending on whether it is called on
48behalf of a "from ... import ..." statement or not. (This is caused
49by the way the __import__ hook is used by the Python interpreter.) It
50would also do wise to install a different version of reload().
51
52XXX Should the imp.load_* functions also be called via the hooks
53instance?
54
55"""
56
57
58import __builtin__
59import imp
60import os
61import sys
62
63
64from imp import C_EXTENSION, PY_SOURCE, PY_COMPILED
65BUILTIN_MODULE = 32
66FROZEN_MODULE = 33
67
68
69class _Verbose:
70
71 def __init__(self, verbose = 0):
72 self.verbose = verbose
73
74 def get_verbose(self):
75 return self.verbose
76
77 def set_verbose(self, verbose):
78 self.verbose = verbose
79
80 # XXX The following is an experimental interface
81
82 def note(self, *args):
83 if self.verbose:
84 apply(self.message, args)
85
86 def message(self, format, *args):
87 print format%args
88
89
90class 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):
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
114
115 def default_path(self):
116 return sys.path
117
118 def find_module_in_dir(self, name, dir):
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
126
127 def find_builtin_module(self, name):
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
133
134 def load_module(self, name, stuff):
135 file, filename, (suff, mode, type) = stuff
136 if type == BUILTIN_MODULE:
137 return imp.init_builtin(name)
138 if type == FROZEN_MODULE:
139 return imp.init_frozen(name)
140 if type == C_EXTENSION:
141 return imp.load_dynamic(name, filename, file)
142 if type == PY_SOURCE:
143 return imp.load_source(name, filename, file)
144 if type == PY_COMPILED:
145 return imp.load_compiled(name, filename, file)
146 raise ImportError, "Unrecognized module type (%s) for %s" % \
147 (`type`, name)
148
149
150class Hooks(_Verbose):
151
152 """Hooks into the filesystem and interpreter.
153
154 By deriving a subclass you can redefine your filesystem interface,
155 e.g. to merge it with the URL space.
156
157 This base class behaves just like the native filesystem.
158
159 """
160
161 # imp interface
162 def get_suffixes(self): return imp.get_suffixes()
163 def new_module(self, name): return imp.new_module(name)
164 def is_builtin(self, name): return imp.is_builtin(name)
165 def init_builtin(self, name): return imp.init_builtin(name)
166 def is_frozen(self, name): return imp.is_frozen(name)
167 def init_frozen(self, name): return imp.init_frozen(name)
168 def get_frozen_object(self, name): return imp.get_frozen_object(name)
169 def load_source(self, name, filename, file=None):
170 return imp.load_source(name, filename, file)
171 def load_compiled(self, name, filename, file=None):
172 return imp.load_compiled(name, filename, file)
173 def load_dynamic(self, name, filename, file=None):
174 return imp.load_dynamic(name, filename, file)
175
176 def add_module(self, name):
177 d = self.modules_dict()
178 if d.has_key(name): 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 apply(open, x)
198 openfile_error = IOError
199 def listdir(self, x): return os.listdir(x)
200 listdir_error = os.error
201 # etc.
202
203
204class 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 = 0):
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 if self.hooks.is_builtin(name):
232 return None, '', ('', '', BUILTIN_MODULE)
233 if self.hooks.is_frozen(name):
234 return None, '', ('', '', FROZEN_MODULE)
235 return None
236
237 def find_module_in_dir(self, name, dir):
238 if dir is None:
239 return self.find_builtin_module(name)
240 for info in self.hooks.get_suffixes():
241 suff, mode, type = info
242 fullname = self.hooks.path_join(dir, name+suff)
243 try:
244 fp = self.hooks.openfile(fullname, mode)
245 return fp, fullname, info
246 except self.hooks.openfile_error:
247 pass
248 return None
249
250 def load_module(self, name, stuff):
251 file, filename, (suff, mode, type) = stuff
252 if type == BUILTIN_MODULE:
253 return self.hooks.init_builtin(name)
254 if type == FROZEN_MODULE:
255 return self.hooks.init_frozen(name)
256 if type == C_EXTENSION:
257 return self.hooks.load_dynamic(name, filename, file)
258 if type == PY_SOURCE:
259 return self.hooks.load_source(name, filename, file)
260 if type == PY_COMPILED:
261 return self.hooks.load_compiled(name, filename, file)
262 raise ImportError, "Unrecognized module type (%s) for %s" % \
263 (`type`, name)
264
265
266class FancyModuleLoader(ModuleLoader):
267
268 """Fancy module loader -- parses and execs the code itself."""
269
270 def load_module(self, name, stuff):
271 file, filename, (suff, mode, type) = stuff
272 if type == FROZEN_MODULE:
273 code = self.hooks.get_frozen_object(name)
274 elif type == PY_COMPILED:
275 file.seek(8)
276 code = marshal.load(file)
277 elif type == PY_SOURCE:
278 data = file.read()
279 code = compile(data, filename, 'exec')
280 else:
281 return ModuleLoader.load_module(self, name, stuff)
282 m = self.hooks.add_module(name)
283 exec code in m.__dict__
284 return m
285
286
287class ModuleImporter(_Verbose):
288
289 """Default module importer; uses module loader.
290
291 This provides the same functionality as built-in import, when
292 combined with ModuleLoader.
293
294 """
295
296 def __init__(self, loader = None, verbose = 0):
297 _Verbose.__init__(self, verbose)
298 self.loader = loader or ModuleLoader(None, verbose)
299 self.modules = self.loader.modules_dict()
300
301 def get_loader(self):
302 return self.loader
303
304 def set_loader(self, loader):
305 self.loader = loader
306
307 def get_hooks(self):
308 return self.loader.get_hooks()
309
310 def set_hooks(self, hooks):
311 return self.loader.set_hooks(hooks)
312
313 def import_module(self, name, globals={}, locals={}, fromlist=[]):
314 if self.modules.has_key(name):
315 return self.modules[name] # Fast path
316 stuff = self.loader.find_module(name)
317 if not stuff:
318 raise ImportError, "No module named %s" % name
319 return self.loader.load_module(name, stuff)
320
321 def reload(self, module, path = None):
322 stuff = self.loader.find_module(name, path)
323 if not stuff:
324 raise ImportError, "Module %s not found for reload" % name
325 return self.loader.load_module(name, stuff)
326
327 def unload(self, module):
328 del self.modules[module.__name__]
329 # XXX Should this try to clear the module's namespace?
330
331 def install(self):
332 self.save_import_module = __builtin__.__import__
333 self.save_reload = __builtin__.reload
334 if not hasattr(__builtin__, 'unload'):
335 __builtin__.unload = None
336 self.save_unload = __builtin__.unload
337 __builtin__.__import__ = self.import_module
338 __builtin__.reload = self.reload
339 __builtin__.unload = self.unload
340
341 def uninstall(self):
342 __builtin__.__import__ = self.save_import_module
343 __builtin__.reload = self.save_reload
344 __builtin__.unload = self.save_unload
345 if not __builtin__.unload:
346 del __builtin__.unload
347
348
349# XXX Some experimental hacks -- importing ihooks auto-installs!
350# XXX (That's supposed to be transparent anyway...)
351
352default_importer = None
353current_importer = None
354
355def install(importer = None):
356 global current_importer
357 current_importer = importer or default_importer or ModuleImporter()
358 current_importer.install()
359
360def uninstall():
361 global current_importer
362 current_importer.uninstall()
363
364
365install()