From 0c97a2bcd6d48b57b26ae76bcc6502181ee4653d Mon Sep 17 00:00:00 2001 From: Kurt McKee Date: Mon, 29 Jun 2026 08:28:58 -0500 Subject: [PATCH] Begin type-checking `valkey._parsers` This commit introduces the following changes: * Configure mypy to enable type-checking of `valkey._parsers.encoders` * Fully type-annotate `valkey._parsers.encoders` Running `mypy --strict` shows that there are no remaining errors in the `valkey._parsers.encoders` module. Signed-off-by: Kurt McKee --- pyproject.toml | 17 +++++++++++- valkey/_parsers/encoders.py | 55 +++++++++++++++++++++++++++++++++++-- 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3343f3ea..d101e9fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -117,9 +117,12 @@ packages = ["valkey"] sqlite_cache = true [[tool.mypy.overrides]] -# The goal is to eliminate the `tool.mypy.overrides` section. +# The goal is to eliminate the `tool.mypy.overrides` sections. # This will be accomplished by resolving type annotation issues # in the modules listed below. +# +# Type checking may be re-enabled for specific submodules +# in a second "overrides" section, below. ignore_errors = true module = [ "valkey.client", @@ -129,3 +132,15 @@ module = [ "valkey.asyncio.*", "valkey._parsers.*", ] + + +[[tool.mypy.overrides]] +# This config section re-enables type-checking for submodules. +# For example, if all submodules in "valkey.x.*" are disabled above, +# this section might re-enable type-checking for "valkey.x.foo" +# as type annotations are added, corrected, and tested +# in "valkey.x.*", submodule by submodule. +ignore_errors = false +module = [ + "valkey._parsers.encoders", +] diff --git a/valkey/_parsers/encoders.py b/valkey/_parsers/encoders.py index 6fdf0ad8..1372a62c 100644 --- a/valkey/_parsers/encoders.py +++ b/valkey/_parsers/encoders.py @@ -1,17 +1,48 @@ +from __future__ import annotations + +import sys +from typing import Any, Literal, overload + from ..exceptions import DataError +if sys.version_info >= (3, 11): + from typing import Never +else: + from typing_extensions import Never + class Encoder: "Encode strings to bytes-like and decode bytes-like to strings" __slots__ = "encoding", "encoding_errors", "decode_responses" - def __init__(self, encoding, encoding_errors, decode_responses): + def __init__( + self, + encoding: str, + encoding_errors: ( + Literal["ignore", "replace", "strict", "xmlcharrefreplace"] | str + ), + decode_responses: bool, + ) -> None: self.encoding = encoding self.encoding_errors = encoding_errors self.decode_responses = decode_responses - def encode(self, value): + @overload + def encode(self, value: memoryview[bytes]) -> memoryview[bytes]: ... + + @overload + def encode(self, value: bool) -> Never: ... + + @overload + def encode(self, value: bytes | int | float | str) -> bytes: ... + + @overload + def encode(self, value: Any) -> Never: ... + + def encode( + self, value: bytes | memoryview[bytes] | int | float | str + ) -> bytes | memoryview[bytes]: "Return a bytestring or bytes-like representation of the value" if isinstance(value, (bytes, memoryview)): return value @@ -34,7 +65,25 @@ def encode(self, value): value = value.encode(self.encoding, self.encoding_errors) return value - def decode(self, value, force=False): + @overload + def decode( + self, + value: bytes | memoryview[bytes] | str, + force: Literal[True], + ) -> str: ... + + @overload + def decode( + self, + value: bytes | memoryview[bytes] | str, + force: Literal[False], + ) -> bytes | memoryview[bytes] | str: ... + + def decode( + self, + value: bytes | memoryview[bytes] | str, + force: bool = False, + ) -> Any: "Return a unicode string from the bytes-like representation" if self.decode_responses or force: if isinstance(value, memoryview):