[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

AltStyle によって変換されたページ (->オリジナル) /