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

brett.cannon python-checkins at python.org
Thu Oct 19 00:48:53 CEST 2006


Author: brett.cannon
Date: Thu Oct 19 00:48:52 2006
New Revision: 52385

Modified:
   sandbox/trunk/import_in_py/importer.py
   sandbox/trunk/import_in_py/test_importer.py
Log:
Complete initial tests for an __import__ replacement.  Packages are not
supported yet, but backwards-compatibility for the search order and entries of
None in sys.path_importer_cache.


Modified: sandbox/trunk/import_in_py/importer.py
==============================================================================
--- sandbox/trunk/import_in_py/importer.py	(original)
+++ sandbox/trunk/import_in_py/importer.py	Thu Oct 19 00:48:52 2006
@@ -16,15 +16,20 @@
 * PEP 328: Imports: Multi-line and Absolute/Relative
     http://www.python.org/dev/peps/pep-0328
 
-Clarifications for PEP 302:
-    * Raise ImportError when load_module() fails to load a module without
-      raising an exception.
-    * Is module returned by load_module() actually used for anything?
-
-Things to be exposed at the Python level:
-    * Python/marshal.c:r_long()/w_long()
-
-Possible Py3K improvements:
+Clarifications for PEP 302
+==========================
+* Raise ImportError when load_module() fails to load a module without
+  raising an exception.
+* Is module returned by load_module() actually used for anything?
+
+Things to be exposed at the Python level
+========================================
+* Python/marshal.c:r_long()/w_long()
+
+Py3K
+====
+Improvements
+------------
 * Have a meta_path entry for checking sys.modules to remove need for
   loaders to do it.
 * Put importer objects directly into sys.path to remove need for
@@ -48,19 +53,24 @@
     + Removes None entries from sys.path_importer_cache.
     + Rely on default importers being in sys.path_hooks or sys.meta_path.
        
-Rejected Py3K improvements:
+Rejected Ideas
+--------------
 * Passing in new module to loaders
     Creating a new module is minimal and loader might want to use a different
     type of object.
 
-PTL use-case:
+Use Cases
+=========
+PTL
+---
 * Use filesystem importer and loader.
 * Subclass PyPycFileHandler
     + Set source_handles to '.ptl' and bytecode to '.ptlc'.
     + get_code_from_source()
         - Handle transforming to pure Python code here.
  
-sqlite3 importer use-case:
+sqlite3
+-------
 + DB
     - Module name.
     - Source code.
@@ -534,30 +544,38 @@
 
 class Importer(object):
 
-    """Class that re-implements __import__."""
-
-    def __init__(self, default_importer_factory=None,
-                 default_meta_path=(BuiltinImporter, FrozenImporter)):
-        """Store the built-in importer factory to use when
-        sys.path_importer_cache has a None entry.
-
-        The importer factory should act just like an object that was put on
-        sys.path_hooks.
+    """Class that re-implements __import__.
+    
+    Backwards compatibility is maintained  by extending sys.meta_path
+    interally (for handling built-in and frozen modules) and providing a
+    default path hooks entry (for extension modules, .py, and .pyc
+    files).  Both are controlled during instance initialization.
+    
+    """
+    # XXX Packages not supported.
 
-        """
-        if default_importer_factory:
-            self.default_importer_factory = default_importer_factory
-        else:
+    def __init__(self, default_path_hook=None,
+                 extended_meta_path=(BuiltinImporter, FrozenImporter)):
+        """Store a default path hook entry and a sequence to internally extend
+        sys.meta_path by."""
+        self.extended_meta_path = extended_meta_path
+        self.default_path_hook = default_path_hook
+        if not self.default_path_hook:
             # Create a handler to deal with extension modules, .py, and .pyc
             # files.  Built-in and frozen modules are handled by sys.meta_path
             # entries.
             handlers = ExtensionFileHandler(), PyPycFileHandler()
-            self.default_importer_factory = FileSystemFactory(*handlers)
-        self.default_meta_path = default_meta_path
+            self.default_path_hook = FileSystemFactory(*handlers)
 
     def search_meta_path(self, name):
-        """Check the importers on sys.meta_path for a loader."""
-        for entry in (tuple(sys.meta_path) + tuple(self.default_meta_path)):
+        """Check the importers on sys.meta_path for a loader along with the
+        extended meta path sequence stored within this instance.
+        
+        The extended sys.meta_path entries are searched after the entries on
+        sys.meta_path.
+        
+        """
+        for entry in (tuple(sys.meta_path) + self.extended_meta_path):
             loader = entry.find_module(name)
             if loader:
                 return loader
@@ -565,13 +583,20 @@
             raise ImportError("%s not found on meta path" % name)
 
     def sys_path_importer(self, path_entry):
-        """Return the importer for the entry on sys.path."""
+        """Return the importer for the entry on sys.path.
+        
+        If an entry on sys.path has None stored in sys.path_importer_cache
+        then use the default path hook.
+        
+        """
         try:
             # See if an importer is cached.
             importer = sys.path_importer_cache[path_entry]
-            # If None was stored, use default importer factory.
+            # If None was returned, use default importer factory.
             if importer is None:
-                return self.default_importer_factory(path_entry)
+                # XXX Would it break backwards-compatibility to set the importer
+                # in sys.path_importer_cache, replacing the None entry?
+                return self.default_path_hook(path_entry)
             else:
                 return importer
         except KeyError:
@@ -580,6 +605,10 @@
             for importer_factory in sys.path_hooks:
                 try:
                     importer = importer_factory(path_entry)
+                    # XXX Going to break backwards-compatibility by storing
+                    # an instance of the default importer?  None still handled
+                    # properly so shouldn't be any different than some other
+                    # importer being stored.
                     sys.path_importer_cache[path_entry] = importer
                     return importer
                 except ImportError:
@@ -587,10 +616,12 @@
             else:
                 # No importer factory on sys.path_hooks works; use the default
                 # importer factory.
-                sys.path_importer_cache[path_entry] = None
                 try:
-                    return self.default_importer_factory(path_entry)
+                    importer = self.default_path_hook(path_entry)
+                    sys.path_importer_cache[path_entry] = importer
+                    return importer
                 except ImportError:
+                    sys.path_importer_cache[path_entry] = None
                     raise ImportError("no importer found for %s" % path_entry)
 
     def search_sys_path(self, name):

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 Oct 19 00:48:52 2006
@@ -577,8 +577,66 @@
                                             self.handler.handles[0])
         # There should be at least one attribute that does not start with '_'.
         self.failUnless(any(True for attr in dir(module)
-                            if not attr.startswith('_')))
-                            
+                            if not attr.startswith('_')))                            
+
+
+class ErrorImporter(object):
+    
+    """Helper class to have a guaranteed error point."""
+    
+    def find_module(self, fullname, path=None):
+        self.find_request = fullname, path
+        raise ImportError
+        
+    @classmethod
+    def set_on_sys_path(cls):
+        error_entry = '<error>'
+        sys.path.append(error_entry)
+        ins = cls()
+        sys.path_importer_cache[error_entry] = ins
+        return ins
+        
+class PassImporter(object):
+    
+    """Always pass on importing a module."""
+    
+    def find_module(self, fullname, path=None):
+        self.find_request = fullname, path
+        return None
+        
+    @classmethod
+    def set_on_sys_path(cls):
+        pass_entry = '<pass>'
+        sys.path.append(pass_entry)
+        ins = cls()
+        sys.path_importer_cache[pass_entry] = ins
+        return ins
+        
+class SucceedImporter(object):
+    
+    """Always succeed by returning 'self'."""
+    
+    module = 42
+    
+    def __call__(self, path_entry):
+        return self
+    
+    def find_module(self, fullname, path=None):
+        self.find_request = fullname, path
+        return self
+        
+    def load_module(self, fullname, path=None):
+        self.load_request = fullname, path
+        return self.module
+    
+    @classmethod
+    def set_on_sys_path(cls):
+        succeed_entry = '<success>'
+        sys.path.append(succeed_entry)
+        ins = cls()
+        sys.path_importer_cache[succeed_entry] = ins
+        return ins
+
 
 class SimpleImportTests(unittest.TestCase):
     
@@ -586,6 +644,8 @@
     
     def setUp(self):
         """Store a copy of the 'sys' attribute pertaining to imports."""
+        # Don't backup sys.modules since dict is cached and losing the cache
+        # is not that severe.
         self.old_sys_modules = sys.modules.copy()
         self.old_meta_path = sys.meta_path[:]
         self.old_sys_path = sys.path[:]
@@ -595,7 +655,8 @@
         
     def tearDown(self):
         """Restore backup of import-related attributes in 'sys'."""
-        sys.modules = self.old_sys_modules
+        sys.modules.clear()
+        sys.modules.update(self.old_sys_modules)
         sys.meta_path = self.old_meta_path
         sys.path = self.old_sys_path
         sys.path_hooks = self.old_path_hooks
@@ -604,12 +665,26 @@
     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.
-        pass
+        succeed_importer = SucceedImporter()
+        import_ = importer.Importer(succeed_importer, ())
+        sys.meta_path = []
+        sys.path = ['<succeed>']
+        sys.path_importer_cache['<succeed>'] = None
+        module = import_('sys')
+        self.failUnlessEqual(succeed_importer.find_request, ('sys', None))
+        self.failUnless(module is SucceedImporter.module)
         
-    def test_default_meta_path(self):
+    def test_extended_meta_path(self):
         # Default meta_path entries set during initialization should be
         # queried after sys.meta_path.
-        pass
+        pass_importer = PassImporter()
+        sys.meta_path = [pass_importer]
+        succeed_importer = SucceedImporter()
+        import_ = importer.Importer(extended_meta_path=(succeed_importer,))
+        module = import_('sys')
+        for meta_importer in (pass_importer, succeed_importer):
+            self.failUnlessEqual(meta_importer.find_request, ('sys', None))
+        self.failUnless(module is SucceedImporter.module)
         
     def test_default_init(self):
         # The default initialization should work with a None entry for every
@@ -637,35 +712,65 @@
         module = self.import_('token')
         self.failUnlessEqual(module.__name__, 'token')
         self.failUnless(hasattr(module, 'ISTERMINAL'))
-    
-    def test_meta_path(self):
-        # Test meta_path searching for a loader.
-        pass
+            
+    def test_search_meta_path(self):
+        # Test search method of sys.meta_path.
+        # Should raise ImportError on error.
+        import_ = importer.Importer(extended_meta_path=())
+        sys.meta_path = []
+        self.failUnlessRaises(ImportError, import_.search_meta_path,
+                                'sys')
+        # Verify call order.
+        meta_path = PassImporter(), SucceedImporter()
+        sys.meta_path = meta_path
+        loader = import_.search_meta_path('sys')
+        for entry in meta_path:
+            self.failUnlessEqual(entry.find_request, ('sys', None))
+        self.failUnless(loader is meta_path[-1])
         
-    def test_sys_path(self):
+    def test_search_sys_path(self):
         # Test sys.path searching for a loader.
-        pass
-        
+        sys.meta_path = []
+        import_ = importer.Importer(extended_meta_path=())
+        sys.path = []
+        sys_path = (PassImporter.set_on_sys_path(),
+                    SucceedImporter.set_on_sys_path())
+        module = import_('token')
+        for entry in sys_path:
+            self.failUnlessEqual(entry.find_request, ('token', None))
+        self.failUnless(module is SucceedImporter.module)
+
     def test_importer_cache_preexisting(self):
         # A pre-existing importer should be returned if it exists in
         # sys.path_importer_cache.
-        pass
-        
-    def test_importer_cache_None(self):
-        # A entry of None in sys.path_importer_cache should get one back an
-        # importer from the default importer factory.
-        pass
+        sys.path = []
+        succeed_importer = SucceedImporter.set_on_sys_path()
+        loader = self.import_.search_sys_path('sys')
+        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.
-        pass
+        path_entry = '<succeed>'
+        succeed_importer = SucceedImporter()
+        sys.path = [path_entry]
+        sys.path_importer_cache.clear()
+        sys.path_hooks = [succeed_importer]
+        loader = self.import_.search_sys_path('sys')
+        self.failUnless(loader is succeed_importer)
+        self.failUnless(sys.path_importer_cache[path_entry] is
+                        succeed_importer)
         
     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.
-        pass
+        path_entry = '<test>'
+        sys.path = [path_entry]
+        sys.path_hooks = []
+        sys.path_importer_cache.clear()
+        self.failUnlessRaises(ImportError, self.import_.search_sys_path, 'sys')
+        self.failUnless(sys.path_importer_cache[path_entry] is None)
 
 
 def test_main():


More information about the Python-checkins mailing list