blob: 6fa9f43cb6ddd4f8efe3507cb426644b9e4f7f1a [file] [log] [blame]
"""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.
For example, 'import test.test_types' will import the test_types
module within the 'test' package. The calling environment would
then access the module as 'test.test_types', which is the name of
the fully-loaded 'test_types' module. It is found contained within
the stub (ie, only partially loaded) 'test' module, hence accessed as
'test.test_types'.
- 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 to variables
defined via 'import ... from', also using the '__.var' shorthand
notation. This establishes a proper equivalence between the import
reference '__.sibling' and the var reference '__.sibling'.
- 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.
For example, 'import test' will load the modules of the entire 'test'
package, at least until a test failure is encountered.
In a package, a module with the name '__main__' has a special role.
If present in a package directory, then it is loaded into the package
module, instead of loading the contents of the directory. This
enables the __main__ module to control the load, possibly loading
the entire directory deliberately (using 'import __', or even
'from __ import *', to load all the module contents directly into the
package module).
- perform any combination of the above - have a package that contains
packages, etc.
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: 1.14"
# Id: newimp.py,v 1.14 1995/05/10 19:15:07 klm Exp
# 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 some 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."""
import __builtin__
global origImportFunc
if not origImportFunc:
try:
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:
while froms:
item = froms[0]; del froms[0]
if item == '*':
froms = theMod.__dict__.keys() + froms
else:
try:
(envLocals or envGlobals)[item] = theMod.__dict__[item]
except KeyError:
raise ImportError, ("name '%s' not found in module %s" %
(item, theMod.__name__))
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:
compiled = compile(sourceFile.read(), sourcePath, 'exec')
except SyntaxError:
# Doctor the exception a bit, to include the source file name in the
# report, and then reraise the doctored version.
os.unlink(compiledFile.name)
sys.exc_value = ((sys.exc_value[0] + ' in ' + sourceFile.name,)
+ sys.exc_value[1:])
raise sys.exc_type, sys.exc_value
try:
compiledFile.write(imp.get_magic()) # compiled magic number
compiledFile.seek(8, 0) # mtime space holder
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: sys.stderr.write('(import: ' + msg + ')\n')
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__
if __name__ == '__main__':
test()