blob: 886deab0135b275445a9fc8c46c4dfa06a5b48a6 [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#
Greg Steind4c64ba1999-11-07 13:14:58 +000012# This module is maintained by Greg and is available at:
13# http://www.lyra.org/greg/python/imputil.py
14#
15# Since this isn't in the Python distribution yet, we'll use the CVS ID
16# for tracking:
17# $Id$
18#
Greg Stein281b8d81999-11-07 12:54:45 +000019
Greg Stein281b8d81999-11-07 12:54:45 +000020# note: avoid importing non-builtin modules
21import imp
22import sys
23import strop
Greg Stein7ec28d21999-11-20 12:31:07 +000024import __builtin__
Greg Stein281b8d81999-11-07 12:54:45 +000025
26# for the DirectoryImporter
27import struct
28import marshal
29
30class Importer:
31 "Base class for replacing standard import functions."
32
33 def install(self):
34 self.__chain_import = __builtin__.__import__
35 self.__chain_reload = __builtin__.reload
36 __builtin__.__import__ = self._import_hook
37 __builtin__.reload = self._reload_hook
38
39 ######################################################################
40 #
41 # PRIVATE METHODS
42 #
43 def _import_hook(self, name, globals=None, locals=None, fromlist=None):
44 """Python calls this hook to locate and import a module.
45
46 This method attempts to load the (dotted) module name. If it cannot
47 find it, then it delegates the import to the next import hook in the
48 chain (where "next" is defined as the import hook that was in place
49 at the time this Importer instance was installed).
50 """
51
52 ### insert a fast-path check for whether the module is already
53 ### loaded? use a variant of _determine_import_context() which
54 ### returns a context regardless of Importer used. generate an
55 ### fqname and look in sys.modules for it.
56
Greg Stein63faa011999-11-20 11:22:37 +000057 ### note that given module a.b which imports c, if c is already
58 ### loaded, python still wants to look for a.c
59
Greg Stein281b8d81999-11-07 12:54:45 +000060 # determine the context of this import
61 parent = self._determine_import_context(globals)
62
63 # import the module within the context, or from the default context
64 top, tail = self._import_top_module(parent, name)
65 if top is None:
66 # the module was not found; delegate to the next import hook
67 return self.__chain_import(name, globals, locals, fromlist)
68
69 # the top module may be under the control of a different importer.
70 # if so, then defer to that importer for completion of the import.
71 # note it may be self, or is undefined so we (self) may as well
72 # finish the import.
73 importer = top.__dict__.get('__importer__', self)
74 return importer._finish_import(top, tail, fromlist)
75
76 def _finish_import(self, top, tail, fromlist):
77 # if "a.b.c" was provided, then load the ".b.c" portion down from
78 # below the top-level module.
79 bottom = self._load_tail(top, tail)
80
81 # if the form is "import a.b.c", then return "a"
82 if not fromlist:
83 # no fromlist: return the top of the import tree
84 return top
85
86 # the top module was imported by self, or it was not imported through
87 # the Importer mechanism and self is simply handling the import of
88 # the sub-modules and fromlist.
89 #
90 # this means that the bottom module was also imported by self, or we
91 # are handling things in the absence of a prior Importer
92 #
93 # ### why the heck are we handling it? what is the example scenario
94 # ### where this happens? note that we can't determine is_package()
95 # ### for non-Importer modules.
96 #
97 # since we imported/handled the bottom module, this means that we can
98 # also handle its fromlist (and reliably determine is_package()).
99
100 # if the bottom node is a package, then (potentially) import some modules.
101 #
102 # note: if it is not a package, then "fromlist" refers to names in
103 # the bottom module rather than modules.
104 # note: for a mix of names and modules in the fromlist, we will
105 # import all modules and insert those into the namespace of
106 # the package module. Python will pick up all fromlist names
107 # from the bottom (package) module; some will be modules that
108 # we imported and stored in the namespace, others are expected
109 # to be present already.
110 if self._is_package(bottom.__dict__):
111 self._import_fromlist(bottom, fromlist)
112
113 # if the form is "from a.b import c, d" then return "b"
114 return bottom
115
116 def _reload_hook(self, module):
117 "Python calls this hook to reload a module."
118
119 # reloading of a module may or may not be possible (depending on the
120 # importer), but at least we can validate that it's ours to reload
121 importer = module.__dict__.get('__importer__', None)
122 if importer is not self:
123 return self.__chain_reload(module)
124
125 # okay. it is ours, but we don't know what to do (yet)
126 ### we should blast the module dict and do another get_code(). need to
127 ### flesh this out and add proper docco...
128 raise SystemError, "reload not yet implemented"
129
130 def _determine_import_context(self, globals):
131 """Returns the context in which a module should be imported.
132
133 The context could be a loaded (package) module and the imported module
134 will be looked for within that package. The context could also be None,
135 meaning there is no context -- the module should be looked for as a
136 "top-level" module.
137 """
138
139 if not globals or \
140 globals.get('__importer__', None) is not self:
141 # globals does not refer to one of our modules or packages.
142 # That implies there is no relative import context, and it
143 # should just pick it off the standard path.
144 return None
145
146 # The globals refer to a module or package of ours. It will define
147 # the context of the new import. Get the module/package fqname.
148 parent_fqname = globals['__name__']
149
150 # for a package, return itself (imports refer to pkg contents)
151 if self._is_package(globals):
152 parent = sys.modules[parent_fqname]
153 assert globals is parent.__dict__
154 return parent
155
156 i = strop.rfind(parent_fqname, '.')
157
158 # a module outside of a package has no particular import context
159 if i == -1:
160 return None
161
162 # for a module in a package, return the package (imports refer to siblings)
163 parent_fqname = parent_fqname[:i]
164 parent = sys.modules[parent_fqname]
165 assert parent.__name__ == parent_fqname
166 return parent
167
168 def _import_top_module(self, parent, name):
169 """Locate the top of the import tree (relative or absolute).
170
171 parent defines the context in which the import should occur. See
172 _determine_import_context() for details.
173
174 Returns a tuple (module, tail). module is the loaded (top-level) module,
175 or None if the module is not found. tail is the remaining portion of
176 the dotted name.
177 """
178 i = strop.find(name, '.')
179 if i == -1:
180 head = name
181 tail = ""
182 else:
183 head = name[:i]
184 tail = name[i+1:]
185 if parent:
186 fqname = "%s.%s" % (parent.__name__, head)
187 else:
188 fqname = head
189 module = self._import_one(parent, head, fqname)
190 if module:
191 # the module was relative, or no context existed (the module was
192 # simply found on the path).
193 return module, tail
194 if parent:
195 # we tried relative, now try an absolute import (from the path)
196 module = self._import_one(None, head, head)
197 if module:
198 return module, tail
199
200 # the module wasn't found
201 return None, None
202
203 def _import_one(self, parent, modname, fqname):
204 "Import a single module."
205
206 # has the module already been imported?
207 try:
208 return sys.modules[fqname]
209 except KeyError:
210 pass
211
212 # load the module's code, or fetch the module itself
213 result = self.get_code(parent, modname, fqname)
214 if result is None:
215 return None
216
217 # did get_code() return an actual module? (rather than a code object)
218 is_module = type(result[1]) is type(sys)
219
220 # use the returned module, or create a new one to exec code into
221 if is_module:
222 module = result[1]
223 else:
224 module = imp.new_module(fqname)
225
226 ### record packages a bit differently??
227 module.__importer__ = self
228 module.__ispkg__ = result[0]
229
230 # if present, the third item is a set of values to insert into the module
231 if len(result) > 2:
232 module.__dict__.update(result[2])
233
234 # the module is almost ready... make it visible
235 sys.modules[fqname] = module
236
237 # execute the code within the module's namespace
238 if not is_module:
239 exec result[1] in module.__dict__
240
241 # insert the module into its parent
242 if parent:
243 setattr(parent, modname, module)
244 return module
245
246 def _load_tail(self, m, tail):
247 """Import the rest of the modules, down from the top-level module.
248
249 Returns the last module in the dotted list of modules.
250 """
251 if tail:
252 for part in strop.splitfields(tail, '.'):
253 fqname = "%s.%s" % (m.__name__, part)
254 m = self._import_one(m, part, fqname)
255 if not m:
256 raise ImportError, "No module named " + fqname
257 return m
258
259 def _import_fromlist(self, package, fromlist):
260 'Import any sub-modules in the "from" list.'
261
262 # if '*' is present in the fromlist, then look for the '__all__' variable
263 # to find additional items (modules) to import.
264 if '*' in fromlist:
265 fromlist = list(fromlist) + list(package.__dict__.get('__all__', []))
266
267 for sub in fromlist:
268 # if the name is already present, then don't try to import it (it
269 # might not be a module!).
270 if sub != '*' and not hasattr(package, sub):
271 subname = "%s.%s" % (package.__name__, sub)
272 submod = self._import_one(package, sub, subname)
273 if not submod:
274 raise ImportError, "cannot import name " + subname
275
276 def _is_package(self, module_dict):
277 """Determine if a given module (dictionary) specifies a package.
278
279 The package status is in the module-level name __ispkg__. The module
280 must also have been imported by self, so that we can reliably apply
281 semantic meaning to __ispkg__.
282
283 ### weaken the test to issubclass(Importer)?
284 """
285 return module_dict.get('__importer__', None) is self and \
286 module_dict['__ispkg__']
287
288 ######################################################################
289 #
290 # METHODS TO OVERRIDE
291 #
292 def get_code(self, parent, modname, fqname):
293 """Find and retrieve the code for the given module.
294
295 parent specifies a parent module to define a context for importing. It
296 may be None, indicating no particular context for the search.
297
298 modname specifies a single module (not dotted) within the parent.
299
300 fqname specifies the fully-qualified module name. This is a (potentially)
301 dotted name from the "root" of the module namespace down to the modname.
302 If there is no parent, then modname==fqname.
303
304 This method should return None, a 2-tuple, or a 3-tuple.
305
306 * If the module was not found, then None should be returned.
307
308 * The first item of the 2- or 3-tuple should be the integer 0 or 1,
309 specifying whether the module that was found is a package or not.
310
311 * The second item is the code object for the module (it will be
312 executed within the new module's namespace). This item can also
313 be a fully-loaded module object (e.g. loaded from a shared lib).
314
315 * If present, the third item is a dictionary of name/value pairs that
316 will be inserted into new module before the code object is executed.
317 This provided in case the module's code expects certain values (such
318 as where the module was found). When the second item is a module
319 object, then these names/values will be inserted *after* the module
320 has been loaded/initialized.
321 """
322 raise RuntimeError, "get_code not implemented"
323
324
325######################################################################
326#
Greg Stein63faa011999-11-20 11:22:37 +0000327# Some handy stuff for the Importers
328#
329
330# byte-compiled file suffic character
331_suffix_char = __debug__ and 'c' or 'o'
332
333# byte-compiled file suffix
334_suffix = '.py' + _suffix_char
335
336
337def _compile(pathname, timestamp):
338 """Compile (and cache) a Python source file.
339
340 The file specified by <pathname> is compiled to a code object and
341 returned.
342
343 Presuming the appropriate privileges exist, the bytecodes will be
344 saved back to the filesystem for future imports. The source file's
345 modification timestamp must be provided as a Long value.
346 """
347 codestring = open(pathname, 'r').read()
348 if codestring and codestring[-1] != '\n':
349 codestring = codestring + '\n'
350 code = __builtin__.compile(codestring, pathname, 'exec')
351
352 # try to cache the compiled code
353 try:
354 f = open(pathname + _suffix_char, 'wb')
355 except IOError:
356 pass
357 else:
358 f.write('\0\0\0\0')
359 f.write(struct.pack('<I', timestamp))
360 marshal.dump(code, f)
361 f.flush()
362 f.seek(0, 0)
363 f.write(imp.get_magic())
364 f.close()
365
366 return code
367
368_os_stat = _os_path_join = None
369def _os_bootstrap():
370 "Set up 'os' module replacement functions for use during import bootstrap."
371
372 names = sys.builtin_module_names
373
374 join = None
375 if 'posix' in names:
376 sep = '/'
377 from posix import stat
378 elif 'nt' in names:
379 sep = '\\'
380 from nt import stat
381 elif 'dos' in names:
382 sep = '\\'
383 from dos import stat
384 elif 'os2' in names:
385 sep = '\\'
386 from os2 import stat
387 elif 'mac' in names:
388 from mac import stat
389 def join(a, b):
390 if a == '':
391 return b
392 path = s
393 if ':' not in a:
394 a = ':' + a
395 if a[-1:] <> ':':
396 a = a + ':'
397 return a + b
398 else:
399 raise ImportError, 'no os specific module found'
400
401 if join is None:
402 def join(a, b, sep=sep):
403 if a == '':
404 return b
405 lastchar = a[-1:]
406 if lastchar == '/' or lastchar == sep:
407 return a + b
408 return a + sep + b
409
410 global _os_stat
411 _os_stat = stat
412
413 global _os_path_join
414 _os_path_join = join
415
416def _os_path_isdir(pathname):
417 "Local replacement for os.path.isdir()."
418 try:
419 s = _os_stat(pathname)
420 except OSError:
421 return None
422 return (s[0] & 0170000) == 0040000
423
424def _timestamp(pathname):
425 "Return the file modification time as a Long."
426 try:
427 s = _os_stat(pathname)
428 except OSError:
429 return None
430 return long(s[8])
431
432def _fs_import(dir, modname):
433 "Fetch a module from the filesystem."
434
435 pathname = _os_path_join(dir, modname)
436 if _os_path_isdir(pathname):
Greg Stein6d3165a1999-11-20 11:39:00 +0000437 values = { '__pkgdir__' : pathname, '__path__' : [ pathname ] }
Greg Stein63faa011999-11-20 11:22:37 +0000438 ispkg = 1
439 pathname = _os_path_join(pathname, '__init__')
440 else:
441 values = { }
442 ispkg = 0
443
444 t_py = _timestamp(pathname + '.py')
445 t_pyc = _timestamp(pathname + _suffix)
446 if t_py is None and t_pyc is None:
447 return None
448 code = None
449 if t_py is None or (t_pyc is not None and t_pyc >= t_py):
450 file = pathname + _suffix
451 f = open(file, 'rb')
452 if f.read(4) == imp.get_magic():
453 t = struct.unpack('<I', f.read(4))[0]
454 if t == t_py:
455 code = marshal.load(f)
456 f.close()
457 if code is None:
458 file = pathname + '.py'
459 code = _compile(file, t_py)
460
461 values['__file__'] = file
462 return ispkg, code, values
463
464
465######################################################################
466#
Greg Stein281b8d81999-11-07 12:54:45 +0000467# Simple function-based importer
468#
469class FuncImporter(Importer):
470 "Importer subclass to use a supplied function rather than method overrides."
471 def __init__(self, func):
472 self.func = func
473 def get_code(self, parent, modname, fqname):
474 return self.func(parent, modname, fqname)
475
476def install_with(func):
477 FuncImporter(func).install()
478
479
480######################################################################
481#
482# Base class for archive-based importing
483#
484class PackageArchiveImporter(Importer):
Greg Stein63faa011999-11-20 11:22:37 +0000485 """Importer subclass to import from (file) archives.
486
487 This Importer handles imports of the style <archive>.<subfile>, where
488 <archive> can be located using a subclass-specific mechanism and the
489 <subfile> is found in the archive using a subclass-specific mechanism.
490
491 This class defines two hooks for subclasses: one to locate an archive
492 (and possibly return some context for future subfile lookups), and one
493 to locate subfiles.
494 """
Greg Stein281b8d81999-11-07 12:54:45 +0000495
496 def get_code(self, parent, modname, fqname):
497 if parent:
Greg Stein63faa011999-11-20 11:22:37 +0000498 # the Importer._finish_import logic ensures that we handle imports
499 # under the top level module (package / archive).
500 assert parent.__importer__ == self
501
Greg Stein281b8d81999-11-07 12:54:45 +0000502 # if a parent "package" is provided, then we are importing a sub-file
503 # from the archive.
504 result = self.get_subfile(parent.__archive__, modname)
505 if result is None:
506 return None
507 if type(result) == type(()):
508 return (0,) + result
509 return 0, result
510
511 # no parent was provided, so the archive should exist somewhere on the
512 # default "path".
513 archive = self.get_archive(modname)
514 if archive is None:
515 return None
516 return 1, "", {'__archive__':archive}
517
518 def get_archive(self, modname):
519 """Get an archive of modules.
520
521 This method should locate an archive and return a value which can be
522 used by get_subfile to load modules from it. The value may be a simple
523 pathname, an open file, or a complex object that caches information
524 for future imports.
525
526 Return None if the archive was not found.
527 """
528 raise RuntimeError, "get_archive not implemented"
529
530 def get_subfile(self, archive, modname):
531 """Get code from a subfile in the specified archive.
532
533 Given the specified archive (as returned by get_archive()), locate
534 and return a code object for the specified module name.
535
536 A 2-tuple may be returned, consisting of a code object and a dict
537 of name/values to place into the target module.
538
539 Return None if the subfile was not found.
540 """
541 raise RuntimeError, "get_subfile not implemented"
542
543
544class PackageArchive(PackageArchiveImporter):
545 "PackageArchiveImporter subclass that refers to a specific archive."
546
547 def __init__(self, modname, archive_pathname):
548 self.__modname = modname
549 self.__path = archive_pathname
550
551 def get_archive(self, modname):
552 if modname == self.__modname:
553 return self.__path
554 return None
555
556 # get_subfile is passed the full pathname of the archive
557
558
559######################################################################
560#
561# Emulate the standard directory-based import mechanism
562#
Greg Stein281b8d81999-11-07 12:54:45 +0000563class DirectoryImporter(Importer):
564 "Importer subclass to emulate the standard importer."
565
566 def __init__(self, dir):
567 self.dir = dir
Greg Stein281b8d81999-11-07 12:54:45 +0000568
569 def get_code(self, parent, modname, fqname):
570 if parent:
571 dir = parent.__pkgdir__
572 else:
573 dir = self.dir
574
Greg Stein63faa011999-11-20 11:22:37 +0000575 # defer the loading of OS-related facilities
576 if not _os_stat:
577 _os_bootstrap()
Greg Stein281b8d81999-11-07 12:54:45 +0000578
Greg Stein63faa011999-11-20 11:22:37 +0000579 # Return the module (and other info) if found in the specified
580 # directory. Otherwise, return None.
581 return _fs_import(dir, modname)
Greg Stein281b8d81999-11-07 12:54:45 +0000582
583 def __repr__(self):
584 return '<%s.%s for "%s" at 0x%x>' % (self.__class__.__module__,
585 self.__class__.__name__,
586 self.dir,
587 id(self))
588
Greg Stein63faa011999-11-20 11:22:37 +0000589######################################################################
590#
Greg Stein7ec28d21999-11-20 12:31:07 +0000591# Emulate the standard path-style import mechanism
Greg Stein63faa011999-11-20 11:22:37 +0000592#
Greg Stein7ec28d21999-11-20 12:31:07 +0000593class PathImporter(Importer):
594 def __init__(self, path=sys.path):
595 self.path = path
Greg Stein63faa011999-11-20 11:22:37 +0000596
597 # we're definitely going to be importing something in the future,
598 # so let's just load the OS-related facilities.
599 if not _os_stat:
600 _os_bootstrap()
601
602 def get_code(self, parent, modname, fqname):
603 if parent:
604 # we are looking for a module inside of a specific package
605 return _fs_import(parent.__pkgdir__, modname)
606
607 # scan sys.path, looking for the requested module
Greg Stein7ec28d21999-11-20 12:31:07 +0000608 for dir in self.path:
Greg Stein63faa011999-11-20 11:22:37 +0000609 result = _fs_import(dir, modname)
610 if result:
Greg Stein63faa011999-11-20 11:22:37 +0000611 return result
612
613 # not found
614 return None
615
616
617######################################################################
618#
619# Emulate the import mechanism for builtin and frozen modules
620#
621class BuiltinImporter(Importer):
622 def get_code(self, parent, modname, fqname):
623 if parent:
624 # these modules definitely do not occur within a package context
625 return None
626
627 # look for the module
628 if imp.is_builtin(modname):
629 type = imp.C_BUILTIN
630 elif imp.is_frozen(modname):
631 type = imp.PY_FROZEN
632 else:
633 # not found
634 return None
635
636 # got it. now load and return it.
637 module = imp.load_module(modname, None, modname, ('', '', type))
638 return 0, module, { }
639
640
641######################################################################
642
Greg Stein281b8d81999-11-07 12:54:45 +0000643def _test_dir():
644 "Debug/test function to create DirectoryImporters from sys.path."
645 path = sys.path[:]
646 path.reverse()
647 for d in path:
648 DirectoryImporter(d).install()
649
Greg Stein63faa011999-11-20 11:22:37 +0000650def _test_revamp():
651 "Debug/test function for the revamped import system."
652 BuiltinImporter().install()
Greg Stein7ec28d21999-11-20 12:31:07 +0000653 PathImporter().install()
Greg Stein63faa011999-11-20 11:22:37 +0000654
655def _print_importers():
656 items = sys.modules.items()
657 items.sort()
658 for name, module in items:
659 if module:
660 print name, module.__dict__.get('__importer__', '-- no importer')
661 else:
662 print name, '-- non-existent module'
663
Greg Stein281b8d81999-11-07 12:54:45 +0000664######################################################################