Skip to content

Commit f8d4d99

Browse files
authored
Upgrade zipimport to Python 3.14.2 (RustPython#6857)
1 parent 483e4a2 commit f8d4d99

3 files changed

Lines changed: 140 additions & 27 deletions

File tree

Lib/test/test_importlib/test_namespace_pkgs.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,6 @@ def test_project3_succeeds(self):
288288
class ZipWithMissingDirectory(NamespacePackageTest):
289289
paths = ['missing_directory.zip']
290290

291-
@unittest.expectedFailure
292291
def test_missing_directory(self):
293292
# This will fail because missing_directory.zip contains:
294293
# Length Date Time Name

Lib/test/test_zipimport.py

Lines changed: 115 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,81 @@ def testSubNamespacePackage(self):
318318
packdir2 + TESTMOD + pyc_ext: test_pyc}
319319
self.doTest(pyc_ext, files, TESTPACK, TESTPACK2, TESTMOD)
320320

321+
def testPackageExplicitDirectories(self):
322+
# Test explicit namespace packages with explicit directory entries.
323+
self.addCleanup(os_helper.unlink, TEMP_ZIP)
324+
with ZipFile(TEMP_ZIP, 'w', compression=self.compression) as z:
325+
z.mkdir('a')
326+
z.writestr('a/__init__.py', test_src)
327+
z.mkdir('a/b')
328+
z.writestr('a/b/__init__.py', test_src)
329+
z.mkdir('a/b/c')
330+
z.writestr('a/b/c/__init__.py', test_src)
331+
z.writestr('a/b/c/d.py', test_src)
332+
self._testPackage(initfile='__init__.py')
333+
334+
def testPackageImplicitDirectories(self):
335+
# Test explicit namespace packages without explicit directory entries.
336+
self.addCleanup(os_helper.unlink, TEMP_ZIP)
337+
with ZipFile(TEMP_ZIP, 'w', compression=self.compression) as z:
338+
z.writestr('a/__init__.py', test_src)
339+
z.writestr('a/b/__init__.py', test_src)
340+
z.writestr('a/b/c/__init__.py', test_src)
341+
z.writestr('a/b/c/d.py', test_src)
342+
self._testPackage(initfile='__init__.py')
343+
344+
def testNamespacePackageExplicitDirectories(self):
345+
# Test implicit namespace packages with explicit directory entries.
346+
self.addCleanup(os_helper.unlink, TEMP_ZIP)
347+
with ZipFile(TEMP_ZIP, 'w', compression=self.compression) as z:
348+
z.mkdir('a')
349+
z.mkdir('a/b')
350+
z.mkdir('a/b/c')
351+
z.writestr('a/b/c/d.py', test_src)
352+
self._testPackage(initfile=None)
353+
354+
def testNamespacePackageImplicitDirectories(self):
355+
# Test implicit namespace packages without explicit directory entries.
356+
self.addCleanup(os_helper.unlink, TEMP_ZIP)
357+
with ZipFile(TEMP_ZIP, 'w', compression=self.compression) as z:
358+
z.writestr('a/b/c/d.py', test_src)
359+
self._testPackage(initfile=None)
360+
361+
def _testPackage(self, initfile):
362+
zi = zipimport.zipimporter(os.path.join(TEMP_ZIP, 'a'))
363+
if initfile is None:
364+
# XXX Should it work?
365+
self.assertRaises(zipimport.ZipImportError, zi.is_package, 'b')
366+
self.assertRaises(zipimport.ZipImportError, zi.get_source, 'b')
367+
self.assertRaises(zipimport.ZipImportError, zi.get_code, 'b')
368+
else:
369+
self.assertTrue(zi.is_package('b'))
370+
self.assertEqual(zi.get_source('b'), test_src)
371+
self.assertEqual(zi.get_code('b').co_filename,
372+
os.path.join(TEMP_ZIP, 'a', 'b', initfile))
373+
374+
sys.path.insert(0, TEMP_ZIP)
375+
self.assertNotIn('a', sys.modules)
376+
377+
mod = importlib.import_module(f'a.b')
378+
self.assertIn('a', sys.modules)
379+
self.assertIs(sys.modules['a.b'], mod)
380+
if initfile is None:
381+
self.assertIsNone(mod.__file__)
382+
else:
383+
self.assertEqual(mod.__file__,
384+
os.path.join(TEMP_ZIP, 'a', 'b', initfile))
385+
self.assertEqual(len(mod.__path__), 1, mod.__path__)
386+
self.assertEqual(mod.__path__[0], os.path.join(TEMP_ZIP, 'a', 'b'))
387+
388+
mod2 = importlib.import_module(f'a.b.c.d')
389+
self.assertIn('a.b.c', sys.modules)
390+
self.assertIn('a.b.c.d', sys.modules)
391+
self.assertIs(sys.modules['a.b.c.d'], mod2)
392+
self.assertIs(mod.c.d, mod2)
393+
self.assertEqual(mod2.__file__,
394+
os.path.join(TEMP_ZIP, 'a', 'b', 'c', 'd.py'))
395+
321396
def testMixedNamespacePackage(self):
322397
# Test implicit namespace packages spread between a
323398
# real filesystem and a zip archive.
@@ -535,21 +610,22 @@ def testInvalidateCaches(self):
535610
packdir2 + "__init__" + pyc_ext: test_pyc,
536611
packdir2 + TESTMOD + pyc_ext: test_pyc,
537612
"spam" + pyc_ext: test_pyc}
613+
extra_files = [packdir, packdir2]
538614
self.makeZip(files, file_comment=b"spam")
539615

540616
zi = zipimport.zipimporter(TEMP_ZIP)
541-
self.assertEqual(zi._get_files().keys(), files.keys())
617+
self.assertEqual(sorted(zi._get_files()), sorted([*files, *extra_files]))
542618
# Check that the file information remains accurate after reloading
543619
zi.invalidate_caches()
544-
self.assertEqual(zi._get_files().keys(), files.keys())
620+
self.assertEqual(sorted(zi._get_files()), sorted([*files, *extra_files]))
545621
# Add a new file to the ZIP archive
546622
newfile = {"spam2" + pyc_ext: test_pyc}
547623
files.update(newfile)
548624
with ZipFile(TEMP_ZIP, "a", compression=self.compression) as z:
549625
self.writeZip(z, newfile, file_comment=b"spam")
550626
# Check that we can detect the new file after invalidating the cache
551627
zi.invalidate_caches()
552-
self.assertEqual(zi._get_files().keys(), files.keys())
628+
self.assertEqual(sorted(zi._get_files()), sorted([*files, *extra_files]))
553629
spec = zi.find_spec('spam2')
554630
self.assertIsNotNone(spec)
555631
self.assertIsInstance(spec.loader, zipimport.zipimporter)
@@ -567,13 +643,14 @@ def testInvalidateCachesWithMultipleZipimports(self):
567643
packdir2 + "__init__" + pyc_ext: test_pyc,
568644
packdir2 + TESTMOD + pyc_ext: test_pyc,
569645
"spam" + pyc_ext: test_pyc}
646+
extra_files = [packdir, packdir2]
570647
self.makeZip(files, file_comment=b"spam")
571648

572649
zi = zipimport.zipimporter(TEMP_ZIP)
573-
self.assertEqual(zi._get_files().keys(), files.keys())
650+
self.assertEqual(sorted(zi._get_files()), sorted([*files, *extra_files]))
574651
# Zipimporter for the same path.
575652
zi2 = zipimport.zipimporter(TEMP_ZIP)
576-
self.assertEqual(zi2._get_files().keys(), files.keys())
653+
self.assertEqual(sorted(zi2._get_files()), sorted([*files, *extra_files]))
577654
# Add a new file to the ZIP archive to make the cache wrong.
578655
newfile = {"spam2" + pyc_ext: test_pyc}
579656
files.update(newfile)
@@ -582,7 +659,7 @@ def testInvalidateCachesWithMultipleZipimports(self):
582659
# Invalidate the cache of the first zipimporter.
583660
zi.invalidate_caches()
584661
# Check that the second zipimporter detects the new file and isn't using a stale cache.
585-
self.assertEqual(zi2._get_files().keys(), files.keys())
662+
self.assertEqual(sorted(zi2._get_files()), sorted([*files, *extra_files]))
586663
spec = zi2.find_spec('spam2')
587664
self.assertIsNotNone(spec)
588665
self.assertIsInstance(spec.loader, zipimport.zipimporter)
@@ -638,17 +715,33 @@ def testZipImporterMethodsInSubDirectory(self):
638715
self.assertIsNone(loader.get_source(mod_name))
639716
self.assertEqual(loader.get_filename(mod_name), mod.__file__)
640717

641-
def testGetData(self):
718+
def testGetDataExplicitDirectories(self):
642719
self.addCleanup(os_helper.unlink, TEMP_ZIP)
643-
with ZipFile(TEMP_ZIP, "w") as z:
644-
z.compression = self.compression
645-
name = "testdata.dat"
646-
data = bytes(x for x in range(256))
647-
z.writestr(name, data)
648-
649-
zi = zipimport.zipimporter(TEMP_ZIP)
650-
self.assertEqual(data, zi.get_data(name))
651-
self.assertIn('zipimporter object', repr(zi))
720+
with ZipFile(TEMP_ZIP, 'w', compression=self.compression) as z:
721+
z.mkdir('a')
722+
z.mkdir('a/b')
723+
z.mkdir('a/b/c')
724+
data = bytes(range(256))
725+
z.writestr('a/b/c/testdata.dat', data)
726+
self._testGetData()
727+
728+
def testGetDataImplicitDirectories(self):
729+
self.addCleanup(os_helper.unlink, TEMP_ZIP)
730+
with ZipFile(TEMP_ZIP, 'w', compression=self.compression) as z:
731+
data = bytes(range(256))
732+
z.writestr('a/b/c/testdata.dat', data)
733+
self._testGetData()
734+
735+
def _testGetData(self):
736+
zi = zipimport.zipimporter(os.path.join(TEMP_ZIP, 'ignored'))
737+
pathname = os.path.join('a', 'b', 'c', 'testdata.dat')
738+
data = bytes(range(256))
739+
self.assertEqual(zi.get_data(pathname), data)
740+
self.assertEqual(zi.get_data(os.path.join(TEMP_ZIP, pathname)), data)
741+
self.assertEqual(zi.get_data(os.path.join('a', 'b', '')), b'')
742+
self.assertEqual(zi.get_data(os.path.join(TEMP_ZIP, 'a', 'b', '')), b'')
743+
self.assertRaises(OSError, zi.get_data, os.path.join('a', 'b'))
744+
self.assertRaises(OSError, zi.get_data, os.path.join(TEMP_ZIP, 'a', 'b'))
652745

653746
def testImporterAttr(self):
654747
src = """if 1: # indent hack
@@ -742,15 +835,15 @@ def doTraceback(self, module):
742835

743836
s = io.StringIO()
744837
print_tb(tb, 1, s)
745-
self.assertTrue(s.getvalue().endswith(
838+
self.assertEndsWith(s.getvalue(),
746839
' def do_raise(): raise TypeError\n'
747840
'' if support.has_no_debug_ranges() else
748841
' ^^^^^^^^^^^^^^^\n'
749-
))
842+
)
750843
else:
751844
raise AssertionError("This ought to be impossible")
752845

753-
@unittest.expectedFailure # TODO: RUSTPYTHON; empty caret lines from equal col/end_col
846+
@unittest.expectedFailure # TODO: RUSTPYTHON; empty caret lines from equal col/end_col
754847
def testTraceback(self):
755848
files = {TESTMOD + ".py": raise_src}
756849
self.doTest(None, files, TESTMOD, call=self.doTraceback)
@@ -788,15 +881,17 @@ def testLargestPossibleComment(self):
788881
files = {TESTMOD + ".py": test_src}
789882
self.doTest(".py", files, TESTMOD, comment=b"c" * ((1 << 16) - 1))
790883

884+
@support.requires_resource('cpu')
791885
def testZip64(self):
792886
files = self.getZip64Files()
793887
self.doTest(".py", files, "f6")
794888

889+
@support.requires_resource('cpu')
795890
def testZip64CruftAndComment(self):
796891
files = self.getZip64Files()
797892
self.doTest(".py", files, "f65536", comment=b"c" * ((1 << 16) - 1))
798893

799-
@unittest.skip('TODO: RUSTPYTHON; (intermittent success/failures); ValueError: name="RustPython/crates/pylib/Lib/test/zipimport_data/sparse-zip64-c0-0x000000000.part" does not fit expected pattern.')
894+
@unittest.skip("TODO: RUSTPYTHON; (intermittent success/failures); ValueError: name=\"RustPython/crates/pylib/Lib/test/zipimport_data/sparse-zip64-c0-0x000000000.part\" does not fit expected pattern.")
800895
def testZip64LargeFile(self):
801896
support.requires(
802897
"largefile",

Lib/zipimport.py

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import marshal # for loads
2121
import sys # for modules
2222
import time # for mktime
23-
import _warnings # For warn()
2423

2524
__all__ = ['ZipImportError', 'zipimporter']
2625

@@ -155,6 +154,8 @@ def get_data(self, pathname):
155154
toc_entry = self._get_files()[key]
156155
except KeyError:
157156
raise OSError(0, '', key)
157+
if toc_entry is None:
158+
return b''
158159
return _get_data(self.archive, toc_entry)
159160

160161

@@ -219,9 +220,11 @@ def load_module(self, fullname):
219220
220221
Deprecated since Python 3.10. Use exec_module() instead.
221222
"""
222-
msg = ("zipimport.zipimporter.load_module() is deprecated and slated for "
223-
"removal in Python 3.12; use exec_module() instead")
224-
_warnings.warn(msg, DeprecationWarning)
223+
import warnings
224+
warnings._deprecated("zipimport.zipimporter.load_module",
225+
f"{warnings._DEPRECATED_MSG}; "
226+
"use zipimport.zipimporter.exec_module() instead",
227+
remove=(3, 15))
225228
code, ispackage, modpath = _get_module_code(self, fullname)
226229
mod = sys.modules.get(fullname)
227230
if mod is None or not isinstance(mod, _module_type):
@@ -513,7 +516,7 @@ def _read_directory(archive):
513516

514517
# N.b. Here be dragons: the ordering of these is different than
515518
# the header fields, and it's really easy to get it wrong since
516-
# naturally-occuring zips that use all 3 are >4GB
519+
# naturally-occurring zips that use all 3 are >4GB
517520
if file_size == MAX_UINT32:
518521
file_size = values.pop(0)
519522
if data_size == MAX_UINT32:
@@ -546,6 +549,22 @@ def _read_directory(archive):
546549
finally:
547550
fp.seek(start_offset)
548551
_bootstrap._verbose_message('zipimport: found {} names in {!r}', count, archive)
552+
553+
# Add implicit directories.
554+
count = 0
555+
for name in list(files):
556+
while True:
557+
i = name.rstrip(path_sep).rfind(path_sep)
558+
if i < 0:
559+
break
560+
name = name[:i + 1]
561+
if name in files:
562+
break
563+
files[name] = None
564+
count += 1
565+
if count:
566+
_bootstrap._verbose_message('zipimport: added {} implicit directories in {!r}',
567+
count, archive)
549568
return files
550569

551570
# During bootstrap, we may need to load the encodings
@@ -679,7 +698,7 @@ def _unmarshal_code(self, pathname, fullpath, fullname, data):
679698
source_bytes = _get_pyc_source(self, fullpath)
680699
if source_bytes is not None:
681700
source_hash = _imp.source_hash(
682-
_bootstrap_external._RAW_MAGIC_NUMBER,
701+
_imp.pyc_magic_number_token,
683702
source_bytes,
684703
)
685704

0 commit comments

Comments
 (0)