mirror of
https://github.com/python/cpython.git
synced 2024-11-30 13:24:13 +08:00
1571 lines
53 KiB
Python
Executable File
1571 lines
53 KiB
Python
Executable File
"""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 '__init__.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 '__init__' 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 __init__ 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$"
|
|
|
|
# $Id$ First release:
|
|
# Ken.Manheimer@nist.gov, 5-Apr-1995, for python 1.2
|
|
|
|
# Issues (scattered in code - search for three asterisks)
|
|
# *** Despite my efforts, 'reload(newimp)' will foul things up.
|
|
# *** Normalize_pathname will only work for Unix - which we need to detect.
|
|
# *** when a module with the name of the platform (as indicated by
|
|
# to-be-created var sys.platform), the package path gets '.' and the
|
|
# platform dir.
|
|
# *** use sys.impadmin for things like an import load-hooks var
|
|
# *** Import-load-hook keying module name versus package path, which dictates
|
|
# additions to the default ('.' and os-specific dir) path
|
|
# *** Document that the __init__.py can set __.__pkgpath__, in which case that
|
|
# will be used for the package-relative loads.
|
|
# *** Add a 'recursive' option to reload, for reload of package constituent
|
|
# modules (including subpackages), as well. Or maybe that should be the
|
|
# default, and eg stub-completion should override that default. ???
|
|
|
|
# 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.
|
|
# - One load nuance - the actual load of most module types goes into the
|
|
# already-generated stub module. HOWEVER, eg dynamically loaded modules
|
|
# generate a new module object, which must supplant the existing stub. One
|
|
# consequence is that the import process must use indirection through
|
|
# sys.stub_modules or sys.modules to track the actual modules across some of
|
|
# the phases.
|
|
# - 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.
|
|
# - There may still be some loose ends, not to mention bugs. But the full
|
|
# functionality should be there.
|
|
# - The ImportStack object is necessary to carry the list of in-process imports
|
|
# across very open-ended recursions, where the state cannot be passed
|
|
# explicitly via the import_module calls; for a primary example, via exec of
|
|
# an 'import' statement within a module.
|
|
# - Python's (current) handling of extension modules, via imp.load_dynamic,
|
|
# does too much, some of which needs to be undone. See comments in
|
|
# load_module. Among other things, we actually change the __name__ of the
|
|
# module, which conceivably may break something.
|
|
|
|
try:
|
|
VERBOSE
|
|
except NameError:
|
|
VERBOSE = 0 # Will be reset by init(1), also.
|
|
|
|
import sys, string, regex, types, os, marshal, traceback
|
|
import __main__, __builtin__
|
|
|
|
newimp_globals = vars()
|
|
|
|
try:
|
|
import imp # Build on this recent addition
|
|
except ImportError:
|
|
raise ImportError, 'Pkg import module depends on optional "imp" module'#==X
|
|
|
|
from imp import SEARCH_ERROR, PY_SOURCE, PY_COMPILED, C_EXTENSION
|
|
|
|
def defvar(varNm, envDict, val, override=0):
|
|
"""If VARNAME does not have value in DICT, assign VAL to it. Optional arg
|
|
OVERRIDE means force the assignment in any case."""
|
|
if (not envDict.has_key(varNm)) or override:
|
|
envDict[varNm] = val
|
|
|
|
def init(full_reset=0):
|
|
"""Do environment initialization, including retrofitting sys.modules with
|
|
module attributes."""
|
|
# Retrofit all existing modules with package attributes, under auspices of
|
|
# __root__:
|
|
|
|
locals, globals = vars(), newimp_globals
|
|
|
|
if full_reset:
|
|
global VERBOSE
|
|
VERBOSE = 0
|
|
|
|
# sys.stub_modules tracks modules partially loaded modules, ie loaded only
|
|
# incidental to load of nested components. Together with sys.modules and
|
|
# the import stack, it serves as part of the module registration mechanism.
|
|
defvar('stub_modules', sys.__dict__, {}, full_reset)
|
|
|
|
# Environment setup - "root" module, '__root__'
|
|
# Establish root package '__root__' in __main__ and newimp envs.
|
|
|
|
# Longhand for name of variable identifying module's containing package:
|
|
defvar('PKG_NM', globals, "__package__", full_reset)
|
|
# Shorthand for module's container:
|
|
defvar('PKG_SHORT_NM', globals, "__", full_reset)
|
|
defvar('PKG_SHORT_NM_LEN', globals, len(PKG_SHORT_NM), full_reset)
|
|
|
|
# Name of controlling module for a package, if any:
|
|
defvar('INIT_MOD_NM', globals, "__init__", full_reset)
|
|
|
|
# Paths eventually will be extended to accomodate non-filesystem media -
|
|
# eg, URLs, composite objects, who knows.
|
|
|
|
# Name assigned in sys for general import administration:
|
|
defvar('IMP_SYS_NM', globals, "imp_admin", full_reset)
|
|
defvar('MOD_LOAD_HOOKS', globals, "mod_load_hooks", full_reset)
|
|
if full_reset:
|
|
defvar(IMP_SYS_NM, sys.__dict__, {MOD_LOAD_HOOKS: {}}, full_reset)
|
|
|
|
|
|
# Name assigned in each module to tuple describing module import attrs:
|
|
defvar('IMP_ADMIN', globals, "__impadmin__", full_reset)
|
|
# The load-path obtaining for this package. Not defined for non-packages.
|
|
# If not set, package directory is used. If no package directory
|
|
# registered, sys.path is used.
|
|
defvar('PKG_PATH', globals, 0, full_reset)
|
|
# File from which module was loaded - may be None, eg, for __root__:
|
|
defvar('MOD_TYPE', globals, 1, full_reset)
|
|
# Exact path from which the module was loaded:
|
|
defvar('MOD_PATHNAME', globals, 2, full_reset)
|
|
# Package within which the module was found:
|
|
defvar('MOD_PACKAGE', globals, 3, full_reset)
|
|
defvar('USE_PATH', globals, 'either PKG_PATH or my dir', full_reset)
|
|
|
|
# We're aliasing the top-level __main__ module as '__root__':
|
|
defvar('__root__', globals, __main__, full_reset)
|
|
defvar('ROOT_MOD_NM', globals, "__root__", full_reset)
|
|
if not sys.modules.has_key('__root__') or full_reset:
|
|
# and register it as an imported module:
|
|
sys.modules[ROOT_MOD_NM] = __root__
|
|
|
|
# Register package information in all existing top-level modules - they'll
|
|
# the None's mean, among other things, that their USE_PATH's all defer to
|
|
# sys.path.
|
|
for aMod in sys.modules.values():
|
|
if (not aMod.__dict__.has_key(PKG_NM)) or full_reset:
|
|
set_mod_attrs(aMod, None, __root__, None, None)
|
|
|
|
try:
|
|
__builtin__.__import__
|
|
defvar('origImportFunc', globals, __builtin__.__import__)
|
|
defvar('origReloadFunc', globals, __builtin__.reload)
|
|
except AttributeError:
|
|
pass
|
|
|
|
defvar('PY_PACKAGE', globals, 4, full_reset)
|
|
defvar('PY_FROZEN', globals, 5, full_reset)
|
|
defvar('PY_BUILTIN', globals, 6, full_reset)
|
|
|
|
# Establish lookup table from mod-type "constants" to names:
|
|
defvar('mod_types', globals,
|
|
{SEARCH_ERROR: 'SEARCH_ERROR',
|
|
PY_SOURCE: 'PY_SOURCE',
|
|
PY_COMPILED: 'PY_COMPILED',
|
|
C_EXTENSION: 'C_EXTENSION',
|
|
PY_PACKAGE: 'PY_PACKAGE',
|
|
PY_FROZEN: 'PY_FROZEN',
|
|
PY_BUILTIN: 'PY_BUILTIN'},
|
|
full_reset)
|
|
|
|
defvar('stack', globals, ImportStack(), full_reset)
|
|
|
|
def install():
|
|
"""Install newimp import_module() routine, for package support.
|
|
|
|
newimp.revert() reverts to __import__ routine that was superceded."""
|
|
__builtin__.__import__ = import_module
|
|
__builtin__.reload = reload
|
|
__builtin__.unload = unload
|
|
__builtin__.bypass = bypass
|
|
return 'Enhanced import functionality in place.'
|
|
def revert():
|
|
"""Revert to original __builtin__.__import__ func, if newimp.install() has
|
|
been executed."""
|
|
if not (origImportFunc and origReloadFunc):
|
|
raise SystemError, "Can't find original import and reload funcs." # ==X
|
|
__builtin__.__import__ = origImportFunc
|
|
__builtin__.reload = origReloadFunc
|
|
del __builtin__.unload, __builtin__.bypass
|
|
return 'Original import routines back in place.'
|
|
|
|
def import_module(name,
|
|
envLocals=None, envGlobals=None,
|
|
froms=None,
|
|
inPkg=None):
|
|
"""Primary service routine implementing 'import' with package nesting.
|
|
|
|
NAME: name as specified to 'import NAME' or 'from NAME...'
|
|
LOCALS, GLOBALS: local and global dicts obtaining for import
|
|
FROMS: list of strings of "..." in 'import blat from ...'
|
|
INPKG: package to which the name search is restricted, for use
|
|
by recursive package loads (from import_module()).
|
|
|
|
A subtle difference from the old import - modules that do fail
|
|
initialization will not be registered in sys.modules, ie will not, in
|
|
effect, be registered as being loaded. Note further that packages which
|
|
fail their overall load, but have successfully loaded constituent modules,
|
|
will be accessible in the importing namespace as stub modules.
|
|
|
|
A new routine, 'newimp.bypass()', provides the means to circumvent
|
|
constituent modules that fail their load, in order to enable load of the
|
|
remainder of a package."""
|
|
|
|
rootMod = sys.modules[ROOT_MOD_NM]
|
|
|
|
note("import_module: seeking '%s'" % name, 1)
|
|
|
|
# We need callers environment dict for local path and resulting module
|
|
# binding.
|
|
if not envGlobals:
|
|
# This should not happen, but does for imports called from within
|
|
# functions.
|
|
envLocals, envGlobals = exterior()
|
|
|
|
if inPkg:
|
|
pkg = inPkg
|
|
elif envGlobals.has_key(PKG_NM):
|
|
pkg = envGlobals[PKG_NM]
|
|
else:
|
|
# ** KLUDGE - cover for modules that lack package attributes:
|
|
pkg = rootMod
|
|
|
|
if pkg != rootMod:
|
|
note(' - relative to package %s' % pkg)
|
|
|
|
modList = theMod = absNm = nesting = None
|
|
|
|
# Normalize
|
|
# - absNm is absolute w.r.t. __root__
|
|
# - relNm is relative w.r.t. pkg.
|
|
if inPkg:
|
|
absNm, relNm = pkg.__name__ + '.' + name, name
|
|
else:
|
|
absNm, relNm, pkg = normalize_import_ref(name, pkg)
|
|
note("Normalized: %s%s" % (absNm, (((relNm != absNm)
|
|
and (" ('%s' in %s)" % (relNm, pkg)))
|
|
or '')), 3)
|
|
|
|
pkgPath = get_mod_attrs(pkg, USE_PATH)
|
|
|
|
try: # try...finally guards import stack integrity.
|
|
|
|
if stack.push(absNm):
|
|
# We're nested inside a containing import of this module, perhaps
|
|
# indirectly. Avoid infinite recursion at this point by using the
|
|
# existing stub module, for now. Load of it will be completed by
|
|
# the superior import.
|
|
note('recursion on in-process module %s, punting with stub' %
|
|
absNm)
|
|
theMod = stack.mod(absNm)
|
|
|
|
else:
|
|
|
|
# Try to find already-imported:
|
|
if sys.modules.has_key(absNm):
|
|
note('found ' + absNm + ' already imported')
|
|
theMod = sys.modules[absNm]
|
|
stack.mod(absNm, theMod)
|
|
|
|
else: # Actually do load, of one sort or another:
|
|
|
|
# Seek builtin or frozen first:
|
|
theMod = imp.init_builtin(absNm)
|
|
if theMod:
|
|
set_mod_attrs(theMod, None, pkg, None, PY_BUILTIN)
|
|
stack.mod(absNm, theMod)
|
|
note('found builtin ' + absNm)
|
|
else:
|
|
theMod = imp.init_frozen(absNm)
|
|
if theMod:
|
|
set_mod_attrs(theMod, None, pkg, None, PY_FROZEN)
|
|
stack.mod(absNm, theMod)
|
|
note('found frozen ' + absNm)
|
|
|
|
if not theMod:
|
|
# Not already-loaded, in-process, builtin, or frozen -
|
|
# we're seeking in the outside world (filesystem):
|
|
|
|
if sys.stub_modules.has_key(absNm):
|
|
|
|
# A package for which we have a stub:
|
|
theMod = reload(sys.stub_modules[absNm], inPkg)
|
|
|
|
else:
|
|
|
|
# Now we actually search the fs.
|
|
|
|
if type(pkgPath) == types.StringType:
|
|
pkgPath = [pkgPath]
|
|
|
|
# Find a path leading to the module:
|
|
modList = find_module(relNm, pkgPath, absNm)
|
|
if not modList:
|
|
raise ImportError, ("module '%s' not found" % #==X
|
|
absNm)
|
|
|
|
# We have a list of successively nested dirs leading
|
|
# to the module, register with import admin, as stubs:
|
|
nesting = register_mod_nesting(modList, pkg)
|
|
|
|
# Load from file if necessary and possible:
|
|
modNm, modf, path, ty = modList[-1]
|
|
note('found type %s - %s' % (mod_types[ty[2]], absNm))
|
|
|
|
# Establish the module object in question:
|
|
theMod = procure_module(absNm)
|
|
stack.mod(absNm, theMod)
|
|
|
|
# Do the load:
|
|
theMod = load_module(theMod, ty[2], modf, inPkg)
|
|
|
|
commit_mod_containment(absNm)
|
|
|
|
# Successful load - promote to fully-imported status:
|
|
register_module(theMod, theMod.__name__)
|
|
|
|
|
|
# We have a loaded module (perhaps stub): situate specified 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."
|
|
# *** The above rationale should probably be reexamined, since newimp
|
|
# actually takes care of populating the caller's namespace.
|
|
|
|
if not froms:
|
|
|
|
# Return the outermost container, possibly stub:
|
|
if nesting:
|
|
return find_mod_registration(nesting[0][0])
|
|
else:
|
|
return find_mod_registration(string.splitfields(absNm,'.')[0])
|
|
else:
|
|
|
|
return theMod
|
|
|
|
finally: # Decrement stack registration:
|
|
stack.pop(absNm)
|
|
|
|
|
|
def reload(module, inPkg = None):
|
|
"""Re-parse and re-initialize an already (or partially) imported MODULE.
|
|
|
|
The argument can be an already loaded module object or a string name of a
|
|
loaded module or a "stub" module that was partially loaded package module
|
|
incidental to the full load of a contained module.
|
|
|
|
This is useful if you have edited the module source file using an external
|
|
editor and want to try out the new version without leaving the Python
|
|
interpreter. The return value is the resulting module object.
|
|
|
|
Contrary to the old 'reload', the load is sought from the same location
|
|
where the module was originally found. If you wish to do a fresh load from
|
|
a different module on the path, do an 'unload()' and then an import.
|
|
|
|
When a module is reloaded, its dictionary (containing the module's
|
|
global variables) is retained. Redefinitions of names will
|
|
override the old definitions, so this is generally not a problem.
|
|
If the new version of a module does not define a name that was
|
|
defined by the old version, the old definition remains. This
|
|
feature can be used to the module's advantage if it maintains a
|
|
global table or cache of objects -- with a `try' statement it can
|
|
test for the table's presence and skip its initialization if
|
|
desired.
|
|
|
|
It is legal though generally not very useful to reload built-in or
|
|
dynamically loaded modules, except for `sys', `__main__' and
|
|
`__builtin__'. In certain cases, however, extension modules are
|
|
not designed to be initialized more than once, and may fail in
|
|
arbitrary ways when reloaded.
|
|
|
|
If a module imports objects from another module using `from' ...
|
|
`import' ..., calling `reload()' for the other module does not
|
|
redefine the objects imported from it -- one way around this is to
|
|
re-execute the `from' statement, another is to use `import' and
|
|
qualified names (MODULE.NAME) instead.
|
|
|
|
If a module instantiates instances of a class, reloading the module
|
|
that defines the class does not affect the method definitions of
|
|
the instances, unless they are reinstantiated -- they continue to use the
|
|
old class definition. The same is true for derived classes."""
|
|
|
|
if type(module) == types.StringType:
|
|
theMod = find_mod_registration(module)
|
|
elif type(module) == types.ModuleType:
|
|
theMod = module
|
|
else:
|
|
raise ImportError, '%s not already imported' # ==X
|
|
|
|
if theMod in [sys.modules[ROOT_MOD_NM], sys.modules['__builtin__']]:
|
|
raise ImportError, 'cannot re-init internal module' # ==X
|
|
|
|
try:
|
|
thePath = get_mod_attrs(theMod, MOD_PATHNAME)
|
|
except KeyError:
|
|
thePath = None
|
|
|
|
if not thePath:
|
|
# If we have no path for the module, we can only reload it from
|
|
# scratch:
|
|
note('no pathname registered for %s, doing full reload' % theMod)
|
|
unload(theMod)
|
|
envGlobals, envLocals = exterior()
|
|
return import_module(theMod.__name__,
|
|
envGlobals, envLocals, None, inPkg)
|
|
else:
|
|
|
|
stack.mod(theMod.__name__, theMod)
|
|
ty = get_mod_attrs(theMod, MOD_TYPE)
|
|
if ty in [PY_SOURCE, PY_COMPILED]:
|
|
note('reload invoked for %s %s' % (mod_types[ty], theMod))
|
|
thePath, ty, openFile = prefer_compiled(thePath, ty)
|
|
else:
|
|
openFile = open(thePath, get_suffixes(ty)[1])
|
|
return load_module(theMod, # ==>
|
|
ty,
|
|
openFile,
|
|
inPkg)
|
|
def unload(module):
|
|
"""Remove registration for a module, so import will do a fresh load.
|
|
|
|
Returns the module registries (sys.modules and/or sys.stub_modules) where
|
|
it was found."""
|
|
if type(module) == types.ModuleType:
|
|
module = module.__name__
|
|
gotit = []
|
|
for which in ['sys.modules', 'sys.stub_modules']:
|
|
m = eval(which)
|
|
try:
|
|
del m[module]
|
|
gotit.append(which)
|
|
except KeyError:
|
|
pass
|
|
if not gotit:
|
|
raise ValueError, '%s not a module or a stub' % module # ==X
|
|
else: return gotit
|
|
def bypass(modNm):
|
|
"""Register MODULE-NAME so module will be skipped, eg in package load."""
|
|
if sys.modules.has_key(modNm):
|
|
raise ImportError("'%s' already imported, cannot be bypassed." % modNm)
|
|
else:
|
|
sys.modules[modNm] = imp.new_module('bypass()ed module %s' % modNm)
|
|
commit_mod_containment(modNm)
|
|
|
|
|
|
def normalize_import_ref(name, pkg):
|
|
"""Produce absolute and relative nm and relative pkg given MODNM and origin
|
|
PACKAGE, reducing out all '__'s in the process."""
|
|
|
|
# First reduce out all the '__' container-refs we can:
|
|
outwards, inwards = 0, []
|
|
for nm in string.splitfields(name, '.'):
|
|
if nm == PKG_SHORT_NM:
|
|
if inwards:
|
|
# Pop a containing inwards:
|
|
del inwards[-1]
|
|
else:
|
|
# (Effectively) leading '__' - notch outwards:
|
|
outwards = outwards + 1
|
|
else:
|
|
inwards.append(nm)
|
|
inwards = string.joinfields(inwards, '.')
|
|
|
|
# Now identify the components:
|
|
|
|
if not outwards:
|
|
pkg = sys.modules[ROOT_MOD_NM]
|
|
else:
|
|
while outwards > 1:
|
|
pkg = pkg.__dict__[PKG_NM] # We'll just loop at top
|
|
if pkg == __root__:
|
|
break # ==v
|
|
outwards = outwards - 1
|
|
|
|
if not inwards: # Entire package:
|
|
return pkg.__name__, pkg.__name__, pkg # ==>
|
|
else: # Name relative to package:
|
|
if pkg == __root__:
|
|
return inwards, inwards, pkg # ==>
|
|
else:
|
|
return pkg.__name__ + '.' + inwards, inwards, pkg # ==>
|
|
|
|
class ImportStack:
|
|
"""Provide judicious support for mutually recursive import loops.
|
|
|
|
Mutually recursive imports, eg a module that imports the package that
|
|
contains it, which in turn imports the module, are not uncommon, and must
|
|
be supported judiciously. This class is used to track cycles, so a module
|
|
already in the process of being imported (via 'stack.push(module)', and
|
|
concluded via 'stack.release(module)') is not redundantly pursued; *except*
|
|
when a module master '__init__.py' loads the module, in which case it is
|
|
'stack.relax(module)'ed, so the full import is pursued."""
|
|
|
|
def __init__(self):
|
|
self._cycles = {}
|
|
self._mods = {}
|
|
self._looped = []
|
|
def in_process(self, modNm):
|
|
"""1 if modNm load already in process, 0 otherwise."""
|
|
return self._cycles.has_key(modNm) # ==>
|
|
def looped(self, modNm):
|
|
"""1 if modNm load has looped once or more, 0 otherwise."""
|
|
return modNm in self._looped
|
|
def push(self, modNm):
|
|
"""1 if modNm already in process and not 'relax'ed, 0 otherwise.
|
|
(Note that the 'looped' status remains even when the cycle count
|
|
returns to 1. This is so error messages can indicate that it was, at
|
|
some point, looped during the import process.)"""
|
|
if self.in_process(modNm):
|
|
self._looped.append(modNm)
|
|
self._cycles[modNm] = self._cycles[modNm] + 1
|
|
return 1 # ==>
|
|
else:
|
|
self._cycles[modNm] = 1
|
|
return 0 # ==>
|
|
def mod(self, modNm, mod=None):
|
|
"""Associate MOD-NAME with MODULE, for easy reference."""
|
|
if mod:
|
|
self._mods[modNm] = mod
|
|
else:
|
|
try:
|
|
return self._mods[modNm] # ==>
|
|
except KeyError:
|
|
return None
|
|
def pop(self, modNm):
|
|
"""Decrement stack count of MODNM"""
|
|
if self.in_process(modNm):
|
|
amt = self._cycles[modNm] = self._cycles[modNm] - 1
|
|
if amt < 1:
|
|
del self._cycles[modNm]
|
|
if modNm in self._looped:
|
|
self._looped.remove(modNm)
|
|
if self._mods.has_key(modNm):
|
|
del self._mods[modNm]
|
|
def relax(self, modNm):
|
|
"""Enable modNm load despite being registered as already in-process."""
|
|
if self._cycles.has_key(modNm):
|
|
del self._cycles[modNm]
|
|
|
|
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_mod_file()', 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_mod_file(curPath, absNm)
|
|
if got:
|
|
note('using %s' % got[2], 3)
|
|
return [got] # ==>
|
|
|
|
else:
|
|
|
|
# Composite name specifying nested module:
|
|
|
|
gotList = []; nameAccume = expNm[0]
|
|
|
|
got = find_mod_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_mod_file(fullPath, nameAccume)
|
|
if got:
|
|
gotList.append(got)
|
|
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_mod_file(pathNm, modname):
|
|
"""Find right module file given DIR and module NAME, compiling if needed.
|
|
|
|
If successful, returns quadruple consisting of:
|
|
- mod name,
|
|
- file object,
|
|
- full pathname for the found file,
|
|
- 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 = modname[1 + string.rfind(modname, '.'):]
|
|
|
|
for suff, mode, ty in get_suffixes():
|
|
fullPath = os.path.join(pathNm, relNm + suff)
|
|
note('trying ' + fullPath + '...', 4)
|
|
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, 3)
|
|
continue # ==^
|
|
else:
|
|
return (modname, modf, fullPath, (suff, mode, ty)) # ==>
|
|
|
|
|
|
elif ty in [PY_SOURCE, PY_COMPILED]:
|
|
usePath, useTy, openFile = prefer_compiled(fullPath, ty)
|
|
return (modname, # ==>
|
|
openFile,
|
|
usePath,
|
|
get_suffixes(useTy))
|
|
|
|
elif ty == C_EXTENSION:
|
|
note('found C_EXTENSION ' + fullPath, 3)
|
|
return (modname, modf, fullPath, (suff, mode, ty)) # ==>
|
|
|
|
else:
|
|
raise SystemError, 'Unanticipated module type encountered' # ==X
|
|
|
|
return None
|
|
|
|
def prefer_compiled(path, ty, modf=None):
|
|
"""Given a path to a .py or .pyc file, attempt to return a path to a
|
|
current pyc file, compiling the .py in the process if necessary. Returns
|
|
the path to the most current version we can get."""
|
|
|
|
if ty == PY_SOURCE:
|
|
if not modf:
|
|
try:
|
|
modf = open(path, 'r')
|
|
except IOError:
|
|
pass
|
|
note('working from PY_SOURCE', 3)
|
|
# Try for a compiled version:
|
|
pyc = path + 'c' # Sadly, we're presuming '.py' suff.
|
|
if (not os.path.exists(pyc) or
|
|
(os.stat(path)[8] > os.stat(pyc)[8])):
|
|
# Try to compile:
|
|
pyc = compile_source(path, modf)
|
|
if pyc and not (os.stat(path)[8] > os.stat(pyc)[8]):
|
|
# Either pyc was already newer or we just made it so; in either
|
|
# case it's what we crave:
|
|
note('but got newer compiled, ' + pyc, 3)
|
|
try:
|
|
return (pyc, PY_COMPILED, open(pyc, 'rb')) # ==>
|
|
except IOError:
|
|
if modf:
|
|
return (path, PY_SOURCE, modf) # ==>
|
|
else:
|
|
raise ImportError, 'Failed acces to .py and .pyc' # ==X
|
|
else:
|
|
note("couldn't get newer compiled, using PY_SOURCE", 3)
|
|
if modf:
|
|
return (path, PY_SOURCE, modf) # ==>
|
|
else:
|
|
raise ImportError, 'Failed acces to .py and .pyc' # ==X
|
|
|
|
elif ty == PY_COMPILED:
|
|
note('working from PY_COMPILED', 3)
|
|
if not modf:
|
|
try:
|
|
modf = open(path, 'rb')
|
|
except IOError:
|
|
return prefer_compiled(path[:-1], PY_SOURCE)
|
|
# Make sure it is current, trying to compile if necessary, and
|
|
# prefer source failing that:
|
|
note('found compiled ' + path, 3)
|
|
py = path[:-1] # ** Presuming '.pyc' suffix
|
|
if not os.path.exists(py):
|
|
note('pyc SANS py: ' + path, 3)
|
|
return (path, PY_COMPILED, open(py, 'r')) # ==>
|
|
elif (os.stat(py)[8] > os.stat(path)[8]):
|
|
note('Forced to compile: ' + py, 3)
|
|
pyc = compile_source(py, open(py, 'r'))
|
|
if pyc:
|
|
return (pyc, PY_COMPILED, modf) # ==>
|
|
else:
|
|
note('failed compile - must use more recent .py', 3)
|
|
return (py, PY_SOURCE, open(py, 'r')) # ==>
|
|
else:
|
|
return (path, PY_COMPILED, modf) # ==>
|
|
|
|
def load_module(theMod, ty, theFile, fromMod):
|
|
"""Load module NAME, of TYPE, from FILE, within MODULE.
|
|
|
|
Optional arg fromMod indicates the module from which the load is being done
|
|
- necessary for detecting import of __ from a package's __init__ module.
|
|
|
|
Return the populated module object."""
|
|
|
|
# Note: we mint and register intermediate package directories, as necessary
|
|
|
|
name = theMod.__name__
|
|
nameTail = name[1 + string.rfind(name, '.'):]
|
|
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 # ==X
|
|
code = marshal.loads(pyc[8:])
|
|
exec_into(code, theMod, theFile.name)
|
|
|
|
elif ty == C_EXTENSION:
|
|
# Dynamically loaded C_EXTENSION modules do too much import admin,
|
|
# themselves, which we need to *undo* in order to integrate them with
|
|
# the new import scheme.
|
|
# 1 They register themselves in sys.modules, registering themselves
|
|
# under their top-level names. Have to rectify that.
|
|
# 2 The produce their own module objects, *unless* they find an
|
|
# existing module already registered a la 1, above. We employ this
|
|
# quirk to make it use the already generated module.
|
|
try:
|
|
# Stash a ref to any module that is already registered under the
|
|
# dyamic module's simple name (nameTail), so we can reestablish it
|
|
# after the dynamic takes over its' slot:
|
|
protMod = None
|
|
if nameTail != name:
|
|
if sys.modules.has_key(nameTail):
|
|
protMod = sys.modules[nameTail]
|
|
# Trick the dynamic load, by registering the module we generated
|
|
# under the nameTail of the module we're loading, so the one we're
|
|
# loading will use that established module, rather than producing a
|
|
# new one:
|
|
sys.modules[nameTail] = theMod
|
|
theMod = imp.load_dynamic(nameTail, thePath, theFile)
|
|
theMod.__name__ = name
|
|
# Cleanup dynamic mod's bogus self-registration, if necessary:
|
|
if nameTail != name:
|
|
if protMod:
|
|
# ... reinstating the one that was already there...
|
|
sys.modules[nameTail] = protMod
|
|
else:
|
|
if sys.modules.has_key(nameTail):
|
|
# Certain, as long os dynamics continue to misbehave.
|
|
del sys.modules[nameTail]
|
|
stack.mod(name, theMod)
|
|
if sys.stub_modules.has_key(name):
|
|
sys.stub_modules[name] = theMod
|
|
elif sys.modules.has_key(name):
|
|
sys.modules[name] = theMod
|
|
except:
|
|
# Provide import-nesting info, including signs of circularity:
|
|
raise sys.exc_type, import_trail_msg(str(sys.exc_value),# ==X
|
|
sys.exc_traceback,
|
|
name)
|
|
elif ty == PY_PACKAGE:
|
|
# Load package constituents, doing the controlling module *if* it
|
|
# exists *and* it isn't already in process:
|
|
|
|
init_mod_f = init_mod = None
|
|
if not stack.in_process(name + '.' + INIT_MOD_NM):
|
|
# Not already doing __init__ - check for it:
|
|
init_mod_f = find_mod_file(thePath, INIT_MOD_NM)
|
|
else:
|
|
note('skipping already-in-process %s.%s' % (theMod.__name__,
|
|
INIT_MOD_NM))
|
|
got = {}
|
|
if init_mod_f:
|
|
note("Found package's __init__: " + init_mod_f[2])
|
|
# Enable full continuance of containing-package-load from __init__:
|
|
if stack.in_process(theMod.__name__):
|
|
stack.relax(theMod.__name__)
|
|
init_mod = import_module(INIT_MOD_NM,
|
|
theMod.__dict__, theMod.__dict__,
|
|
None,
|
|
theMod)
|
|
else:
|
|
# ... or else recursively load all constituent modules, except
|
|
# __init__:
|
|
for prospect in mod_prospects(thePath):
|
|
if prospect != INIT_MOD_NM:
|
|
import_module(prospect,
|
|
theMod.__dict__, theMod.__dict__,
|
|
None,
|
|
theMod)
|
|
|
|
else:
|
|
raise ImportError, 'Unimplemented import type: %s' % ty # ==X
|
|
|
|
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:
|
|
# Make the error message nicer?
|
|
raise sys.exc_type, import_trail_msg(str(sys.exc_value), # ==X
|
|
sys.exc_traceback,
|
|
module.__name__)
|
|
|
|
|
|
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 os.curdir and os.pardir) 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 = [os.curdir, os.pardir]
|
|
sortedSuffs = sorted_suffixes()
|
|
entries = []
|
|
for item in dirList:
|
|
if item in excludes: continue # ==^
|
|
for suff in sortedSuffs:
|
|
# *** ?? maybe platform-specific:
|
|
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):
|
|
return sys.modules[name] # ==>
|
|
elif sys.stub_modules.has_key(name):
|
|
return sys.stub_modules[name] # ==>
|
|
else:
|
|
return (stack.mod(name) or imp.new_module(name)) # ==>
|
|
|
|
def commit_mod_containment(name):
|
|
"""Bind a module object and its containers within their respective
|
|
containers."""
|
|
cume, pkg = '', find_mod_registration(ROOT_MOD_NM)
|
|
for next in string.splitfields(name, '.'):
|
|
if cume:
|
|
cume = cume + '.' + next
|
|
else:
|
|
cume = next
|
|
cumeMod = find_mod_registration(cume)
|
|
pkg.__dict__[next] = cumeMod
|
|
pkg = cumeMod
|
|
|
|
def register_mod_nesting(modList, pkg):
|
|
"""Given find_module()-style NEST-LIST and parent PACKAGE, register new
|
|
package components as stub modules, and return list of nested
|
|
module/relative-name pairs.
|
|
|
|
Note that the modules objects are not situated in their containing packages
|
|
here - that is left 'til after a successful load, and done by
|
|
commit_mod_nesting()."""
|
|
nesting = []
|
|
|
|
for modNm, modF, path, ty in modList:
|
|
|
|
relNm = modNm[1 + string.rfind(modNm, '.'):]
|
|
|
|
if sys.modules.has_key(modNm):
|
|
theMod = sys.modules[modNm] # Nestle in containing package
|
|
pkg = theMod # Set as parent for next in sequence.
|
|
elif sys.stub_modules.has_key(modNm):
|
|
# Similar to above...
|
|
theMod = sys.stub_modules[modNm]
|
|
pkg = theMod
|
|
else:
|
|
theMod = procure_module(modNm)
|
|
stack.mod(modNm, theMod)
|
|
# *** ??? Should we be using 'path' instead of modF.name? If not,
|
|
# should we get rid of the 'path' return val?
|
|
set_mod_attrs(theMod, normalize_pathname(modF.name),
|
|
pkg, None, ty[2])
|
|
if ty[2] == PY_PACKAGE:
|
|
# Register as a stub:
|
|
register_module(theMod, modNm, 1)
|
|
pkg = theMod
|
|
nesting.append((theMod.__name__,relNm))
|
|
|
|
return nesting
|
|
|
|
def register_module(theMod, name, stub=0):
|
|
"""Properly register MODULE, NAME, and optional STUB qualification."""
|
|
|
|
if stub:
|
|
sys.stub_modules[name] = theMod
|
|
else:
|
|
sys.modules[name] = theMod
|
|
if sys.stub_modules.has_key(name):
|
|
del sys.stub_modules[name]
|
|
|
|
def find_mod_registration(name):
|
|
"""Find module named NAME sys.modules, .stub_modules, or on the stack."""
|
|
if sys.stub_modules.has_key(name):
|
|
return sys.stub_modules[name] # ==>
|
|
elif sys.modules.has_key(name):
|
|
return sys.modules[name] # ==>
|
|
else:
|
|
if stack.in_process(name):
|
|
it = stack.mod(name)
|
|
if it:
|
|
return it # ==>
|
|
else:
|
|
raise ValueError, '%s %s in %s or %s' % (name, # ==X
|
|
'not registered',
|
|
'sys.modules',
|
|
'sys.stub_modules')
|
|
|
|
def get_mod_attrs(theMod, which = None):
|
|
"""Get MODULE object's path, containing-package, and designated path.
|
|
|
|
Virtual attribute USE_PATH is derived from PKG_PATH, MOD_PATHNAME,
|
|
and/or sys.path, depending on the module type and settings."""
|
|
it = theMod.__dict__[IMP_ADMIN]
|
|
if which:
|
|
# Load path is either the explicitly designated load path for the
|
|
# package, or else the directory in which it resides:
|
|
if which == USE_PATH:
|
|
if it[PKG_PATH]:
|
|
# Return explicitly designated path:
|
|
return it[PKG_PATH] # ==>
|
|
if it[MOD_PATHNAME]:
|
|
if it[MOD_TYPE] == PY_PACKAGE:
|
|
# Return the package's directory path:
|
|
return [it[MOD_PATHNAME]] # ==>
|
|
else:
|
|
# Return the directory where the module resides:
|
|
return [os.path.split(it[MOD_PATHNAME])[0]] # ==>
|
|
# No explicitly designated path - use sys.path, eg for system
|
|
# modules, etc:
|
|
return sys.path
|
|
return it[which] # ==>
|
|
else:
|
|
return it # ==>
|
|
|
|
def set_mod_attrs(theMod, path, pkg, pkgPath, ty):
|
|
"""Register MOD import attrs PATH, PKG container, and PKGPATH, linking
|
|
the package container into the module along the way."""
|
|
theDict = theMod.__dict__
|
|
try:
|
|
# Get existing one, if any:
|
|
it = theDict[IMP_ADMIN]
|
|
except KeyError:
|
|
# None existing, gen a new one:
|
|
it = [None] * 4
|
|
for fld, val in ((MOD_PATHNAME, path), (MOD_PACKAGE, pkg),
|
|
(PKG_PATH, pkgPath), (MOD_TYPE, ty)):
|
|
if val:
|
|
it[fld] = val
|
|
|
|
theDict[IMP_ADMIN] = it
|
|
if pkg:
|
|
theDict[PKG_NM] = theDict[PKG_SHORT_NM] = pkg
|
|
return it # ==>
|
|
|
|
def format_tb_msg(tb, recursive):
|
|
"""This should be in traceback.py, and traceback.print_tb() should use it
|
|
and traceback.extract_tb(), instead of print_tb() and extract_tb() having
|
|
so much redundant code!"""
|
|
tb_lines, formed = traceback.extract_tb(tb), ''
|
|
for line in tb_lines:
|
|
f, lno, nm, ln = line
|
|
if f[-1 * (len(__name__) + 3):] == __name__ + '.py':
|
|
# Skip newimp notices - agregious hack, justified only by the fact
|
|
# that this functionality will be properly doable in new impending
|
|
# exception mechanism:
|
|
continue
|
|
formed = formed + ('\n%s File "%s", line %d, in %s%s' %
|
|
(((recursive and '*') or ' '),
|
|
f, lno, nm,
|
|
((ln and '\n ' + string.strip(ln)) or '')))
|
|
return formed
|
|
|
|
def import_trail_msg(msg, tb, modNm):
|
|
"""Doctor an error message to include the path of the current import, and
|
|
a sign that it is a circular import, if so."""
|
|
return (msg +
|
|
format_tb_msg(tb,
|
|
(stack.looped(modNm) and stack.in_process(modNm))))
|
|
|
|
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, 3)
|
|
return None
|
|
mtime = os.stat(sourcePath)[8]
|
|
|
|
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 # ==X
|
|
|
|
# Ok, we have a valid compilation.
|
|
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 # ==>
|
|
|
|
|
|
got_suffixes = None
|
|
got_suffixes_dict = {}
|
|
def get_suffixes(ty=None):
|
|
"""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, got_suffixes_dict
|
|
|
|
if not got_suffixes:
|
|
# Ensure that the .pyc suffix precedes the .py:
|
|
got_suffixes = [('', 'r', PY_PACKAGE)]
|
|
got_suffixes_dict[PY_PACKAGE] = ('', 'r', PY_PACKAGE)
|
|
py = pyc = None
|
|
for suff in imp.get_suffixes():
|
|
got_suffixes_dict[suff[2]] = suff
|
|
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)
|
|
if ty:
|
|
return got_suffixes_dict[ty] # ==>
|
|
else:
|
|
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
|
|
|
|
|
|
def normalize_pathname(path):
|
|
"""Given PATHNAME, return an absolute pathname relative to cwd, reducing
|
|
unnecessary components where convenient (eg, on Unix)."""
|
|
|
|
# We do a lot more when we have posix-style paths, eg os.sep == '/'.
|
|
|
|
if os.sep != '/':
|
|
return os.path.join(os.getcwd, path) # ==>
|
|
|
|
outwards, inwards = 0, []
|
|
for nm in string.splitfields(path, os.sep):
|
|
if nm != os.curdir:
|
|
if nm == os.pardir:
|
|
# Translate parent-dir entries to outward notches:
|
|
if inwards:
|
|
# Pop a containing inwards:
|
|
del inwards[-1]
|
|
else:
|
|
# Register leading outward notches:
|
|
outwards = outwards + 1
|
|
else:
|
|
inwards.append(nm)
|
|
inwards = string.joinfields(inwards, os.sep)
|
|
|
|
if (not inwards) or (inwards[0] != os.sep):
|
|
# Relative path - join with current working directory, (ascending
|
|
# outwards to account for leading parent-dir components):
|
|
cwd = os.getcwd()
|
|
if outwards:
|
|
cwd = string.splitfields(cwd, os.sep)
|
|
cwd = string.joinfields(cwd[:len(cwd) - outwards], os.sep)
|
|
if inwards:
|
|
return os.path.join(cwd, inwards) # ==>
|
|
else:
|
|
return cwd # ==>
|
|
else:
|
|
return inwards # ==>
|
|
|
|
|
|
# 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=2):
|
|
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' % # ==X
|
|
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: eradicating %s)' % name
|
|
os.system('rm -r ' + name)
|
|
else:
|
|
raise IOError, "can't remove non-existent " + name # ==X
|
|
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, leaveHiers=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 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)
|
|
|
|
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, 1
|
|
__builtin__.__import__ = import_module # Install new version
|
|
|
|
if testMods: # Clear out imports from previous tests
|
|
for m in testMods[:]:
|
|
unloadFull(m)
|
|
testMods.remove(m)
|
|
|
|
# ------
|
|
# Test 1
|
|
testExec("already imported module: %s" % sys.modules.keys()[0],
|
|
['import ' + sys.modules.keys()[0]],
|
|
vars(), newimp_globals)
|
|
no_sirree = 'no_sirree_does_not_exist'
|
|
# ------
|
|
# Test 2
|
|
testExec("non-existent module: %s" % no_sirree,
|
|
['try: import ' + no_sirree +
|
|
'\nexcept ImportError: pass'],
|
|
vars(), newimp_globals)
|
|
got = None
|
|
|
|
# ------
|
|
# Test 3
|
|
# Find a module that's not yet loaded, from a list of prospects:
|
|
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)'],
|
|
vars(), newimp_globals)
|
|
else:
|
|
testExec("not-yet loaded module: list exhausted, never mind",
|
|
[], vars(), newimp_globals)
|
|
|
|
# Now some package stuff.
|
|
|
|
# ------
|
|
# Test 4
|
|
# 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)",
|
|
"base = hier1.PKG_NM",
|
|
"exec 'import ' + base",
|
|
"testMods.append(base)"]
|
|
testExec("trivial package, with one module, a.py",
|
|
stmts, vars(), newimp_globals)
|
|
|
|
# ------
|
|
# Test 5
|
|
# Slightly less trivial package - reference to '__':
|
|
stmts = [("hier2 = TestDirHier({'ref.py': 'print \"Pkg __:\", __'})"),
|
|
"base = hier2.PKG_NM",
|
|
"hiers.append(hier2)",
|
|
"exec 'import ' + base",
|
|
"testMods.append(base)"]
|
|
testExec("trivial package, with module that has pkg shorthand ref",
|
|
stmts, vars(), newimp_globals)
|
|
|
|
# ------
|
|
# Test 6
|
|
# 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)",
|
|
"base = hier3.PKG_NM",
|
|
"hiers.append(hier3)",
|
|
"exec 'import ' + base",
|
|
"testMods.append(base)"]
|
|
testExec("Significantly nestled package:",
|
|
stmts, vars(), newimp_globals)
|
|
|
|
# ------
|
|
# Test 7
|
|
# Try an elaborate hierarchy which includes an __init__ master in one
|
|
# one portion, a ref across packages within the hierarchies, and an
|
|
# indirect recursive import which cannot be satisfied (and hence,
|
|
# prevents load of part of the hierarchy).
|
|
complexTemplate = {'mid':
|
|
{'prime':
|
|
{'__init__.py': 'import __.easy, __.nother',
|
|
'easy.py': 'print "easy.py:", __name__',
|
|
'nother.py': ('%s\n%s\n%s\n' %
|
|
('import __.easy',
|
|
'print "nother got __.easy"',
|
|
# __.__.awry should be found but
|
|
# should not load successfully,
|
|
# disrupting nother, but not easy
|
|
'import __.__.awry'))},
|
|
# continuing dict 'mid':
|
|
'awry':
|
|
{'__init__.py':
|
|
('%s\n%s' %
|
|
('print "got " + __name__',
|
|
'from __ import *')),
|
|
# This mutual recursion (b->a, a->d->b) should be
|
|
# ok, since a.py sets ax before recursing.
|
|
'a.py': 'ax = 1; from __.b import bx',
|
|
'b.py': 'bx = 1; from __.a import ax'}}}
|
|
stmts = ["hier5 = TestDirHier(complexTemplate)",
|
|
"base = hier5.PKG_NM",
|
|
"testMods.append(base)",
|
|
"hiers.append(hier5)",
|
|
"exec 'import %s.mid.prime' % base",
|
|
"print eval(base)", # Verify the base was bound
|
|
"testMods.append(base)"]
|
|
testExec("Elaborate, clean hierarchy",
|
|
stmts, vars(), newimp_globals)
|
|
|
|
# ------
|
|
# test 8
|
|
# Here we disrupt the mutual recursion in the mid.awry package, so the
|
|
# import should now fail.
|
|
complexTemplate['mid']['awry']['a.py'] = 'from __.b import bx; ax = 1'
|
|
complexTemplate['mid']['awry']['b.py'] = 'from __.a import ax; bx = 1'
|
|
stmts = ["hier6 = TestDirHier(complexTemplate)",
|
|
"base = hier6.PKG_NM",
|
|
"testMods.append(base)",
|
|
"hiers.append(hier6)",
|
|
"work = ('import %s.mid.prime' % base)",
|
|
("try: exec work" +
|
|
"\nexcept ImportError: print ' -- import failed, as ought'" +
|
|
"\nelse: raise SystemError, sys.exc_value"),
|
|
"testMods.append(base)"]
|
|
testExec("Elaborate hier w/ deliberately flawed import recursion",
|
|
stmts, vars(), newimp_globals)
|
|
|
|
sys.exc_traceback = None # Signify clean conclusion.
|
|
|
|
finally:
|
|
skipToTest = 0
|
|
atTest = 1
|
|
sys.path = wasPath
|
|
VERBOSE = wasVerbose
|
|
if wasImport: # Resurrect prior routine
|
|
__builtin__.__import__ = wasImport
|
|
else:
|
|
del __builtin__.__import__
|
|
if leaveHiers:
|
|
print 'Cleanup inhibited'
|
|
else:
|
|
if sys.exc_traceback:
|
|
print ' ** Import test FAILURE... cleanup.'
|
|
else:
|
|
print ' Import test SUCCESS... cleanup'
|
|
for h in hiers: h.remove(); del h # Dispose of test directories
|
|
|
|
init()
|
|
|
|
if __name__ == '__main__':
|
|
test()
|