From a4f19a42e9a4ae3f44661ca831d2be183adb9cb3 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith [Google LLC]" Date: Wed, 18 Jan 2023 18:09:54 -0800 Subject: [PATCH 01/13] gh-101144: Allow open and read_text encoding to be positional. As was the behavior in 3.9 and earlier. The fix for https://github.com/python/cpython/issues/87817 introduced an API regression in 3.10.0b1. --- Lib/test/test_zipfile/test_path.py | 6 +++++- Lib/zipfile/_path.py | 6 ++++-- .../Library/2023-01-18-17-58-50.gh-issue-101144.FHd8Un.rst | 4 ++++ 3 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-01-18-17-58-50.gh-issue-101144.FHd8Un.rst diff --git a/Lib/test/test_zipfile/test_path.py b/Lib/test/test_zipfile/test_path.py index 02253c59e959fb..b961e657a7146d 100644 --- a/Lib/test/test_zipfile/test_path.py +++ b/Lib/test/test_zipfile/test_path.py @@ -145,7 +145,10 @@ def test_open(self, alpharep): a, b, g = root.iterdir() with a.open(encoding="utf-8") as strm: data = strm.read() - assert data == "content of a" + self.assertEqual(data, "content of a") + with a.open('r', "utf-8") as strm: # No gh-101144 TypeError + data = strm.read() + self.assertEqual(data, "content of a") def test_open_write(self): """ @@ -187,6 +190,7 @@ def test_read(self, alpharep): root = zipfile.Path(alpharep) a, b, g = root.iterdir() assert a.read_text(encoding="utf-8") == "content of a" + a.read_text("utf-8") # No TypeError per gh-101144. assert a.read_bytes() == b"content of a" @pass_alpharep diff --git a/Lib/zipfile/_path.py b/Lib/zipfile/_path.py index aea17b65b6aa2d..89823f6c1a252b 100644 --- a/Lib/zipfile/_path.py +++ b/Lib/zipfile/_path.py @@ -258,7 +258,8 @@ def open(self, mode='r', *args, pwd=None, **kwargs): raise ValueError("encoding args invalid for binary operation") return stream else: - kwargs["encoding"] = io.text_encoding(kwargs.get("encoding")) + if "encoding" in kwargs: + kwargs["encoding"] = io.text_encoding(kwargs["encoding"]) return io.TextIOWrapper(stream, *args, **kwargs) @property @@ -282,7 +283,8 @@ def filename(self): return pathlib.Path(self.root.filename).joinpath(self.at) def read_text(self, *args, **kwargs): - kwargs["encoding"] = io.text_encoding(kwargs.get("encoding")) + if "encoding" in kwargs: + kwargs["encoding"] = io.text_encoding(kwargs["encoding"]) with self.open('r', *args, **kwargs) as strm: return strm.read() diff --git a/Misc/NEWS.d/next/Library/2023-01-18-17-58-50.gh-issue-101144.FHd8Un.rst b/Misc/NEWS.d/next/Library/2023-01-18-17-58-50.gh-issue-101144.FHd8Un.rst new file mode 100644 index 00000000000000..6b34b1a24bc4b8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-01-18-17-58-50.gh-issue-101144.FHd8Un.rst @@ -0,0 +1,4 @@ +:func:`zipfile.Path.open` and :func:`zipfile.Path.read_text` accept +``encoding`` as a positional argument. This was the behavior in Python 3.9 and +earlier. 3.10 introduced an unintended regression where supplying it as a +positional argument would lead to a :exc:`TypeError`. From b495172864f24d3558d473432b59de6065518cfd Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Wed, 18 Jan 2023 18:14:53 -0800 Subject: [PATCH 02/13] news wording tweak --- .../Library/2023-01-18-17-58-50.gh-issue-101144.FHd8Un.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2023-01-18-17-58-50.gh-issue-101144.FHd8Un.rst b/Misc/NEWS.d/next/Library/2023-01-18-17-58-50.gh-issue-101144.FHd8Un.rst index 6b34b1a24bc4b8..e90d2331fd1295 100644 --- a/Misc/NEWS.d/next/Library/2023-01-18-17-58-50.gh-issue-101144.FHd8Un.rst +++ b/Misc/NEWS.d/next/Library/2023-01-18-17-58-50.gh-issue-101144.FHd8Un.rst @@ -1,4 +1,4 @@ :func:`zipfile.Path.open` and :func:`zipfile.Path.read_text` accept ``encoding`` as a positional argument. This was the behavior in Python 3.9 and -earlier. 3.10 introduced an unintended regression where supplying it as a -positional argument would lead to a :exc:`TypeError`. +earlier. 3.10 introduced a regression where supplying it as a positional +argument would lead to a :exc:`TypeError`. From e3040944a3b5feedb58af768f432c10fe6ab5ed6 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith [Google LLC]" Date: Wed, 18 Jan 2023 18:22:05 -0800 Subject: [PATCH 03/13] Explicitly handle encoding as an arg. This preserves the intent of https://github.com/python/cpython/issues/87817 while restoring the ability to pass `encoding` as a positional argument. --- Lib/zipfile/_path.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/Lib/zipfile/_path.py b/Lib/zipfile/_path.py index 89823f6c1a252b..a64f5f41ba4875 100644 --- a/Lib/zipfile/_path.py +++ b/Lib/zipfile/_path.py @@ -241,7 +241,7 @@ def __init__(self, root, at=""): self.root = FastLookup.make(root) self.at = at - def open(self, mode='r', *args, pwd=None, **kwargs): + def open(self, mode='r', encoding=None, *args, pwd=None, **kwargs): """ Open this entry as text or binary following the semantics of ``pathlib.Path.open()`` by passing arguments through @@ -254,13 +254,12 @@ def open(self, mode='r', *args, pwd=None, **kwargs): raise FileNotFoundError(self) stream = self.root.open(self.at, zip_mode, pwd=pwd) if 'b' in mode: - if args or kwargs: + if encoding is not None or args or kwargs: raise ValueError("encoding args invalid for binary operation") return stream else: - if "encoding" in kwargs: - kwargs["encoding"] = io.text_encoding(kwargs["encoding"]) - return io.TextIOWrapper(stream, *args, **kwargs) + encoding = io.text_encoding(encoding) + return io.TextIOWrapper(stream, *args, encoding=encoding, **kwargs) @property def name(self): @@ -282,10 +281,9 @@ def stem(self): def filename(self): return pathlib.Path(self.root.filename).joinpath(self.at) - def read_text(self, *args, **kwargs): - if "encoding" in kwargs: - kwargs["encoding"] = io.text_encoding(kwargs["encoding"]) - with self.open('r', *args, **kwargs) as strm: + def read_text(self, encoding=None, *args, **kwargs): + encoding = io.text_encoding(encoding) + with self.open('r', *args, encoding=encoding, **kwargs) as strm: return strm.read() def read_bytes(self): From 9caec6c6a2ab3b392a358cc3a1df34a74eae1116 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Wed, 18 Jan 2023 18:35:03 -0800 Subject: [PATCH 04/13] wording tweak in news. --- .../next/Library/2023-01-18-17-58-50.gh-issue-101144.FHd8Un.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2023-01-18-17-58-50.gh-issue-101144.FHd8Un.rst b/Misc/NEWS.d/next/Library/2023-01-18-17-58-50.gh-issue-101144.FHd8Un.rst index e90d2331fd1295..297652259949fc 100644 --- a/Misc/NEWS.d/next/Library/2023-01-18-17-58-50.gh-issue-101144.FHd8Un.rst +++ b/Misc/NEWS.d/next/Library/2023-01-18-17-58-50.gh-issue-101144.FHd8Un.rst @@ -1,4 +1,4 @@ -:func:`zipfile.Path.open` and :func:`zipfile.Path.read_text` accept +Make :func:`zipfile.Path.open` and :func:`zipfile.Path.read_text` also accept ``encoding`` as a positional argument. This was the behavior in Python 3.9 and earlier. 3.10 introduced a regression where supplying it as a positional argument would lead to a :exc:`TypeError`. From b4248b167e9b9a87fd98d9220beac07562616c39 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith [Google LLC]" Date: Wed, 18 Jan 2023 21:52:59 -0800 Subject: [PATCH 05/13] Explicitly test an unusual encoding to verify it passed through. --- Lib/test/test_zipfile/test_path.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_zipfile/test_path.py b/Lib/test/test_zipfile/test_path.py index b961e657a7146d..c85e6a1ab79a81 100644 --- a/Lib/test/test_zipfile/test_path.py +++ b/Lib/test/test_zipfile/test_path.py @@ -146,10 +146,25 @@ def test_open(self, alpharep): with a.open(encoding="utf-8") as strm: data = strm.read() self.assertEqual(data, "content of a") - with a.open('r', "utf-8") as strm: # No gh-101144 TypeError + with a.open('r', "utf-8") as strm: # not a kw, no gh-101144 TypeError data = strm.read() self.assertEqual(data, "content of a") + def test_open_encoding_utf16(self): + in_memory_file = io.BytesIO() + zf = zipfile.ZipFile(in_memory_file, "w") + zf.writestr("path/16.txt", "This was utf-16".encode("utf-16")) + zf.filename = "test_open_utf16.zip" + root = zipfile.Path(zf) + path, = root.iterdir() + u16 = path.joinpath("16.txt") + with u16.open('r', "utf-16") as strm: + data = strm.read() + self.assertEqual(data, "This was utf-16") + with u16.open(encoding="utf-16") as strm: + data = strm.read() + self.assertEqual(data, "This was utf-16") + def test_open_write(self): """ If the zipfile is open for write, it should be possible to @@ -190,7 +205,7 @@ def test_read(self, alpharep): root = zipfile.Path(alpharep) a, b, g = root.iterdir() assert a.read_text(encoding="utf-8") == "content of a" - a.read_text("utf-8") # No TypeError per gh-101144. + a.read_text("utf-8") # No positional arg TypeError per gh-101144. assert a.read_bytes() == b"content of a" @pass_alpharep From 32b252bf97d2305712c5c77a976210a77a81abbf Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith [Google LLC]" Date: Wed, 18 Jan 2023 21:58:42 -0800 Subject: [PATCH 06/13] simplify read_text, just defer encoding to open --- Lib/zipfile/_path.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/zipfile/_path.py b/Lib/zipfile/_path.py index a64f5f41ba4875..02d31356d89d0f 100644 --- a/Lib/zipfile/_path.py +++ b/Lib/zipfile/_path.py @@ -281,9 +281,8 @@ def stem(self): def filename(self): return pathlib.Path(self.root.filename).joinpath(self.at) - def read_text(self, encoding=None, *args, **kwargs): - encoding = io.text_encoding(encoding) - with self.open('r', *args, encoding=encoding, **kwargs) as strm: + def read_text(self, *args, **kwargs): + with self.open('r', *args, **kwargs) as strm: return strm.read() def read_bytes(self): From d1228b52bd813c0d1b64f6d4e984c6fc8c063710 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith [Google LLC]" Date: Wed, 18 Jan 2023 22:37:24 -0800 Subject: [PATCH 07/13] address review comments: fixup pos+kw logic, test it. --- Lib/test/test_zipfile/test_path.py | 16 ++++++++++++++++ Lib/zipfile/_path.py | 22 +++++++++++++++++----- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_zipfile/test_path.py b/Lib/test/test_zipfile/test_path.py index c85e6a1ab79a81..0a78c37d4dcce7 100644 --- a/Lib/test/test_zipfile/test_path.py +++ b/Lib/test/test_zipfile/test_path.py @@ -165,6 +165,22 @@ def test_open_encoding_utf16(self): data = strm.read() self.assertEqual(data, "This was utf-16") + def test_read_text_encoding_errors(self): + in_memory_file = io.BytesIO() + zf = zipfile.ZipFile(in_memory_file, "w") + zf.writestr("path/bad-utf8.bin", b"invalid utf-8: \xff\xff.") + zf.filename = "test_read_text_encoding_errors.zip" + root = zipfile.Path(zf) + path, = root.iterdir() + u16 = path.joinpath("bad-utf8.bin") + with self.assertRaises(UnicodeDecodeError): + u16.read_text(encoding="utf-8", errors="strict") # both keywords + data = u16.read_text("utf-8", errors="ignore") # errors keyword + self.assertEqual(data, "invalid utf-8: .") + # encoding= both positional and keyword is an error; gh-101144. + with self.assertRaisesRegex(TypeError, "encoding"): + data = u16.read_text("utf-8", encoding="utf-8") + def test_open_write(self): """ If the zipfile is open for write, it should be possible to diff --git a/Lib/zipfile/_path.py b/Lib/zipfile/_path.py index 02d31356d89d0f..7bba4d360930b8 100644 --- a/Lib/zipfile/_path.py +++ b/Lib/zipfile/_path.py @@ -241,7 +241,7 @@ def __init__(self, root, at=""): self.root = FastLookup.make(root) self.at = at - def open(self, mode='r', encoding=None, *args, pwd=None, **kwargs): + def open(self, mode='r', *args, pwd=None, **kwargs): """ Open this entry as text or binary following the semantics of ``pathlib.Path.open()`` by passing arguments through @@ -254,12 +254,24 @@ def open(self, mode='r', encoding=None, *args, pwd=None, **kwargs): raise FileNotFoundError(self) stream = self.root.open(self.at, zip_mode, pwd=pwd) if 'b' in mode: - if encoding is not None or args or kwargs: + if args or kwargs: raise ValueError("encoding args invalid for binary operation") return stream - else: - encoding = io.text_encoding(encoding) - return io.TextIOWrapper(stream, *args, encoding=encoding, **kwargs) + # Text mode: + encoding = None + if args: + # Per io.TextIOWrapper, encoding is the first positional arg. + # Our API is to pass all *args and **kwargs to TextIOWrapper. + # Extract it so we can process it. + encoding = args[0] + args = args[1:] + # We must check this manually as we extract it for processing. + if "encoding" in kwargs: + raise TypeError( + "argument ('encoding') given by name and position (1)") + encoding = kwargs.pop("encoding", encoding) + encoding = io.text_encoding(encoding) + return io.TextIOWrapper(stream, encoding, *args, **kwargs) @property def name(self): From 8ee213e1352aef36abfb8f445199ccbbee8ef1bf Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith [Google LLC]" Date: Thu, 19 Jan 2023 15:17:41 -0800 Subject: [PATCH 08/13] Apply jaraco's suggestion: an encoding extraction method. --- Lib/test/test_zipfile/test_path.py | 17 +++++++++++++---- Lib/zipfile/_path.py | 21 +++++++-------------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/Lib/test/test_zipfile/test_path.py b/Lib/test/test_zipfile/test_path.py index 0a78c37d4dcce7..a12ada750945fa 100644 --- a/Lib/test/test_zipfile/test_path.py +++ b/Lib/test/test_zipfile/test_path.py @@ -165,7 +165,7 @@ def test_open_encoding_utf16(self): data = strm.read() self.assertEqual(data, "This was utf-16") - def test_read_text_encoding_errors(self): + def test_open_encoding_errors(self): in_memory_file = io.BytesIO() zf = zipfile.ZipFile(in_memory_file, "w") zf.writestr("path/bad-utf8.bin", b"invalid utf-8: \xff\xff.") @@ -173,14 +173,23 @@ def test_read_text_encoding_errors(self): root = zipfile.Path(zf) path, = root.iterdir() u16 = path.joinpath("bad-utf8.bin") - with self.assertRaises(UnicodeDecodeError): - u16.read_text(encoding="utf-8", errors="strict") # both keywords - data = u16.read_text("utf-8", errors="ignore") # errors keyword + + # encoding= as a positional argument for gh-101144. + data = u16.read_text("utf-8", errors="ignore") self.assertEqual(data, "invalid utf-8: .") + with u16.open("r", "utf-8", errors="surrogateescape") as f: + self.assertEqual(f.read(), "invalid utf-8: \udcff\udcff.") + # encoding= both positional and keyword is an error; gh-101144. with self.assertRaisesRegex(TypeError, "encoding"): data = u16.read_text("utf-8", encoding="utf-8") + # both keyword arguments work. + with u16.open("r", encoding="utf-8", errors="strict") as f: + # error during decoding with wrong codec. + with self.assertRaises(UnicodeDecodeError): + f.read() + def test_open_write(self): """ If the zipfile is open for write, it should be possible to diff --git a/Lib/zipfile/_path.py b/Lib/zipfile/_path.py index 7bba4d360930b8..32e7422543b12d 100644 --- a/Lib/zipfile/_path.py +++ b/Lib/zipfile/_path.py @@ -148,6 +148,10 @@ def _name_set(self): return self.__lookup +def _extract_text_encoding(encoding=None, *args, **kwargs): + return io.text_encoding(encoding, 3), args, kwargs + + class Path: """ A pathlib-compatible interface for zip files. @@ -258,19 +262,7 @@ def open(self, mode='r', *args, pwd=None, **kwargs): raise ValueError("encoding args invalid for binary operation") return stream # Text mode: - encoding = None - if args: - # Per io.TextIOWrapper, encoding is the first positional arg. - # Our API is to pass all *args and **kwargs to TextIOWrapper. - # Extract it so we can process it. - encoding = args[0] - args = args[1:] - # We must check this manually as we extract it for processing. - if "encoding" in kwargs: - raise TypeError( - "argument ('encoding') given by name and position (1)") - encoding = kwargs.pop("encoding", encoding) - encoding = io.text_encoding(encoding) + encoding, args, kwargs = _extract_text_encoding(*args, **kwargs) return io.TextIOWrapper(stream, encoding, *args, **kwargs) @property @@ -294,7 +286,8 @@ def filename(self): return pathlib.Path(self.root.filename).joinpath(self.at) def read_text(self, *args, **kwargs): - with self.open('r', *args, **kwargs) as strm: + encoding, args, kwargs = _extract_text_encoding(*args, **kwargs) + with self.open('r', encoding, *args, **kwargs) as strm: return strm.read() def read_bytes(self): From 25dd5bd476f37107f782db1cb32c3bf300cf2ec2 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith [Google LLC]" Date: Thu, 19 Jan 2023 16:22:52 -0800 Subject: [PATCH 09/13] Test EncodingWarning blame of open & read_text. --- Lib/test/test_zipfile/test_path.py | 28 ++++++++++++++++++++++++---- Lib/zipfile/_path.py | 1 + 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_zipfile/test_path.py b/Lib/test/test_zipfile/test_path.py index a12ada750945fa..eb26002f8db237 100644 --- a/Lib/test/test_zipfile/test_path.py +++ b/Lib/test/test_zipfile/test_path.py @@ -1,11 +1,12 @@ import io -import zipfile +import itertools import contextlib import pathlib -import unittest -import string import pickle -import itertools +import string +from test.support.script_helper import assert_python_ok +import unittest +import zipfile from ._test_params import parameterize, Invoked from ._functools import compose @@ -190,6 +191,25 @@ def test_open_encoding_errors(self): with self.assertRaises(UnicodeDecodeError): f.read() + def test_encoding_warnings(self): + """EncodingWarning must blame the read_text and open calls.""" + code = '''\ +import io, zipfile +with zipfile.ZipFile(io.BytesIO(), "w") as zf: + zf.filename = '' + zf.writestr("path/file.txt", b"Spanish Inquisition") + root = zipfile.Path(zf) + path, = root.iterdir() + file_path = path.joinpath("file.txt") + unused = file_path.read_text() # should warn + file_path.open("r").close() # should warn +''' + proc = assert_python_ok('-X', 'warn_default_encoding', '-c', code) + warnings = proc.err.splitlines() + self.assertEqual(len(warnings), 2, proc.err) + self.assertRegex(warnings[0], rb"^:8: EncodingWarning:") + self.assertRegex(warnings[1], rb"^:9: EncodingWarning:") + def test_open_write(self): """ If the zipfile is open for write, it should be possible to diff --git a/Lib/zipfile/_path.py b/Lib/zipfile/_path.py index 32e7422543b12d..7c7a6a0e2c0d32 100644 --- a/Lib/zipfile/_path.py +++ b/Lib/zipfile/_path.py @@ -149,6 +149,7 @@ def _name_set(self): def _extract_text_encoding(encoding=None, *args, **kwargs): + # stacklevel=3 so that the caller of the caller see any warning. return io.text_encoding(encoding, 3), args, kwargs From e9b373fb9a0b17d86ee348391098ce3f7e36f54e Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith [Google LLC]" Date: Thu, 19 Jan 2023 16:34:08 -0800 Subject: [PATCH 10/13] Surface the behavior in the zipfile.Path docs. --- Doc/library/zipfile.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Doc/library/zipfile.rst b/Doc/library/zipfile.rst index ef6934483bcd31..26044ad3e4c7a6 100644 --- a/Doc/library/zipfile.rst +++ b/Doc/library/zipfile.rst @@ -551,6 +551,12 @@ Path objects are traversable using the ``/`` operator or ``joinpath``. Added support for text and binary modes for open. Default mode is now text. + .. versionchanged:: 3.11.2 + The ``encoding`` parameter can be supplied as the first positional + argument again without causing a :exc:`TypeError`. As it could in 3.9 and + earlier. Code needing to be compatible with unpatched 3.10 and 3.11 + versions should always pass ``encoding=`` as a keyword argument. + .. method:: Path.iterdir() Enumerate the children of the current directory. @@ -596,6 +602,12 @@ Path objects are traversable using the ``/`` operator or ``joinpath``. :class:`io.TextIOWrapper` (except ``buffer``, which is implied by the context). + .. versionchanged:: 3.11.2 + The ``encoding`` parameter can be supplied as the first positional + argument again without causing a :exc:`TypeError`. As it could in 3.9 and + earlier. Code needing to be compatible with unpatched 3.10 and 3.11 + versions should always pass ``encoding=`` as a keyword argument. + .. method:: Path.read_bytes() Read the current file as bytes. From a12243bf47bd3e25616ce7f4ba7bad08c876d79f Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith [Google LLC]" Date: Thu, 19 Jan 2023 16:36:13 -0800 Subject: [PATCH 11/13] simplify the doc wording. --- Doc/library/zipfile.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Doc/library/zipfile.rst b/Doc/library/zipfile.rst index 26044ad3e4c7a6..51bd9447bec6fc 100644 --- a/Doc/library/zipfile.rst +++ b/Doc/library/zipfile.rst @@ -552,10 +552,10 @@ Path objects are traversable using the ``/`` operator or ``joinpath``. mode is now text. .. versionchanged:: 3.11.2 - The ``encoding`` parameter can be supplied as the first positional - argument again without causing a :exc:`TypeError`. As it could in 3.9 and - earlier. Code needing to be compatible with unpatched 3.10 and 3.11 - versions should always pass ``encoding=`` as a keyword argument. + The ``encoding`` parameter can be supplied as a positional argument + without causing a :exc:`TypeError`. As it could in 3.9 and earlier. Code + needing to be compatible with unpatched 3.10 and 3.11 versions should + always pass ``encoding=`` as a keyword argument. .. method:: Path.iterdir() @@ -603,10 +603,10 @@ Path objects are traversable using the ``/`` operator or ``joinpath``. implied by the context). .. versionchanged:: 3.11.2 - The ``encoding`` parameter can be supplied as the first positional - argument again without causing a :exc:`TypeError`. As it could in 3.9 and - earlier. Code needing to be compatible with unpatched 3.10 and 3.11 - versions should always pass ``encoding=`` as a keyword argument. + The ``encoding`` parameter can be supplied as a positional argument + without causing a :exc:`TypeError`. As it could in 3.9 and earlier. Code + needing to be compatible with unpatched 3.10 and 3.11 versions should + always pass ``encoding=`` as a keyword argument. .. method:: Path.read_bytes() From 35abedba940c45b2075d0bf6134d8867585c7358 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith [Google LLC]" Date: Thu, 19 Jan 2023 17:53:53 -0800 Subject: [PATCH 12/13] more doc wording updates based on review. --- Doc/library/zipfile.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Doc/library/zipfile.rst b/Doc/library/zipfile.rst index 51bd9447bec6fc..1c516723f04a33 100644 --- a/Doc/library/zipfile.rst +++ b/Doc/library/zipfile.rst @@ -553,9 +553,9 @@ Path objects are traversable using the ``/`` operator or ``joinpath``. .. versionchanged:: 3.11.2 The ``encoding`` parameter can be supplied as a positional argument - without causing a :exc:`TypeError`. As it could in 3.9 and earlier. Code - needing to be compatible with unpatched 3.10 and 3.11 versions should - always pass ``encoding=`` as a keyword argument. + without causing a :exc:`TypeError`. As it could in 3.9. Code needing to + be compatible with unpatched 3.10 and 3.11 versions must pass all + :class:`io.TextIOWrapper` arguments, ``encoding`` included, as keywords. .. method:: Path.iterdir() @@ -604,9 +604,9 @@ Path objects are traversable using the ``/`` operator or ``joinpath``. .. versionchanged:: 3.11.2 The ``encoding`` parameter can be supplied as a positional argument - without causing a :exc:`TypeError`. As it could in 3.9 and earlier. Code - needing to be compatible with unpatched 3.10 and 3.11 versions should - always pass ``encoding=`` as a keyword argument. + without causing a :exc:`TypeError`. As it could in 3.9. Code needing to + be compatible with unpatched 3.10 and 3.11 versions must pass all + :class:`io.TextIOWrapper` arguments, ``encoding`` included, as keywords. .. method:: Path.read_bytes() From 88ecd561bc27df97abec30be373a12a089e4fbf4 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith [Google LLC]" Date: Thu, 19 Jan 2023 17:59:49 -0800 Subject: [PATCH 13/13] use () on single element tuple assignment for clarity. --- Lib/test/test_zipfile/test_path.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_zipfile/test_path.py b/Lib/test/test_zipfile/test_path.py index eb26002f8db237..3086fd2080a97d 100644 --- a/Lib/test/test_zipfile/test_path.py +++ b/Lib/test/test_zipfile/test_path.py @@ -157,7 +157,7 @@ def test_open_encoding_utf16(self): zf.writestr("path/16.txt", "This was utf-16".encode("utf-16")) zf.filename = "test_open_utf16.zip" root = zipfile.Path(zf) - path, = root.iterdir() + (path,) = root.iterdir() u16 = path.joinpath("16.txt") with u16.open('r', "utf-16") as strm: data = strm.read() @@ -172,7 +172,7 @@ def test_open_encoding_errors(self): zf.writestr("path/bad-utf8.bin", b"invalid utf-8: \xff\xff.") zf.filename = "test_read_text_encoding_errors.zip" root = zipfile.Path(zf) - path, = root.iterdir() + (path,) = root.iterdir() u16 = path.joinpath("bad-utf8.bin") # encoding= as a positional argument for gh-101144. @@ -199,7 +199,7 @@ def test_encoding_warnings(self): zf.filename = '' zf.writestr("path/file.txt", b"Spanish Inquisition") root = zipfile.Path(zf) - path, = root.iterdir() + (path,) = root.iterdir() file_path = path.joinpath("file.txt") unused = file_path.read_text() # should warn file_path.open("r").close() # should warn