https://github.com/python/cpython/commit/025fe552c462796b32d57c34987355d04aa55ed5 commit: 025fe552c462796b32d57c34987355d04aa55ed5 branch: main author: Emma Smith <[email protected]> committer: StanFromIreland <[email protected]> date: 2026-10-30T10:35:44Z summary:
gh-84027: Add `whl` as an unpack format in `shutil` (#140385) Co-authored-by: Emma Smith <[email protected]> Co-authored-by: Stan Ulbrych <[email protected]> files: A Misc/NEWS.d/next/Library/2025-10-20-13-08-04.gh-issue-84027.HjBO0B.rst M Doc/library/shutil.rst M Doc/whatsnew/3.16.rst M Lib/shutil.py M Lib/test/test_shutil.py diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index 8f3c90719a9a15..2d3edd8e5aebfc 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -753,7 +753,7 @@ provided. They rely on the :mod:`zipfile` and :mod:`tarfile` modules. unpacked. If not provided, the current working directory is used. *format* is the archive format: one of "zip", "tar", "gztar", "bztar", - "xztar", or "zstdtar". Or any other format registered with + "xztar", "zstdtar", or "whl". Or any other format registered with :func:`register_unpack_format`. If not provided, :func:`unpack_archive` will use the archive file name extension and see if an unpacker was registered for that extension. In case none is found, @@ -786,6 +786,9 @@ provided. They rely on the :mod:`zipfile` and :mod:`tarfile` modules. .. versionchanged:: 3.12 Added the *filter* argument. + .. versionchanged:: next + Added the "whl" format. + .. function:: register_unpack_format(name, extensions, function[, extra_args[, description]]) Registers an unpack format. *name* is the name of the format and @@ -827,10 +830,16 @@ provided. They rely on the :mod:`zipfile` and :mod:`tarfile` modules. - *xztar*: xz'ed tar-file (if the :mod:`lzma` module is available). - *zstdtar*: Zstandard compressed tar-file (if the :mod:`compression.zstd` module is available). + - *whl*: `wheel file <https://packaging.python.org/en/latest/discussions/package-formats/#what-is-a-wheel>`__ + (unpacking compressed files works only if the corresponding module is + available). You can register new formats or provide your own unpacker for any existing formats, by using :func:`register_unpack_format`. + .. versionchanged:: next + Added the *whl* format. + .. _shutil-archiving-example: diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index ce001d07731bcc..99fd00938ab8bc 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -681,6 +681,9 @@ shutil :func:`shutil.copystat`, e.g. when copying a read-only tree. (Contributed by Ćukasz Langa in :gh:`89721`.) +* :func:`shutil.unpack_archive` can now unpack wheel files using the ``whl`` format. + (Contributed by Emma Harper Smith and Stan Ulbrych in :gh:`84027`.) + socket ------ diff --git a/Lib/shutil.py b/Lib/shutil.py index f678601e6758eb..f0057164c7ce35 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -1376,6 +1376,7 @@ def _unpack_tarfile(filename, extract_dir, *, filter=None): _UNPACK_FORMATS = { 'tar': (['.tar'], _unpack_tarfile, [], "uncompressed tar file"), 'zip': (['.zip'], _unpack_zipfile, [], "ZIP file"), + 'whl': (['.whl'], _unpack_zipfile, [], "Wheel file"), } if _ZLIB_SUPPORTED: @@ -1410,9 +1411,9 @@ def unpack_archive(filename, extract_dir=None, format=None, *, filter=None): is unpacked. If not provided, the current working directory is used. `format` is the archive format: one of "zip", "tar", "gztar", "bztar", - "xztar", or "zstdtar". Or any other registered format. If not provided, - unpack_archive will use the filename extension and see if an unpacker - was registered for that extension. + "xztar", "zstdtar", or "whl". Or any other registered format. If not + provided, unpack_archive will use the filename extension and see if an + unpacker was registered for that extension. In case none is found, a ValueError is raised. diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 1a1a77ecdaeb76..67f45167e680a9 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -2287,12 +2287,18 @@ def check_unpack_archive(self, format, **kwargs): self.check_unpack_archive_with_converter(format, FakePath, **kwargs) def check_unpack_archive_with_converter(self, format, converter, **kwargs): + make_format = kwargs.pop("make_format", format) + extension = kwargs.pop("extension", None) root_dir, base_dir = self._create_files() expected = rlistdir(root_dir) expected.remove('outer') base_name = os.path.join(self.mkdtemp(), 'archive') - filename = make_archive(base_name, format, root_dir, base_dir) + filename = make_archive(base_name, make_format, root_dir, base_dir) + if extension is not None: + new_filename = os.path.splitext(filename)[0] + extension + os.rename(filename, new_filename) + filename = new_filename # let's try to unpack it now tmpdir2 = self.mkdtemp() @@ -2340,6 +2346,13 @@ def test_unpack_archive_zip(self): with self.assertRaises(TypeError): self.check_unpack_archive('zip', filter='data') + @support.requires_zlib() + def test_unpack_archive_whl(self): + self.check_unpack_archive('whl', make_format='zip', extension='.whl') + with self.assertRaises(TypeError): + self.check_unpack_archive('whl', filter='data', make_format='zip', + extension='.whl') + def test_unpack_archive_zip_badpaths(self): srcdir = self.mkdtemp() zipname = os.path.join(srcdir, 'test.zip') diff --git a/Misc/NEWS.d/next/Library/2025-10-20-13-08-04.gh-issue-84027.HjBO0B.rst b/Misc/NEWS.d/next/Library/2025-10-20-13-08-04.gh-issue-84027.HjBO0B.rst new file mode 100644 index 00000000000000..060eeabd903bbb --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-10-20-13-08-04.gh-issue-84027.HjBO0B.rst @@ -0,0 +1 @@ +:mod:`shutil` now can unpack wheel packages using the ``whl`` format. _______________________________________________ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3//lists/python-checkins.python.org Member address: [email protected]
