Merge pull request #46 from HideyoshiNakazone/feature/better-internal-typing

Better Internat Static Typing
This commit was merged in pull request #46.
This commit is contained in:
2025-09-13 19:49:17 -03:00
committed by GitHub
13 changed files with 236 additions and 135 deletions

View File

@@ -44,6 +44,9 @@ jobs:
uv run poe tests uv run poe tests
uv run poe tests-report uv run poe tests-report
- name: Static type check
run: uv run poe type-check
- name: Upload coverage reports to Codecov - name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v5 uses: codecov/codecov-action@v5
with: with:

View File

@@ -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 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 from abc import ABC, abstractmethod
T = TypeVar("T") T = TypeVar("T", bound=type)
class GenericTypeParser(ABC, Generic[T]): class GenericTypeParser(ABC, Generic[T]):
json_schema_type: str = None json_schema_type: ClassVar[str]
type_mappings: dict[str, str] = {} type_mappings: dict[str, str] = {}
@@ -21,7 +21,7 @@ class GenericTypeParser(ABC, Generic[T]):
@abstractmethod @abstractmethod
def from_properties_impl( 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]: ) -> tuple[T, dict]:
""" """
Abstract method to convert properties to a type and its fields properties. Abstract method to convert properties to a type and its fields properties.
@@ -32,7 +32,7 @@ class GenericTypeParser(ABC, Generic[T]):
""" """
def from_properties( def from_properties(
self, name: str, properties: dict[str, Any], **kwargs: Unpack[TypeParserOptions] self, name: str, properties: JSONSchema, **kwargs: Unpack[TypeParserOptions]
) -> tuple[T, dict]: ) -> tuple[T, dict]:
""" """
Converts properties to a type and its fields properties. Converts properties to a type and its fields properties.
@@ -54,7 +54,7 @@ class GenericTypeParser(ABC, Generic[T]):
@classmethod @classmethod
def type_from_properties( 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]: ) -> tuple[type, dict]:
""" """
Factory method to fetch the appropriate type parser based on properties 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) return parser().from_properties(name=name, properties=properties, **kwargs)
@classmethod @classmethod
def _get_impl(cls, properties: dict[str, Any]) -> type[Self]: def _get_impl(cls, properties: JSONSchema) -> type[Self]:
for subcls in cls.__subclasses__(): for subcls in cls.__subclasses__():
schema_type, schema_value = subcls._get_schema_type() schema_type, schema_value = subcls._get_schema_type()
if schema_type not in properties: if schema_type not in properties:
continue 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 return subcls
raise ValueError("Unknown type") raise ValueError("Unknown type")
@@ -108,7 +108,7 @@ class GenericTypeParser(ABC, Generic[T]):
} }
@staticmethod @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") value = field_prop.get("default")
if value is None and field_prop.get("default_factory") is not None: if value is None and field_prop.get("default_factory") is not None:
@@ -118,7 +118,7 @@ class GenericTypeParser(ABC, Generic[T]):
return True return True
try: try:
field = Annotated[field_type, Field(**field_prop)] field = Annotated[field_type, Field(**field_prop)] # type: ignore
TypeAdapter(field).validate_python(value) TypeAdapter(field).validate_python(value)
except Exception as _: except Exception as _:
return False return False

View File

@@ -1,7 +1,8 @@
from jambo.parser._type_parser import GenericTypeParser from jambo.parser._type_parser import GenericTypeParser
from jambo.types.json_schema_type import JSONSchema
from jambo.types.type_parser_options import TypeParserOptions from jambo.types.type_parser_options import TypeParserOptions
from typing_extensions import Any, Unpack from typing_extensions import Unpack
class AllOfTypeParser(GenericTypeParser): class AllOfTypeParser(GenericTypeParser):
@@ -10,7 +11,7 @@ class AllOfTypeParser(GenericTypeParser):
json_schema_type = "allOf" json_schema_type = "allOf"
def from_properties_impl( def from_properties_impl(
self, name, properties, **kwargs: Unpack[TypeParserOptions] self, name: str, properties: JSONSchema, **kwargs: Unpack[TypeParserOptions]
): ):
sub_properties = properties.get("allOf", []) sub_properties = properties.get("allOf", [])
@@ -29,12 +30,12 @@ class AllOfTypeParser(GenericTypeParser):
@staticmethod @staticmethod
def _get_type_parser( def _get_type_parser(
sub_properties: list[dict[str, Any]], sub_properties: list[JSONSchema],
) -> type[GenericTypeParser]: ) -> type[GenericTypeParser]:
if not sub_properties: if not sub_properties:
raise ValueError("Invalid JSON Schema: 'allOf' is empty.") 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 GenericTypeParser._get_impl(sub_property) for sub_property in sub_properties
) )
if len(parsers) != 1: if len(parsers) != 1:
@@ -44,17 +45,19 @@ class AllOfTypeParser(GenericTypeParser):
@staticmethod @staticmethod
def _rebuild_properties_from_subproperties( def _rebuild_properties_from_subproperties(
sub_properties: list[dict[str, Any]], sub_properties: list[JSONSchema],
) -> dict[str, Any]: ) -> JSONSchema:
properties = {} properties: JSONSchema = {}
for subProperty in sub_properties: for subProperty in sub_properties:
for name, prop in subProperty.items(): for name, prop in subProperty.items():
if name not in properties: if name not in properties:
properties[name] = prop properties[name] = prop # type: ignore
else: else:
# Merge properties if they exist in both sub-properties # Merge properties if they exist in both sub-properties
properties[name] = AllOfTypeParser._validate_prop( properties[name] = AllOfTypeParser._validate_prop( # type: ignore
name, properties[name], prop name,
properties[name], # type: ignore
prop,
) )
return properties return properties

View File

@@ -1,6 +1,6 @@
from jambo.parser._type_parser import GenericTypeParser from jambo.parser._type_parser import GenericTypeParser
from jambo.types.json_schema_type import JSONSchemaNativeTypes 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 from typing_extensions import Unpack
@@ -11,7 +11,7 @@ class EnumTypeParser(GenericTypeParser):
json_schema_type = "enum" json_schema_type = "enum"
def from_properties_impl( def from_properties_impl(
self, name, properties, **kwargs: Unpack[TypeParserOptions] self, name: str, properties: JSONSchema, **kwargs: Unpack[TypeParserOptions]
): ):
if "enum" not in properties: if "enum" not in properties:
raise ValueError(f"Enum type {name} must have 'enum' property defined.") raise ValueError(f"Enum type {name} must have 'enum' property defined.")
@@ -27,7 +27,7 @@ class EnumTypeParser(GenericTypeParser):
) )
# Create a new Enum type dynamically # 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) parsed_properties = self.mappings_properties_builder(properties, **kwargs)
if "default" in parsed_properties and parsed_properties["default"] is not None: if "default" in parsed_properties and parsed_properties["default"] is not None:

View File

@@ -1,8 +1,10 @@
from jambo.parser._type_parser import GenericTypeParser from jambo.parser._type_parser import GenericTypeParser
from jambo.types.json_schema_type import JSONSchema
from jambo.types.type_parser_options import TypeParserOptions from jambo.types.type_parser_options import TypeParserOptions
from pydantic import BaseModel, ConfigDict, Field, create_model 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): class ObjectTypeParser(GenericTypeParser):
@@ -11,7 +13,7 @@ class ObjectTypeParser(GenericTypeParser):
json_schema_type = "type:object" json_schema_type = "type:object"
def from_properties_impl( 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]: ) -> tuple[type[BaseModel], dict]:
type_parsing = self.to_model( type_parsing = self.to_model(
name, name,
@@ -32,29 +34,29 @@ class ObjectTypeParser(GenericTypeParser):
def to_model( def to_model(
cls, cls,
name: str, name: str,
schema: dict[str, Any], properties: dict[str, JSONSchema],
required_keys: list[str], required_keys: list[str],
**kwargs: Unpack[TypeParserOptions], **kwargs: Unpack[TypeParserOptions],
) -> type[BaseModel]: ) -> type[BaseModel]:
""" """
Converts JSON Schema object properties to a Pydantic model. Converts JSON Schema object properties to a Pydantic model.
:param name: The name of the 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. :param required_keys: List of required keys in the schema.
:return: A Pydantic model class. :return: A Pydantic model class.
""" """
model_config = ConfigDict(validate_assignment=True) 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 @classmethod
def _parse_properties( def _parse_properties(
cls, cls,
properties: dict[str, Any], properties: dict[str, JSONSchema],
required_keys: list[str], required_keys: list[str],
**kwargs: Unpack[TypeParserOptions], **kwargs: Unpack[TypeParserOptions],
) -> dict[str, tuple[type, Field]]: ) -> dict[str, tuple[type, FieldInfo]]:
required_keys = required_keys or [] required_keys = required_keys or []
fields = {} fields = {}
@@ -63,7 +65,9 @@ class ObjectTypeParser(GenericTypeParser):
sub_property["required"] = name in required_keys sub_property["required"] = name in required_keys
parsed_type, parsed_properties = GenericTypeParser.type_from_properties( 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)) fields[name] = (parsed_type, Field(**parsed_properties))

View File

@@ -5,6 +5,9 @@ from pydantic import BaseModel, BeforeValidator, Field, TypeAdapter, ValidationE
from typing_extensions import Annotated, Any, Union, Unpack, get_args from typing_extensions import Annotated, Any, Union, Unpack, get_args
Annotation = Annotated[Any, ...]
class OneOfTypeParser(GenericTypeParser): class OneOfTypeParser(GenericTypeParser):
mapped_type = Union mapped_type = Union
@@ -49,8 +52,8 @@ class OneOfTypeParser(GenericTypeParser):
@staticmethod @staticmethod
def _build_type_one_of_with_discriminator( def _build_type_one_of_with_discriminator(
subfield_types: list[Annotated], discriminator_prop: dict subfield_types: list[Annotation], discriminator_prop: dict
) -> Annotated: ) -> Annotation:
""" """
Build a type with a discriminator. Build a type with a discriminator.
""" """
@@ -74,7 +77,7 @@ class OneOfTypeParser(GenericTypeParser):
return Annotated[Union[(*subfield_types,)], Field(discriminator=property_name)] return Annotated[Union[(*subfield_types,)], Field(discriminator=property_name)]
@staticmethod @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. Build a type with a validation function for the oneOf constraint.
""" """

View File

@@ -1,10 +1,11 @@
from jambo.parser import GenericTypeParser from jambo.parser import GenericTypeParser
from jambo.types.json_schema_type import JSONSchema
from jambo.types.type_parser_options import TypeParserOptions 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"] RefStrategy = Literal["forward_ref", "def_ref"]
@@ -13,7 +14,7 @@ class RefTypeParser(GenericTypeParser):
json_schema_type = "$ref" json_schema_type = "$ref"
def from_properties_impl( 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]: ) -> tuple[RefType, dict]:
if "$ref" not in properties: if "$ref" not in properties:
raise ValueError(f"RefTypeParser: Missing $ref in properties for {name}") 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 # If the reference is either processing or already cached
return ref_state, mapped_properties return ref_state, mapped_properties
ref_cache[ref_name] = self._parse_from_strategy( ref = self._parse_from_strategy(ref_strategy, ref_name, ref_property, **kwargs)
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( def _parse_from_strategy(
self, self,
ref_strategy: RefStrategy, ref_strategy: RefStrategy,
ref_name: str, ref_name: str,
ref_property: dict[str, Any], ref_property: JSONSchema,
**kwargs: Unpack[TypeParserOptions], **kwargs: Unpack[TypeParserOptions],
): ) -> RefType:
mapped_type: RefType
match ref_strategy: match ref_strategy:
case "forward_ref": case "forward_ref":
mapped_type = ForwardRef(ref_name) mapped_type = ForwardRef(ref_name)
@@ -69,7 +70,7 @@ class RefTypeParser(GenericTypeParser):
return mapped_type return mapped_type
def _get_ref_from_cache( 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: ) -> RefType | type | None:
try: try:
ref_state = ref_cache[ref_name] 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 # If the reference is not in the cache, we will set it to None
ref_cache[ref_name] = None ref_cache[ref_name] = None
return None
def _examine_ref_strategy( def _examine_ref_strategy(
self, name: str, properties: dict[str, Any], **kwargs: Unpack[TypeParserOptions] self, name: str, properties: JSONSchema, **kwargs: Unpack[TypeParserOptions]
) -> tuple[RefStrategy, str, dict] | None: ) -> tuple[RefStrategy, str, JSONSchema]:
if properties["$ref"] == "#": if properties.get("$ref") == "#":
ref_name = kwargs["context"].get("title") ref_name = kwargs["context"].get("title")
if ref_name is None: if ref_name is None:
raise ValueError( raise ValueError(
@@ -95,7 +98,7 @@ class RefTypeParser(GenericTypeParser):
) )
return "forward_ref", ref_name, {} return "forward_ref", ref_name, {}
if properties["$ref"].startswith("#/$defs/"): if properties.get("$ref", "").startswith("#/$defs/"):
target_name, target_property = self._extract_target_ref( target_name, target_property = self._extract_target_ref(
name, properties, **kwargs name, properties, **kwargs
) )
@@ -106,8 +109,8 @@ class RefTypeParser(GenericTypeParser):
) )
def _extract_target_ref( def _extract_target_ref(
self, name: str, properties: dict[str, Any], **kwargs: Unpack[TypeParserOptions] self, name: str, properties: JSONSchema, **kwargs: Unpack[TypeParserOptions]
) -> tuple[str, dict]: ) -> tuple[str, JSONSchema]:
target_name = None target_name = None
target_property = kwargs["context"] target_property = kwargs["context"]
for prop_name in properties["$ref"].split("/")[1:]: for prop_name in properties["$ref"].split("/")[1:]:
@@ -117,9 +120,9 @@ class RefTypeParser(GenericTypeParser):
" properties for $ref {properties['$ref']}" " properties for $ref {properties['$ref']}"
) )
target_name = prop_name 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']}") raise ValueError(f"RefTypeParser: Invalid $ref {properties['$ref']}")
return target_name, target_property return target_name, target_property

View File

@@ -25,7 +25,7 @@ class SchemaConverter:
try: try:
validator = validator_for(schema) validator = validator_for(schema)
validator.check_schema(schema) validator.check_schema(schema) # type: ignore
except SchemaError as e: except SchemaError as e:
raise ValueError(f"Invalid JSON Schema: {e}") raise ValueError(f"Invalid JSON Schema: {e}")
@@ -38,10 +38,11 @@ class SchemaConverter:
case "object": case "object":
return ObjectTypeParser.to_model( return ObjectTypeParser.to_model(
schema["title"], schema["title"],
schema["properties"], schema.get("properties", {}),
schema.get("required", []), schema.get("required", []),
context=schema, context=schema,
ref_cache=dict(), ref_cache=dict(),
required=True,
) )
case "$ref": case "$ref":
@@ -50,13 +51,14 @@ class SchemaConverter:
schema, schema,
context=schema, context=schema,
ref_cache=dict(), ref_cache=dict(),
required=True,
) )
return parsed_model return parsed_model
case _: case _:
raise TypeError(f"Unsupported schema type: {schema_type}") raise TypeError(f"Unsupported schema type: {schema_type}")
@staticmethod @staticmethod
def _get_schema_type(schema: JSONSchema) -> str: def _get_schema_type(schema: JSONSchema) -> str | None:
""" """
Returns the type of the schema. Returns the type of the schema.
:param schema: The JSON Schema to check. :param schema: The JSON Schema to check.
@@ -65,4 +67,4 @@ class SchemaConverter:
if "$ref" in schema: if "$ref" in schema:
return "$ref" return "$ref"
return schema.get("type", "undefined") return schema.get("type")

View File

@@ -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 from types import NoneType
# Primitive JSON types
JSONSchemaType = Literal[ JSONSchemaType = Literal[
"string", "number", "integer", "boolean", "object", "array", "null" "string", "number", "integer", "boolean", "object", "array", "null"
] ]
JSONSchemaNativeTypes: tuple[type, ...] = ( JSONSchemaNativeTypes: tuple[type, ...] = (
str, str,
int,
float, float,
int,
bool, bool,
list, list,
set, set,
NoneType, NoneType,
) )
JSONType = Union[str, int, float, bool, None, Dict[str, "JSONType"], List["JSONType"]] JSONType = Union[str, int, float, bool, None, Dict[str, "JSONType"], List["JSONType"]]
# Dynamically define TypedDict with JSON Schema keywords
class JSONSchema(TypedDict, total=False): JSONSchema = TypedDict(
# Basic metadata "JSONSchema",
title: str {
description: str "$id": str,
default: JSONType "$schema": str,
examples: List[JSONType] "$ref": str,
"$anchor": str,
# Type definitions "$comment": str,
type: Union[JSONSchemaType, List[JSONSchemaType]] "$defs": Dict[str, "JSONSchema"],
"title": str,
# Object-specific keywords "description": str,
properties: Dict[str, "JSONSchema"] "default": JSONType,
required: List[str] "examples": List[JSONType],
additionalProperties: Union[bool, "JSONSchema"] "type": JSONSchemaType,
minProperties: int "enum": List[JSONType],
maxProperties: int "const": JSONType,
patternProperties: Dict[str, "JSONSchema"] "properties": Dict[str, "JSONSchema"],
dependencies: Dict[str, Union[List[str], "JSONSchema"]] "patternProperties": Dict[str, "JSONSchema"],
"additionalProperties": Union[bool, "JSONSchema"],
# Array-specific keywords "required": List[str],
items: Union["JSONSchema", List["JSONSchema"]] "minProperties": int,
additionalItems: Union[bool, "JSONSchema"] "maxProperties": int,
minItems: int "dependencies": Dict[str, Union[List[str], "JSONSchema"]],
maxItems: int "items": Union["JSONSchema", List["JSONSchema"]],
uniqueItems: bool "prefixItems": List["JSONSchema"],
"additionalItems": Union[bool, "JSONSchema"],
# String-specific keywords "contains": "JSONSchema",
minLength: int "minItems": int,
maxLength: int "maxItems": int,
pattern: str "uniqueItems": bool,
format: str "minLength": int,
"maxLength": int,
# Number-specific keywords "pattern": str,
minimum: float "format": str,
maximum: float "minimum": float,
exclusiveMinimum: float "maximum": float,
exclusiveMaximum: float "exclusiveMinimum": Union[bool, float],
multipleOf: float "exclusiveMaximum": Union[bool, float],
"multipleOf": float,
# Enum and const "if": "JSONSchema",
enum: List[JSONType] "then": "JSONSchema",
const: JSONType "else": "JSONSchema",
"allOf": List["JSONSchema"],
# Conditionals "anyOf": List["JSONSchema"],
if_: "JSONSchema" # 'if' is a reserved word in Python "oneOf": List["JSONSchema"],
then: "JSONSchema" "not": "JSONSchema",
else_: "JSONSchema" # 'else' is also a reserved word },
total=False, # all fields optional
# 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

View File

@@ -1,9 +1,9 @@
from jambo.types.json_schema_type import JSONSchema from jambo.types.json_schema_type import JSONSchema
from typing_extensions import TypedDict from typing_extensions import ForwardRef, TypedDict
class TypeParserOptions(TypedDict): class TypeParserOptions(TypedDict):
required: bool required: bool
context: JSONSchema context: JSONSchema
ref_cache: dict[str, type] ref_cache: dict[str, ForwardRef | type | None]

View File

@@ -31,12 +31,14 @@ dependencies = [
[dependency-groups] [dependency-groups]
dev = [ dev = [
"coverage>=7.8.0", "coverage>=7.8.0",
"mypy>=1.18.1",
"poethepoet>=0.33.1", "poethepoet>=0.33.1",
"pre-commit>=4.2.0", "pre-commit>=4.2.0",
"ruff>=0.11.4", "ruff>=0.11.4",
"sphinx>=8.1.3", "sphinx>=8.1.3",
"sphinx-autobuild>=2024.10.3", "sphinx-autobuild>=2024.10.3",
"sphinx-rtd-theme>=3.0.2", "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" create-hooks = "bash .githooks/set-hooks.sh"
tests = "python -m coverage run -m unittest discover -v" tests = "python -m coverage run -m unittest discover -v"
tests-report = "python -m coverage xml" 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 # Build System
[tool.hatch.version] [tool.hatch.version]

View File

@@ -26,6 +26,20 @@ class TestSchemaConverter(TestCase):
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
SchemaConverter.build(schema) 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): def test_build_expects_title(self):
schema = { schema = {
"description": "A person", "description": "A person",

79
uv.lock generated
View File

@@ -326,6 +326,7 @@ dependencies = [
[package.dev-dependencies] [package.dev-dependencies]
dev = [ dev = [
{ name = "coverage" }, { name = "coverage" },
{ name = "mypy" },
{ name = "poethepoet" }, { name = "poethepoet" },
{ name = "pre-commit" }, { name = "pre-commit" },
{ name = "ruff" }, { 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", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
{ name = "sphinx-autobuild" }, { name = "sphinx-autobuild" },
{ name = "sphinx-rtd-theme" }, { name = "sphinx-rtd-theme" },
{ name = "types-jsonschema" },
] ]
[package.metadata] [package.metadata]
@@ -345,12 +347,14 @@ requires-dist = [
[package.metadata.requires-dev] [package.metadata.requires-dev]
dev = [ dev = [
{ name = "coverage", specifier = ">=7.8.0" }, { name = "coverage", specifier = ">=7.8.0" },
{ name = "mypy", specifier = ">=1.18.1" },
{ name = "poethepoet", specifier = ">=0.33.1" }, { name = "poethepoet", specifier = ">=0.33.1" },
{ name = "pre-commit", specifier = ">=4.2.0" }, { name = "pre-commit", specifier = ">=4.2.0" },
{ name = "ruff", specifier = ">=0.11.4" }, { name = "ruff", specifier = ">=0.11.4" },
{ name = "sphinx", specifier = ">=8.1.3" }, { name = "sphinx", specifier = ">=8.1.3" },
{ name = "sphinx-autobuild", specifier = ">=2024.10.3" }, { name = "sphinx-autobuild", specifier = ">=2024.10.3" },
{ name = "sphinx-rtd-theme", specifier = ">=3.0.2" }, { name = "sphinx-rtd-theme", specifier = ">=3.0.2" },
{ name = "types-jsonschema", specifier = ">=4.25.1.20250822" },
] ]
[[package]] [[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" }, { 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]] [[package]]
name = "nodeenv" name = "nodeenv"
version = "1.9.1" 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" }, { 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]] [[package]]
name = "platformdirs" name = "platformdirs"
version = "4.3.7" 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" }, { 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]] [[package]]
name = "typing-extensions" name = "typing-extensions"
version = "4.12.2" version = "4.12.2"