From fda9cbc0dbf137e90b136d5147dd2e3331406407 Mon Sep 17 00:00:00 2001 From: jw9829 Date: Sat, 26 Sep 2026 22:53:45 -0400 Subject: [PATCH 1/4] [codecs] Allow keyword errors for decoders --- stdlib/codecs.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/codecs.pyi b/stdlib/codecs.pyi index ee1ab25bac47..94a1fdd25577 100644 --- a/stdlib/codecs.pyi +++ b/stdlib/codecs.pyi @@ -101,7 +101,7 @@ class _Encoder(Protocol): @type_check_only class _Decoder(Protocol): - def __call__(self, input: ReadableBuffer, errors: str = ..., /) -> tuple[str, int]: ... # signature of Codec().decode + def __call__(self, input: ReadableBuffer, /, errors: str = ...) -> tuple[str, int]: ... # signature of Codec().decode @type_check_only class _StreamReader(Protocol): From 62b97fc932f924802fbd18ba4364a627ebe09728 Mon Sep 17 00:00:00 2001 From: jw9829 Date: Sat, 26 Sep 2026 22:53:46 -0400 Subject: [PATCH 2/4] [codecs] Add decoder keyword regression --- stdlib/@tests/test_cases/check_codecs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/stdlib/@tests/test_cases/check_codecs.py b/stdlib/@tests/test_cases/check_codecs.py index 19e663ceeaaf..76411fa6dc50 100644 --- a/stdlib/@tests/test_cases/check_codecs.py +++ b/stdlib/@tests/test_cases/check_codecs.py @@ -7,6 +7,7 @@ assert_type(codecs.decode(b"x", "unicode-escape"), str) assert_type(codecs.decode(b"x", "utf-8"), str) +assert_type(codecs.lookup("UTF-8").decode(b"potato", errors="replace"), tuple[str, int]) codecs.decode("x", "utf-8") # type: ignore assert_type(codecs.decode("ab", "hex"), bytes) From fce814fa03953a8ed752d3356bffaebf42ea50b9 Mon Sep 17 00:00:00 2001 From: jw9829 <245427686+jw9829@users.noreply.github.com> Date: Sun, 27 Sep 2026 17:20:26 -0400 Subject: [PATCH 3/4] [codecs] Limit keyword errors to UTF-8 --- stdlib/@tests/test_cases/check_codecs.py | 1 + stdlib/_codecs.pyi | 5 +++++ stdlib/codecs.pyi | 11 ++++++++++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/stdlib/@tests/test_cases/check_codecs.py b/stdlib/@tests/test_cases/check_codecs.py index 76411fa6dc50..2b5579d36444 100644 --- a/stdlib/@tests/test_cases/check_codecs.py +++ b/stdlib/@tests/test_cases/check_codecs.py @@ -8,6 +8,7 @@ assert_type(codecs.decode(b"x", "utf-8"), str) assert_type(codecs.lookup("UTF-8").decode(b"potato", errors="replace"), tuple[str, int]) +codecs.lookup("ascii").decode(b"potato", errors="replace") # type: ignore codecs.decode("x", "utf-8") # type: ignore assert_type(codecs.decode("ab", "hex"), bytes) diff --git a/stdlib/_codecs.pyi b/stdlib/_codecs.pyi index 449e9e24af9b..90f3d1d8eab9 100644 --- a/stdlib/_codecs.pyi +++ b/stdlib/_codecs.pyi @@ -70,6 +70,11 @@ def decode(obj: str, encoding: Literal["hex", "hex_codec"], errors: str = "stric @overload def decode(obj: ReadableBuffer, encoding: str = "utf-8", errors: str = "strict") -> str: ... +_UTF8Encoding: TypeAlias = Literal["UTF-8", "utf-8", "UTF_8", "utf_8", "UTF8", "utf8"] + +@overload +def lookup(encoding: _UTF8Encoding, /) -> codecs._UTF8CodecInfo: ... +@overload def lookup(encoding: str, /) -> codecs.CodecInfo: ... def charmap_build(map: str, /) -> dict[int, int] | _EncodingMap: ... def ascii_decode(data: ReadableBuffer, errors: str | None = None, /) -> tuple[str, int]: ... diff --git a/stdlib/codecs.pyi b/stdlib/codecs.pyi index 94a1fdd25577..1e6fc8c94ecc 100644 --- a/stdlib/codecs.pyi +++ b/stdlib/codecs.pyi @@ -101,7 +101,7 @@ class _Encoder(Protocol): @type_check_only class _Decoder(Protocol): - def __call__(self, input: ReadableBuffer, /, errors: str = ...) -> tuple[str, int]: ... # signature of Codec().decode + def __call__(self, input: ReadableBuffer, errors: str = ..., /) -> tuple[str, int]: ... # signature of Codec().decode @type_check_only class _StreamReader(Protocol): @@ -182,6 +182,15 @@ else: _is_text_encoding: bool | None = None, ) -> Self: ... +@type_check_only +class _UTF8Decoder(Protocol): + def __call__(self, input: ReadableBuffer, /, errors: str = ...) -> tuple[str, int]: ... + +@type_check_only +class _UTF8CodecInfo(CodecInfo): + @property + def decode(self) -> _UTF8Decoder: ... + def getencoder(encoding: str) -> _Encoder: ... def getdecoder(encoding: str) -> _Decoder: ... def getincrementalencoder(encoding: str) -> _IncrementalEncoder: ... From 5d2bbf982611a07d4fc5d38c32d955c0f39c9c00 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 27 Sep 2026 21:24:25 +0000 Subject: [PATCH 4/4] [pre-commit.ci] auto fixes from pre-commit.com hooks --- stdlib/_codecs.pyi | 1 + 1 file changed, 1 insertion(+) diff --git a/stdlib/_codecs.pyi b/stdlib/_codecs.pyi index 90f3d1d8eab9..f3a9cee04ad5 100644 --- a/stdlib/_codecs.pyi +++ b/stdlib/_codecs.pyi @@ -76,6 +76,7 @@ _UTF8Encoding: TypeAlias = Literal["UTF-8", "utf-8", "UTF_8", "utf_8", "UTF8", " def lookup(encoding: _UTF8Encoding, /) -> codecs._UTF8CodecInfo: ... @overload def lookup(encoding: str, /) -> codecs.CodecInfo: ... + def charmap_build(map: str, /) -> dict[int, int] | _EncodingMap: ... def ascii_decode(data: ReadableBuffer, errors: str | None = None, /) -> tuple[str, int]: ... def ascii_encode(str: str, errors: str | None = None, /) -> tuple[bytes, int]: ...