[Python-checkins] r52717 - sandbox/trunk/import_in_py/importer.py sandbox/trunk/import_in_py/test_importer.py
brett.cannon
python-checkins at python.org
Fri Nov 10 01:49:07 CET 2006
Author: brett.cannon
Date: Fri Nov 10 01:49:07 2006
New Revision: 52717
Modified:
sandbox/trunk/import_in_py/importer.py
sandbox/trunk/import_in_py/test_importer.py
Log:
Implement classic relative name imports. Still need to flush out integration
tests to verify implementation works properly.
Modified: sandbox/trunk/import_in_py/importer.py
==============================================================================
--- sandbox/trunk/import_in_py/importer.py (original)
+++ sandbox/trunk/import_in_py/importer.py Fri Nov 10 01:49:07 2006
@@ -622,8 +622,8 @@
else:
raise ImportError("%s not found on sys.path" % name)
- def import_(self, name, path=None):
- """Import a module."""
+ def import_module(self, name, path=None):
+ """Import the specified module with no handling of parent modules."""
try:
# Attempt to get a cached version of the module from sys.modules.
return sys.modules[name]
@@ -640,28 +640,9 @@
# entry in sys.modules.
return loader.load_module(name, path)
- def __call__(self, name, globals={}, locals={}, fromlist=[], level=-1):
- """Import a module after resolving relative/absolute import issues.
-
- 'name' is the dotted name of the module/package to import. 'globals'and
- 'locals' are the global and local namespace dictionaries of the caller
- (only 'globals' is used to introspect the __path__ attribute of the calling
- module). fromlist lists any specific objects that are to eventually be put
- into the namespace (e.g., ``from for.bar import baz`` would have baz in the
- fromlist). 'level' is set to -1 if both relative and absolute imports are
- supported, 0 if only for absolute, and positive values represent the number
- of levels up from the directory the calling module is in.
-
- When 'name' is a dotted name, there are two different situations to
- consider. One is when the fromlist is empty. In this situation the import
- imports and returns the name up to the first dot. All subsequent names are
- imported but set as attributes as needed. When fromlist is not empty then
- the module represented by the full dotted name is returned.
-
- """
- # XXX Check for a relative import; if it is one make it absolute to try to import that,
- # otherwise import as a top-level module.
-
+ def import_full_module(self, name):
+ """Import a module along with its parent modules and set into
+ sys.modules."""
# Import the module (importing its parent modules first).
name_parts = name.split('.')
current_name_parts = []
@@ -680,10 +661,76 @@
path_list = parent_module.__path__
except AttributeError:
pass
- module = self.import_(current_name, path_list)
+ module = self.import_module(current_name, path_list)
if parent_module:
setattr(parent_module, name_part, module)
parent_module = module
+
+ def classic_resolve_name(self, name, caller_name, caller_is_package):
+ """Return the absolute name of the module specified in 'name' as based
+ on the name of the caller and whether it is a package."""
+ if caller_is_package:
+ base_name = caller_name
+ else:
+ if '.' not in caller_name:
+ # Importing in a top-level module, so name is already absolute.
+ return name
+ else:
+ base_name = caller_name.rsplit('.', 1)[0]
+ return base_name + '.' + name
+
+ def __call__(self, name, globals={}, locals={}, fromlist=[], level=-1):
+ """Import a module.
+
+ The 'name' argument is the name of the module to be imported (e.g.,
+ 'foo' in ``import foo`` or ``from foo import ...``).
+
+ 'globals' and
+ 'locals' are the global and local namespace dictionaries of the module
+ where the import statement appears. 'globals' is used to introspect
+ the __path__ and __name__ attributes of the module making the call.
+ 'local's is ignored.
+
+ 'fromlist' lists any specific objects that are to eventually be put
+ into the namespace (e.g., ``from for.bar import baz`` would have 'baz'
+ in the fromlist). When 'name' is a dotted name, there are two
+ different situations to consider. One is when the fromlist is empty.
+ In this situation the import statement imports and returns the name up
+ to the first dot. All subsequent names are imported but set as
+ attributes as needed on parent modules. When fromlist is not empty
+ then the module represented by the full dotted name is returned.
+
+ 'level' represents how to handle possible relative imports. If 'level'
+ is set to -1 then the module name could be either relative or absolute.
+ A value of 0 is for absolute module names., Any positive value
+ represents the number of dots listed in the relative import statement
+ (e.g. has a value of 2 for ``from .. import foo``).
+
+ """
+ if level and '__name__' in globals:
+ caller_name = globals['__name__']
+ is_pkg = True if '__path__' in globals else False
+ # Handle the classic style of import: relative first, then
+ # absolute.
+ if level == -1:
+ relative_name = self.classic_resolve_name(name, caller_name,
+ is_pkg)
+ try:
+ # Try a relative import first.
+ self.import_full_module(relative_name)
+ # XXX Probably need to do something about values of None
+ # being in sys.modules here.
+ except ImportError:
+ # If the relative import fails, try an absolute import.
+ self.import_full_module(name)
+ # If using absolute imports with a relative path, only attempt with
+ # the fully-resolved module name.
+ else:
+ relative_name = self.resolve_name(name, caller_name, is_pkg,
+ level)
+ self.import_full_module(relative_name)
+ else:
+ self.import_full_module(name)
# When fromlist is not specified, return the root module (i.e., module
# up to first dot).
if not fromlist:
Modified: sandbox/trunk/import_in_py/test_importer.py
==============================================================================
--- sandbox/trunk/import_in_py/test_importer.py (original)
+++ sandbox/trunk/import_in_py/test_importer.py Fri Nov 10 01:49:07 2006
@@ -128,7 +128,14 @@
class TestPyPycFiles(unittest.TestCase):
- """Base class to help in generating a fresh source and bytecode file."""
+ """Base class to help in generating a fresh source and bytecode file.
+
+ Structure of created files:
+ * directory/
+ + py_path [module_name + py_ext]
+ + pyc_path [module_name +pyc_ext]
+
+ """
def create_files(self, faked_names=True):
"""Generate the path to a temporary file to test with.
@@ -187,10 +194,27 @@
class TestPyPycPackages(TestPyPycFiles):
- """Create a testing package."""
+ """Create a testing package.
+
+ Structure of created files (on top of files created in superclasses):
+ * self.directory/
+ + top_level_module_path [top_level_module_name + py_ext]
+ + pkg_path/ [pkg_name]
+ - pkg_init_path [ '__init__' + py_ext]
+ - pkg_module_path [module_name + py_ext]
+ - sub_pkg_path/ [sub_pkg_name]
+ * sub_pkg_init_path ['__init__' + py_ext]
+ * sub_pkg_module_path [module_name + py_ext]
+
+ """
def create_files(self, faked_names=True):
TestPyPycFiles.create_files(self, faked_names)
+ self.top_level_module_name = 'top_level_' + self.module_name
+ self.top_level_module_path = os.path.join(self.directory,
+ self.top_level_module_name+self.py_ext)
+ with open(self.top_level_module_path, 'w') as top_level_file:
+ top_level_file.write(self.source)
if faked_names:
self.pkg_name = '<test_pkg>'
else:
@@ -227,6 +251,7 @@
def remove_files(self):
TestPyPycFiles.remove_files(self)
+ os.remove(self.top_level_module_path)
shutil.rmtree(self.pkg_path)
def verify_package(self, module, actual_name=None):
@@ -667,7 +692,7 @@
def test_sys_module_return(self):
# A module found in sys.modules should be returned immediately.
sys.modules[self.parent_name] = self.parent_module
- module = self.importer.import_(self.parent_name)
+ module = self.importer.import_module(self.parent_name)
self.failUnless(module is self.parent_module)
def test_parent_missing(self):
@@ -719,6 +744,25 @@
module = self.importer(self.full_child_name, fromlist=[test_attr_name])
self.failUnless(module is self.child_module)
+ def test_classic_relative_import_in_package(self):
+ # Importing from within a package's __init__ file should lead to a
+ # resolved import name of the package name tacked on to the name of the
+ # module being imported.
+ resolved_name = self.importer.classic_resolve_name(self.child_name,
+ self.parent_name,
+ True)
+ self.failUnlessEqual(resolved_name, self.full_child_name)
+
+ def test_classic_relative_import_in_module(self):
+ # Importing within a module in a package should lead to the importer's
+ # module name being removed and replaced with the name of what is to be
+ # imported.
+ calling_from = self.parent_name + '.' + '<calling from>'
+ resolved_name = self.importer.classic_resolve_name(self.child_name,
+ calling_from,
+ False)
+ self.failUnlessEqual(resolved_name, self.full_child_name)
+
class ImportMetaPathTests(ImportHelper):
@@ -747,7 +791,7 @@
sys.meta_path = [pass_importer]
succeed_importer = mock_importer.SucceedImporter()
importer_ = importer.Import(extended_meta_path=(succeed_importer,))
- module = importer_.import_('sys')
+ module = importer_.import_module('sys')
for meta_importer in (pass_importer, succeed_importer):
self.failUnlessEqual(meta_importer.find_request, ('sys', None))
self.failUnless(module in succeed_importer.loaded_modules)
@@ -785,7 +829,7 @@
sys.meta_path = []
sys.path = ['<succeed>']
sys.path_importer_cache['<succeed>'] = None
- module = importer_.import_(module_name)
+ module = importer_.import_module(module_name)
self.failUnlessEqual(succeed_importer.find_request,
(module_name, None))
self.failUnless(module in succeed_importer.loaded_modules)
@@ -799,7 +843,7 @@
pass_importer = mock_importer.PassImporter.set_on_sys_path()
succeed_importer = mock_importer.SucceedImporter.set_on_sys_path()
sys_path = (pass_importer, succeed_importer)
- module = importer_.import_(module_name)
+ module = importer_.import_module(module_name)
for entry in sys_path:
self.failUnlessEqual(entry.find_request, (module_name, None))
self.failUnless(module in succeed_importer.loaded_modules)
@@ -858,7 +902,7 @@
self.clear_sys_modules(module_name)
del sys.path_importer_cache[search_paths[0]]
succeed_importer.path_entries = []
- self.importer.import_(module_name, search_paths)
+ self.importer.import_module(module_name, search_paths)
self.failUnless(search_paths[0] in succeed_importer.path_entries)
@@ -980,6 +1024,26 @@
module = self.import_(self.sub_pkg_module_name)
self.verify_package(module, self.sub_pkg_module_name)
+ def XXX_test_classic_relative_import_in_package_init(self):
+ # Importing within a package's __init__ file using a relative name
+ # should work properly.
+ pass
+
+ def XXX_test_classic_relative_import_in_module(self):
+ # Importing using a relative name in a module in a package should work.
+ pass
+
+ def test_absolute_name_in_classic_relative_context(self):
+ # Importing a module that happens to be ambiguous in terms of being
+ # relative or absolute, but only exists in an absolute name context,
+ # should work.
+ package_module_globals = {'__name__':self.pkg_module_name}
+ module = self.import_(self.top_level_module_name,
+ package_module_globals, level=-1)
+ self.verify_package(module)
+ self.failUnlessEqual(module.__name__, self.top_level_module_name)
+
+
def test_main():
test_classes = [cls for cls in globals().itervalues()
More information about the Python-checkins
mailing list