From dc88d4787d9b750ff69a5eac2e7082a22b874b31 Mon Sep 17 00:00:00 2001 From: Paul Moore Date: Wed, 9 Aug 2017 20:34:59 +0100 Subject: [PATCH 1/2] bpo-31072: Rename the new filter argument for zipapp.create_archive. * Rename the new argument to "filterfunc" * Improve tests for the new functionality * Add a "What's New" entry. --- Doc/library/zipapp.rst | 12 ++++++++---- Doc/whatsnew/3.7.rst | 7 +++++++ Lib/test/test_zipapp.py | 31 ++++++++++++++++++++++++++----- Lib/zipapp.py | 8 ++++---- 4 files changed, 45 insertions(+), 13 deletions(-) diff --git a/Doc/library/zipapp.rst b/Doc/library/zipapp.rst index 993c2ccc39883a8..1cf58ebf33d12ce 100644 --- a/Doc/library/zipapp.rst +++ b/Doc/library/zipapp.rst @@ -99,7 +99,7 @@ The module defines two convenience functions: .. function:: create_archive(source, target=None, interpreter=None, main=None, - include_file=None) + filterfunc=None) Create an application archive from *source*. The source can be any of the following: @@ -144,9 +144,10 @@ The module defines two convenience functions: contain a ``__main__.py`` file, as otherwise the resulting archive would not be executable. - The *include_file* argument specifies a callback function that is passed the - relative path to the file in order to determine which files to store when - being called against a directory. + The optional *filterfunc* argument specifies a callback function that + is passed a Path object representing the path to the file being added + (relative to the source directory). It should return ``True`` if the + file is to be added. If a file object is specified for *source* or *target*, it is the caller's responsibility to close it after calling create_archive. @@ -157,6 +158,9 @@ The module defines two convenience functions: passed to the ``zipfile.ZipFile`` class, and must supply the methods needed by that class. + .. versionadded:: 3.7 + Added the *filterfunc* argument. + .. function:: get_interpreter(archive) Return the interpreter specified in the ``#!`` line at the start of the diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst index 21e9c568ae42419..2337d36f8a5d78b 100644 --- a/Doc/whatsnew/3.7.rst +++ b/Doc/whatsnew/3.7.rst @@ -258,6 +258,13 @@ Function :func:`~uu.encode` now accepts an optional *backtick* keyword argument. When it's true, zeros are represented by ``'`'`` instead of spaces. (Contributed by Xiang Zhang in :issue:`30103`.) +zipapp +------ + +Function :func:`zipapp.create_archive` now accepts an optional *filterfunc* +argument, to allow the user to select which files should be included in the +archive. + Optimizations ============= diff --git a/Lib/test/test_zipapp.py b/Lib/test/test_zipapp.py index 47eed5f4a6cf83f..4dbc83ad269552c 100644 --- a/Lib/test/test_zipapp.py +++ b/Lib/test/test_zipapp.py @@ -53,10 +53,11 @@ def test_create_archive_with_subdirs(self): self.assertIn('foo/', z.namelist()) self.assertIn('bar/', z.namelist()) - def test_create_archive_with_include_file(self): - # Test packing a directory and using include_file to specify which files to include. - def skip_pyc_files(file): - return '.pyc' not in str(file) + def test_create_archive_with_filterfunc(self): + # Test packing a directory and using filterfunc to specify + # which files to include. + def skip_pyc_files(path): + return path.suffix != '.pyc' source = self.tmpdir / 'source' source.mkdir() (source / '__main__.py').touch() @@ -64,12 +65,32 @@ def skip_pyc_files(file): (source / 'test.pyc').touch() target = self.tmpdir / 'source.pyz' - zipapp.create_archive(source, target, include_file=skip_pyc_files) + zipapp.create_archive(source, target, filterfunc=skip_pyc_files) with zipfile.ZipFile(target, 'r') as z: self.assertIn('__main__.py', z.namelist()) self.assertIn('test.py', z.namelist()) self.assertNotIn('test.pyc', z.namelist()) + def test_create_archive_filterfunc_exclude_dir(self): + # Test packing a directory and using filterfunc to exclude a + # subdirectory (ensures that the path supplied to filterfunc + # is relative to the source location, as expected). + def skip_dummy_dir(path): + return path.parts[0] != 'dummy' + source = self.tmpdir / 'source' + source.mkdir() + (source / '__main__.py').touch() + (source / 'test.py').touch() + (source / 'dummy').mkdir() + (source / 'dummy' / 'test2.py').touch() + target = self.tmpdir / 'source.pyz' + + zipapp.create_archive(source, target, filterfunc=skip_dummy_dir) + with zipfile.ZipFile(target, 'r') as z: + self.assertEqual(len(z.namelist()), 2) + self.assertIn('__main__.py', z.namelist()) + self.assertIn('test.py', z.namelist()) + def test_create_archive_default_target(self): # Test packing a directory to the default name. source = self.tmpdir / 'source' diff --git a/Lib/zipapp.py b/Lib/zipapp.py index bf15b6806dd217e..35ec6f3be3df660 100644 --- a/Lib/zipapp.py +++ b/Lib/zipapp.py @@ -74,7 +74,7 @@ def _copy_archive(archive, new_archive, interpreter=None): def create_archive(source, target=None, interpreter=None, main=None, - include_file=None): + filterfunc=None): """Create an application archive from SOURCE. The SOURCE can be the name of a directory, or a filename or a file-like @@ -135,9 +135,9 @@ def create_archive(source, target=None, interpreter=None, main=None, _write_file_prefix(fd, interpreter) with zipfile.ZipFile(fd, 'w') as z: for child in source.rglob('*'): - arcname = child.relative_to(source).as_posix() - if include_file is None or include_file(pathlib.Path(arcname)): - z.write(child, arcname) + arcname = child.relative_to(source) + if filterfunc is None or filterfunc(arcname): + z.write(child, arcname.as_posix()) if main_py: z.writestr('__main__.py', main_py.encode('utf-8')) From 494563b4d60317d357cf9a74ae2fd403102d7a0a Mon Sep 17 00:00:00 2001 From: Paul Moore Date: Sat, 26 Aug 2017 17:45:21 +0100 Subject: [PATCH 2/2] Rename argument to 'filter' --- Doc/library/zipapp.rst | 6 +++--- Doc/whatsnew/3.7.rst | 2 +- Lib/test/test_zipapp.py | 14 +++++++------- Lib/zipapp.py | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Doc/library/zipapp.rst b/Doc/library/zipapp.rst index 1cf58ebf33d12ce..120bbbb66201f64 100644 --- a/Doc/library/zipapp.rst +++ b/Doc/library/zipapp.rst @@ -99,7 +99,7 @@ The module defines two convenience functions: .. function:: create_archive(source, target=None, interpreter=None, main=None, - filterfunc=None) + filter=None) Create an application archive from *source*. The source can be any of the following: @@ -144,7 +144,7 @@ The module defines two convenience functions: contain a ``__main__.py`` file, as otherwise the resulting archive would not be executable. - The optional *filterfunc* argument specifies a callback function that + The optional *filter* argument specifies a callback function that is passed a Path object representing the path to the file being added (relative to the source directory). It should return ``True`` if the file is to be added. @@ -159,7 +159,7 @@ The module defines two convenience functions: needed by that class. .. versionadded:: 3.7 - Added the *filterfunc* argument. + Added the *filter* argument. .. function:: get_interpreter(archive) diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst index 2337d36f8a5d78b..22825524362f472 100644 --- a/Doc/whatsnew/3.7.rst +++ b/Doc/whatsnew/3.7.rst @@ -261,7 +261,7 @@ instead of spaces. (Contributed by Xiang Zhang in :issue:`30103`.) zipapp ------ -Function :func:`zipapp.create_archive` now accepts an optional *filterfunc* +Function :func:`zipapp.create_archive` now accepts an optional *filter* argument, to allow the user to select which files should be included in the archive. diff --git a/Lib/test/test_zipapp.py b/Lib/test/test_zipapp.py index 4dbc83ad269552c..56cf37c9afa03b4 100644 --- a/Lib/test/test_zipapp.py +++ b/Lib/test/test_zipapp.py @@ -53,8 +53,8 @@ def test_create_archive_with_subdirs(self): self.assertIn('foo/', z.namelist()) self.assertIn('bar/', z.namelist()) - def test_create_archive_with_filterfunc(self): - # Test packing a directory and using filterfunc to specify + def test_create_archive_with_filter(self): + # Test packing a directory and using filter to specify # which files to include. def skip_pyc_files(path): return path.suffix != '.pyc' @@ -65,15 +65,15 @@ def skip_pyc_files(path): (source / 'test.pyc').touch() target = self.tmpdir / 'source.pyz' - zipapp.create_archive(source, target, filterfunc=skip_pyc_files) + zipapp.create_archive(source, target, filter=skip_pyc_files) with zipfile.ZipFile(target, 'r') as z: self.assertIn('__main__.py', z.namelist()) self.assertIn('test.py', z.namelist()) self.assertNotIn('test.pyc', z.namelist()) - def test_create_archive_filterfunc_exclude_dir(self): - # Test packing a directory and using filterfunc to exclude a - # subdirectory (ensures that the path supplied to filterfunc + 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 # is relative to the source location, as expected). def skip_dummy_dir(path): return path.parts[0] != 'dummy' @@ -85,7 +85,7 @@ def skip_dummy_dir(path): (source / 'dummy' / 'test2.py').touch() target = self.tmpdir / 'source.pyz' - zipapp.create_archive(source, target, filterfunc=skip_dummy_dir) + zipapp.create_archive(source, target, filter=skip_dummy_dir) with zipfile.ZipFile(target, 'r') as z: self.assertEqual(len(z.namelist()), 2) self.assertIn('__main__.py', z.namelist()) diff --git a/Lib/zipapp.py b/Lib/zipapp.py index 35ec6f3be3df660..51d0290a901ba8b 100644 --- a/Lib/zipapp.py +++ b/Lib/zipapp.py @@ -74,7 +74,7 @@ def _copy_archive(archive, new_archive, interpreter=None): def create_archive(source, target=None, interpreter=None, main=None, - filterfunc=None): + filter=None): """Create an application archive from SOURCE. The SOURCE can be the name of a directory, or a filename or a file-like @@ -136,7 +136,7 @@ def create_archive(source, target=None, interpreter=None, main=None, with zipfile.ZipFile(fd, 'w') as z: for child in source.rglob('*'): arcname = child.relative_to(source) - if filterfunc is None or filterfunc(arcname): + if filter is None or filter(arcname): z.write(child, arcname.as_posix()) if main_py: z.writestr('__main__.py', main_py.encode('utf-8'))