diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 19d3fac..022fdcb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -44,6 +44,9 @@ jobs: uv run poe tests uv run poe tests-report + - name: Static type check + run: uv run poe type-check + - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v5 with: diff --git a/jambo/parser/_type_parser.py b/jambo/parser/_type_parser.py index 080965c..cce8042 100644 --- a/jambo/parser/_type_parser.py +++ b/jambo/parser/_type_parser.py @@ -1,16 +1,16 @@ -from jambo.types.type_parser_options import TypeParserOptions +from jambo.types.type_parser_options import JSONSchema, TypeParserOptions from pydantic import Field, TypeAdapter -from typing_extensions import Annotated, Any, Generic, Self, TypeVar, Unpack +from typing_extensions import Annotated, Any, ClassVar, Generic, Self, TypeVar, Unpack from abc import ABC, abstractmethod -T = TypeVar("T") +T = TypeVar("T", bound=type) class GenericTypeParser(ABC, Generic[T]): - json_schema_type: str = None + json_schema_type: ClassVar[str] type_mappings: dict[str, str] = {} @@ -21,7 +21,7 @@ class GenericTypeParser(ABC, Generic[T]): @abstractmethod def from_properties_impl( - self, name: str, properties: dict[str, Any], **kwargs: Unpack[TypeParserOptions] + self, name: str, properties: JSONSchema, **kwargs: Unpack[TypeParserOptions] ) -> tuple[T, dict]: """ Abstract method to convert properties to a type and its fields properties. @@ -32,7 +32,7 @@ class GenericTypeParser(ABC, Generic[T]): """ def from_properties( - self, name: str, properties: dict[str, Any], **kwargs: Unpack[TypeParserOptions] + self, name: str, properties: JSONSchema, **kwargs: Unpack[TypeParserOptions] ) -> tuple[T, dict]: """ Converts properties to a type and its fields properties. @@ -54,7 +54,7 @@ class GenericTypeParser(ABC, Generic[T]): @classmethod def type_from_properties( - cls, name: str, properties: dict[str, Any], **kwargs: Unpack[TypeParserOptions] + cls, name: str, properties: JSONSchema, **kwargs: Unpack[TypeParserOptions] ) -> tuple[type, dict]: """ Factory method to fetch the appropriate type parser based on properties @@ -69,14 +69,14 @@ class GenericTypeParser(ABC, Generic[T]): return parser().from_properties(name=name, properties=properties, **kwargs) @classmethod - def _get_impl(cls, properties: dict[str, Any]) -> type[Self]: + def _get_impl(cls, properties: JSONSchema) -> type[Self]: for subcls in cls.__subclasses__(): schema_type, schema_value = subcls._get_schema_type() if schema_type not in properties: continue - if schema_value is None or schema_value == properties[schema_type]: + if schema_value is None or schema_value == properties[schema_type]: # type: ignore return subcls raise ValueError("Unknown type") @@ -108,7 +108,7 @@ class GenericTypeParser(ABC, Generic[T]): } @staticmethod - def _validate_default(field_type: type, field_prop: dict) -> bool: + def _validate_default(field_type: T, field_prop: dict) -> bool: value = field_prop.get("default") if value is None and field_prop.get("default_factory") is not None: @@ -118,7 +118,7 @@ class GenericTypeParser(ABC, Generic[T]): return True try: - field = Annotated[field_type, Field(**field_prop)] + field = Annotated[field_type, Field(**field_prop)] # type: ignore TypeAdapter(field).validate_python(value) except Exception as _: return False diff --git a/jambo/parser/allof_type_parser.py b/jambo/parser/allof_type_parser.py index 3180ae3..2fb62f9 100644 --- a/jambo/parser/allof_type_parser.py +++ b/jambo/parser/allof_type_parser.py @@ -1,7 +1,8 @@ from jambo.parser._type_parser import GenericTypeParser +from jambo.types.json_schema_type import JSONSchema from jambo.types.type_parser_options import TypeParserOptions -from typing_extensions import Any, Unpack +from typing_extensions import Unpack class AllOfTypeParser(GenericTypeParser): @@ -10,7 +11,7 @@ class AllOfTypeParser(GenericTypeParser): json_schema_type = "allOf" def from_properties_impl( - self, name, properties, **kwargs: Unpack[TypeParserOptions] + self, name: str, properties: JSONSchema, **kwargs: Unpack[TypeParserOptions] ): sub_properties = properties.get("allOf", []) @@ -29,12 +30,12 @@ class AllOfTypeParser(GenericTypeParser): @staticmethod def _get_type_parser( - sub_properties: list[dict[str, Any]], + sub_properties: list[JSONSchema], ) -> type[GenericTypeParser]: if not sub_properties: raise ValueError("Invalid JSON Schema: 'allOf' is empty.") - parsers = set( + parsers: set[type[GenericTypeParser]] = set( GenericTypeParser._get_impl(sub_property) for sub_property in sub_properties ) if len(parsers) != 1: @@ -44,17 +45,19 @@ class AllOfTypeParser(GenericTypeParser): @staticmethod def _rebuild_properties_from_subproperties( - sub_properties: list[dict[str, Any]], - ) -> dict[str, Any]: - properties = {} + sub_properties: list[JSONSchema], + ) -> JSONSchema: + properties: JSONSchema = {} for subProperty in sub_properties: for name, prop in subProperty.items(): if name not in properties: - properties[name] = prop + properties[name] = prop # type: ignore else: # Merge properties if they exist in both sub-properties - properties[name] = AllOfTypeParser._validate_prop( - name, properties[name], prop + properties[name] = AllOfTypeParser._validate_prop( # type: ignore + name, + properties[name], # type: ignore + prop, ) return properties diff --git a/jambo/parser/enum_type_parser.py b/jambo/parser/enum_type_parser.py index 42f4d6f..c59a725 100644 --- a/jambo/parser/enum_type_parser.py +++ b/jambo/parser/enum_type_parser.py @@ -1,6 +1,6 @@ from jambo.parser._type_parser import GenericTypeParser from jambo.types.json_schema_type import JSONSchemaNativeTypes -from jambo.types.type_parser_options import TypeParserOptions +from jambo.types.type_parser_options import JSONSchema, TypeParserOptions from typing_extensions import Unpack @@ -11,7 +11,7 @@ class EnumTypeParser(GenericTypeParser): json_schema_type = "enum" def from_properties_impl( - self, name, properties, **kwargs: Unpack[TypeParserOptions] + self, name: str, properties: JSONSchema, **kwargs: Unpack[TypeParserOptions] ): if "enum" not in properties: raise ValueError(f"Enum type {name} must have 'enum' property defined.") @@ -27,7 +27,7 @@ class EnumTypeParser(GenericTypeParser): ) # Create a new Enum type dynamically - enum_type = Enum(name, {str(value).upper(): value for value in enum_values}) + enum_type = Enum(name, {str(value).upper(): value for value in enum_values}) # type: ignore parsed_properties = self.mappings_properties_builder(properties, **kwargs) if "default" in parsed_properties and parsed_properties["default"] is not None: diff --git a/jambo/parser/object_type_parser.py b/jambo/parser/object_type_parser.py index 0f0ab7e..6cb60e7 100644 --- a/jambo/parser/object_type_parser.py +++ b/jambo/parser/object_type_parser.py @@ -1,8 +1,10 @@ from jambo.parser._type_parser import GenericTypeParser +from jambo.types.json_schema_type import JSONSchema from jambo.types.type_parser_options import TypeParserOptions from pydantic import BaseModel, ConfigDict, Field, create_model -from typing_extensions import Any, Unpack +from pydantic.fields import FieldInfo +from typing_extensions import Unpack class ObjectTypeParser(GenericTypeParser): @@ -11,7 +13,7 @@ class ObjectTypeParser(GenericTypeParser): json_schema_type = "type:object" def from_properties_impl( - self, name: str, properties: dict[str, Any], **kwargs: Unpack[TypeParserOptions] + self, name: str, properties: JSONSchema, **kwargs: Unpack[TypeParserOptions] ) -> tuple[type[BaseModel], dict]: type_parsing = self.to_model( name, @@ -32,29 +34,29 @@ class ObjectTypeParser(GenericTypeParser): def to_model( cls, name: str, - schema: dict[str, Any], + properties: dict[str, JSONSchema], required_keys: list[str], **kwargs: Unpack[TypeParserOptions], ) -> type[BaseModel]: """ Converts JSON Schema object properties to a Pydantic model. :param name: The name of the model. - :param schema: The properties of the JSON Schema object. + :param properties: The properties of the JSON Schema object. :param required_keys: List of required keys in the schema. :return: A Pydantic model class. """ model_config = ConfigDict(validate_assignment=True) - fields = cls._parse_properties(schema, required_keys, **kwargs) + fields = cls._parse_properties(properties, required_keys, **kwargs) - return create_model(name, __config__=model_config, **fields) + return create_model(name, __config__=model_config, **fields) # type: ignore @classmethod def _parse_properties( cls, - properties: dict[str, Any], + properties: dict[str, JSONSchema], required_keys: list[str], **kwargs: Unpack[TypeParserOptions], - ) -> dict[str, tuple[type, Field]]: + ) -> dict[str, tuple[type, FieldInfo]]: required_keys = required_keys or [] fields = {} @@ -63,7 +65,9 @@ class ObjectTypeParser(GenericTypeParser): sub_property["required"] = name in required_keys parsed_type, parsed_properties = GenericTypeParser.type_from_properties( - name, prop, **sub_property + name, + prop, + **sub_property, # type: ignore ) fields[name] = (parsed_type, Field(**parsed_properties)) diff --git a/jambo/parser/oneof_type_parser.py b/jambo/parser/oneof_type_parser.py index 707d277..317ce61 100644 --- a/jambo/parser/oneof_type_parser.py +++ b/jambo/parser/oneof_type_parser.py @@ -5,6 +5,9 @@ from pydantic import BaseModel, BeforeValidator, Field, TypeAdapter, ValidationE from typing_extensions import Annotated, Any, Union, Unpack, get_args +Annotation = Annotated[Any, ...] + + class OneOfTypeParser(GenericTypeParser): mapped_type = Union @@ -49,8 +52,8 @@ class OneOfTypeParser(GenericTypeParser): @staticmethod def _build_type_one_of_with_discriminator( - subfield_types: list[Annotated], discriminator_prop: dict - ) -> Annotated: + subfield_types: list[Annotation], discriminator_prop: dict + ) -> Annotation: """ Build a type with a discriminator. """ @@ -74,7 +77,7 @@ class OneOfTypeParser(GenericTypeParser): return Annotated[Union[(*subfield_types,)], Field(discriminator=property_name)] @staticmethod - def _build_type_one_of_with_func(subfield_types: list[Annotated]) -> Annotated: + def _build_type_one_of_with_func(subfield_types: list[Annotation]) -> Annotation: """ Build a type with a validation function for the oneOf constraint. """ diff --git a/jambo/parser/ref_type_parser.py b/jambo/parser/ref_type_parser.py index 57abeac..7aa435d 100644 --- a/jambo/parser/ref_type_parser.py +++ b/jambo/parser/ref_type_parser.py @@ -1,10 +1,11 @@ from jambo.parser import GenericTypeParser +from jambo.types.json_schema_type import JSONSchema from jambo.types.type_parser_options import TypeParserOptions -from typing_extensions import Any, ForwardRef, Literal, TypeVar, Union, Unpack +from typing_extensions import ForwardRef, Literal, Union, Unpack -RefType = TypeVar("RefType", bound=Union[type, ForwardRef]) +RefType = Union[type, ForwardRef] RefStrategy = Literal["forward_ref", "def_ref"] @@ -13,7 +14,7 @@ class RefTypeParser(GenericTypeParser): json_schema_type = "$ref" def from_properties_impl( - self, name: str, properties: dict[str, Any], **kwargs: Unpack[TypeParserOptions] + self, name: str, properties: JSONSchema, **kwargs: Unpack[TypeParserOptions] ) -> tuple[RefType, dict]: if "$ref" not in properties: raise ValueError(f"RefTypeParser: Missing $ref in properties for {name}") @@ -41,19 +42,19 @@ class RefTypeParser(GenericTypeParser): # If the reference is either processing or already cached return ref_state, mapped_properties - ref_cache[ref_name] = self._parse_from_strategy( - ref_strategy, ref_name, ref_property, **kwargs - ) + ref = self._parse_from_strategy(ref_strategy, ref_name, ref_property, **kwargs) + ref_cache[ref_name] = ref - return ref_cache[ref_name], mapped_properties + return ref, mapped_properties def _parse_from_strategy( self, ref_strategy: RefStrategy, ref_name: str, - ref_property: dict[str, Any], + ref_property: JSONSchema, **kwargs: Unpack[TypeParserOptions], - ): + ) -> RefType: + mapped_type: RefType match ref_strategy: case "forward_ref": mapped_type = ForwardRef(ref_name) @@ -69,7 +70,7 @@ class RefTypeParser(GenericTypeParser): return mapped_type def _get_ref_from_cache( - self, ref_name: str, ref_cache: dict[str, type] + self, ref_name: str, ref_cache: dict[str, ForwardRef | type | None] ) -> RefType | type | None: try: ref_state = ref_cache[ref_name] @@ -84,10 +85,12 @@ class RefTypeParser(GenericTypeParser): # If the reference is not in the cache, we will set it to None ref_cache[ref_name] = None + return None + def _examine_ref_strategy( - self, name: str, properties: dict[str, Any], **kwargs: Unpack[TypeParserOptions] - ) -> tuple[RefStrategy, str, dict] | None: - if properties["$ref"] == "#": + self, name: str, properties: JSONSchema, **kwargs: Unpack[TypeParserOptions] + ) -> tuple[RefStrategy, str, JSONSchema]: + if properties.get("$ref") == "#": ref_name = kwargs["context"].get("title") if ref_name is None: raise ValueError( @@ -95,7 +98,7 @@ class RefTypeParser(GenericTypeParser): ) return "forward_ref", ref_name, {} - if properties["$ref"].startswith("#/$defs/"): + if properties.get("$ref", "").startswith("#/$defs/"): target_name, target_property = self._extract_target_ref( name, properties, **kwargs ) @@ -106,8 +109,8 @@ class RefTypeParser(GenericTypeParser): ) def _extract_target_ref( - self, name: str, properties: dict[str, Any], **kwargs: Unpack[TypeParserOptions] - ) -> tuple[str, dict]: + self, name: str, properties: JSONSchema, **kwargs: Unpack[TypeParserOptions] + ) -> tuple[str, JSONSchema]: target_name = None target_property = kwargs["context"] for prop_name in properties["$ref"].split("/")[1:]: @@ -117,9 +120,9 @@ class RefTypeParser(GenericTypeParser): " properties for $ref {properties['$ref']}" ) target_name = prop_name - target_property = target_property[prop_name] + target_property = target_property[prop_name] # type: ignore - if target_name is None or target_property is None: + if not isinstance(target_name, str) or target_property is None: raise ValueError(f"RefTypeParser: Invalid $ref {properties['$ref']}") return target_name, target_property diff --git a/jambo/schema_converter.py b/jambo/schema_converter.py index 6f9020e..1624940 100644 --- a/jambo/schema_converter.py +++ b/jambo/schema_converter.py @@ -25,7 +25,7 @@ class SchemaConverter: try: validator = validator_for(schema) - validator.check_schema(schema) + validator.check_schema(schema) # type: ignore except SchemaError as e: raise ValueError(f"Invalid JSON Schema: {e}") @@ -38,10 +38,11 @@ class SchemaConverter: case "object": return ObjectTypeParser.to_model( schema["title"], - schema["properties"], + schema.get("properties", {}), schema.get("required", []), context=schema, ref_cache=dict(), + required=True, ) case "$ref": @@ -50,13 +51,14 @@ class SchemaConverter: schema, context=schema, ref_cache=dict(), + required=True, ) return parsed_model case _: raise TypeError(f"Unsupported schema type: {schema_type}") @staticmethod - def _get_schema_type(schema: JSONSchema) -> str: + def _get_schema_type(schema: JSONSchema) -> str | None: """ Returns the type of the schema. :param schema: The JSON Schema to check. @@ -65,4 +67,4 @@ class SchemaConverter: if "$ref" in schema: return "$ref" - return schema.get("type", "undefined") + return schema.get("type") diff --git a/jambo/types/json_schema_type.py b/jambo/types/json_schema_type.py index d99d791..954db75 100644 --- a/jambo/types/json_schema_type.py +++ b/jambo/types/json_schema_type.py @@ -1,93 +1,80 @@ -from typing_extensions import Dict, List, Literal, TypedDict, Union +from __future__ import annotations + +from typing_extensions import ( + Dict, + List, + Literal, + TypedDict, + Union, +) from types import NoneType +# Primitive JSON types JSONSchemaType = Literal[ "string", "number", "integer", "boolean", "object", "array", "null" ] - JSONSchemaNativeTypes: tuple[type, ...] = ( str, - int, float, + int, bool, list, set, NoneType, ) - JSONType = Union[str, int, float, bool, None, Dict[str, "JSONType"], List["JSONType"]] - -class JSONSchema(TypedDict, total=False): - # Basic metadata - title: str - description: str - default: JSONType - examples: List[JSONType] - - # Type definitions - type: Union[JSONSchemaType, List[JSONSchemaType]] - - # Object-specific keywords - properties: Dict[str, "JSONSchema"] - required: List[str] - additionalProperties: Union[bool, "JSONSchema"] - minProperties: int - maxProperties: int - patternProperties: Dict[str, "JSONSchema"] - dependencies: Dict[str, Union[List[str], "JSONSchema"]] - - # Array-specific keywords - items: Union["JSONSchema", List["JSONSchema"]] - additionalItems: Union[bool, "JSONSchema"] - minItems: int - maxItems: int - uniqueItems: bool - - # String-specific keywords - minLength: int - maxLength: int - pattern: str - format: str - - # Number-specific keywords - minimum: float - maximum: float - exclusiveMinimum: float - exclusiveMaximum: float - multipleOf: float - - # Enum and const - enum: List[JSONType] - const: JSONType - - # Conditionals - if_: "JSONSchema" # 'if' is a reserved word in Python - then: "JSONSchema" - else_: "JSONSchema" # 'else' is also a reserved word - - # Combination keywords - allOf: List["JSONSchema"] - anyOf: List["JSONSchema"] - oneOf: List["JSONSchema"] - not_: "JSONSchema" # 'not' is a reserved word - - -# Fix forward references -JSONSchema.__annotations__["properties"] = Dict[str, JSONSchema] -JSONSchema.__annotations__["items"] = Union[JSONSchema, List[JSONSchema]] -JSONSchema.__annotations__["additionalItems"] = Union[bool, JSONSchema] -JSONSchema.__annotations__["additionalProperties"] = Union[bool, JSONSchema] -JSONSchema.__annotations__["patternProperties"] = Dict[str, JSONSchema] -JSONSchema.__annotations__["dependencies"] = Dict[str, Union[List[str], JSONSchema]] -JSONSchema.__annotations__["if_"] = JSONSchema -JSONSchema.__annotations__["then"] = JSONSchema -JSONSchema.__annotations__["else_"] = JSONSchema -JSONSchema.__annotations__["allOf"] = List[JSONSchema] -JSONSchema.__annotations__["anyOf"] = List[JSONSchema] -JSONSchema.__annotations__["oneOf"] = List[JSONSchema] -JSONSchema.__annotations__["not_"] = JSONSchema +# Dynamically define TypedDict with JSON Schema keywords +JSONSchema = TypedDict( + "JSONSchema", + { + "$id": str, + "$schema": str, + "$ref": str, + "$anchor": str, + "$comment": str, + "$defs": Dict[str, "JSONSchema"], + "title": str, + "description": str, + "default": JSONType, + "examples": List[JSONType], + "type": JSONSchemaType, + "enum": List[JSONType], + "const": JSONType, + "properties": Dict[str, "JSONSchema"], + "patternProperties": Dict[str, "JSONSchema"], + "additionalProperties": Union[bool, "JSONSchema"], + "required": List[str], + "minProperties": int, + "maxProperties": int, + "dependencies": Dict[str, Union[List[str], "JSONSchema"]], + "items": Union["JSONSchema", List["JSONSchema"]], + "prefixItems": List["JSONSchema"], + "additionalItems": Union[bool, "JSONSchema"], + "contains": "JSONSchema", + "minItems": int, + "maxItems": int, + "uniqueItems": bool, + "minLength": int, + "maxLength": int, + "pattern": str, + "format": str, + "minimum": float, + "maximum": float, + "exclusiveMinimum": Union[bool, float], + "exclusiveMaximum": Union[bool, float], + "multipleOf": float, + "if": "JSONSchema", + "then": "JSONSchema", + "else": "JSONSchema", + "allOf": List["JSONSchema"], + "anyOf": List["JSONSchema"], + "oneOf": List["JSONSchema"], + "not": "JSONSchema", + }, + total=False, # all fields optional +) diff --git a/jambo/types/type_parser_options.py b/jambo/types/type_parser_options.py index 4f7d8e0..baf518b 100644 --- a/jambo/types/type_parser_options.py +++ b/jambo/types/type_parser_options.py @@ -1,9 +1,9 @@ from jambo.types.json_schema_type import JSONSchema -from typing_extensions import TypedDict +from typing_extensions import ForwardRef, TypedDict class TypeParserOptions(TypedDict): required: bool context: JSONSchema - ref_cache: dict[str, type] + ref_cache: dict[str, ForwardRef | type | None] diff --git a/pyproject.toml b/pyproject.toml index 9aab917..34e1fee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,12 +31,14 @@ dependencies = [ [dependency-groups] dev = [ "coverage>=7.8.0", + "mypy>=1.18.1", "poethepoet>=0.33.1", "pre-commit>=4.2.0", "ruff>=0.11.4", "sphinx>=8.1.3", "sphinx-autobuild>=2024.10.3", "sphinx-rtd-theme>=3.0.2", + "types-jsonschema>=4.25.1.20250822", ] @@ -50,7 +52,8 @@ repository = "https://github.com/HideyoshiNakazone/jambo.git" create-hooks = "bash .githooks/set-hooks.sh" tests = "python -m coverage run -m unittest discover -v" tests-report = "python -m coverage xml" -serve-docs = "sphinx-autobuild docs/source docs/build" +type-check = "mypy jambo" +serve-docs = "sphinx-autobuild docs/source docs/build" # Build System [tool.hatch.version] diff --git a/tests/test_schema_converter.py b/tests/test_schema_converter.py index cdd294d..6f831f2 100644 --- a/tests/test_schema_converter.py +++ b/tests/test_schema_converter.py @@ -26,6 +26,20 @@ class TestSchemaConverter(TestCase): with self.assertRaises(ValueError): SchemaConverter.build(schema) + def test_invalid_schema_type(self): + schema = { + "title": 1, + "description": "A person", + "type": 1, + "properties": { + "name": {"type": "string"}, + "age": {"type": "integer"}, + }, + } + + with self.assertRaises(ValueError): + SchemaConverter.build(schema) + def test_build_expects_title(self): schema = { "description": "A person", diff --git a/uv.lock b/uv.lock index 541d6f5..da0c1b5 100644 --- a/uv.lock +++ b/uv.lock @@ -326,6 +326,7 @@ dependencies = [ [package.dev-dependencies] dev = [ { name = "coverage" }, + { name = "mypy" }, { name = "poethepoet" }, { name = "pre-commit" }, { name = "ruff" }, @@ -333,6 +334,7 @@ dev = [ { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "sphinx-autobuild" }, { name = "sphinx-rtd-theme" }, + { name = "types-jsonschema" }, ] [package.metadata] @@ -345,12 +347,14 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ { name = "coverage", specifier = ">=7.8.0" }, + { name = "mypy", specifier = ">=1.18.1" }, { name = "poethepoet", specifier = ">=0.33.1" }, { name = "pre-commit", specifier = ">=4.2.0" }, { name = "ruff", specifier = ">=0.11.4" }, { name = "sphinx", specifier = ">=8.1.3" }, { name = "sphinx-autobuild", specifier = ">=2024.10.3" }, { name = "sphinx-rtd-theme", specifier = ">=3.0.2" }, + { name = "types-jsonschema", specifier = ">=4.25.1.20250822" }, ] [[package]] @@ -450,6 +454,60 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, ] +[[package]] +name = "mypy" +version = "1.18.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/14/a3/931e09fc02d7ba96da65266884da4e4a8806adcdb8a57faaacc6edf1d538/mypy-1.18.1.tar.gz", hash = "sha256:9e988c64ad3ac5987f43f5154f884747faf62141b7f842e87465b45299eea5a9", size = 3448447, upload-time = "2025-09-11T23:00:47.067Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/06/29ea5a34c23938ae93bc0040eb2900eb3f0f2ef4448cc59af37ab3ddae73/mypy-1.18.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2761b6ae22a2b7d8e8607fb9b81ae90bc2e95ec033fd18fa35e807af6c657763", size = 12811535, upload-time = "2025-09-11T22:58:55.399Z" }, + { url = "https://files.pythonhosted.org/packages/a8/40/04c38cb04fa9f1dc224b3e9634021a92c47b1569f1c87dfe6e63168883bb/mypy-1.18.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5b10e3ea7f2eec23b4929a3fabf84505da21034a4f4b9613cda81217e92b74f3", size = 11897559, upload-time = "2025-09-11T22:59:48.041Z" }, + { url = "https://files.pythonhosted.org/packages/46/bf/4c535bd45ea86cebbc1a3b6a781d442f53a4883f322ebd2d442db6444d0b/mypy-1.18.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:261fbfced030228bc0f724d5d92f9ae69f46373bdfd0e04a533852677a11dbea", size = 12507430, upload-time = "2025-09-11T22:59:30.415Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e1/cbefb16f2be078d09e28e0b9844e981afb41f6ffc85beb68b86c6976e641/mypy-1.18.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4dc6b34a1c6875e6286e27d836a35c0d04e8316beac4482d42cfea7ed2527df8", size = 13243717, upload-time = "2025-09-11T22:59:11.297Z" }, + { url = "https://files.pythonhosted.org/packages/65/e8/3e963da63176f16ca9caea7fa48f1bc8766de317cd961528c0391565fd47/mypy-1.18.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1cabb353194d2942522546501c0ff75c4043bf3b63069cb43274491b44b773c9", size = 13492052, upload-time = "2025-09-11T23:00:09.29Z" }, + { url = "https://files.pythonhosted.org/packages/4b/09/d5d70c252a3b5b7530662d145437bd1de15f39fa0b48a27ee4e57d254aa1/mypy-1.18.1-cp310-cp310-win_amd64.whl", hash = "sha256:738b171690c8e47c93569635ee8ec633d2cdb06062f510b853b5f233020569a9", size = 9765846, upload-time = "2025-09-11T22:58:26.198Z" }, + { url = "https://files.pythonhosted.org/packages/32/28/47709d5d9e7068b26c0d5189c8137c8783e81065ad1102b505214a08b548/mypy-1.18.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6c903857b3e28fc5489e54042684a9509039ea0aedb2a619469438b544ae1961", size = 12734635, upload-time = "2025-09-11T23:00:24.983Z" }, + { url = "https://files.pythonhosted.org/packages/7c/12/ee5c243e52497d0e59316854041cf3b3130131b92266d0764aca4dec3c00/mypy-1.18.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2a0c8392c19934c2b6c65566d3a6abdc6b51d5da7f5d04e43f0eb627d6eeee65", size = 11817287, upload-time = "2025-09-11T22:59:07.38Z" }, + { url = "https://files.pythonhosted.org/packages/48/bd/2aeb950151005fe708ab59725afed7c4aeeb96daf844f86a05d4b8ac34f8/mypy-1.18.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f85eb7efa2ec73ef63fc23b8af89c2fe5bf2a4ad985ed2d3ff28c1bb3c317c92", size = 12430464, upload-time = "2025-09-11T22:58:48.084Z" }, + { url = "https://files.pythonhosted.org/packages/71/e8/7a20407aafb488acb5734ad7fb5e8c2ef78d292ca2674335350fa8ebef67/mypy-1.18.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:82ace21edf7ba8af31c3308a61dc72df30500f4dbb26f99ac36b4b80809d7e94", size = 13164555, upload-time = "2025-09-11T23:00:13.803Z" }, + { url = "https://files.pythonhosted.org/packages/e8/c9/5f39065252e033b60f397096f538fb57c1d9fd70a7a490f314df20dd9d64/mypy-1.18.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a2dfd53dfe632f1ef5d161150a4b1f2d0786746ae02950eb3ac108964ee2975a", size = 13359222, upload-time = "2025-09-11T23:00:33.469Z" }, + { url = "https://files.pythonhosted.org/packages/85/b6/d54111ef3c1e55992cd2ec9b8b6ce9c72a407423e93132cae209f7e7ba60/mypy-1.18.1-cp311-cp311-win_amd64.whl", hash = "sha256:320f0ad4205eefcb0e1a72428dde0ad10be73da9f92e793c36228e8ebf7298c0", size = 9760441, upload-time = "2025-09-11T23:00:44.826Z" }, + { url = "https://files.pythonhosted.org/packages/e7/14/1c3f54d606cb88a55d1567153ef3a8bc7b74702f2ff5eb64d0994f9e49cb/mypy-1.18.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:502cde8896be8e638588b90fdcb4c5d5b8c1b004dfc63fd5604a973547367bb9", size = 12911082, upload-time = "2025-09-11T23:00:41.465Z" }, + { url = "https://files.pythonhosted.org/packages/90/83/235606c8b6d50a8eba99773add907ce1d41c068edb523f81eb0d01603a83/mypy-1.18.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7509549b5e41be279afc1228242d0e397f1af2919a8f2877ad542b199dc4083e", size = 11919107, upload-time = "2025-09-11T22:58:40.903Z" }, + { url = "https://files.pythonhosted.org/packages/ca/25/4e2ce00f8d15b99d0c68a2536ad63e9eac033f723439ef80290ec32c1ff5/mypy-1.18.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5956ecaabb3a245e3f34100172abca1507be687377fe20e24d6a7557e07080e2", size = 12472551, upload-time = "2025-09-11T22:58:37.272Z" }, + { url = "https://files.pythonhosted.org/packages/32/bb/92642a9350fc339dd9dcefcf6862d171b52294af107d521dce075f32f298/mypy-1.18.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8750ceb014a96c9890421c83f0db53b0f3b8633e2864c6f9bc0a8e93951ed18d", size = 13340554, upload-time = "2025-09-11T22:59:38.756Z" }, + { url = "https://files.pythonhosted.org/packages/cd/ee/38d01db91c198fb6350025d28f9719ecf3c8f2c55a0094bfbf3ef478cc9a/mypy-1.18.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fb89ea08ff41adf59476b235293679a6eb53a7b9400f6256272fb6029bec3ce5", size = 13530933, upload-time = "2025-09-11T22:59:20.228Z" }, + { url = "https://files.pythonhosted.org/packages/da/8d/6d991ae631f80d58edbf9d7066e3f2a96e479dca955d9a968cd6e90850a3/mypy-1.18.1-cp312-cp312-win_amd64.whl", hash = "sha256:2657654d82fcd2a87e02a33e0d23001789a554059bbf34702d623dafe353eabf", size = 9828426, upload-time = "2025-09-11T23:00:21.007Z" }, + { url = "https://files.pythonhosted.org/packages/e4/ec/ef4a7260e1460a3071628a9277a7579e7da1b071bc134ebe909323f2fbc7/mypy-1.18.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d70d2b5baf9b9a20bc9c730015615ae3243ef47fb4a58ad7b31c3e0a59b5ef1f", size = 12918671, upload-time = "2025-09-11T22:58:29.814Z" }, + { url = "https://files.pythonhosted.org/packages/a1/82/0ea6c3953f16223f0b8eda40c1aeac6bd266d15f4902556ae6e91f6fca4c/mypy-1.18.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b8367e33506300f07a43012fc546402f283c3f8bcff1dc338636affb710154ce", size = 11913023, upload-time = "2025-09-11T23:00:29.049Z" }, + { url = "https://files.pythonhosted.org/packages/ae/ef/5e2057e692c2690fc27b3ed0a4dbde4388330c32e2576a23f0302bc8358d/mypy-1.18.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:913f668ec50c3337b89df22f973c1c8f0b29ee9e290a8b7fe01cc1ef7446d42e", size = 12473355, upload-time = "2025-09-11T23:00:04.544Z" }, + { url = "https://files.pythonhosted.org/packages/98/43/b7e429fc4be10e390a167b0cd1810d41cb4e4add4ae50bab96faff695a3b/mypy-1.18.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a0e70b87eb27b33209fa4792b051c6947976f6ab829daa83819df5f58330c71", size = 13346944, upload-time = "2025-09-11T22:58:23.024Z" }, + { url = "https://files.pythonhosted.org/packages/89/4e/899dba0bfe36bbd5b7c52e597de4cf47b5053d337b6d201a30e3798e77a6/mypy-1.18.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c378d946e8a60be6b6ede48c878d145546fb42aad61df998c056ec151bf6c746", size = 13512574, upload-time = "2025-09-11T22:59:52.152Z" }, + { url = "https://files.pythonhosted.org/packages/f5/f8/7661021a5b0e501b76440454d786b0f01bb05d5c4b125fcbda02023d0250/mypy-1.18.1-cp313-cp313-win_amd64.whl", hash = "sha256:2cd2c1e0f3a7465f22731987fff6fc427e3dcbb4ca5f7db5bbeaff2ff9a31f6d", size = 9837684, upload-time = "2025-09-11T22:58:44.454Z" }, + { url = "https://files.pythonhosted.org/packages/bf/87/7b173981466219eccc64c107cf8e5ab9eb39cc304b4c07df8e7881533e4f/mypy-1.18.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ba24603c58e34dd5b096dfad792d87b304fc6470cbb1c22fd64e7ebd17edcc61", size = 12900265, upload-time = "2025-09-11T22:59:03.4Z" }, + { url = "https://files.pythonhosted.org/packages/ae/cc/b10e65bae75b18a5ac8f81b1e8e5867677e418f0dd2c83b8e2de9ba96ebd/mypy-1.18.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ed36662fb92ae4cb3cacc682ec6656208f323bbc23d4b08d091eecfc0863d4b5", size = 11942890, upload-time = "2025-09-11T23:00:00.607Z" }, + { url = "https://files.pythonhosted.org/packages/39/d4/aeefa07c44d09f4c2102e525e2031bc066d12e5351f66b8a83719671004d/mypy-1.18.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:040ecc95e026f71a9ad7956fea2724466602b561e6a25c2e5584160d3833aaa8", size = 12472291, upload-time = "2025-09-11T22:59:43.425Z" }, + { url = "https://files.pythonhosted.org/packages/c6/07/711e78668ff8e365f8c19735594ea95938bff3639a4c46a905e3ed8ff2d6/mypy-1.18.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:937e3ed86cb731276706e46e03512547e43c391a13f363e08d0fee49a7c38a0d", size = 13318610, upload-time = "2025-09-11T23:00:17.604Z" }, + { url = "https://files.pythonhosted.org/packages/ca/85/df3b2d39339c31d360ce299b418c55e8194ef3205284739b64962f6074e7/mypy-1.18.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1f95cc4f01c0f1701ca3b0355792bccec13ecb2ec1c469e5b85a6ef398398b1d", size = 13513697, upload-time = "2025-09-11T22:58:59.534Z" }, + { url = "https://files.pythonhosted.org/packages/b1/df/462866163c99ea73bb28f0eb4d415c087e30de5d36ee0f5429d42e28689b/mypy-1.18.1-cp314-cp314-win_amd64.whl", hash = "sha256:e4f16c0019d48941220ac60b893615be2f63afedaba6a0801bdcd041b96991ce", size = 9985739, upload-time = "2025-09-11T22:58:51.644Z" }, + { url = "https://files.pythonhosted.org/packages/e0/1d/4b97d3089b48ef3d904c9ca69fab044475bd03245d878f5f0b3ea1daf7ce/mypy-1.18.1-py3-none-any.whl", hash = "sha256:b76a4de66a0ac01da1be14ecc8ae88ddea33b8380284a9e3eae39d57ebcbe26e", size = 2352212, upload-time = "2025-09-11T22:59:26.576Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + [[package]] name = "nodeenv" version = "1.9.1" @@ -477,6 +535,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/aa/18/a8444036c6dd65ba3624c63b734d3ba95ba63ace513078e1580590075d21/pastel-0.2.1-py2.py3-none-any.whl", hash = "sha256:4349225fcdf6c2bb34d483e523475de5bb04a5c10ef711263452cb37d7dd4364", size = 5955, upload-time = "2020-09-16T19:21:11.409Z" }, ] +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, +] + [[package]] name = "platformdirs" version = "4.3.7" @@ -1048,6 +1115,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, ] +[[package]] +name = "types-jsonschema" +version = "4.25.1.20250822" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/7f/369b54dad6eb6b5adc1fb1c53edbed18e6c32cbc600357135308902fdbdc/types_jsonschema-4.25.1.20250822.tar.gz", hash = "sha256:aac69ed4b23f49aaceb7fcb834141d61b9e4e6a7f6008cb2f0d3b831dfa8464a", size = 15628, upload-time = "2025-08-22T03:04:18.293Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/3d/bc1d171f032fcf63cedd4ade241f3f4e66d7e3bb53ee1da3c8f2f043eb0b/types_jsonschema-4.25.1.20250822-py3-none-any.whl", hash = "sha256:f82c2d7fa1ce1c0b84ba1de4ed6798469768188884db04e66421913a4e181294", size = 15923, upload-time = "2025-08-22T03:04:17.346Z" }, +] + [[package]] name = "typing-extensions" version = "4.12.2"