[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