[Python-checkins] r52594 - sandbox/trunk/import_in_py/importer.py sandbox/trunk/import_in_py/mock_importer.py sandbox/trunk/import_in_py/test_importer.py

brett.cannon python-checkins at python.org
Thu Nov 2 22:43:18 CET 2006


Author: brett.cannon
Date: Thu Nov  2 22:43:17 2006
New Revision: 52594

Modified:
   sandbox/trunk/import_in_py/importer.py
   sandbox/trunk/import_in_py/mock_importer.py
   sandbox/trunk/import_in_py/test_importer.py
Log:
Add support in the Import class for handling modules contained within a
top-level package.

It is untested whether this will work for arbitrarily nested packages.  Also no
support yet in filesystem importer.  Still need to look at what __path__ is
used for when found in globals argument to __import__.


Modified: sandbox/trunk/import_in_py/importer.py
==============================================================================
--- sandbox/trunk/import_in_py/importer.py	(original)
+++ sandbox/trunk/import_in_py/importer.py	Thu Nov  2 22:43:17 2006
@@ -62,10 +62,18 @@
   this means that the usual path_hooks/path_importer_cache dance is done for
   entries in __path__ instead of sys.path (meta_path is still searched
   regardless of the value of __path__) [PEP 302].
+* Only meta_path entries have the 'path' argument for find_module and
+  load_module set.  path_importer_cache entries (which covers sys.path and
+  entries for __path__ for package) do not have 'path' set to anything (but
+  still have the full name of the module passed in)
+  [pep 302 and introspection(Python/import.c:1278)].
   
 Implementing
 ------------
 #. Importing module within a package.
+    + One level deep.
+    + Arbitrarily deep.
+    + Search __path__ and not sys.path.
 #. Importing within package.
     + Root package already imported.
     + Root package not imported yet.
@@ -533,7 +541,7 @@
             handlers = ExtensionFileHandler(), PyPycHandler()
             self.default_path_hook = FileSystemFactory(*handlers)
 
-    def search_meta_path(self, name):
+    def search_meta_path(self, name, path=None):
         """Check the importers on sys.meta_path for a loader along with the
         extended meta path sequence stored within this instance.
         
@@ -542,17 +550,17 @@
         
         """
         for entry in (tuple(sys.meta_path) + self.extended_meta_path):
-            loader = entry.find_module(name)
+            loader = entry.find_module(name, path)
             if loader:
                 return loader
         else:
-            raise ImportError("%s not found on meta path" % name)
+            raise ImportError("%s not found on sys.meta_path" % name)
 
     def sys_path_importer(self, path_entry):
-        """Return the importer for the entry on sys.path.
+        """Return the importer for the specified path.
         
-        If an entry on sys.path has None stored in sys.path_importer_cache
-        then use the default path hook.
+        If None is stored in sys.path_importer_cache then use the default path
+        hook.
         
         """
         try:
@@ -590,9 +598,14 @@
                     sys.path_importer_cache[path_entry] = None
                     raise ImportError("no importer found for %s" % path_entry)
 
-    def search_sys_path(self, name):
-        """Check sys.path for the module and return a loader if found."""
-        for entry in sys.path:
+    def search_std_path(self, name, path=None):
+        """Check sys.path or 'path' (depending if 'path' is set) for the
+        named module and return its loader."""
+        if path:
+            search_paths = path
+        else:
+            search_paths = sys.path
+        for entry in search_paths:
             try:
                 importer = self.sys_path_importer(entry)
             except ImportError:
@@ -603,7 +616,7 @@
         else:
             raise ImportError("%s not found on sys.path" % name)
             
-    def import_(self, name):
+    def import_(self, name, path=None):
         """Import a module."""
         try:
             # Attempt to get a cached version of the module from sys.modules.
@@ -612,14 +625,14 @@
             pass
         try:
             # Attempt to find a loader on sys.meta_path.
-            loader = self.search_meta_path(name)
+            loader = self.search_meta_path(name, path)
         except ImportError:
             # sys.meta_path search failed.  Attempt to find a loader on
             # sys.path.  If this fails then module cannot be found.
-            loader = self.search_sys_path(name)
+            loader = self.search_std_path(name, path)
         # A loader was found.  It is the loader's responsibility to have put an
         # entry in sys.modules.
-        return loader.load_module(name)
+        return loader.load_module(name, path)
 
     def __call__(self, name, globals={}, locals={}, fromlist=[], level=-1):
         """Import a module after resolving relative/absolute import issues.
@@ -646,9 +659,28 @@
         # 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.
 
-        # XXX Import submodules; short-circuits search if module is already
-        # in sys.modules.
-        module = self.import_(name)
+        # Import the module (importing its parent modules first).
+        name_parts = name.split('.')
+        current_name_parts = []
+        parent_module = None
+        path_list = None
+        # XXX Any perk to being optimistic and assuming parent modules were
+        # imported already, and thus search in the reverse order for the first module
+        # to be found?
+        for name_part in name_parts:
+            current_name_parts.append(name_part)
+            # Recreating name every time around the loop is sub-optimal, but
+            # does away with base case of a non-dotted name.
+            current_name = ".".join(current_name_parts)
+            if parent_module:
+                try:
+                    path_list = parent_module.__path__
+                except AttributeError:
+                    pass
+            module = self.import_(current_name, path_list)
+            if parent_module:
+                setattr(parent_module, current_name, module)
+            parent_module = module
         # When fromlist is not specified, return the root module (i.e., module
         # up to first dot).
         if not fromlist:
@@ -656,4 +688,4 @@
         # When fromlist is not empty, return the actual module specified in
         # the import.
         else:
-            return module
\ No newline at end of file
+            return sys.modules[name]
\ No newline at end of file

Modified: sandbox/trunk/import_in_py/mock_importer.py
==============================================================================
--- sandbox/trunk/import_in_py/mock_importer.py	(original)
+++ sandbox/trunk/import_in_py/mock_importer.py	Thu Nov  2 22:43:17 2006
@@ -215,8 +215,12 @@
     """Mock importer that always succeed by returning 'self'."""
 
     module = 42
+    
+    def __init__(self):
+        self.path_entries = []
 
     def __call__(self, path_entry):
+        self.path_entries.append(path_entry)
         return self
 
     def find_module(self, fullname, path=None):

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	Thu Nov  2 22:43:17 2006
@@ -540,7 +540,7 @@
         sys.path_hooks = self.old_path_hooks
         sys.path_importer_cache = self.old_path_importer_cache
 
-    def clear_sys_module(*modules):
+    def clear_sys_modules(*modules):
         for module in modules:
             try:
                 del sys.modules[module]
@@ -564,7 +564,12 @@
         module = self.importer.import_('<test>')
         del sys.modules[test_module]
         self.failUnlessEqual(module, test_module)
-    
+        
+    def test_parent_missing(self):
+        # An import should fail if a parent module cannot be found.
+        sys.modules['a.b'] = 'a.b'
+        self.failUnlessRaises(ImportError, self.importer, 'a.b')
+
     def test_empty_fromlist(self):
         # An empty fromlist means that the root module is returned.
         # XXX
@@ -583,7 +588,7 @@
     def test_search_meta_path(self):
         # Test search method of sys.meta_path.
         # Should raise ImportError on error.
-        self.clear_sys_module('sys')
+        self.clear_sys_modules('sys')
         self.failUnlessRaises(ImportError, self.importer.search_meta_path,
                                 'sys')
         # Verify call order.
@@ -598,7 +603,7 @@
     def test_extended_meta_path(self):
         # Default meta_path entries set during initialization should be
         # queried after sys.meta_path.
-        self.clear_sys_module('sys')
+        self.clear_sys_modules('sys')
         pass_importer = mock_importer.PassImporter()
         sys.meta_path = [pass_importer]
         succeed_importer = mock_importer.SucceedImporter()
@@ -607,57 +612,78 @@
         for meta_importer in (pass_importer, succeed_importer):
             self.failUnlessEqual(meta_importer.find_request, ('sys', None))
         self.failUnless(module is mock_importer.SucceedImporter.module)
+        
+    def test_parent_path(self):
+        # If a parent module has __path__ defined it should be passed as an
+        # argument during importing.
+        test_path = ['<test path>']
+        pkg_module = imp.new_module('_test_pkg')
+        pkg_module.__path__ = test_path
+        sys.modules['_test_pkg'] = pkg_module
+        succeed_importer = mock_importer.SucceedImporter()
+        sys.meta_path.append(succeed_importer)
+        module_name = '_test_pkg.module'
+        lookup_args = (module_name, test_path)
+        self.importer(module_name)
+        self.failUnless(module_name in sys.modules)
+        self.failUnlessEqual(succeed_importer.find_request, lookup_args)
+        self.failUnlessEqual(succeed_importer.load_request, lookup_args)
 
 
-class ImportSysPathTests(ImportHelper):
+class ImportStdPathTests(ImportHelper):
     
     """Test sys.path usage."""
 
     def test_default_importer_factory(self):
         # Make sure that the object passed in during initialization is used
         # when sys.path_importer_cache has a value of None.
-        self.clear_sys_module('sys')
+        module_name = '<dummy>'
+        self.clear_sys_modules(module_name)
         succeed_importer = mock_importer.SucceedImporter()
         importer_ = importer.Import(succeed_importer, tuple())
         sys.meta_path = []
         sys.path = ['<succeed>']
         sys.path_importer_cache['<succeed>'] = None
-        module = importer_.import_('sys')
-        self.failUnlessEqual(succeed_importer.find_request, ('sys', None))
+        module = importer_.import_(module_name)
+        self.failUnlessEqual(succeed_importer.find_request,
+                                (module_name, None))
         self.failUnless(module is mock_importer.SucceedImporter.module)
 
-    def test_search_sys_path(self):
+    def test_search_std_path(self):
         # Test sys.path searching for a loader.
-        self.clear_sys_module('sys')
+        module_name = '<dummy>'
+        self.clear_sys_modules(module_name)
         importer_ = importer.Import(extended_meta_path=())
         sys.path = []
         sys_path = (mock_importer.PassImporter.set_on_sys_path(),
                     mock_importer.SucceedImporter.set_on_sys_path())
-        module = importer_.import_('token')
+        module = importer_.import_(module_name)
         for entry in sys_path:
-            self.failUnlessEqual(entry.find_request, ('token', None))
+            self.failUnlessEqual(entry.find_request, (module_name, None))
         self.failUnless(module is mock_importer.SucceedImporter.module)
 
     def test_importer_cache_preexisting(self):
         # A pre-existing importer should be returned if it exists in
         # sys.path_importer_cache.
-        self.clear_sys_module('sys')
+        module_name = '<dummy>'
+        self.clear_sys_modules(module_name)
         sys.path = []
         succeed_importer = mock_importer.SucceedImporter.set_on_sys_path()
-        loader = self.importer.search_sys_path('sys')
+        loader = self.importer.search_std_path(module_name)
         self.failUnless(loader is succeed_importer)
         
     def test_importer_cache_from_path_hooks(self):
         # If an entry does not exist for a sys.path entry in the importer cache
         # then sys.path_hooks should be searched and if one is found then cache
         # it.
-        self.clear_sys_module('sys')
+        module_name = '<dummy>'
+        self.clear_sys_modules(module_name)
         path_entry = '<succeed>'
         succeed_importer = mock_importer.SucceedImporter()
         sys.path = [path_entry]
         sys.path_importer_cache.clear()
         sys.path_hooks = [succeed_importer]
-        loader = self.importer.search_sys_path('sys')
+        loader = self.importer.search_std_path(module_name)
         self.failUnless(loader is succeed_importer)
         self.failUnless(sys.path_importer_cache[path_entry] is
                         succeed_importer)
@@ -665,14 +691,34 @@
     def test_importer_cache_no_path_hooks(self):
         # If an entry does not exist for a sys.path entry in the importer cache
         # and sys.path_hooks has nothing for the entry, None should be set.
-        self.clear_sys_module('sys')
+        module_name = '<dummy>'
+        self.clear_sys_modules(module_name)
         path_entry = '<test>'
         sys.path = [path_entry]
         sys.path_hooks = []
         sys.path_importer_cache.clear()
-        self.failUnlessRaises(ImportError, self.importer.search_sys_path, 'sys')
+        self.failUnlessRaises(ImportError, self.importer.search_std_path,
+                                module_name)
         self.failUnless(sys.path_importer_cache[path_entry] is None)
         
+    def test_searching_package_path(self):
+        # If importing in a package then search path is the package's __path__
+        # value; otherwise it is sys.path.
+        succeed_importer = mock_importer.SucceedImporter()
+        sys.path_hooks.append(succeed_importer)
+        search_paths = ['test path']
+        module_name = '<pkg>.<dummy>'
+        loader = self.importer.search_std_path(module_name, search_paths)
+        self.failUnless(loader is succeed_importer)
+        self.failUnless(search_paths[0] in succeed_importer.path_entries)
+        self.failUnlessEqual(succeed_importer.find_request,
+                                (module_name, None))
+        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.failUnless(search_paths[0] in succeed_importer.path_entries)
+        
         
 class IntegrationTests(unittest.TestCase):
     
@@ -693,7 +739,7 @@
         # built-in, frozen, extension, .pyc, and .py files being imported if
         # desired.
         sys.path_importer_cache = dict((entry, None) for entry in sys.path)
-        self.clear_sys_module('sys', '__hello__', 'time', 'token')
+        self.clear_sys_modules('sys', '__hello__', 'time', 'token')
         # Restore sys.path for 'time' import.
         sys.path = self.old_sys_path
         import_ = importer.Import()


More information about the Python-checkins mailing list