From d1c73ac5b832d46d248acaac3024eb538f3bc703 Mon Sep 17 00:00:00 2001 From: CPython Developers <> Date: Wed, 4 Mar 2026 22:25:00 +0900 Subject: [PATCH] Update zipapp from v3.14.3 --- Lib/test/test_zipapp.py | 19 ++++++++++++++++--- Lib/zipapp.py | 14 ++++++++------ 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_zipapp.py b/Lib/test/test_zipapp.py index ad1328396..8fb0a68de 100644 --- a/Lib/test/test_zipapp.py +++ b/Lib/test/test_zipapp.py @@ -113,6 +113,19 @@ class ZipAppTest(unittest.TestCase): with self.assertRaises(zipapp.ZipAppError): zipapp.create_archive(source, target) + def test_target_overwrites_filtered_source_file(self): + # If there's a filter that excludes the target, + # the overwrite check shouldn't trigger. + source = self.tmpdir + (source / '__main__.py').touch() + target = source / 'target.pyz' + target.touch() + pyz_filter = lambda p: not p.match('*.pyz') + zipapp.create_archive(source, target, filter=pyz_filter) + with zipfile.ZipFile(target, 'r') as z: + self.assertEqual(len(z.namelist()), 1) + self.assertIn('__main__.py', z.namelist()) + def test_create_archive_filter_exclude_dir(self): # Test packing a directory and using a filter to exclude a # subdirectory (ensures that the path supplied to include @@ -246,7 +259,7 @@ class ZipAppTest(unittest.TestCase): (source / '__main__.py').touch() target = io.BytesIO() zipapp.create_archive(str(source), target, interpreter='python') - self.assertTrue(target.getvalue().startswith(b'#!python\n')) + self.assertStartsWith(target.getvalue(), b'#!python\n') def test_read_shebang(self): # Test that we can read the shebang line correctly. @@ -287,7 +300,7 @@ class ZipAppTest(unittest.TestCase): zipapp.create_archive(str(source), str(target), interpreter='python') new_target = io.BytesIO() zipapp.create_archive(str(target), new_target, interpreter='python2.7') - self.assertTrue(new_target.getvalue().startswith(b'#!python2.7\n')) + self.assertStartsWith(new_target.getvalue(), b'#!python2.7\n') def test_read_from_pathlike_obj(self): # Test that we can copy an archive using a path-like object @@ -313,7 +326,7 @@ class ZipAppTest(unittest.TestCase): new_target = io.BytesIO() temp_archive.seek(0) zipapp.create_archive(temp_archive, new_target, interpreter='python2.7') - self.assertTrue(new_target.getvalue().startswith(b'#!python2.7\n')) + self.assertStartsWith(new_target.getvalue(), b'#!python2.7\n') def test_remove_shebang(self): # Test that we can remove the shebang from a file. diff --git a/Lib/zipapp.py b/Lib/zipapp.py index 4ffacc49f..7a4ef96ea 100644 --- a/Lib/zipapp.py +++ b/Lib/zipapp.py @@ -134,7 +134,11 @@ def create_archive(source, target=None, interpreter=None, main=None, # Create the list of files to add to the archive now, in case # the target is being created in the source directory - we # don't want the target being added to itself - files_to_add = sorted(source.rglob('*')) + files_to_add = {} + for path in sorted(source.rglob('*')): + relative_path = path.relative_to(source) + if filter is None or filter(relative_path): + files_to_add[path] = relative_path # The target cannot be in the list of files to add. If it were, we'd # end up overwriting the source file and writing the archive into @@ -159,10 +163,8 @@ def create_archive(source, target=None, interpreter=None, main=None, compression = (zipfile.ZIP_DEFLATED if compressed else zipfile.ZIP_STORED) with zipfile.ZipFile(fd, 'w', compression=compression) as z: - for child in files_to_add: - arcname = child.relative_to(source) - if filter is None or filter(arcname): - z.write(child, arcname.as_posix()) + for path, relative_path in files_to_add.items(): + z.write(path, relative_path.as_posix()) if main_py: z.writestr('__main__.py', main_py.encode('utf-8')) @@ -185,7 +187,7 @@ def main(args=None): """ import argparse - parser = argparse.ArgumentParser() + parser = argparse.ArgumentParser(color=True) parser.add_argument('--output', '-o', default=None, help="The name of the output archive. " "Required if SOURCE is an archive.")