bpo-42131: Add PEP 451-related methods to zipimport (GH-23187)
Specifically, find_spec(), create_module(), and exec_module(). Co-authored-by: Nick Coghlan <ncoghlan@gmail.com>
This commit is contained in:
parent
9b6934230c
commit
d2e94bb084
|
@ -44,8 +44,9 @@ doesn't contain :file:`.pyc` files, importing may be rather slow.
|
||||||
follows the specification in :pep:`273`, but uses an implementation written by Just
|
follows the specification in :pep:`273`, but uses an implementation written by Just
|
||||||
van Rossum that uses the import hooks described in :pep:`302`.
|
van Rossum that uses the import hooks described in :pep:`302`.
|
||||||
|
|
||||||
:pep:`302` - New Import Hooks
|
:mod:`importlib` - The implementation of the import machinery
|
||||||
The PEP to add the import hooks that help this module work.
|
Package providing the relevant protocols for all importers to
|
||||||
|
implement.
|
||||||
|
|
||||||
|
|
||||||
This module defines an exception:
|
This module defines an exception:
|
||||||
|
@ -73,7 +74,31 @@ zipimporter Objects
|
||||||
:exc:`ZipImportError` is raised if *archivepath* doesn't point to a valid ZIP
|
:exc:`ZipImportError` is raised if *archivepath* doesn't point to a valid ZIP
|
||||||
archive.
|
archive.
|
||||||
|
|
||||||
.. method:: find_module(fullname[, path])
|
.. method:: create_module(spec)
|
||||||
|
|
||||||
|
Implementation of :meth:`importlib.abc.Loader.create_module` that returns
|
||||||
|
:const:`None` to explicitly request the default semantics.
|
||||||
|
|
||||||
|
.. versionadded:: 3.10
|
||||||
|
|
||||||
|
|
||||||
|
.. method:: exec_module(module)
|
||||||
|
|
||||||
|
Implementation of :meth:`importlib.abc.Loader.exec_module`.
|
||||||
|
|
||||||
|
.. versionadded:: 3.10
|
||||||
|
|
||||||
|
|
||||||
|
.. method:: find_loader(fullname, path=None)
|
||||||
|
|
||||||
|
An implementation of :meth:`importlib.abc.PathEntryFinder.find_loader`.
|
||||||
|
|
||||||
|
.. deprecated:: 3.10
|
||||||
|
|
||||||
|
Use :meth:`find_spec` instead.
|
||||||
|
|
||||||
|
|
||||||
|
.. method:: find_module(fullname, path=None)
|
||||||
|
|
||||||
Search for a module specified by *fullname*. *fullname* must be the fully
|
Search for a module specified by *fullname*. *fullname* must be the fully
|
||||||
qualified (dotted) module name. It returns the zipimporter instance itself
|
qualified (dotted) module name. It returns the zipimporter instance itself
|
||||||
|
@ -81,6 +106,17 @@ zipimporter Objects
|
||||||
*path* argument is ignored---it's there for compatibility with the
|
*path* argument is ignored---it's there for compatibility with the
|
||||||
importer protocol.
|
importer protocol.
|
||||||
|
|
||||||
|
.. deprecated:: 3.10
|
||||||
|
|
||||||
|
Use :meth:`find_spec` instead.
|
||||||
|
|
||||||
|
|
||||||
|
.. method:: find_spec(fullname, target=None)
|
||||||
|
|
||||||
|
An implementation of :meth:`importlib.abc.PathEntryFinder.find_spec`.
|
||||||
|
|
||||||
|
.. versionadded:: 3.10
|
||||||
|
|
||||||
|
|
||||||
.. method:: get_code(fullname)
|
.. method:: get_code(fullname)
|
||||||
|
|
||||||
|
@ -126,6 +162,10 @@ zipimporter Objects
|
||||||
qualified (dotted) module name. It returns the imported module, or raises
|
qualified (dotted) module name. It returns the imported module, or raises
|
||||||
:exc:`ZipImportError` if it wasn't found.
|
:exc:`ZipImportError` if it wasn't found.
|
||||||
|
|
||||||
|
.. deprecated:: 3.10
|
||||||
|
|
||||||
|
Use :meth:`exec_module` instead.
|
||||||
|
|
||||||
|
|
||||||
.. attribute:: archive
|
.. attribute:: archive
|
||||||
|
|
||||||
|
|
|
@ -303,6 +303,13 @@ Add a :class:`~xml.sax.handler.LexicalHandler` class to the
|
||||||
:mod:`xml.sax.handler` module.
|
:mod:`xml.sax.handler` module.
|
||||||
(Contributed by Jonathan Gossage and Zackery Spytz in :issue:`35018`.)
|
(Contributed by Jonathan Gossage and Zackery Spytz in :issue:`35018`.)
|
||||||
|
|
||||||
|
zipimport
|
||||||
|
---------
|
||||||
|
Add methods related to :pep:`451`: :meth:`~zipimport.zipimporter.find_spec`,
|
||||||
|
:meth:`zipimport.zipimporter.create_module`, and
|
||||||
|
:meth:`zipimport.zipimporter.exec_module`.
|
||||||
|
(Contributed by Brett Cannon in :issue:`42131`.
|
||||||
|
|
||||||
|
|
||||||
Optimizations
|
Optimizations
|
||||||
=============
|
=============
|
||||||
|
|
|
@ -450,8 +450,9 @@ class UncompressedZipImportTestCase(ImportHooksBaseTestCase):
|
||||||
|
|
||||||
zi = zipimport.zipimporter(TEMP_ZIP)
|
zi = zipimport.zipimporter(TEMP_ZIP)
|
||||||
self.assertEqual(zi.archive, TEMP_ZIP)
|
self.assertEqual(zi.archive, TEMP_ZIP)
|
||||||
self.assertEqual(zi.is_package(TESTPACK), True)
|
self.assertTrue(zi.is_package(TESTPACK))
|
||||||
|
|
||||||
|
# PEP 302
|
||||||
find_mod = zi.find_module('spam')
|
find_mod = zi.find_module('spam')
|
||||||
self.assertIsNotNone(find_mod)
|
self.assertIsNotNone(find_mod)
|
||||||
self.assertIsInstance(find_mod, zipimport.zipimporter)
|
self.assertIsInstance(find_mod, zipimport.zipimporter)
|
||||||
|
@ -462,25 +463,39 @@ class UncompressedZipImportTestCase(ImportHooksBaseTestCase):
|
||||||
mod = zi.load_module(TESTPACK)
|
mod = zi.load_module(TESTPACK)
|
||||||
self.assertEqual(zi.get_filename(TESTPACK), mod.__file__)
|
self.assertEqual(zi.get_filename(TESTPACK), mod.__file__)
|
||||||
|
|
||||||
|
# PEP 451
|
||||||
|
spec = zi.find_spec('spam')
|
||||||
|
self.assertIsNotNone(spec)
|
||||||
|
self.assertIsInstance(spec.loader, zipimport.zipimporter)
|
||||||
|
self.assertFalse(spec.loader.is_package('spam'))
|
||||||
|
exec_mod = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(exec_mod)
|
||||||
|
self.assertEqual(spec.loader.get_filename('spam'), exec_mod.__file__)
|
||||||
|
|
||||||
|
spec = zi.find_spec(TESTPACK)
|
||||||
|
mod = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(mod)
|
||||||
|
self.assertEqual(zi.get_filename(TESTPACK), mod.__file__)
|
||||||
|
|
||||||
existing_pack_path = importlib.import_module(TESTPACK).__path__[0]
|
existing_pack_path = importlib.import_module(TESTPACK).__path__[0]
|
||||||
expected_path_path = os.path.join(TEMP_ZIP, TESTPACK)
|
expected_path_path = os.path.join(TEMP_ZIP, TESTPACK)
|
||||||
self.assertEqual(existing_pack_path, expected_path_path)
|
self.assertEqual(existing_pack_path, expected_path_path)
|
||||||
|
|
||||||
self.assertEqual(zi.is_package(packdir + '__init__'), False)
|
self.assertFalse(zi.is_package(packdir + '__init__'))
|
||||||
self.assertEqual(zi.is_package(packdir + TESTPACK2), True)
|
self.assertTrue(zi.is_package(packdir + TESTPACK2))
|
||||||
self.assertEqual(zi.is_package(packdir2 + TESTMOD), False)
|
self.assertFalse(zi.is_package(packdir2 + TESTMOD))
|
||||||
|
|
||||||
mod_path = packdir2 + TESTMOD
|
mod_path = packdir2 + TESTMOD
|
||||||
mod_name = module_path_to_dotted_name(mod_path)
|
mod_name = module_path_to_dotted_name(mod_path)
|
||||||
mod = importlib.import_module(mod_name)
|
mod = importlib.import_module(mod_name)
|
||||||
self.assertTrue(mod_name in sys.modules)
|
self.assertTrue(mod_name in sys.modules)
|
||||||
self.assertEqual(zi.get_source(TESTPACK), None)
|
self.assertIsNone(zi.get_source(TESTPACK))
|
||||||
self.assertEqual(zi.get_source(mod_path), None)
|
self.assertIsNone(zi.get_source(mod_path))
|
||||||
self.assertEqual(zi.get_filename(mod_path), mod.__file__)
|
self.assertEqual(zi.get_filename(mod_path), mod.__file__)
|
||||||
# To pass in the module name instead of the path, we must use the
|
# To pass in the module name instead of the path, we must use the
|
||||||
# right importer
|
# right importer
|
||||||
loader = mod.__loader__
|
loader = mod.__spec__.loader
|
||||||
self.assertEqual(loader.get_source(mod_name), None)
|
self.assertIsNone(loader.get_source(mod_name))
|
||||||
self.assertEqual(loader.get_filename(mod_name), mod.__file__)
|
self.assertEqual(loader.get_filename(mod_name), mod.__file__)
|
||||||
|
|
||||||
# test prefix and archivepath members
|
# test prefix and archivepath members
|
||||||
|
@ -505,17 +520,22 @@ class UncompressedZipImportTestCase(ImportHooksBaseTestCase):
|
||||||
zi = zipimport.zipimporter(TEMP_ZIP + os.sep + packdir)
|
zi = zipimport.zipimporter(TEMP_ZIP + os.sep + packdir)
|
||||||
self.assertEqual(zi.archive, TEMP_ZIP)
|
self.assertEqual(zi.archive, TEMP_ZIP)
|
||||||
self.assertEqual(zi.prefix, packdir)
|
self.assertEqual(zi.prefix, packdir)
|
||||||
self.assertEqual(zi.is_package(TESTPACK2), True)
|
self.assertTrue(zi.is_package(TESTPACK2))
|
||||||
|
# PEP 302
|
||||||
mod = zi.load_module(TESTPACK2)
|
mod = zi.load_module(TESTPACK2)
|
||||||
self.assertEqual(zi.get_filename(TESTPACK2), mod.__file__)
|
self.assertEqual(zi.get_filename(TESTPACK2), mod.__file__)
|
||||||
|
# PEP 451
|
||||||
|
spec = zi.find_spec(TESTPACK2)
|
||||||
|
mod = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(mod)
|
||||||
|
self.assertEqual(spec.loader.get_filename(TESTPACK2), mod.__file__)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertFalse(zi.is_package(TESTPACK2 + os.sep + '__init__'))
|
||||||
zi.is_package(TESTPACK2 + os.sep + '__init__'), False)
|
self.assertFalse(zi.is_package(TESTPACK2 + os.sep + TESTMOD))
|
||||||
self.assertEqual(
|
|
||||||
zi.is_package(TESTPACK2 + os.sep + TESTMOD), False)
|
|
||||||
|
|
||||||
pkg_path = TEMP_ZIP + os.sep + packdir + TESTPACK2
|
pkg_path = TEMP_ZIP + os.sep + packdir + TESTPACK2
|
||||||
zi2 = zipimport.zipimporter(pkg_path)
|
zi2 = zipimport.zipimporter(pkg_path)
|
||||||
|
# PEP 302
|
||||||
find_mod_dotted = zi2.find_module(TESTMOD)
|
find_mod_dotted = zi2.find_module(TESTMOD)
|
||||||
self.assertIsNotNone(find_mod_dotted)
|
self.assertIsNotNone(find_mod_dotted)
|
||||||
self.assertIsInstance(find_mod_dotted, zipimport.zipimporter)
|
self.assertIsInstance(find_mod_dotted, zipimport.zipimporter)
|
||||||
|
@ -524,17 +544,27 @@ class UncompressedZipImportTestCase(ImportHooksBaseTestCase):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
find_mod_dotted.get_filename(TESTMOD), load_mod.__file__)
|
find_mod_dotted.get_filename(TESTMOD), load_mod.__file__)
|
||||||
|
|
||||||
|
# PEP 451
|
||||||
|
spec = zi2.find_spec(TESTMOD)
|
||||||
|
self.assertIsNotNone(spec)
|
||||||
|
self.assertIsInstance(spec.loader, zipimport.zipimporter)
|
||||||
|
self.assertFalse(spec.loader.is_package(TESTMOD))
|
||||||
|
load_mod = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(load_mod)
|
||||||
|
self.assertEqual(
|
||||||
|
spec.loader.get_filename(TESTMOD), load_mod.__file__)
|
||||||
|
|
||||||
mod_path = TESTPACK2 + os.sep + TESTMOD
|
mod_path = TESTPACK2 + os.sep + TESTMOD
|
||||||
mod_name = module_path_to_dotted_name(mod_path)
|
mod_name = module_path_to_dotted_name(mod_path)
|
||||||
mod = importlib.import_module(mod_name)
|
mod = importlib.import_module(mod_name)
|
||||||
self.assertTrue(mod_name in sys.modules)
|
self.assertTrue(mod_name in sys.modules)
|
||||||
self.assertEqual(zi.get_source(TESTPACK2), None)
|
self.assertIsNone(zi.get_source(TESTPACK2))
|
||||||
self.assertEqual(zi.get_source(mod_path), None)
|
self.assertIsNone(zi.get_source(mod_path))
|
||||||
self.assertEqual(zi.get_filename(mod_path), mod.__file__)
|
self.assertEqual(zi.get_filename(mod_path), mod.__file__)
|
||||||
# To pass in the module name instead of the path, we must use the
|
# To pass in the module name instead of the path, we must use the
|
||||||
# right importer.
|
# right importer.
|
||||||
loader = mod.__loader__
|
loader = mod.__loader__
|
||||||
self.assertEqual(loader.get_source(mod_name), None)
|
self.assertIsNone(loader.get_source(mod_name))
|
||||||
self.assertEqual(loader.get_filename(mod_name), mod.__file__)
|
self.assertEqual(loader.get_filename(mod_name), mod.__file__)
|
||||||
|
|
||||||
def testGetData(self):
|
def testGetData(self):
|
||||||
|
@ -655,7 +685,9 @@ class UncompressedZipImportTestCase(ImportHooksBaseTestCase):
|
||||||
zinfo = ZipInfo(TESTMOD + ".py", time.localtime(NOW))
|
zinfo = ZipInfo(TESTMOD + ".py", time.localtime(NOW))
|
||||||
zinfo.compress_type = self.compression
|
zinfo.compress_type = self.compression
|
||||||
z.writestr(zinfo, test_src)
|
z.writestr(zinfo, test_src)
|
||||||
zipimport.zipimporter(filename).load_module(TESTMOD)
|
spec = zipimport.zipimporter(filename).find_spec(TESTMOD)
|
||||||
|
mod = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(mod)
|
||||||
|
|
||||||
def testBytesPath(self):
|
def testBytesPath(self):
|
||||||
filename = os_helper.TESTFN + ".zip"
|
filename = os_helper.TESTFN + ".zip"
|
||||||
|
@ -747,6 +779,8 @@ class BadFileZipImportTestCase(unittest.TestCase):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.assertRaises(TypeError, z.find_module, None)
|
self.assertRaises(TypeError, z.find_module, None)
|
||||||
|
self.assertRaises(TypeError, z.find_spec, None)
|
||||||
|
self.assertRaises(TypeError, z.exec_module, None)
|
||||||
self.assertRaises(TypeError, z.load_module, None)
|
self.assertRaises(TypeError, z.load_module, None)
|
||||||
self.assertRaises(TypeError, z.is_package, None)
|
self.assertRaises(TypeError, z.is_package, None)
|
||||||
self.assertRaises(TypeError, z.get_code, None)
|
self.assertRaises(TypeError, z.get_code, None)
|
||||||
|
@ -754,7 +788,8 @@ class BadFileZipImportTestCase(unittest.TestCase):
|
||||||
self.assertRaises(TypeError, z.get_source, None)
|
self.assertRaises(TypeError, z.get_source, None)
|
||||||
|
|
||||||
error = zipimport.ZipImportError
|
error = zipimport.ZipImportError
|
||||||
self.assertEqual(z.find_module('abc'), None)
|
self.assertIsNone(z.find_module('abc'))
|
||||||
|
self.assertIsNone(z.find_spec('abc'))
|
||||||
|
|
||||||
self.assertRaises(error, z.load_module, 'abc')
|
self.assertRaises(error, z.load_module, 'abc')
|
||||||
self.assertRaises(error, z.get_code, 'abc')
|
self.assertRaises(error, z.get_code, 'abc')
|
||||||
|
|
|
@ -42,7 +42,7 @@ END_CENTRAL_DIR_SIZE = 22
|
||||||
STRING_END_ARCHIVE = b'PK\x05\x06'
|
STRING_END_ARCHIVE = b'PK\x05\x06'
|
||||||
MAX_COMMENT_LEN = (1 << 16) - 1
|
MAX_COMMENT_LEN = (1 << 16) - 1
|
||||||
|
|
||||||
class zipimporter:
|
class zipimporter(_bootstrap_external._LoaderBasics):
|
||||||
"""zipimporter(archivepath) -> zipimporter object
|
"""zipimporter(archivepath) -> zipimporter object
|
||||||
|
|
||||||
Create a new zipimporter instance. 'archivepath' must be a path to
|
Create a new zipimporter instance. 'archivepath' must be a path to
|
||||||
|
@ -115,6 +115,8 @@ class zipimporter:
|
||||||
full path name if it's possibly a portion of a namespace package,
|
full path name if it's possibly a portion of a namespace package,
|
||||||
or None otherwise. The optional 'path' argument is ignored -- it's
|
or None otherwise. The optional 'path' argument is ignored -- it's
|
||||||
there for compatibility with the importer protocol.
|
there for compatibility with the importer protocol.
|
||||||
|
|
||||||
|
Deprecated since Python 3.10. Use find_spec() instead.
|
||||||
"""
|
"""
|
||||||
mi = _get_module_info(self, fullname)
|
mi = _get_module_info(self, fullname)
|
||||||
if mi is not None:
|
if mi is not None:
|
||||||
|
@ -146,9 +148,37 @@ class zipimporter:
|
||||||
instance itself if the module was found, or None if it wasn't.
|
instance itself if the module was found, or None if it wasn't.
|
||||||
The optional 'path' argument is ignored -- it's there for compatibility
|
The optional 'path' argument is ignored -- it's there for compatibility
|
||||||
with the importer protocol.
|
with the importer protocol.
|
||||||
|
|
||||||
|
Deprecated since Python 3.10. Use find_spec() instead.
|
||||||
"""
|
"""
|
||||||
return self.find_loader(fullname, path)[0]
|
return self.find_loader(fullname, path)[0]
|
||||||
|
|
||||||
|
def find_spec(self, fullname, target=None):
|
||||||
|
"""Create a ModuleSpec for the specified module.
|
||||||
|
|
||||||
|
Returns None if the module cannot be found.
|
||||||
|
"""
|
||||||
|
module_info = _get_module_info(self, fullname)
|
||||||
|
if module_info is not None:
|
||||||
|
return _bootstrap.spec_from_loader(fullname, self, is_package=module_info)
|
||||||
|
else:
|
||||||
|
# Not a module or regular package. See if this is a directory, and
|
||||||
|
# therefore possibly a portion of a namespace package.
|
||||||
|
|
||||||
|
# We're only interested in the last path component of fullname
|
||||||
|
# earlier components are recorded in self.prefix.
|
||||||
|
modpath = _get_module_path(self, fullname)
|
||||||
|
if _is_dir(self, modpath):
|
||||||
|
# This is possibly a portion of a namespace
|
||||||
|
# package. Return the string representing its path,
|
||||||
|
# without a trailing separator.
|
||||||
|
path = f'{self.archive}{path_sep}{modpath}'
|
||||||
|
spec = _bootstrap.ModuleSpec(name=fullname, loader=None,
|
||||||
|
is_package=True)
|
||||||
|
spec.submodule_search_locations.append(path)
|
||||||
|
return spec
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
def get_code(self, fullname):
|
def get_code(self, fullname):
|
||||||
"""get_code(fullname) -> code object.
|
"""get_code(fullname) -> code object.
|
||||||
|
@ -237,6 +267,8 @@ class zipimporter:
|
||||||
Load the module specified by 'fullname'. 'fullname' must be the
|
Load the module specified by 'fullname'. 'fullname' must be the
|
||||||
fully qualified (dotted) module name. It returns the imported
|
fully qualified (dotted) module name. It returns the imported
|
||||||
module, or raises ZipImportError if it wasn't found.
|
module, or raises ZipImportError if it wasn't found.
|
||||||
|
|
||||||
|
Deprecated since Python 3.10. use exec_module() instead.
|
||||||
"""
|
"""
|
||||||
code, ispackage, modpath = _get_module_code(self, fullname)
|
code, ispackage, modpath = _get_module_code(self, fullname)
|
||||||
mod = sys.modules.get(fullname)
|
mod = sys.modules.get(fullname)
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
Implement PEP 451/spec methods on zipimport.zipimporter: find_spec(),
|
||||||
|
create_module(), and exec_module().
|
||||||
|
|
||||||
|
This also allows for the documented deprecation of find_loader(),
|
||||||
|
find_module(), and load_module().
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue