[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