Better Internat Static Typing #46
3
.github/workflows/build.yml
vendored
3
.github/workflows/build.yml
vendored
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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,6 +52,7 @@ 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"
|
||||||
|
type-check = "mypy jambo"
|
||||||
serve-docs = "sphinx-autobuild docs/source docs/build"
|
serve-docs = "sphinx-autobuild docs/source docs/build"
|
||||||
|
|
||||||
# Build System
|
# Build System
|
||||||
|
|||||||
@@ -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
79
uv.lock
generated
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user