From dd227e2d3bec636967c180104b01df58961bfced Mon Sep 17 00:00:00 2001 From: beavailable Date: Thu, 9 Oct 2025 13:35:00 +0800 Subject: [PATCH 1/6] Correctly forward keyword arguments --- Doc/library/tarfile.rst | 3 +++ Lib/tarfile.py | 18 ++++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/Doc/library/tarfile.rst b/Doc/library/tarfile.rst index acaec5b592bf6e..dd170e4c821c38 100644 --- a/Doc/library/tarfile.rst +++ b/Doc/library/tarfile.rst @@ -137,6 +137,9 @@ Some facts and figures: a Zstandard dictionary used to improve compression of smaller amounts of data. + For modes ``'r:zst'`` and ``'r|zst'``, :func:`tarfile.open` accepts the keyword + arguments *options* and *zstd_dict* as well. + For special purposes, there is a second format for *mode*: ``'filemode|[compression]'``. :func:`tarfile.open` will return a :class:`TarFile` object that processes its data as a stream of blocks. No random seeking will diff --git a/Lib/tarfile.py b/Lib/tarfile.py index 7db3a40c9b33cf..261715e78734d5 100644 --- a/Lib/tarfile.py +++ b/Lib/tarfile.py @@ -338,7 +338,7 @@ class _Stream: """ def __init__(self, name, mode, comptype, fileobj, bufsize, - compresslevel, preset): + compresslevel, preset, level, options, zstd_dict): """Construct a _Stream object. """ self._extfileobj = True @@ -405,10 +405,10 @@ def __init__(self, name, mode, comptype, fileobj, bufsize, raise CompressionError("compression.zstd module is not available") from None if mode == "r": self.dbuf = b"" - self.cmp = zstd.ZstdDecompressor() + self.cmp = zstd.ZstdDecompressor(zstd_dict, options) self.exception = zstd.ZstdError else: - self.cmp = zstd.ZstdCompressor() + self.cmp = zstd.ZstdCompressor(level, options, zstd_dict) elif comptype != "tar": raise CompressionError("unknown compression type %r" % comptype) @@ -1929,10 +1929,20 @@ def not_compressed(comptype): if "preset" in kwargs and comptype not in ("xz",): raise ValueError("preset is only valid for w|xz mode") + if comptype not in ("zst",): + for arg in ("level", "options", "zstd_dict"): + if arg in kwargs: + raise ValueError( + f"{arg} is only valid for w:zst, x:zst and w|zst modes" + ) + compresslevel = kwargs.pop("compresslevel", 6) preset = kwargs.pop("preset", None) + level = kwargs.pop("level", None) + options = kwargs.pop("options", None) + zstd_dict = kwargs.pop("zstd_dict", None) stream = _Stream(name, filemode, comptype, fileobj, bufsize, - compresslevel, preset) + compresslevel, preset, level, options, zstd_dict) try: t = cls(name, filemode, stream, **kwargs) except: From 0d172d2c60b1bb15655086a9608cec1877872c5d Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Thu, 9 Oct 2025 05:42:08 +0000 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2025-10-09-05-42-07.gh-issue-139821.cHM3qC.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2025-10-09-05-42-07.gh-issue-139821.cHM3qC.rst diff --git a/Misc/NEWS.d/next/Library/2025-10-09-05-42-07.gh-issue-139821.cHM3qC.rst b/Misc/NEWS.d/next/Library/2025-10-09-05-42-07.gh-issue-139821.cHM3qC.rst new file mode 100644 index 00000000000000..1408a11cde32d5 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-10-09-05-42-07.gh-issue-139821.cHM3qC.rst @@ -0,0 +1 @@ +Make ``tarfile.open`` correctly forward keyword arguments for zstd in stream mode. From fabfa11b1283e15c292aca922fe67eae5765fa6b Mon Sep 17 00:00:00 2001 From: beavailable Date: Thu, 9 Oct 2025 13:54:51 +0800 Subject: [PATCH 3/6] Correct the error message --- Lib/tarfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/tarfile.py b/Lib/tarfile.py index 261715e78734d5..64ce865250844d 100644 --- a/Lib/tarfile.py +++ b/Lib/tarfile.py @@ -1933,7 +1933,7 @@ def not_compressed(comptype): for arg in ("level", "options", "zstd_dict"): if arg in kwargs: raise ValueError( - f"{arg} is only valid for w:zst, x:zst and w|zst modes" + f"{arg} is only valid for zstd compression" ) compresslevel = kwargs.pop("compresslevel", 6) From 11527ce71d30a66d75c7a95bf908fe6ae43bab8c Mon Sep 17 00:00:00 2001 From: beavailable Date: Sat, 11 Oct 2025 08:13:36 +0800 Subject: [PATCH 4/6] Use `**kwargs` in `_Stream` --- Lib/tarfile.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/Lib/tarfile.py b/Lib/tarfile.py index 64ce865250844d..68d0203875731e 100644 --- a/Lib/tarfile.py +++ b/Lib/tarfile.py @@ -337,8 +337,7 @@ class _Stream: _Stream is intended to be used only internally. """ - def __init__(self, name, mode, comptype, fileobj, bufsize, - compresslevel, preset, level, options, zstd_dict): + def __init__(self, name, mode, comptype, fileobj, bufsize, **kwargs): """Construct a _Stream object. """ self._extfileobj = True @@ -373,7 +372,7 @@ def __init__(self, name, mode, comptype, fileobj, bufsize, self.exception = zlib.error self._init_read_gz() else: - self._init_write_gz(compresslevel) + self._init_write_gz(kwargs["compresslevel"]) elif comptype == "bz2": try: @@ -385,7 +384,7 @@ def __init__(self, name, mode, comptype, fileobj, bufsize, self.cmp = bz2.BZ2Decompressor() self.exception = OSError else: - self.cmp = bz2.BZ2Compressor(compresslevel) + self.cmp = bz2.BZ2Compressor(kwargs["compresslevel"]) elif comptype == "xz": try: @@ -397,7 +396,7 @@ def __init__(self, name, mode, comptype, fileobj, bufsize, self.cmp = lzma.LZMADecompressor() self.exception = lzma.LZMAError else: - self.cmp = lzma.LZMACompressor(preset=preset) + self.cmp = lzma.LZMACompressor(kwargs["preset"]) elif comptype == "zst": try: from compression import zstd @@ -405,10 +404,13 @@ def __init__(self, name, mode, comptype, fileobj, bufsize, raise CompressionError("compression.zstd module is not available") from None if mode == "r": self.dbuf = b"" - self.cmp = zstd.ZstdDecompressor(zstd_dict, options) + self.cmp = zstd.ZstdDecompressor(kwargs["zstd_dict"], + kwargs["options"]) self.exception = zstd.ZstdError else: - self.cmp = zstd.ZstdCompressor(level, options, zstd_dict) + self.cmp = zstd.ZstdCompressor(kwargs["level"], + kwargs["options"], + kwargs["zstd_dict"]) elif comptype != "tar": raise CompressionError("unknown compression type %r" % comptype) @@ -1936,13 +1938,12 @@ def not_compressed(comptype): f"{arg} is only valid for zstd compression" ) - compresslevel = kwargs.pop("compresslevel", 6) - preset = kwargs.pop("preset", None) - level = kwargs.pop("level", None) - options = kwargs.pop("options", None) - zstd_dict = kwargs.pop("zstd_dict", None) stream = _Stream(name, filemode, comptype, fileobj, bufsize, - compresslevel, preset, level, options, zstd_dict) + compresslevel=kwargs.pop("compresslevel", 6), + preset=kwargs.pop("preset", None), + level=kwargs.pop("level", None), + options=kwargs.pop("options", None), + zstd_dict=kwargs.pop("zstd_dict", None)) try: t = cls(name, filemode, stream, **kwargs) except: From 8d6ddb396bdceae366a78f0b2b11e0c38dc0a5c2 Mon Sep 17 00:00:00 2001 From: beavailable Date: Sat, 11 Oct 2025 08:47:39 +0800 Subject: [PATCH 5/6] Pass `preset` as a keyword argument --- Lib/tarfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/tarfile.py b/Lib/tarfile.py index 68d0203875731e..5d0e48cd366244 100644 --- a/Lib/tarfile.py +++ b/Lib/tarfile.py @@ -396,7 +396,7 @@ def __init__(self, name, mode, comptype, fileobj, bufsize, **kwargs): self.cmp = lzma.LZMADecompressor() self.exception = lzma.LZMAError else: - self.cmp = lzma.LZMACompressor(kwargs["preset"]) + self.cmp = lzma.LZMACompressor(preset=kwargs["preset"]) elif comptype == "zst": try: from compression import zstd From c4768630f1d3dee754c261c9d2732f566583be8b Mon Sep 17 00:00:00 2001 From: beavailable Date: Tue, 14 Oct 2025 09:07:20 +0800 Subject: [PATCH 6/6] Use `kwargs.get()` to get the arguments --- Lib/tarfile.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Lib/tarfile.py b/Lib/tarfile.py index 5d0e48cd366244..f618f826a8bb39 100644 --- a/Lib/tarfile.py +++ b/Lib/tarfile.py @@ -372,7 +372,7 @@ def __init__(self, name, mode, comptype, fileobj, bufsize, **kwargs): self.exception = zlib.error self._init_read_gz() else: - self._init_write_gz(kwargs["compresslevel"]) + self._init_write_gz(kwargs.get("compresslevel")) elif comptype == "bz2": try: @@ -384,7 +384,7 @@ def __init__(self, name, mode, comptype, fileobj, bufsize, **kwargs): self.cmp = bz2.BZ2Decompressor() self.exception = OSError else: - self.cmp = bz2.BZ2Compressor(kwargs["compresslevel"]) + self.cmp = bz2.BZ2Compressor(kwargs.get("compresslevel")) elif comptype == "xz": try: @@ -396,7 +396,7 @@ def __init__(self, name, mode, comptype, fileobj, bufsize, **kwargs): self.cmp = lzma.LZMADecompressor() self.exception = lzma.LZMAError else: - self.cmp = lzma.LZMACompressor(preset=kwargs["preset"]) + self.cmp = lzma.LZMACompressor(preset=kwargs.get("preset")) elif comptype == "zst": try: from compression import zstd @@ -404,13 +404,13 @@ def __init__(self, name, mode, comptype, fileobj, bufsize, **kwargs): raise CompressionError("compression.zstd module is not available") from None if mode == "r": self.dbuf = b"" - self.cmp = zstd.ZstdDecompressor(kwargs["zstd_dict"], - kwargs["options"]) + self.cmp = zstd.ZstdDecompressor(kwargs.get("zstd_dict"), + kwargs.get("options")) self.exception = zstd.ZstdError else: - self.cmp = zstd.ZstdCompressor(kwargs["level"], - kwargs["options"], - kwargs["zstd_dict"]) + self.cmp = zstd.ZstdCompressor(kwargs.get("level"), + kwargs.get("options"), + kwargs.get("zstd_dict")) elif comptype != "tar": raise CompressionError("unknown compression type %r" % comptype)