]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
original received from Ken
authorGuido van Rossum <guido@python.org>
Fri, 7 Apr 1995 09:03:10 +0000 (09:03 +0000)
committerGuido van Rossum <guido@python.org>
Fri, 7 Apr 1995 09:03:10 +0000 (09:03 +0000)
Lib/newimp.py [new file with mode: 0755]

diff --git a/Lib/newimp.py b/Lib/newimp.py
new file mode 100755 (executable)
index 0000000..24d082d
--- /dev/null
@@ -0,0 +1,956 @@
+"""Prototype of 'import' functionality enhanced to implement packages.
+
+Why packages?  Packages enable module nesting and sibling module
+imports.  'Til now, the python module namespace was flat, which
+means every module had to have a unique name, in order to not
+conflict with names of other modules on the load path.  Furthermore,
+suites of modules could not be structurally affiliated with one
+another.
+
+With packages, a suite of, eg, email-oriented modules can include a
+module named 'mailbox', without conflicting with the, eg, 'mailbox'
+module of a shared-memory suite - 'email.mailbox' vs
+'shmem.mailbox'.  Packages also enable modules within a suite to
+load other modules within their package without having the package
+name hard-coded.  Similarly, package suites of modules can be loaded
+as a unit, by loading the package that contains them.
+
+Usage: once installed (newimp.install(); newimp.revert() to revert to
+the prior __import__ routine), 'import ...' and 'from ... import ...'
+can be used to:
+
+  - import modules from the search path, as before.
+
+  - import modules from within other directory "packages" on the search
+    path using a '.' dot-delimited nesting syntax.  The nesting is fully
+    recursive.
+
+  - import an entire package as a unit, by importing the package directory.
+    If there is a module named '__main__.py' in the package, it controls the
+    load.  Otherwise, all the modules in the dir, including packages, are
+    inherently loaded into the package module's namespace.
+
+    __main__.py can load the entire directory, by loading the package
+    itself, via eg 'import __', or even 'from __ import *'.  The benefit
+    is (1) the ability to do additional things before and after the loads
+    of the other modules, and (2) the ability to populate the package
+    module with the *contents* of the component modules, ie with a
+    'from __ import *'.)
+
+  - import siblings from modules within a package, using '__.' as a shorthand
+    prefix to refer to the parent package.  This enables referential
+    transparency - package modules need not know their package name.
+
+  - The '__' package references are actually names assigned within
+    modules, to refer to their containing package.  This means that
+    variable references can be made to imported modules, or variables
+    defined via 'import ... from' of the modules, also using the '__.var'
+    shorthand notation.  This establishes an proper equivalence between
+    the import reference '__.sibling' and the var reference '__.sibling'. 
+
+Modules have a few new attributes, in support of packages.  As
+mentioned above, '__' is a shorthand attribute denoting the
+modules' parent package, also denoted in the module by
+'__package__'.  Additionally, modules have associated with them a
+'__pkgpath__', a path by which sibling modules are found."""
+
+__version__ = "$Revision$"
+
+# $Id$
+# First release:  Ken.Manheimer@nist.gov,  5-Apr-1995, for python 1.2
+
+# Developers Notes:
+#
+# - 'sys.stub_modules' registers "incidental" (partially loaded) modules.
+#   A stub module is promoted to the fully-loaded 'sys.modules' list when it is
+#   explicitly loaded as a unit.
+# - The __main__ loads of '__' have not yet been tested.
+# - The test routines are cool, including a transient directory
+#   hierarchy facility, and a means of skipping to later tests by giving
+#   the test routine a numeric arg.
+# - This could be substantially optimized, and there are many loose ends
+#   lying around, since i wanted to get this released for 1.2.
+
+VERBOSE = 0
+
+import sys, string, regex, types, os, marshal, new, __main__
+try:
+    import imp                         # Build on this recent addition
+except ImportError:
+    raise ImportError, 'Pkg import module depends on optional "imp" module'
+
+from imp import SEARCH_ERROR, PY_SOURCE, PY_COMPILED, C_EXTENSION
+PY_PACKAGE = 4                         # In addition to above PY_*
+
+modes = {SEARCH_ERROR: 'SEARCH_ERROR',
+        PY_SOURCE: 'PY_SOURCE',
+        PY_COMPILED: 'PY_COMPILED',
+        C_EXTENSION: 'C_EXTENSION',
+        PY_PACKAGE: 'PY_PACKAGE'}
+
+# sys.stub_modules tracks modules partially loaded modules, ie loaded only
+# incidental to load of nested components.
+
+try: sys.stub_modules
+except AttributeError:
+    sys.stub_modules = {}
+
+# Environment setup - "root" module, '__python__'
+
+# Establish root package '__python__' in __main__ and newimp envs.
+
+PKG_MAIN_NM = '__main__'               # 'pkg/__main__.py' master, if present.
+PKG_NM = '__package__'                 # Longhand for module's container.
+PKG_SHORT_NM = '__'                    # Shorthand for module's container.
+PKG_SHORT_NM_LEN = len(PKG_SHORT_NM)
+PKG_PATH = '__pkgpath__'               # Var holding package search path,
+                                       # usually just the path of the pkg dir.
+__python__ = __main__
+sys.modules['__python__'] = __python__ # Register as an importable module.
+__python__.__dict__[PKG_PATH] = sys.path
+
+origImportFunc = None
+def install():
+    """Install newimp import_module() routine, for package support.
+
+    newimp.revert() reverts to __import__ routine that was superceded."""
+    global origImportFunc
+    if not origImportFunc:
+       try:
+           import __builtin__
+           origImportFunc = __builtin__.__import__
+       except AttributeError:
+           pass
+    __builtin__.__import__ = import_module
+    print 'Enhanced import functionality installed.'
+def revert():
+    """Revert to original __builtin__.__import__ func, if newimp.install() has
+    been executed."""
+    if origImportFunc:
+       import __builtin__
+       __builtin__.__import__ = origImportFunc
+       print 'Original import routine back in place.'
+
+def import_module(name,
+                 envLocals=None, envGlobals=None,
+                 froms=None,
+                 inPkg=None):
+    """Primary service routine implementing 'import' with package nesting."""
+
+    # The job is divided into a few distinct steps:
+    #
+    # - Look for either an already loaded module or a file to be loaded.
+    #   * if neither loaded module nor prospect file is found, raise an error.
+    #   - If we have a file, not an already loaded module:
+    #     - Load the file into a module.
+    #     - Register the new module and intermediate package stubs.
+    # (We have a module at this point...)
+    # - Bind requested syms (module or specified 'from' defs) in calling env.
+    # - Return the appropriate component.
+
+    note("import_module: seeking '%s'%s" %
+        (name, ((inPkg and ' (in package %s)' % inPkg.__name__) or '')))
+
+    # We need callers environment dict for local path and resulting module
+    # binding.
+    if not (envLocals or envGlobals):
+       envLocals, envGlobals = exterior()
+
+    modList = theMod = absNm = container = None
+
+    # Get module obj if one already established, or else module file if not:
+
+    if inPkg:
+       # We've been invoked with a specific containing package:
+       pkg, pkgPath, pkgNm = inPkg, inPkg.__dict__[PKG_PATH], inPkg.__name__
+       relNm = name
+       absNm = pkgNm + '.' + name
+       
+    elif name[:PKG_SHORT_NM_LEN+1] != PKG_SHORT_NM + '.':
+       # name is NOT '__.something' - setup to seek according to specified
+       # absolute name.
+       pkg = __python__
+       pkgPath = sys.path
+       absNm = name
+       relNm = absNm
+
+    else:
+       # name IS '__.' + something - setup to seek according to relative name,
+       # in current package.
+
+       relNm = name[len(PKG_SHORT_NM)+1:]      # Relative portion of name.
+       try:
+           pkg = envGlobals[PKG_NM]    # The immediately containing package.
+           pkgPath = pkg.__dict__[PKG_PATH]
+           if pkg == __python__:       # At outermost package.
+               absNm = relNm
+           else:
+               absNm = (pkg.__name__ + '.' + relNm)
+       except KeyError:                # Missing package, path, or name.
+           note("Can't identify parent package, package name, or pkgpath")
+           pass                                                        # ==v
+
+    # Try to find existing module:
+    if sys.modules.has_key(absNm):
+       note('found ' + absNm + ' already imported')
+       theMod = sys.modules[absNm]
+    else:
+       # Try for builtin or frozen first:
+       theMod = imp.init_builtin(absNm)
+       if theMod:
+           note('found builtin ' + absNm)
+       else:
+           theMod = imp.init_frozen(absNm)
+           if theMod:
+               note('found frozen ' + absNm)
+       if not theMod:
+           if type(pkgPath) == types.StringType:
+               pkgPath = [pkgPath]
+           modList = find_module(relNm, pkgPath, absNm)
+           if not modList:
+               raise ImportError, "module '%s' not found" % absNm      # ===X
+           # We have a list of successively nested files leading to the
+           # module, register them as stubs:
+           container = register_module_nesting(modList, pkg)
+
+           # Load from file if necessary and possible:
+           modNm, modf, path, ty = modList[-1]
+           note('found type ' + modes[ty[2]] + ' - ' + absNm)
+
+           # Do the load:
+           theMod = load_module(absNm, ty[2], modf, inPkg)
+
+           # Loaded successfully - promote module to full module status:
+           register_module(theMod, theMod.__name__, pkgPath, pkg)
+
+    # Have a loaded module, impose designated components, and return
+    # appropriate thing - according to guido:
+    # "Note that for "from spam.ham import bacon" your function should
+    #  return the object denoted by 'spam.ham', while for "import
+    #  spam.ham" it should return the object denoted by 'spam' -- the
+    #  STORE instructions following the import statement expect it this
+    #  way."
+    if not froms:
+       # Establish the module defs in the importing name space:
+       (envLocals or envGlobals)[name] = theMod
+       return (container or theMod)
+    else:
+       # Implement 'from': Populate immediate env with module defs:
+       if froms == '*':
+           froms = theMod.__dict__.keys()      # resolve '*'
+       for item in froms:
+           (envLocals or envGlobals)[item] = theMod.__dict__[item]
+       return theMod
+
+def unload(module):
+    """Remove registration for a module, so import will do a fresh load."""
+    if type(module) == types.ModuleType:
+       module = module.__name__
+    for m in [sys.modules, sys.stub_modules]:
+       try:
+           del m[module]
+       except KeyError:
+           pass
+
+def find_module(name, path, absNm=''):
+    """Locate module NAME on PATH.  PATH is pathname string or a list of them.
+
+    Note that up-to-date compiled versions of a module are preferred to plain
+    source, and compilation is automatically performed when necessary and
+    possible.
+
+    Returns a list of the tuples returned by 'find_module_file' (cf), one for
+    each nested level, deepest last."""
+
+    checked = []                       # For avoiding redundant dir lists.
+
+    if not absNm: absNm = name
+
+    # Parse name into list of nested components, 
+    expNm = string.splitfields(name, '.')
+
+    for curPath in path:
+
+       if (type(curPath) != types.StringType) or (curPath in checked):
+           # Disregard bogus or already investigated path elements:
+           continue                                                    # ==^
+       else:
+           # Register it for subsequent disregard.
+           checked.append(curPath)
+
+       if len(expNm) == 1:
+
+           # Non-nested module name:
+
+           got = find_module_file(curPath, absNm)
+           if got:
+               note('using %s' % got[2], 2)
+               return [got]                                            # ===>
+
+       else:
+
+           # Composite name specifying nested module:
+
+           gotList = []; nameAccume = expNm[0]
+
+           got = find_module_file(curPath, nameAccume)
+           if not got:                 # Continue to next prospective path.
+               continue                                                # ==^
+           else:
+               gotList.append(got)
+               nm, file, fullPath, ty = got
+
+           # Work on successively nested components:
+           for component in expNm[1:]:
+               # 'ty'pe of containing component must be package:
+               if ty[2] != PY_PACKAGE:
+                   gotList, got = [], None
+                   break                                               # ==v^
+               if nameAccume:
+                   nameAccume = nameAccume + '.' + component
+               else:
+                   nameAccume = component
+               got = find_module_file(fullPath, nameAccume)
+               if got:
+                   gotList.append(got)
+                   # ** have to return the *full* name here:
+                   nm, file, fullPath, ty = got
+               else:
+                   # Clear state vars:
+                   gotList, got, nameAccume = [], None, ''
+                   break                                               # ==v^
+           # Found nesting all the way to the specified tip:
+           if got:
+               return gotList                                          # ===>
+
+    # Failed.
+    return None
+
+def find_module_file(pathNm, modname):
+    """Find module file given dir PATHNAME and module NAME.
+
+    If successful, returns quadruple consisting of a mod name, file object,
+    PATHNAME for the found file, and a description triple as contained in the
+    list returned by get_suffixes.
+
+    Otherwise, returns None.
+
+    Note that up-to-date compiled versions of a module are preferred to plain
+    source, and compilation is automatically performed, when necessary and
+    possible."""
+
+    relNm = string.splitfields(modname,'.')[-1]
+
+    if pathNm[-1] != '/': pathNm = pathNm + '/'
+
+    for suff, mode, ty in get_suffixes():
+       note('trying ' + pathNm + relNm + suff + '...', 3)
+       fullPath = pathNm + relNm + suff
+       try:
+           modf = open(fullPath, mode)
+       except IOError:
+           # ?? Skip unreadable ones.
+           continue                                                    # ==^
+
+       if ty == PY_PACKAGE:
+           # Enforce directory characteristic:
+           if not os.path.isdir(fullPath):
+               note('Skipping non-dir match ' + fullPath)
+               continue                                                # ==^
+           else:
+               return (modname, modf, fullPath, (suff, mode, ty))      # ===>
+           
+
+       elif ty == PY_SOURCE:
+           # Try for a compiled version:
+           note('found source ' + fullPath, 2)
+           pyc = fullPath + 'c'        # Sadly, we're presuming '.py' suff.
+           if (not os.path.exists(pyc) or
+               (os.stat(fullPath)[8] > os.stat(pyc)[8])):
+               # Try to compile:
+               pyc = compile_source(fullPath, modf)
+           if pyc and (os.stat(fullPath)[8] < os.stat(pyc)[8]):
+               # Either pyc was already newer or we just made it so; in either
+               # case it's what we crave:
+               return (modname, open(pyc, 'rb'), pyc,                  # ===>
+                       ('.pyc', 'rb', PY_COMPILED))
+           # Couldn't get a compiled version - return the source:
+           return (modname, modf, fullPath, (suff, mode, ty))          # ===>
+
+       elif ty == PY_COMPILED:
+           # Make sure it is current, trying to compile if necessary, and
+           # prefer source failing that:
+           note('found compiled ' + fullPath, 2)
+           py = fullPath[:-1]          # Sadly again, presuming '.pyc' suff.
+           if not os.path.exists(py):
+               note('found pyc sans py: ' + fullPath)
+               return (modname, modf, fullPath, (suff, mode, ty))      # ===>
+           elif (os.stat(py)[8] > os.stat(fullPath)[8]):
+               note('forced to try compiling: ' + py)
+               pyc = compile_source(py, modf)
+               if pyc:
+                   return (modname, modf, fullPath, (suff, mode, ty))  # ===>
+               else:
+                   note('failed compile - must use more recent .py')
+                   return (modname,                                    # ===>
+                           open(py, 'r'), py, ('.py', 'r', PY_SOURCE))
+           else:
+               return (modname, modf, fullPath, (suff, mode, ty))      # ===>
+
+       elif ty == C_EXTENSION:
+           note('found extension ' + fullPath, 2)
+           return (modname, modf, fullPath, (suff, mode, ty))          # ===>
+
+       else:
+           raise SystemError, 'Unanticipated (new?) module type encountered'
+
+    return None
+
+
+def load_module(name, ty, theFile, fromMod=None):
+    """Load module NAME, type TYPE, from file FILE.
+
+    Optional arg fromMod indicated the module from which the load is being done
+    - necessary for detecting import of __ from a package's __main__ module.
+
+    Return the populated module object."""
+
+    # Note: we mint and register intermediate package directories, as necessary
+    
+    # Determine packagepath extension:
+
+    # Establish the module object in question:
+    theMod = procure_module(name)
+    nameTail = string.splitfields(name, '.')[-1]
+    thePath = theFile.name
+
+    if ty == PY_SOURCE:
+       exec_into(theFile, theMod, theFile.name)
+
+    elif ty == PY_COMPILED:
+       pyc = open(theFile.name, 'rb').read()
+       if pyc[0:4] != imp.get_magic():
+           raise ImportError, 'bad magic number: ' + theFile.name      # ===>
+       code = marshal.loads(pyc[8:])
+       exec_into(code, theMod, theFile.name)
+
+    elif ty == C_EXTENSION:
+       try:
+           theMod = imp.load_dynamic(nameTail, thePath, theFile)
+       except:
+           # ?? Ok to embellish the error message?
+           raise sys.exc_type, ('%s (from %s)' %
+                                (str(sys.exc_value), theFile.name))
+
+    elif ty == PY_PACKAGE:
+       # Load constituents:
+       if (os.path.exists(thePath + '/' + PKG_MAIN_NM) and
+           # pkg has a __main__, and this import not already from __main__, so
+           # __main__ can 'import __', or even better, 'from __ import *'
+           ((theMod.__name__ != PKG_MAIN_NM) and (fromMod.__ == theMod))):
+           exec_into(thePath + '/' + PKG_MAIN_NM, theMod, theFile.name)
+       else:
+           # ... or else recursively load constituent modules.
+           prospects = mod_prospects(thePath)
+           for item in prospects:
+               theMod.__dict__[item] = import_module(item,
+                                                     theMod.__dict__,
+                                                     theMod.__dict__,
+                                                     None,
+                                                     theMod)
+               
+    else:
+       raise ImportError, 'Unimplemented import type: %s' % ty         # ===>
+       
+    return theMod
+    
+def exec_into(obj, module, path):
+    """Helper for load_module, execfile/exec path or code OBJ within MODULE."""
+
+    # This depends on ability of exec and execfile to mutilate, erhm, mutate
+    # the __dict__ of a module.  It will not work if/when this becomes
+    # disallowed, as it is for normal assignments.
+
+    try:
+       if type(obj) == types.FileType:
+           execfile(path, module.__dict__, module.__dict__)
+       elif type(obj) in [types.CodeType, types.StringType]:
+           exec obj in module.__dict__, module.__dict__
+    except:
+       # ?? Ok to embellish the error message?
+       raise sys.exc_type, ('%s (from %s)' %
+                            (str(sys.exc_value), path))
+       
+
+def mod_prospects(path):
+    """Return a list of prospective modules within directory PATH.
+
+    We actually return the distinct names resulting from stripping the dir
+    entries (excluding '.' and '..') of their suffixes (as represented by
+    'get_suffixes').
+
+    (Note that matches for the PY_PACKAGE type with null suffix are
+    implicitly constrained to be directories.)"""
+
+    # We actually strip the longest matching suffixes, so eg 'dbmmodule.so'
+    # mates with 'module.so' rather than '.so'.
+
+    dirList = os.listdir(path)
+    excludes = ['.', '..']
+    sortedSuffs = sorted_suffixes()
+    entries = []
+    for item in dirList:
+       if item in excludes: continue                                   # ==^
+       for suff in sortedSuffs:
+           sub = -1 * len(suff)
+           if sub == 0:
+               if os.path.isdir(os.path.join(path, item)):
+                   entries.append(item)
+           elif item[sub:] == suff:
+               it = item[:sub]
+               if not it in entries:
+                   entries.append(it)
+               break                                                   # ==v^
+    return entries
+               
+
+
+def procure_module(name):
+    """Return an established or else new module object having NAME.
+
+    First checks sys.modules, then sys.stub_modules."""
+
+    if sys.modules.has_key(name):
+       it = sys.modules[name]
+    elif sys.stub_modules.has_key(name):
+       it = sys.stub_modules[name]
+    else:
+       it = new.module(name)
+    return it                                                          # ===>
+
+def register_module_nesting(modList, pkg):
+    """Given a find_module()-style NESTING and a parent PACKAGE, register
+    components as stub modules."""
+    container = None
+    for stubModNm, stubModF, stubPath, stubTy in modList:
+       relStubNm = string.splitfields(stubModNm, '.')[-1]
+       if sys.modules.has_key(stubModNm):
+           # Nestle in containing package:
+           stubMod = sys.modules[stubModNm]
+           pkg.__dict__[relStubNm] = stubMod
+           pkg = stubMod       # will be parent for next in sequence.
+       elif sys.stub_modules.has_key(stubModNm):
+           stubMod = sys.stub_modules[stubModNm]
+           pkg.__dict__[relStubNm] = stubMod
+           pkg = stubMod
+       else:
+           stubMod = procure_module(stubModNm)
+           # Register as a stub:
+           register_module(stubMod, stubModNm, stubPath, pkg, 1)
+           pkg.__dict__[relStubNm] = stubMod
+           pkg = stubMod
+       if not container:
+           container = stubMod
+    return container
+
+def register_module(theMod, name, path, package, stub=0):
+    """Properly register MODULE, w/ name, path, package, opt, as stub."""
+    
+    if stub:
+       sys.stub_modules[name] = theMod
+    else:
+       sys.modules[name] = theMod
+       if sys.stub_modules.has_key(name):
+           del sys.stub_modules[name]
+    theMod.__ = theMod.__dict__[PKG_NM] = package
+    theMod.__dict__[PKG_PATH] = path
+
+
+def compile_source(sourcePath, sourceFile):
+    """Given python code source path and file obj, Create a compiled version.
+
+    Return path of compiled version, or None if file creation is not
+    successful.  (Compilation errors themselves are passed without restraint.)
+
+    This is an import-private interface, and not well-behaved for general use.
+    
+    In particular, we presume the validity of the sourcePath, and that it
+    includes a '.py' extension."""
+
+    compiledPath = sourcePath[:-3] + '.pyc'
+    try:
+       compiledFile = open(compiledPath, 'wb')
+    except IOError:
+       note("write permission denied to " + compiledPath)
+       return None
+    mtime = os.stat(sourcePath)[8]
+    sourceFile.seek(0)                 # rewind
+    try:
+       compiledFile.write(imp.get_magic())             # compiled magic number
+       compiledFile.seek(8, 0)                         # mtime space holder
+       # We let compilation errors go their own way...
+       compiled = compile(sourceFile.read(), sourcePath, 'exec')
+       marshal.dump(compiled, compiledFile)            # write the code obj
+       compiledFile.seek(4, 0)                         # position for mtime
+       compiledFile.write(marshal.dumps(mtime)[1:])    # register mtime
+       compiledFile.flush()
+       compiledFile.close()
+       return compiledPath
+    except IOError:
+       return None
+
+
+def PathExtension(locals, globals):    # Probably obsolete.
+    """Determine import search path extension vis-a-vis __pkgpath__ entries.
+
+    local dict __pkgpath__ will preceed global dict __pkgpath__."""
+
+    pathadd = []
+    if globals and globals.has_key(PKG_PATH):
+       pathadd = PrependPath(pathadd, globals[PKG_PATH], 'global')
+    if locals and locals.has_key(PKG_PATH):
+       pathadd = PrependPath(pathadd, locals[PKG_PATH], 'local')
+    if pathadd:
+       note(PKG_PATH + ' extension: ' + pathadd)
+    return pathadd
+
+def PrependPath(path, pre, whence):    # Probably obsolete
+    """Return copy of PATH list with string or list-of-strings PRE prepended.
+
+    If PRE is neither a string nor list-of-strings, print warning that
+    locality WHENCE has malformed value."""
+
+    # (There is probably a better way to handle malformed PREs, but raising an
+    # error seems too severe - in that case, a bad setting for
+    # sys.__pkgpath__ would prevent any imports!)
+
+    if type(pre) == types.StringType: return [pre] + path[:]           # ===>
+    elif type(pre) == types.ListType: return pre + path[:]             # ===>
+    else:
+       print "**Ignoring '%s' bad %s value**" % (whence, PKG_PATH)
+       return path                                                     # ===>
+
+got_suffixes = None
+def get_suffixes():
+    """Produce a list of triples, each describing a type of import file.
+
+    Triples have the form '(SUFFIX, MODE, TYPE)', where:
+
+    SUFFIX is a string found appended to a module name to make a filename for
+    that type of import file.
+
+    MODE is the mode string to be passed to the built-in 'open' function - "r"
+    for text files, "rb" for binary.
+
+    TYPE is the file type:
+
+     PY_SOURCE:                python source code,
+     PY_COMPILED:      byte-compiled python source,
+     C_EXTENSION:      compiled-code object file,
+     PY_PACKAGE:       python library directory, or
+     SEARCH_ERROR:     no module found. """
+
+    # Note: sorted_suffixes() depends on this function's value being invariant.
+    # sorted_suffixes() must be revised if this becomes untrue.
+    
+    global got_suffixes
+
+    if got_suffixes:
+       return got_suffixes
+    else:
+       # Ensure that the .pyc suffix precedes the .py:
+       got_suffixes = [('', 'r', PY_PACKAGE)]
+       py = pyc = None
+       for suff in imp.get_suffixes():
+           if suff[0] == '.py':
+               py = suff
+           elif suff[0] == '.pyc':
+               pyc = suff
+           else:
+               got_suffixes.append(suff)
+       got_suffixes.append(pyc)
+       got_suffixes.append(py)
+       return got_suffixes
+               
+
+sortedSuffs = []                       # State vars for sorted_suffixes().  Go
+def sorted_suffixes():
+    """Helper function ~efficiently~ tracks sorted list of module suffixes."""
+
+    # Produce sortedSuffs once - this presumes that get_suffixes does not
+    # change from call to call during a python session.  Needs to be
+    # corrected if that becomes no longer true.
+
+    global sortedsuffs
+    if not sortedSuffs:                        # do compute only the "first" time
+       for item in get_suffixes():
+           sortedSuffs.append(item[0])
+       # Sort them in descending order:
+       sortedSuffs.sort(lambda x, y: (((len(x) > len(y)) and 1) or
+                                      ((len(x) < len(y)) and -1)))
+       sortedSuffs.reverse()
+    return sortedSuffs
+
+
+# exterior(): Utility routine, obtain local and global dicts of environment
+#            containing/outside the callers environment, ie that of the
+#            caller's caller.  Routines can use exterior() to determine the
+#            environment from which they were called. 
+
+def exterior():
+    """Return dyad containing locals and globals of caller's caller.
+
+    Locals will be None if same as globals, ie env is global env."""
+
+    bogus = 'bogus'                    # A locally usable exception
+    try: raise bogus                   # Force an exception object
+    except bogus:
+       at = sys.exc_traceback.tb_frame.f_back          # The external frame.
+       if at.f_back: at = at.f_back                    # And further, if any.
+       globals, locals = at.f_globals, at.f_locals
+       if locals == globals:                           # Exterior is global?
+           locals = None
+       return (locals, globals)
+
+#########################################################################
+#                            TESTING FACILITIES                        #
+
+def note(msg, threshold=1):
+    if VERBOSE >= threshold: print '(import:', msg, ')'
+
+class TestDirHier:
+    """Populate a transient directory hierarchy according to a definition
+    template - so we can create package/module hierarchies with which to
+    exercise the new import facilities..."""
+
+    def __init__(self, template, where='/var/tmp'):
+       """Establish a dir hierarchy, according to TEMPLATE, that will be
+       deleted upon deletion of this object (or deliberate invocation of the
+       __del__ method)."""
+       self.PKG_NM = 'tdh_'
+       rev = 0
+       while os.path.exists(os.path.join(where, self.PKG_NM+str(rev))):
+           rev = rev + 1
+       sys.exc_traceback = None        # Ensure Discard of try/except obj ref
+       self.PKG_NM = self.PKG_NM + str(rev)
+       self.root = os.path.join(where, self.PKG_NM)
+       self.createDir(self.root)
+       self.add(template)
+
+    def __del__(self):
+       """Cleanup the test hierarchy."""
+       self.remove()
+    def add(self, template, root=None):
+       """Populate directory according to template dictionary.
+
+       Keys indicate file names, possibly directories themselves.
+
+       String values dictate contents of flat files.
+
+       Dictionary values dictate recursively embedded dictionary templates."""
+       if root == None: root = self.root
+       for key, val in template.items():
+           name = os.path.join(root, key)
+           if type(val) == types.StringType:   # flat file
+               self.createFile(name, val)
+           elif type(val) == types.DictionaryType:     # embedded dir
+               self.createDir(name)
+               self.add(val, name)
+           else:
+               raise ValueError, 'invalid file-value type, %s' % type(val)
+    def remove(self, name=''):
+       """Dispose of the NAME (or keys in dictionary), using 'rm -r'."""
+       name = os.path.join(self.root, name)
+       sys.exc_traceback = None        # Ensure Discard of try/except obj ref
+       if os.path.exists(name):
+           print '(TestDirHier: deleting %s)' % name
+           os.system('rm -r ' + name)
+       else:
+           raise IOError, "can't remove non-existent " + name
+    def createFile(self, name, contents=None):
+       """Establish file NAME with CONTENTS.
+
+       If no contents specfied, contents will be 'print NAME'."""
+       f = open(name, 'w')
+       if not contents:
+           f.write("print '" + name + "'\n")
+       else:
+           f.write(contents)
+       f.close
+    def createDir(self, name):
+       """Create dir with NAME."""
+       return os.mkdir(name, 0755)
+
+skipToTest = 0
+atTest = 1
+def testExec(msg, execList, locals, globals):
+    global skipToTest, atTest
+    print 'Import Test:', '(' + str(atTest) + ')', msg, '...'
+    atTest = atTest + 1
+    if skipToTest > (atTest - 1):
+       print ' ... skipping til test', skipToTest
+       return
+    else:
+       print ''
+    for stmt in execList:
+       exec stmt in locals, globals
+
+def test(number=0):
+    """Exercise import functionality, creating a transient dir hierarchy for
+    the purpose.
+
+    We actually install the new import functionality, temporarily, resuming the
+    existing function on cleanup."""
+
+    import __builtin__
+
+    global skipToTest, atTest
+    skipToTest = number
+    hier = None
+
+    def confPkgVars(mod, locals, globals):
+       if not sys.modules.has_key(mod):
+           print 'import test: missing module "%s"' % mod
+       else:
+           modMod = sys.modules[mod]
+           if not modMod.__dict__.has_key(PKG_SHORT_NM):
+               print ('import test: module "%s" missing %s pkg shorthand' %
+                      (mod, PKG_SHORT_NM))
+           if not modMod.__dict__.has_key(PKG_PATH):
+               print ('import test: module "%s" missing %s package path' %
+                      (mod, PKG_PATH))
+    def unloadFull(mod):
+       """Unload module and offspring submodules, if any."""
+       modMod = ''
+       if type(mod) == types.StringType:
+           modNm = mod
+       elif type(mod) == types.ModuleType:
+           modNm = modMod.__name__
+       for subj in sys.modules.keys() + sys.stub_modules.keys():
+           if subj[0:len(modNm)] == modNm:
+               unload(subj)
+
+    # First, get the globals and locals to pass to our testExec():
+    exec 'import ' + __name__
+    globals, locals = eval(__name__ + '.__dict__'), vars()
+
+    try:
+       __main__.testMods
+    except AttributeError:
+       __main__.testMods = []
+    testMods = __main__.testMods
+       
+
+    # Install the newimp routines, within a try/finally:
+    try:
+       sys.exc_traceback = None
+       wasImport = __builtin__.__import__      # Stash default
+       wasPath = sys.path
+    except AttributeError:
+       wasImport = None
+    try:
+       hiers = []; modules = []
+       global VERBOSE
+       wasVerbose, VERBOSE = VERBOSE, 2
+       __builtin__.__import__ = import_module  # Install new version
+
+       if testMods:            # Clear out imports from previous tests
+           for m in testMods[:]:
+               unloadFull(m)
+               testMods.remove(m)
+
+       testExec("already imported module: %s" % sys.modules.keys()[0],
+                ['import ' + sys.modules.keys()[0]],
+                locals, globals)
+       try:
+           no_sirree = 'no_sirree_does_not_exist'
+           testExec("non-existent module: %s" % no_sirree,
+                    ['import ' + no_sirree],
+                    locals, globals)
+       except ImportError:
+           testExec("ok", ['pass'], locals, globals)
+       got = None
+       for mod in ['Complex', 'UserDict', 'UserList', 'calendar',
+                   'cmd', 'dis', 'mailbox', 'profile', 'random', 'rfc822']:
+           if not (mod in sys.modules.keys()):
+               got = mod
+               break                                                   # ==v
+       if got:
+           testExec("not-yet loaded module: %s" % mod,
+                    ['import ' + mod, 'modules.append(got)'],
+                    locals, globals)
+       else:
+           print "Import Test: couldn't find unimported module from list"
+
+       # Now some package stuff.
+       
+       # First change the path to include our temp dir, copying so the
+       # addition can be revoked on cleanup in the finally, below:
+       sys.path = ['/var/tmp'] + sys.path[:]
+       # Now create a trivial package:
+       stmts = ["hier1 = TestDirHier({'a.py': 'print \"a.py executing\"'})",
+                "hiers.append(hier1)",
+                "root = hier1.PKG_NM",
+                "exec 'import ' + root",
+                "testMods.append(root)",
+                "confPkgVars(sys.modules[root].__name__, locals, globals)",
+                "confPkgVars(sys.modules[root].__name__+'.a',locals,globals)"]
+       testExec("trivial package, with one module, a.py",
+                stmts, locals, globals)
+       # Slightly less trivial package - reference to '__':
+       stmts = [("hier2 = TestDirHier({'ref.py': 'print \"Pkg __:\", __'})"),
+                "root = hier2.PKG_NM",
+                "hiers.append(hier2)",
+                "exec 'import ' + root",
+                "testMods.append(root)"]
+       testExec("trivial package, with module that has pkg shorthand ref",
+                stmts, locals, globals)
+       # Nested package, plus '__' references:
+
+       complexTemplate = {'ref.py': 'print "ref.py loading..."',
+                           'suite': {'s1.py': 'print "s1.py, in pkg:", __',
+                                     'subsuite': {'sub1.py':
+                                                  'print "sub1.py"'}}}
+       stmts = [('print """%s\n%s\n%s\n%s\n%s\n%s"""' %
+                 ('.../',
+                  '    ref.py\t\t\t"ref.py loading..."',
+                  '    suite/',
+                  '        s1.py \t\t"s1.py, in pkg: xxxx.suite"',
+                  '        subsuite/',
+                  '            sub1.py         "sub1.py" ')),
+                "hier3 = TestDirHier(complexTemplate)",
+                "root = hier3.PKG_NM",
+                "hiers.append(hier3)",
+                "exec 'import ' + root",
+                "testMods.append(root)"]
+       testExec("Significantly nestled package:",
+                stmts, locals, globals)
+
+       # Now try to do an embedded sibling import, using '__' shorthand -
+       # alter our complexTemplate for a new dirHier:
+       complexTemplate['suite']['s1.py'] = 'import __.subsuite'
+       stmts = ["hier4 = TestDirHier(complexTemplate)",
+                "root = hier4.PKG_NM",
+                "testMods.append(root)",
+                "hiers.append(hier4)",
+                "exec 'import %s.suite.s1' % root",
+                "testMods.append(root)"]
+       testExec("Similar structure, but suite/s1.py imports '__.subsuite'",
+                stmts, locals, globals)
+
+       sys.exc_traceback = None        # Signify clean conclusion.
+
+    finally:
+       if sys.exc_traceback:
+           print ' ** Import test FAILURE... cleanup.'
+       else:
+           print ' Import test SUCCESS... cleanup'
+       VERBOSE = wasVerbose
+       skipToTest = 0
+       atTest = 1
+       sys.path = wasPath
+       for h in hiers: h.remove(); del h       # Dispose of test directories
+       if wasImport:                           # Resurrect prior routine
+           __builtin__.__import__ = wasImport
+       else:
+           del __builtin__.__import__