blob: ab3faf49481cc1be98207ba7bcee58ce5fdbbc8c [file] [log] [blame]
Greg Stein281b8d81999-11-07 12:54:45 +00001#
2# imputil.py
3#
4# Written by Greg Stein. Public Domain.
5# No Copyright, no Rights Reserved, and no Warranties.
6#
7# Utilities to help out with custom import mechanisms.
8#
9# Additional modifications were contribed by Marc-Andre Lemburg and
10# Gordon McMillan.
11#
12
13__version__ = '0.3'
14
15# note: avoid importing non-builtin modules
16import imp
17import sys
18import strop
19import __builtin__ ### why this instead of just using __builtins__ ??
20
21# for the DirectoryImporter
22import struct
23import marshal
24
25class Importer:
26 "Base class for replacing standard import functions."
27
28 def install(self):
29 self.__chain_import = __builtin__.__import__
30 self.__chain_reload = __builtin__.reload
31 __builtin__.__import__ = self._import_hook
32 __builtin__.reload = self._reload_hook
33
34 ######################################################################
35 #
36 # PRIVATE METHODS
37 #
38 def _import_hook(self, name, globals=None, locals=None, fromlist=None):
39 """Python calls this hook to locate and import a module.
40
41 This method attempts to load the (dotted) module name. If it cannot
42 find it, then it delegates the import to the next import hook in the
43 chain (where "next" is defined as the import hook that was in place
44 at the time this Importer instance was installed).
45 """
46
47 ### insert a fast-path check for whether the module is already
48 ### loaded? use a variant of _determine_import_context() which
49 ### returns a context regardless of Importer used. generate an
50 ### fqname and look in sys.modules for it.
51
52 # determine the context of this import
53 parent = self._determine_import_context(globals)
54
55 # import the module within the context, or from the default context
56 top, tail = self._import_top_module(parent, name)
57 if top is None:
58 # the module was not found; delegate to the next import hook
59 return self.__chain_import(name, globals, locals, fromlist)
60
61 # the top module may be under the control of a different importer.
62 # if so, then defer to that importer for completion of the import.
63 # note it may be self, or is undefined so we (self) may as well
64 # finish the import.
65 importer = top.__dict__.get('__importer__', self)
66 return importer._finish_import(top, tail, fromlist)
67
68 def _finish_import(self, top, tail, fromlist):
69 # if "a.b.c" was provided, then load the ".b.c" portion down from
70 # below the top-level module.
71 bottom = self._load_tail(top, tail)
72
73 # if the form is "import a.b.c", then return "a"
74 if not fromlist:
75 # no fromlist: return the top of the import tree
76 return top
77
78 # the top module was imported by self, or it was not imported through
79 # the Importer mechanism and self is simply handling the import of
80 # the sub-modules and fromlist.
81 #
82 # this means that the bottom module was also imported by self, or we
83 # are handling things in the absence of a prior Importer
84 #
85 # ### why the heck are we handling it? what is the example scenario
86 # ### where this happens? note that we can't determine is_package()
87 # ### for non-Importer modules.
88 #
89 # since we imported/handled the bottom module, this means that we can
90 # also handle its fromlist (and reliably determine is_package()).
91
92 # if the bottom node is a package, then (potentially) import some modules.
93 #
94 # note: if it is not a package, then "fromlist" refers to names in
95 # the bottom module rather than modules.
96 # note: for a mix of names and modules in the fromlist, we will
97 # import all modules and insert those into the namespace of
98 # the package module. Python will pick up all fromlist names
99 # from the bottom (package) module; some will be modules that
100 # we imported and stored in the namespace, others are expected
101 # to be present already.
102 if self._is_package(bottom.__dict__):
103 self._import_fromlist(bottom, fromlist)
104
105 # if the form is "from a.b import c, d" then return "b"
106 return bottom
107
108 def _reload_hook(self, module):
109 "Python calls this hook to reload a module."
110
111 # reloading of a module may or may not be possible (depending on the
112 # importer), but at least we can validate that it's ours to reload
113 importer = module.__dict__.get('__importer__', None)
114 if importer is not self:
115 return self.__chain_reload(module)
116
117 # okay. it is ours, but we don't know what to do (yet)
118 ### we should blast the module dict and do another get_code(). need to
119 ### flesh this out and add proper docco...
120 raise SystemError, "reload not yet implemented"
121
122 def _determine_import_context(self, globals):
123 """Returns the context in which a module should be imported.
124
125 The context could be a loaded (package) module and the imported module
126 will be looked for within that package. The context could also be None,
127 meaning there is no context -- the module should be looked for as a
128 "top-level" module.
129 """
130
131 if not globals or \
132 globals.get('__importer__', None) is not self:
133 # globals does not refer to one of our modules or packages.
134 # That implies there is no relative import context, and it
135 # should just pick it off the standard path.
136 return None
137
138 # The globals refer to a module or package of ours. It will define
139 # the context of the new import. Get the module/package fqname.
140 parent_fqname = globals['__name__']
141
142 # for a package, return itself (imports refer to pkg contents)
143 if self._is_package(globals):
144 parent = sys.modules[parent_fqname]
145 assert globals is parent.__dict__
146 return parent
147
148 i = strop.rfind(parent_fqname, '.')
149
150 # a module outside of a package has no particular import context
151 if i == -1:
152 return None
153
154 # for a module in a package, return the package (imports refer to siblings)
155 parent_fqname = parent_fqname[:i]
156 parent = sys.modules[parent_fqname]
157 assert parent.__name__ == parent_fqname
158 return parent
159
160 def _import_top_module(self, parent, name):
161 """Locate the top of the import tree (relative or absolute).
162
163 parent defines the context in which the import should occur. See
164 _determine_import_context() for details.
165
166 Returns a tuple (module, tail). module is the loaded (top-level) module,
167 or None if the module is not found. tail is the remaining portion of
168 the dotted name.
169 """
170 i = strop.find(name, '.')
171 if i == -1:
172 head = name
173 tail = ""
174 else:
175 head = name[:i]
176 tail = name[i+1:]
177 if parent:
178 fqname = "%s.%s" % (parent.__name__, head)
179 else:
180 fqname = head
181 module = self._import_one(parent, head, fqname)
182 if module:
183 # the module was relative, or no context existed (the module was
184 # simply found on the path).
185 return module, tail
186 if parent:
187 # we tried relative, now try an absolute import (from the path)
188 module = self._import_one(None, head, head)
189 if module:
190 return module, tail
191
192 # the module wasn't found
193 return None, None
194
195 def _import_one(self, parent, modname, fqname):
196 "Import a single module."
197
198 # has the module already been imported?
199 try:
200 return sys.modules[fqname]
201 except KeyError:
202 pass
203
204 # load the module's code, or fetch the module itself
205 result = self.get_code(parent, modname, fqname)
206 if result is None:
207 return None
208
209 # did get_code() return an actual module? (rather than a code object)
210 is_module = type(result[1]) is type(sys)
211
212 # use the returned module, or create a new one to exec code into
213 if is_module:
214 module = result[1]
215 else:
216 module = imp.new_module(fqname)
217
218 ### record packages a bit differently??
219 module.__importer__ = self
220 module.__ispkg__ = result[0]
221
222 # if present, the third item is a set of values to insert into the module
223 if len(result) > 2:
224 module.__dict__.update(result[2])
225
226 # the module is almost ready... make it visible
227 sys.modules[fqname] = module
228
229 # execute the code within the module's namespace
230 if not is_module:
231 exec result[1] in module.__dict__
232
233 # insert the module into its parent
234 if parent:
235 setattr(parent, modname, module)
236 return module
237
238 def _load_tail(self, m, tail):
239 """Import the rest of the modules, down from the top-level module.
240
241 Returns the last module in the dotted list of modules.
242 """
243 if tail:
244 for part in strop.splitfields(tail, '.'):
245 fqname = "%s.%s" % (m.__name__, part)
246 m = self._import_one(m, part, fqname)
247 if not m:
248 raise ImportError, "No module named " + fqname
249 return m
250
251 def _import_fromlist(self, package, fromlist):
252 'Import any sub-modules in the "from" list.'
253
254 # if '*' is present in the fromlist, then look for the '__all__' variable
255 # to find additional items (modules) to import.
256 if '*' in fromlist:
257 fromlist = list(fromlist) + list(package.__dict__.get('__all__', []))
258
259 for sub in fromlist:
260 # if the name is already present, then don't try to import it (it
261 # might not be a module!).
262 if sub != '*' and not hasattr(package, sub):
263 subname = "%s.%s" % (package.__name__, sub)
264 submod = self._import_one(package, sub, subname)
265 if not submod:
266 raise ImportError, "cannot import name " + subname
267
268 def _is_package(self, module_dict):
269 """Determine if a given module (dictionary) specifies a package.
270
271 The package status is in the module-level name __ispkg__. The module
272 must also have been imported by self, so that we can reliably apply
273 semantic meaning to __ispkg__.
274
275 ### weaken the test to issubclass(Importer)?
276 """
277 return module_dict.get('__importer__', None) is self and \
278 module_dict['__ispkg__']
279
280 ######################################################################
281 #
282 # METHODS TO OVERRIDE
283 #
284 def get_code(self, parent, modname, fqname):
285 """Find and retrieve the code for the given module.
286
287 parent specifies a parent module to define a context for importing. It
288 may be None, indicating no particular context for the search.
289
290 modname specifies a single module (not dotted) within the parent.
291
292 fqname specifies the fully-qualified module name. This is a (potentially)
293 dotted name from the "root" of the module namespace down to the modname.
294 If there is no parent, then modname==fqname.
295
296 This method should return None, a 2-tuple, or a 3-tuple.
297
298 * If the module was not found, then None should be returned.
299
300 * The first item of the 2- or 3-tuple should be the integer 0 or 1,
301 specifying whether the module that was found is a package or not.
302
303 * The second item is the code object for the module (it will be
304 executed within the new module's namespace). This item can also
305 be a fully-loaded module object (e.g. loaded from a shared lib).
306
307 * If present, the third item is a dictionary of name/value pairs that
308 will be inserted into new module before the code object is executed.
309 This provided in case the module's code expects certain values (such
310 as where the module was found). When the second item is a module
311 object, then these names/values will be inserted *after* the module
312 has been loaded/initialized.
313 """
314 raise RuntimeError, "get_code not implemented"
315
316
317######################################################################
318#
319# Simple function-based importer
320#
321class FuncImporter(Importer):
322 "Importer subclass to use a supplied function rather than method overrides."
323 def __init__(self, func):
324 self.func = func
325 def get_code(self, parent, modname, fqname):
326 return self.func(parent, modname, fqname)
327
328def install_with(func):
329 FuncImporter(func).install()
330
331
332######################################################################
333#
334# Base class for archive-based importing
335#
336class PackageArchiveImporter(Importer):
337 "Importer subclass to import from (file) archives."
338
339 def get_code(self, parent, modname, fqname):
340 if parent:
341 # if a parent "package" is provided, then we are importing a sub-file
342 # from the archive.
343 result = self.get_subfile(parent.__archive__, modname)
344 if result is None:
345 return None
346 if type(result) == type(()):
347 return (0,) + result
348 return 0, result
349
350 # no parent was provided, so the archive should exist somewhere on the
351 # default "path".
352 archive = self.get_archive(modname)
353 if archive is None:
354 return None
355 return 1, "", {'__archive__':archive}
356
357 def get_archive(self, modname):
358 """Get an archive of modules.
359
360 This method should locate an archive and return a value which can be
361 used by get_subfile to load modules from it. The value may be a simple
362 pathname, an open file, or a complex object that caches information
363 for future imports.
364
365 Return None if the archive was not found.
366 """
367 raise RuntimeError, "get_archive not implemented"
368
369 def get_subfile(self, archive, modname):
370 """Get code from a subfile in the specified archive.
371
372 Given the specified archive (as returned by get_archive()), locate
373 and return a code object for the specified module name.
374
375 A 2-tuple may be returned, consisting of a code object and a dict
376 of name/values to place into the target module.
377
378 Return None if the subfile was not found.
379 """
380 raise RuntimeError, "get_subfile not implemented"
381
382
383class PackageArchive(PackageArchiveImporter):
384 "PackageArchiveImporter subclass that refers to a specific archive."
385
386 def __init__(self, modname, archive_pathname):
387 self.__modname = modname
388 self.__path = archive_pathname
389
390 def get_archive(self, modname):
391 if modname == self.__modname:
392 return self.__path
393 return None
394
395 # get_subfile is passed the full pathname of the archive
396
397
398######################################################################
399#
400# Emulate the standard directory-based import mechanism
401#
402
403class DirectoryImporter(Importer):
404 "Importer subclass to emulate the standard importer."
405
406 def __init__(self, dir):
407 self.dir = dir
408 self.ext_char = __debug__ and 'c' or 'o'
409 self.ext = '.py' + self.ext_char
410
411 def get_code(self, parent, modname, fqname):
412 if parent:
413 dir = parent.__pkgdir__
414 else:
415 dir = self.dir
416
417 # pull the os module from our instance data. we don't do this at the
418 # top-level, because it isn't a builtin module (and we want to defer
419 # loading non-builtins until as late as possible).
420 try:
421 os = self.os
422 except AttributeError:
423 import os
424 self.os = os
425
426 pathname = os.path.join(dir, modname)
427 if os.path.isdir(pathname):
428 values = { '__pkgdir__' : pathname }
429 ispkg = 1
430 pathname = os.path.join(pathname, '__init__')
431 else:
432 values = { }
433 ispkg = 0
434
435 t_py = self._timestamp(pathname + '.py')
436 t_pyc = self._timestamp(pathname + self.ext)
437 if t_py is None and t_pyc is None:
438 return None
439 code = None
440 if t_py is None or (t_pyc is not None and t_pyc >= t_py):
441 f = open(pathname + self.ext, 'rb')
442 if f.read(4) == imp.get_magic():
443 t = struct.unpack('<I', f.read(4))[0]
444 if t == t_py:
445 code = marshal.load(f)
446 f.close()
447 if code is None:
448 code = self._compile(pathname + '.py', t_py)
449 return ispkg, code, values
450
451 def _timestamp(self, pathname):
452 try:
453 s = self.os.stat(pathname)
454 except OSError:
455 return None
456 return long(s[8])
457
458 def _compile(self, pathname, timestamp):
459 codestring = open(pathname, 'r').read()
460 if codestring and codestring[-1] != '\n':
461 codestring = codestring + '\n'
462 code = __builtin__.compile(codestring, pathname, 'exec')
463
464 # try to cache the compiled code
465 try:
466 f = open(pathname + self.ext_char, 'wb')
467 f.write('\0\0\0\0')
468 f.write(struct.pack('<I', timestamp))
469 marshal.dump(code, f)
470 f.flush()
471 f.seek(0, 0)
472 f.write(imp.get_magic())
473 f.close()
474 except OSError:
475 pass
476
477 return code
478
479 def __repr__(self):
480 return '<%s.%s for "%s" at 0x%x>' % (self.__class__.__module__,
481 self.__class__.__name__,
482 self.dir,
483 id(self))
484
485def _test_dir():
486 "Debug/test function to create DirectoryImporters from sys.path."
487 path = sys.path[:]
488 path.reverse()
489 for d in path:
490 DirectoryImporter(d).install()
491
492######################################################################