From 89be0a49d7352d43eff8fb9d6c1ac3b60acd70c8 Mon Sep 17 00:00:00 2001 From: Stephen Rosen Date: Tue, 20 Oct 2026 09:01:42 -0500 Subject: [PATCH 1/5] Add missing generic params to json5 loader --- src/check_jsonschema/parsers/json5.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/check_jsonschema/parsers/json5.py b/src/check_jsonschema/parsers/json5.py index 1020375b7..66837bf01 100644 --- a/src/check_jsonschema/parsers/json5.py +++ b/src/check_jsonschema/parsers/json5.py @@ -8,7 +8,7 @@ import pyjson5 ParseError: type[Exception] = pyjson5.Json5DecoderException - _load: t.Callable | None = pyjson5.load + _load: t.Callable[[t.IO[bytes]], t.Any] | None = pyjson5.load except ImportError: # if pyjson5 was not available, try to import 'json5', the pure-python implementation try: @@ -25,7 +25,7 @@ ENABLED = _load is not None if _load is not None: - _load_concrete: t.Callable = _load + _load_concrete: t.Callable[[t.IO[bytes]], t.Any] = _load def load(stream: t.IO[bytes]) -> t.Any: return _load_concrete(stream) From 46f3db0eaa1a3ec1d8c34567c0ab6f7656d0cdf8 Mon Sep 17 00:00:00 2001 From: Stephen Rosen Date: Tue, 20 Oct 2026 09:05:54 -0500 Subject: [PATCH 2/5] Add missing annotations to click param types --- pyproject.toml | 1 + src/check_jsonschema/cli/param_types.py | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 09729cd45..a366b13ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,6 +80,7 @@ profile = "black" [tool.mypy] # strict = true # TODO: enable +disallow_any_generics = true disallow_untyped_defs = true ignore_missing_imports = true warn_unreachable = true diff --git a/src/check_jsonschema/cli/param_types.py b/src/check_jsonschema/cli/param_types.py index acdcd52d3..700af0c3d 100644 --- a/src/check_jsonschema/cli/param_types.py +++ b/src/check_jsonschema/cli/param_types.py @@ -24,7 +24,7 @@ def wrapper(*args: t.Any, **kwargs: t.Any) -> None: return wrapper # type: ignore[return-value] -class CommaDelimitedList(click.ParamType): +class CommaDelimitedList(click.ParamType[list[str]]): name = "comma_delimited" def __init__( @@ -46,7 +46,8 @@ def get_metavar(self, param: click.Parameter, ctx: click.Context | None) -> str: def convert( self, value: str, param: click.Parameter | None, ctx: click.Context | None ) -> list[str]: - value = super().convert(value, param, ctx) + # ignore assignment error (super().convert() returns `str`, not `list[str]`) + value = super().convert(value, param, ctx) # type: ignore[assignment] # if `--foo` is a comma delimited list and someone passes # `--foo ""`, take that as `foo=[]` rather than foo=[""] @@ -67,7 +68,7 @@ def convert( return resolved -class ValidatorClassName(click.ParamType): +class ValidatorClassName(click.ParamType[type[jsonschema.protocols.Validator]]): name = "validator" def convert( @@ -89,7 +90,8 @@ def convert( As a result, only one import is needed, rather than iterative imports over the list of names. """ - value = super().convert(value, param, ctx) + # ignore type error: super().convert() returns str, not a validator class + value = super().convert(value, param, ctx) # type: ignore[assignment] pattern = re.compile( r"^(?P(?!\d)(\w+)(\.(?!\d)(\w+))*):" r"(?P(?!\d)(\w+)(\.(?!\d)(\w+))*)$" From 73098c3f529bf06bd193c9493caac7cedc070dd0 Mon Sep 17 00:00:00 2001 From: Stephen Rosen Date: Tue, 20 Oct 2026 09:08:55 -0500 Subject: [PATCH 3/5] Add missing generic type params to reporter --- src/check_jsonschema/reporter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/check_jsonschema/reporter.py b/src/check_jsonschema/reporter.py index 0cf3971e6..3ed75472a 100644 --- a/src/check_jsonschema/reporter.py +++ b/src/check_jsonschema/reporter.py @@ -157,7 +157,7 @@ def report_success(self, result: CheckResult) -> None: def _dump_error_map( self, error_map: dict[str, list[jsonschema.ValidationError]], - ) -> t.Iterator[dict]: + ) -> t.Iterator[dict[str, t.Any]]: for filename, errors in error_map.items(): for err in errors: item = { @@ -190,7 +190,7 @@ def _dump_error_map( def _dump_parse_errors( self, error_map: dict[str, list[ParseError]], - ) -> t.Iterator[dict]: + ) -> t.Iterator[dict[str, str]]: for filename, errors in error_map.items(): for err in errors: yield { From 07adeaa39824f75ae91d6ca445b1b05adc11b460 Mon Sep 17 00:00:00 2001 From: Stephen Rosen Date: Tue, 20 Oct 2026 09:14:35 -0500 Subject: [PATCH 4/5] Add missing generic annotations to transforms --- src/check_jsonschema/transforms/azure_pipelines.py | 8 +++++--- src/check_jsonschema/transforms/base.py | 6 ++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/check_jsonschema/transforms/azure_pipelines.py b/src/check_jsonschema/transforms/azure_pipelines.py index 8888a9e6b..831a8f39d 100644 --- a/src/check_jsonschema/transforms/azure_pipelines.py +++ b/src/check_jsonschema/transforms/azure_pipelines.py @@ -52,7 +52,7 @@ def traverse_data(data: t.Any) -> t.Any: return data -def traverse_list(data: list) -> list: +def traverse_list(data: list[t.Any]) -> list[t.Any]: ret = [] for item in data: # is the current item a single-value dict with an expression as its key? @@ -77,7 +77,7 @@ def traverse_list(data: list) -> list: return ret -def traverse_dict(data: dict) -> dict: +def traverse_dict(data: dict[t.Any, t.Any]) -> dict[t.Any, t.Any]: newdata = {} for key, value in data.items(): newvalue = traverse_data(value) @@ -122,7 +122,9 @@ def traverse_dict(data: dict) -> dict: return newdata -def azure_main(data: dict | list) -> dict | list: +def azure_main( + data: dict[t.Any, t.Any] | list[t.Any], +) -> dict[t.Any, t.Any] | list[t.Any]: if isinstance(data, list): raise AzurePipelinesDataError( "this transform requires that the data be an object, got list" diff --git a/src/check_jsonschema/transforms/base.py b/src/check_jsonschema/transforms/base.py index 0ed9a8647..b3847d2d3 100644 --- a/src/check_jsonschema/transforms/base.py +++ b/src/check_jsonschema/transforms/base.py @@ -4,19 +4,21 @@ import ruamel.yaml +_AnyData: t.TypeAlias = list[t.Any] | dict[t.Any, t.Any] + class Transform: def __init__( self, *, - on_data: t.Callable[[list | dict], list | dict] | None = None, + on_data: t.Callable[[_AnyData], _AnyData] | None = None, ) -> None: self.on_data = on_data def modify_yaml_implementation(self, implementation: ruamel.yaml.YAML) -> None: pass - def __call__(self, data: list | dict) -> list | dict: + def __call__(self, data: _AnyData) -> _AnyData: if self.on_data is not None: return self.on_data(data) return data From 674309d697fa90fa6f4f2cc8de746ed51a469eb3 Mon Sep 17 00:00:00 2001 From: Stephen Rosen Date: Tue, 20 Oct 2026 09:29:25 -0500 Subject: [PATCH 5/5] Improve annotations in schema readers - Add missing type params - Use an enum for the UNSET sentinel In one particular case, play some games to make mypy recognize type narrowing. --- src/check_jsonschema/schema_loader/readers.py | 34 +++++++++++-------- .../schema_loader/resolver.py | 5 ++- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/check_jsonschema/schema_loader/readers.py b/src/check_jsonschema/schema_loader/readers.py index 61299350a..4145e61c8 100644 --- a/src/check_jsonschema/schema_loader/readers.py +++ b/src/check_jsonschema/schema_loader/readers.py @@ -1,5 +1,6 @@ from __future__ import annotations +import enum import io import json import sys @@ -15,14 +16,16 @@ yaml = ruamel.yaml.YAML(typ="safe") -class _UnsetType: - pass +class _UnsetType(enum.Enum): + UNSET = enum.auto -_UNSET = _UnsetType() +_UNSET = _UnsetType.UNSET -def _run_load_callback(schema_location: str, callback: t.Callable) -> dict: +def _run_load_callback( + schema_location: str, callback: t.Callable[[], dict[str, t.Any]] +) -> dict[str, t.Any]: try: schema = callback() # only local loads can raise the YAMLError, but catch for both cases for simplicity @@ -38,7 +41,7 @@ def __init__(self, filename: str) -> None: self.path = filename2path(filename) self.filename = str(self.path) self.parsers = ParserSet() - self._parsed_schema: dict | _UnsetType = _UNSET + self._parsed_schema: dict[str, t.Any] | t.Literal[_UnsetType.UNSET] = _UNSET def get_retrieval_uri(self) -> str | None: return self.path.as_uri() @@ -46,27 +49,30 @@ def get_retrieval_uri(self) -> str | None: def _read_impl(self) -> t.Any: return self.parsers.parse_file(self.path, default_filetype="json") - def read_schema(self) -> dict: + def read_schema(self) -> dict[str, t.Any]: if self._parsed_schema is _UNSET: self._parsed_schema = _run_load_callback(self.filename, self._read_impl) - return t.cast(dict, self._parsed_schema) + return self._parsed_schema class StdinSchemaReader: def __init__(self) -> None: self.parsers = ParserSet() - self._parsed_schema: dict | _UnsetType = _UNSET + self._parsed_schema: dict[str, t.Any] | t.Literal[_UnsetType.UNSET] = _UNSET def get_retrieval_uri(self) -> str | None: return None - def read_schema(self) -> dict: + def read_schema(self) -> dict[str, t.Any]: if self._parsed_schema is _UNSET: try: - self._parsed_schema = json.load(sys.stdin) + loaded: dict[str, t.Any] = json.load(sys.stdin) except ValueError as e: raise ParseError("Failed to parse JSON from stdin") from e - return t.cast(dict, self._parsed_schema) + self._parsed_schema = loaded + return self._parsed_schema + else: + return self._parsed_schema class HttpSchemaReader: @@ -80,7 +86,7 @@ def __init__( self.downloader = CacheDownloader("schemas", disable_cache=disable_cache).bind( url, validation_callback=self._parse ) - self._parsed_schema: dict | _UnsetType = _UNSET + self._parsed_schema: dict[str, t.Any] | t.Literal[_UnsetType.UNSET] = _UNSET def _parse(self, schema_bytes: bytes) -> t.Any: return self.parsers.parse_data_with_path( @@ -94,7 +100,7 @@ def _read_impl(self) -> t.Any: with self.downloader.open() as fp: return self._parse(fp.read()) - def read_schema(self) -> dict: + def read_schema(self) -> dict[str, t.Any]: if self._parsed_schema is _UNSET: self._parsed_schema = _run_load_callback(self.url, self._read_impl) - return t.cast(dict, self._parsed_schema) + return self._parsed_schema diff --git a/src/check_jsonschema/schema_loader/resolver.py b/src/check_jsonschema/schema_loader/resolver.py index 29b7ad428..d700f1066 100644 --- a/src/check_jsonschema/schema_loader/resolver.py +++ b/src/check_jsonschema/schema_loader/resolver.py @@ -12,7 +12,10 @@ def make_reference_registry( - parsers: ParserSet, retrieval_uri: str | None, schema: dict, disable_cache: bool + parsers: ParserSet, + retrieval_uri: str | None, + schema: dict[str, t.Any], + disable_cache: bool, ) -> referencing.Registry: id_attribute_: t.Any = schema.get("$id") if isinstance(id_attribute_, str):