diff options
Diffstat (limited to 'Lib/ni.py')
-rw-r--r-- | Lib/ni.py | 390 |
1 files changed, 390 insertions, 0 deletions
diff --git a/Lib/ni.py b/Lib/ni.py new file mode 100644 index 0000000..70b1c22 --- /dev/null +++ b/Lib/ni.py @@ -0,0 +1,390 @@ +"""New import scheme with package support. + +A Package is a module that can contain other modules. Packages can be +nested. Package introduce dotted names for modules, like P.Q.M, which +could correspond to a file P/Q/M.py found somewhere on sys.path. It +is possible to import a package itself, though this makes little sense +unless the package contains a module called __init__. + +A package has two variables that control the namespace used for +packages and modules, both initialized to sensible defaults the first +time the package is referenced. + +(1) A package's *module search path*, contained in the per-package +variable __path__, defines a list of *directories* where submodules or +subpackages of the package are searched. It is initialized to the +directory containing the package. Setting this variable to None makes +the module search path default to sys.path (this is not quite the same +as setting it to sys.path, since the latter won't track later +assignments to sys.path). + +(2) A package's *import domain*, contained in the per-package variable +__domain__, defines a list of *packages* that are searched (using +their respective module search paths) to satisfy imports. It is +initialized to the list cosisting of the package itself, its parent +package, its parent's parent, and so on, ending with the root package +(the nameless package containing all top-level packages and modules, +whose module search path is None, implying sys.path). + +The default domain implements a search algorithm called "expanding +search". An alternative search algorithm called "explicit search" +fixes the import search path to contain only the root package, +requiring the modules in the package to name all imported modules by +their full name. The convention of using '__' to refer to the current +package (both as a per-module variable and in module names) can be +used by packages using explicit search to refer to modules in the same +package; this combination is known as "explicit-relative search". + +The PackageImporter and PackageLoader classes together implement the +following policies: + +- There is a root package, whose name is ''. It cannot be imported + directly but may be referenced, e.g. by using '__' from a top-level + module. + +- In each module or package, the variable '__' contains a reference to + the parent package; in the root package, '__' points to itself. + +- In the name for imported modules (e.g. M in "import M" or "from M + import ..."), a leading '__' refers to the current package (i.e. + the package containing the current module); leading '__.__' and so + on refer to the current package's parent, and so on. The use of + '__' elsewhere in the module name is not supported. + +- Modules are searched using the "expanding search" algorithm by + virtue of the default value for __domain__. + +- If A.B.C is imported, A is searched using __domain__; then + subpackage B is searched in A using its __path__, and so on. + +- Built-in modules have priority: even if a file sys.py exists in a + package, "import sys" imports the built-in sys module. + +- The same holds for frozen modules, for better or for worse. + +- Submodules and subpackages are not automatically loaded when their + parent packages is loaded. + +- The construct "from package import *" is illegal. (It can still be + used to import names from a module.) + +- When "from package import module1, module2, ..." is used, those + modules are explicitly loaded. + +- When a package is loaded, if it has a submodule __init__, that + module is loaded. This is the place where required submodules can + be loaded, the __path__ variable extended, etc. The __init__ module + is loaded even if the package was loaded only in order to create a + stub for a sub-package: if "import P.Q.R" is the first reference to + P, and P has a submodule __init__, P.__init__ is loaded before P.Q + is even searched. + +Caveats: + +- It is possible to import a package that has no __init__ submodule; + this is not particularly useful but there may be useful applications + for it (e.g. to manipulate its search paths from the outside!). + +- There are no special provisions for os.chdir(). If you plan to use + os.chdir() before you have imported all your modules, it is better + not to have relative pathnames in sys.path. (This could actually be + fixed by changing the implementation of path_join() in the hook to + absolutize paths.) + +- Packages and modules are introduced in sys.modules as soon as their + loading is started. When the loading is terminated by an exception, + the sys.modules entries remain around. + +- There are no special measures to support mutually recursive modules, + but it will work under the same conditions where it works in the + flat module space system. + +- Sometimes dummy entries (whose value is None) are entered in + sys.modules, to indicate that a particular module does not exist -- + this is done to speed up the expanding search algorithm when a + module residing at a higher level is repeatedly imported (Python + promises that importing a previously imported module is cheap!) + +- Although dynamically loaded extensions are allowed inside packages, + the current implementation (hardcoded in the interpreter) of their + initialization may cause problems if an extension invokes the + interpreter during its initialization. + +- reload() may find another version of the module only if it occurs on + the package search path. Thus, it keeps the connection to the + package to which the module belongs, but may find a different file. + +XXX Need to have an explicit name for '', e.g. '__root__'. + +""" + + +import imp +import string +import sys +import __builtin__ + +import ihooks +from ihooks import ModuleLoader, ModuleImporter + + +class PackageLoader(ModuleLoader): + + """A subclass of ModuleLoader with package support. + + find_module_in_dir() will succeed if there's a subdirectory with + the given name; load_module() will create a stub for a package and + load its __init__ module if it exists. + + """ + + def find_module_in_dir(self, name, dir): + if dir is not None: + dirname = self.hooks.path_join(dir, name) + if self.hooks.path_isdir(dirname): + return None, dirname, ('', '', 'PACKAGE') + return ModuleLoader.find_module_in_dir(self, name, dir) + + def load_module(self, name, stuff): + file, filename, info = stuff + suff, mode, type = info + if type == 'PACKAGE': + return self.load_package(name, stuff) + if sys.modules.has_key(name): + m = sys.modules[name] + else: + sys.modules[name] = m = imp.new_module(name) + self.set_parent(m) + if type == imp.C_EXTENSION and '.' in name: + return self.load_dynamic(name, stuff) + else: + return ModuleLoader.load_module(self, name, stuff) + + def load_dynamic(self, name, stuff): + file, filename, (suff, mode, type) = stuff + # Hack around restriction in imp.load_dynamic() + i = string.rfind(name, '.') + tail = name[i+1:] + if sys.modules.has_key(tail): + save = sys.modules[tail] + else: + save = None + sys.modules[tail] = imp.new_module(name) + try: + m = imp.load_dynamic(tail, filename, file) + finally: + if save: + sys.modules[tail] = save + else: + del sys.modules[tail] + sys.modules[name] = m + return m + + def load_package(self, name, stuff): + file, filename, info = stuff + if sys.modules.has_key(name): + package = sys.modules[name] + else: + sys.modules[name] = package = imp.new_module(name) + package.__path__ = [filename] + self.init_package(package) + return package + + def init_package(self, package): + self.set_parent(package) + self.set_domain(package) + self.call_init_module(package) + + def set_parent(self, m): + name = m.__name__ + if '.' in name: + name = name[:string.rfind(name, '.')] + else: + name = '' + m.__ = sys.modules[name] + + def set_domain(self, package): + name = package.__name__ + package.__domain__ = domain = [name] + while '.' in name: + name = name[:string.rfind(name, '.')] + domain.append(name) + if name: + domain.append('') + + def call_init_module(self, package): + stuff = self.find_module('__init__', package.__path__) + if stuff: + m = self.load_module(package.__name__ + '.__init__', stuff) + package.__init__ = m + + +class PackageImporter(ModuleImporter): + + """Importer that understands packages and '__'.""" + + def __init__(self, loader = None, verbose = 0): + ModuleImporter.__init__(self, + loader or PackageLoader(None, verbose), verbose) + + def import_module(self, name, globals={}, locals={}, fromlist=[]): + if globals.has_key('__'): + package = globals['__'] + else: + # No calling context, assume in root package + package = sys.modules[''] + if name[:3] in ('__.', '__'): + p = package + name = name[3:] + while name[:3] in ('__.', '__'): + p = package.__ + name = name[3:] + if not name: + return self.finish(package, p, '', fromlist) + if '.' in name: + i = string.find(name, '.') + name, tail = name[:i], name[i:] + else: + tail = '' + mname = p.__name__ and p.__name__+'.'+name or name + m = self.get1(mname) + return self.finish(package, m, tail, fromlist) + if '.' in name: + i = string.find(name, '.') + name, tail = name[:i], name[i:] + else: + tail = '' + for pname in package.__domain__: + mname = pname and pname+'.'+name or name + m = self.get0(mname) + if m: break + else: + raise ImportError, "No such module %s" % name + return self.finish(m, m, tail, fromlist) + + def finish(self, module, m, tail, fromlist): + # Got ....A; now get ....A.B.C.D + yname = m.__name__ + if tail and sys.modules.has_key(yname + tail): # Fast path + yname, tail = yname + tail, '' + m = self.get1(yname) + while tail: + i = string.find(tail, '.', 1) + if i > 0: + head, tail = tail[:i], tail[i:] + else: + head, tail = tail, '' + yname = yname + head + m = self.get1(yname) + + # Got ....A.B.C.D; now finalize things depending on fromlist + if not fromlist: + return module + if '__' in fromlist: + raise ImportError, "Can't import __ from anywhere" + if not hasattr(m, '__path__'): return m + if '*' in fromlist: + raise ImportError, "Can't import * from a package" + for f in fromlist: + if hasattr(m, f): continue + fname = yname + '.' + f + self.get1(fname) + return m + + def get1(self, name): + m = self.get(name) + if not m: + raise ImportError, "No module named %s" % name + return m + + def get0(self, name): + m = self.get(name) + if not m: + sys.modules[name] = None + return m + + def get(self, name): + # Internal routine to get or load a module when its parent exists + if sys.modules.has_key(name): + return sys.modules[name] + if '.' in name: + i = string.rfind(name, '.') + head, tail = name[:i], name[i+1:] + else: + head, tail = '', name + path = sys.modules[head].__path__ + stuff = self.loader.find_module(tail, path) + if not stuff: + return None + sys.modules[name] = m = self.loader.load_module(name, stuff) + if head: + setattr(sys.modules[head], tail, m) + return m + + def reload(self, module): + name = module.__name__ + if '.' in name: + i = string.rfind(name, '.') + head, tail = name[:i], name[i+1:] + path = sys.modules[head].__path__ + else: + tail = name + path = sys.modules[''].__path__ + stuff = self.loader.find_module(tail, path) + if not stuff: + raise ImportError, "No module named %s" % name + return self.loader.load_module(name, stuff) + + def unload(self, module): + if hasattr(module, '__path__'): + raise ImportError, "don't know how to unload packages yet" + PackageImporter.unload(self, module) + + def install(self): + if not sys.modules.has_key(''): + sys.modules[''] = package = imp.new_module('') + package.__path__ = None + self.loader.init_package(package) + for m in sys.modules.values(): + if not m: continue + if not hasattr(m, '__'): + self.loader.set_parent(m) + ModuleImporter.install(self) + + +def install(v = 0): + ihooks.install(PackageImporter(None, v)) + +def uninstall(): + ihooks.uninstall() + +def ni(v = 0): + install(v) + +def no(): + uninstall() + +def test(): + import pdb + try: + testproper() + except: + sys.last_type, sys.last_value, sys.last_traceback = ( + sys.exc_type, sys.exc_value, sys.exc_traceback) + print + print sys.last_type, ':', sys.last_value + print + pdb.pm() + +def testproper(): + install(1) + try: + import mactest + print dir(mactest) + raw_input('OK?') + finally: + uninstall() + + +if __name__ == '__main__': + test() |