From 5232590bf144efc886a14584bd41642d3b87095f Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Fri, 7 Apr 1995 09:03:10 +0000 Subject: original received from Ken --- Lib/newimp.py | 956 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 956 insertions(+) create mode 100755 Lib/newimp.py diff --git a/Lib/newimp.py b/Lib/newimp.py new file mode 100755 index 0000000..24d082d --- /dev/null +++ b/Lib/newimp.py @@ -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__ -- cgit v0.12