| """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() |