From e9d61a126803d034a7a1117ab5fae34267a874a9 Mon Sep 17 00:00:00 2001 From: Fred Sonnenwald Date: Mon, 30 Jun 2025 12:23:47 +0100 Subject: [PATCH 01/96] Add null type parser --- jambo/parser/__init__.py | 2 ++ jambo/parser/null_type_parser.py | 25 ++++++++++++++++ tests/parser/test_null_type_parser.py | 43 +++++++++++++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 jambo/parser/null_type_parser.py create mode 100644 tests/parser/test_null_type_parser.py diff --git a/jambo/parser/__init__.py b/jambo/parser/__init__.py index a953057..f3b8b25 100644 --- a/jambo/parser/__init__.py +++ b/jambo/parser/__init__.py @@ -7,6 +7,7 @@ from .const_type_parser import ConstTypeParser from .enum_type_parser import EnumTypeParser from .float_type_parser import FloatTypeParser from .int_type_parser import IntTypeParser +from .null_type_parser import NullTypeParser from .object_type_parser import ObjectTypeParser from .ref_type_parser import RefTypeParser from .string_type_parser import StringTypeParser @@ -22,6 +23,7 @@ __all__ = [ "BooleanTypeParser", "FloatTypeParser", "IntTypeParser", + "NullTypeParser", "ObjectTypeParser", "StringTypeParser", "RefTypeParser", diff --git a/jambo/parser/null_type_parser.py b/jambo/parser/null_type_parser.py new file mode 100644 index 0000000..35036ef --- /dev/null +++ b/jambo/parser/null_type_parser.py @@ -0,0 +1,25 @@ +from jambo.parser._type_parser import GenericTypeParser +from jambo.types.type_parser_options import TypeParserOptions + +from typing_extensions import Unpack + + +class NullTypeParser(GenericTypeParser): + mapped_type = None + + json_schema_type = "type:null" + + type_mappings = { + "default": "default", + } + + def from_properties_impl( + self, name, properties, **kwargs: Unpack[TypeParserOptions] + ): + mapped_properties = self.mappings_properties_builder(properties, **kwargs) + + default_value = properties.get("default") + if default_value is not None: + raise ValueError(f"Default value for {name} must be None.") + + return None, mapped_properties diff --git a/tests/parser/test_null_type_parser.py b/tests/parser/test_null_type_parser.py new file mode 100644 index 0000000..414fbae --- /dev/null +++ b/tests/parser/test_null_type_parser.py @@ -0,0 +1,43 @@ +from jambo.parser import NullTypeParser + +from unittest import TestCase + + +class TestNullTypeParser(TestCase): + def test_null_parser_no_options(self): + parser = NullTypeParser() + + properties = {"type": "null"} + + type_parsing, type_validator = parser.from_properties_impl( + "placeholder", properties + ) + + self.assertEqual(type_parsing, None) + self.assertEqual(type_validator, {"default": None}) + + def test_null_parser_with_default(self): + parser = NullTypeParser() + + properties = { + "type": "null", + "default": None, + } + + type_parsing, type_validator = parser.from_properties_impl( + "placeholder", properties + ) + + self.assertEqual(type_parsing, None) + self.assertEqual(type_validator["default"], None) + + def test_null_parser_with_invalid_default(self): + parser = NullTypeParser() + + properties = { + "type": "null", + "default": "invalid", + } + + with self.assertRaises(ValueError): + parser.from_properties_impl("placeholder", properties) -- 2.49.1 From cbcb56c3c4bc97d2c61efef90e024d5d88717365 Mon Sep 17 00:00:00 2001 From: Thomas <34217413+thommann@users.noreply.github.com> Date: Fri, 4 Jul 2025 11:00:06 +0200 Subject: [PATCH 02/96] fix(jambo): Fix allOf constraints (#1) * Fix incorrect min/max length handling in allOf type parser and update tests accordingly * Fix schema converter test to correct allOf handling --- jambo/parser/allof_type_parser.py | 4 ++-- tests/parser/test_allof_type_parser.py | 8 ++++---- tests/test_schema_converter.py | 7 +++++-- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/jambo/parser/allof_type_parser.py b/jambo/parser/allof_type_parser.py index 3180ae3..709365e 100644 --- a/jambo/parser/allof_type_parser.py +++ b/jambo/parser/allof_type_parser.py @@ -74,10 +74,10 @@ class AllOfTypeParser(GenericTypeParser): return old_value + new_value if prop_name in ("maxLength", "maximum", "exclusiveMaximum"): - return old_value if old_value > new_value else new_value + return old_value if old_value < new_value else new_value if prop_name in ("minLength", "minimum", "exclusiveMinimum"): - return old_value if old_value < new_value else new_value + return old_value if old_value > new_value else new_value if prop_name == "properties": for key, value in new_value.items(): diff --git a/tests/parser/test_allof_type_parser.py b/tests/parser/test_allof_type_parser.py index 2ae4bc2..290c636 100644 --- a/tests/parser/test_allof_type_parser.py +++ b/tests/parser/test_allof_type_parser.py @@ -117,8 +117,8 @@ class TestAllOfTypeParser(TestCase): ) self.assertEqual(type_parsing, str) - self.assertEqual(type_validator["max_length"], 11) - self.assertEqual(type_validator["min_length"], 1) + self.assertEqual(type_validator["max_length"], 4) + self.assertEqual(type_validator["min_length"], 2) def test_all_of_type_parser_in_fields(self): """ @@ -138,8 +138,8 @@ class TestAllOfTypeParser(TestCase): ) self.assertEqual(type_parsing, str) - self.assertEqual(type_validator["max_length"], 11) - self.assertEqual(type_validator["min_length"], 1) + self.assertEqual(type_validator["max_length"], 4) + self.assertEqual(type_validator["min_length"], 2) def test_invalid_all_of(self): """ diff --git a/tests/test_schema_converter.py b/tests/test_schema_converter.py index fbba3c9..d25ae9a 100644 --- a/tests/test_schema_converter.py +++ b/tests/test_schema_converter.py @@ -358,10 +358,13 @@ class TestSchemaConverter(TestCase): Model = SchemaConverter.build(schema) obj = Model( - name="J", + name="John", ) - self.assertEqual(obj.name, "J") + self.assertEqual(obj.name, "John") + + with self.assertRaises(ValueError): + Model(name="J") with self.assertRaises(ValueError): Model(name="John Invalid") -- 2.49.1 From 7c1278af160992fd27a45a629ec0caf9de11af4a Mon Sep 17 00:00:00 2001 From: Thomas <34217413+thommann@users.noreply.github.com> Date: Fri, 4 Jul 2025 11:24:56 +0200 Subject: [PATCH 03/96] fix(jambo): Add NullTypeParser implementation and tests (#2) --- jambo/parser/__init__.py | 2 ++ jambo/parser/null_type_parser.py | 25 ++++++++++++++++ tests/parser/test_null_type_parser.py | 43 +++++++++++++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 jambo/parser/null_type_parser.py create mode 100644 tests/parser/test_null_type_parser.py diff --git a/jambo/parser/__init__.py b/jambo/parser/__init__.py index a953057..f3b8b25 100644 --- a/jambo/parser/__init__.py +++ b/jambo/parser/__init__.py @@ -7,6 +7,7 @@ from .const_type_parser import ConstTypeParser from .enum_type_parser import EnumTypeParser from .float_type_parser import FloatTypeParser from .int_type_parser import IntTypeParser +from .null_type_parser import NullTypeParser from .object_type_parser import ObjectTypeParser from .ref_type_parser import RefTypeParser from .string_type_parser import StringTypeParser @@ -22,6 +23,7 @@ __all__ = [ "BooleanTypeParser", "FloatTypeParser", "IntTypeParser", + "NullTypeParser", "ObjectTypeParser", "StringTypeParser", "RefTypeParser", diff --git a/jambo/parser/null_type_parser.py b/jambo/parser/null_type_parser.py new file mode 100644 index 0000000..35036ef --- /dev/null +++ b/jambo/parser/null_type_parser.py @@ -0,0 +1,25 @@ +from jambo.parser._type_parser import GenericTypeParser +from jambo.types.type_parser_options import TypeParserOptions + +from typing_extensions import Unpack + + +class NullTypeParser(GenericTypeParser): + mapped_type = None + + json_schema_type = "type:null" + + type_mappings = { + "default": "default", + } + + def from_properties_impl( + self, name, properties, **kwargs: Unpack[TypeParserOptions] + ): + mapped_properties = self.mappings_properties_builder(properties, **kwargs) + + default_value = properties.get("default") + if default_value is not None: + raise ValueError(f"Default value for {name} must be None.") + + return None, mapped_properties diff --git a/tests/parser/test_null_type_parser.py b/tests/parser/test_null_type_parser.py new file mode 100644 index 0000000..414fbae --- /dev/null +++ b/tests/parser/test_null_type_parser.py @@ -0,0 +1,43 @@ +from jambo.parser import NullTypeParser + +from unittest import TestCase + + +class TestNullTypeParser(TestCase): + def test_null_parser_no_options(self): + parser = NullTypeParser() + + properties = {"type": "null"} + + type_parsing, type_validator = parser.from_properties_impl( + "placeholder", properties + ) + + self.assertEqual(type_parsing, None) + self.assertEqual(type_validator, {"default": None}) + + def test_null_parser_with_default(self): + parser = NullTypeParser() + + properties = { + "type": "null", + "default": None, + } + + type_parsing, type_validator = parser.from_properties_impl( + "placeholder", properties + ) + + self.assertEqual(type_parsing, None) + self.assertEqual(type_validator["default"], None) + + def test_null_parser_with_invalid_default(self): + parser = NullTypeParser() + + properties = { + "type": "null", + "default": "invalid", + } + + with self.assertRaises(ValueError): + parser.from_properties_impl("placeholder", properties) -- 2.49.1 From 0c4de62bba2d2421eb9e3a3ca6a0887206e202d5 Mon Sep 17 00:00:00 2001 From: Thomas <34217413+thommann@users.noreply.github.com> Date: Fri, 4 Jul 2025 11:56:29 +0200 Subject: [PATCH 04/96] fix(jambo): skip Annotated wrapping for trivial anyOf field constraints (#3) --- jambo/parser/anyof_type_parser.py | 28 +++++++++++++++++++++++++- tests/parser/test_anyof_type_parser.py | 14 ++++--------- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/jambo/parser/anyof_type_parser.py b/jambo/parser/anyof_type_parser.py index 55ff3ec..8f43975 100644 --- a/jambo/parser/anyof_type_parser.py +++ b/jambo/parser/anyof_type_parser.py @@ -34,8 +34,34 @@ class AnyOfTypeParser(GenericTypeParser): # By defining the type as Union of Annotated type we can use the Field validator # to enforce the constraints of each union type when needed. # We use Annotated to attach the Field validators to the type. + # Only wrap in Annotated[T, Field(**v)] if there are meaningful field constraints + # Don't wrap for simple cases where v only contains {'default': None} field_types = [ - Annotated[t, Field(**v)] if v is not None else t for t, v in sub_types + Annotated[t, Field(**v)] if self._has_meaningful_constraints(v) else t + for t, v in sub_types ] return Union[(*field_types,)], mapped_properties + + @staticmethod + def _has_meaningful_constraints(field_props): + """ + Check if field properties contain meaningful constraints that require Field wrapping. + + Returns False if: + - field_props is None or empty + - field_props only contains {'default': None} + + Returns True if: + - field_props contains a non-None default value + - field_props contains other constraint properties (min_length, max_length, pattern, etc.) + """ + if not field_props: + return False + + # If only default is set and it's None, no meaningful constraints + if len(field_props) == 1 and field_props.get('default') is None: + return False + + # If there are multiple properties or non-None default, that's meaningful + return True \ No newline at end of file diff --git a/tests/parser/test_anyof_type_parser.py b/tests/parser/test_anyof_type_parser.py index 32c2f45..de5cb9b 100644 --- a/tests/parser/test_anyof_type_parser.py +++ b/tests/parser/test_anyof_type_parser.py @@ -46,11 +46,8 @@ class TestAnyOfTypeParser(TestCase): type_1, type_2 = get_args(type_parsing) - self.assertEqual(get_origin(type_1), Annotated) - self.assertIn(str, get_args(type_1)) - - self.assertEqual(get_origin(type_2), Annotated) - self.assertIn(int, get_args(type_2)) + self.assertEqual(type_1, str) + self.assertEqual(type_2, int) def test_any_of_string_or_int_with_default(self): """ @@ -74,11 +71,8 @@ class TestAnyOfTypeParser(TestCase): type_1, type_2 = get_args(type_parsing) - self.assertEqual(get_origin(type_1), Annotated) - self.assertIn(str, get_args(type_1)) - - self.assertEqual(get_origin(type_2), Annotated) - self.assertIn(int, get_args(type_2)) + self.assertEqual(type_1, str) + self.assertEqual(type_2, int) self.assertEqual(type_validator["default"], 42) -- 2.49.1 From 06a5ef71ca1a4b1f66f0eed4db9104a23743834d Mon Sep 17 00:00:00 2001 From: Thomas <34217413+thommann@users.noreply.github.com> Date: Fri, 4 Jul 2025 12:04:37 +0200 Subject: [PATCH 05/96] fix(jambo): Fix required array fields without defaults (#4) - Adds a test to ensure required array fields without defaults are enforced as required. - Updates `ArrayTypeParser` to correctly handle `default_factory` for required fields and fields with defaults. --- jambo/parser/array_type_parser.py | 10 +++++++++- tests/parser/test_array_type_parser.py | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/jambo/parser/array_type_parser.py b/jambo/parser/array_type_parser.py index c92de3d..0faa37e 100644 --- a/jambo/parser/array_type_parser.py +++ b/jambo/parser/array_type_parser.py @@ -35,10 +35,18 @@ class ArrayTypeParser(GenericTypeParser): mapped_properties = self.mappings_properties_builder(properties, **kwargs) - if "default" not in mapped_properties: + # Only set default_factory if the field is not required OR if there's an actual default + if not kwargs.get("required", False) and "default" not in mapped_properties: mapped_properties["default_factory"] = self._build_default_factory( properties.get("default"), wrapper_type ) + elif "default" in properties: + # If there's a default value specified, set the default_factory + mapped_properties["default_factory"] = self._build_default_factory( + properties["default"], wrapper_type + ) + # Remove the regular default since we're using default_factory + mapped_properties.pop("default", None) return field_type, mapped_properties diff --git a/tests/parser/test_array_type_parser.py b/tests/parser/test_array_type_parser.py index d27330f..9f3337a 100644 --- a/tests/parser/test_array_type_parser.py +++ b/tests/parser/test_array_type_parser.py @@ -97,3 +97,18 @@ class TestArrayTypeParser(TestCase): with self.assertRaises(ValueError): parser.from_properties("placeholder", properties) + + def test_array_parser_required_without_default(self): + """Regression test: Required array fields without defaults should be required""" + parser = ArrayTypeParser() + + properties = {"items": {"type": "string"}} + + # Test with required=True (should be required) + type_parsing, type_validator = parser.from_properties( + "test_array", properties, required=True + ) + + # Should NOT have default_factory when required and no default specified + self.assertNotIn("default_factory", type_validator) + self.assertNotIn("default", type_validator) -- 2.49.1 From c7afb5f80be28c7c69163722c5a4dab455a2d25d Mon Sep 17 00:00:00 2001 From: Thomas <34217413+thommann@users.noreply.github.com> Date: Mon, 7 Jul 2025 15:49:58 +0200 Subject: [PATCH 06/96] feat(jambo): Add oneOf parser (#5) * Add support for `oneOf` type parsing with validation and example cases * Improve `oneOf` type parsing: refine validators, add discriminator support, and expand test coverage * Add hashable and non-hashable value support to `ConstTypeParser` with expanded test cases * Refine `field_props` check in `_type_parser` for cleaner default handling * Update `StringTypeParser` to refine `format` handling and enrich `json_schema_extra` * Remove outdated `oneOf` examples from docs, expand test cases and provide refined examples with discriminator support --- docs/source/usage.oneof.rst | 108 ++++++ docs/source/usage.rst | 1 + jambo/parser/__init__.py | 4 +- jambo/parser/_type_parser.py | 23 ++ jambo/parser/anyof_type_parser.py | 23 -- jambo/parser/const_type_parser.py | 24 +- jambo/parser/oneof_type_parser.py | 69 ++++ jambo/parser/string_type_parser.py | 5 +- tests/parser/test_const_type_parser.py | 61 ++- tests/parser/test_oneof_type_parser.py | 493 +++++++++++++++++++++++++ 10 files changed, 774 insertions(+), 37 deletions(-) create mode 100644 docs/source/usage.oneof.rst create mode 100644 jambo/parser/oneof_type_parser.py create mode 100644 tests/parser/test_oneof_type_parser.py diff --git a/docs/source/usage.oneof.rst b/docs/source/usage.oneof.rst new file mode 100644 index 0000000..a836df4 --- /dev/null +++ b/docs/source/usage.oneof.rst @@ -0,0 +1,108 @@ +OneOf Type +================= + +The OneOf type is used to specify that an object must conform to exactly one of the specified schemas. Unlike AnyOf which allows matching multiple schemas, OneOf enforces that the data matches one and only one of the provided schemas. + + +Examples +----------------- + +1. **Overlapping String Example** - A field that accepts strings with overlapping constraints: + +.. code-block:: python + + from jambo import SchemaConverter + + schema = { + "title": "SimpleExample", + "type": "object", + "properties": { + "value": { + "oneOf": [ + {"type": "string", "maxLength": 6}, + {"type": "string", "minLength": 4} + ] + } + }, + "required": ["value"] + } + + Model = SchemaConverter.build(schema) + + # Valid: Short string (matches first schema only) + obj1 = Model(value="hi") + print(obj1.value) # Output: hi + + # Valid: Long string (matches second schema only) + obj2 = Model(value="very long string") + print(obj2.value) # Output: very long string + + # Invalid: Medium string (matches BOTH schemas - violates oneOf) + try: + obj3 = Model(value="hello") # 5 chars: matches maxLength=6 AND minLength=4 + except ValueError as e: + print("Validation fails as expected:", e) + + +2. **Discriminator Example** - Different shapes with a type field: + +.. code-block:: python + + from jambo import SchemaConverter + + schema = { + "title": "Shape", + "type": "object", + "properties": { + "shape": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": {"const": "circle"}, + "radius": {"type": "number", "minimum": 0} + }, + "required": ["type", "radius"] + }, + { + "type": "object", + "properties": { + "type": {"const": "rectangle"}, + "width": {"type": "number", "minimum": 0}, + "height": {"type": "number", "minimum": 0} + }, + "required": ["type", "width", "height"] + } + ], + "discriminator": { + "propertyName": "type" + } + } + }, + "required": ["shape"] + } + + Model = SchemaConverter.build(schema) + + # Valid: Circle + circle = Model(shape={"type": "circle", "radius": 5.0}) + print(circle.shape.type) # Output: circle + + # Valid: Rectangle + rectangle = Model(shape={"type": "rectangle", "width": 10, "height": 20}) + print(rectangle.shape.type) # Output: rectangle + + # Invalid: Wrong properties for the type + try: + invalid = Model(shape={"type": "circle", "width": 10}) + except ValueError as e: + print("Validation fails as expected:", e) + + +.. note:: + + OneOf ensures exactly one schema matches. The discriminator helps Pydantic efficiently determine which schema to use based on a specific property value. + +.. warning:: + + If your data could match multiple schemas in a oneOf, validation will fail. Ensure schemas are mutually exclusive. diff --git a/docs/source/usage.rst b/docs/source/usage.rst index 8896842..3bdb2d9 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -45,5 +45,6 @@ For more complex schemas and types see our documentation on usage.reference usage.allof usage.anyof + usage.oneof usage.enum usage.const \ No newline at end of file diff --git a/jambo/parser/__init__.py b/jambo/parser/__init__.py index f3b8b25..44b4424 100644 --- a/jambo/parser/__init__.py +++ b/jambo/parser/__init__.py @@ -9,6 +9,7 @@ from .float_type_parser import FloatTypeParser from .int_type_parser import IntTypeParser from .null_type_parser import NullTypeParser from .object_type_parser import ObjectTypeParser +from .oneof_type_parser import OneOfTypeParser from .ref_type_parser import RefTypeParser from .string_type_parser import StringTypeParser @@ -25,6 +26,7 @@ __all__ = [ "IntTypeParser", "NullTypeParser", "ObjectTypeParser", + "OneOfTypeParser", "StringTypeParser", "RefTypeParser", -] +] \ No newline at end of file diff --git a/jambo/parser/_type_parser.py b/jambo/parser/_type_parser.py index 080965c..6c5cdc9 100644 --- a/jambo/parser/_type_parser.py +++ b/jambo/parser/_type_parser.py @@ -124,3 +124,26 @@ class GenericTypeParser(ABC, Generic[T]): return False return True + + @staticmethod + def _has_meaningful_constraints(field_props): + """ + Check if field properties contain meaningful constraints that require Field wrapping. + + Returns False if: + - field_props is None or empty + - field_props only contains {'default': None} + + Returns True if: + - field_props contains a non-None default value + - field_props contains other constraint properties (min_length, max_length, pattern, etc.) + """ + if not field_props: + return False + + # If only default is set and it's None, no meaningful constraints + if field_props == {"default": None}: + return False + + # If there are multiple properties or non-None default, that's meaningful + return True diff --git a/jambo/parser/anyof_type_parser.py b/jambo/parser/anyof_type_parser.py index 8f43975..819867b 100644 --- a/jambo/parser/anyof_type_parser.py +++ b/jambo/parser/anyof_type_parser.py @@ -42,26 +42,3 @@ class AnyOfTypeParser(GenericTypeParser): ] return Union[(*field_types,)], mapped_properties - - @staticmethod - def _has_meaningful_constraints(field_props): - """ - Check if field properties contain meaningful constraints that require Field wrapping. - - Returns False if: - - field_props is None or empty - - field_props only contains {'default': None} - - Returns True if: - - field_props contains a non-None default value - - field_props contains other constraint properties (min_length, max_length, pattern, etc.) - """ - if not field_props: - return False - - # If only default is set and it's None, no meaningful constraints - if len(field_props) == 1 and field_props.get('default') is None: - return False - - # If there are multiple properties or non-None default, that's meaningful - return True \ No newline at end of file diff --git a/jambo/parser/const_type_parser.py b/jambo/parser/const_type_parser.py index b5c846f..1e4ce84 100644 --- a/jambo/parser/const_type_parser.py +++ b/jambo/parser/const_type_parser.py @@ -3,7 +3,7 @@ from jambo.types.json_schema_type import JSONSchemaNativeTypes from jambo.types.type_parser_options import TypeParserOptions from pydantic import AfterValidator -from typing_extensions import Annotated, Any, Unpack +from typing_extensions import Annotated, Any, Literal, Unpack class ConstTypeParser(GenericTypeParser): @@ -33,11 +33,19 @@ class ConstTypeParser(GenericTypeParser): return const_type, parsed_properties def _build_const_type(self, const_value): - def _validate_const_value(value: Any) -> Any: - if value != const_value: - raise ValueError( - f"Value must be equal to the constant value: {const_value}" - ) - return value + # Try to use Literal for hashable types (required for discriminated unions) + # Fall back to validator approach for non-hashable types + try: + # Test if the value is hashable (can be used in Literal) + hash(const_value) + return Literal[const_value] + except TypeError: + # Non-hashable type (like list, dict), use validator approach + def _validate_const_value(value: Any) -> Any: + if value != const_value: + raise ValueError( + f"Value must be equal to the constant value: {const_value}" + ) + return value - return Annotated[type(const_value), AfterValidator(_validate_const_value)] + return Annotated[type(const_value), AfterValidator(_validate_const_value)] \ No newline at end of file diff --git a/jambo/parser/oneof_type_parser.py b/jambo/parser/oneof_type_parser.py new file mode 100644 index 0000000..79146b9 --- /dev/null +++ b/jambo/parser/oneof_type_parser.py @@ -0,0 +1,69 @@ +from jambo.parser._type_parser import GenericTypeParser +from jambo.types.type_parser_options import TypeParserOptions + +from pydantic import Field, BeforeValidator, TypeAdapter, ValidationError +from typing_extensions import Annotated, Union, Unpack, Any + + +class OneOfTypeParser(GenericTypeParser): + mapped_type = Union + + json_schema_type = "oneOf" + + def from_properties_impl( + self, name, properties, **kwargs: Unpack[TypeParserOptions] + ): + if "oneOf" not in properties: + raise ValueError(f"Invalid JSON Schema: {properties}") + + if not isinstance(properties["oneOf"], list): + raise ValueError(f"Invalid JSON Schema: {properties['oneOf']}") + + mapped_properties = self.mappings_properties_builder(properties, **kwargs) + + sub_properties = properties["oneOf"] + + sub_types = [ + GenericTypeParser.type_from_properties(name, subProperty, **kwargs) + for subProperty in sub_properties + ] + + if not kwargs.get("required", False): + mapped_properties["default"] = mapped_properties.get("default") + + field_types = [ + Annotated[t, Field(**v)] if self._has_meaningful_constraints(v) else t + for t, v in sub_types + ] + + union_type = Union[(*field_types,)] + + discriminator = properties.get("discriminator") + if discriminator and isinstance(discriminator, dict): + property_name = discriminator.get("propertyName") + if property_name: + validated_type = Annotated[union_type, Field(discriminator=property_name)] + return validated_type, mapped_properties + + def validate_one_of(value: Any) -> Any: + matched_count = 0 + validation_errors = [] + + for field_type in field_types: + try: + adapter = TypeAdapter(field_type) + adapter.validate_python(value) + matched_count += 1 + except ValidationError as e: + validation_errors.append(str(e)) + continue + + if matched_count == 0: + raise ValueError(f"Value does not match any of the oneOf schemas") + elif matched_count > 1: + raise ValueError(f"Value matches multiple oneOf schemas, exactly one expected") + + return value + + validated_type = Annotated[union_type, BeforeValidator(validate_one_of)] + return validated_type, mapped_properties diff --git a/jambo/parser/string_type_parser.py b/jambo/parser/string_type_parser.py index 1eb25b9..4e7a7e4 100644 --- a/jambo/parser/string_type_parser.py +++ b/jambo/parser/string_type_parser.py @@ -16,7 +16,6 @@ class StringTypeParser(GenericTypeParser): "maxLength": "max_length", "minLength": "min_length", "pattern": "pattern", - "format": "format", } format_type_mapping = { @@ -52,4 +51,8 @@ class StringTypeParser(GenericTypeParser): if format_type in self.format_pattern_mapping: mapped_properties["pattern"] = self.format_pattern_mapping[format_type] + if "json_schema_extra" not in mapped_properties: + mapped_properties["json_schema_extra"] = {} + mapped_properties["json_schema_extra"]["format"] = format_type + return mapped_type, mapped_properties diff --git a/tests/parser/test_const_type_parser.py b/tests/parser/test_const_type_parser.py index ca92bb0..5a8c9c1 100644 --- a/tests/parser/test_const_type_parser.py +++ b/tests/parser/test_const_type_parser.py @@ -1,12 +1,13 @@ from jambo.parser import ConstTypeParser -from typing_extensions import Annotated, get_args, get_origin +from typing_extensions import Annotated, Literal, get_args, get_origin from unittest import TestCase class TestConstTypeParser(TestCase): - def test_const_type_parser(self): + def test_const_type_parser_hashable_value(self): + """Test const parser with hashable values (uses Literal)""" parser = ConstTypeParser() expected_const_value = "United States of America" @@ -16,8 +17,60 @@ class TestConstTypeParser(TestCase): "country", properties ) + # Check that we get a Literal type for hashable values + self.assertEqual(get_origin(parsed_type), Literal) + self.assertEqual(get_args(parsed_type), (expected_const_value,)) + + self.assertEqual(parsed_properties["default"], expected_const_value) + + def test_const_type_parser_non_hashable_value(self): + """Test const parser with non-hashable values (uses Annotated with validator)""" + parser = ConstTypeParser() + + expected_const_value = [1, 2, 3] # Lists are not hashable + properties = {"const": expected_const_value} + + parsed_type, parsed_properties = parser.from_properties_impl( + "list_const", properties + ) + + # Check that we get an Annotated type for non-hashable values self.assertEqual(get_origin(parsed_type), Annotated) - self.assertIn(str, get_args(parsed_type)) + self.assertIn(list, get_args(parsed_type)) + + self.assertEqual(parsed_properties["default"], expected_const_value) + + def test_const_type_parser_integer_value(self): + """Test const parser with integer values (uses Literal)""" + parser = ConstTypeParser() + + expected_const_value = 42 + properties = {"const": expected_const_value} + + parsed_type, parsed_properties = parser.from_properties_impl( + "int_const", properties + ) + + # Check that we get a Literal type for hashable values + self.assertEqual(get_origin(parsed_type), Literal) + self.assertEqual(get_args(parsed_type), (expected_const_value,)) + + self.assertEqual(parsed_properties["default"], expected_const_value) + + def test_const_type_parser_boolean_value(self): + """Test const parser with boolean values (uses Literal)""" + parser = ConstTypeParser() + + expected_const_value = True + properties = {"const": expected_const_value} + + parsed_type, parsed_properties = parser.from_properties_impl( + "bool_const", properties + ) + + # Check that we get a Literal type for hashable values + self.assertEqual(get_origin(parsed_type), Literal) + self.assertEqual(get_args(parsed_type), (expected_const_value,)) self.assertEqual(parsed_properties["default"], expected_const_value) @@ -46,4 +99,4 @@ class TestConstTypeParser(TestCase): self.assertIn( "Const type invalid_country must have 'const' value of allowed types", str(context.exception), - ) + ) \ No newline at end of file diff --git a/tests/parser/test_oneof_type_parser.py b/tests/parser/test_oneof_type_parser.py new file mode 100644 index 0000000..8c75f04 --- /dev/null +++ b/tests/parser/test_oneof_type_parser.py @@ -0,0 +1,493 @@ +from jambo import SchemaConverter + +from unittest import TestCase + + +class TestOneOfTypeParser(TestCase): + def test_oneof_basic_integer_and_string(self): + schema = { + "title": "Person", + "description": "A person with an ID that can be either an integer or a formatted string", + "type": "object", + "properties": { + "id": { + "oneOf": [ + {"type": "integer", "minimum": 1}, + {"type": "string", "pattern": "^[A-Z]{2}[0-9]{4}$"}, + ] + }, + }, + "required": ["id"], + } + + Model = SchemaConverter.build(schema) + + obj1 = Model(id=123) + self.assertEqual(obj1.id, 123) + + obj2 = Model(id="AB1234") + self.assertEqual(obj2.id, "AB1234") + + def test_oneof_validation_failures(self): + schema = { + "title": "Person", + "type": "object", + "properties": { + "id": { + "oneOf": [ + {"type": "integer", "minimum": 1}, + {"type": "string", "pattern": "^[A-Z]{2}[0-9]{4}$"}, + ] + }, + }, + "required": ["id"], + } + + Model = SchemaConverter.build(schema) + + with self.assertRaises(ValueError): + Model(id=-5) + + with self.assertRaises(ValueError): + Model(id="invalid") + + with self.assertRaises(ValueError): + Model(id=123.45) + + def test_oneof_with_conflicting_schemas(self): + schema = { + "title": "Value", + "type": "object", + "properties": { + "data": { + "oneOf": [ + {"type": "number", "multipleOf": 2}, + {"type": "number", "multipleOf": 3}, + ] + }, + }, + "required": ["data"], + } + + Model = SchemaConverter.build(schema) + + obj1 = Model(data=4) + self.assertEqual(obj1.data, 4) + + obj2 = Model(data=9) + self.assertEqual(obj2.data, 9) + + with self.assertRaises(ValueError) as cm: + Model(data=6) + self.assertIn("matches multiple oneOf schemas", str(cm.exception)) + + with self.assertRaises(ValueError): + Model(data=5) + + def test_oneof_with_objects(self): + schema = { + "title": "Contact", + "type": "object", + "properties": { + "contact_info": { + "oneOf": [ + { + "type": "object", + "properties": { + "email": {"type": "string", "format": "email"} + }, + "required": ["email"], + "additionalProperties": False + }, + { + "type": "object", + "properties": { + "phone": {"type": "string", "pattern": "^[0-9-]+$"} + }, + "required": ["phone"], + "additionalProperties": False + } + ] + }, + }, + "required": ["contact_info"], + } + + Model = SchemaConverter.build(schema) + + obj1 = Model(contact_info={"email": "user@example.com"}) + self.assertEqual(obj1.contact_info.email, "user@example.com") + + obj2 = Model(contact_info={"phone": "123-456-7890"}) + self.assertEqual(obj2.contact_info.phone, "123-456-7890") + + with self.assertRaises(ValueError): + Model(contact_info={"email": "user@example.com", "phone": "123-456-7890"}) + + def test_oneof_with_discriminator_basic(self): + schema = { + "title": "Pet", + "type": "object", + "properties": { + "pet": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": {"const": "cat"}, + "meows": {"type": "boolean"} + }, + "required": ["type", "meows"] + }, + { + "type": "object", + "properties": { + "type": {"const": "dog"}, + "barks": {"type": "boolean"} + }, + "required": ["type", "barks"] + } + ], + "discriminator": { + "propertyName": "type" + } + } + }, + "required": ["pet"] + } + + Model = SchemaConverter.build(schema) + + cat = Model(pet={"type": "cat", "meows": True}) + self.assertEqual(cat.pet.type, "cat") + self.assertEqual(cat.pet.meows, True) + + dog = Model(pet={"type": "dog", "barks": False}) + self.assertEqual(dog.pet.type, "dog") + self.assertEqual(dog.pet.barks, False) + + with self.assertRaises(ValueError): + Model(pet={"type": "cat", "barks": True}) + + with self.assertRaises(ValueError): + Model(pet={"type": "bird", "flies": True}) + + def test_oneof_with_discriminator_mapping(self): + schema = { + "title": "Vehicle", + "type": "object", + "properties": { + "vehicle": { + "oneOf": [ + { + "type": "object", + "properties": { + "vehicle_type": {"const": "car"}, + "doors": {"type": "integer", "minimum": 2, "maximum": 4} + }, + "required": ["vehicle_type", "doors"] + }, + { + "type": "object", + "properties": { + "vehicle_type": {"const": "motorcycle"}, + "engine_size": {"type": "number", "minimum": 125} + }, + "required": ["vehicle_type", "engine_size"] + } + ], + "discriminator": { + "propertyName": "vehicle_type", + "mapping": { + "car": "#/properties/vehicle/oneOf/0", + "motorcycle": "#/properties/vehicle/oneOf/1" + } + } + } + }, + "required": ["vehicle"] + } + + Model = SchemaConverter.build(schema) + + car = Model(vehicle={"vehicle_type": "car", "doors": 4}) + self.assertEqual(car.vehicle.vehicle_type, "car") + self.assertEqual(car.vehicle.doors, 4) + + motorcycle = Model(vehicle={"vehicle_type": "motorcycle", "engine_size": 600.0}) + self.assertEqual(motorcycle.vehicle.vehicle_type, "motorcycle") + self.assertEqual(motorcycle.vehicle.engine_size, 600.0) + + def test_oneof_with_discriminator_invalid_values(self): + schema = { + "title": "Shape", + "type": "object", + "properties": { + "shape": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": {"const": "circle"}, + "radius": {"type": "number", "minimum": 0} + }, + "required": ["type", "radius"] + }, + { + "type": "object", + "properties": { + "type": {"const": "square"}, + "side": {"type": "number", "minimum": 0} + }, + "required": ["type", "side"] + } + ], + "discriminator": { + "propertyName": "type" + } + } + }, + "required": ["shape"] + } + + Model = SchemaConverter.build(schema) + + with self.assertRaises(ValueError): + Model(shape={"type": "triangle", "base": 5, "height": 3}) + + with self.assertRaises(ValueError): + Model(shape={"type": "circle", "side": 5}) + + with self.assertRaises(ValueError): + Model(shape={"radius": 5}) + + def test_oneof_missing_properties(self): + schema = { + "title": "Test", + "type": "object", + "properties": { + "value": { + "notOneOf": [ + {"type": "string"}, + {"type": "integer"}, + ] + }, + }, + } + + with self.assertRaises(ValueError): + SchemaConverter.build(schema) + + def test_oneof_invalid_properties(self): + schema = { + "title": "Test", + "type": "object", + "properties": { + "value": { + "oneOf": None + }, + }, + } + + with self.assertRaises(ValueError): + SchemaConverter.build(schema) + + def test_oneof_with_default_value(self): + schema = { + "title": "Test", + "type": "object", + "properties": { + "value": { + "oneOf": [ + {"type": "string"}, + {"type": "integer"}, + ], + "default": "test" + }, + }, + } + + Model = SchemaConverter.build(schema) + obj = Model() + self.assertEqual(obj.value, "test") + + def test_oneof_with_invalid_default_value(self): + schema = { + "title": "Test", + "type": "object", + "properties": { + "value": { + "oneOf": [ + {"type": "string", "minLength": 5}, + {"type": "integer", "minimum": 10}, + ], + "default": "hi" + }, + }, + } + + with self.assertRaises(ValueError): + SchemaConverter.build(schema) + + def test_oneof_discriminator_without_property_name(self): + schema = { + "title": "Test", + "type": "object", + "properties": { + "value": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": {"const": "a"}, + "value": {"type": "string"} + } + }, + { + "type": "object", + "properties": { + "type": {"const": "b"}, + "value": {"type": "integer"} + } + } + ], + "discriminator": {} # discriminator without propertyName + } + } + } + + Model = SchemaConverter.build(schema) + + # Should succeed because input matches exactly one schema (the first one) + # The first schema matches: type="a" matches const("a"), value="test" is a string + # The second schema doesn't match: type="a" does not match const("b") + obj = Model(value={"type": "a", "value": "test", "extra": "invalid"}) + self.assertEqual(obj.value.type, "a") + self.assertEqual(obj.value.value, "test") + + # Test with input that matches the second schema + obj2 = Model(value={"type": "b", "value": 42}) + self.assertEqual(obj2.value.type, "b") + self.assertEqual(obj2.value.value, 42) + + # Test with input that matches neither schema (should fail) + with self.assertRaises(ValueError) as cm: + Model(value={"type": "c", "value": "test"}) + self.assertIn("does not match any of the oneOf schemas", str(cm.exception)) + + def test_oneof_multiple_matches_without_discriminator(self): + """Test case where input genuinely matches multiple oneOf schemas""" + schema = { + "title": "Test", + "type": "object", + "properties": { + "value": { + "oneOf": [ + { + "type": "object", + "properties": { + "data": {"type": "string"} + } + }, + { + "type": "object", + "properties": { + "data": {"type": "string"}, + "optional": {"type": "string"} + } + } + ], + "discriminator": {} # discriminator without propertyName + } + } + } + + Model = SchemaConverter.build(schema) + + # This input matches both schemas since both accept data as string + # and neither requires specific additional properties + with self.assertRaises(ValueError) as cm: + Model(value={"data": "test"}) + self.assertIn("matches multiple oneOf schemas", str(cm.exception)) + + def test_oneof_overlapping_strings_from_docs(self): + """Test the overlapping strings example from documentation""" + schema = { + "title": "SimpleExample", + "type": "object", + "properties": { + "value": { + "oneOf": [ + {"type": "string", "maxLength": 6}, + {"type": "string", "minLength": 4} + ] + } + }, + "required": ["value"] + } + + Model = SchemaConverter.build(schema) + + # Valid: Short string (matches first schema only) + obj1 = Model(value="hi") + self.assertEqual(obj1.value, "hi") + + # Valid: Long string (matches second schema only) + obj2 = Model(value="very long string") + self.assertEqual(obj2.value, "very long string") + + # Invalid: Medium string (matches BOTH schemas - violates oneOf) + with self.assertRaises(ValueError) as cm: + Model(value="hello") # 5 chars: matches maxLength=6 AND minLength=4 + self.assertIn("matches multiple oneOf schemas", str(cm.exception)) + + def test_oneof_shapes_discriminator_from_docs(self): + """Test the shapes discriminator example from documentation""" + schema = { + "title": "Shape", + "type": "object", + "properties": { + "shape": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": {"const": "circle"}, + "radius": {"type": "number", "minimum": 0} + }, + "required": ["type", "radius"] + }, + { + "type": "object", + "properties": { + "type": {"const": "rectangle"}, + "width": {"type": "number", "minimum": 0}, + "height": {"type": "number", "minimum": 0} + }, + "required": ["type", "width", "height"] + } + ], + "discriminator": { + "propertyName": "type" + } + } + }, + "required": ["shape"] + } + + Model = SchemaConverter.build(schema) + + # Valid: Circle + circle = Model(shape={"type": "circle", "radius": 5.0}) + self.assertEqual(circle.shape.type, "circle") + self.assertEqual(circle.shape.radius, 5.0) + + # Valid: Rectangle + rectangle = Model(shape={"type": "rectangle", "width": 10, "height": 20}) + self.assertEqual(rectangle.shape.type, "rectangle") + self.assertEqual(rectangle.shape.width, 10) + self.assertEqual(rectangle.shape.height, 20) + + # Invalid: Wrong properties for the type + with self.assertRaises(ValueError): + Model(shape={"type": "circle", "width": 10}) -- 2.49.1 From 45018eadd181e3698e13a89d731dc3255aab49f6 Mon Sep 17 00:00:00 2001 From: Thomas <34217413+thommann@users.noreply.github.com> Date: Mon, 7 Jul 2025 17:20:45 +0200 Subject: [PATCH 07/96] fix(jambo): Add binary string support (#6) --- jambo/parser/string_type_parser.py | 1 + tests/parser/test_string_type_parser.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/jambo/parser/string_type_parser.py b/jambo/parser/string_type_parser.py index 4e7a7e4..e45012e 100644 --- a/jambo/parser/string_type_parser.py +++ b/jambo/parser/string_type_parser.py @@ -27,6 +27,7 @@ class StringTypeParser(GenericTypeParser): "date": date, "time": time, "date-time": datetime, + "binary": bytes, } format_pattern_mapping = { diff --git a/tests/parser/test_string_type_parser.py b/tests/parser/test_string_type_parser.py index f4dc3d6..d86dfc1 100644 --- a/tests/parser/test_string_type_parser.py +++ b/tests/parser/test_string_type_parser.py @@ -197,3 +197,18 @@ class TestStringTypeParser(TestCase): type_parsing, type_validator = parser.from_properties("placeholder", properties) self.assertEqual(type_parsing, datetime) + + def test_string_parser_with_byte_format(self): + parser = StringTypeParser() + + properties = { + "type": "string", + "format": "binary", + } + + type_parsing, type_validator = parser.from_properties("placeholder", properties) + + self.assertEqual(type_parsing, bytes) + + self.assertIn("json_schema_extra", type_validator) + self.assertEqual(type_validator["json_schema_extra"]["format"], "binary") -- 2.49.1 From 053279ba95c9b4aa595fb766b37e37511f026731 Mon Sep 17 00:00:00 2001 From: Thomas <34217413+thommann@users.noreply.github.com> Date: Mon, 7 Jul 2025 17:31:33 +0200 Subject: [PATCH 08/96] fix(jambo): Add support for `file-path` format in `StringTypeParser` (#7) --- jambo/parser/string_type_parser.py | 5 +++-- tests/parser/test_string_type_parser.py | 14 +++++++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/jambo/parser/string_type_parser.py b/jambo/parser/string_type_parser.py index e45012e..0f746fb 100644 --- a/jambo/parser/string_type_parser.py +++ b/jambo/parser/string_type_parser.py @@ -1,7 +1,7 @@ from jambo.parser._type_parser import GenericTypeParser from jambo.types.type_parser_options import TypeParserOptions -from pydantic import EmailStr, HttpUrl, IPvAnyAddress +from pydantic import EmailStr, HttpUrl, IPvAnyAddress, FilePath from typing_extensions import Unpack from datetime import date, datetime, time @@ -28,6 +28,7 @@ class StringTypeParser(GenericTypeParser): "time": time, "date-time": datetime, "binary": bytes, + "file-path": FilePath, # Added file-path format } format_pattern_mapping = { @@ -56,4 +57,4 @@ class StringTypeParser(GenericTypeParser): mapped_properties["json_schema_extra"] = {} mapped_properties["json_schema_extra"]["format"] = format_type - return mapped_type, mapped_properties + return mapped_type, mapped_properties \ No newline at end of file diff --git a/tests/parser/test_string_type_parser.py b/tests/parser/test_string_type_parser.py index d86dfc1..dda0aef 100644 --- a/tests/parser/test_string_type_parser.py +++ b/tests/parser/test_string_type_parser.py @@ -1,6 +1,6 @@ from jambo.parser import StringTypeParser -from pydantic import EmailStr, HttpUrl, IPvAnyAddress +from pydantic import EmailStr, HttpUrl, IPvAnyAddress, FilePath from datetime import date, datetime, time from unittest import TestCase @@ -159,6 +159,18 @@ class TestStringTypeParser(TestCase): type_validator["pattern"], parser.format_pattern_mapping[format_type] ) + def test_string_parser_with_file_path_format(self): + parser = StringTypeParser() + + properties = { + "type": "string", + "format": "file-path", + } + + type_parsing, type_validator = parser.from_properties("placeholder", properties) + + self.assertEqual(type_parsing, FilePath) + def test_string_parser_with_unsupported_format(self): parser = StringTypeParser() -- 2.49.1 From 8dbe84fd1c0bf5e6de47a09f9cad21c10a0cc99e Mon Sep 17 00:00:00 2001 From: Thomas <34217413+thommann@users.noreply.github.com> Date: Tue, 8 Jul 2025 09:06:07 +0200 Subject: [PATCH 09/96] chore(jambo): Remove redundant comments and docstrings (#8) * Remove redundant comments in type parsers and tests * Clean up redundant comments in `test_oneof_type_parser.py` * Remove redundant comments across parsers and tests --- jambo/parser/__init__.py | 2 +- jambo/parser/_type_parser.py | 14 -------------- jambo/parser/array_type_parser.py | 3 --- jambo/parser/const_type_parser.py | 6 +----- jambo/parser/string_type_parser.py | 4 ++-- tests/parser/test_array_type_parser.py | 3 --- tests/parser/test_const_type_parser.py | 12 ++---------- tests/parser/test_oneof_type_parser.py | 20 ++------------------ 8 files changed, 8 insertions(+), 56 deletions(-) diff --git a/jambo/parser/__init__.py b/jambo/parser/__init__.py index 44b4424..0de9f00 100644 --- a/jambo/parser/__init__.py +++ b/jambo/parser/__init__.py @@ -29,4 +29,4 @@ __all__ = [ "OneOfTypeParser", "StringTypeParser", "RefTypeParser", -] \ No newline at end of file +] diff --git a/jambo/parser/_type_parser.py b/jambo/parser/_type_parser.py index 6c5cdc9..9350c25 100644 --- a/jambo/parser/_type_parser.py +++ b/jambo/parser/_type_parser.py @@ -127,23 +127,9 @@ class GenericTypeParser(ABC, Generic[T]): @staticmethod def _has_meaningful_constraints(field_props): - """ - Check if field properties contain meaningful constraints that require Field wrapping. - - Returns False if: - - field_props is None or empty - - field_props only contains {'default': None} - - Returns True if: - - field_props contains a non-None default value - - field_props contains other constraint properties (min_length, max_length, pattern, etc.) - """ if not field_props: return False - # If only default is set and it's None, no meaningful constraints if field_props == {"default": None}: return False - - # If there are multiple properties or non-None default, that's meaningful return True diff --git a/jambo/parser/array_type_parser.py b/jambo/parser/array_type_parser.py index 0faa37e..52e3bcf 100644 --- a/jambo/parser/array_type_parser.py +++ b/jambo/parser/array_type_parser.py @@ -35,17 +35,14 @@ class ArrayTypeParser(GenericTypeParser): mapped_properties = self.mappings_properties_builder(properties, **kwargs) - # Only set default_factory if the field is not required OR if there's an actual default if not kwargs.get("required", False) and "default" not in mapped_properties: mapped_properties["default_factory"] = self._build_default_factory( properties.get("default"), wrapper_type ) elif "default" in properties: - # If there's a default value specified, set the default_factory mapped_properties["default_factory"] = self._build_default_factory( properties["default"], wrapper_type ) - # Remove the regular default since we're using default_factory mapped_properties.pop("default", None) return field_type, mapped_properties diff --git a/jambo/parser/const_type_parser.py b/jambo/parser/const_type_parser.py index 1e4ce84..4865df0 100644 --- a/jambo/parser/const_type_parser.py +++ b/jambo/parser/const_type_parser.py @@ -33,14 +33,10 @@ class ConstTypeParser(GenericTypeParser): return const_type, parsed_properties def _build_const_type(self, const_value): - # Try to use Literal for hashable types (required for discriminated unions) - # Fall back to validator approach for non-hashable types try: - # Test if the value is hashable (can be used in Literal) hash(const_value) return Literal[const_value] except TypeError: - # Non-hashable type (like list, dict), use validator approach def _validate_const_value(value: Any) -> Any: if value != const_value: raise ValueError( @@ -48,4 +44,4 @@ class ConstTypeParser(GenericTypeParser): ) return value - return Annotated[type(const_value), AfterValidator(_validate_const_value)] \ No newline at end of file + return Annotated[type(const_value), AfterValidator(_validate_const_value)] diff --git a/jambo/parser/string_type_parser.py b/jambo/parser/string_type_parser.py index 0f746fb..04f1e98 100644 --- a/jambo/parser/string_type_parser.py +++ b/jambo/parser/string_type_parser.py @@ -28,7 +28,7 @@ class StringTypeParser(GenericTypeParser): "time": time, "date-time": datetime, "binary": bytes, - "file-path": FilePath, # Added file-path format + "file-path": FilePath, } format_pattern_mapping = { @@ -57,4 +57,4 @@ class StringTypeParser(GenericTypeParser): mapped_properties["json_schema_extra"] = {} mapped_properties["json_schema_extra"]["format"] = format_type - return mapped_type, mapped_properties \ No newline at end of file + return mapped_type, mapped_properties diff --git a/tests/parser/test_array_type_parser.py b/tests/parser/test_array_type_parser.py index 9f3337a..ee09987 100644 --- a/tests/parser/test_array_type_parser.py +++ b/tests/parser/test_array_type_parser.py @@ -99,16 +99,13 @@ class TestArrayTypeParser(TestCase): parser.from_properties("placeholder", properties) def test_array_parser_required_without_default(self): - """Regression test: Required array fields without defaults should be required""" parser = ArrayTypeParser() properties = {"items": {"type": "string"}} - # Test with required=True (should be required) type_parsing, type_validator = parser.from_properties( "test_array", properties, required=True ) - # Should NOT have default_factory when required and no default specified self.assertNotIn("default_factory", type_validator) self.assertNotIn("default", type_validator) diff --git a/tests/parser/test_const_type_parser.py b/tests/parser/test_const_type_parser.py index 5a8c9c1..8f3661c 100644 --- a/tests/parser/test_const_type_parser.py +++ b/tests/parser/test_const_type_parser.py @@ -7,7 +7,6 @@ from unittest import TestCase class TestConstTypeParser(TestCase): def test_const_type_parser_hashable_value(self): - """Test const parser with hashable values (uses Literal)""" parser = ConstTypeParser() expected_const_value = "United States of America" @@ -17,31 +16,27 @@ class TestConstTypeParser(TestCase): "country", properties ) - # Check that we get a Literal type for hashable values self.assertEqual(get_origin(parsed_type), Literal) self.assertEqual(get_args(parsed_type), (expected_const_value,)) self.assertEqual(parsed_properties["default"], expected_const_value) def test_const_type_parser_non_hashable_value(self): - """Test const parser with non-hashable values (uses Annotated with validator)""" parser = ConstTypeParser() - expected_const_value = [1, 2, 3] # Lists are not hashable + expected_const_value = [1, 2, 3] properties = {"const": expected_const_value} parsed_type, parsed_properties = parser.from_properties_impl( "list_const", properties ) - # Check that we get an Annotated type for non-hashable values self.assertEqual(get_origin(parsed_type), Annotated) self.assertIn(list, get_args(parsed_type)) self.assertEqual(parsed_properties["default"], expected_const_value) def test_const_type_parser_integer_value(self): - """Test const parser with integer values (uses Literal)""" parser = ConstTypeParser() expected_const_value = 42 @@ -51,14 +46,12 @@ class TestConstTypeParser(TestCase): "int_const", properties ) - # Check that we get a Literal type for hashable values self.assertEqual(get_origin(parsed_type), Literal) self.assertEqual(get_args(parsed_type), (expected_const_value,)) self.assertEqual(parsed_properties["default"], expected_const_value) def test_const_type_parser_boolean_value(self): - """Test const parser with boolean values (uses Literal)""" parser = ConstTypeParser() expected_const_value = True @@ -68,7 +61,6 @@ class TestConstTypeParser(TestCase): "bool_const", properties ) - # Check that we get a Literal type for hashable values self.assertEqual(get_origin(parsed_type), Literal) self.assertEqual(get_args(parsed_type), (expected_const_value,)) @@ -99,4 +91,4 @@ class TestConstTypeParser(TestCase): self.assertIn( "Const type invalid_country must have 'const' value of allowed types", str(context.exception), - ) \ No newline at end of file + ) diff --git a/tests/parser/test_oneof_type_parser.py b/tests/parser/test_oneof_type_parser.py index 8c75f04..cea0a5e 100644 --- a/tests/parser/test_oneof_type_parser.py +++ b/tests/parser/test_oneof_type_parser.py @@ -351,32 +351,26 @@ class TestOneOfTypeParser(TestCase): } } ], - "discriminator": {} # discriminator without propertyName + "discriminator": {} } } } Model = SchemaConverter.build(schema) - # Should succeed because input matches exactly one schema (the first one) - # The first schema matches: type="a" matches const("a"), value="test" is a string - # The second schema doesn't match: type="a" does not match const("b") obj = Model(value={"type": "a", "value": "test", "extra": "invalid"}) self.assertEqual(obj.value.type, "a") self.assertEqual(obj.value.value, "test") - # Test with input that matches the second schema obj2 = Model(value={"type": "b", "value": 42}) self.assertEqual(obj2.value.type, "b") self.assertEqual(obj2.value.value, 42) - # Test with input that matches neither schema (should fail) with self.assertRaises(ValueError) as cm: Model(value={"type": "c", "value": "test"}) self.assertIn("does not match any of the oneOf schemas", str(cm.exception)) def test_oneof_multiple_matches_without_discriminator(self): - """Test case where input genuinely matches multiple oneOf schemas""" schema = { "title": "Test", "type": "object", @@ -397,21 +391,18 @@ class TestOneOfTypeParser(TestCase): } } ], - "discriminator": {} # discriminator without propertyName + "discriminator": {} } } } Model = SchemaConverter.build(schema) - # This input matches both schemas since both accept data as string - # and neither requires specific additional properties with self.assertRaises(ValueError) as cm: Model(value={"data": "test"}) self.assertIn("matches multiple oneOf schemas", str(cm.exception)) def test_oneof_overlapping_strings_from_docs(self): - """Test the overlapping strings example from documentation""" schema = { "title": "SimpleExample", "type": "object", @@ -428,21 +419,17 @@ class TestOneOfTypeParser(TestCase): Model = SchemaConverter.build(schema) - # Valid: Short string (matches first schema only) obj1 = Model(value="hi") self.assertEqual(obj1.value, "hi") - # Valid: Long string (matches second schema only) obj2 = Model(value="very long string") self.assertEqual(obj2.value, "very long string") - # Invalid: Medium string (matches BOTH schemas - violates oneOf) with self.assertRaises(ValueError) as cm: Model(value="hello") # 5 chars: matches maxLength=6 AND minLength=4 self.assertIn("matches multiple oneOf schemas", str(cm.exception)) def test_oneof_shapes_discriminator_from_docs(self): - """Test the shapes discriminator example from documentation""" schema = { "title": "Shape", "type": "object", @@ -477,17 +464,14 @@ class TestOneOfTypeParser(TestCase): Model = SchemaConverter.build(schema) - # Valid: Circle circle = Model(shape={"type": "circle", "radius": 5.0}) self.assertEqual(circle.shape.type, "circle") self.assertEqual(circle.shape.radius, 5.0) - # Valid: Rectangle rectangle = Model(shape={"type": "rectangle", "width": 10, "height": 20}) self.assertEqual(rectangle.shape.type, "rectangle") self.assertEqual(rectangle.shape.width, 10) self.assertEqual(rectangle.shape.height, 20) - # Invalid: Wrong properties for the type with self.assertRaises(ValueError): Model(shape={"type": "circle", "width": 10}) -- 2.49.1 From 9c598eeaccb191dec5519f86a5a0809649d873f3 Mon Sep 17 00:00:00 2001 From: Thomas <34217413+thommann@users.noreply.github.com> Date: Tue, 8 Jul 2025 16:41:09 +0200 Subject: [PATCH 10/96] fix(jambo): Update legacy typing to built in types (#9) * Migrate from `typing_extensions` to `typing` for supported Python versions, and update code to use modern type hinting with unions and annotations. * Update type hinting to use lowercase generics for Python 3.9+ compatibility. --- jambo/parser/_type_parser.py | 4 +-- jambo/parser/allof_type_parser.py | 2 +- jambo/parser/anyof_type_parser.py | 11 ++++-- jambo/parser/array_type_parser.py | 2 +- jambo/parser/boolean_type_parser.py | 2 +- jambo/parser/const_type_parser.py | 2 +- jambo/parser/enum_type_parser.py | 2 +- jambo/parser/float_type_parser.py | 2 +- jambo/parser/int_type_parser.py | 2 +- jambo/parser/null_type_parser.py | 2 +- jambo/parser/object_type_parser.py | 2 +- jambo/parser/oneof_type_parser.py | 11 +++--- jambo/parser/ref_type_parser.py | 6 ++-- jambo/parser/string_type_parser.py | 2 +- jambo/types/json_schema_type.py | 50 +++++++++++++------------- jambo/types/type_parser_options.py | 2 +- tests/parser/test_anyof_type_parser.py | 6 ++-- tests/parser/test_array_type_parser.py | 2 +- tests/parser/test_const_type_parser.py | 2 +- 19 files changed, 63 insertions(+), 51 deletions(-) diff --git a/jambo/parser/_type_parser.py b/jambo/parser/_type_parser.py index 9350c25..76d2614 100644 --- a/jambo/parser/_type_parser.py +++ b/jambo/parser/_type_parser.py @@ -1,7 +1,7 @@ from jambo.types.type_parser_options import TypeParserOptions from pydantic import Field, TypeAdapter -from typing_extensions import Annotated, Any, Generic, Self, TypeVar, Unpack +from typing import Annotated, Any, Generic, Self, TypeVar, Unpack from abc import ABC, abstractmethod @@ -47,7 +47,7 @@ class GenericTypeParser(ABC, Generic[T]): if not self._validate_default(parsed_type, parsed_properties): raise ValueError( - f"Default value {properties.get('default')} is not valid for type {parsed_type.__name__}" + f"Default value {properties.get('default')} is not valid for type {parsed_type}" ) return parsed_type, parsed_properties diff --git a/jambo/parser/allof_type_parser.py b/jambo/parser/allof_type_parser.py index 709365e..f6fa7c9 100644 --- a/jambo/parser/allof_type_parser.py +++ b/jambo/parser/allof_type_parser.py @@ -1,7 +1,7 @@ from jambo.parser._type_parser import GenericTypeParser from jambo.types.type_parser_options import TypeParserOptions -from typing_extensions import Any, Unpack +from typing import Any, Unpack class AllOfTypeParser(GenericTypeParser): diff --git a/jambo/parser/anyof_type_parser.py b/jambo/parser/anyof_type_parser.py index 819867b..6dae037 100644 --- a/jambo/parser/anyof_type_parser.py +++ b/jambo/parser/anyof_type_parser.py @@ -2,11 +2,14 @@ from jambo.parser._type_parser import GenericTypeParser from jambo.types.type_parser_options import TypeParserOptions from pydantic import Field -from typing_extensions import Annotated, Union, Unpack +from typing import Annotated, Unpack +from types import UnionType +from functools import reduce +from operator import or_ class AnyOfTypeParser(GenericTypeParser): - mapped_type = Union + mapped_type = UnionType json_schema_type = "anyOf" @@ -41,4 +44,6 @@ class AnyOfTypeParser(GenericTypeParser): for t, v in sub_types ] - return Union[(*field_types,)], mapped_properties + union_type = reduce(or_, field_types) + + return union_type, mapped_properties diff --git a/jambo/parser/array_type_parser.py b/jambo/parser/array_type_parser.py index 52e3bcf..0113f24 100644 --- a/jambo/parser/array_type_parser.py +++ b/jambo/parser/array_type_parser.py @@ -1,7 +1,7 @@ from jambo.parser._type_parser import GenericTypeParser from jambo.types.type_parser_options import TypeParserOptions -from typing_extensions import Iterable, TypeVar, Unpack +from typing import Iterable, TypeVar, Unpack import copy diff --git a/jambo/parser/boolean_type_parser.py b/jambo/parser/boolean_type_parser.py index ecb703a..7c66d1a 100644 --- a/jambo/parser/boolean_type_parser.py +++ b/jambo/parser/boolean_type_parser.py @@ -1,7 +1,7 @@ from jambo.parser._type_parser import GenericTypeParser from jambo.types.type_parser_options import TypeParserOptions -from typing_extensions import Unpack +from typing import Unpack class BooleanTypeParser(GenericTypeParser): diff --git a/jambo/parser/const_type_parser.py b/jambo/parser/const_type_parser.py index 4865df0..e0840f2 100644 --- a/jambo/parser/const_type_parser.py +++ b/jambo/parser/const_type_parser.py @@ -3,7 +3,7 @@ from jambo.types.json_schema_type import JSONSchemaNativeTypes from jambo.types.type_parser_options import TypeParserOptions from pydantic import AfterValidator -from typing_extensions import Annotated, Any, Literal, Unpack +from typing import Annotated, Any, Literal, Unpack class ConstTypeParser(GenericTypeParser): diff --git a/jambo/parser/enum_type_parser.py b/jambo/parser/enum_type_parser.py index 5ea9e67..cec110b 100644 --- a/jambo/parser/enum_type_parser.py +++ b/jambo/parser/enum_type_parser.py @@ -2,7 +2,7 @@ from jambo.parser._type_parser import GenericTypeParser from jambo.types.json_schema_type import JSONSchemaNativeTypes from jambo.types.type_parser_options import TypeParserOptions -from typing_extensions import Unpack +from typing import Unpack from enum import Enum diff --git a/jambo/parser/float_type_parser.py b/jambo/parser/float_type_parser.py index f4655c3..a7f0d2a 100644 --- a/jambo/parser/float_type_parser.py +++ b/jambo/parser/float_type_parser.py @@ -1,7 +1,7 @@ from jambo.parser._type_parser import GenericTypeParser from jambo.types.type_parser_options import TypeParserOptions -from typing_extensions import Unpack +from typing import Unpack class FloatTypeParser(GenericTypeParser): diff --git a/jambo/parser/int_type_parser.py b/jambo/parser/int_type_parser.py index 161465b..96932ec 100644 --- a/jambo/parser/int_type_parser.py +++ b/jambo/parser/int_type_parser.py @@ -1,7 +1,7 @@ from jambo.parser._type_parser import GenericTypeParser from jambo.types.type_parser_options import TypeParserOptions -from typing_extensions import Unpack +from typing import Unpack class IntTypeParser(GenericTypeParser): diff --git a/jambo/parser/null_type_parser.py b/jambo/parser/null_type_parser.py index 35036ef..b660f25 100644 --- a/jambo/parser/null_type_parser.py +++ b/jambo/parser/null_type_parser.py @@ -1,7 +1,7 @@ from jambo.parser._type_parser import GenericTypeParser from jambo.types.type_parser_options import TypeParserOptions -from typing_extensions import Unpack +from typing import Unpack class NullTypeParser(GenericTypeParser): diff --git a/jambo/parser/object_type_parser.py b/jambo/parser/object_type_parser.py index 8deb5ac..d17a8f0 100644 --- a/jambo/parser/object_type_parser.py +++ b/jambo/parser/object_type_parser.py @@ -2,7 +2,7 @@ from jambo.parser._type_parser import GenericTypeParser from jambo.types.type_parser_options import TypeParserOptions from pydantic import BaseModel, ConfigDict, Field, create_model -from typing_extensions import Any, Unpack +from typing import Any, Unpack class ObjectTypeParser(GenericTypeParser): diff --git a/jambo/parser/oneof_type_parser.py b/jambo/parser/oneof_type_parser.py index 79146b9..fb396e0 100644 --- a/jambo/parser/oneof_type_parser.py +++ b/jambo/parser/oneof_type_parser.py @@ -1,12 +1,15 @@ +from types import UnionType + from jambo.parser._type_parser import GenericTypeParser from jambo.types.type_parser_options import TypeParserOptions from pydantic import Field, BeforeValidator, TypeAdapter, ValidationError -from typing_extensions import Annotated, Union, Unpack, Any - +from typing import Annotated, Unpack, Any +from functools import reduce +from operator import or_ class OneOfTypeParser(GenericTypeParser): - mapped_type = Union + mapped_type = UnionType json_schema_type = "oneOf" @@ -36,7 +39,7 @@ class OneOfTypeParser(GenericTypeParser): for t, v in sub_types ] - union_type = Union[(*field_types,)] + union_type = reduce(or_, field_types) discriminator = properties.get("discriminator") if discriminator and isinstance(discriminator, dict): diff --git a/jambo/parser/ref_type_parser.py b/jambo/parser/ref_type_parser.py index 57abeac..d3cf49e 100644 --- a/jambo/parser/ref_type_parser.py +++ b/jambo/parser/ref_type_parser.py @@ -1,10 +1,12 @@ +from __future__ import annotations + from jambo.parser import GenericTypeParser from jambo.types.type_parser_options import TypeParserOptions -from typing_extensions import Any, ForwardRef, Literal, TypeVar, Union, Unpack +from typing import Any, ForwardRef, Literal, TypeVar, Unpack -RefType = TypeVar("RefType", bound=Union[type, ForwardRef]) +RefType = TypeVar("RefType", bound=type | ForwardRef) RefStrategy = Literal["forward_ref", "def_ref"] diff --git a/jambo/parser/string_type_parser.py b/jambo/parser/string_type_parser.py index 04f1e98..3d7b2b9 100644 --- a/jambo/parser/string_type_parser.py +++ b/jambo/parser/string_type_parser.py @@ -2,7 +2,7 @@ from jambo.parser._type_parser import GenericTypeParser from jambo.types.type_parser_options import TypeParserOptions from pydantic import EmailStr, HttpUrl, IPvAnyAddress, FilePath -from typing_extensions import Unpack +from typing import Unpack from datetime import date, datetime, time diff --git a/jambo/types/json_schema_type.py b/jambo/types/json_schema_type.py index 6f61837..a30612c 100644 --- a/jambo/types/json_schema_type.py +++ b/jambo/types/json_schema_type.py @@ -1,4 +1,6 @@ -from typing_extensions import Dict, List, Literal, TypedDict, Union +from __future__ import annotations + +from typing import Literal, TypedDict from types import NoneType @@ -19,7 +21,7 @@ JSONSchemaNativeTypes: tuple[type, ...] = ( ) -JSONType = Union[str, int, float, bool, None, Dict[str, "JSONType"], List["JSONType"]] +JSONType = str | int | float | bool | None | dict[str, "JSONType"] | list["JSONType"] class JSONSchema(TypedDict, total=False): @@ -27,23 +29,23 @@ class JSONSchema(TypedDict, total=False): title: str description: str default: JSONType - examples: List[JSONType] + examples: list[JSONType] # Type definitions - type: Union[JSONSchemaType, List[JSONSchemaType]] + type: JSONSchemaType | list[JSONSchemaType] # Object-specific keywords - properties: Dict[str, "JSONSchema"] - required: List[str] - additionalProperties: Union[bool, "JSONSchema"] + properties: dict[str, "JSONSchema"] + required: list[str] + additionalProperties: bool | "JSONSchema" minProperties: int maxProperties: int - patternProperties: Dict[str, "JSONSchema"] - dependencies: Dict[str, Union[List[str], "JSONSchema"]] + patternProperties: dict[str, "JSONSchema"] + dependencies: dict[str, list[str] | "JSONSchema"] # Array-specific keywords - items: Union["JSONSchema", List["JSONSchema"]] - additionalItems: Union[bool, "JSONSchema"] + items: "JSONSchema" | list["JSONSchema"] + additionalItems: bool | "JSONSchema" minItems: int maxItems: int uniqueItems: bool @@ -62,7 +64,7 @@ class JSONSchema(TypedDict, total=False): multipleOf: float # Enum and const - enum: List[JSONType] + enum: list[JSONType] const: JSONType # Conditionals @@ -71,23 +73,23 @@ class JSONSchema(TypedDict, total=False): else_: "JSONSchema" # 'else' is also a reserved word # Combination keywords - allOf: List["JSONSchema"] - anyOf: List["JSONSchema"] - oneOf: List["JSONSchema"] + 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__["properties"] = dict[str, JSONSchema] +JSONSchema.__annotations__["items"] = JSONSchema | list[JSONSchema] +JSONSchema.__annotations__["additionalItems"] = bool | JSONSchema +JSONSchema.__annotations__["additionalProperties"] = bool | JSONSchema +JSONSchema.__annotations__["patternProperties"] = dict[str, JSONSchema] +JSONSchema.__annotations__["dependencies"] = dict[str, 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__["allOf"] = list[JSONSchema] +JSONSchema.__annotations__["anyOf"] = list[JSONSchema] +JSONSchema.__annotations__["oneOf"] = list[JSONSchema] JSONSchema.__annotations__["not_"] = JSONSchema diff --git a/jambo/types/type_parser_options.py b/jambo/types/type_parser_options.py index 4f7d8e0..b882598 100644 --- a/jambo/types/type_parser_options.py +++ b/jambo/types/type_parser_options.py @@ -1,6 +1,6 @@ from jambo.types.json_schema_type import JSONSchema -from typing_extensions import TypedDict +from typing import TypedDict class TypeParserOptions(TypedDict): diff --git a/tests/parser/test_anyof_type_parser.py b/tests/parser/test_anyof_type_parser.py index de5cb9b..72ff6c3 100644 --- a/tests/parser/test_anyof_type_parser.py +++ b/tests/parser/test_anyof_type_parser.py @@ -1,6 +1,6 @@ from jambo.parser.anyof_type_parser import AnyOfTypeParser -from typing_extensions import Annotated, Union, get_args, get_origin +from typing import Annotated, get_args, get_origin from unittest import TestCase @@ -42,7 +42,7 @@ class TestAnyOfTypeParser(TestCase): ) # check union type has string and int - self.assertEqual(get_origin(type_parsing), Union) + self.assertEqual(get_origin(type_parsing), type(str | int)) type_1, type_2 = get_args(type_parsing) @@ -67,7 +67,7 @@ class TestAnyOfTypeParser(TestCase): ) # check union type has string and int - self.assertEqual(get_origin(type_parsing), Union) + self.assertEqual(get_origin(type_parsing), type(str | int)) type_1, type_2 = get_args(type_parsing) diff --git a/tests/parser/test_array_type_parser.py b/tests/parser/test_array_type_parser.py index ee09987..135b31c 100644 --- a/tests/parser/test_array_type_parser.py +++ b/tests/parser/test_array_type_parser.py @@ -1,6 +1,6 @@ from jambo.parser import ArrayTypeParser -from typing_extensions import get_args +from typing import get_args from unittest import TestCase diff --git a/tests/parser/test_const_type_parser.py b/tests/parser/test_const_type_parser.py index 8f3661c..31783f6 100644 --- a/tests/parser/test_const_type_parser.py +++ b/tests/parser/test_const_type_parser.py @@ -1,6 +1,6 @@ from jambo.parser import ConstTypeParser -from typing_extensions import Annotated, Literal, get_args, get_origin +from typing import Annotated, Literal, get_args, get_origin from unittest import TestCase -- 2.49.1 From 545ec8dda714554ac71fc24964ae96fe8ea7aec8 Mon Sep 17 00:00:00 2001 From: Thomas <34217413+thommann@users.noreply.github.com> Date: Tue, 8 Jul 2025 16:50:53 +0200 Subject: [PATCH 11/96] chore(jambo): Clean up __future__ typing (#10) * Replace forward-referenced strings with direct type annotations in `json_schema_type.py`. * refactor(ref_type_parser): eliminate unused future annotations import --- jambo/parser/ref_type_parser.py | 2 -- jambo/types/json_schema_type.py | 26 +++++++++++++------------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/jambo/parser/ref_type_parser.py b/jambo/parser/ref_type_parser.py index d3cf49e..69f43a9 100644 --- a/jambo/parser/ref_type_parser.py +++ b/jambo/parser/ref_type_parser.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from jambo.parser import GenericTypeParser from jambo.types.type_parser_options import TypeParserOptions diff --git a/jambo/types/json_schema_type.py b/jambo/types/json_schema_type.py index a30612c..be82093 100644 --- a/jambo/types/json_schema_type.py +++ b/jambo/types/json_schema_type.py @@ -35,17 +35,17 @@ class JSONSchema(TypedDict, total=False): type: JSONSchemaType | list[JSONSchemaType] # Object-specific keywords - properties: dict[str, "JSONSchema"] + properties: dict[str, JSONSchema] required: list[str] - additionalProperties: bool | "JSONSchema" + additionalProperties: bool | JSONSchema minProperties: int maxProperties: int - patternProperties: dict[str, "JSONSchema"] - dependencies: dict[str, list[str] | "JSONSchema"] + patternProperties: dict[str, JSONSchema] + dependencies: dict[str, list[str] | JSONSchema] # Array-specific keywords - items: "JSONSchema" | list["JSONSchema"] - additionalItems: bool | "JSONSchema" + items: JSONSchema | list[JSONSchema] + additionalItems: bool | JSONSchema minItems: int maxItems: int uniqueItems: bool @@ -68,15 +68,15 @@ class JSONSchema(TypedDict, total=False): const: JSONType # Conditionals - if_: "JSONSchema" # 'if' is a reserved word in Python - then: "JSONSchema" - else_: "JSONSchema" # 'else' is also a reserved word + 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 + allOf: list[JSONSchema] + anyOf: list[JSONSchema] + oneOf: list[JSONSchema] + not_: JSONSchema # 'not' is a reserved word # Fix forward references -- 2.49.1 From 976708934fcfbc5d6551d83bb71b326b2e73856d Mon Sep 17 00:00:00 2001 From: Fred Sonnenwald Date: Fri, 8 Aug 2025 12:38:33 +0100 Subject: [PATCH 12/96] add string duration -> timedelta --- jambo/parser/string_type_parser.py | 3 ++- tests/parser/test_string_type_parser.py | 14 +++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/jambo/parser/string_type_parser.py b/jambo/parser/string_type_parser.py index 1eb25b9..389f8f4 100644 --- a/jambo/parser/string_type_parser.py +++ b/jambo/parser/string_type_parser.py @@ -4,7 +4,7 @@ from jambo.types.type_parser_options import TypeParserOptions from pydantic import EmailStr, HttpUrl, IPvAnyAddress from typing_extensions import Unpack -from datetime import date, datetime, time +from datetime import date, datetime, time, timedelta class StringTypeParser(GenericTypeParser): @@ -28,6 +28,7 @@ class StringTypeParser(GenericTypeParser): "date": date, "time": time, "date-time": datetime, + "duration": timedelta, } format_pattern_mapping = { diff --git a/tests/parser/test_string_type_parser.py b/tests/parser/test_string_type_parser.py index f4dc3d6..51a37e4 100644 --- a/tests/parser/test_string_type_parser.py +++ b/tests/parser/test_string_type_parser.py @@ -2,7 +2,7 @@ from jambo.parser import StringTypeParser from pydantic import EmailStr, HttpUrl, IPvAnyAddress -from datetime import date, datetime, time +from datetime import date, datetime, time, timedelta from unittest import TestCase @@ -197,3 +197,15 @@ class TestStringTypeParser(TestCase): type_parsing, type_validator = parser.from_properties("placeholder", properties) self.assertEqual(type_parsing, datetime) + + def test_string_parser_with_timedelta_format(self): + parser = StringTypeParser() + + properties = { + "type": "string", + "format": "duration", + } + + type_parsing, type_validator = parser.from_properties("placeholder", properties) + + self.assertEqual(type_parsing, timedelta) -- 2.49.1 From 617f1aab2b43e714d62e8a21565e46f6b458b6f9 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Mon, 18 Aug 2025 22:27:49 -0300 Subject: [PATCH 13/96] Adds Failing Test Case to Test --- tests/test_schema_converter.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_schema_converter.py b/tests/test_schema_converter.py index fbba3c9..96adf34 100644 --- a/tests/test_schema_converter.py +++ b/tests/test_schema_converter.py @@ -204,6 +204,9 @@ class TestSchemaConverter(TestCase): model(friends=["John", "Jane", "John"]).friends, {"John", "Jane"} ) + with self.assertRaises(ValueError): + model() + with self.assertRaises(ValueError): model(friends=[]) -- 2.49.1 From 7b9464f458633bf81299119f99a914ae6a562f1e Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Mon, 18 Aug 2025 22:53:28 -0300 Subject: [PATCH 14/96] Fixes Array So No DefaultFactory is Created When no Default is Set and Field is Required --- jambo/parser/array_type_parser.py | 2 +- jambo/parser/object_type_parser.py | 2 +- tests/test_schema_converter.py | 48 +++++++++++++++++++++++++++--- 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/jambo/parser/array_type_parser.py b/jambo/parser/array_type_parser.py index c92de3d..7d59ea5 100644 --- a/jambo/parser/array_type_parser.py +++ b/jambo/parser/array_type_parser.py @@ -35,7 +35,7 @@ class ArrayTypeParser(GenericTypeParser): mapped_properties = self.mappings_properties_builder(properties, **kwargs) - if "default" not in mapped_properties: + if "default" in properties or not kwargs.get("required", False): mapped_properties["default_factory"] = self._build_default_factory( properties.get("default"), wrapper_type ) diff --git a/jambo/parser/object_type_parser.py b/jambo/parser/object_type_parser.py index 8deb5ac..0f0ab7e 100644 --- a/jambo/parser/object_type_parser.py +++ b/jambo/parser/object_type_parser.py @@ -59,7 +59,7 @@ class ObjectTypeParser(GenericTypeParser): fields = {} for name, prop in properties.items(): - sub_property = kwargs.copy() + sub_property: TypeParserOptions = kwargs.copy() sub_property["required"] = name in required_keys parsed_type, parsed_properties = GenericTypeParser.type_from_properties( diff --git a/tests/test_schema_converter.py b/tests/test_schema_converter.py index 96adf34..472ea8d 100644 --- a/tests/test_schema_converter.py +++ b/tests/test_schema_converter.py @@ -181,7 +181,7 @@ class TestSchemaConverter(TestCase): self.assertEqual(model(is_active="true").is_active, True) - def test_validation_list(self): + def test_validation_list_with_valid_items(self): schema = { "title": "Person", "description": "A person", @@ -204,15 +204,52 @@ class TestSchemaConverter(TestCase): model(friends=["John", "Jane", "John"]).friends, {"John", "Jane"} ) - with self.assertRaises(ValueError): - model() - with self.assertRaises(ValueError): model(friends=[]) with self.assertRaises(ValueError): model(friends=["John", "Jane", "Invalid"]) + def test_validation_list_with_missing_items(self): + model = SchemaConverter.build( + { + "title": "Person", + "description": "A person", + "type": "object", + "properties": { + "friends": { + "type": "array", + "items": {"type": "string"}, + "minItems": 1, + "maxItems": 2, + "default": ["John", "Jane"], + }, + }, + } + ) + + self.assertEqual(model().friends, ["John", "Jane"]) + + model = SchemaConverter.build( + { + "title": "Person", + "description": "A person", + "type": "object", + "properties": { + "friends": { + "type": "array", + "items": {"type": "string"}, + "minItems": 1, + "maxItems": 2, + }, + }, + "required": ["friends"], + } + ) + + with self.assertRaises(ValueError): + model() + def test_validation_object(self): schema = { "title": "Person", @@ -238,6 +275,9 @@ class TestSchemaConverter(TestCase): self.assertEqual(obj.address.street, "123 Main St") self.assertEqual(obj.address.city, "Springfield") + with self.assertRaises(ValueError): + model() + def test_default_for_string(self): schema = { "title": "Person", -- 2.49.1 From 00d88388f84bd8372929dd3bb402019f8999b365 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Mon, 18 Aug 2025 23:33:16 -0300 Subject: [PATCH 15/96] Fixes Behavior of Pydantic None Type and Adds More Tests --- jambo/parser/null_type_parser.py | 13 +++---------- tests/parser/test_null_type_parser.py | 24 +++++------------------- tests/test_schema_converter.py | 20 ++++++++++++++++++++ 3 files changed, 28 insertions(+), 29 deletions(-) diff --git a/jambo/parser/null_type_parser.py b/jambo/parser/null_type_parser.py index 35036ef..4c384f4 100644 --- a/jambo/parser/null_type_parser.py +++ b/jambo/parser/null_type_parser.py @@ -5,21 +5,14 @@ from typing_extensions import Unpack class NullTypeParser(GenericTypeParser): - mapped_type = None + mapped_type = type(None) json_schema_type = "type:null" - type_mappings = { - "default": "default", - } - def from_properties_impl( self, name, properties, **kwargs: Unpack[TypeParserOptions] ): mapped_properties = self.mappings_properties_builder(properties, **kwargs) + mapped_properties["default"] = None - default_value = properties.get("default") - if default_value is not None: - raise ValueError(f"Default value for {name} must be None.") - - return None, mapped_properties + return self.mapped_type, mapped_properties diff --git a/tests/parser/test_null_type_parser.py b/tests/parser/test_null_type_parser.py index 414fbae..e2732c0 100644 --- a/tests/parser/test_null_type_parser.py +++ b/tests/parser/test_null_type_parser.py @@ -13,31 +13,17 @@ class TestNullTypeParser(TestCase): "placeholder", properties ) - self.assertEqual(type_parsing, None) + self.assertEqual(type_parsing, type(None)) self.assertEqual(type_validator, {"default": None}) - def test_null_parser_with_default(self): + def test_null_parser_with_invalid_default(self): parser = NullTypeParser() - properties = { - "type": "null", - "default": None, - } + properties = {"type": "null", "default": "invalid"} type_parsing, type_validator = parser.from_properties_impl( "placeholder", properties ) - self.assertEqual(type_parsing, None) - self.assertEqual(type_validator["default"], None) - - def test_null_parser_with_invalid_default(self): - parser = NullTypeParser() - - properties = { - "type": "null", - "default": "invalid", - } - - with self.assertRaises(ValueError): - parser.from_properties_impl("placeholder", properties) + self.assertEqual(type_parsing, type(None)) + self.assertEqual(type_validator, {"default": None}) diff --git a/tests/test_schema_converter.py b/tests/test_schema_converter.py index fbba3c9..6a1421d 100644 --- a/tests/test_schema_converter.py +++ b/tests/test_schema_converter.py @@ -657,3 +657,23 @@ class TestSchemaConverter(TestCase): with self.assertRaises(ValueError): Model(name="Canada") + + def test_null_type_parser(self): + schema = { + "title": "Test", + "type": "object", + "properties": { + "a_thing": {"type": "null"}, + }, + } + + Model = SchemaConverter.build(schema) + + obj = Model() + self.assertIsNone(obj.a_thing) + + obj = Model(a_thing=None) + self.assertIsNone(obj.a_thing) + + with self.assertRaises(ValueError): + Model(a_thing="not none") -- 2.49.1 From 9797fb35d9855ccfb023b5286c682cf3b40ec5b5 Mon Sep 17 00:00:00 2001 From: Thomas <34217413+thommann@users.noreply.github.com> Date: Mon, 7 Jul 2025 15:49:58 +0200 Subject: [PATCH 16/96] feat(jambo): Add oneOf parser (#5) * Add support for `oneOf` type parsing with validation and example cases * Improve `oneOf` type parsing: refine validators, add discriminator support, and expand test coverage * Add hashable and non-hashable value support to `ConstTypeParser` with expanded test cases * Refine `field_props` check in `_type_parser` for cleaner default handling * Update `StringTypeParser` to refine `format` handling and enrich `json_schema_extra` * Remove outdated `oneOf` examples from docs, expand test cases and provide refined examples with discriminator support --- docs/source/usage.oneof.rst | 108 ++++++ docs/source/usage.rst | 1 + jambo/parser/__init__.py | 4 +- jambo/parser/_type_parser.py | 23 ++ jambo/parser/const_type_parser.py | 24 +- jambo/parser/oneof_type_parser.py | 69 ++++ jambo/parser/string_type_parser.py | 5 +- tests/parser/test_const_type_parser.py | 61 ++- tests/parser/test_oneof_type_parser.py | 493 +++++++++++++++++++++++++ 9 files changed, 774 insertions(+), 14 deletions(-) create mode 100644 docs/source/usage.oneof.rst create mode 100644 jambo/parser/oneof_type_parser.py create mode 100644 tests/parser/test_oneof_type_parser.py diff --git a/docs/source/usage.oneof.rst b/docs/source/usage.oneof.rst new file mode 100644 index 0000000..a836df4 --- /dev/null +++ b/docs/source/usage.oneof.rst @@ -0,0 +1,108 @@ +OneOf Type +================= + +The OneOf type is used to specify that an object must conform to exactly one of the specified schemas. Unlike AnyOf which allows matching multiple schemas, OneOf enforces that the data matches one and only one of the provided schemas. + + +Examples +----------------- + +1. **Overlapping String Example** - A field that accepts strings with overlapping constraints: + +.. code-block:: python + + from jambo import SchemaConverter + + schema = { + "title": "SimpleExample", + "type": "object", + "properties": { + "value": { + "oneOf": [ + {"type": "string", "maxLength": 6}, + {"type": "string", "minLength": 4} + ] + } + }, + "required": ["value"] + } + + Model = SchemaConverter.build(schema) + + # Valid: Short string (matches first schema only) + obj1 = Model(value="hi") + print(obj1.value) # Output: hi + + # Valid: Long string (matches second schema only) + obj2 = Model(value="very long string") + print(obj2.value) # Output: very long string + + # Invalid: Medium string (matches BOTH schemas - violates oneOf) + try: + obj3 = Model(value="hello") # 5 chars: matches maxLength=6 AND minLength=4 + except ValueError as e: + print("Validation fails as expected:", e) + + +2. **Discriminator Example** - Different shapes with a type field: + +.. code-block:: python + + from jambo import SchemaConverter + + schema = { + "title": "Shape", + "type": "object", + "properties": { + "shape": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": {"const": "circle"}, + "radius": {"type": "number", "minimum": 0} + }, + "required": ["type", "radius"] + }, + { + "type": "object", + "properties": { + "type": {"const": "rectangle"}, + "width": {"type": "number", "minimum": 0}, + "height": {"type": "number", "minimum": 0} + }, + "required": ["type", "width", "height"] + } + ], + "discriminator": { + "propertyName": "type" + } + } + }, + "required": ["shape"] + } + + Model = SchemaConverter.build(schema) + + # Valid: Circle + circle = Model(shape={"type": "circle", "radius": 5.0}) + print(circle.shape.type) # Output: circle + + # Valid: Rectangle + rectangle = Model(shape={"type": "rectangle", "width": 10, "height": 20}) + print(rectangle.shape.type) # Output: rectangle + + # Invalid: Wrong properties for the type + try: + invalid = Model(shape={"type": "circle", "width": 10}) + except ValueError as e: + print("Validation fails as expected:", e) + + +.. note:: + + OneOf ensures exactly one schema matches. The discriminator helps Pydantic efficiently determine which schema to use based on a specific property value. + +.. warning:: + + If your data could match multiple schemas in a oneOf, validation will fail. Ensure schemas are mutually exclusive. diff --git a/docs/source/usage.rst b/docs/source/usage.rst index 8896842..3bdb2d9 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -45,5 +45,6 @@ For more complex schemas and types see our documentation on usage.reference usage.allof usage.anyof + usage.oneof usage.enum usage.const \ No newline at end of file diff --git a/jambo/parser/__init__.py b/jambo/parser/__init__.py index f3b8b25..44b4424 100644 --- a/jambo/parser/__init__.py +++ b/jambo/parser/__init__.py @@ -9,6 +9,7 @@ from .float_type_parser import FloatTypeParser from .int_type_parser import IntTypeParser from .null_type_parser import NullTypeParser from .object_type_parser import ObjectTypeParser +from .oneof_type_parser import OneOfTypeParser from .ref_type_parser import RefTypeParser from .string_type_parser import StringTypeParser @@ -25,6 +26,7 @@ __all__ = [ "IntTypeParser", "NullTypeParser", "ObjectTypeParser", + "OneOfTypeParser", "StringTypeParser", "RefTypeParser", -] +] \ No newline at end of file diff --git a/jambo/parser/_type_parser.py b/jambo/parser/_type_parser.py index 080965c..6c5cdc9 100644 --- a/jambo/parser/_type_parser.py +++ b/jambo/parser/_type_parser.py @@ -124,3 +124,26 @@ class GenericTypeParser(ABC, Generic[T]): return False return True + + @staticmethod + def _has_meaningful_constraints(field_props): + """ + Check if field properties contain meaningful constraints that require Field wrapping. + + Returns False if: + - field_props is None or empty + - field_props only contains {'default': None} + + Returns True if: + - field_props contains a non-None default value + - field_props contains other constraint properties (min_length, max_length, pattern, etc.) + """ + if not field_props: + return False + + # If only default is set and it's None, no meaningful constraints + if field_props == {"default": None}: + return False + + # If there are multiple properties or non-None default, that's meaningful + return True diff --git a/jambo/parser/const_type_parser.py b/jambo/parser/const_type_parser.py index b5c846f..1e4ce84 100644 --- a/jambo/parser/const_type_parser.py +++ b/jambo/parser/const_type_parser.py @@ -3,7 +3,7 @@ from jambo.types.json_schema_type import JSONSchemaNativeTypes from jambo.types.type_parser_options import TypeParserOptions from pydantic import AfterValidator -from typing_extensions import Annotated, Any, Unpack +from typing_extensions import Annotated, Any, Literal, Unpack class ConstTypeParser(GenericTypeParser): @@ -33,11 +33,19 @@ class ConstTypeParser(GenericTypeParser): return const_type, parsed_properties def _build_const_type(self, const_value): - def _validate_const_value(value: Any) -> Any: - if value != const_value: - raise ValueError( - f"Value must be equal to the constant value: {const_value}" - ) - return value + # Try to use Literal for hashable types (required for discriminated unions) + # Fall back to validator approach for non-hashable types + try: + # Test if the value is hashable (can be used in Literal) + hash(const_value) + return Literal[const_value] + except TypeError: + # Non-hashable type (like list, dict), use validator approach + def _validate_const_value(value: Any) -> Any: + if value != const_value: + raise ValueError( + f"Value must be equal to the constant value: {const_value}" + ) + return value - return Annotated[type(const_value), AfterValidator(_validate_const_value)] + return Annotated[type(const_value), AfterValidator(_validate_const_value)] \ No newline at end of file diff --git a/jambo/parser/oneof_type_parser.py b/jambo/parser/oneof_type_parser.py new file mode 100644 index 0000000..79146b9 --- /dev/null +++ b/jambo/parser/oneof_type_parser.py @@ -0,0 +1,69 @@ +from jambo.parser._type_parser import GenericTypeParser +from jambo.types.type_parser_options import TypeParserOptions + +from pydantic import Field, BeforeValidator, TypeAdapter, ValidationError +from typing_extensions import Annotated, Union, Unpack, Any + + +class OneOfTypeParser(GenericTypeParser): + mapped_type = Union + + json_schema_type = "oneOf" + + def from_properties_impl( + self, name, properties, **kwargs: Unpack[TypeParserOptions] + ): + if "oneOf" not in properties: + raise ValueError(f"Invalid JSON Schema: {properties}") + + if not isinstance(properties["oneOf"], list): + raise ValueError(f"Invalid JSON Schema: {properties['oneOf']}") + + mapped_properties = self.mappings_properties_builder(properties, **kwargs) + + sub_properties = properties["oneOf"] + + sub_types = [ + GenericTypeParser.type_from_properties(name, subProperty, **kwargs) + for subProperty in sub_properties + ] + + if not kwargs.get("required", False): + mapped_properties["default"] = mapped_properties.get("default") + + field_types = [ + Annotated[t, Field(**v)] if self._has_meaningful_constraints(v) else t + for t, v in sub_types + ] + + union_type = Union[(*field_types,)] + + discriminator = properties.get("discriminator") + if discriminator and isinstance(discriminator, dict): + property_name = discriminator.get("propertyName") + if property_name: + validated_type = Annotated[union_type, Field(discriminator=property_name)] + return validated_type, mapped_properties + + def validate_one_of(value: Any) -> Any: + matched_count = 0 + validation_errors = [] + + for field_type in field_types: + try: + adapter = TypeAdapter(field_type) + adapter.validate_python(value) + matched_count += 1 + except ValidationError as e: + validation_errors.append(str(e)) + continue + + if matched_count == 0: + raise ValueError(f"Value does not match any of the oneOf schemas") + elif matched_count > 1: + raise ValueError(f"Value matches multiple oneOf schemas, exactly one expected") + + return value + + validated_type = Annotated[union_type, BeforeValidator(validate_one_of)] + return validated_type, mapped_properties diff --git a/jambo/parser/string_type_parser.py b/jambo/parser/string_type_parser.py index 389f8f4..9f43034 100644 --- a/jambo/parser/string_type_parser.py +++ b/jambo/parser/string_type_parser.py @@ -16,7 +16,6 @@ class StringTypeParser(GenericTypeParser): "maxLength": "max_length", "minLength": "min_length", "pattern": "pattern", - "format": "format", } format_type_mapping = { @@ -53,4 +52,8 @@ class StringTypeParser(GenericTypeParser): if format_type in self.format_pattern_mapping: mapped_properties["pattern"] = self.format_pattern_mapping[format_type] + if "json_schema_extra" not in mapped_properties: + mapped_properties["json_schema_extra"] = {} + mapped_properties["json_schema_extra"]["format"] = format_type + return mapped_type, mapped_properties diff --git a/tests/parser/test_const_type_parser.py b/tests/parser/test_const_type_parser.py index ca92bb0..5a8c9c1 100644 --- a/tests/parser/test_const_type_parser.py +++ b/tests/parser/test_const_type_parser.py @@ -1,12 +1,13 @@ from jambo.parser import ConstTypeParser -from typing_extensions import Annotated, get_args, get_origin +from typing_extensions import Annotated, Literal, get_args, get_origin from unittest import TestCase class TestConstTypeParser(TestCase): - def test_const_type_parser(self): + def test_const_type_parser_hashable_value(self): + """Test const parser with hashable values (uses Literal)""" parser = ConstTypeParser() expected_const_value = "United States of America" @@ -16,8 +17,60 @@ class TestConstTypeParser(TestCase): "country", properties ) + # Check that we get a Literal type for hashable values + self.assertEqual(get_origin(parsed_type), Literal) + self.assertEqual(get_args(parsed_type), (expected_const_value,)) + + self.assertEqual(parsed_properties["default"], expected_const_value) + + def test_const_type_parser_non_hashable_value(self): + """Test const parser with non-hashable values (uses Annotated with validator)""" + parser = ConstTypeParser() + + expected_const_value = [1, 2, 3] # Lists are not hashable + properties = {"const": expected_const_value} + + parsed_type, parsed_properties = parser.from_properties_impl( + "list_const", properties + ) + + # Check that we get an Annotated type for non-hashable values self.assertEqual(get_origin(parsed_type), Annotated) - self.assertIn(str, get_args(parsed_type)) + self.assertIn(list, get_args(parsed_type)) + + self.assertEqual(parsed_properties["default"], expected_const_value) + + def test_const_type_parser_integer_value(self): + """Test const parser with integer values (uses Literal)""" + parser = ConstTypeParser() + + expected_const_value = 42 + properties = {"const": expected_const_value} + + parsed_type, parsed_properties = parser.from_properties_impl( + "int_const", properties + ) + + # Check that we get a Literal type for hashable values + self.assertEqual(get_origin(parsed_type), Literal) + self.assertEqual(get_args(parsed_type), (expected_const_value,)) + + self.assertEqual(parsed_properties["default"], expected_const_value) + + def test_const_type_parser_boolean_value(self): + """Test const parser with boolean values (uses Literal)""" + parser = ConstTypeParser() + + expected_const_value = True + properties = {"const": expected_const_value} + + parsed_type, parsed_properties = parser.from_properties_impl( + "bool_const", properties + ) + + # Check that we get a Literal type for hashable values + self.assertEqual(get_origin(parsed_type), Literal) + self.assertEqual(get_args(parsed_type), (expected_const_value,)) self.assertEqual(parsed_properties["default"], expected_const_value) @@ -46,4 +99,4 @@ class TestConstTypeParser(TestCase): self.assertIn( "Const type invalid_country must have 'const' value of allowed types", str(context.exception), - ) + ) \ No newline at end of file diff --git a/tests/parser/test_oneof_type_parser.py b/tests/parser/test_oneof_type_parser.py new file mode 100644 index 0000000..8c75f04 --- /dev/null +++ b/tests/parser/test_oneof_type_parser.py @@ -0,0 +1,493 @@ +from jambo import SchemaConverter + +from unittest import TestCase + + +class TestOneOfTypeParser(TestCase): + def test_oneof_basic_integer_and_string(self): + schema = { + "title": "Person", + "description": "A person with an ID that can be either an integer or a formatted string", + "type": "object", + "properties": { + "id": { + "oneOf": [ + {"type": "integer", "minimum": 1}, + {"type": "string", "pattern": "^[A-Z]{2}[0-9]{4}$"}, + ] + }, + }, + "required": ["id"], + } + + Model = SchemaConverter.build(schema) + + obj1 = Model(id=123) + self.assertEqual(obj1.id, 123) + + obj2 = Model(id="AB1234") + self.assertEqual(obj2.id, "AB1234") + + def test_oneof_validation_failures(self): + schema = { + "title": "Person", + "type": "object", + "properties": { + "id": { + "oneOf": [ + {"type": "integer", "minimum": 1}, + {"type": "string", "pattern": "^[A-Z]{2}[0-9]{4}$"}, + ] + }, + }, + "required": ["id"], + } + + Model = SchemaConverter.build(schema) + + with self.assertRaises(ValueError): + Model(id=-5) + + with self.assertRaises(ValueError): + Model(id="invalid") + + with self.assertRaises(ValueError): + Model(id=123.45) + + def test_oneof_with_conflicting_schemas(self): + schema = { + "title": "Value", + "type": "object", + "properties": { + "data": { + "oneOf": [ + {"type": "number", "multipleOf": 2}, + {"type": "number", "multipleOf": 3}, + ] + }, + }, + "required": ["data"], + } + + Model = SchemaConverter.build(schema) + + obj1 = Model(data=4) + self.assertEqual(obj1.data, 4) + + obj2 = Model(data=9) + self.assertEqual(obj2.data, 9) + + with self.assertRaises(ValueError) as cm: + Model(data=6) + self.assertIn("matches multiple oneOf schemas", str(cm.exception)) + + with self.assertRaises(ValueError): + Model(data=5) + + def test_oneof_with_objects(self): + schema = { + "title": "Contact", + "type": "object", + "properties": { + "contact_info": { + "oneOf": [ + { + "type": "object", + "properties": { + "email": {"type": "string", "format": "email"} + }, + "required": ["email"], + "additionalProperties": False + }, + { + "type": "object", + "properties": { + "phone": {"type": "string", "pattern": "^[0-9-]+$"} + }, + "required": ["phone"], + "additionalProperties": False + } + ] + }, + }, + "required": ["contact_info"], + } + + Model = SchemaConverter.build(schema) + + obj1 = Model(contact_info={"email": "user@example.com"}) + self.assertEqual(obj1.contact_info.email, "user@example.com") + + obj2 = Model(contact_info={"phone": "123-456-7890"}) + self.assertEqual(obj2.contact_info.phone, "123-456-7890") + + with self.assertRaises(ValueError): + Model(contact_info={"email": "user@example.com", "phone": "123-456-7890"}) + + def test_oneof_with_discriminator_basic(self): + schema = { + "title": "Pet", + "type": "object", + "properties": { + "pet": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": {"const": "cat"}, + "meows": {"type": "boolean"} + }, + "required": ["type", "meows"] + }, + { + "type": "object", + "properties": { + "type": {"const": "dog"}, + "barks": {"type": "boolean"} + }, + "required": ["type", "barks"] + } + ], + "discriminator": { + "propertyName": "type" + } + } + }, + "required": ["pet"] + } + + Model = SchemaConverter.build(schema) + + cat = Model(pet={"type": "cat", "meows": True}) + self.assertEqual(cat.pet.type, "cat") + self.assertEqual(cat.pet.meows, True) + + dog = Model(pet={"type": "dog", "barks": False}) + self.assertEqual(dog.pet.type, "dog") + self.assertEqual(dog.pet.barks, False) + + with self.assertRaises(ValueError): + Model(pet={"type": "cat", "barks": True}) + + with self.assertRaises(ValueError): + Model(pet={"type": "bird", "flies": True}) + + def test_oneof_with_discriminator_mapping(self): + schema = { + "title": "Vehicle", + "type": "object", + "properties": { + "vehicle": { + "oneOf": [ + { + "type": "object", + "properties": { + "vehicle_type": {"const": "car"}, + "doors": {"type": "integer", "minimum": 2, "maximum": 4} + }, + "required": ["vehicle_type", "doors"] + }, + { + "type": "object", + "properties": { + "vehicle_type": {"const": "motorcycle"}, + "engine_size": {"type": "number", "minimum": 125} + }, + "required": ["vehicle_type", "engine_size"] + } + ], + "discriminator": { + "propertyName": "vehicle_type", + "mapping": { + "car": "#/properties/vehicle/oneOf/0", + "motorcycle": "#/properties/vehicle/oneOf/1" + } + } + } + }, + "required": ["vehicle"] + } + + Model = SchemaConverter.build(schema) + + car = Model(vehicle={"vehicle_type": "car", "doors": 4}) + self.assertEqual(car.vehicle.vehicle_type, "car") + self.assertEqual(car.vehicle.doors, 4) + + motorcycle = Model(vehicle={"vehicle_type": "motorcycle", "engine_size": 600.0}) + self.assertEqual(motorcycle.vehicle.vehicle_type, "motorcycle") + self.assertEqual(motorcycle.vehicle.engine_size, 600.0) + + def test_oneof_with_discriminator_invalid_values(self): + schema = { + "title": "Shape", + "type": "object", + "properties": { + "shape": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": {"const": "circle"}, + "radius": {"type": "number", "minimum": 0} + }, + "required": ["type", "radius"] + }, + { + "type": "object", + "properties": { + "type": {"const": "square"}, + "side": {"type": "number", "minimum": 0} + }, + "required": ["type", "side"] + } + ], + "discriminator": { + "propertyName": "type" + } + } + }, + "required": ["shape"] + } + + Model = SchemaConverter.build(schema) + + with self.assertRaises(ValueError): + Model(shape={"type": "triangle", "base": 5, "height": 3}) + + with self.assertRaises(ValueError): + Model(shape={"type": "circle", "side": 5}) + + with self.assertRaises(ValueError): + Model(shape={"radius": 5}) + + def test_oneof_missing_properties(self): + schema = { + "title": "Test", + "type": "object", + "properties": { + "value": { + "notOneOf": [ + {"type": "string"}, + {"type": "integer"}, + ] + }, + }, + } + + with self.assertRaises(ValueError): + SchemaConverter.build(schema) + + def test_oneof_invalid_properties(self): + schema = { + "title": "Test", + "type": "object", + "properties": { + "value": { + "oneOf": None + }, + }, + } + + with self.assertRaises(ValueError): + SchemaConverter.build(schema) + + def test_oneof_with_default_value(self): + schema = { + "title": "Test", + "type": "object", + "properties": { + "value": { + "oneOf": [ + {"type": "string"}, + {"type": "integer"}, + ], + "default": "test" + }, + }, + } + + Model = SchemaConverter.build(schema) + obj = Model() + self.assertEqual(obj.value, "test") + + def test_oneof_with_invalid_default_value(self): + schema = { + "title": "Test", + "type": "object", + "properties": { + "value": { + "oneOf": [ + {"type": "string", "minLength": 5}, + {"type": "integer", "minimum": 10}, + ], + "default": "hi" + }, + }, + } + + with self.assertRaises(ValueError): + SchemaConverter.build(schema) + + def test_oneof_discriminator_without_property_name(self): + schema = { + "title": "Test", + "type": "object", + "properties": { + "value": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": {"const": "a"}, + "value": {"type": "string"} + } + }, + { + "type": "object", + "properties": { + "type": {"const": "b"}, + "value": {"type": "integer"} + } + } + ], + "discriminator": {} # discriminator without propertyName + } + } + } + + Model = SchemaConverter.build(schema) + + # Should succeed because input matches exactly one schema (the first one) + # The first schema matches: type="a" matches const("a"), value="test" is a string + # The second schema doesn't match: type="a" does not match const("b") + obj = Model(value={"type": "a", "value": "test", "extra": "invalid"}) + self.assertEqual(obj.value.type, "a") + self.assertEqual(obj.value.value, "test") + + # Test with input that matches the second schema + obj2 = Model(value={"type": "b", "value": 42}) + self.assertEqual(obj2.value.type, "b") + self.assertEqual(obj2.value.value, 42) + + # Test with input that matches neither schema (should fail) + with self.assertRaises(ValueError) as cm: + Model(value={"type": "c", "value": "test"}) + self.assertIn("does not match any of the oneOf schemas", str(cm.exception)) + + def test_oneof_multiple_matches_without_discriminator(self): + """Test case where input genuinely matches multiple oneOf schemas""" + schema = { + "title": "Test", + "type": "object", + "properties": { + "value": { + "oneOf": [ + { + "type": "object", + "properties": { + "data": {"type": "string"} + } + }, + { + "type": "object", + "properties": { + "data": {"type": "string"}, + "optional": {"type": "string"} + } + } + ], + "discriminator": {} # discriminator without propertyName + } + } + } + + Model = SchemaConverter.build(schema) + + # This input matches both schemas since both accept data as string + # and neither requires specific additional properties + with self.assertRaises(ValueError) as cm: + Model(value={"data": "test"}) + self.assertIn("matches multiple oneOf schemas", str(cm.exception)) + + def test_oneof_overlapping_strings_from_docs(self): + """Test the overlapping strings example from documentation""" + schema = { + "title": "SimpleExample", + "type": "object", + "properties": { + "value": { + "oneOf": [ + {"type": "string", "maxLength": 6}, + {"type": "string", "minLength": 4} + ] + } + }, + "required": ["value"] + } + + Model = SchemaConverter.build(schema) + + # Valid: Short string (matches first schema only) + obj1 = Model(value="hi") + self.assertEqual(obj1.value, "hi") + + # Valid: Long string (matches second schema only) + obj2 = Model(value="very long string") + self.assertEqual(obj2.value, "very long string") + + # Invalid: Medium string (matches BOTH schemas - violates oneOf) + with self.assertRaises(ValueError) as cm: + Model(value="hello") # 5 chars: matches maxLength=6 AND minLength=4 + self.assertIn("matches multiple oneOf schemas", str(cm.exception)) + + def test_oneof_shapes_discriminator_from_docs(self): + """Test the shapes discriminator example from documentation""" + schema = { + "title": "Shape", + "type": "object", + "properties": { + "shape": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": {"const": "circle"}, + "radius": {"type": "number", "minimum": 0} + }, + "required": ["type", "radius"] + }, + { + "type": "object", + "properties": { + "type": {"const": "rectangle"}, + "width": {"type": "number", "minimum": 0}, + "height": {"type": "number", "minimum": 0} + }, + "required": ["type", "width", "height"] + } + ], + "discriminator": { + "propertyName": "type" + } + } + }, + "required": ["shape"] + } + + Model = SchemaConverter.build(schema) + + # Valid: Circle + circle = Model(shape={"type": "circle", "radius": 5.0}) + self.assertEqual(circle.shape.type, "circle") + self.assertEqual(circle.shape.radius, 5.0) + + # Valid: Rectangle + rectangle = Model(shape={"type": "rectangle", "width": 10, "height": 20}) + self.assertEqual(rectangle.shape.type, "rectangle") + self.assertEqual(rectangle.shape.width, 10) + self.assertEqual(rectangle.shape.height, 20) + + # Invalid: Wrong properties for the type + with self.assertRaises(ValueError): + Model(shape={"type": "circle", "width": 10}) -- 2.49.1 From cc6f2d42d572c4b249c3491822ba3b68256a9802 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Tue, 19 Aug 2025 18:39:37 -0300 Subject: [PATCH 17/96] Separates PR for Better Testing and Readability --- jambo/parser/_type_parser.py | 23 ----- jambo/parser/const_type_parser.py | 24 ++--- jambo/parser/oneof_type_parser.py | 37 ++++++-- jambo/parser/string_type_parser.py | 9 +- tests/parser/test_const_type_parser.py | 61 +------------ tests/parser/test_oneof_type_parser.py | 119 ++++++++++++------------- 6 files changed, 100 insertions(+), 173 deletions(-) diff --git a/jambo/parser/_type_parser.py b/jambo/parser/_type_parser.py index 6c5cdc9..080965c 100644 --- a/jambo/parser/_type_parser.py +++ b/jambo/parser/_type_parser.py @@ -124,26 +124,3 @@ class GenericTypeParser(ABC, Generic[T]): return False return True - - @staticmethod - def _has_meaningful_constraints(field_props): - """ - Check if field properties contain meaningful constraints that require Field wrapping. - - Returns False if: - - field_props is None or empty - - field_props only contains {'default': None} - - Returns True if: - - field_props contains a non-None default value - - field_props contains other constraint properties (min_length, max_length, pattern, etc.) - """ - if not field_props: - return False - - # If only default is set and it's None, no meaningful constraints - if field_props == {"default": None}: - return False - - # If there are multiple properties or non-None default, that's meaningful - return True diff --git a/jambo/parser/const_type_parser.py b/jambo/parser/const_type_parser.py index 1e4ce84..b5c846f 100644 --- a/jambo/parser/const_type_parser.py +++ b/jambo/parser/const_type_parser.py @@ -3,7 +3,7 @@ from jambo.types.json_schema_type import JSONSchemaNativeTypes from jambo.types.type_parser_options import TypeParserOptions from pydantic import AfterValidator -from typing_extensions import Annotated, Any, Literal, Unpack +from typing_extensions import Annotated, Any, Unpack class ConstTypeParser(GenericTypeParser): @@ -33,19 +33,11 @@ class ConstTypeParser(GenericTypeParser): return const_type, parsed_properties def _build_const_type(self, const_value): - # Try to use Literal for hashable types (required for discriminated unions) - # Fall back to validator approach for non-hashable types - try: - # Test if the value is hashable (can be used in Literal) - hash(const_value) - return Literal[const_value] - except TypeError: - # Non-hashable type (like list, dict), use validator approach - def _validate_const_value(value: Any) -> Any: - if value != const_value: - raise ValueError( - f"Value must be equal to the constant value: {const_value}" - ) - return value + def _validate_const_value(value: Any) -> Any: + if value != const_value: + raise ValueError( + f"Value must be equal to the constant value: {const_value}" + ) + return value - return Annotated[type(const_value), AfterValidator(_validate_const_value)] \ No newline at end of file + return Annotated[type(const_value), AfterValidator(_validate_const_value)] diff --git a/jambo/parser/oneof_type_parser.py b/jambo/parser/oneof_type_parser.py index 79146b9..c0eeecc 100644 --- a/jambo/parser/oneof_type_parser.py +++ b/jambo/parser/oneof_type_parser.py @@ -1,8 +1,8 @@ from jambo.parser._type_parser import GenericTypeParser from jambo.types.type_parser_options import TypeParserOptions -from pydantic import Field, BeforeValidator, TypeAdapter, ValidationError -from typing_extensions import Annotated, Union, Unpack, Any +from pydantic import BeforeValidator, Field, TypeAdapter, ValidationError +from typing_extensions import Annotated, Any, Union, Unpack class OneOfTypeParser(GenericTypeParser): @@ -11,7 +11,7 @@ class OneOfTypeParser(GenericTypeParser): json_schema_type = "oneOf" def from_properties_impl( - self, name, properties, **kwargs: Unpack[TypeParserOptions] + self, name, properties, **kwargs: Unpack[TypeParserOptions] ): if "oneOf" not in properties: raise ValueError(f"Invalid JSON Schema: {properties}") @@ -42,7 +42,9 @@ class OneOfTypeParser(GenericTypeParser): if discriminator and isinstance(discriminator, dict): property_name = discriminator.get("propertyName") if property_name: - validated_type = Annotated[union_type, Field(discriminator=property_name)] + validated_type = Annotated[ + union_type, Field(discriminator=property_name) + ] return validated_type, mapped_properties def validate_one_of(value: Any) -> Any: @@ -59,11 +61,34 @@ class OneOfTypeParser(GenericTypeParser): continue if matched_count == 0: - raise ValueError(f"Value does not match any of the oneOf schemas") + raise ValueError("Value does not match any of the oneOf schemas") elif matched_count > 1: - raise ValueError(f"Value matches multiple oneOf schemas, exactly one expected") + raise ValueError( + "Value matches multiple oneOf schemas, exactly one expected" + ) return value validated_type = Annotated[union_type, BeforeValidator(validate_one_of)] return validated_type, mapped_properties + + @staticmethod + def _has_meaningful_constraints(field_props): + """ + Check if field properties contain meaningful constraints that require Field wrapping. + Returns False if: + - field_props is None or empty + - field_props only contains {'default': None} + Returns True if: + - field_props contains a non-None default value + - field_props contains other constraint properties (min_length, max_length, pattern, etc.) + """ + if not field_props: + return False + + # If only default is set and it's None, no meaningful constraints + if field_props == {"default": None}: + return False + + # If there are multiple properties or non-None default, that's meaningful + return True diff --git a/jambo/parser/string_type_parser.py b/jambo/parser/string_type_parser.py index 9f43034..7e94b0d 100644 --- a/jambo/parser/string_type_parser.py +++ b/jambo/parser/string_type_parser.py @@ -16,6 +16,7 @@ class StringTypeParser(GenericTypeParser): "maxLength": "max_length", "minLength": "min_length", "pattern": "pattern", + "format": "format", } format_type_mapping = { @@ -37,9 +38,7 @@ class StringTypeParser(GenericTypeParser): def from_properties_impl( self, name, properties, **kwargs: Unpack[TypeParserOptions] ): - mapped_properties = self.mappings_properties_builder( - properties, **kwargs - ) + mapped_properties = self.mappings_properties_builder(properties, **kwargs) format_type = properties.get("format") if not format_type: @@ -52,8 +51,4 @@ class StringTypeParser(GenericTypeParser): if format_type in self.format_pattern_mapping: mapped_properties["pattern"] = self.format_pattern_mapping[format_type] - if "json_schema_extra" not in mapped_properties: - mapped_properties["json_schema_extra"] = {} - mapped_properties["json_schema_extra"]["format"] = format_type - return mapped_type, mapped_properties diff --git a/tests/parser/test_const_type_parser.py b/tests/parser/test_const_type_parser.py index 5a8c9c1..ca92bb0 100644 --- a/tests/parser/test_const_type_parser.py +++ b/tests/parser/test_const_type_parser.py @@ -1,13 +1,12 @@ from jambo.parser import ConstTypeParser -from typing_extensions import Annotated, Literal, get_args, get_origin +from typing_extensions import Annotated, get_args, get_origin from unittest import TestCase class TestConstTypeParser(TestCase): - def test_const_type_parser_hashable_value(self): - """Test const parser with hashable values (uses Literal)""" + def test_const_type_parser(self): parser = ConstTypeParser() expected_const_value = "United States of America" @@ -17,60 +16,8 @@ class TestConstTypeParser(TestCase): "country", properties ) - # Check that we get a Literal type for hashable values - self.assertEqual(get_origin(parsed_type), Literal) - self.assertEqual(get_args(parsed_type), (expected_const_value,)) - - self.assertEqual(parsed_properties["default"], expected_const_value) - - def test_const_type_parser_non_hashable_value(self): - """Test const parser with non-hashable values (uses Annotated with validator)""" - parser = ConstTypeParser() - - expected_const_value = [1, 2, 3] # Lists are not hashable - properties = {"const": expected_const_value} - - parsed_type, parsed_properties = parser.from_properties_impl( - "list_const", properties - ) - - # Check that we get an Annotated type for non-hashable values self.assertEqual(get_origin(parsed_type), Annotated) - self.assertIn(list, get_args(parsed_type)) - - self.assertEqual(parsed_properties["default"], expected_const_value) - - def test_const_type_parser_integer_value(self): - """Test const parser with integer values (uses Literal)""" - parser = ConstTypeParser() - - expected_const_value = 42 - properties = {"const": expected_const_value} - - parsed_type, parsed_properties = parser.from_properties_impl( - "int_const", properties - ) - - # Check that we get a Literal type for hashable values - self.assertEqual(get_origin(parsed_type), Literal) - self.assertEqual(get_args(parsed_type), (expected_const_value,)) - - self.assertEqual(parsed_properties["default"], expected_const_value) - - def test_const_type_parser_boolean_value(self): - """Test const parser with boolean values (uses Literal)""" - parser = ConstTypeParser() - - expected_const_value = True - properties = {"const": expected_const_value} - - parsed_type, parsed_properties = parser.from_properties_impl( - "bool_const", properties - ) - - # Check that we get a Literal type for hashable values - self.assertEqual(get_origin(parsed_type), Literal) - self.assertEqual(get_args(parsed_type), (expected_const_value,)) + self.assertIn(str, get_args(parsed_type)) self.assertEqual(parsed_properties["default"], expected_const_value) @@ -99,4 +46,4 @@ class TestConstTypeParser(TestCase): self.assertIn( "Const type invalid_country must have 'const' value of allowed types", str(context.exception), - ) \ No newline at end of file + ) diff --git a/tests/parser/test_oneof_type_parser.py b/tests/parser/test_oneof_type_parser.py index 8c75f04..fbe362c 100644 --- a/tests/parser/test_oneof_type_parser.py +++ b/tests/parser/test_oneof_type_parser.py @@ -97,7 +97,7 @@ class TestOneOfTypeParser(TestCase): "email": {"type": "string", "format": "email"} }, "required": ["email"], - "additionalProperties": False + "additionalProperties": False, }, { "type": "object", @@ -105,8 +105,8 @@ class TestOneOfTypeParser(TestCase): "phone": {"type": "string", "pattern": "^[0-9-]+$"} }, "required": ["phone"], - "additionalProperties": False - } + "additionalProperties": False, + }, ] }, }, @@ -135,25 +135,23 @@ class TestOneOfTypeParser(TestCase): "type": "object", "properties": { "type": {"const": "cat"}, - "meows": {"type": "boolean"} + "meows": {"type": "boolean"}, }, - "required": ["type", "meows"] + "required": ["type", "meows"], }, { "type": "object", "properties": { "type": {"const": "dog"}, - "barks": {"type": "boolean"} + "barks": {"type": "boolean"}, }, - "required": ["type", "barks"] - } + "required": ["type", "barks"], + }, ], - "discriminator": { - "propertyName": "type" - } + "discriminator": {"propertyName": "type"}, } }, - "required": ["pet"] + "required": ["pet"], } Model = SchemaConverter.build(schema) @@ -183,29 +181,33 @@ class TestOneOfTypeParser(TestCase): "type": "object", "properties": { "vehicle_type": {"const": "car"}, - "doors": {"type": "integer", "minimum": 2, "maximum": 4} + "doors": { + "type": "integer", + "minimum": 2, + "maximum": 4, + }, }, - "required": ["vehicle_type", "doors"] + "required": ["vehicle_type", "doors"], }, { "type": "object", "properties": { "vehicle_type": {"const": "motorcycle"}, - "engine_size": {"type": "number", "minimum": 125} + "engine_size": {"type": "number", "minimum": 125}, }, - "required": ["vehicle_type", "engine_size"] - } + "required": ["vehicle_type", "engine_size"], + }, ], "discriminator": { "propertyName": "vehicle_type", "mapping": { "car": "#/properties/vehicle/oneOf/0", - "motorcycle": "#/properties/vehicle/oneOf/1" - } - } + "motorcycle": "#/properties/vehicle/oneOf/1", + }, + }, } }, - "required": ["vehicle"] + "required": ["vehicle"], } Model = SchemaConverter.build(schema) @@ -229,25 +231,23 @@ class TestOneOfTypeParser(TestCase): "type": "object", "properties": { "type": {"const": "circle"}, - "radius": {"type": "number", "minimum": 0} + "radius": {"type": "number", "minimum": 0}, }, - "required": ["type", "radius"] + "required": ["type", "radius"], }, { "type": "object", "properties": { "type": {"const": "square"}, - "side": {"type": "number", "minimum": 0} + "side": {"type": "number", "minimum": 0}, }, - "required": ["type", "side"] - } + "required": ["type", "side"], + }, ], - "discriminator": { - "propertyName": "type" - } + "discriminator": {"propertyName": "type"}, } }, - "required": ["shape"] + "required": ["shape"], } Model = SchemaConverter.build(schema) @@ -283,9 +283,7 @@ class TestOneOfTypeParser(TestCase): "title": "Test", "type": "object", "properties": { - "value": { - "oneOf": None - }, + "value": {"oneOf": None}, }, } @@ -302,7 +300,7 @@ class TestOneOfTypeParser(TestCase): {"type": "string"}, {"type": "integer"}, ], - "default": "test" + "default": "test", }, }, } @@ -321,7 +319,7 @@ class TestOneOfTypeParser(TestCase): {"type": "string", "minLength": 5}, {"type": "integer", "minimum": 10}, ], - "default": "hi" + "default": "hi", }, }, } @@ -340,20 +338,20 @@ class TestOneOfTypeParser(TestCase): "type": "object", "properties": { "type": {"const": "a"}, - "value": {"type": "string"} - } + "value": {"type": "string"}, + }, }, { "type": "object", "properties": { "type": {"const": "b"}, - "value": {"type": "integer"} - } - } + "value": {"type": "integer"}, + }, + }, ], - "discriminator": {} # discriminator without propertyName + "discriminator": {}, # discriminator without propertyName } - } + }, } Model = SchemaConverter.build(schema) @@ -383,23 +381,18 @@ class TestOneOfTypeParser(TestCase): "properties": { "value": { "oneOf": [ - { - "type": "object", - "properties": { - "data": {"type": "string"} - } - }, + {"type": "object", "properties": {"data": {"type": "string"}}}, { "type": "object", "properties": { "data": {"type": "string"}, - "optional": {"type": "string"} - } - } + "optional": {"type": "string"}, + }, + }, ], - "discriminator": {} # discriminator without propertyName + "discriminator": {}, # discriminator without propertyName } - } + }, } Model = SchemaConverter.build(schema) @@ -419,11 +412,11 @@ class TestOneOfTypeParser(TestCase): "value": { "oneOf": [ {"type": "string", "maxLength": 6}, - {"type": "string", "minLength": 4} + {"type": "string", "minLength": 4}, ] } }, - "required": ["value"] + "required": ["value"], } Model = SchemaConverter.build(schema) @@ -453,26 +446,24 @@ class TestOneOfTypeParser(TestCase): "type": "object", "properties": { "type": {"const": "circle"}, - "radius": {"type": "number", "minimum": 0} + "radius": {"type": "number", "minimum": 0}, }, - "required": ["type", "radius"] + "required": ["type", "radius"], }, { "type": "object", "properties": { "type": {"const": "rectangle"}, "width": {"type": "number", "minimum": 0}, - "height": {"type": "number", "minimum": 0} + "height": {"type": "number", "minimum": 0}, }, - "required": ["type", "width", "height"] - } + "required": ["type", "width", "height"], + }, ], - "discriminator": { - "propertyName": "type" - } + "discriminator": {"propertyName": "type"}, } }, - "required": ["shape"] + "required": ["shape"], } Model = SchemaConverter.build(schema) -- 2.49.1 From 9aec7c3e3bc9bd80cccc7b4476b27ea224abbc03 Mon Sep 17 00:00:00 2001 From: Thomas <34217413+thommann@users.noreply.github.com> Date: Mon, 7 Jul 2025 15:49:58 +0200 Subject: [PATCH 18/96] feat(jambo): Add oneOf parser (#5) * Add support for `oneOf` type parsing with validation and example cases * Improve `oneOf` type parsing: refine validators, add discriminator support, and expand test coverage * Add hashable and non-hashable value support to `ConstTypeParser` with expanded test cases * Refine `field_props` check in `_type_parser` for cleaner default handling * Update `StringTypeParser` to refine `format` handling and enrich `json_schema_extra` * Remove outdated `oneOf` examples from docs, expand test cases and provide refined examples with discriminator support --- docs/source/usage.oneof.rst | 108 ++++++ docs/source/usage.rst | 1 + jambo/parser/__init__.py | 4 +- jambo/parser/_type_parser.py | 23 ++ jambo/parser/const_type_parser.py | 24 +- jambo/parser/oneof_type_parser.py | 69 ++++ jambo/parser/string_type_parser.py | 5 +- tests/parser/test_const_type_parser.py | 61 ++- tests/parser/test_oneof_type_parser.py | 493 +++++++++++++++++++++++++ 9 files changed, 774 insertions(+), 14 deletions(-) create mode 100644 docs/source/usage.oneof.rst create mode 100644 jambo/parser/oneof_type_parser.py create mode 100644 tests/parser/test_oneof_type_parser.py diff --git a/docs/source/usage.oneof.rst b/docs/source/usage.oneof.rst new file mode 100644 index 0000000..a836df4 --- /dev/null +++ b/docs/source/usage.oneof.rst @@ -0,0 +1,108 @@ +OneOf Type +================= + +The OneOf type is used to specify that an object must conform to exactly one of the specified schemas. Unlike AnyOf which allows matching multiple schemas, OneOf enforces that the data matches one and only one of the provided schemas. + + +Examples +----------------- + +1. **Overlapping String Example** - A field that accepts strings with overlapping constraints: + +.. code-block:: python + + from jambo import SchemaConverter + + schema = { + "title": "SimpleExample", + "type": "object", + "properties": { + "value": { + "oneOf": [ + {"type": "string", "maxLength": 6}, + {"type": "string", "minLength": 4} + ] + } + }, + "required": ["value"] + } + + Model = SchemaConverter.build(schema) + + # Valid: Short string (matches first schema only) + obj1 = Model(value="hi") + print(obj1.value) # Output: hi + + # Valid: Long string (matches second schema only) + obj2 = Model(value="very long string") + print(obj2.value) # Output: very long string + + # Invalid: Medium string (matches BOTH schemas - violates oneOf) + try: + obj3 = Model(value="hello") # 5 chars: matches maxLength=6 AND minLength=4 + except ValueError as e: + print("Validation fails as expected:", e) + + +2. **Discriminator Example** - Different shapes with a type field: + +.. code-block:: python + + from jambo import SchemaConverter + + schema = { + "title": "Shape", + "type": "object", + "properties": { + "shape": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": {"const": "circle"}, + "radius": {"type": "number", "minimum": 0} + }, + "required": ["type", "radius"] + }, + { + "type": "object", + "properties": { + "type": {"const": "rectangle"}, + "width": {"type": "number", "minimum": 0}, + "height": {"type": "number", "minimum": 0} + }, + "required": ["type", "width", "height"] + } + ], + "discriminator": { + "propertyName": "type" + } + } + }, + "required": ["shape"] + } + + Model = SchemaConverter.build(schema) + + # Valid: Circle + circle = Model(shape={"type": "circle", "radius": 5.0}) + print(circle.shape.type) # Output: circle + + # Valid: Rectangle + rectangle = Model(shape={"type": "rectangle", "width": 10, "height": 20}) + print(rectangle.shape.type) # Output: rectangle + + # Invalid: Wrong properties for the type + try: + invalid = Model(shape={"type": "circle", "width": 10}) + except ValueError as e: + print("Validation fails as expected:", e) + + +.. note:: + + OneOf ensures exactly one schema matches. The discriminator helps Pydantic efficiently determine which schema to use based on a specific property value. + +.. warning:: + + If your data could match multiple schemas in a oneOf, validation will fail. Ensure schemas are mutually exclusive. diff --git a/docs/source/usage.rst b/docs/source/usage.rst index 8896842..3bdb2d9 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -45,5 +45,6 @@ For more complex schemas and types see our documentation on usage.reference usage.allof usage.anyof + usage.oneof usage.enum usage.const \ No newline at end of file diff --git a/jambo/parser/__init__.py b/jambo/parser/__init__.py index f3b8b25..44b4424 100644 --- a/jambo/parser/__init__.py +++ b/jambo/parser/__init__.py @@ -9,6 +9,7 @@ from .float_type_parser import FloatTypeParser from .int_type_parser import IntTypeParser from .null_type_parser import NullTypeParser from .object_type_parser import ObjectTypeParser +from .oneof_type_parser import OneOfTypeParser from .ref_type_parser import RefTypeParser from .string_type_parser import StringTypeParser @@ -25,6 +26,7 @@ __all__ = [ "IntTypeParser", "NullTypeParser", "ObjectTypeParser", + "OneOfTypeParser", "StringTypeParser", "RefTypeParser", -] +] \ No newline at end of file diff --git a/jambo/parser/_type_parser.py b/jambo/parser/_type_parser.py index 080965c..6c5cdc9 100644 --- a/jambo/parser/_type_parser.py +++ b/jambo/parser/_type_parser.py @@ -124,3 +124,26 @@ class GenericTypeParser(ABC, Generic[T]): return False return True + + @staticmethod + def _has_meaningful_constraints(field_props): + """ + Check if field properties contain meaningful constraints that require Field wrapping. + + Returns False if: + - field_props is None or empty + - field_props only contains {'default': None} + + Returns True if: + - field_props contains a non-None default value + - field_props contains other constraint properties (min_length, max_length, pattern, etc.) + """ + if not field_props: + return False + + # If only default is set and it's None, no meaningful constraints + if field_props == {"default": None}: + return False + + # If there are multiple properties or non-None default, that's meaningful + return True diff --git a/jambo/parser/const_type_parser.py b/jambo/parser/const_type_parser.py index b5c846f..1e4ce84 100644 --- a/jambo/parser/const_type_parser.py +++ b/jambo/parser/const_type_parser.py @@ -3,7 +3,7 @@ from jambo.types.json_schema_type import JSONSchemaNativeTypes from jambo.types.type_parser_options import TypeParserOptions from pydantic import AfterValidator -from typing_extensions import Annotated, Any, Unpack +from typing_extensions import Annotated, Any, Literal, Unpack class ConstTypeParser(GenericTypeParser): @@ -33,11 +33,19 @@ class ConstTypeParser(GenericTypeParser): return const_type, parsed_properties def _build_const_type(self, const_value): - def _validate_const_value(value: Any) -> Any: - if value != const_value: - raise ValueError( - f"Value must be equal to the constant value: {const_value}" - ) - return value + # Try to use Literal for hashable types (required for discriminated unions) + # Fall back to validator approach for non-hashable types + try: + # Test if the value is hashable (can be used in Literal) + hash(const_value) + return Literal[const_value] + except TypeError: + # Non-hashable type (like list, dict), use validator approach + def _validate_const_value(value: Any) -> Any: + if value != const_value: + raise ValueError( + f"Value must be equal to the constant value: {const_value}" + ) + return value - return Annotated[type(const_value), AfterValidator(_validate_const_value)] + return Annotated[type(const_value), AfterValidator(_validate_const_value)] \ No newline at end of file diff --git a/jambo/parser/oneof_type_parser.py b/jambo/parser/oneof_type_parser.py new file mode 100644 index 0000000..79146b9 --- /dev/null +++ b/jambo/parser/oneof_type_parser.py @@ -0,0 +1,69 @@ +from jambo.parser._type_parser import GenericTypeParser +from jambo.types.type_parser_options import TypeParserOptions + +from pydantic import Field, BeforeValidator, TypeAdapter, ValidationError +from typing_extensions import Annotated, Union, Unpack, Any + + +class OneOfTypeParser(GenericTypeParser): + mapped_type = Union + + json_schema_type = "oneOf" + + def from_properties_impl( + self, name, properties, **kwargs: Unpack[TypeParserOptions] + ): + if "oneOf" not in properties: + raise ValueError(f"Invalid JSON Schema: {properties}") + + if not isinstance(properties["oneOf"], list): + raise ValueError(f"Invalid JSON Schema: {properties['oneOf']}") + + mapped_properties = self.mappings_properties_builder(properties, **kwargs) + + sub_properties = properties["oneOf"] + + sub_types = [ + GenericTypeParser.type_from_properties(name, subProperty, **kwargs) + for subProperty in sub_properties + ] + + if not kwargs.get("required", False): + mapped_properties["default"] = mapped_properties.get("default") + + field_types = [ + Annotated[t, Field(**v)] if self._has_meaningful_constraints(v) else t + for t, v in sub_types + ] + + union_type = Union[(*field_types,)] + + discriminator = properties.get("discriminator") + if discriminator and isinstance(discriminator, dict): + property_name = discriminator.get("propertyName") + if property_name: + validated_type = Annotated[union_type, Field(discriminator=property_name)] + return validated_type, mapped_properties + + def validate_one_of(value: Any) -> Any: + matched_count = 0 + validation_errors = [] + + for field_type in field_types: + try: + adapter = TypeAdapter(field_type) + adapter.validate_python(value) + matched_count += 1 + except ValidationError as e: + validation_errors.append(str(e)) + continue + + if matched_count == 0: + raise ValueError(f"Value does not match any of the oneOf schemas") + elif matched_count > 1: + raise ValueError(f"Value matches multiple oneOf schemas, exactly one expected") + + return value + + validated_type = Annotated[union_type, BeforeValidator(validate_one_of)] + return validated_type, mapped_properties diff --git a/jambo/parser/string_type_parser.py b/jambo/parser/string_type_parser.py index 389f8f4..9f43034 100644 --- a/jambo/parser/string_type_parser.py +++ b/jambo/parser/string_type_parser.py @@ -16,7 +16,6 @@ class StringTypeParser(GenericTypeParser): "maxLength": "max_length", "minLength": "min_length", "pattern": "pattern", - "format": "format", } format_type_mapping = { @@ -53,4 +52,8 @@ class StringTypeParser(GenericTypeParser): if format_type in self.format_pattern_mapping: mapped_properties["pattern"] = self.format_pattern_mapping[format_type] + if "json_schema_extra" not in mapped_properties: + mapped_properties["json_schema_extra"] = {} + mapped_properties["json_schema_extra"]["format"] = format_type + return mapped_type, mapped_properties diff --git a/tests/parser/test_const_type_parser.py b/tests/parser/test_const_type_parser.py index ca92bb0..5a8c9c1 100644 --- a/tests/parser/test_const_type_parser.py +++ b/tests/parser/test_const_type_parser.py @@ -1,12 +1,13 @@ from jambo.parser import ConstTypeParser -from typing_extensions import Annotated, get_args, get_origin +from typing_extensions import Annotated, Literal, get_args, get_origin from unittest import TestCase class TestConstTypeParser(TestCase): - def test_const_type_parser(self): + def test_const_type_parser_hashable_value(self): + """Test const parser with hashable values (uses Literal)""" parser = ConstTypeParser() expected_const_value = "United States of America" @@ -16,8 +17,60 @@ class TestConstTypeParser(TestCase): "country", properties ) + # Check that we get a Literal type for hashable values + self.assertEqual(get_origin(parsed_type), Literal) + self.assertEqual(get_args(parsed_type), (expected_const_value,)) + + self.assertEqual(parsed_properties["default"], expected_const_value) + + def test_const_type_parser_non_hashable_value(self): + """Test const parser with non-hashable values (uses Annotated with validator)""" + parser = ConstTypeParser() + + expected_const_value = [1, 2, 3] # Lists are not hashable + properties = {"const": expected_const_value} + + parsed_type, parsed_properties = parser.from_properties_impl( + "list_const", properties + ) + + # Check that we get an Annotated type for non-hashable values self.assertEqual(get_origin(parsed_type), Annotated) - self.assertIn(str, get_args(parsed_type)) + self.assertIn(list, get_args(parsed_type)) + + self.assertEqual(parsed_properties["default"], expected_const_value) + + def test_const_type_parser_integer_value(self): + """Test const parser with integer values (uses Literal)""" + parser = ConstTypeParser() + + expected_const_value = 42 + properties = {"const": expected_const_value} + + parsed_type, parsed_properties = parser.from_properties_impl( + "int_const", properties + ) + + # Check that we get a Literal type for hashable values + self.assertEqual(get_origin(parsed_type), Literal) + self.assertEqual(get_args(parsed_type), (expected_const_value,)) + + self.assertEqual(parsed_properties["default"], expected_const_value) + + def test_const_type_parser_boolean_value(self): + """Test const parser with boolean values (uses Literal)""" + parser = ConstTypeParser() + + expected_const_value = True + properties = {"const": expected_const_value} + + parsed_type, parsed_properties = parser.from_properties_impl( + "bool_const", properties + ) + + # Check that we get a Literal type for hashable values + self.assertEqual(get_origin(parsed_type), Literal) + self.assertEqual(get_args(parsed_type), (expected_const_value,)) self.assertEqual(parsed_properties["default"], expected_const_value) @@ -46,4 +99,4 @@ class TestConstTypeParser(TestCase): self.assertIn( "Const type invalid_country must have 'const' value of allowed types", str(context.exception), - ) + ) \ No newline at end of file diff --git a/tests/parser/test_oneof_type_parser.py b/tests/parser/test_oneof_type_parser.py new file mode 100644 index 0000000..8c75f04 --- /dev/null +++ b/tests/parser/test_oneof_type_parser.py @@ -0,0 +1,493 @@ +from jambo import SchemaConverter + +from unittest import TestCase + + +class TestOneOfTypeParser(TestCase): + def test_oneof_basic_integer_and_string(self): + schema = { + "title": "Person", + "description": "A person with an ID that can be either an integer or a formatted string", + "type": "object", + "properties": { + "id": { + "oneOf": [ + {"type": "integer", "minimum": 1}, + {"type": "string", "pattern": "^[A-Z]{2}[0-9]{4}$"}, + ] + }, + }, + "required": ["id"], + } + + Model = SchemaConverter.build(schema) + + obj1 = Model(id=123) + self.assertEqual(obj1.id, 123) + + obj2 = Model(id="AB1234") + self.assertEqual(obj2.id, "AB1234") + + def test_oneof_validation_failures(self): + schema = { + "title": "Person", + "type": "object", + "properties": { + "id": { + "oneOf": [ + {"type": "integer", "minimum": 1}, + {"type": "string", "pattern": "^[A-Z]{2}[0-9]{4}$"}, + ] + }, + }, + "required": ["id"], + } + + Model = SchemaConverter.build(schema) + + with self.assertRaises(ValueError): + Model(id=-5) + + with self.assertRaises(ValueError): + Model(id="invalid") + + with self.assertRaises(ValueError): + Model(id=123.45) + + def test_oneof_with_conflicting_schemas(self): + schema = { + "title": "Value", + "type": "object", + "properties": { + "data": { + "oneOf": [ + {"type": "number", "multipleOf": 2}, + {"type": "number", "multipleOf": 3}, + ] + }, + }, + "required": ["data"], + } + + Model = SchemaConverter.build(schema) + + obj1 = Model(data=4) + self.assertEqual(obj1.data, 4) + + obj2 = Model(data=9) + self.assertEqual(obj2.data, 9) + + with self.assertRaises(ValueError) as cm: + Model(data=6) + self.assertIn("matches multiple oneOf schemas", str(cm.exception)) + + with self.assertRaises(ValueError): + Model(data=5) + + def test_oneof_with_objects(self): + schema = { + "title": "Contact", + "type": "object", + "properties": { + "contact_info": { + "oneOf": [ + { + "type": "object", + "properties": { + "email": {"type": "string", "format": "email"} + }, + "required": ["email"], + "additionalProperties": False + }, + { + "type": "object", + "properties": { + "phone": {"type": "string", "pattern": "^[0-9-]+$"} + }, + "required": ["phone"], + "additionalProperties": False + } + ] + }, + }, + "required": ["contact_info"], + } + + Model = SchemaConverter.build(schema) + + obj1 = Model(contact_info={"email": "user@example.com"}) + self.assertEqual(obj1.contact_info.email, "user@example.com") + + obj2 = Model(contact_info={"phone": "123-456-7890"}) + self.assertEqual(obj2.contact_info.phone, "123-456-7890") + + with self.assertRaises(ValueError): + Model(contact_info={"email": "user@example.com", "phone": "123-456-7890"}) + + def test_oneof_with_discriminator_basic(self): + schema = { + "title": "Pet", + "type": "object", + "properties": { + "pet": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": {"const": "cat"}, + "meows": {"type": "boolean"} + }, + "required": ["type", "meows"] + }, + { + "type": "object", + "properties": { + "type": {"const": "dog"}, + "barks": {"type": "boolean"} + }, + "required": ["type", "barks"] + } + ], + "discriminator": { + "propertyName": "type" + } + } + }, + "required": ["pet"] + } + + Model = SchemaConverter.build(schema) + + cat = Model(pet={"type": "cat", "meows": True}) + self.assertEqual(cat.pet.type, "cat") + self.assertEqual(cat.pet.meows, True) + + dog = Model(pet={"type": "dog", "barks": False}) + self.assertEqual(dog.pet.type, "dog") + self.assertEqual(dog.pet.barks, False) + + with self.assertRaises(ValueError): + Model(pet={"type": "cat", "barks": True}) + + with self.assertRaises(ValueError): + Model(pet={"type": "bird", "flies": True}) + + def test_oneof_with_discriminator_mapping(self): + schema = { + "title": "Vehicle", + "type": "object", + "properties": { + "vehicle": { + "oneOf": [ + { + "type": "object", + "properties": { + "vehicle_type": {"const": "car"}, + "doors": {"type": "integer", "minimum": 2, "maximum": 4} + }, + "required": ["vehicle_type", "doors"] + }, + { + "type": "object", + "properties": { + "vehicle_type": {"const": "motorcycle"}, + "engine_size": {"type": "number", "minimum": 125} + }, + "required": ["vehicle_type", "engine_size"] + } + ], + "discriminator": { + "propertyName": "vehicle_type", + "mapping": { + "car": "#/properties/vehicle/oneOf/0", + "motorcycle": "#/properties/vehicle/oneOf/1" + } + } + } + }, + "required": ["vehicle"] + } + + Model = SchemaConverter.build(schema) + + car = Model(vehicle={"vehicle_type": "car", "doors": 4}) + self.assertEqual(car.vehicle.vehicle_type, "car") + self.assertEqual(car.vehicle.doors, 4) + + motorcycle = Model(vehicle={"vehicle_type": "motorcycle", "engine_size": 600.0}) + self.assertEqual(motorcycle.vehicle.vehicle_type, "motorcycle") + self.assertEqual(motorcycle.vehicle.engine_size, 600.0) + + def test_oneof_with_discriminator_invalid_values(self): + schema = { + "title": "Shape", + "type": "object", + "properties": { + "shape": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": {"const": "circle"}, + "radius": {"type": "number", "minimum": 0} + }, + "required": ["type", "radius"] + }, + { + "type": "object", + "properties": { + "type": {"const": "square"}, + "side": {"type": "number", "minimum": 0} + }, + "required": ["type", "side"] + } + ], + "discriminator": { + "propertyName": "type" + } + } + }, + "required": ["shape"] + } + + Model = SchemaConverter.build(schema) + + with self.assertRaises(ValueError): + Model(shape={"type": "triangle", "base": 5, "height": 3}) + + with self.assertRaises(ValueError): + Model(shape={"type": "circle", "side": 5}) + + with self.assertRaises(ValueError): + Model(shape={"radius": 5}) + + def test_oneof_missing_properties(self): + schema = { + "title": "Test", + "type": "object", + "properties": { + "value": { + "notOneOf": [ + {"type": "string"}, + {"type": "integer"}, + ] + }, + }, + } + + with self.assertRaises(ValueError): + SchemaConverter.build(schema) + + def test_oneof_invalid_properties(self): + schema = { + "title": "Test", + "type": "object", + "properties": { + "value": { + "oneOf": None + }, + }, + } + + with self.assertRaises(ValueError): + SchemaConverter.build(schema) + + def test_oneof_with_default_value(self): + schema = { + "title": "Test", + "type": "object", + "properties": { + "value": { + "oneOf": [ + {"type": "string"}, + {"type": "integer"}, + ], + "default": "test" + }, + }, + } + + Model = SchemaConverter.build(schema) + obj = Model() + self.assertEqual(obj.value, "test") + + def test_oneof_with_invalid_default_value(self): + schema = { + "title": "Test", + "type": "object", + "properties": { + "value": { + "oneOf": [ + {"type": "string", "minLength": 5}, + {"type": "integer", "minimum": 10}, + ], + "default": "hi" + }, + }, + } + + with self.assertRaises(ValueError): + SchemaConverter.build(schema) + + def test_oneof_discriminator_without_property_name(self): + schema = { + "title": "Test", + "type": "object", + "properties": { + "value": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": {"const": "a"}, + "value": {"type": "string"} + } + }, + { + "type": "object", + "properties": { + "type": {"const": "b"}, + "value": {"type": "integer"} + } + } + ], + "discriminator": {} # discriminator without propertyName + } + } + } + + Model = SchemaConverter.build(schema) + + # Should succeed because input matches exactly one schema (the first one) + # The first schema matches: type="a" matches const("a"), value="test" is a string + # The second schema doesn't match: type="a" does not match const("b") + obj = Model(value={"type": "a", "value": "test", "extra": "invalid"}) + self.assertEqual(obj.value.type, "a") + self.assertEqual(obj.value.value, "test") + + # Test with input that matches the second schema + obj2 = Model(value={"type": "b", "value": 42}) + self.assertEqual(obj2.value.type, "b") + self.assertEqual(obj2.value.value, 42) + + # Test with input that matches neither schema (should fail) + with self.assertRaises(ValueError) as cm: + Model(value={"type": "c", "value": "test"}) + self.assertIn("does not match any of the oneOf schemas", str(cm.exception)) + + def test_oneof_multiple_matches_without_discriminator(self): + """Test case where input genuinely matches multiple oneOf schemas""" + schema = { + "title": "Test", + "type": "object", + "properties": { + "value": { + "oneOf": [ + { + "type": "object", + "properties": { + "data": {"type": "string"} + } + }, + { + "type": "object", + "properties": { + "data": {"type": "string"}, + "optional": {"type": "string"} + } + } + ], + "discriminator": {} # discriminator without propertyName + } + } + } + + Model = SchemaConverter.build(schema) + + # This input matches both schemas since both accept data as string + # and neither requires specific additional properties + with self.assertRaises(ValueError) as cm: + Model(value={"data": "test"}) + self.assertIn("matches multiple oneOf schemas", str(cm.exception)) + + def test_oneof_overlapping_strings_from_docs(self): + """Test the overlapping strings example from documentation""" + schema = { + "title": "SimpleExample", + "type": "object", + "properties": { + "value": { + "oneOf": [ + {"type": "string", "maxLength": 6}, + {"type": "string", "minLength": 4} + ] + } + }, + "required": ["value"] + } + + Model = SchemaConverter.build(schema) + + # Valid: Short string (matches first schema only) + obj1 = Model(value="hi") + self.assertEqual(obj1.value, "hi") + + # Valid: Long string (matches second schema only) + obj2 = Model(value="very long string") + self.assertEqual(obj2.value, "very long string") + + # Invalid: Medium string (matches BOTH schemas - violates oneOf) + with self.assertRaises(ValueError) as cm: + Model(value="hello") # 5 chars: matches maxLength=6 AND minLength=4 + self.assertIn("matches multiple oneOf schemas", str(cm.exception)) + + def test_oneof_shapes_discriminator_from_docs(self): + """Test the shapes discriminator example from documentation""" + schema = { + "title": "Shape", + "type": "object", + "properties": { + "shape": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": {"const": "circle"}, + "radius": {"type": "number", "minimum": 0} + }, + "required": ["type", "radius"] + }, + { + "type": "object", + "properties": { + "type": {"const": "rectangle"}, + "width": {"type": "number", "minimum": 0}, + "height": {"type": "number", "minimum": 0} + }, + "required": ["type", "width", "height"] + } + ], + "discriminator": { + "propertyName": "type" + } + } + }, + "required": ["shape"] + } + + Model = SchemaConverter.build(schema) + + # Valid: Circle + circle = Model(shape={"type": "circle", "radius": 5.0}) + self.assertEqual(circle.shape.type, "circle") + self.assertEqual(circle.shape.radius, 5.0) + + # Valid: Rectangle + rectangle = Model(shape={"type": "rectangle", "width": 10, "height": 20}) + self.assertEqual(rectangle.shape.type, "rectangle") + self.assertEqual(rectangle.shape.width, 10) + self.assertEqual(rectangle.shape.height, 20) + + # Invalid: Wrong properties for the type + with self.assertRaises(ValueError): + Model(shape={"type": "circle", "width": 10}) -- 2.49.1 From fbbff0bd9ea1d5ac64ac42c62b5c360958dfb2c4 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Tue, 19 Aug 2025 18:48:43 -0300 Subject: [PATCH 19/96] Removes Changes Not Feature Specific --- docs/source/usage.oneof.rst | 108 ------ docs/source/usage.rst | 1 - jambo/parser/__init__.py | 4 +- jambo/parser/_type_parser.py | 23 -- jambo/parser/oneof_type_parser.py | 69 ---- jambo/parser/string_type_parser.py | 9 +- tests/parser/test_oneof_type_parser.py | 493 ------------------------- 7 files changed, 3 insertions(+), 704 deletions(-) delete mode 100644 docs/source/usage.oneof.rst delete mode 100644 jambo/parser/oneof_type_parser.py delete mode 100644 tests/parser/test_oneof_type_parser.py diff --git a/docs/source/usage.oneof.rst b/docs/source/usage.oneof.rst deleted file mode 100644 index a836df4..0000000 --- a/docs/source/usage.oneof.rst +++ /dev/null @@ -1,108 +0,0 @@ -OneOf Type -================= - -The OneOf type is used to specify that an object must conform to exactly one of the specified schemas. Unlike AnyOf which allows matching multiple schemas, OneOf enforces that the data matches one and only one of the provided schemas. - - -Examples ------------------ - -1. **Overlapping String Example** - A field that accepts strings with overlapping constraints: - -.. code-block:: python - - from jambo import SchemaConverter - - schema = { - "title": "SimpleExample", - "type": "object", - "properties": { - "value": { - "oneOf": [ - {"type": "string", "maxLength": 6}, - {"type": "string", "minLength": 4} - ] - } - }, - "required": ["value"] - } - - Model = SchemaConverter.build(schema) - - # Valid: Short string (matches first schema only) - obj1 = Model(value="hi") - print(obj1.value) # Output: hi - - # Valid: Long string (matches second schema only) - obj2 = Model(value="very long string") - print(obj2.value) # Output: very long string - - # Invalid: Medium string (matches BOTH schemas - violates oneOf) - try: - obj3 = Model(value="hello") # 5 chars: matches maxLength=6 AND minLength=4 - except ValueError as e: - print("Validation fails as expected:", e) - - -2. **Discriminator Example** - Different shapes with a type field: - -.. code-block:: python - - from jambo import SchemaConverter - - schema = { - "title": "Shape", - "type": "object", - "properties": { - "shape": { - "oneOf": [ - { - "type": "object", - "properties": { - "type": {"const": "circle"}, - "radius": {"type": "number", "minimum": 0} - }, - "required": ["type", "radius"] - }, - { - "type": "object", - "properties": { - "type": {"const": "rectangle"}, - "width": {"type": "number", "minimum": 0}, - "height": {"type": "number", "minimum": 0} - }, - "required": ["type", "width", "height"] - } - ], - "discriminator": { - "propertyName": "type" - } - } - }, - "required": ["shape"] - } - - Model = SchemaConverter.build(schema) - - # Valid: Circle - circle = Model(shape={"type": "circle", "radius": 5.0}) - print(circle.shape.type) # Output: circle - - # Valid: Rectangle - rectangle = Model(shape={"type": "rectangle", "width": 10, "height": 20}) - print(rectangle.shape.type) # Output: rectangle - - # Invalid: Wrong properties for the type - try: - invalid = Model(shape={"type": "circle", "width": 10}) - except ValueError as e: - print("Validation fails as expected:", e) - - -.. note:: - - OneOf ensures exactly one schema matches. The discriminator helps Pydantic efficiently determine which schema to use based on a specific property value. - -.. warning:: - - If your data could match multiple schemas in a oneOf, validation will fail. Ensure schemas are mutually exclusive. diff --git a/docs/source/usage.rst b/docs/source/usage.rst index 3bdb2d9..8896842 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -45,6 +45,5 @@ For more complex schemas and types see our documentation on usage.reference usage.allof usage.anyof - usage.oneof usage.enum usage.const \ No newline at end of file diff --git a/jambo/parser/__init__.py b/jambo/parser/__init__.py index 44b4424..f3b8b25 100644 --- a/jambo/parser/__init__.py +++ b/jambo/parser/__init__.py @@ -9,7 +9,6 @@ from .float_type_parser import FloatTypeParser from .int_type_parser import IntTypeParser from .null_type_parser import NullTypeParser from .object_type_parser import ObjectTypeParser -from .oneof_type_parser import OneOfTypeParser from .ref_type_parser import RefTypeParser from .string_type_parser import StringTypeParser @@ -26,7 +25,6 @@ __all__ = [ "IntTypeParser", "NullTypeParser", "ObjectTypeParser", - "OneOfTypeParser", "StringTypeParser", "RefTypeParser", -] \ No newline at end of file +] diff --git a/jambo/parser/_type_parser.py b/jambo/parser/_type_parser.py index 6c5cdc9..080965c 100644 --- a/jambo/parser/_type_parser.py +++ b/jambo/parser/_type_parser.py @@ -124,26 +124,3 @@ class GenericTypeParser(ABC, Generic[T]): return False return True - - @staticmethod - def _has_meaningful_constraints(field_props): - """ - Check if field properties contain meaningful constraints that require Field wrapping. - - Returns False if: - - field_props is None or empty - - field_props only contains {'default': None} - - Returns True if: - - field_props contains a non-None default value - - field_props contains other constraint properties (min_length, max_length, pattern, etc.) - """ - if not field_props: - return False - - # If only default is set and it's None, no meaningful constraints - if field_props == {"default": None}: - return False - - # If there are multiple properties or non-None default, that's meaningful - return True diff --git a/jambo/parser/oneof_type_parser.py b/jambo/parser/oneof_type_parser.py deleted file mode 100644 index 79146b9..0000000 --- a/jambo/parser/oneof_type_parser.py +++ /dev/null @@ -1,69 +0,0 @@ -from jambo.parser._type_parser import GenericTypeParser -from jambo.types.type_parser_options import TypeParserOptions - -from pydantic import Field, BeforeValidator, TypeAdapter, ValidationError -from typing_extensions import Annotated, Union, Unpack, Any - - -class OneOfTypeParser(GenericTypeParser): - mapped_type = Union - - json_schema_type = "oneOf" - - def from_properties_impl( - self, name, properties, **kwargs: Unpack[TypeParserOptions] - ): - if "oneOf" not in properties: - raise ValueError(f"Invalid JSON Schema: {properties}") - - if not isinstance(properties["oneOf"], list): - raise ValueError(f"Invalid JSON Schema: {properties['oneOf']}") - - mapped_properties = self.mappings_properties_builder(properties, **kwargs) - - sub_properties = properties["oneOf"] - - sub_types = [ - GenericTypeParser.type_from_properties(name, subProperty, **kwargs) - for subProperty in sub_properties - ] - - if not kwargs.get("required", False): - mapped_properties["default"] = mapped_properties.get("default") - - field_types = [ - Annotated[t, Field(**v)] if self._has_meaningful_constraints(v) else t - for t, v in sub_types - ] - - union_type = Union[(*field_types,)] - - discriminator = properties.get("discriminator") - if discriminator and isinstance(discriminator, dict): - property_name = discriminator.get("propertyName") - if property_name: - validated_type = Annotated[union_type, Field(discriminator=property_name)] - return validated_type, mapped_properties - - def validate_one_of(value: Any) -> Any: - matched_count = 0 - validation_errors = [] - - for field_type in field_types: - try: - adapter = TypeAdapter(field_type) - adapter.validate_python(value) - matched_count += 1 - except ValidationError as e: - validation_errors.append(str(e)) - continue - - if matched_count == 0: - raise ValueError(f"Value does not match any of the oneOf schemas") - elif matched_count > 1: - raise ValueError(f"Value matches multiple oneOf schemas, exactly one expected") - - return value - - validated_type = Annotated[union_type, BeforeValidator(validate_one_of)] - return validated_type, mapped_properties diff --git a/jambo/parser/string_type_parser.py b/jambo/parser/string_type_parser.py index 9f43034..7e94b0d 100644 --- a/jambo/parser/string_type_parser.py +++ b/jambo/parser/string_type_parser.py @@ -16,6 +16,7 @@ class StringTypeParser(GenericTypeParser): "maxLength": "max_length", "minLength": "min_length", "pattern": "pattern", + "format": "format", } format_type_mapping = { @@ -37,9 +38,7 @@ class StringTypeParser(GenericTypeParser): def from_properties_impl( self, name, properties, **kwargs: Unpack[TypeParserOptions] ): - mapped_properties = self.mappings_properties_builder( - properties, **kwargs - ) + mapped_properties = self.mappings_properties_builder(properties, **kwargs) format_type = properties.get("format") if not format_type: @@ -52,8 +51,4 @@ class StringTypeParser(GenericTypeParser): if format_type in self.format_pattern_mapping: mapped_properties["pattern"] = self.format_pattern_mapping[format_type] - if "json_schema_extra" not in mapped_properties: - mapped_properties["json_schema_extra"] = {} - mapped_properties["json_schema_extra"]["format"] = format_type - return mapped_type, mapped_properties diff --git a/tests/parser/test_oneof_type_parser.py b/tests/parser/test_oneof_type_parser.py deleted file mode 100644 index 8c75f04..0000000 --- a/tests/parser/test_oneof_type_parser.py +++ /dev/null @@ -1,493 +0,0 @@ -from jambo import SchemaConverter - -from unittest import TestCase - - -class TestOneOfTypeParser(TestCase): - def test_oneof_basic_integer_and_string(self): - schema = { - "title": "Person", - "description": "A person with an ID that can be either an integer or a formatted string", - "type": "object", - "properties": { - "id": { - "oneOf": [ - {"type": "integer", "minimum": 1}, - {"type": "string", "pattern": "^[A-Z]{2}[0-9]{4}$"}, - ] - }, - }, - "required": ["id"], - } - - Model = SchemaConverter.build(schema) - - obj1 = Model(id=123) - self.assertEqual(obj1.id, 123) - - obj2 = Model(id="AB1234") - self.assertEqual(obj2.id, "AB1234") - - def test_oneof_validation_failures(self): - schema = { - "title": "Person", - "type": "object", - "properties": { - "id": { - "oneOf": [ - {"type": "integer", "minimum": 1}, - {"type": "string", "pattern": "^[A-Z]{2}[0-9]{4}$"}, - ] - }, - }, - "required": ["id"], - } - - Model = SchemaConverter.build(schema) - - with self.assertRaises(ValueError): - Model(id=-5) - - with self.assertRaises(ValueError): - Model(id="invalid") - - with self.assertRaises(ValueError): - Model(id=123.45) - - def test_oneof_with_conflicting_schemas(self): - schema = { - "title": "Value", - "type": "object", - "properties": { - "data": { - "oneOf": [ - {"type": "number", "multipleOf": 2}, - {"type": "number", "multipleOf": 3}, - ] - }, - }, - "required": ["data"], - } - - Model = SchemaConverter.build(schema) - - obj1 = Model(data=4) - self.assertEqual(obj1.data, 4) - - obj2 = Model(data=9) - self.assertEqual(obj2.data, 9) - - with self.assertRaises(ValueError) as cm: - Model(data=6) - self.assertIn("matches multiple oneOf schemas", str(cm.exception)) - - with self.assertRaises(ValueError): - Model(data=5) - - def test_oneof_with_objects(self): - schema = { - "title": "Contact", - "type": "object", - "properties": { - "contact_info": { - "oneOf": [ - { - "type": "object", - "properties": { - "email": {"type": "string", "format": "email"} - }, - "required": ["email"], - "additionalProperties": False - }, - { - "type": "object", - "properties": { - "phone": {"type": "string", "pattern": "^[0-9-]+$"} - }, - "required": ["phone"], - "additionalProperties": False - } - ] - }, - }, - "required": ["contact_info"], - } - - Model = SchemaConverter.build(schema) - - obj1 = Model(contact_info={"email": "user@example.com"}) - self.assertEqual(obj1.contact_info.email, "user@example.com") - - obj2 = Model(contact_info={"phone": "123-456-7890"}) - self.assertEqual(obj2.contact_info.phone, "123-456-7890") - - with self.assertRaises(ValueError): - Model(contact_info={"email": "user@example.com", "phone": "123-456-7890"}) - - def test_oneof_with_discriminator_basic(self): - schema = { - "title": "Pet", - "type": "object", - "properties": { - "pet": { - "oneOf": [ - { - "type": "object", - "properties": { - "type": {"const": "cat"}, - "meows": {"type": "boolean"} - }, - "required": ["type", "meows"] - }, - { - "type": "object", - "properties": { - "type": {"const": "dog"}, - "barks": {"type": "boolean"} - }, - "required": ["type", "barks"] - } - ], - "discriminator": { - "propertyName": "type" - } - } - }, - "required": ["pet"] - } - - Model = SchemaConverter.build(schema) - - cat = Model(pet={"type": "cat", "meows": True}) - self.assertEqual(cat.pet.type, "cat") - self.assertEqual(cat.pet.meows, True) - - dog = Model(pet={"type": "dog", "barks": False}) - self.assertEqual(dog.pet.type, "dog") - self.assertEqual(dog.pet.barks, False) - - with self.assertRaises(ValueError): - Model(pet={"type": "cat", "barks": True}) - - with self.assertRaises(ValueError): - Model(pet={"type": "bird", "flies": True}) - - def test_oneof_with_discriminator_mapping(self): - schema = { - "title": "Vehicle", - "type": "object", - "properties": { - "vehicle": { - "oneOf": [ - { - "type": "object", - "properties": { - "vehicle_type": {"const": "car"}, - "doors": {"type": "integer", "minimum": 2, "maximum": 4} - }, - "required": ["vehicle_type", "doors"] - }, - { - "type": "object", - "properties": { - "vehicle_type": {"const": "motorcycle"}, - "engine_size": {"type": "number", "minimum": 125} - }, - "required": ["vehicle_type", "engine_size"] - } - ], - "discriminator": { - "propertyName": "vehicle_type", - "mapping": { - "car": "#/properties/vehicle/oneOf/0", - "motorcycle": "#/properties/vehicle/oneOf/1" - } - } - } - }, - "required": ["vehicle"] - } - - Model = SchemaConverter.build(schema) - - car = Model(vehicle={"vehicle_type": "car", "doors": 4}) - self.assertEqual(car.vehicle.vehicle_type, "car") - self.assertEqual(car.vehicle.doors, 4) - - motorcycle = Model(vehicle={"vehicle_type": "motorcycle", "engine_size": 600.0}) - self.assertEqual(motorcycle.vehicle.vehicle_type, "motorcycle") - self.assertEqual(motorcycle.vehicle.engine_size, 600.0) - - def test_oneof_with_discriminator_invalid_values(self): - schema = { - "title": "Shape", - "type": "object", - "properties": { - "shape": { - "oneOf": [ - { - "type": "object", - "properties": { - "type": {"const": "circle"}, - "radius": {"type": "number", "minimum": 0} - }, - "required": ["type", "radius"] - }, - { - "type": "object", - "properties": { - "type": {"const": "square"}, - "side": {"type": "number", "minimum": 0} - }, - "required": ["type", "side"] - } - ], - "discriminator": { - "propertyName": "type" - } - } - }, - "required": ["shape"] - } - - Model = SchemaConverter.build(schema) - - with self.assertRaises(ValueError): - Model(shape={"type": "triangle", "base": 5, "height": 3}) - - with self.assertRaises(ValueError): - Model(shape={"type": "circle", "side": 5}) - - with self.assertRaises(ValueError): - Model(shape={"radius": 5}) - - def test_oneof_missing_properties(self): - schema = { - "title": "Test", - "type": "object", - "properties": { - "value": { - "notOneOf": [ - {"type": "string"}, - {"type": "integer"}, - ] - }, - }, - } - - with self.assertRaises(ValueError): - SchemaConverter.build(schema) - - def test_oneof_invalid_properties(self): - schema = { - "title": "Test", - "type": "object", - "properties": { - "value": { - "oneOf": None - }, - }, - } - - with self.assertRaises(ValueError): - SchemaConverter.build(schema) - - def test_oneof_with_default_value(self): - schema = { - "title": "Test", - "type": "object", - "properties": { - "value": { - "oneOf": [ - {"type": "string"}, - {"type": "integer"}, - ], - "default": "test" - }, - }, - } - - Model = SchemaConverter.build(schema) - obj = Model() - self.assertEqual(obj.value, "test") - - def test_oneof_with_invalid_default_value(self): - schema = { - "title": "Test", - "type": "object", - "properties": { - "value": { - "oneOf": [ - {"type": "string", "minLength": 5}, - {"type": "integer", "minimum": 10}, - ], - "default": "hi" - }, - }, - } - - with self.assertRaises(ValueError): - SchemaConverter.build(schema) - - def test_oneof_discriminator_without_property_name(self): - schema = { - "title": "Test", - "type": "object", - "properties": { - "value": { - "oneOf": [ - { - "type": "object", - "properties": { - "type": {"const": "a"}, - "value": {"type": "string"} - } - }, - { - "type": "object", - "properties": { - "type": {"const": "b"}, - "value": {"type": "integer"} - } - } - ], - "discriminator": {} # discriminator without propertyName - } - } - } - - Model = SchemaConverter.build(schema) - - # Should succeed because input matches exactly one schema (the first one) - # The first schema matches: type="a" matches const("a"), value="test" is a string - # The second schema doesn't match: type="a" does not match const("b") - obj = Model(value={"type": "a", "value": "test", "extra": "invalid"}) - self.assertEqual(obj.value.type, "a") - self.assertEqual(obj.value.value, "test") - - # Test with input that matches the second schema - obj2 = Model(value={"type": "b", "value": 42}) - self.assertEqual(obj2.value.type, "b") - self.assertEqual(obj2.value.value, 42) - - # Test with input that matches neither schema (should fail) - with self.assertRaises(ValueError) as cm: - Model(value={"type": "c", "value": "test"}) - self.assertIn("does not match any of the oneOf schemas", str(cm.exception)) - - def test_oneof_multiple_matches_without_discriminator(self): - """Test case where input genuinely matches multiple oneOf schemas""" - schema = { - "title": "Test", - "type": "object", - "properties": { - "value": { - "oneOf": [ - { - "type": "object", - "properties": { - "data": {"type": "string"} - } - }, - { - "type": "object", - "properties": { - "data": {"type": "string"}, - "optional": {"type": "string"} - } - } - ], - "discriminator": {} # discriminator without propertyName - } - } - } - - Model = SchemaConverter.build(schema) - - # This input matches both schemas since both accept data as string - # and neither requires specific additional properties - with self.assertRaises(ValueError) as cm: - Model(value={"data": "test"}) - self.assertIn("matches multiple oneOf schemas", str(cm.exception)) - - def test_oneof_overlapping_strings_from_docs(self): - """Test the overlapping strings example from documentation""" - schema = { - "title": "SimpleExample", - "type": "object", - "properties": { - "value": { - "oneOf": [ - {"type": "string", "maxLength": 6}, - {"type": "string", "minLength": 4} - ] - } - }, - "required": ["value"] - } - - Model = SchemaConverter.build(schema) - - # Valid: Short string (matches first schema only) - obj1 = Model(value="hi") - self.assertEqual(obj1.value, "hi") - - # Valid: Long string (matches second schema only) - obj2 = Model(value="very long string") - self.assertEqual(obj2.value, "very long string") - - # Invalid: Medium string (matches BOTH schemas - violates oneOf) - with self.assertRaises(ValueError) as cm: - Model(value="hello") # 5 chars: matches maxLength=6 AND minLength=4 - self.assertIn("matches multiple oneOf schemas", str(cm.exception)) - - def test_oneof_shapes_discriminator_from_docs(self): - """Test the shapes discriminator example from documentation""" - schema = { - "title": "Shape", - "type": "object", - "properties": { - "shape": { - "oneOf": [ - { - "type": "object", - "properties": { - "type": {"const": "circle"}, - "radius": {"type": "number", "minimum": 0} - }, - "required": ["type", "radius"] - }, - { - "type": "object", - "properties": { - "type": {"const": "rectangle"}, - "width": {"type": "number", "minimum": 0}, - "height": {"type": "number", "minimum": 0} - }, - "required": ["type", "width", "height"] - } - ], - "discriminator": { - "propertyName": "type" - } - } - }, - "required": ["shape"] - } - - Model = SchemaConverter.build(schema) - - # Valid: Circle - circle = Model(shape={"type": "circle", "radius": 5.0}) - self.assertEqual(circle.shape.type, "circle") - self.assertEqual(circle.shape.radius, 5.0) - - # Valid: Rectangle - rectangle = Model(shape={"type": "rectangle", "width": 10, "height": 20}) - self.assertEqual(rectangle.shape.type, "rectangle") - self.assertEqual(rectangle.shape.width, 10) - self.assertEqual(rectangle.shape.height, 20) - - # Invalid: Wrong properties for the type - with self.assertRaises(ValueError): - Model(shape={"type": "circle", "width": 10}) -- 2.49.1 From 6dad6e0c68ea026f6ae31751f22ad8742215dd1d Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Tue, 19 Aug 2025 18:58:33 -0300 Subject: [PATCH 20/96] (feat): Adds Aditional Test for Non-Hashable Const Values --- tests/test_schema_converter.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/test_schema_converter.py b/tests/test_schema_converter.py index c5a7c3a..9a756c7 100644 --- a/tests/test_schema_converter.py +++ b/tests/test_schema_converter.py @@ -701,6 +701,29 @@ class TestSchemaConverter(TestCase): with self.assertRaises(ValueError): Model(name="Canada") + def test_const_type_parser_with_non_hashable_value(self): + schema = { + "title": "Country", + "type": "object", + "properties": { + "name": { + "const": ["Brazil"], + } + }, + "required": ["name"], + } + + Model = SchemaConverter.build(schema) + + obj = Model() + self.assertEqual(obj.name, ["Brazil"]) + + with self.assertRaises(ValueError): + obj.name = ["Argentina"] + + with self.assertRaises(ValueError): + Model(name=["Argentina"]) + def test_null_type_parser(self): schema = { "title": "Test", -- 2.49.1 From 86894fa918f8f603c37b5e897584d849d74d80ae Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Tue, 19 Aug 2025 20:08:34 -0300 Subject: [PATCH 21/96] (feature): Fix OneOf behavior on invalid discriminator According to the spec, propertyName is required when using a discriminator. If it is missing, the schema is invalid and should throw. --- jambo/parser/oneof_type_parser.py | 58 ++++++--- tests/parser/test_oneof_type_parser.py | 169 +++++++++---------------- 2 files changed, 99 insertions(+), 128 deletions(-) diff --git a/jambo/parser/oneof_type_parser.py b/jambo/parser/oneof_type_parser.py index c0eeecc..f0a1c88 100644 --- a/jambo/parser/oneof_type_parser.py +++ b/jambo/parser/oneof_type_parser.py @@ -31,33 +31,50 @@ class OneOfTypeParser(GenericTypeParser): if not kwargs.get("required", False): mapped_properties["default"] = mapped_properties.get("default") - field_types = [ - Annotated[t, Field(**v)] if self._has_meaningful_constraints(v) else t - for t, v in sub_types - ] - - union_type = Union[(*field_types,)] + subfield_types = [Annotated[t, Field(**v)] for t, v in sub_types] + # Added with the understanding of discriminator are not in the JsonSchema Spec, + # they were added by OpenAI and not all implementations may support them, + # and they do not always generate a model one-to-one to the Pydantic model + # TL;DR: Discriminators were added by OpenAI and not a Official JSON Schema feature discriminator = properties.get("discriminator") - if discriminator and isinstance(discriminator, dict): - property_name = discriminator.get("propertyName") - if property_name: - validated_type = Annotated[ - union_type, Field(discriminator=property_name) - ] - return validated_type, mapped_properties + if discriminator is not None: + validated_type = self._build_type_one_of_with_discriminator( + subfield_types, discriminator + ) + else: + validated_type = self._build_type_one_of_with_func(subfield_types) + + return validated_type, mapped_properties + + @staticmethod + def _build_type_one_of_with_discriminator( + subfield_types: list[Annotated], discriminator_prop: dict + ) -> Annotated: + if not isinstance(discriminator_prop, dict): + raise ValueError("Discriminator must be a dictionary") + + property_name = discriminator_prop.get("propertyName") + if property_name is None or not isinstance(property_name, str): + raise ValueError("Discriminator must have a 'propertyName' key") + + return Annotated[Union[(*subfield_types,)], Field(discriminator=property_name)] + + @staticmethod + def _build_type_one_of_with_func(subfield_types: list[Annotated]) -> Annotated: + """ + Build a validation function for the oneOf constraint. + This function will validate that the value matches exactly one of the schemas. + """ def validate_one_of(value: Any) -> Any: matched_count = 0 - validation_errors = [] - for field_type in field_types: + for field_type in subfield_types: try: - adapter = TypeAdapter(field_type) - adapter.validate_python(value) + TypeAdapter(field_type).validate_python(value) matched_count += 1 - except ValidationError as e: - validation_errors.append(str(e)) + except ValidationError: continue if matched_count == 0: @@ -69,8 +86,7 @@ class OneOfTypeParser(GenericTypeParser): return value - validated_type = Annotated[union_type, BeforeValidator(validate_one_of)] - return validated_type, mapped_properties + return Annotated[Union[(*subfield_types,)], BeforeValidator(validate_one_of)] @staticmethod def _has_meaningful_constraints(field_props): diff --git a/tests/parser/test_oneof_type_parser.py b/tests/parser/test_oneof_type_parser.py index fbe362c..80c2096 100644 --- a/tests/parser/test_oneof_type_parser.py +++ b/tests/parser/test_oneof_type_parser.py @@ -354,131 +354,86 @@ class TestOneOfTypeParser(TestCase): }, } - Model = SchemaConverter.build(schema) + # Should throw because the spec determines propertyName is required for discriminator + with self.assertRaises(ValueError): + SchemaConverter.build(schema) - # Should succeed because input matches exactly one schema (the first one) - # The first schema matches: type="a" matches const("a"), value="test" is a string - # The second schema doesn't match: type="a" does not match const("b") - obj = Model(value={"type": "a", "value": "test", "extra": "invalid"}) - self.assertEqual(obj.value.type, "a") - self.assertEqual(obj.value.value, "test") - - # Test with input that matches the second schema - obj2 = Model(value={"type": "b", "value": 42}) - self.assertEqual(obj2.value.type, "b") - self.assertEqual(obj2.value.value, 42) - - # Test with input that matches neither schema (should fail) - with self.assertRaises(ValueError) as cm: - Model(value={"type": "c", "value": "test"}) - self.assertIn("does not match any of the oneOf schemas", str(cm.exception)) - - def test_oneof_multiple_matches_without_discriminator(self): - """Test case where input genuinely matches multiple oneOf schemas""" + def test_oneof_overlapping_strings_from_docs(self): + """Test the overlapping strings example from documentation""" schema = { - "title": "Test", + "title": "SimpleExample", "type": "object", "properties": { "value": { "oneOf": [ - {"type": "object", "properties": {"data": {"type": "string"}}}, - { - "type": "object", - "properties": { - "data": {"type": "string"}, - "optional": {"type": "string"}, - }, - }, - ], - "discriminator": {}, # discriminator without propertyName + {"type": "string", "maxLength": 6}, + {"type": "string", "minLength": 4}, + ] } }, + "required": ["value"], } Model = SchemaConverter.build(schema) - # This input matches both schemas since both accept data as string - # and neither requires specific additional properties + # Valid: Short string (matches first schema only) + obj1 = Model(value="hi") + self.assertEqual(obj1.value, "hi") + + # Valid: Long string (matches second schema only) + obj2 = Model(value="very long string") + self.assertEqual(obj2.value, "very long string") + + # Invalid: Medium string (matches BOTH schemas - violates oneOf) with self.assertRaises(ValueError) as cm: - Model(value={"data": "test"}) + Model(value="hello") # 5 chars: matches maxLength=6 AND minLength=4 self.assertIn("matches multiple oneOf schemas", str(cm.exception)) - def test_oneof_overlapping_strings_from_docs(self): - """Test the overlapping strings example from documentation""" - schema = { - "title": "SimpleExample", - "type": "object", - "properties": { - "value": { - "oneOf": [ - {"type": "string", "maxLength": 6}, - {"type": "string", "minLength": 4}, - ] - } - }, - "required": ["value"], - } - - Model = SchemaConverter.build(schema) - - # Valid: Short string (matches first schema only) - obj1 = Model(value="hi") - self.assertEqual(obj1.value, "hi") - - # Valid: Long string (matches second schema only) - obj2 = Model(value="very long string") - self.assertEqual(obj2.value, "very long string") - - # Invalid: Medium string (matches BOTH schemas - violates oneOf) - with self.assertRaises(ValueError) as cm: - Model(value="hello") # 5 chars: matches maxLength=6 AND minLength=4 - self.assertIn("matches multiple oneOf schemas", str(cm.exception)) - - def test_oneof_shapes_discriminator_from_docs(self): - """Test the shapes discriminator example from documentation""" - schema = { - "title": "Shape", - "type": "object", - "properties": { - "shape": { - "oneOf": [ - { - "type": "object", - "properties": { - "type": {"const": "circle"}, - "radius": {"type": "number", "minimum": 0}, - }, - "required": ["type", "radius"], + def test_oneof_shapes_discriminator_from_docs(self): + """Test the shapes discriminator example from documentation""" + schema = { + "title": "Shape", + "type": "object", + "properties": { + "shape": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": {"const": "circle"}, + "radius": {"type": "number", "minimum": 0}, }, - { - "type": "object", - "properties": { - "type": {"const": "rectangle"}, - "width": {"type": "number", "minimum": 0}, - "height": {"type": "number", "minimum": 0}, - }, - "required": ["type", "width", "height"], + "required": ["type", "radius"], + }, + { + "type": "object", + "properties": { + "type": {"const": "rectangle"}, + "width": {"type": "number", "minimum": 0}, + "height": {"type": "number", "minimum": 0}, }, - ], - "discriminator": {"propertyName": "type"}, - } - }, - "required": ["shape"], - } + "required": ["type", "width", "height"], + }, + ], + "discriminator": {"propertyName": "type"}, + } + }, + "required": ["shape"], + } - Model = SchemaConverter.build(schema) + Model = SchemaConverter.build(schema) - # Valid: Circle - circle = Model(shape={"type": "circle", "radius": 5.0}) - self.assertEqual(circle.shape.type, "circle") - self.assertEqual(circle.shape.radius, 5.0) + # Valid: Circle + circle = Model(shape={"type": "circle", "radius": 5.0}) + self.assertEqual(circle.shape.type, "circle") + self.assertEqual(circle.shape.radius, 5.0) - # Valid: Rectangle - rectangle = Model(shape={"type": "rectangle", "width": 10, "height": 20}) - self.assertEqual(rectangle.shape.type, "rectangle") - self.assertEqual(rectangle.shape.width, 10) - self.assertEqual(rectangle.shape.height, 20) + # Valid: Rectangle + rectangle = Model(shape={"type": "rectangle", "width": 10, "height": 20}) + self.assertEqual(rectangle.shape.type, "rectangle") + self.assertEqual(rectangle.shape.width, 10) + self.assertEqual(rectangle.shape.height, 20) - # Invalid: Wrong properties for the type - with self.assertRaises(ValueError): - Model(shape={"type": "circle", "width": 10}) + # Invalid: Wrong properties for the type + with self.assertRaises(ValueError): + Model(shape={"type": "circle", "width": 10}) -- 2.49.1 From 79932bb5952df4e9404e45d4d2a2c62621fbfa4c Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Tue, 19 Aug 2025 20:29:25 -0300 Subject: [PATCH 22/96] (feature): Removes _has_meaningful_constraints Removes _has_meaningful_constraints since nowhere in the spec says that a subproperty should have a meaningful value other that its type --- jambo/parser/oneof_type_parser.py | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/jambo/parser/oneof_type_parser.py b/jambo/parser/oneof_type_parser.py index f0a1c88..12b62fe 100644 --- a/jambo/parser/oneof_type_parser.py +++ b/jambo/parser/oneof_type_parser.py @@ -51,6 +51,9 @@ class OneOfTypeParser(GenericTypeParser): def _build_type_one_of_with_discriminator( subfield_types: list[Annotated], discriminator_prop: dict ) -> Annotated: + """ + Build a type with a discriminator. + """ if not isinstance(discriminator_prop, dict): raise ValueError("Discriminator must be a dictionary") @@ -63,8 +66,7 @@ class OneOfTypeParser(GenericTypeParser): @staticmethod def _build_type_one_of_with_func(subfield_types: list[Annotated]) -> Annotated: """ - Build a validation function for the oneOf constraint. - This function will validate that the value matches exactly one of the schemas. + Build a type with a validation function for the oneOf constraint. """ def validate_one_of(value: Any) -> Any: @@ -87,24 +89,3 @@ class OneOfTypeParser(GenericTypeParser): return value return Annotated[Union[(*subfield_types,)], BeforeValidator(validate_one_of)] - - @staticmethod - def _has_meaningful_constraints(field_props): - """ - Check if field properties contain meaningful constraints that require Field wrapping. - Returns False if: - - field_props is None or empty - - field_props only contains {'default': None} - Returns True if: - - field_props contains a non-None default value - - field_props contains other constraint properties (min_length, max_length, pattern, etc.) - """ - if not field_props: - return False - - # If only default is set and it's None, no meaningful constraints - if field_props == {"default": None}: - return False - - # If there are multiple properties or non-None default, that's meaningful - return True -- 2.49.1 From 15944549a0c212efefb455b7847c89310281397e Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Tue, 19 Aug 2025 20:40:49 -0300 Subject: [PATCH 23/96] (feat): Adds Aditional Tests --- tests/parser/test_oneof_type_parser.py | 111 +++++++++++++++++++------ 1 file changed, 84 insertions(+), 27 deletions(-) diff --git a/tests/parser/test_oneof_type_parser.py b/tests/parser/test_oneof_type_parser.py index 80c2096..b559a0a 100644 --- a/tests/parser/test_oneof_type_parser.py +++ b/tests/parser/test_oneof_type_parser.py @@ -1,9 +1,35 @@ from jambo import SchemaConverter +from jambo.parser.oneof_type_parser import OneOfTypeParser from unittest import TestCase class TestOneOfTypeParser(TestCase): + def test_oneof_raises_on_invalid_property(self): + with self.assertRaises(ValueError): + OneOfTypeParser().from_properties_impl( + "test_field", + { + # Invalid schema, should have property "oneOf" + }, + required=True, + context={}, + ref_cache={}, + ) + + with self.assertRaises(ValueError): + SchemaConverter.build( + { + "title": "Test", + "type": "object", + "properties": { + "value": { + "oneOf": [], # should throw because oneOf requires at least one schema + } + }, + } + ) + def test_oneof_basic_integer_and_string(self): schema = { "title": "Person", @@ -328,35 +354,66 @@ class TestOneOfTypeParser(TestCase): SchemaConverter.build(schema) def test_oneof_discriminator_without_property_name(self): - schema = { - "title": "Test", - "type": "object", - "properties": { - "value": { - "oneOf": [ - { - "type": "object", - "properties": { - "type": {"const": "a"}, - "value": {"type": "string"}, - }, - }, - { - "type": "object", - "properties": { - "type": {"const": "b"}, - "value": {"type": "integer"}, - }, - }, - ], - "discriminator": {}, # discriminator without propertyName - } - }, - } - # Should throw because the spec determines propertyName is required for discriminator with self.assertRaises(ValueError): - SchemaConverter.build(schema) + SchemaConverter.build( + { + "title": "Test", + "type": "object", + "properties": { + "value": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": {"const": "a"}, + "value": {"type": "string"}, + }, + }, + { + "type": "object", + "properties": { + "type": {"const": "b"}, + "value": {"type": "integer"}, + }, + }, + ], + "discriminator": {}, # discriminator without propertyName + } + }, + } + ) + + def test_oneof_discriminator_with_invalid_discriminator(self): + # Should throw because a valid discriminator is required + with self.assertRaises(ValueError): + SchemaConverter.build( + { + "title": "Test", + "type": "object", + "properties": { + "value": { + "oneOf": [ + { + "type": "object", + "properties": { + "type": {"const": "a"}, + "value": {"type": "string"}, + }, + }, + { + "type": "object", + "properties": { + "type": {"const": "b"}, + "value": {"type": "integer"}, + }, + }, + ], + "discriminator": "invalid", # discriminator without propertyName + } + }, + } + ) def test_oneof_overlapping_strings_from_docs(self): """Test the overlapping strings example from documentation""" -- 2.49.1 From c5e70402dbb77d231d2c9302f516aebf602c20f5 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Tue, 19 Aug 2025 20:44:16 -0300 Subject: [PATCH 24/96] (feat): Adds Warning to Docs About Discriminator Keyword --- docs/source/usage.oneof.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/source/usage.oneof.rst b/docs/source/usage.oneof.rst index a836df4..6e875a4 100644 --- a/docs/source/usage.oneof.rst +++ b/docs/source/usage.oneof.rst @@ -106,3 +106,7 @@ Examples .. warning:: If your data could match multiple schemas in a oneOf, validation will fail. Ensure schemas are mutually exclusive. + +.. warning:: + + The discriminator feature is not officially in the JSON Schema specification, it was introduced by OpenAI. Use it with caution and ensure it fits your use case. -- 2.49.1 From 1e5b686c23c45290971c28c87c04337eda6c2773 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Tue, 19 Aug 2025 20:47:58 -0300 Subject: [PATCH 25/96] (project): Fixes Readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 28f1c6d..56a5962 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ Created to simplifying the process of dynamically generating Pydantic models for - nested objects - allOf - anyOf + - oneOf - ref - enum - const -- 2.49.1 From 7e11c817a70ee7c650559c731dab56caa13858bf Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Tue, 19 Aug 2025 22:25:36 -0300 Subject: [PATCH 26/96] (fix): Adds check for discriminator type --- jambo/parser/oneof_type_parser.py | 16 +++++++++++++--- tests/parser/test_oneof_type_parser.py | 23 +++++++++++++++++++++++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/jambo/parser/oneof_type_parser.py b/jambo/parser/oneof_type_parser.py index 12b62fe..b9393fe 100644 --- a/jambo/parser/oneof_type_parser.py +++ b/jambo/parser/oneof_type_parser.py @@ -1,8 +1,8 @@ from jambo.parser._type_parser import GenericTypeParser from jambo.types.type_parser_options import TypeParserOptions -from pydantic import BeforeValidator, Field, TypeAdapter, ValidationError -from typing_extensions import Annotated, Any, Union, Unpack +from pydantic import BaseModel, BeforeValidator, Field, TypeAdapter, ValidationError +from typing_extensions import Annotated, Any, Union, Unpack, get_args class OneOfTypeParser(GenericTypeParser): @@ -16,7 +16,7 @@ class OneOfTypeParser(GenericTypeParser): if "oneOf" not in properties: raise ValueError(f"Invalid JSON Schema: {properties}") - if not isinstance(properties["oneOf"], list): + if not isinstance(properties["oneOf"], list) or len(properties["oneOf"]) == 0: raise ValueError(f"Invalid JSON Schema: {properties['oneOf']}") mapped_properties = self.mappings_properties_builder(properties, **kwargs) @@ -57,6 +57,16 @@ class OneOfTypeParser(GenericTypeParser): if not isinstance(discriminator_prop, dict): raise ValueError("Discriminator must be a dictionary") + for field in subfield_types: + field_type, field_info = get_args(field) + + if issubclass(field_type, BaseModel): + continue + + raise ValueError( + "When using a discriminator, all subfield types must be of type 'object'." + ) + property_name = discriminator_prop.get("propertyName") if property_name is None or not isinstance(property_name, str): raise ValueError("Discriminator must have a 'propertyName' key") diff --git a/tests/parser/test_oneof_type_parser.py b/tests/parser/test_oneof_type_parser.py index b559a0a..69104ae 100644 --- a/tests/parser/test_oneof_type_parser.py +++ b/tests/parser/test_oneof_type_parser.py @@ -196,6 +196,29 @@ class TestOneOfTypeParser(TestCase): with self.assertRaises(ValueError): Model(pet={"type": "bird", "flies": True}) + def test_oneof_with_invalid_types(self): + with self.assertRaises(ValueError): + SchemaConverter.build( + { + "title": "Pet", + "type": "object", + "properties": { + "pet": { + "oneOf": [ + { + "type": "number", + }, + { + "type": "string", + }, + ], + "discriminator": {"propertyName": "type"}, + } + }, + "required": ["pet"], + } + ) + def test_oneof_with_discriminator_mapping(self): schema = { "title": "Vehicle", -- 2.49.1 From 8761ee5ef6d3392fedbdb55336a5081df4c4e429 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Wed, 20 Aug 2025 00:00:03 -0300 Subject: [PATCH 27/96] (fix): Fixes docs --- docs/source/usage.oneof.rst | 2 +- jambo/parser/oneof_type_parser.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/usage.oneof.rst b/docs/source/usage.oneof.rst index 6e875a4..af625e5 100644 --- a/docs/source/usage.oneof.rst +++ b/docs/source/usage.oneof.rst @@ -109,4 +109,4 @@ Examples .. warning:: - The discriminator feature is not officially in the JSON Schema specification, it was introduced by OpenAI. Use it with caution and ensure it fits your use case. + The discriminator feature is not officially in the JSON Schema specification, it was introduced by OpenAPI. Use it with caution and ensure it fits your use case. diff --git a/jambo/parser/oneof_type_parser.py b/jambo/parser/oneof_type_parser.py index b9393fe..707d277 100644 --- a/jambo/parser/oneof_type_parser.py +++ b/jambo/parser/oneof_type_parser.py @@ -34,9 +34,9 @@ class OneOfTypeParser(GenericTypeParser): subfield_types = [Annotated[t, Field(**v)] for t, v in sub_types] # Added with the understanding of discriminator are not in the JsonSchema Spec, - # they were added by OpenAI and not all implementations may support them, + # they were added by OpenAPI and not all implementations may support them, # and they do not always generate a model one-to-one to the Pydantic model - # TL;DR: Discriminators were added by OpenAI and not a Official JSON Schema feature + # TL;DR: Discriminators were added by OpenAPI and not a Official JSON Schema feature discriminator = properties.get("discriminator") if discriminator is not None: validated_type = self._build_type_one_of_with_discriminator( -- 2.49.1 From d3a2f1e76c8ddba7409177ee8ee188514aebfb2e Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Wed, 20 Aug 2025 00:25:02 -0300 Subject: [PATCH 28/96] (improvement): Adds More Type Formats to String Parser --- jambo/parser/string_type_parser.py | 21 +++++++++++++++------ tests/parser/test_string_type_parser.py | 11 +++++++---- tests/test_schema_converter.py | 4 ++-- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/jambo/parser/string_type_parser.py b/jambo/parser/string_type_parser.py index 7e94b0d..21e1fd6 100644 --- a/jambo/parser/string_type_parser.py +++ b/jambo/parser/string_type_parser.py @@ -1,10 +1,12 @@ from jambo.parser._type_parser import GenericTypeParser from jambo.types.type_parser_options import TypeParserOptions -from pydantic import EmailStr, HttpUrl, IPvAnyAddress +from pydantic import AnyUrl, EmailStr from typing_extensions import Unpack from datetime import date, datetime, time, timedelta +from ipaddress import IPv4Address, IPv6Address +from uuid import UUID class StringTypeParser(GenericTypeParser): @@ -20,15 +22,22 @@ class StringTypeParser(GenericTypeParser): } format_type_mapping = { - "email": EmailStr, - "uri": HttpUrl, - "ipv4": IPvAnyAddress, - "ipv6": IPvAnyAddress, - "hostname": str, + # 7.3.1. Dates, Times, and Duration "date": date, "time": time, "date-time": datetime, "duration": timedelta, + # 7.3.2. Email Addresses + "email": EmailStr, + # 7.3.3. Hostnames + "hostname": str, + # 7.3.4. IP Addresses + "ipv4": IPv4Address, + "ipv6": IPv6Address, + # 7.3.5. Resource Identifiers + "uri": AnyUrl, + # "iri" # Not supported by pydantic and currently not supported by jambo + "uuid": UUID, } format_pattern_mapping = { diff --git a/tests/parser/test_string_type_parser.py b/tests/parser/test_string_type_parser.py index 51a37e4..e667ad7 100644 --- a/tests/parser/test_string_type_parser.py +++ b/tests/parser/test_string_type_parser.py @@ -1,8 +1,9 @@ from jambo.parser import StringTypeParser -from pydantic import EmailStr, HttpUrl, IPvAnyAddress +from pydantic import AnyUrl, EmailStr from datetime import date, datetime, time, timedelta +from ipaddress import IPv4Address, IPv6Address from unittest import TestCase @@ -111,12 +112,14 @@ class TestStringTypeParser(TestCase): type_parsing, type_validator = parser.from_properties("placeholder", properties) - self.assertEqual(type_parsing, HttpUrl) + self.assertEqual(type_parsing, AnyUrl) def test_string_parser_with_ip_formats(self): parser = StringTypeParser() - for ip_format in ["ipv4", "ipv6"]: + formats = {"ipv4": IPv4Address, "ipv6": IPv6Address} + + for ip_format, expected_type in formats.items(): properties = { "type": "string", "format": ip_format, @@ -126,7 +129,7 @@ class TestStringTypeParser(TestCase): "placeholder", properties ) - self.assertEqual(type_parsing, IPvAnyAddress) + self.assertEqual(type_parsing, expected_type) def test_string_parser_with_time_format(self): parser = StringTypeParser() diff --git a/tests/test_schema_converter.py b/tests/test_schema_converter.py index 9a756c7..d30a5ad 100644 --- a/tests/test_schema_converter.py +++ b/tests/test_schema_converter.py @@ -1,6 +1,6 @@ from jambo import SchemaConverter -from pydantic import BaseModel, HttpUrl +from pydantic import AnyUrl, BaseModel from ipaddress import IPv4Address, IPv6Address from unittest import TestCase @@ -463,7 +463,7 @@ class TestSchemaConverter(TestCase): } model = SchemaConverter.build(schema) self.assertEqual( - model(website="https://example.com").website, HttpUrl("https://example.com") + model(website="https://example.com").website, AnyUrl("https://example.com") ) with self.assertRaises(ValueError): model(website="invalid-uri") -- 2.49.1 From 97aed6e9aacac058843c94561779f0ac90657843 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Wed, 20 Aug 2025 00:30:54 -0300 Subject: [PATCH 29/96] (improvement): Adds tests for UUID String Format --- tests/parser/test_string_type_parser.py | 13 +++++++++++++ tests/test_schema_converter.py | 17 +++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/tests/parser/test_string_type_parser.py b/tests/parser/test_string_type_parser.py index e667ad7..279e20f 100644 --- a/tests/parser/test_string_type_parser.py +++ b/tests/parser/test_string_type_parser.py @@ -5,6 +5,7 @@ from pydantic import AnyUrl, EmailStr from datetime import date, datetime, time, timedelta from ipaddress import IPv4Address, IPv6Address from unittest import TestCase +from uuid import UUID class TestStringTypeParser(TestCase): @@ -131,6 +132,18 @@ class TestStringTypeParser(TestCase): self.assertEqual(type_parsing, expected_type) + def test_string_parser_with_uuid_format(self): + parser = StringTypeParser() + + properties = { + "type": "string", + "format": "uuid", + } + + type_parsing, type_validator = parser.from_properties("placeholder", properties) + + self.assertEqual(type_parsing, UUID) + def test_string_parser_with_time_format(self): parser = StringTypeParser() diff --git a/tests/test_schema_converter.py b/tests/test_schema_converter.py index d30a5ad..cdd294d 100644 --- a/tests/test_schema_converter.py +++ b/tests/test_schema_converter.py @@ -4,6 +4,7 @@ from pydantic import AnyUrl, BaseModel from ipaddress import IPv4Address, IPv6Address from unittest import TestCase +from uuid import UUID def is_pydantic_model(cls): @@ -493,6 +494,22 @@ class TestSchemaConverter(TestCase): with self.assertRaises(ValueError): model(ip="invalid-ipv6") + def test_string_format_uuid(self): + schema = { + "title": "UUIDTest", + "type": "object", + "properties": {"id": {"type": "string", "format": "uuid"}}, + } + model = SchemaConverter.build(schema) + + self.assertEqual( + model(id="123e4567-e89b-12d3-a456-426614174000").id, + UUID("123e4567-e89b-12d3-a456-426614174000"), + ) + + with self.assertRaises(ValueError): + model(id="invalid-uuid") + def test_string_format_hostname(self): schema = { "title": "HostnameTest", -- 2.49.1 From 71380073e46dbe7f1f5bc60314bf2ffdca79285d Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Wed, 20 Aug 2025 01:12:56 -0300 Subject: [PATCH 30/96] (improvement): Formats and Lints Code - Minor Changes --- jambo/parser/__init__.py | 2 +- jambo/parser/const_type_parser.py | 2 +- jambo/parser/enum_type_parser.py | 8 ++------ jambo/types/json_schema_type.py | 2 +- tests/parser/test_const_type_parser.py | 2 +- tests/parser/test_enum_type_parser.py | 2 +- tests/parser/test_type_parser.py | 2 +- 7 files changed, 8 insertions(+), 12 deletions(-) diff --git a/jambo/parser/__init__.py b/jambo/parser/__init__.py index 44b4424..0de9f00 100644 --- a/jambo/parser/__init__.py +++ b/jambo/parser/__init__.py @@ -29,4 +29,4 @@ __all__ = [ "OneOfTypeParser", "StringTypeParser", "RefTypeParser", -] \ No newline at end of file +] diff --git a/jambo/parser/const_type_parser.py b/jambo/parser/const_type_parser.py index 1e4ce84..da55bc0 100644 --- a/jambo/parser/const_type_parser.py +++ b/jambo/parser/const_type_parser.py @@ -48,4 +48,4 @@ class ConstTypeParser(GenericTypeParser): ) return value - return Annotated[type(const_value), AfterValidator(_validate_const_value)] \ No newline at end of file + return Annotated[type(const_value), AfterValidator(_validate_const_value)] diff --git a/jambo/parser/enum_type_parser.py b/jambo/parser/enum_type_parser.py index 5ea9e67..42f4d6f 100644 --- a/jambo/parser/enum_type_parser.py +++ b/jambo/parser/enum_type_parser.py @@ -21,9 +21,7 @@ class EnumTypeParser(GenericTypeParser): if not isinstance(enum_values, list): raise ValueError(f"Enum type {name} must have 'enum' as a list of values.") - if any( - not isinstance(value, JSONSchemaNativeTypes) for value in enum_values - ): + if any(not isinstance(value, JSONSchemaNativeTypes) for value in enum_values): raise ValueError( f"Enum type {name} must have 'enum' values of allowed types: {JSONSchemaNativeTypes}." ) @@ -32,9 +30,7 @@ class EnumTypeParser(GenericTypeParser): enum_type = Enum(name, {str(value).upper(): value for value in enum_values}) 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: parsed_properties["default"] = enum_type(parsed_properties["default"]) return enum_type, parsed_properties diff --git a/jambo/types/json_schema_type.py b/jambo/types/json_schema_type.py index 6f61837..d99d791 100644 --- a/jambo/types/json_schema_type.py +++ b/jambo/types/json_schema_type.py @@ -9,7 +9,7 @@ JSONSchemaType = Literal[ JSONSchemaNativeTypes: tuple[type, ...] = ( - str, + str, int, float, bool, diff --git a/tests/parser/test_const_type_parser.py b/tests/parser/test_const_type_parser.py index 5a8c9c1..bd7dcc5 100644 --- a/tests/parser/test_const_type_parser.py +++ b/tests/parser/test_const_type_parser.py @@ -99,4 +99,4 @@ class TestConstTypeParser(TestCase): self.assertIn( "Const type invalid_country must have 'const' value of allowed types", str(context.exception), - ) \ No newline at end of file + ) diff --git a/tests/parser/test_enum_type_parser.py b/tests/parser/test_enum_type_parser.py index 0f830e0..aad95a4 100644 --- a/tests/parser/test_enum_type_parser.py +++ b/tests/parser/test_enum_type_parser.py @@ -81,7 +81,7 @@ class TestEnumTypeParser(TestCase): def test_enum_type_parser_throws_invalid_enum_value(self): parser = EnumTypeParser() - + schema = { "enum": ["value1", 42, dict()], } diff --git a/tests/parser/test_type_parser.py b/tests/parser/test_type_parser.py index 195297b..4722de8 100644 --- a/tests/parser/test_type_parser.py +++ b/tests/parser/test_type_parser.py @@ -18,4 +18,4 @@ class TestGenericTypeParser(TestCase): def test_get_impl_invalid_type(self): with self.assertRaises(ValueError): - GenericTypeParser._get_impl({"type": "invalid_type"}) \ No newline at end of file + GenericTypeParser._get_impl({"type": "invalid_type"}) -- 2.49.1 From 1167b8a540912f6b9619ffbcb66b239269c8cf31 Mon Sep 17 00:00:00 2001 From: Robby Date: Wed, 10 Sep 2025 13:59:39 -0400 Subject: [PATCH 31/96] feat: Add py.typed marker file for proper typing support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add py.typed marker file to jambo package directory - Enable static type checkers to recognize and use type annotations from the library This allows IDEs and tools like mypy, pyright to properly type-check code using this library. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- jambo/py.typed | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 jambo/py.typed diff --git a/jambo/py.typed b/jambo/py.typed new file mode 100644 index 0000000..e69de29 -- 2.49.1 From 53418f2b2bde6a34146b367bd2543ff9bdc611c7 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Fri, 12 Sep 2025 11:36:11 -0300 Subject: [PATCH 32/96] chore: fixes license in pyproject - no change was made --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 201f92e..9aab917 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ classifiers = [ "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", ] -license = { file = "LICENSE" } +license = "MIT" readme = "README.md" # Project Dependencies -- 2.49.1 From 5eb086bafdfa9a814287e1759e7bd1de48cd972f Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Fri, 12 Sep 2025 23:58:33 -0300 Subject: [PATCH 33/96] Better Internat Static Typing --- jambo/parser/_type_parser.py | 22 ++--- jambo/parser/allof_type_parser.py | 23 ++--- jambo/parser/enum_type_parser.py | 6 +- jambo/parser/object_type_parser.py | 22 +++-- jambo/parser/oneof_type_parser.py | 9 +- jambo/parser/ref_type_parser.py | 39 +++++---- jambo/schema_converter.py | 10 ++- jambo/types/json_schema_type.py | 135 +++++++++++++---------------- jambo/types/type_parser_options.py | 4 +- tests/test_schema_converter.py | 14 +++ 10 files changed, 152 insertions(+), 132 deletions(-) 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..da89d21 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}") @@ -42,6 +42,7 @@ class SchemaConverter: schema.get("required", []), context=schema, ref_cache=dict(), + required=True, ) case "$ref": @@ -50,6 +51,7 @@ class SchemaConverter: schema, context=schema, ref_cache=dict(), + required=True, ) return parsed_model case _: @@ -65,4 +67,8 @@ class SchemaConverter: if "$ref" in schema: return "$ref" - return schema.get("type", "undefined") + schema_type = schema.get("type") + if isinstance(schema_type, str): + return schema_type + + raise ValueError("Schema must have a valid 'type' or '$ref' field.") diff --git a/jambo/types/json_schema_type.py b/jambo/types/json_schema_type.py index d99d791..dcb0951 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": Union[JSONSchemaType, List[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/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", -- 2.49.1 From c1f04606ad1955bb5392ba380977aefbe9bfd264 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Sat, 13 Sep 2025 19:36:53 -0300 Subject: [PATCH 34/96] fix: removes unecessary check --- jambo/schema_converter.py | 10 +++------- jambo/types/json_schema_type.py | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/jambo/schema_converter.py b/jambo/schema_converter.py index da89d21..1624940 100644 --- a/jambo/schema_converter.py +++ b/jambo/schema_converter.py @@ -38,7 +38,7 @@ class SchemaConverter: case "object": return ObjectTypeParser.to_model( schema["title"], - schema["properties"], + schema.get("properties", {}), schema.get("required", []), context=schema, ref_cache=dict(), @@ -58,7 +58,7 @@ class SchemaConverter: 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. @@ -67,8 +67,4 @@ class SchemaConverter: if "$ref" in schema: return "$ref" - schema_type = schema.get("type") - if isinstance(schema_type, str): - return schema_type - - raise ValueError("Schema must have a valid 'type' or '$ref' field.") + return schema.get("type") diff --git a/jambo/types/json_schema_type.py b/jambo/types/json_schema_type.py index dcb0951..954db75 100644 --- a/jambo/types/json_schema_type.py +++ b/jambo/types/json_schema_type.py @@ -42,7 +42,7 @@ JSONSchema = TypedDict( "description": str, "default": JSONType, "examples": List[JSONType], - "type": Union[JSONSchemaType, List[JSONSchemaType]], + "type": JSONSchemaType, "enum": List[JSONType], "const": JSONType, "properties": Dict[str, "JSONSchema"], -- 2.49.1 From e45086e29e5ef793ebfd6b68ac57433dd4ee0910 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Sat, 13 Sep 2025 19:48:17 -0300 Subject: [PATCH 35/96] feat: adds static type check to ci/cd --- .github/workflows/build.yml | 3 ++ pyproject.toml | 5 ++- uv.lock | 79 +++++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 1 deletion(-) 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/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/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" -- 2.49.1 From e61d48881f39d54a2ceba50138b74450ef2c36af Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Sat, 13 Sep 2025 20:43:30 -0300 Subject: [PATCH 36/96] feat: initial implementation of explicit exception types --- jambo/exceptions/__init__.py | 5 ++++ jambo/exceptions/invalid_schema_exception.py | 27 +++++++++++++++++++ .../unsupported_schema_exception.py | 23 ++++++++++++++++ jambo/schema_converter.py | 21 +++++++++++---- jambo/types/__init__.py | 16 +++++++++++ 5 files changed, 87 insertions(+), 5 deletions(-) create mode 100644 jambo/exceptions/__init__.py create mode 100644 jambo/exceptions/invalid_schema_exception.py create mode 100644 jambo/exceptions/unsupported_schema_exception.py diff --git a/jambo/exceptions/__init__.py b/jambo/exceptions/__init__.py new file mode 100644 index 0000000..0292136 --- /dev/null +++ b/jambo/exceptions/__init__.py @@ -0,0 +1,5 @@ +from .invalid_schema_exception import InvalidSchemaException +from .unsupported_schema_exception import UnsupportedSchemaException + + +__all__ = ["InvalidSchemaException", "UnsupportedSchemaException"] diff --git a/jambo/exceptions/invalid_schema_exception.py b/jambo/exceptions/invalid_schema_exception.py new file mode 100644 index 0000000..2d7acff --- /dev/null +++ b/jambo/exceptions/invalid_schema_exception.py @@ -0,0 +1,27 @@ +from typing_extensions import Optional + + +class InvalidSchemaException(ValueError): + """Exception raised for invalid JSON schemas.""" + + def __init__( + self, + message: str, + invalid_field: Optional[str] = None, + cause: Optional[BaseException] = None, + ) -> None: + # Normalize message by stripping redundant prefix if present + message = message.removeprefix("Invalid JSON Schema: ") + self.invalid_field = invalid_field + self.cause = cause + super().__init__(message) + + def __str__(self) -> str: + base_msg = f"Invalid JSON Schema: {super().__str__()}" + if self.invalid_field: + return f"{base_msg} (invalid field: {self.invalid_field})" + if self.cause: + return ( + f"{base_msg} (caused by {self.cause.__class__.__name__}: {self.cause})" + ) + return base_msg diff --git a/jambo/exceptions/unsupported_schema_exception.py b/jambo/exceptions/unsupported_schema_exception.py new file mode 100644 index 0000000..14bf65a --- /dev/null +++ b/jambo/exceptions/unsupported_schema_exception.py @@ -0,0 +1,23 @@ +from typing_extensions import Optional + + +class UnsupportedSchemaException(ValueError): + """Exception raised for unsupported JSON schemas.""" + + def __init__( + self, + message: str, + unsupported_field: Optional[str] = None, + cause: Optional[BaseException] = None, + ) -> None: + # Normalize message by stripping redundant prefix if present + message = message.removeprefix("Unsupported JSON Schema: ") + self.unsupported_field = unsupported_field + self.cause = cause + super().__init__(message) + + def __str__(self) -> str: + base_msg = f"Unsupported JSON Schema: {super().__str__()}" + if self.unsupported_field: + return f"{base_msg} (unsupported field: {self.unsupported_field})" + return base_msg diff --git a/jambo/schema_converter.py b/jambo/schema_converter.py index 1624940..3d38dc7 100644 --- a/jambo/schema_converter.py +++ b/jambo/schema_converter.py @@ -1,5 +1,6 @@ +from jambo.exceptions import InvalidSchemaException, UnsupportedSchemaException from jambo.parser import ObjectTypeParser, RefTypeParser -from jambo.types.json_schema_type import JSONSchema +from jambo.types import JSONSchema from jsonschema.exceptions import SchemaError from jsonschema.validators import validator_for @@ -26,11 +27,15 @@ class SchemaConverter: try: validator = validator_for(schema) validator.check_schema(schema) # type: ignore - except SchemaError as e: - raise ValueError(f"Invalid JSON Schema: {e}") + except SchemaError as err: + raise InvalidSchemaException( + "Validation of JSON Schema failed.", cause=err + ) from err if "title" not in schema: - raise ValueError("JSON Schema must have a title.") + raise InvalidSchemaException( + "Schema must have a title.", invalid_field="title" + ) schema_type = SchemaConverter._get_schema_type(schema) @@ -55,7 +60,13 @@ class SchemaConverter: ) return parsed_model case _: - raise TypeError(f"Unsupported schema type: {schema_type}") + unsupported_type = ( + f"type:{schema_type}" if schema_type else "missing type" + ) + raise UnsupportedSchemaException( + "Only object and $ref schema types are supported.", + unsupported_field=unsupported_type, + ) @staticmethod def _get_schema_type(schema: JSONSchema) -> str | None: diff --git a/jambo/types/__init__.py b/jambo/types/__init__.py index e69de29..d5c88a2 100644 --- a/jambo/types/__init__.py +++ b/jambo/types/__init__.py @@ -0,0 +1,16 @@ +from .json_schema_type import ( + JSONSchema, + JSONSchemaNativeTypes, + JSONSchemaType, + JSONType, +) +from .type_parser_options import TypeParserOptions + + +__all__ = [ + "JSONSchemaType", + "JSONSchemaNativeTypes", + "JSONType", + "JSONSchema", + "TypeParserOptions", +] -- 2.49.1 From f4d84d27491dd84b1fa0c99ac86f407b5cc2850e Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Sat, 13 Sep 2025 21:10:14 -0300 Subject: [PATCH 37/96] feat: better exceptions for GenericTypeParser and AllOfTypeParser --- jambo/parser/_type_parser.py | 9 ++++++--- jambo/parser/allof_type_parser.py | 14 ++++++++++---- tests/test_schema_converter.py | 2 +- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/jambo/parser/_type_parser.py b/jambo/parser/_type_parser.py index cce8042..cb7885e 100644 --- a/jambo/parser/_type_parser.py +++ b/jambo/parser/_type_parser.py @@ -1,3 +1,4 @@ +from jambo.exceptions import InvalidSchemaException from jambo.types.type_parser_options import JSONSchema, TypeParserOptions from pydantic import Field, TypeAdapter @@ -46,8 +47,8 @@ class GenericTypeParser(ABC, Generic[T]): ) if not self._validate_default(parsed_type, parsed_properties): - raise ValueError( - f"Default value {properties.get('default')} is not valid for type {parsed_type.__name__}" + raise InvalidSchemaException( + "Default value is not valid", invalid_field=name ) return parsed_type, parsed_properties @@ -79,7 +80,9 @@ class GenericTypeParser(ABC, Generic[T]): if schema_value is None or schema_value == properties[schema_type]: # type: ignore return subcls - raise ValueError("Unknown type") + raise InvalidSchemaException( + "No suitable type parser found", invalid_field=str(properties) + ) @classmethod def _get_schema_type(cls) -> tuple[str, str | None]: diff --git a/jambo/parser/allof_type_parser.py b/jambo/parser/allof_type_parser.py index 2fb62f9..92c5ff2 100644 --- a/jambo/parser/allof_type_parser.py +++ b/jambo/parser/allof_type_parser.py @@ -1,3 +1,4 @@ +from jambo.exceptions import InvalidSchemaException from jambo.parser._type_parser import GenericTypeParser from jambo.types.json_schema_type import JSONSchema from jambo.types.type_parser_options import TypeParserOptions @@ -33,13 +34,18 @@ class AllOfTypeParser(GenericTypeParser): sub_properties: list[JSONSchema], ) -> type[GenericTypeParser]: if not sub_properties: - raise ValueError("Invalid JSON Schema: 'allOf' is empty.") + raise InvalidSchemaException( + "'allOf' must contain at least one schema", invalid_field="allOf" + ) parsers: set[type[GenericTypeParser]] = set( GenericTypeParser._get_impl(sub_property) for sub_property in sub_properties ) if len(parsers) != 1: - raise ValueError("Invalid JSON Schema: allOf types do not match.") + raise InvalidSchemaException( + "All sub-schemas in 'allOf' must resolve to the same type", + invalid_field="allOf", + ) return parsers.pop() @@ -68,8 +74,8 @@ class AllOfTypeParser(GenericTypeParser): if prop_name == "default": if old_value != new_value: - raise ValueError( - f"Invalid JSON Schema: conflicting defaults for '{prop_name}'" + raise InvalidSchemaException( + f"Conflicting defaults for '{prop_name}'", invalid_field=prop_name ) return old_value diff --git a/tests/test_schema_converter.py b/tests/test_schema_converter.py index 6f831f2..2d4099b 100644 --- a/tests/test_schema_converter.py +++ b/tests/test_schema_converter.py @@ -60,7 +60,7 @@ class TestSchemaConverter(TestCase): "type": "string", } - with self.assertRaises(TypeError): + with self.assertRaises(ValueError): SchemaConverter.build(schema) def test_is_invalid_field(self): -- 2.49.1 From 30290771b193fe0976f92191bbf53018fb457d4b Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Sun, 14 Sep 2025 00:05:21 -0300 Subject: [PATCH 38/96] feat: alters all standart errors and messages for more specific errors --- jambo/parser/anyof_type_parser.py | 10 +++++-- jambo/parser/array_type_parser.py | 15 ++++++++-- jambo/parser/boolean_type_parser.py | 6 +++- jambo/parser/const_type_parser.py | 11 +++++-- jambo/parser/enum_type_parser.py | 16 ++++++++--- jambo/parser/oneof_type_parser.py | 23 +++++++++++---- jambo/parser/ref_type_parser.py | 38 +++++++++++++++---------- jambo/parser/string_type_parser.py | 5 +++- jambo/types/json_schema_type.py | 2 +- tests/parser/test_ref_type_parser.py | 4 +-- tests/parser/test_string_type_parser.py | 3 +- 11 files changed, 94 insertions(+), 39 deletions(-) diff --git a/jambo/parser/anyof_type_parser.py b/jambo/parser/anyof_type_parser.py index 55ff3ec..4b961ac 100644 --- a/jambo/parser/anyof_type_parser.py +++ b/jambo/parser/anyof_type_parser.py @@ -1,3 +1,4 @@ +from jambo.exceptions import InvalidSchemaException from jambo.parser._type_parser import GenericTypeParser from jambo.types.type_parser_options import TypeParserOptions @@ -14,10 +15,15 @@ class AnyOfTypeParser(GenericTypeParser): self, name, properties, **kwargs: Unpack[TypeParserOptions] ): if "anyOf" not in properties: - raise ValueError(f"Invalid JSON Schema: {properties}") + raise InvalidSchemaException( + f"AnyOf type {name} must have 'anyOf' property defined.", + invalid_field="anyOf", + ) if not isinstance(properties["anyOf"], list): - raise ValueError(f"Invalid JSON Schema: {properties['anyOf']}") + raise InvalidSchemaException( + "AnyOf must be a list of types.", invalid_field="anyOf" + ) mapped_properties = self.mappings_properties_builder(properties, **kwargs) diff --git a/jambo/parser/array_type_parser.py b/jambo/parser/array_type_parser.py index 7d59ea5..790a65a 100644 --- a/jambo/parser/array_type_parser.py +++ b/jambo/parser/array_type_parser.py @@ -1,3 +1,4 @@ +from jambo.exceptions import InvalidSchemaException from jambo.parser._type_parser import GenericTypeParser from jambo.types.type_parser_options import TypeParserOptions @@ -26,8 +27,15 @@ class ArrayTypeParser(GenericTypeParser): ): item_properties = kwargs.copy() item_properties["required"] = True + + if (items := properties.get("items")) is None: + raise InvalidSchemaException( + f"Array type {name} must have 'items' property defined.", + invalid_field="items", + ) + _item_type, _item_args = GenericTypeParser.type_from_properties( - name, properties["items"], **item_properties + name, items, **item_properties ) wrapper_type = set if properties.get("uniqueItems", False) else list @@ -47,8 +55,9 @@ class ArrayTypeParser(GenericTypeParser): return lambda: None if not isinstance(default_list, Iterable): - raise ValueError( - f"Default value for array must be an iterable, got {type(default_list)}" + raise InvalidSchemaException( + f"Default value for array must be an iterable, got {type(default_list)}", + invalid_field="default", ) return lambda: copy.deepcopy(wrapper_type(default_list)) diff --git a/jambo/parser/boolean_type_parser.py b/jambo/parser/boolean_type_parser.py index ecb703a..d948069 100644 --- a/jambo/parser/boolean_type_parser.py +++ b/jambo/parser/boolean_type_parser.py @@ -1,3 +1,4 @@ +from jambo.exceptions import InvalidSchemaException from jambo.parser._type_parser import GenericTypeParser from jambo.types.type_parser_options import TypeParserOptions @@ -20,6 +21,9 @@ class BooleanTypeParser(GenericTypeParser): default_value = properties.get("default") if default_value is not None and not isinstance(default_value, bool): - raise ValueError(f"Default value for {name} must be a boolean.") + raise InvalidSchemaException( + f"Default value for {name} must be a boolean.", + invalid_field="default", + ) return bool, mapped_properties diff --git a/jambo/parser/const_type_parser.py b/jambo/parser/const_type_parser.py index da55bc0..76c6893 100644 --- a/jambo/parser/const_type_parser.py +++ b/jambo/parser/const_type_parser.py @@ -1,3 +1,4 @@ +from jambo.exceptions import InvalidSchemaException from jambo.parser._type_parser import GenericTypeParser from jambo.types.json_schema_type import JSONSchemaNativeTypes from jambo.types.type_parser_options import TypeParserOptions @@ -18,13 +19,17 @@ class ConstTypeParser(GenericTypeParser): self, name, properties, **kwargs: Unpack[TypeParserOptions] ): if "const" not in properties: - raise ValueError(f"Const type {name} must have 'const' property defined.") + raise InvalidSchemaException( + f"Const type {name} must have 'const' property defined.", + invalid_field="const", + ) const_value = properties["const"] if not isinstance(const_value, JSONSchemaNativeTypes): - raise ValueError( - f"Const type {name} must have 'const' value of allowed types: {JSONSchemaNativeTypes}." + raise InvalidSchemaException( + f"Const type {name} must have 'const' value of allowed types: {JSONSchemaNativeTypes}.", + invalid_field="const", ) const_type = self._build_const_type(const_value) diff --git a/jambo/parser/enum_type_parser.py b/jambo/parser/enum_type_parser.py index c59a725..f0b001f 100644 --- a/jambo/parser/enum_type_parser.py +++ b/jambo/parser/enum_type_parser.py @@ -1,3 +1,4 @@ +from jambo.exceptions import InvalidSchemaException from jambo.parser._type_parser import GenericTypeParser from jambo.types.json_schema_type import JSONSchemaNativeTypes from jambo.types.type_parser_options import JSONSchema, TypeParserOptions @@ -14,16 +15,23 @@ class EnumTypeParser(GenericTypeParser): self, name: str, properties: JSONSchema, **kwargs: Unpack[TypeParserOptions] ): if "enum" not in properties: - raise ValueError(f"Enum type {name} must have 'enum' property defined.") + raise InvalidSchemaException( + f"Enum type {name} must have 'enum' property defined.", + invalid_field="enum", + ) enum_values = properties["enum"] if not isinstance(enum_values, list): - raise ValueError(f"Enum type {name} must have 'enum' as a list of values.") + raise InvalidSchemaException( + f"Enum type {name} must have 'enum' as a list of values.", + invalid_field="enum", + ) if any(not isinstance(value, JSONSchemaNativeTypes) for value in enum_values): - raise ValueError( - f"Enum type {name} must have 'enum' values of allowed types: {JSONSchemaNativeTypes}." + raise InvalidSchemaException( + f"Enum type {name} must have 'enum' values of allowed types: {JSONSchemaNativeTypes}.", + invalid_field="enum", ) # Create a new Enum type dynamically diff --git a/jambo/parser/oneof_type_parser.py b/jambo/parser/oneof_type_parser.py index 317ce61..4713d9c 100644 --- a/jambo/parser/oneof_type_parser.py +++ b/jambo/parser/oneof_type_parser.py @@ -1,3 +1,4 @@ +from jambo.exceptions import InvalidSchemaException from jambo.parser._type_parser import GenericTypeParser from jambo.types.type_parser_options import TypeParserOptions @@ -17,10 +18,14 @@ class OneOfTypeParser(GenericTypeParser): self, name, properties, **kwargs: Unpack[TypeParserOptions] ): if "oneOf" not in properties: - raise ValueError(f"Invalid JSON Schema: {properties}") + raise InvalidSchemaException( + f"Invalid JSON Schema: {properties}", invalid_field="oneOf" + ) if not isinstance(properties["oneOf"], list) or len(properties["oneOf"]) == 0: - raise ValueError(f"Invalid JSON Schema: {properties['oneOf']}") + raise InvalidSchemaException( + f"Invalid JSON Schema: {properties['oneOf']}", invalid_field="oneOf" + ) mapped_properties = self.mappings_properties_builder(properties, **kwargs) @@ -58,7 +63,9 @@ class OneOfTypeParser(GenericTypeParser): Build a type with a discriminator. """ if not isinstance(discriminator_prop, dict): - raise ValueError("Discriminator must be a dictionary") + raise InvalidSchemaException( + "Discriminator must be a dictionary", invalid_field="discriminator" + ) for field in subfield_types: field_type, field_info = get_args(field) @@ -66,13 +73,17 @@ class OneOfTypeParser(GenericTypeParser): if issubclass(field_type, BaseModel): continue - raise ValueError( - "When using a discriminator, all subfield types must be of type 'object'." + raise InvalidSchemaException( + "When using a discriminator, all subfield types must be of type 'object'.", + invalid_field="discriminator", ) property_name = discriminator_prop.get("propertyName") if property_name is None or not isinstance(property_name, str): - raise ValueError("Discriminator must have a 'propertyName' key") + raise InvalidSchemaException( + "Discriminator must have a 'propertyName' key", + invalid_field="propertyName", + ) return Annotated[Union[(*subfield_types,)], Field(discriminator=property_name)] diff --git a/jambo/parser/ref_type_parser.py b/jambo/parser/ref_type_parser.py index 7aa435d..615283e 100644 --- a/jambo/parser/ref_type_parser.py +++ b/jambo/parser/ref_type_parser.py @@ -1,3 +1,4 @@ +from jambo.exceptions import InvalidSchemaException from jambo.parser import GenericTypeParser from jambo.types.json_schema_type import JSONSchema from jambo.types.type_parser_options import TypeParserOptions @@ -17,18 +18,21 @@ class RefTypeParser(GenericTypeParser): 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}") + raise InvalidSchemaException( + f"Missing $ref in properties for {name}", invalid_field="$ref" + ) context = kwargs.get("context") if context is None: - raise RuntimeError( - f"RefTypeParser: Missing `content` in properties for {name}" + raise InvalidSchemaException( + f"Missing `context` in properties for {name}", invalid_field="context" ) ref_cache = kwargs.get("ref_cache") if ref_cache is None: - raise RuntimeError( - f"RefTypeParser: Missing `ref_cache` in properties for {name}" + raise InvalidSchemaException( + f"Missing `ref_cache` in properties for {name}", + invalid_field="ref_cache", ) mapped_properties = self.mappings_properties_builder(properties, **kwargs) @@ -63,8 +67,8 @@ class RefTypeParser(GenericTypeParser): ref_name, ref_property, **kwargs ) case _: - raise ValueError( - f"RefTypeParser: Unsupported $ref {ref_property['$ref']}" + raise InvalidSchemaException( + f"Unsupported $ref {ref_property['$ref']}", invalid_field="$ref" ) return mapped_type @@ -93,8 +97,9 @@ class RefTypeParser(GenericTypeParser): if properties.get("$ref") == "#": ref_name = kwargs["context"].get("title") if ref_name is None: - raise ValueError( - "RefTypeParser: Missing title in properties for $ref of Root Reference" + raise InvalidSchemaException( + "Missing title in properties for $ref of Root Reference", + invalid_field="title", ) return "forward_ref", ref_name, {} @@ -104,8 +109,9 @@ class RefTypeParser(GenericTypeParser): ) return "def_ref", target_name, target_property - raise ValueError( - "RefTypeParser: Only Root and $defs references are supported at the moment" + raise InvalidSchemaException( + "Only Root and $defs references are supported at the moment", + invalid_field="$ref", ) def _extract_target_ref( @@ -115,14 +121,16 @@ class RefTypeParser(GenericTypeParser): target_property = kwargs["context"] for prop_name in properties["$ref"].split("/")[1:]: if prop_name not in target_property: - raise ValueError( - f"RefTypeParser: Missing {prop_name} in" - " properties for $ref {properties['$ref']}" + raise InvalidSchemaException( + f"Missing {prop_name} in properties for $ref {properties['$ref']}", + invalid_field=prop_name, ) target_name = prop_name target_property = target_property[prop_name] # type: ignore if not isinstance(target_name, str) or target_property is None: - raise ValueError(f"RefTypeParser: Invalid $ref {properties['$ref']}") + raise InvalidSchemaException( + f"Invalid $ref {properties['$ref']}", invalid_field="$ref" + ) return target_name, target_property diff --git a/jambo/parser/string_type_parser.py b/jambo/parser/string_type_parser.py index 21e1fd6..8ec583e 100644 --- a/jambo/parser/string_type_parser.py +++ b/jambo/parser/string_type_parser.py @@ -1,3 +1,4 @@ +from jambo.exceptions import InvalidSchemaException from jambo.parser._type_parser import GenericTypeParser from jambo.types.type_parser_options import TypeParserOptions @@ -54,7 +55,9 @@ class StringTypeParser(GenericTypeParser): return str, mapped_properties if format_type not in self.format_type_mapping: - raise ValueError(f"Unsupported string format: {format_type}") + raise InvalidSchemaException( + f"Unsupported string format: {format_type}", invalid_field="format" + ) mapped_type = self.format_type_mapping[format_type] if format_type in self.format_pattern_mapping: diff --git a/jambo/types/json_schema_type.py b/jambo/types/json_schema_type.py index 954db75..3e73387 100644 --- a/jambo/types/json_schema_type.py +++ b/jambo/types/json_schema_type.py @@ -52,7 +52,7 @@ JSONSchema = TypedDict( "minProperties": int, "maxProperties": int, "dependencies": Dict[str, Union[List[str], "JSONSchema"]], - "items": Union["JSONSchema", List["JSONSchema"]], + "items": "JSONSchema", "prefixItems": List["JSONSchema"], "additionalItems": Union[bool, "JSONSchema"], "contains": "JSONSchema", diff --git a/tests/parser/test_ref_type_parser.py b/tests/parser/test_ref_type_parser.py index 3e08ff4..c16d0b6 100644 --- a/tests/parser/test_ref_type_parser.py +++ b/tests/parser/test_ref_type_parser.py @@ -40,7 +40,7 @@ class TestRefTypeParser(TestCase): }, } - with self.assertRaises(RuntimeError): + with self.assertRaises(ValueError): RefTypeParser().from_properties( "person", properties, @@ -63,7 +63,7 @@ class TestRefTypeParser(TestCase): }, } - with self.assertRaises(RuntimeError): + with self.assertRaises(ValueError): RefTypeParser().from_properties( "person", properties, diff --git a/tests/parser/test_string_type_parser.py b/tests/parser/test_string_type_parser.py index 279e20f..2a242e2 100644 --- a/tests/parser/test_string_type_parser.py +++ b/tests/parser/test_string_type_parser.py @@ -187,7 +187,8 @@ class TestStringTypeParser(TestCase): parser.from_properties("placeholder", properties) self.assertEqual( - str(context.exception), "Unsupported string format: unsupported-format" + str(context.exception), + "Invalid JSON Schema: Unsupported string format: unsupported-format (invalid field: format)", ) def test_string_parser_with_date_format(self): -- 2.49.1 From e31002af32ae6e2879539ee193635019ca8e4695 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Sun, 14 Sep 2025 00:47:24 -0300 Subject: [PATCH 39/96] feat: fixes tests to validate the type of exception thrown --- jambo/exceptions/__init__.py | 7 +- .../internal_assertion_exception.py | 16 ++++ jambo/parser/ref_type_parser.py | 14 ++- jambo/parser/string_type_parser.py | 10 +- tests/parser/test_allof_type_parser.py | 23 +++-- tests/parser/test_anyof_type_parser.py | 7 +- tests/parser/test_array_type_parser.py | 11 ++- tests/parser/test_bool_type_parser.py | 3 +- tests/parser/test_const_type_parser.py | 5 +- tests/parser/test_enum_type_parser.py | 7 +- tests/parser/test_float_type_parser.py | 13 +-- tests/parser/test_int_type_parser.py | 13 +-- tests/parser/test_oneof_type_parser.py | 46 ++++----- tests/parser/test_ref_type_parser.py | 23 +++-- tests/parser/test_string_type_parser.py | 9 +- tests/parser/test_type_parser.py | 3 +- tests/test_schema_converter.py | 93 +++++++++++-------- 17 files changed, 179 insertions(+), 124 deletions(-) create mode 100644 jambo/exceptions/internal_assertion_exception.py diff --git a/jambo/exceptions/__init__.py b/jambo/exceptions/__init__.py index 0292136..5fe1c00 100644 --- a/jambo/exceptions/__init__.py +++ b/jambo/exceptions/__init__.py @@ -1,5 +1,10 @@ +from .internal_assertion_exception import InternalAssertionException from .invalid_schema_exception import InvalidSchemaException from .unsupported_schema_exception import UnsupportedSchemaException -__all__ = ["InvalidSchemaException", "UnsupportedSchemaException"] +__all__ = [ + "InternalAssertionException", + "InvalidSchemaException", + "UnsupportedSchemaException", +] diff --git a/jambo/exceptions/internal_assertion_exception.py b/jambo/exceptions/internal_assertion_exception.py new file mode 100644 index 0000000..ce3338d --- /dev/null +++ b/jambo/exceptions/internal_assertion_exception.py @@ -0,0 +1,16 @@ +class InternalAssertionException(AssertionError): + """Exception raised for internal assertions.""" + + def __init__( + self, + message: str, + ) -> None: + # Normalize message by stripping redundant prefix if present + message = message.removeprefix("Internal Assertion Failed: ") + super().__init__(message) + + def __str__(self) -> str: + return ( + f"Internal Assertion Failed: {super().__str__()}\n" + "This is likely a bug in Jambo. Please report it at" + ) diff --git a/jambo/parser/ref_type_parser.py b/jambo/parser/ref_type_parser.py index 615283e..123260f 100644 --- a/jambo/parser/ref_type_parser.py +++ b/jambo/parser/ref_type_parser.py @@ -1,4 +1,4 @@ -from jambo.exceptions import InvalidSchemaException +from jambo.exceptions import InternalAssertionException, InvalidSchemaException from jambo.parser import GenericTypeParser from jambo.types.json_schema_type import JSONSchema from jambo.types.type_parser_options import TypeParserOptions @@ -22,17 +22,15 @@ class RefTypeParser(GenericTypeParser): f"Missing $ref in properties for {name}", invalid_field="$ref" ) - context = kwargs.get("context") - if context is None: - raise InvalidSchemaException( - f"Missing `context` in properties for {name}", invalid_field="context" + if kwargs.get("context") is None: + raise InternalAssertionException( + "`context` must be provided in kwargs for RefTypeParser" ) ref_cache = kwargs.get("ref_cache") if ref_cache is None: - raise InvalidSchemaException( - f"Missing `ref_cache` in properties for {name}", - invalid_field="ref_cache", + raise InternalAssertionException( + "`ref_cache` must be provided in kwargs for RefTypeParser" ) mapped_properties = self.mappings_properties_builder(properties, **kwargs) diff --git a/jambo/parser/string_type_parser.py b/jambo/parser/string_type_parser.py index 8ec583e..f8d0c9e 100644 --- a/jambo/parser/string_type_parser.py +++ b/jambo/parser/string_type_parser.py @@ -23,19 +23,19 @@ class StringTypeParser(GenericTypeParser): } format_type_mapping = { - # 7.3.1. Dates, Times, and Duration + # [7.3.1](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.7.3.1). Dates, Times, and Duration "date": date, "time": time, "date-time": datetime, "duration": timedelta, - # 7.3.2. Email Addresses + # [7.3.2](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.7.3.2). Email Addresses "email": EmailStr, - # 7.3.3. Hostnames + # [7.3.3](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.7.3.3). Hostnames "hostname": str, - # 7.3.4. IP Addresses + # [7.3.4](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.7.3.4). IP Addresses "ipv4": IPv4Address, "ipv6": IPv6Address, - # 7.3.5. Resource Identifiers + # [7.3.5](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.7.3.5). Resource Identifiers "uri": AnyUrl, # "iri" # Not supported by pydantic and currently not supported by jambo "uuid": UUID, diff --git a/tests/parser/test_allof_type_parser.py b/tests/parser/test_allof_type_parser.py index 2ae4bc2..f99c657 100644 --- a/tests/parser/test_allof_type_parser.py +++ b/tests/parser/test_allof_type_parser.py @@ -1,5 +1,8 @@ +from jambo.exceptions import InvalidSchemaException from jambo.parser.allof_type_parser import AllOfTypeParser +from pydantic import ValidationError + from unittest import TestCase @@ -42,13 +45,13 @@ class TestAllOfTypeParser(TestCase): "placeholder", properties ) - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): type_parsing(name="John", age=101) - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): type_parsing(name="", age=30) - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): type_parsing(name="John Invalid", age=30) obj = type_parsing(name="John", age=30) @@ -87,10 +90,10 @@ class TestAllOfTypeParser(TestCase): "placeholder", properties ) - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): type_parsing(name="John") - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): type_parsing(age=30) obj = type_parsing(name="John", age=30) @@ -154,7 +157,7 @@ class TestAllOfTypeParser(TestCase): ] } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): AllOfTypeParser().from_properties("placeholder", properties) def test_all_of_invalid_type_not_present(self): @@ -167,7 +170,7 @@ class TestAllOfTypeParser(TestCase): ] } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): AllOfTypeParser().from_properties("placeholder", properties) def test_all_of_invalid_type_in_fields(self): @@ -180,7 +183,7 @@ class TestAllOfTypeParser(TestCase): ] } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): AllOfTypeParser().from_properties("placeholder", properties) def test_all_of_invalid_type_not_all_equal(self): @@ -196,7 +199,7 @@ class TestAllOfTypeParser(TestCase): ] } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): AllOfTypeParser().from_properties("placeholder", properties) def test_all_of_description_field(self): @@ -304,5 +307,5 @@ class TestAllOfTypeParser(TestCase): ], } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): AllOfTypeParser().from_properties("placeholder", properties) diff --git a/tests/parser/test_anyof_type_parser.py b/tests/parser/test_anyof_type_parser.py index 32c2f45..d7740fd 100644 --- a/tests/parser/test_anyof_type_parser.py +++ b/tests/parser/test_anyof_type_parser.py @@ -1,3 +1,4 @@ +from jambo.exceptions import InvalidSchemaException from jambo.parser.anyof_type_parser import AnyOfTypeParser from typing_extensions import Annotated, Union, get_args, get_origin @@ -14,7 +15,7 @@ class TestAnyOfTypeParser(TestCase): ], } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): AnyOfTypeParser().from_properties("placeholder", properties) def test_any_of_with_invalid_properties(self): @@ -22,7 +23,7 @@ class TestAnyOfTypeParser(TestCase): "anyOf": None, } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): AnyOfTypeParser().from_properties("placeholder", properties) def test_any_of_string_or_int(self): @@ -95,5 +96,5 @@ class TestAnyOfTypeParser(TestCase): "default": 3.14, } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): AnyOfTypeParser().from_properties("placeholder", properties) diff --git a/tests/parser/test_array_type_parser.py b/tests/parser/test_array_type_parser.py index d27330f..1cac217 100644 --- a/tests/parser/test_array_type_parser.py +++ b/tests/parser/test_array_type_parser.py @@ -1,3 +1,4 @@ +from jambo.exceptions import InvalidSchemaException from jambo.parser import ArrayTypeParser from typing_extensions import get_args @@ -67,7 +68,7 @@ class TestArrayTypeParser(TestCase): properties = {"items": {"type": "string"}, "default": ["a", 1, "c"]} - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): parser.from_properties("placeholder", properties) def test_array_parser_with_invalid_default_type(self): @@ -75,15 +76,15 @@ class TestArrayTypeParser(TestCase): properties = {"items": {"type": "string"}, "default": 000} - with self.assertRaises(ValueError): - parser.from_properties("placeholder", properties) + with self.assertRaises(InvalidSchemaException): + parser.from_properties("placeholder", properties=properties) def test_array_parser_with_invalid_default_min(self): parser = ArrayTypeParser() properties = {"items": {"type": "string"}, "default": ["a"], "minItems": 2} - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): parser.from_properties("placeholder", properties) def test_array_parser_with_invalid_default_max(self): @@ -95,5 +96,5 @@ class TestArrayTypeParser(TestCase): "maxItems": 3, } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): parser.from_properties("placeholder", properties) diff --git a/tests/parser/test_bool_type_parser.py b/tests/parser/test_bool_type_parser.py index 1e3a6c9..2c2be6c 100644 --- a/tests/parser/test_bool_type_parser.py +++ b/tests/parser/test_bool_type_parser.py @@ -1,3 +1,4 @@ +from jambo.exceptions import InvalidSchemaException from jambo.parser import BooleanTypeParser from unittest import TestCase @@ -39,5 +40,5 @@ class TestBoolTypeParser(TestCase): "default": "invalid", } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): parser.from_properties_impl("placeholder", properties) diff --git a/tests/parser/test_const_type_parser.py b/tests/parser/test_const_type_parser.py index bd7dcc5..5e0fc29 100644 --- a/tests/parser/test_const_type_parser.py +++ b/tests/parser/test_const_type_parser.py @@ -1,3 +1,4 @@ +from jambo.exceptions import InvalidSchemaException from jambo.parser import ConstTypeParser from typing_extensions import Annotated, Literal, get_args, get_origin @@ -80,7 +81,7 @@ class TestConstTypeParser(TestCase): expected_const_value = "United States of America" properties = {"notConst": expected_const_value} - with self.assertRaises(ValueError) as context: + with self.assertRaises(InvalidSchemaException) as context: parser.from_properties_impl("invalid_country", properties) self.assertIn( @@ -93,7 +94,7 @@ class TestConstTypeParser(TestCase): properties = {"const": {}} - with self.assertRaises(ValueError) as context: + with self.assertRaises(InvalidSchemaException) as context: parser.from_properties_impl("invalid_country", properties) self.assertIn( diff --git a/tests/parser/test_enum_type_parser.py b/tests/parser/test_enum_type_parser.py index aad95a4..dafb9d8 100644 --- a/tests/parser/test_enum_type_parser.py +++ b/tests/parser/test_enum_type_parser.py @@ -1,3 +1,4 @@ +from jambo.exceptions import InvalidSchemaException from jambo.parser import EnumTypeParser from enum import Enum @@ -10,7 +11,7 @@ class TestEnumTypeParser(TestCase): schema = {} - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): parsed_type, parsed_properties = parser.from_properties_impl( "TestEnum", schema, @@ -23,7 +24,7 @@ class TestEnumTypeParser(TestCase): "enum": "not_a_list", } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): parsed_type, parsed_properties = parser.from_properties_impl( "TestEnum", schema, @@ -86,5 +87,5 @@ class TestEnumTypeParser(TestCase): "enum": ["value1", 42, dict()], } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): parser.from_properties_impl("TestEnum", schema) diff --git a/tests/parser/test_float_type_parser.py b/tests/parser/test_float_type_parser.py index c462d64..1bdd65a 100644 --- a/tests/parser/test_float_type_parser.py +++ b/tests/parser/test_float_type_parser.py @@ -1,3 +1,4 @@ +from jambo.exceptions import InvalidSchemaException from jambo.parser import FloatTypeParser from unittest import TestCase @@ -61,7 +62,7 @@ class TestFloatTypeParser(TestCase): "multipleOf": 0.5, } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): parser.from_properties("placeholder", properties) def test_float_parser_with_default_invalid_maximum(self): @@ -75,7 +76,7 @@ class TestFloatTypeParser(TestCase): "multipleOf": 0.5, } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): parser.from_properties("placeholder", properties) def test_float_parser_with_default_invalid_minimum(self): @@ -89,7 +90,7 @@ class TestFloatTypeParser(TestCase): "multipleOf": 0.5, } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): parser.from_properties("placeholder", properties) def test_float_parser_with_default_invalid_exclusive_maximum(self): @@ -103,7 +104,7 @@ class TestFloatTypeParser(TestCase): "multipleOf": 0.5, } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): parser.from_properties("placeholder", properties) def test_float_parser_with_default_invalid_exclusive_minimum(self): @@ -117,7 +118,7 @@ class TestFloatTypeParser(TestCase): "multipleOf": 0.5, } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): parser.from_properties("placeholder", properties) def test_float_parser_with_default_invalid_multiple(self): @@ -131,5 +132,5 @@ class TestFloatTypeParser(TestCase): "multipleOf": 2.0, } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): parser.from_properties("placeholder", properties) diff --git a/tests/parser/test_int_type_parser.py b/tests/parser/test_int_type_parser.py index 5cfeed5..fa563f4 100644 --- a/tests/parser/test_int_type_parser.py +++ b/tests/parser/test_int_type_parser.py @@ -1,3 +1,4 @@ +from jambo.exceptions import InvalidSchemaException from jambo.parser import IntTypeParser from unittest import TestCase @@ -61,7 +62,7 @@ class TestIntTypeParser(TestCase): "multipleOf": 2, } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): parser.from_properties("placeholder", properties) def test_int_parser_with_default_invalid_maximum(self): @@ -75,7 +76,7 @@ class TestIntTypeParser(TestCase): "multipleOf": 2, } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): parser.from_properties("placeholder", properties) def test_int_parser_with_default_invalid_minimum(self): @@ -89,7 +90,7 @@ class TestIntTypeParser(TestCase): "multipleOf": 2, } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): parser.from_properties("placeholder", properties) def test_int_parser_with_default_invalid_exclusive_maximum(self): @@ -103,7 +104,7 @@ class TestIntTypeParser(TestCase): "multipleOf": 2, } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): parser.from_properties("placeholder", properties) def test_int_parser_with_default_invalid_exclusive_minimum(self): @@ -117,7 +118,7 @@ class TestIntTypeParser(TestCase): "multipleOf": 2, } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): parser.from_properties("placeholder", properties) def test_int_parser_with_default_invalid_multipleOf(self): @@ -131,5 +132,5 @@ class TestIntTypeParser(TestCase): "multipleOf": 2, } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): parser.from_properties("placeholder", properties) diff --git a/tests/parser/test_oneof_type_parser.py b/tests/parser/test_oneof_type_parser.py index 69104ae..99e9074 100644 --- a/tests/parser/test_oneof_type_parser.py +++ b/tests/parser/test_oneof_type_parser.py @@ -1,12 +1,15 @@ from jambo import SchemaConverter +from jambo.exceptions import InvalidSchemaException from jambo.parser.oneof_type_parser import OneOfTypeParser +from pydantic import ValidationError + from unittest import TestCase class TestOneOfTypeParser(TestCase): def test_oneof_raises_on_invalid_property(self): - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): OneOfTypeParser().from_properties_impl( "test_field", { @@ -17,7 +20,7 @@ class TestOneOfTypeParser(TestCase): ref_cache={}, ) - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): SchemaConverter.build( { "title": "Test", @@ -71,13 +74,13 @@ class TestOneOfTypeParser(TestCase): Model = SchemaConverter.build(schema) - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): Model(id=-5) - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): Model(id="invalid") - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): Model(id=123.45) def test_oneof_with_conflicting_schemas(self): @@ -103,11 +106,11 @@ class TestOneOfTypeParser(TestCase): obj2 = Model(data=9) self.assertEqual(obj2.data, 9) - with self.assertRaises(ValueError) as cm: + with self.assertRaises(ValidationError) as cm: Model(data=6) self.assertIn("matches multiple oneOf schemas", str(cm.exception)) - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): Model(data=5) def test_oneof_with_objects(self): @@ -147,7 +150,7 @@ class TestOneOfTypeParser(TestCase): obj2 = Model(contact_info={"phone": "123-456-7890"}) self.assertEqual(obj2.contact_info.phone, "123-456-7890") - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): Model(contact_info={"email": "user@example.com", "phone": "123-456-7890"}) def test_oneof_with_discriminator_basic(self): @@ -190,14 +193,14 @@ class TestOneOfTypeParser(TestCase): self.assertEqual(dog.pet.type, "dog") self.assertEqual(dog.pet.barks, False) - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): Model(pet={"type": "cat", "barks": True}) - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): Model(pet={"type": "bird", "flies": True}) def test_oneof_with_invalid_types(self): - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): SchemaConverter.build( { "title": "Pet", @@ -301,13 +304,13 @@ class TestOneOfTypeParser(TestCase): Model = SchemaConverter.build(schema) - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): Model(shape={"type": "triangle", "base": 5, "height": 3}) - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): Model(shape={"type": "circle", "side": 5}) - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): Model(shape={"radius": 5}) def test_oneof_missing_properties(self): @@ -324,7 +327,7 @@ class TestOneOfTypeParser(TestCase): }, } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): SchemaConverter.build(schema) def test_oneof_invalid_properties(self): @@ -336,7 +339,7 @@ class TestOneOfTypeParser(TestCase): }, } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): SchemaConverter.build(schema) def test_oneof_with_default_value(self): @@ -373,12 +376,12 @@ class TestOneOfTypeParser(TestCase): }, } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): SchemaConverter.build(schema) def test_oneof_discriminator_without_property_name(self): # Should throw because the spec determines propertyName is required for discriminator - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): SchemaConverter.build( { "title": "Test", @@ -409,7 +412,7 @@ class TestOneOfTypeParser(TestCase): def test_oneof_discriminator_with_invalid_discriminator(self): # Should throw because a valid discriminator is required - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): SchemaConverter.build( { "title": "Test", @@ -465,8 +468,9 @@ class TestOneOfTypeParser(TestCase): self.assertEqual(obj2.value, "very long string") # Invalid: Medium string (matches BOTH schemas - violates oneOf) - with self.assertRaises(ValueError) as cm: + with self.assertRaises(ValidationError) as cm: Model(value="hello") # 5 chars: matches maxLength=6 AND minLength=4 + self.assertIn("matches multiple oneOf schemas", str(cm.exception)) def test_oneof_shapes_discriminator_from_docs(self): @@ -515,5 +519,5 @@ class TestOneOfTypeParser(TestCase): self.assertEqual(rectangle.shape.height, 20) # Invalid: Wrong properties for the type - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): Model(shape={"type": "circle", "width": 10}) diff --git a/tests/parser/test_ref_type_parser.py b/tests/parser/test_ref_type_parser.py index c16d0b6..44b0949 100644 --- a/tests/parser/test_ref_type_parser.py +++ b/tests/parser/test_ref_type_parser.py @@ -1,5 +1,8 @@ +from jambo.exceptions import InternalAssertionException, InvalidSchemaException from jambo.parser import ObjectTypeParser, RefTypeParser +from pydantic import ValidationError + from typing import ForwardRef from unittest import TestCase @@ -16,7 +19,7 @@ class TestRefTypeParser(TestCase): "required": ["name", "age"], } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): RefTypeParser().from_properties( "person", properties, @@ -40,7 +43,7 @@ class TestRefTypeParser(TestCase): }, } - with self.assertRaises(ValueError): + with self.assertRaises(InternalAssertionException): RefTypeParser().from_properties( "person", properties, @@ -63,7 +66,7 @@ class TestRefTypeParser(TestCase): }, } - with self.assertRaises(ValueError): + with self.assertRaises(InternalAssertionException): RefTypeParser().from_properties( "person", properties, @@ -77,7 +80,7 @@ class TestRefTypeParser(TestCase): "$ref": "https://example.com/schemas/person.json", } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): RefTypeParser().from_properties( "person", properties, @@ -110,7 +113,7 @@ class TestRefTypeParser(TestCase): }, } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): ObjectTypeParser().from_properties( "person", properties, @@ -126,7 +129,7 @@ class TestRefTypeParser(TestCase): "$defs": {}, } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): RefTypeParser().from_properties( "person", properties, @@ -142,7 +145,7 @@ class TestRefTypeParser(TestCase): "$defs": {"person": None}, } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): RefTypeParser().from_properties( "person", properties, @@ -232,7 +235,7 @@ class TestRefTypeParser(TestCase): "required": ["name", "age"], } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): ObjectTypeParser().from_properties( "person", properties, @@ -264,7 +267,7 @@ class TestRefTypeParser(TestCase): ) # checks if when created via FowardRef the model is validated correctly. - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): model( name="John", age=30, @@ -421,7 +424,7 @@ class TestRefTypeParser(TestCase): }, } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): ref_strategy, ref_name, ref_property = RefTypeParser()._parse_from_strategy( "invalid_strategy", "person", diff --git a/tests/parser/test_string_type_parser.py b/tests/parser/test_string_type_parser.py index 2a242e2..ac42145 100644 --- a/tests/parser/test_string_type_parser.py +++ b/tests/parser/test_string_type_parser.py @@ -1,3 +1,4 @@ +from jambo.exceptions import InvalidSchemaException from jambo.parser import StringTypeParser from pydantic import AnyUrl, EmailStr @@ -62,7 +63,7 @@ class TestStringTypeParser(TestCase): "minLength": 5, } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): parser.from_properties("placeholder", properties) def test_string_parser_with_default_invalid_maxlength(self): @@ -75,7 +76,7 @@ class TestStringTypeParser(TestCase): "minLength": 1, } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): parser.from_properties("placeholder", properties) def test_string_parser_with_default_invalid_minlength(self): @@ -88,7 +89,7 @@ class TestStringTypeParser(TestCase): "minLength": 2, } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): parser.from_properties("placeholder", properties) def test_string_parser_with_email_format(self): @@ -183,7 +184,7 @@ class TestStringTypeParser(TestCase): "format": "unsupported-format", } - with self.assertRaises(ValueError) as context: + with self.assertRaises(InvalidSchemaException) as context: parser.from_properties("placeholder", properties) self.assertEqual( diff --git a/tests/parser/test_type_parser.py b/tests/parser/test_type_parser.py index 4722de8..748ea7e 100644 --- a/tests/parser/test_type_parser.py +++ b/tests/parser/test_type_parser.py @@ -1,3 +1,4 @@ +from jambo.exceptions import InvalidSchemaException from jambo.parser import StringTypeParser from jambo.parser._type_parser import GenericTypeParser @@ -17,5 +18,5 @@ class TestGenericTypeParser(TestCase): StringTypeParser.json_schema_type = "type:string" def test_get_impl_invalid_type(self): - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): GenericTypeParser._get_impl({"type": "invalid_type"}) diff --git a/tests/test_schema_converter.py b/tests/test_schema_converter.py index 2d4099b..6f2b15a 100644 --- a/tests/test_schema_converter.py +++ b/tests/test_schema_converter.py @@ -1,6 +1,7 @@ from jambo import SchemaConverter +from jambo.exceptions import InvalidSchemaException, UnsupportedSchemaException -from pydantic import AnyUrl, BaseModel +from pydantic import AnyUrl, BaseModel, ValidationError from ipaddress import IPv4Address, IPv6Address from unittest import TestCase @@ -23,7 +24,7 @@ class TestSchemaConverter(TestCase): }, } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): SchemaConverter.build(schema) def test_invalid_schema_type(self): @@ -37,7 +38,7 @@ class TestSchemaConverter(TestCase): }, } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): SchemaConverter.build(schema) def test_build_expects_title(self): @@ -50,7 +51,7 @@ class TestSchemaConverter(TestCase): }, } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): SchemaConverter.build(schema) def test_build_expects_object(self): @@ -60,7 +61,7 @@ class TestSchemaConverter(TestCase): "type": "string", } - with self.assertRaises(ValueError): + with self.assertRaises(UnsupportedSchemaException): SchemaConverter.build(schema) def test_is_invalid_field(self): @@ -76,7 +77,7 @@ class TestSchemaConverter(TestCase): # 'required': ['name', 'age', 'is_active', 'friends', 'address'], } - with self.assertRaises(ValueError) as context: + with self.assertRaises(InvalidSchemaException) as context: SchemaConverter.build(schema) self.assertTrue("Unknown type" in str(context.exception)) @@ -117,16 +118,16 @@ class TestSchemaConverter(TestCase): self.assertEqual(model(name="John", age=30).name, "John") - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): model(name=123, age=30, email="teste@hideyoshi.com") - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): model(name="John Invalid", age=45, email="teste@hideyoshi.com") - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): model(name="", age=45, email="teste@hideyoshi.com") - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): model(name="John", age=45, email="hideyoshi.com") def test_validation_integer(self): @@ -148,10 +149,10 @@ class TestSchemaConverter(TestCase): self.assertEqual(model(age=30).age, 30) - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): model(age=-1) - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): model(age=121) def test_validation_float(self): @@ -173,10 +174,10 @@ class TestSchemaConverter(TestCase): self.assertEqual(model(age=30).age, 30.0) - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): model(age=-1.0) - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): model(age=121.0) def test_validation_boolean(self): @@ -219,10 +220,10 @@ class TestSchemaConverter(TestCase): model(friends=["John", "Jane", "John"]).friends, {"John", "Jane"} ) - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): model(friends=[]) - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): model(friends=["John", "Jane", "Invalid"]) def test_validation_list_with_missing_items(self): @@ -262,7 +263,7 @@ class TestSchemaConverter(TestCase): } ) - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): model() def test_validation_object(self): @@ -290,7 +291,7 @@ class TestSchemaConverter(TestCase): self.assertEqual(obj.address.street, "123 Main St") self.assertEqual(obj.address.city, "Springfield") - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): model() def test_default_for_string(self): @@ -329,7 +330,7 @@ class TestSchemaConverter(TestCase): "required": ["name"], } - with self.assertRaises(ValueError): + with self.assertRaises(InvalidSchemaException): SchemaConverter.build(schema_max_length) def test_default_for_list(self): @@ -421,10 +422,10 @@ class TestSchemaConverter(TestCase): self.assertEqual(obj.name, "J") - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): Model(name="John Invalid") - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): Model(name="") def test_any_of(self): @@ -450,13 +451,13 @@ class TestSchemaConverter(TestCase): obj = Model(id="12345678901") self.assertEqual(obj.id, "12345678901") - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): Model(id="") - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): Model(id="12345678901234567890") - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): Model(id=11) def test_string_format_email(self): @@ -465,9 +466,11 @@ class TestSchemaConverter(TestCase): "type": "object", "properties": {"email": {"type": "string", "format": "email"}}, } + model = SchemaConverter.build(schema) self.assertEqual(model(email="test@example.com").email, "test@example.com") - with self.assertRaises(ValueError): + + with self.assertRaises(ValidationError): model(email="invalid-email") def test_string_format_uri(self): @@ -476,11 +479,13 @@ class TestSchemaConverter(TestCase): "type": "object", "properties": {"website": {"type": "string", "format": "uri"}}, } + model = SchemaConverter.build(schema) self.assertEqual( model(website="https://example.com").website, AnyUrl("https://example.com") ) - with self.assertRaises(ValueError): + + with self.assertRaises(ValidationError): model(website="invalid-uri") def test_string_format_ipv4(self): @@ -489,9 +494,11 @@ class TestSchemaConverter(TestCase): "type": "object", "properties": {"ip": {"type": "string", "format": "ipv4"}}, } + model = SchemaConverter.build(schema) self.assertEqual(model(ip="192.168.1.1").ip, IPv4Address("192.168.1.1")) - with self.assertRaises(ValueError): + + with self.assertRaises(ValidationError): model(ip="256.256.256.256") def test_string_format_ipv6(self): @@ -500,12 +507,14 @@ class TestSchemaConverter(TestCase): "type": "object", "properties": {"ip": {"type": "string", "format": "ipv6"}}, } + model = SchemaConverter.build(schema) self.assertEqual( model(ip="2001:0db8:85a3:0000:0000:8a2e:0370:7334").ip, IPv6Address("2001:0db8:85a3:0000:0000:8a2e:0370:7334"), ) - with self.assertRaises(ValueError): + + with self.assertRaises(ValidationError): model(ip="invalid-ipv6") def test_string_format_uuid(self): @@ -514,6 +523,7 @@ class TestSchemaConverter(TestCase): "type": "object", "properties": {"id": {"type": "string", "format": "uuid"}}, } + model = SchemaConverter.build(schema) self.assertEqual( @@ -521,7 +531,7 @@ class TestSchemaConverter(TestCase): UUID("123e4567-e89b-12d3-a456-426614174000"), ) - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): model(id="invalid-uuid") def test_string_format_hostname(self): @@ -530,9 +540,11 @@ class TestSchemaConverter(TestCase): "type": "object", "properties": {"hostname": {"type": "string", "format": "hostname"}}, } + model = SchemaConverter.build(schema) self.assertEqual(model(hostname="example.com").hostname, "example.com") - with self.assertRaises(ValueError): + + with self.assertRaises(ValidationError): model(hostname="invalid..hostname") def test_string_format_datetime(self): @@ -541,12 +553,14 @@ class TestSchemaConverter(TestCase): "type": "object", "properties": {"timestamp": {"type": "string", "format": "date-time"}}, } + model = SchemaConverter.build(schema) self.assertEqual( model(timestamp="2024-01-01T12:00:00Z").timestamp.isoformat(), "2024-01-01T12:00:00+00:00", ) - with self.assertRaises(ValueError): + + with self.assertRaises(ValidationError): model(timestamp="invalid-datetime") def test_string_format_time(self): @@ -555,11 +569,13 @@ class TestSchemaConverter(TestCase): "type": "object", "properties": {"time": {"type": "string", "format": "time"}}, } + model = SchemaConverter.build(schema) self.assertEqual( model(time="20:20:39+00:00").time.isoformat(), "20:20:39+00:00" ) - with self.assertRaises(ValueError): + + with self.assertRaises(ValidationError): model(time="25:00:00") def test_string_format_unsupported(self): @@ -568,7 +584,8 @@ class TestSchemaConverter(TestCase): "type": "object", "properties": {"field": {"type": "string", "format": "unsupported"}}, } - with self.assertRaises(ValueError): + + with self.assertRaises(InvalidSchemaException): SchemaConverter.build(schema) def test_ref_with_root_ref(self): @@ -726,10 +743,10 @@ class TestSchemaConverter(TestCase): obj = Model() self.assertEqual(obj.name, "United States of America") - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): obj.name = "Canada" - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): Model(name="Canada") def test_const_type_parser_with_non_hashable_value(self): @@ -749,10 +766,10 @@ class TestSchemaConverter(TestCase): obj = Model() self.assertEqual(obj.name, ["Brazil"]) - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): obj.name = ["Argentina"] - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): Model(name=["Argentina"]) def test_null_type_parser(self): @@ -772,5 +789,5 @@ class TestSchemaConverter(TestCase): obj = Model(a_thing=None) self.assertIsNone(obj.a_thing) - with self.assertRaises(ValueError): + with self.assertRaises(ValidationError): Model(a_thing="not none") -- 2.49.1 From 8c6a04bbdfc288e590b18ad9d4e45a4e96387918 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Sun, 14 Sep 2025 00:57:36 -0300 Subject: [PATCH 40/96] feat: adds simple tests for internal exceptions --- tests/exceptions/__init__.py | 0 .../test_internal_assertion_exception.py | 21 +++++++++ .../test_invalid_schema_exception.py | 44 +++++++++++++++++++ .../test_unsupported_schema_exception.py | 31 +++++++++++++ tests/parser/test_array_type_parser.py | 11 +++++ tests/parser/test_oneof_type_parser.py | 11 +++++ 6 files changed, 118 insertions(+) create mode 100644 tests/exceptions/__init__.py create mode 100644 tests/exceptions/test_internal_assertion_exception.py create mode 100644 tests/exceptions/test_invalid_schema_exception.py create mode 100644 tests/exceptions/test_unsupported_schema_exception.py diff --git a/tests/exceptions/__init__.py b/tests/exceptions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/exceptions/test_internal_assertion_exception.py b/tests/exceptions/test_internal_assertion_exception.py new file mode 100644 index 0000000..4a6d4de --- /dev/null +++ b/tests/exceptions/test_internal_assertion_exception.py @@ -0,0 +1,21 @@ +from jambo.exceptions.internal_assertion_exception import InternalAssertionException + +from unittest import TestCase + + +class TestInternalAssertionException(TestCase): + def test_inheritance(self): + self.assertTrue(issubclass(InternalAssertionException, AssertionError)) + + def test_message(self): + message = "This is an internal assertion error." + + expected_message = ( + f"Internal Assertion Failed: {message}\n" + "This is likely a bug in Jambo. Please report it at" + ) + + with self.assertRaises(InternalAssertionException) as ctx: + raise InternalAssertionException(message) + + self.assertEqual(str(ctx.exception), expected_message) diff --git a/tests/exceptions/test_invalid_schema_exception.py b/tests/exceptions/test_invalid_schema_exception.py new file mode 100644 index 0000000..8f96fd1 --- /dev/null +++ b/tests/exceptions/test_invalid_schema_exception.py @@ -0,0 +1,44 @@ +from jambo.exceptions.invalid_schema_exception import InvalidSchemaException + +from unittest import TestCase + + +class TestInternalAssertionException(TestCase): + def test_inheritance(self): + self.assertTrue(issubclass(InvalidSchemaException, ValueError)) + + def test_message(self): + message = "This is an internal assertion error." + + expected_message = f"Invalid JSON Schema: {message}" + + with self.assertRaises(InvalidSchemaException) as ctx: + raise InvalidSchemaException(message) + + self.assertEqual(str(ctx.exception), expected_message) + + def test_invalid_field(self): + message = "This is an internal assertion error." + invalid_field = "testField" + + expected_message = ( + f"Invalid JSON Schema: {message} (invalid field: {invalid_field})" + ) + + with self.assertRaises(InvalidSchemaException) as ctx: + raise InvalidSchemaException(message, invalid_field=invalid_field) + + self.assertEqual(str(ctx.exception), expected_message) + + def test_cause(self): + message = "This is an internal assertion error." + cause = ValueError("Underlying cause") + + expected_message = ( + f"Invalid JSON Schema: {message} (caused by ValueError: Underlying cause)" + ) + + with self.assertRaises(InvalidSchemaException) as ctx: + raise InvalidSchemaException(message, cause=cause) + + self.assertEqual(str(ctx.exception), expected_message) diff --git a/tests/exceptions/test_unsupported_schema_exception.py b/tests/exceptions/test_unsupported_schema_exception.py new file mode 100644 index 0000000..e01e3e3 --- /dev/null +++ b/tests/exceptions/test_unsupported_schema_exception.py @@ -0,0 +1,31 @@ +from jambo.exceptions.unsupported_schema_exception import UnsupportedSchemaException + +from unittest import TestCase + + +class TestUnsupportedSchemaException(TestCase): + def test_inheritance(self): + self.assertTrue(issubclass(UnsupportedSchemaException, ValueError)) + + def test_message(self): + message = "This is an internal assertion error." + + expected_message = f"Unsupported JSON Schema: {message}" + + with self.assertRaises(UnsupportedSchemaException) as ctx: + raise UnsupportedSchemaException(message) + + self.assertEqual(str(ctx.exception), expected_message) + + def test_unsupported_field(self): + message = "This is an internal assertion error." + invalid_field = "testField" + + expected_message = ( + f"Unsupported JSON Schema: {message} (unsupported field: {invalid_field})" + ) + + with self.assertRaises(UnsupportedSchemaException) as ctx: + raise UnsupportedSchemaException(message, unsupported_field=invalid_field) + + self.assertEqual(str(ctx.exception), expected_message) diff --git a/tests/parser/test_array_type_parser.py b/tests/parser/test_array_type_parser.py index 1cac217..dc9212f 100644 --- a/tests/parser/test_array_type_parser.py +++ b/tests/parser/test_array_type_parser.py @@ -19,6 +19,17 @@ class TestArrayTypeParser(TestCase): self.assertEqual(type_parsing.__origin__, list) self.assertEqual(element_type, str) + def test_array_parser_with_no_items(self): + parser = ArrayTypeParser() + + properties = { + "default": ["a", "b", "c", "d"], + "maxItems": 3, + } + + with self.assertRaises(InvalidSchemaException): + parser.from_properties("placeholder", properties) + def test_array_parser_with_options_unique(self): parser = ArrayTypeParser() diff --git a/tests/parser/test_oneof_type_parser.py b/tests/parser/test_oneof_type_parser.py index 99e9074..14bf942 100644 --- a/tests/parser/test_oneof_type_parser.py +++ b/tests/parser/test_oneof_type_parser.py @@ -20,6 +20,17 @@ class TestOneOfTypeParser(TestCase): ref_cache={}, ) + with self.assertRaises(InvalidSchemaException): + OneOfTypeParser().from_properties_impl( + "test_field", + { + "oneOf": [], # should throw because oneOf must be a list with at least one item + }, + required=True, + context={}, + ref_cache={}, + ) + with self.assertRaises(InvalidSchemaException): SchemaConverter.build( { -- 2.49.1 From 7f44e84bce08f788e0923eb5d039ffa1605c36fd Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Sun, 14 Sep 2025 01:12:43 -0300 Subject: [PATCH 41/96] feat: updates outdated docs for exceptions --- docs/source/jambo.exceptions.rst | 37 ++++++++++++++++++++++++++++++++ docs/source/jambo.parser.rst | 32 +++++++++++++++++++++++++++ docs/source/jambo.rst | 1 + 3 files changed, 70 insertions(+) create mode 100644 docs/source/jambo.exceptions.rst diff --git a/docs/source/jambo.exceptions.rst b/docs/source/jambo.exceptions.rst new file mode 100644 index 0000000..93797bc --- /dev/null +++ b/docs/source/jambo.exceptions.rst @@ -0,0 +1,37 @@ +jambo.exceptions package +======================== + +Submodules +---------- + +jambo.exceptions.internal\_assertion\_exception module +------------------------------------------------------ + +.. automodule:: jambo.exceptions.internal_assertion_exception + :members: + :show-inheritance: + :undoc-members: + +jambo.exceptions.invalid\_schema\_exception module +-------------------------------------------------- + +.. automodule:: jambo.exceptions.invalid_schema_exception + :members: + :show-inheritance: + :undoc-members: + +jambo.exceptions.unsupported\_schema\_exception module +------------------------------------------------------ + +.. automodule:: jambo.exceptions.unsupported_schema_exception + :members: + :show-inheritance: + :undoc-members: + +Module contents +--------------- + +.. automodule:: jambo.exceptions + :members: + :show-inheritance: + :undoc-members: diff --git a/docs/source/jambo.parser.rst b/docs/source/jambo.parser.rst index daa4c46..7c55a67 100644 --- a/docs/source/jambo.parser.rst +++ b/docs/source/jambo.parser.rst @@ -36,6 +36,22 @@ jambo.parser.boolean\_type\_parser module :show-inheritance: :undoc-members: +jambo.parser.const\_type\_parser module +--------------------------------------- + +.. automodule:: jambo.parser.const_type_parser + :members: + :show-inheritance: + :undoc-members: + +jambo.parser.enum\_type\_parser module +-------------------------------------- + +.. automodule:: jambo.parser.enum_type_parser + :members: + :show-inheritance: + :undoc-members: + jambo.parser.float\_type\_parser module --------------------------------------- @@ -52,6 +68,14 @@ jambo.parser.int\_type\_parser module :show-inheritance: :undoc-members: +jambo.parser.null\_type\_parser module +-------------------------------------- + +.. automodule:: jambo.parser.null_type_parser + :members: + :show-inheritance: + :undoc-members: + jambo.parser.object\_type\_parser module ---------------------------------------- @@ -60,6 +84,14 @@ jambo.parser.object\_type\_parser module :show-inheritance: :undoc-members: +jambo.parser.oneof\_type\_parser module +--------------------------------------- + +.. automodule:: jambo.parser.oneof_type_parser + :members: + :show-inheritance: + :undoc-members: + jambo.parser.ref\_type\_parser module ------------------------------------- diff --git a/docs/source/jambo.rst b/docs/source/jambo.rst index 791ad0d..0bd0d5b 100644 --- a/docs/source/jambo.rst +++ b/docs/source/jambo.rst @@ -7,6 +7,7 @@ Subpackages .. toctree:: :maxdepth: 4 + jambo.exceptions jambo.parser jambo.types -- 2.49.1 From 156c825a678e489e5ba8737c65cfc2a100190a19 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Sun, 14 Sep 2025 01:40:59 -0300 Subject: [PATCH 42/96] feat: more pythonic error parent class --- jambo/exceptions/internal_assertion_exception.py | 2 +- tests/exceptions/test_internal_assertion_exception.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jambo/exceptions/internal_assertion_exception.py b/jambo/exceptions/internal_assertion_exception.py index ce3338d..836394d 100644 --- a/jambo/exceptions/internal_assertion_exception.py +++ b/jambo/exceptions/internal_assertion_exception.py @@ -1,4 +1,4 @@ -class InternalAssertionException(AssertionError): +class InternalAssertionException(RuntimeError): """Exception raised for internal assertions.""" def __init__( diff --git a/tests/exceptions/test_internal_assertion_exception.py b/tests/exceptions/test_internal_assertion_exception.py index 4a6d4de..7b0ba86 100644 --- a/tests/exceptions/test_internal_assertion_exception.py +++ b/tests/exceptions/test_internal_assertion_exception.py @@ -5,7 +5,7 @@ from unittest import TestCase class TestInternalAssertionException(TestCase): def test_inheritance(self): - self.assertTrue(issubclass(InternalAssertionException, AssertionError)) + self.assertTrue(issubclass(InternalAssertionException, RuntimeError)) def test_message(self): message = "This is an internal assertion error." -- 2.49.1 From 92c174c189ae57357e6b03c28a414c2222f88c11 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Sun, 14 Sep 2025 10:49:53 -0300 Subject: [PATCH 43/96] fix: fixes docs build --- docs/source/conf.py | 4 +++ pyproject.toml | 1 + uv.lock | 70 ++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 68 insertions(+), 7 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 1f714bb..4a138d5 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -18,6 +18,7 @@ extensions = [ "sphinx.ext.viewcode", "sphinx.ext.autodoc", "sphinx.ext.napoleon", + "sphinx_autodoc_typehints", # <-- needed ] @@ -35,3 +36,6 @@ html_static_path = ["_static"] # -- Options for autodoc ----------------------------------------------------- add_module_names = False python_use_unqualified_type_names = True + + +autodoc_typehints = "both" diff --git a/pyproject.toml b/pyproject.toml index 34e1fee..b6c1db7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,7 @@ dev = [ "ruff>=0.11.4", "sphinx>=8.1.3", "sphinx-autobuild>=2024.10.3", + "sphinx-autodoc-typehints>=3.0.1", "sphinx-rtd-theme>=3.0.2", "types-jsonschema>=4.25.1.20250822", ] diff --git a/uv.lock b/uv.lock index da0c1b5..7e08d9a 100644 --- a/uv.lock +++ b/uv.lock @@ -332,7 +332,10 @@ dev = [ { name = "ruff" }, { name = "sphinx", version = "8.1.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", version = "2024.10.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx-autobuild", version = "2025.8.25", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "sphinx-autodoc-typehints", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx-autodoc-typehints", version = "3.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "sphinx-rtd-theme" }, { name = "types-jsonschema" }, ] @@ -353,6 +356,7 @@ dev = [ { name = "ruff", specifier = ">=0.11.4" }, { name = "sphinx", specifier = ">=8.1.3" }, { name = "sphinx-autobuild", specifier = ">=2024.10.3" }, + { name = "sphinx-autodoc-typehints", specifier = ">=3.0.1" }, { name = "sphinx-rtd-theme", specifier = ">=3.0.2" }, { name = "types-jsonschema", specifier = ">=4.25.1.20250822" }, ] @@ -968,20 +972,72 @@ wheels = [ name = "sphinx-autobuild" version = "2024.10.3" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] dependencies = [ - { name = "colorama" }, + { name = "colorama", marker = "python_full_version < '3.11'" }, { name = "sphinx", version = "8.1.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 = "starlette" }, - { name = "uvicorn" }, - { name = "watchfiles" }, - { name = "websockets" }, + { name = "starlette", marker = "python_full_version < '3.11'" }, + { name = "uvicorn", marker = "python_full_version < '3.11'" }, + { name = "watchfiles", marker = "python_full_version < '3.11'" }, + { name = "websockets", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a5/2c/155e1de2c1ba96a72e5dba152c509a8b41e047ee5c2def9e9f0d812f8be7/sphinx_autobuild-2024.10.3.tar.gz", hash = "sha256:248150f8f333e825107b6d4b86113ab28fa51750e5f9ae63b59dc339be951fb1", size = 14023, upload-time = "2024-10-02T23:15:30.172Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/18/c0/eba125db38c84d3c74717008fd3cb5000b68cd7e2cbafd1349c6a38c3d3b/sphinx_autobuild-2024.10.3-py3-none-any.whl", hash = "sha256:158e16c36f9d633e613c9aaf81c19b0fc458ca78b112533b20dafcda430d60fa", size = 11908, upload-time = "2024-10-02T23:15:28.739Z" }, ] +[[package]] +name = "sphinx-autobuild" +version = "2025.8.25" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", +] +dependencies = [ + { name = "colorama", 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 = "starlette", marker = "python_full_version >= '3.11'" }, + { name = "uvicorn", marker = "python_full_version >= '3.11'" }, + { name = "watchfiles", marker = "python_full_version >= '3.11'" }, + { name = "websockets", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e0/3c/a59a3a453d4133777f7ed2e83c80b7dc817d43c74b74298ca0af869662ad/sphinx_autobuild-2025.8.25.tar.gz", hash = "sha256:9cf5aab32853c8c31af572e4fecdc09c997e2b8be5a07daf2a389e270e85b213", size = 15200, upload-time = "2025-08-25T18:44:55.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/20/56411b52f917696995f5ad27d2ea7e9492c84a043c5b49a3a3173573cd93/sphinx_autobuild-2025.8.25-py3-none-any.whl", hash = "sha256:b750ac7d5a18603e4665294323fd20f6dcc0a984117026d1986704fa68f0379a", size = 12535, upload-time = "2025-08-25T18:44:54.164Z" }, +] + +[[package]] +name = "sphinx-autodoc-typehints" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/26/f0/43c6a5ff3e7b08a8c3b32f81b859f1b518ccc31e45f22e2b41ced38be7b9/sphinx_autodoc_typehints-3.0.1.tar.gz", hash = "sha256:b9b40dd15dee54f6f810c924f863f9cf1c54f9f3265c495140ea01be7f44fa55", size = 36282, upload-time = "2025-01-16T18:25:30.958Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/dc/dc46c5c7c566b7ec5e8f860f9c89533bf03c0e6aadc96fb9b337867e4460/sphinx_autodoc_typehints-3.0.1-py3-none-any.whl", hash = "sha256:4b64b676a14b5b79cefb6628a6dc8070e320d4963e8ff640a2f3e9390ae9045a", size = 20245, upload-time = "2025-01-16T18:25:27.394Z" }, +] + +[[package]] +name = "sphinx-autodoc-typehints" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", +] +dependencies = [ + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/93/68/a388a9b8f066cd865d9daa65af589d097efbfab9a8c302d2cb2daa43b52e/sphinx_autodoc_typehints-3.2.0.tar.gz", hash = "sha256:107ac98bc8b4837202c88c0736d59d6da44076e65a0d7d7d543a78631f662a9b", size = 36724, upload-time = "2025-04-25T16:53:25.872Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/c7/8aab362e86cbf887e58be749a78d20ad743e1eb2c73c2b13d4761f39a104/sphinx_autodoc_typehints-3.2.0-py3-none-any.whl", hash = "sha256:884b39be23b1d884dcc825d4680c9c6357a476936e3b381a67ae80091984eb49", size = 20563, upload-time = "2025-04-25T16:53:24.492Z" }, +] + [[package]] name = "sphinx-rtd-theme" version = "3.0.2" -- 2.49.1 From 82feea0ab1329fcd09cd0dce8f040405a2e2c15e Mon Sep 17 00:00:00 2001 From: Fred Sonnenwald Date: Mon, 15 Sep 2025 16:46:30 +0100 Subject: [PATCH 44/96] Fix string Field deprecation warning (partial revert of fbbff0b) --- jambo/parser/string_type_parser.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/jambo/parser/string_type_parser.py b/jambo/parser/string_type_parser.py index f8d0c9e..0cd3aa1 100644 --- a/jambo/parser/string_type_parser.py +++ b/jambo/parser/string_type_parser.py @@ -19,7 +19,6 @@ class StringTypeParser(GenericTypeParser): "maxLength": "max_length", "minLength": "min_length", "pattern": "pattern", - "format": "format", } format_type_mapping = { @@ -63,4 +62,8 @@ class StringTypeParser(GenericTypeParser): if format_type in self.format_pattern_mapping: mapped_properties["pattern"] = self.format_pattern_mapping[format_type] + if "json_schema_extra" not in mapped_properties: + mapped_properties["json_schema_extra"] = {} + mapped_properties["json_schema_extra"]["format"] = format_type + return mapped_type, mapped_properties -- 2.49.1 From f0192ee6d36deb9fe373a25b4cf9f73f271873f4 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Mon, 15 Sep 2025 13:54:32 -0300 Subject: [PATCH 45/96] chore: adds pyright config to project --- pyproject.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index b6c1db7..999c247 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,3 +87,8 @@ section-order=[ "standard-library", ] lines-after-imports = 2 + + +[tool.pyright] +venvPath = "." +venv = ".venv" -- 2.49.1 From 43ce95cc9aa06f797daaa568450503a992a2cb59 Mon Sep 17 00:00:00 2001 From: JCHacking Date: Mon, 17 Nov 2025 23:41:16 +0100 Subject: [PATCH 46/96] feat(examples): Add examples for primitive types Refs: #52 --- jambo/parser/_type_parser.py | 25 +++++++ jambo/parser/const_type_parser.py | 1 + jambo/parser/enum_type_parser.py | 5 ++ jambo/parser/string_type_parser.py | 31 ++++++++ tests/parser/test_bool_type_parser.py | 16 +++++ tests/parser/test_const_type_parser.py | 12 ++-- tests/parser/test_enum_type_parser.py | 24 +++++++ tests/parser/test_float_type_parser.py | 2 + tests/parser/test_int_type_parser.py | 2 + tests/parser/test_null_type_parser.py | 16 +++++ tests/parser/test_string_type_parser.py | 96 ++++++++++++++++++++++++- 11 files changed, 223 insertions(+), 7 deletions(-) diff --git a/jambo/parser/_type_parser.py b/jambo/parser/_type_parser.py index cb7885e..33d067d 100644 --- a/jambo/parser/_type_parser.py +++ b/jambo/parser/_type_parser.py @@ -18,6 +18,7 @@ class GenericTypeParser(ABC, Generic[T]): default_mappings = { "default": "default", "description": "description", + "examples": "examples", } @abstractmethod @@ -51,6 +52,11 @@ class GenericTypeParser(ABC, Generic[T]): "Default value is not valid", invalid_field=name ) + if not self._validate_default(parsed_type, parsed_properties): + raise InvalidSchemaException( + "Examples values are not valid", invalid_field=name + ) + return parsed_type, parsed_properties @classmethod @@ -127,3 +133,22 @@ class GenericTypeParser(ABC, Generic[T]): return False return True + + @staticmethod + def _validate_examples(field_type: T, field_prop: dict) -> bool: + values = field_prop.get("examples") + + if values is None: + return True + + if not isinstance(values, list): + return False + + try: + field = Annotated[field_type, Field(**field_prop)] # type: ignore + for value in values: + TypeAdapter(field).validate_python(value) + except Exception as _: + return False + + return True diff --git a/jambo/parser/const_type_parser.py b/jambo/parser/const_type_parser.py index 76c6893..2548e68 100644 --- a/jambo/parser/const_type_parser.py +++ b/jambo/parser/const_type_parser.py @@ -13,6 +13,7 @@ class ConstTypeParser(GenericTypeParser): default_mappings = { "const": "default", "description": "description", + "examples": "examples", } def from_properties_impl( diff --git a/jambo/parser/enum_type_parser.py b/jambo/parser/enum_type_parser.py index f0b001f..3d0fee3 100644 --- a/jambo/parser/enum_type_parser.py +++ b/jambo/parser/enum_type_parser.py @@ -41,4 +41,9 @@ class EnumTypeParser(GenericTypeParser): if "default" in parsed_properties and parsed_properties["default"] is not None: parsed_properties["default"] = enum_type(parsed_properties["default"]) + if "examples" in parsed_properties: + parsed_properties["examples"] = [ + enum_type(example) for example in parsed_properties["examples"] + ] + return enum_type, parsed_properties diff --git a/jambo/parser/string_type_parser.py b/jambo/parser/string_type_parser.py index 0cd3aa1..4f970a7 100644 --- a/jambo/parser/string_type_parser.py +++ b/jambo/parser/string_type_parser.py @@ -7,6 +7,7 @@ from typing_extensions import Unpack from datetime import date, datetime, time, timedelta from ipaddress import IPv4Address, IPv6Address +from typing import Any from uuid import UUID @@ -62,8 +63,38 @@ class StringTypeParser(GenericTypeParser): if format_type in self.format_pattern_mapping: mapped_properties["pattern"] = self.format_pattern_mapping[format_type] + print("A") + if "examples" in mapped_properties: + mapped_properties["examples"] = [ + self.__parse_example(example, format_type, mapped_type) + for example in mapped_properties["examples"] + ] + if "json_schema_extra" not in mapped_properties: mapped_properties["json_schema_extra"] = {} mapped_properties["json_schema_extra"]["format"] = format_type return mapped_type, mapped_properties + + def __parse_example( + self, example: Any, format_type: str, mapped_type: type[Any] + ) -> Any: + """ + Parse example from JSON Schema format to python format + :param example: Example Value + :param format_type: Format Type + :param mapped_type: Type to parse + :return: Example parsed + """ + match format_type: + case "date" | "time" | "date-time": + return mapped_type.fromisoformat(example) + case "duration": + # TODO: Implement duration parser + raise NotImplementedError + case "ipv4" | "ipv6": + return mapped_type(example) + case "uuid": + return mapped_type(example) + case _: + return example diff --git a/tests/parser/test_bool_type_parser.py b/tests/parser/test_bool_type_parser.py index 2c2be6c..6e7d6de 100644 --- a/tests/parser/test_bool_type_parser.py +++ b/tests/parser/test_bool_type_parser.py @@ -42,3 +42,19 @@ class TestBoolTypeParser(TestCase): with self.assertRaises(InvalidSchemaException): parser.from_properties_impl("placeholder", properties) + + def test_bool_parser_with_examples(self): + parser = BooleanTypeParser() + + properties = { + "type": "boolean", + "examples": [True, False], + } + + type_parsing, type_validator = parser.from_properties_impl( + "placeholder", properties + ) + + self.assertEqual(type_parsing, bool) + self.assertEqual(type_validator["default"], None) + self.assertEqual(type_validator["examples"], [True, False]) diff --git a/tests/parser/test_const_type_parser.py b/tests/parser/test_const_type_parser.py index 5e0fc29..9b8dae4 100644 --- a/tests/parser/test_const_type_parser.py +++ b/tests/parser/test_const_type_parser.py @@ -12,7 +12,7 @@ class TestConstTypeParser(TestCase): parser = ConstTypeParser() expected_const_value = "United States of America" - properties = {"const": expected_const_value} + properties = {"const": expected_const_value, "examples": [expected_const_value]} parsed_type, parsed_properties = parser.from_properties_impl( "country", properties @@ -23,13 +23,14 @@ class TestConstTypeParser(TestCase): self.assertEqual(get_args(parsed_type), (expected_const_value,)) self.assertEqual(parsed_properties["default"], expected_const_value) + self.assertEqual(parsed_properties["examples"], [expected_const_value]) def test_const_type_parser_non_hashable_value(self): """Test const parser with non-hashable values (uses Annotated with validator)""" parser = ConstTypeParser() expected_const_value = [1, 2, 3] # Lists are not hashable - properties = {"const": expected_const_value} + properties = {"const": expected_const_value, "examples": [expected_const_value]} parsed_type, parsed_properties = parser.from_properties_impl( "list_const", properties @@ -40,13 +41,14 @@ class TestConstTypeParser(TestCase): self.assertIn(list, get_args(parsed_type)) self.assertEqual(parsed_properties["default"], expected_const_value) + self.assertEqual(parsed_properties["examples"], [expected_const_value]) def test_const_type_parser_integer_value(self): """Test const parser with integer values (uses Literal)""" parser = ConstTypeParser() expected_const_value = 42 - properties = {"const": expected_const_value} + properties = {"const": expected_const_value, "examples": [expected_const_value]} parsed_type, parsed_properties = parser.from_properties_impl( "int_const", properties @@ -57,13 +59,14 @@ class TestConstTypeParser(TestCase): self.assertEqual(get_args(parsed_type), (expected_const_value,)) self.assertEqual(parsed_properties["default"], expected_const_value) + self.assertEqual(parsed_properties["examples"], [expected_const_value]) def test_const_type_parser_boolean_value(self): """Test const parser with boolean values (uses Literal)""" parser = ConstTypeParser() expected_const_value = True - properties = {"const": expected_const_value} + properties = {"const": expected_const_value, "examples": [expected_const_value]} parsed_type, parsed_properties = parser.from_properties_impl( "bool_const", properties @@ -74,6 +77,7 @@ class TestConstTypeParser(TestCase): self.assertEqual(get_args(parsed_type), (expected_const_value,)) self.assertEqual(parsed_properties["default"], expected_const_value) + self.assertEqual(parsed_properties["examples"], [expected_const_value]) def test_const_type_parser_invalid_properties(self): parser = ConstTypeParser() diff --git a/tests/parser/test_enum_type_parser.py b/tests/parser/test_enum_type_parser.py index dafb9d8..7665007 100644 --- a/tests/parser/test_enum_type_parser.py +++ b/tests/parser/test_enum_type_parser.py @@ -89,3 +89,27 @@ class TestEnumTypeParser(TestCase): with self.assertRaises(InvalidSchemaException): parser.from_properties_impl("TestEnum", schema) + + def test_enum_type_parser_creates_enum_with_examples(self): + parser = EnumTypeParser() + + schema = { + "enum": ["value1", "value2", "value3"], + "examples": ["value1", "value3"], + } + + parsed_type, parsed_properties = parser.from_properties_impl( + "TestEnum", + schema, + ) + + self.assertIsInstance(parsed_type, type) + self.assertTrue(issubclass(parsed_type, Enum)) + self.assertEqual( + set(parsed_type.__members__.keys()), {"VALUE1", "VALUE2", "VALUE3"} + ) + self.assertEqual(parsed_properties["default"], None) + self.assertEqual( + parsed_properties["examples"], + [getattr(parsed_type, "VALUE1"), getattr(parsed_type, "VALUE3")], + ) diff --git a/tests/parser/test_float_type_parser.py b/tests/parser/test_float_type_parser.py index 1bdd65a..8bcf6e0 100644 --- a/tests/parser/test_float_type_parser.py +++ b/tests/parser/test_float_type_parser.py @@ -23,6 +23,7 @@ class TestFloatTypeParser(TestCase): "maximum": 10.5, "minimum": 1.0, "multipleOf": 0.5, + "examples": [1.5, 2.5], } type_parsing, type_validator = parser.from_properties("placeholder", properties) @@ -31,6 +32,7 @@ class TestFloatTypeParser(TestCase): self.assertEqual(type_validator["le"], 10.5) self.assertEqual(type_validator["ge"], 1.0) self.assertEqual(type_validator["multiple_of"], 0.5) + self.assertEqual(type_validator["examples"], [1.5, 2.5]) def test_float_parser_with_default(self): parser = FloatTypeParser() diff --git a/tests/parser/test_int_type_parser.py b/tests/parser/test_int_type_parser.py index fa563f4..9038642 100644 --- a/tests/parser/test_int_type_parser.py +++ b/tests/parser/test_int_type_parser.py @@ -23,6 +23,7 @@ class TestIntTypeParser(TestCase): "maximum": 10, "minimum": 1, "multipleOf": 2, + "examples": [2, 4], } type_parsing, type_validator = parser.from_properties("placeholder", properties) @@ -31,6 +32,7 @@ class TestIntTypeParser(TestCase): self.assertEqual(type_validator["le"], 10) self.assertEqual(type_validator["ge"], 1) self.assertEqual(type_validator["multiple_of"], 2) + self.assertEqual(type_validator["examples"], [2, 4]) def test_int_parser_with_default(self): parser = IntTypeParser() diff --git a/tests/parser/test_null_type_parser.py b/tests/parser/test_null_type_parser.py index e2732c0..f44b49f 100644 --- a/tests/parser/test_null_type_parser.py +++ b/tests/parser/test_null_type_parser.py @@ -16,6 +16,22 @@ class TestNullTypeParser(TestCase): self.assertEqual(type_parsing, type(None)) self.assertEqual(type_validator, {"default": None}) + def test_null_parser_with_examples(self): + parser = NullTypeParser() + + properties = { + "type": "null", + "examples": [None], + } + + type_parsing, type_validator = parser.from_properties_impl( + "placeholder", properties + ) + + self.assertEqual(type_parsing, type(None)) + self.assertEqual(type_validator["default"], None) + self.assertEqual(type_validator["examples"], [None]) + def test_null_parser_with_invalid_default(self): parser = NullTypeParser() diff --git a/tests/parser/test_string_type_parser.py b/tests/parser/test_string_type_parser.py index ac42145..1db5765 100644 --- a/tests/parser/test_string_type_parser.py +++ b/tests/parser/test_string_type_parser.py @@ -3,8 +3,8 @@ from jambo.parser import StringTypeParser from pydantic import AnyUrl, EmailStr -from datetime import date, datetime, time, timedelta -from ipaddress import IPv4Address, IPv6Address +from datetime import date, datetime, time, timedelta, timezone +from ipaddress import IPv4Address, IPv6Address, ip_address from unittest import TestCase from uuid import UUID @@ -27,6 +27,7 @@ class TestStringTypeParser(TestCase): "maxLength": 10, "minLength": 1, "pattern": "^[a-zA-Z]+$", + "examples": ["test", "TEST"], } type_parsing, type_validator = parser.from_properties("placeholder", properties) @@ -35,6 +36,7 @@ class TestStringTypeParser(TestCase): self.assertEqual(type_validator["max_length"], 10) self.assertEqual(type_validator["min_length"], 1) self.assertEqual(type_validator["pattern"], "^[a-zA-Z]+$") + self.assertEqual(type_validator["examples"], ["test", "TEST"]) def test_string_parser_with_default_value(self): parser = StringTypeParser() @@ -98,11 +100,13 @@ class TestStringTypeParser(TestCase): properties = { "type": "string", "format": "email", + "examples": ["test@example.com"], } type_parsing, type_validator = parser.from_properties("placeholder", properties) self.assertEqual(type_parsing, EmailStr) + self.assertEqual(type_validator["examples"], ["test@example.com"]) def test_string_parser_with_uri_format(self): parser = StringTypeParser() @@ -110,21 +114,27 @@ class TestStringTypeParser(TestCase): properties = { "type": "string", "format": "uri", + "examples": ["test://domain/resource"], } type_parsing, type_validator = parser.from_properties("placeholder", properties) self.assertEqual(type_parsing, AnyUrl) + self.assertEqual(type_validator["examples"], ["test://domain/resource"]) def test_string_parser_with_ip_formats(self): parser = StringTypeParser() formats = {"ipv4": IPv4Address, "ipv6": IPv6Address} + examples = {"ipv4": "192.168.1.1", "ipv6": "::1"} for ip_format, expected_type in formats.items(): + example = examples[ip_format] + properties = { "type": "string", "format": ip_format, + "examples": [example], } type_parsing, type_validator = parser.from_properties( @@ -132,6 +142,7 @@ class TestStringTypeParser(TestCase): ) self.assertEqual(type_parsing, expected_type) + self.assertEqual(type_validator["examples"], [ip_address(example)]) def test_string_parser_with_uuid_format(self): parser = StringTypeParser() @@ -139,11 +150,15 @@ class TestStringTypeParser(TestCase): properties = { "type": "string", "format": "uuid", + "examples": ["ab71aaf4-ab6e-43cd-a369-cebdd9f7a4c6"], } type_parsing, type_validator = parser.from_properties("placeholder", properties) self.assertEqual(type_parsing, UUID) + self.assertEqual( + type_validator["examples"], [UUID("ab71aaf4-ab6e-43cd-a369-cebdd9f7a4c6")] + ) def test_string_parser_with_time_format(self): parser = StringTypeParser() @@ -151,19 +166,34 @@ class TestStringTypeParser(TestCase): properties = { "type": "string", "format": "time", + "examples": ["14:30:00", "09:15:30.500", "23:59:59Z", "10:00:00+02:00"], } type_parsing, type_validator = parser.from_properties("placeholder", properties) self.assertEqual(type_parsing, time) + self.assertEqual( + type_validator["examples"], + [ + time(hour=14, minute=30, second=0), + time(hour=9, minute=15, second=30, microsecond=500_000), + time(hour=23, minute=59, second=59, tzinfo=timezone.utc), + time(hour=10, minute=0, second=0, tzinfo=timezone(timedelta(hours=2))), + ], + ) def test_string_parser_with_pattern_based_formats(self): parser = StringTypeParser() - for format_type in ["hostname"]: + format_types = { + "hostname": "hostname_example", + } + + for format_type, example_type in format_types.items(): properties = { "type": "string", "format": format_type, + "examples": [example_type], } type_parsing, type_validator = parser.from_properties( @@ -175,6 +205,7 @@ class TestStringTypeParser(TestCase): self.assertEqual( type_validator["pattern"], parser.format_pattern_mapping[format_type] ) + self.assertEqual(type_validator["examples"], [example_type]) def test_string_parser_with_unsupported_format(self): parser = StringTypeParser() @@ -198,11 +229,20 @@ class TestStringTypeParser(TestCase): properties = { "type": "string", "format": "date", + "examples": ["2025-11-17", "1999-12-31", "2000-01-01"], } type_parsing, type_validator = parser.from_properties("placeholder", properties) self.assertEqual(type_parsing, date) + self.assertEqual( + type_validator["examples"], + [ + date(year=2025, month=11, day=17), + date(year=1999, month=12, day=31), + date(year=2000, month=1, day=1), + ], + ) def test_string_parser_with_datetime_format(self): parser = StringTypeParser() @@ -210,11 +250,51 @@ class TestStringTypeParser(TestCase): properties = { "type": "string", "format": "date-time", + "examples": [ + "2025-11-17T11:15:00", + "2025-11-17T11:15:00Z", + "2025-11-17T11:15:00+01:00", + "2025-11-17T11:15:00.123456-05:00", + ], } type_parsing, type_validator = parser.from_properties("placeholder", properties) self.assertEqual(type_parsing, datetime) + self.assertEqual( + type_validator["examples"], + [ + datetime(year=2025, month=11, day=17, hour=11, minute=15, second=0), + datetime( + year=2025, + month=11, + day=17, + hour=11, + minute=15, + second=0, + tzinfo=timezone.utc, + ), + datetime( + year=2025, + month=11, + day=17, + hour=11, + minute=15, + second=0, + tzinfo=timezone(timedelta(hours=1)), + ), + datetime( + year=2025, + month=11, + day=17, + hour=11, + minute=15, + second=0, + microsecond=123456, + tzinfo=timezone(timedelta(hours=-5)), + ), + ], + ) def test_string_parser_with_timedelta_format(self): parser = StringTypeParser() @@ -222,8 +302,18 @@ class TestStringTypeParser(TestCase): properties = { "type": "string", "format": "duration", + "examples": ["P1Y2M3DT4H5M6S", "PT30M", "P7D", "PT0.5S"], } type_parsing, type_validator = parser.from_properties("placeholder", properties) self.assertEqual(type_parsing, timedelta) + self.assertEqual( + type_validator["examples"], + [ + timedelta(days=7), + timedelta(minutes=30), + timedelta(hours=4, minutes=5, seconds=6), + timedelta(seconds=0.5), + ], + ) -- 2.49.1 From 9bc16ff1aaccdc9d19684c7d2dd8cded1ed70f61 Mon Sep 17 00:00:00 2001 From: JCHacking Date: Mon, 17 Nov 2025 23:45:08 +0100 Subject: [PATCH 47/96] remove print --- jambo/parser/string_type_parser.py | 1 - 1 file changed, 1 deletion(-) diff --git a/jambo/parser/string_type_parser.py b/jambo/parser/string_type_parser.py index 4f970a7..20a74ea 100644 --- a/jambo/parser/string_type_parser.py +++ b/jambo/parser/string_type_parser.py @@ -63,7 +63,6 @@ class StringTypeParser(GenericTypeParser): if format_type in self.format_pattern_mapping: mapped_properties["pattern"] = self.format_pattern_mapping[format_type] - print("A") if "examples" in mapped_properties: mapped_properties["examples"] = [ self.__parse_example(example, format_type, mapped_type) -- 2.49.1 From c9330dfd6d58c3fdd6d6469fdff5a95892ca6866 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Sun, 23 Nov 2025 02:15:41 -0300 Subject: [PATCH 48/96] feat: fixes error on validation of IPAddresses by Upgrading Pydantic min version to v2.12.4, fixes internal tests implementation and fixes minor logic errors --- jambo/parser/_type_parser.py | 40 ++-- jambo/parser/string_type_parser.py | 4 +- pyproject.toml | 7 +- tests/parser/test_string_type_parser.py | 12 +- uv.lock | 300 +++++++++++++++--------- 5 files changed, 227 insertions(+), 136 deletions(-) diff --git a/jambo/parser/_type_parser.py b/jambo/parser/_type_parser.py index 33d067d..15ceb80 100644 --- a/jambo/parser/_type_parser.py +++ b/jambo/parser/_type_parser.py @@ -52,7 +52,7 @@ class GenericTypeParser(ABC, Generic[T]): "Default value is not valid", invalid_field=name ) - if not self._validate_default(parsed_type, parsed_properties): + if not self._validate_examples(parsed_type, parsed_properties): raise InvalidSchemaException( "Examples values are not valid", invalid_field=name ) @@ -126,6 +126,25 @@ class GenericTypeParser(ABC, Generic[T]): if value is None: return True + return GenericTypeParser._is_valid_value(field_type, field_prop, value) + + @staticmethod + def _validate_examples(field_type: T, field_prop: dict) -> bool: + examples = field_prop.get("examples") + + if examples is None: + return True + + if not isinstance(examples, list): + return False + + return all( + GenericTypeParser._is_valid_value(field_type, field_prop, e) + for e in examples + ) + + @staticmethod + def _is_valid_value(field_type: T, field_prop: dict, value: Any) -> bool: try: field = Annotated[field_type, Field(**field_prop)] # type: ignore TypeAdapter(field).validate_python(value) @@ -133,22 +152,3 @@ class GenericTypeParser(ABC, Generic[T]): return False return True - - @staticmethod - def _validate_examples(field_type: T, field_prop: dict) -> bool: - values = field_prop.get("examples") - - if values is None: - return True - - if not isinstance(values, list): - return False - - try: - field = Annotated[field_type, Field(**field_prop)] # type: ignore - for value in values: - TypeAdapter(field).validate_python(value) - except Exception as _: - return False - - return True diff --git a/jambo/parser/string_type_parser.py b/jambo/parser/string_type_parser.py index 20a74ea..1d81268 100644 --- a/jambo/parser/string_type_parser.py +++ b/jambo/parser/string_type_parser.py @@ -65,7 +65,7 @@ class StringTypeParser(GenericTypeParser): if "examples" in mapped_properties: mapped_properties["examples"] = [ - self.__parse_example(example, format_type, mapped_type) + self._parse_example(example, format_type, mapped_type) for example in mapped_properties["examples"] ] @@ -75,7 +75,7 @@ class StringTypeParser(GenericTypeParser): return mapped_type, mapped_properties - def __parse_example( + def _parse_example( self, example: Any, format_type: str, mapped_type: type[Any] ) -> Any: """ diff --git a/pyproject.toml b/pyproject.toml index 999c247..8e37e7d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ readme = "README.md" dependencies = [ "email-validator>=2.2.0", "jsonschema>=4.23.0", - "pydantic>=2.10.6", + "pydantic>=2.12.4", ] [dependency-groups] @@ -47,6 +47,11 @@ dev = [ homepage = "https://github.com/HideyoshiNakazone/jambo" repository = "https://github.com/HideyoshiNakazone/jambo.git" +[project.optional-dependencies] +str-fmt = [ + "isodate>=0.7.2", +] + # POE Tasks [tool.poe.tasks] diff --git a/tests/parser/test_string_type_parser.py b/tests/parser/test_string_type_parser.py index 1db5765..191ccd8 100644 --- a/tests/parser/test_string_type_parser.py +++ b/tests/parser/test_string_type_parser.py @@ -3,6 +3,7 @@ from jambo.parser import StringTypeParser from pydantic import AnyUrl, EmailStr +import unittest from datetime import date, datetime, time, timedelta, timezone from ipaddress import IPv4Address, IPv6Address, ip_address from unittest import TestCase @@ -126,7 +127,7 @@ class TestStringTypeParser(TestCase): parser = StringTypeParser() formats = {"ipv4": IPv4Address, "ipv6": IPv6Address} - examples = {"ipv4": "192.168.1.1", "ipv6": "::1"} + examples = {"ipv4": ["192.168.1.1"], "ipv6": ["::1"]} for ip_format, expected_type in formats.items(): example = examples[ip_format] @@ -134,7 +135,7 @@ class TestStringTypeParser(TestCase): properties = { "type": "string", "format": ip_format, - "examples": [example], + "examples": example, } type_parsing, type_validator = parser.from_properties( @@ -142,7 +143,9 @@ class TestStringTypeParser(TestCase): ) self.assertEqual(type_parsing, expected_type) - self.assertEqual(type_validator["examples"], [ip_address(example)]) + self.assertEqual( + type_validator["examples"], [ip_address(e) for e in example] + ) def test_string_parser_with_uuid_format(self): parser = StringTypeParser() @@ -186,7 +189,7 @@ class TestStringTypeParser(TestCase): parser = StringTypeParser() format_types = { - "hostname": "hostname_example", + "hostname": "example.com", } for format_type, example_type in format_types.items(): @@ -296,6 +299,7 @@ class TestStringTypeParser(TestCase): ], ) + @unittest.skip("Duration parsing not yet implemented") def test_string_parser_with_timedelta_format(self): parser = StringTypeParser() diff --git a/uv.lock b/uv.lock index 7e08d9a..e4184b3 100644 --- a/uv.lock +++ b/uv.lock @@ -259,14 +259,14 @@ wheels = [ [[package]] name = "exceptiongroup" -version = "1.3.0" +version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, ] [[package]] @@ -314,6 +314,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769, upload-time = "2022-07-01T12:21:02.467Z" }, ] +[[package]] +name = "isodate" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/4d/e940025e2ce31a8ce1202635910747e5a87cc3a6a6bb2d00973375014749/isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6", size = 29705, upload-time = "2024-10-08T23:04:11.5Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" }, +] + [[package]] name = "jambo" source = { editable = "." } @@ -323,6 +332,11 @@ dependencies = [ { name = "pydantic" }, ] +[package.optional-dependencies] +str-fmt = [ + { name = "isodate" }, +] + [package.dev-dependencies] dev = [ { name = "coverage" }, @@ -343,9 +357,11 @@ dev = [ [package.metadata] requires-dist = [ { name = "email-validator", specifier = ">=2.2.0" }, + { name = "isodate", marker = "extra == 'str-fmt'", specifier = ">=0.7.2" }, { name = "jsonschema", specifier = ">=4.23.0" }, - { name = "pydantic", specifier = ">=2.10.6" }, + { name = "pydantic", specifier = ">=2.12.4" }, ] +provides-extras = ["str-fmt"] [package.metadata.requires-dev] dev = [ @@ -589,91 +605,135 @@ wheels = [ [[package]] name = "pydantic" -version = "2.10.6" +version = "2.12.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, { name = "pydantic-core" }, { name = "typing-extensions" }, + { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b7/ae/d5220c5c52b158b1de7ca89fc5edb72f304a70a4c540c84c8844bf4008de/pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236", size = 761681, upload-time = "2025-01-24T01:42:12.693Z" } +sdist = { url = "https://files.pythonhosted.org/packages/96/ad/a17bc283d7d81837c061c49e3eaa27a45991759a1b7eae1031921c6bd924/pydantic-2.12.4.tar.gz", hash = "sha256:0f8cb9555000a4b5b617f66bfd2566264c4984b27589d3b845685983e8ea85ac", size = 821038, upload-time = "2025-11-05T10:50:08.59Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", size = 431696, upload-time = "2025-01-24T01:42:10.371Z" }, + { url = "https://files.pythonhosted.org/packages/82/2f/e68750da9b04856e2a7ec56fc6f034a5a79775e9b9a81882252789873798/pydantic-2.12.4-py3-none-any.whl", hash = "sha256:92d3d202a745d46f9be6df459ac5a064fdaa3c1c4cd8adcfa332ccf3c05f871e", size = 463400, upload-time = "2025-11-05T10:50:06.732Z" }, ] [[package]] name = "pydantic-core" -version = "2.27.2" +version = "2.41.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443, upload-time = "2024-12-18T11:31:54.917Z" } +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/bc/fed5f74b5d802cf9a03e83f60f18864e90e3aed7223adaca5ffb7a8d8d64/pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa", size = 1895938, upload-time = "2024-12-18T11:27:14.406Z" }, - { url = "https://files.pythonhosted.org/packages/71/2a/185aff24ce844e39abb8dd680f4e959f0006944f4a8a0ea372d9f9ae2e53/pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c", size = 1815684, upload-time = "2024-12-18T11:27:16.489Z" }, - { url = "https://files.pythonhosted.org/packages/c3/43/fafabd3d94d159d4f1ed62e383e264f146a17dd4d48453319fd782e7979e/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a", size = 1829169, upload-time = "2024-12-18T11:27:22.16Z" }, - { url = "https://files.pythonhosted.org/packages/a2/d1/f2dfe1a2a637ce6800b799aa086d079998959f6f1215eb4497966efd2274/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5", size = 1867227, upload-time = "2024-12-18T11:27:25.097Z" }, - { url = "https://files.pythonhosted.org/packages/7d/39/e06fcbcc1c785daa3160ccf6c1c38fea31f5754b756e34b65f74e99780b5/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c", size = 2037695, upload-time = "2024-12-18T11:27:28.656Z" }, - { url = "https://files.pythonhosted.org/packages/7a/67/61291ee98e07f0650eb756d44998214231f50751ba7e13f4f325d95249ab/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7", size = 2741662, upload-time = "2024-12-18T11:27:30.798Z" }, - { url = "https://files.pythonhosted.org/packages/32/90/3b15e31b88ca39e9e626630b4c4a1f5a0dfd09076366f4219429e6786076/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a", size = 1993370, upload-time = "2024-12-18T11:27:33.692Z" }, - { url = "https://files.pythonhosted.org/packages/ff/83/c06d333ee3a67e2e13e07794995c1535565132940715931c1c43bfc85b11/pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236", size = 1996813, upload-time = "2024-12-18T11:27:37.111Z" }, - { url = "https://files.pythonhosted.org/packages/7c/f7/89be1c8deb6e22618a74f0ca0d933fdcb8baa254753b26b25ad3acff8f74/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962", size = 2005287, upload-time = "2024-12-18T11:27:40.566Z" }, - { url = "https://files.pythonhosted.org/packages/b7/7d/8eb3e23206c00ef7feee17b83a4ffa0a623eb1a9d382e56e4aa46fd15ff2/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9", size = 2128414, upload-time = "2024-12-18T11:27:43.757Z" }, - { url = "https://files.pythonhosted.org/packages/4e/99/fe80f3ff8dd71a3ea15763878d464476e6cb0a2db95ff1c5c554133b6b83/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af", size = 2155301, upload-time = "2024-12-18T11:27:47.36Z" }, - { url = "https://files.pythonhosted.org/packages/2b/a3/e50460b9a5789ca1451b70d4f52546fa9e2b420ba3bfa6100105c0559238/pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4", size = 1816685, upload-time = "2024-12-18T11:27:50.508Z" }, - { url = "https://files.pythonhosted.org/packages/57/4c/a8838731cb0f2c2a39d3535376466de6049034d7b239c0202a64aaa05533/pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31", size = 1982876, upload-time = "2024-12-18T11:27:53.54Z" }, - { url = "https://files.pythonhosted.org/packages/c2/89/f3450af9d09d44eea1f2c369f49e8f181d742f28220f88cc4dfaae91ea6e/pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc", size = 1893421, upload-time = "2024-12-18T11:27:55.409Z" }, - { url = "https://files.pythonhosted.org/packages/9e/e3/71fe85af2021f3f386da42d291412e5baf6ce7716bd7101ea49c810eda90/pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7", size = 1814998, upload-time = "2024-12-18T11:27:57.252Z" }, - { url = "https://files.pythonhosted.org/packages/a6/3c/724039e0d848fd69dbf5806894e26479577316c6f0f112bacaf67aa889ac/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15", size = 1826167, upload-time = "2024-12-18T11:27:59.146Z" }, - { url = "https://files.pythonhosted.org/packages/2b/5b/1b29e8c1fb5f3199a9a57c1452004ff39f494bbe9bdbe9a81e18172e40d3/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306", size = 1865071, upload-time = "2024-12-18T11:28:02.625Z" }, - { url = "https://files.pythonhosted.org/packages/89/6c/3985203863d76bb7d7266e36970d7e3b6385148c18a68cc8915fd8c84d57/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99", size = 2036244, upload-time = "2024-12-18T11:28:04.442Z" }, - { url = "https://files.pythonhosted.org/packages/0e/41/f15316858a246b5d723f7d7f599f79e37493b2e84bfc789e58d88c209f8a/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459", size = 2737470, upload-time = "2024-12-18T11:28:07.679Z" }, - { url = "https://files.pythonhosted.org/packages/a8/7c/b860618c25678bbd6d1d99dbdfdf0510ccb50790099b963ff78a124b754f/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048", size = 1992291, upload-time = "2024-12-18T11:28:10.297Z" }, - { url = "https://files.pythonhosted.org/packages/bf/73/42c3742a391eccbeab39f15213ecda3104ae8682ba3c0c28069fbcb8c10d/pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d", size = 1994613, upload-time = "2024-12-18T11:28:13.362Z" }, - { url = "https://files.pythonhosted.org/packages/94/7a/941e89096d1175d56f59340f3a8ebaf20762fef222c298ea96d36a6328c5/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b", size = 2002355, upload-time = "2024-12-18T11:28:16.587Z" }, - { url = "https://files.pythonhosted.org/packages/6e/95/2359937a73d49e336a5a19848713555605d4d8d6940c3ec6c6c0ca4dcf25/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474", size = 2126661, upload-time = "2024-12-18T11:28:18.407Z" }, - { url = "https://files.pythonhosted.org/packages/2b/4c/ca02b7bdb6012a1adef21a50625b14f43ed4d11f1fc237f9d7490aa5078c/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6", size = 2153261, upload-time = "2024-12-18T11:28:21.471Z" }, - { url = "https://files.pythonhosted.org/packages/72/9d/a241db83f973049a1092a079272ffe2e3e82e98561ef6214ab53fe53b1c7/pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c", size = 1812361, upload-time = "2024-12-18T11:28:23.53Z" }, - { url = "https://files.pythonhosted.org/packages/e8/ef/013f07248041b74abd48a385e2110aa3a9bbfef0fbd97d4e6d07d2f5b89a/pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc", size = 1982484, upload-time = "2024-12-18T11:28:25.391Z" }, - { url = "https://files.pythonhosted.org/packages/10/1c/16b3a3e3398fd29dca77cea0a1d998d6bde3902fa2706985191e2313cc76/pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4", size = 1867102, upload-time = "2024-12-18T11:28:28.593Z" }, - { url = "https://files.pythonhosted.org/packages/d6/74/51c8a5482ca447871c93e142d9d4a92ead74de6c8dc5e66733e22c9bba89/pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", size = 1893127, upload-time = "2024-12-18T11:28:30.346Z" }, - { url = "https://files.pythonhosted.org/packages/d3/f3/c97e80721735868313c58b89d2de85fa80fe8dfeeed84dc51598b92a135e/pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", size = 1811340, upload-time = "2024-12-18T11:28:32.521Z" }, - { url = "https://files.pythonhosted.org/packages/9e/91/840ec1375e686dbae1bd80a9e46c26a1e0083e1186abc610efa3d9a36180/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", size = 1822900, upload-time = "2024-12-18T11:28:34.507Z" }, - { url = "https://files.pythonhosted.org/packages/f6/31/4240bc96025035500c18adc149aa6ffdf1a0062a4b525c932065ceb4d868/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", size = 1869177, upload-time = "2024-12-18T11:28:36.488Z" }, - { url = "https://files.pythonhosted.org/packages/fa/20/02fbaadb7808be578317015c462655c317a77a7c8f0ef274bc016a784c54/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", size = 2038046, upload-time = "2024-12-18T11:28:39.409Z" }, - { url = "https://files.pythonhosted.org/packages/06/86/7f306b904e6c9eccf0668248b3f272090e49c275bc488a7b88b0823444a4/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", size = 2685386, upload-time = "2024-12-18T11:28:41.221Z" }, - { url = "https://files.pythonhosted.org/packages/8d/f0/49129b27c43396581a635d8710dae54a791b17dfc50c70164866bbf865e3/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", size = 1997060, upload-time = "2024-12-18T11:28:44.709Z" }, - { url = "https://files.pythonhosted.org/packages/0d/0f/943b4af7cd416c477fd40b187036c4f89b416a33d3cc0ab7b82708a667aa/pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", size = 2004870, upload-time = "2024-12-18T11:28:46.839Z" }, - { url = "https://files.pythonhosted.org/packages/35/40/aea70b5b1a63911c53a4c8117c0a828d6790483f858041f47bab0b779f44/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", size = 1999822, upload-time = "2024-12-18T11:28:48.896Z" }, - { url = "https://files.pythonhosted.org/packages/f2/b3/807b94fd337d58effc5498fd1a7a4d9d59af4133e83e32ae39a96fddec9d/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", size = 2130364, upload-time = "2024-12-18T11:28:50.755Z" }, - { url = "https://files.pythonhosted.org/packages/fc/df/791c827cd4ee6efd59248dca9369fb35e80a9484462c33c6649a8d02b565/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", size = 2158303, upload-time = "2024-12-18T11:28:54.122Z" }, - { url = "https://files.pythonhosted.org/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064, upload-time = "2024-12-18T11:28:56.074Z" }, - { url = "https://files.pythonhosted.org/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046, upload-time = "2024-12-18T11:28:58.107Z" }, - { url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092, upload-time = "2024-12-18T11:29:01.335Z" }, - { url = "https://files.pythonhosted.org/packages/41/b1/9bc383f48f8002f99104e3acff6cba1231b29ef76cfa45d1506a5cad1f84/pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b", size = 1892709, upload-time = "2024-12-18T11:29:03.193Z" }, - { url = "https://files.pythonhosted.org/packages/10/6c/e62b8657b834f3eb2961b49ec8e301eb99946245e70bf42c8817350cbefc/pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154", size = 1811273, upload-time = "2024-12-18T11:29:05.306Z" }, - { url = "https://files.pythonhosted.org/packages/ba/15/52cfe49c8c986e081b863b102d6b859d9defc63446b642ccbbb3742bf371/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9", size = 1823027, upload-time = "2024-12-18T11:29:07.294Z" }, - { url = "https://files.pythonhosted.org/packages/b1/1c/b6f402cfc18ec0024120602bdbcebc7bdd5b856528c013bd4d13865ca473/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9", size = 1868888, upload-time = "2024-12-18T11:29:09.249Z" }, - { url = "https://files.pythonhosted.org/packages/bd/7b/8cb75b66ac37bc2975a3b7de99f3c6f355fcc4d89820b61dffa8f1e81677/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1", size = 2037738, upload-time = "2024-12-18T11:29:11.23Z" }, - { url = "https://files.pythonhosted.org/packages/c8/f1/786d8fe78970a06f61df22cba58e365ce304bf9b9f46cc71c8c424e0c334/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a", size = 2685138, upload-time = "2024-12-18T11:29:16.396Z" }, - { url = "https://files.pythonhosted.org/packages/a6/74/d12b2cd841d8724dc8ffb13fc5cef86566a53ed358103150209ecd5d1999/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e", size = 1997025, upload-time = "2024-12-18T11:29:20.25Z" }, - { url = "https://files.pythonhosted.org/packages/a0/6e/940bcd631bc4d9a06c9539b51f070b66e8f370ed0933f392db6ff350d873/pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4", size = 2004633, upload-time = "2024-12-18T11:29:23.877Z" }, - { url = "https://files.pythonhosted.org/packages/50/cc/a46b34f1708d82498c227d5d80ce615b2dd502ddcfd8376fc14a36655af1/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27", size = 1999404, upload-time = "2024-12-18T11:29:25.872Z" }, - { url = "https://files.pythonhosted.org/packages/ca/2d/c365cfa930ed23bc58c41463bae347d1005537dc8db79e998af8ba28d35e/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee", size = 2130130, upload-time = "2024-12-18T11:29:29.252Z" }, - { url = "https://files.pythonhosted.org/packages/f4/d7/eb64d015c350b7cdb371145b54d96c919d4db516817f31cd1c650cae3b21/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1", size = 2157946, upload-time = "2024-12-18T11:29:31.338Z" }, - { url = "https://files.pythonhosted.org/packages/a4/99/bddde3ddde76c03b65dfd5a66ab436c4e58ffc42927d4ff1198ffbf96f5f/pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", size = 1834387, upload-time = "2024-12-18T11:29:33.481Z" }, - { url = "https://files.pythonhosted.org/packages/71/47/82b5e846e01b26ac6f1893d3c5f9f3a2eb6ba79be26eef0b759b4fe72946/pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", size = 1990453, upload-time = "2024-12-18T11:29:35.533Z" }, - { url = "https://files.pythonhosted.org/packages/51/b2/b2b50d5ecf21acf870190ae5d093602d95f66c9c31f9d5de6062eb329ad1/pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", size = 1885186, upload-time = "2024-12-18T11:29:37.649Z" }, - { url = "https://files.pythonhosted.org/packages/46/72/af70981a341500419e67d5cb45abe552a7c74b66326ac8877588488da1ac/pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e", size = 1891159, upload-time = "2024-12-18T11:30:54.382Z" }, - { url = "https://files.pythonhosted.org/packages/ad/3d/c5913cccdef93e0a6a95c2d057d2c2cba347815c845cda79ddd3c0f5e17d/pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8", size = 1768331, upload-time = "2024-12-18T11:30:58.178Z" }, - { url = "https://files.pythonhosted.org/packages/f6/f0/a3ae8fbee269e4934f14e2e0e00928f9346c5943174f2811193113e58252/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3", size = 1822467, upload-time = "2024-12-18T11:31:00.6Z" }, - { url = "https://files.pythonhosted.org/packages/d7/7a/7bbf241a04e9f9ea24cd5874354a83526d639b02674648af3f350554276c/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f", size = 1979797, upload-time = "2024-12-18T11:31:07.243Z" }, - { url = "https://files.pythonhosted.org/packages/4f/5f/4784c6107731f89e0005a92ecb8a2efeafdb55eb992b8e9d0a2be5199335/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133", size = 1987839, upload-time = "2024-12-18T11:31:09.775Z" }, - { url = "https://files.pythonhosted.org/packages/6d/a7/61246562b651dff00de86a5f01b6e4befb518df314c54dec187a78d81c84/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc", size = 1998861, upload-time = "2024-12-18T11:31:13.469Z" }, - { url = "https://files.pythonhosted.org/packages/86/aa/837821ecf0c022bbb74ca132e117c358321e72e7f9702d1b6a03758545e2/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50", size = 2116582, upload-time = "2024-12-18T11:31:17.423Z" }, - { url = "https://files.pythonhosted.org/packages/81/b0/5e74656e95623cbaa0a6278d16cf15e10a51f6002e3ec126541e95c29ea3/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9", size = 2151985, upload-time = "2024-12-18T11:31:19.901Z" }, - { url = "https://files.pythonhosted.org/packages/63/37/3e32eeb2a451fddaa3898e2163746b0cffbbdbb4740d38372db0490d67f3/pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151", size = 2004715, upload-time = "2024-12-18T11:31:22.821Z" }, + { url = "https://files.pythonhosted.org/packages/c6/90/32c9941e728d564b411d574d8ee0cf09b12ec978cb22b294995bae5549a5/pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146", size = 2107298, upload-time = "2025-11-04T13:39:04.116Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a8/61c96a77fe28993d9a6fb0f4127e05430a267b235a124545d79fea46dd65/pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2", size = 1901475, upload-time = "2025-11-04T13:39:06.055Z" }, + { url = "https://files.pythonhosted.org/packages/5d/b6/338abf60225acc18cdc08b4faef592d0310923d19a87fba1faf05af5346e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97", size = 1918815, upload-time = "2025-11-04T13:39:10.41Z" }, + { url = "https://files.pythonhosted.org/packages/d1/1c/2ed0433e682983d8e8cba9c8d8ef274d4791ec6a6f24c58935b90e780e0a/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9", size = 2065567, upload-time = "2025-11-04T13:39:12.244Z" }, + { url = "https://files.pythonhosted.org/packages/b3/24/cf84974ee7d6eae06b9e63289b7b8f6549d416b5c199ca2d7ce13bbcf619/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52", size = 2230442, upload-time = "2025-11-04T13:39:13.962Z" }, + { url = "https://files.pythonhosted.org/packages/fd/21/4e287865504b3edc0136c89c9c09431be326168b1eb7841911cbc877a995/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941", size = 2350956, upload-time = "2025-11-04T13:39:15.889Z" }, + { url = "https://files.pythonhosted.org/packages/a8/76/7727ef2ffa4b62fcab916686a68a0426b9b790139720e1934e8ba797e238/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a", size = 2068253, upload-time = "2025-11-04T13:39:17.403Z" }, + { url = "https://files.pythonhosted.org/packages/d5/8c/a4abfc79604bcb4c748e18975c44f94f756f08fb04218d5cb87eb0d3a63e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c", size = 2177050, upload-time = "2025-11-04T13:39:19.351Z" }, + { url = "https://files.pythonhosted.org/packages/67/b1/de2e9a9a79b480f9cb0b6e8b6ba4c50b18d4e89852426364c66aa82bb7b3/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2", size = 2147178, upload-time = "2025-11-04T13:39:21Z" }, + { url = "https://files.pythonhosted.org/packages/16/c1/dfb33f837a47b20417500efaa0378adc6635b3c79e8369ff7a03c494b4ac/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556", size = 2341833, upload-time = "2025-11-04T13:39:22.606Z" }, + { url = "https://files.pythonhosted.org/packages/47/36/00f398642a0f4b815a9a558c4f1dca1b4020a7d49562807d7bc9ff279a6c/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49", size = 2321156, upload-time = "2025-11-04T13:39:25.843Z" }, + { url = "https://files.pythonhosted.org/packages/7e/70/cad3acd89fde2010807354d978725ae111ddf6d0ea46d1ea1775b5c1bd0c/pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba", size = 1989378, upload-time = "2025-11-04T13:39:27.92Z" }, + { url = "https://files.pythonhosted.org/packages/76/92/d338652464c6c367e5608e4488201702cd1cbb0f33f7b6a85a60fe5f3720/pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9", size = 2013622, upload-time = "2025-11-04T13:39:29.848Z" }, + { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" }, + { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" }, + { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" }, + { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" }, + { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" }, + { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" }, + { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" }, + { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" }, + { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" }, + { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" }, + { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, + { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, + { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, + { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, + { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, + { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, + { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, + { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, + { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, + { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, + { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, + { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, + { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, + { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, + { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, + { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, + { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, + { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, + { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, + { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, + { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, + { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, + { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, + { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, + { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, + { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, + { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, + { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, + { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, + { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, + { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, + { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, + { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, + { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" }, + { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" }, + { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" }, + { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, + { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, + { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, + { url = "https://files.pythonhosted.org/packages/e6/b0/1a2aa41e3b5a4ba11420aba2d091b2d17959c8d1519ece3627c371951e73/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8", size = 2103351, upload-time = "2025-11-04T13:43:02.058Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ee/31b1f0020baaf6d091c87900ae05c6aeae101fa4e188e1613c80e4f1ea31/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a", size = 1925363, upload-time = "2025-11-04T13:43:05.159Z" }, + { url = "https://files.pythonhosted.org/packages/e1/89/ab8e86208467e467a80deaca4e434adac37b10a9d134cd2f99b28a01e483/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b", size = 2135615, upload-time = "2025-11-04T13:43:08.116Z" }, + { url = "https://files.pythonhosted.org/packages/99/0a/99a53d06dd0348b2008f2f30884b34719c323f16c3be4e6cc1203b74a91d/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2", size = 2175369, upload-time = "2025-11-04T13:43:12.49Z" }, + { url = "https://files.pythonhosted.org/packages/6d/94/30ca3b73c6d485b9bb0bc66e611cff4a7138ff9736b7e66bcf0852151636/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093", size = 2144218, upload-time = "2025-11-04T13:43:15.431Z" }, + { url = "https://files.pythonhosted.org/packages/87/57/31b4f8e12680b739a91f472b5671294236b82586889ef764b5fbc6669238/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a", size = 2329951, upload-time = "2025-11-04T13:43:18.062Z" }, + { url = "https://files.pythonhosted.org/packages/7d/73/3c2c8edef77b8f7310e6fb012dbc4b8551386ed575b9eb6fb2506e28a7eb/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963", size = 2318428, upload-time = "2025-11-04T13:43:20.679Z" }, + { url = "https://files.pythonhosted.org/packages/2f/02/8559b1f26ee0d502c74f9cca5c0d2fd97e967e083e006bbbb4e97f3a043a/pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a", size = 2147009, upload-time = "2025-11-04T13:43:23.286Z" }, + { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" }, + { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" }, + { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" }, + { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" }, + { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" }, + { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" }, ] [[package]] @@ -1134,41 +1194,51 @@ wheels = [ [[package]] name = "tomli" -version = "2.2.1" +version = "2.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } +sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, - { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, - { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, - { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, - { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, - { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, - { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, - { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, - { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, - { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, - { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, - { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, - { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, - { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, - { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, - { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, - { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, - { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, - { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, - { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, - { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, - { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, - { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, - { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, - { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, - { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, - { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, - { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, - { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, - { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, - { 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/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" }, + { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" }, + { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" }, + { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" }, + { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" }, + { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" }, + { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" }, + { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" }, + { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" }, + { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" }, + { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" }, + { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" }, + { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" }, + { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" }, + { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" }, + { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" }, + { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" }, + { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" }, + { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" }, + { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" }, + { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" }, + { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" }, + { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" }, + { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" }, + { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" }, + { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" }, + { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" }, + { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" }, + { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" }, + { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" }, + { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" }, + { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" }, + { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" }, + { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" }, + { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" }, + { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, ] [[package]] @@ -1185,11 +1255,23 @@ wheels = [ [[package]] name = "typing-extensions" -version = "4.12.2" +version = "4.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321, upload-time = "2024-06-07T18:52:15.995Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438, upload-time = "2024-06-07T18:52:13.582Z" }, + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, ] [[package]] -- 2.49.1 From 07f301db1c613db8136436fedd47af33d90dea75 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Sun, 23 Nov 2025 02:22:47 -0300 Subject: [PATCH 49/96] feat: removes python3.10 specific broken test --- tests/parser/test_string_type_parser.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/tests/parser/test_string_type_parser.py b/tests/parser/test_string_type_parser.py index 191ccd8..eb8de81 100644 --- a/tests/parser/test_string_type_parser.py +++ b/tests/parser/test_string_type_parser.py @@ -169,7 +169,7 @@ class TestStringTypeParser(TestCase): properties = { "type": "string", "format": "time", - "examples": ["14:30:00", "09:15:30.500", "23:59:59Z", "10:00:00+02:00"], + "examples": ["14:30:00", "09:15:30.500", "10:00:00+02:00"], } type_parsing, type_validator = parser.from_properties("placeholder", properties) @@ -180,7 +180,6 @@ class TestStringTypeParser(TestCase): [ time(hour=14, minute=30, second=0), time(hour=9, minute=15, second=30, microsecond=500_000), - time(hour=23, minute=59, second=59, tzinfo=timezone.utc), time(hour=10, minute=0, second=0, tzinfo=timezone(timedelta(hours=2))), ], ) @@ -255,7 +254,6 @@ class TestStringTypeParser(TestCase): "format": "date-time", "examples": [ "2025-11-17T11:15:00", - "2025-11-17T11:15:00Z", "2025-11-17T11:15:00+01:00", "2025-11-17T11:15:00.123456-05:00", ], @@ -268,15 +266,6 @@ class TestStringTypeParser(TestCase): type_validator["examples"], [ datetime(year=2025, month=11, day=17, hour=11, minute=15, second=0), - datetime( - year=2025, - month=11, - day=17, - hour=11, - minute=15, - second=0, - tzinfo=timezone.utc, - ), datetime( year=2025, month=11, -- 2.49.1 From ebcc8a295ea7b8b28bc8abeee23010627b5cff71 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Sun, 23 Nov 2025 02:37:14 -0300 Subject: [PATCH 50/96] feat: remove unecessary dependency group --- pyproject.toml | 5 ----- uv.lock | 16 ---------------- 2 files changed, 21 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8e37e7d..199edcd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,11 +47,6 @@ dev = [ homepage = "https://github.com/HideyoshiNakazone/jambo" repository = "https://github.com/HideyoshiNakazone/jambo.git" -[project.optional-dependencies] -str-fmt = [ - "isodate>=0.7.2", -] - # POE Tasks [tool.poe.tasks] diff --git a/uv.lock b/uv.lock index e4184b3..fabfe91 100644 --- a/uv.lock +++ b/uv.lock @@ -314,15 +314,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769, upload-time = "2022-07-01T12:21:02.467Z" }, ] -[[package]] -name = "isodate" -version = "0.7.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/4d/e940025e2ce31a8ce1202635910747e5a87cc3a6a6bb2d00973375014749/isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6", size = 29705, upload-time = "2024-10-08T23:04:11.5Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" }, -] - [[package]] name = "jambo" source = { editable = "." } @@ -332,11 +323,6 @@ dependencies = [ { name = "pydantic" }, ] -[package.optional-dependencies] -str-fmt = [ - { name = "isodate" }, -] - [package.dev-dependencies] dev = [ { name = "coverage" }, @@ -357,11 +343,9 @@ dev = [ [package.metadata] requires-dist = [ { name = "email-validator", specifier = ">=2.2.0" }, - { name = "isodate", marker = "extra == 'str-fmt'", specifier = ">=0.7.2" }, { name = "jsonschema", specifier = ">=4.23.0" }, { name = "pydantic", specifier = ">=2.12.4" }, ] -provides-extras = ["str-fmt"] [package.metadata.requires-dev] dev = [ -- 2.49.1 From c7e366cf08526b5c88e98ecbc4f0dc7e68ccd0fb Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Sun, 23 Nov 2025 02:42:54 -0300 Subject: [PATCH 51/96] feat: improves test coverage --- tests/parser/test_string_type_parser.py | 11 +++++++++++ tests/parser/test_type_parser.py | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/tests/parser/test_string_type_parser.py b/tests/parser/test_string_type_parser.py index eb8de81..0971588 100644 --- a/tests/parser/test_string_type_parser.py +++ b/tests/parser/test_string_type_parser.py @@ -310,3 +310,14 @@ class TestStringTypeParser(TestCase): timedelta(seconds=0.5), ], ) + + def test_string_parser_with_invalid_example_value(self): + with self.assertRaises(InvalidSchemaException): + StringTypeParser().from_properties( + "placeholder", + { + "type": "string", + "format": "email", + "examples": ["invalid-email"], + }, + ) diff --git a/tests/parser/test_type_parser.py b/tests/parser/test_type_parser.py index 748ea7e..0871867 100644 --- a/tests/parser/test_type_parser.py +++ b/tests/parser/test_type_parser.py @@ -20,3 +20,14 @@ class TestGenericTypeParser(TestCase): def test_get_impl_invalid_type(self): with self.assertRaises(InvalidSchemaException): GenericTypeParser._get_impl({"type": "invalid_type"}) + + def test_invalid_examples_not_list(self): + parser = StringTypeParser() + + properties = { + "type": "integer", + "examples": "this should be a list", + } + + with self.assertRaises(InvalidSchemaException): + parser.from_properties("placeholder", properties) -- 2.49.1 From 8b1520741b32237e7aaaad5f9f3aa2be93e67898 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Sun, 23 Nov 2025 14:31:48 -0300 Subject: [PATCH 52/96] feat: fixes and validates that objects have parsed examples --- jambo/parser/object_type_parser.py | 11 +++++-- tests/parser/test_object_type_parser.py | 38 +++++++++++++++++++++++++ tests/parser/test_string_type_parser.py | 22 +++++++------- 3 files changed, 57 insertions(+), 14 deletions(-) diff --git a/jambo/parser/object_type_parser.py b/jambo/parser/object_type_parser.py index 6cb60e7..516ffc5 100644 --- a/jambo/parser/object_type_parser.py +++ b/jambo/parser/object_type_parser.py @@ -21,13 +21,18 @@ class ObjectTypeParser(GenericTypeParser): properties.get("required", []), **kwargs, ) - type_properties = {} + type_properties = self.mappings_properties_builder(properties, **kwargs) - if "default" in properties: + if (default_value := type_properties.get("default")) is not None: type_properties["default_factory"] = lambda: type_parsing.model_validate( - properties["default"] + default_value ) + if (example_values := type_properties.get("examples")) is not None: + type_properties["examples"] = [ + type_parsing.model_validate(example) for example in example_values + ] + return type_parsing, type_properties @classmethod diff --git a/tests/parser/test_object_type_parser.py b/tests/parser/test_object_type_parser.py index 4adbc52..ed50167 100644 --- a/tests/parser/test_object_type_parser.py +++ b/tests/parser/test_object_type_parser.py @@ -22,6 +22,44 @@ class TestObjectTypeParser(TestCase): self.assertEqual(obj.name, "name") self.assertEqual(obj.age, 10) + def test_object_type_parser_with_object_example(self): + parser = ObjectTypeParser() + + properties = { + "type": "object", + "properties": { + "name": {"type": "string"}, + "age": {"type": "integer"}, + "address": { + "type": "object", + "properties": { + "street": {"type": "string"}, + "city": {"type": "string"}, + }, + "examples": [ + { + "street": "123 Main St", + "city": "Anytown", + } + ], + }, + }, + } + + parsed_type, type_validator = parser.from_properties_impl( + "placeholder", properties + ) + + model_schema = parsed_type.model_json_schema() + + # # Check example value + address_schema = model_schema["properties"]["address"] + self.assertIn("examples", address_schema) + + example_address = address_schema["examples"][0] + self.assertEqual(example_address["street"], "123 Main St") + self.assertEqual(example_address["city"], "Anytown") + def test_object_type_parser_with_default(self): parser = ObjectTypeParser() diff --git a/tests/parser/test_string_type_parser.py b/tests/parser/test_string_type_parser.py index 0971588..b00b487 100644 --- a/tests/parser/test_string_type_parser.py +++ b/tests/parser/test_string_type_parser.py @@ -288,6 +288,17 @@ class TestStringTypeParser(TestCase): ], ) + def test_string_parser_with_invalid_example_value(self): + with self.assertRaises(InvalidSchemaException): + StringTypeParser().from_properties( + "placeholder", + { + "type": "string", + "format": "email", + "examples": ["invalid-email"], + }, + ) + @unittest.skip("Duration parsing not yet implemented") def test_string_parser_with_timedelta_format(self): parser = StringTypeParser() @@ -310,14 +321,3 @@ class TestStringTypeParser(TestCase): timedelta(seconds=0.5), ], ) - - def test_string_parser_with_invalid_example_value(self): - with self.assertRaises(InvalidSchemaException): - StringTypeParser().from_properties( - "placeholder", - { - "type": "string", - "format": "email", - "examples": ["invalid-email"], - }, - ) -- 2.49.1 From 84292cf3c0ee211d0cc6488b3391eb7472c6daa1 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Sun, 23 Nov 2025 14:59:16 -0300 Subject: [PATCH 53/96] feat: fixes and validates so that arrays have parsed examples --- jambo/parser/array_type_parser.py | 32 ++++++++++++++++++++------ jambo/parser/object_type_parser.py | 4 ++-- tests/parser/test_array_type_parser.py | 16 +++++++++++++ 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/jambo/parser/array_type_parser.py b/jambo/parser/array_type_parser.py index 790a65a..787e2cc 100644 --- a/jambo/parser/array_type_parser.py +++ b/jambo/parser/array_type_parser.py @@ -2,21 +2,28 @@ from jambo.exceptions import InvalidSchemaException from jambo.parser._type_parser import GenericTypeParser from jambo.types.type_parser_options import TypeParserOptions -from typing_extensions import Iterable, TypeVar, Unpack +from typing_extensions import ( + Callable, + Generic, + Iterable, + Optional, + TypeVar, + Union, + Unpack, +) import copy V = TypeVar("V") +T = TypeVar("T", bound=list) -class ArrayTypeParser(GenericTypeParser): +class ArrayTypeParser(GenericTypeParser, Generic[T, V]): mapped_type = list json_schema_type = "type:array" - default_mappings = {"description": "description"} - type_mappings = { "maxItems": "max_length", "minItems": "min_length", @@ -43,14 +50,25 @@ class ArrayTypeParser(GenericTypeParser): mapped_properties = self.mappings_properties_builder(properties, **kwargs) - if "default" in properties or not kwargs.get("required", False): + if ( + default_value := mapped_properties.pop("default", None) + ) is not None or not kwargs.get("required", False): mapped_properties["default_factory"] = self._build_default_factory( - properties.get("default"), wrapper_type + default_value, wrapper_type ) + if ( + example_values := mapped_properties.pop("example_values", None) + ) is not None: + mapped_properties["examples"] = [ + wrapper_type(example) for example in example_values + ] + return field_type, mapped_properties - def _build_default_factory(self, default_list, wrapper_type): + def _build_default_factory( + self, default_list: Optional[Iterable[V]], wrapper_type: type[V] + ) -> Callable[[], Union[V, None]]: if default_list is None: return lambda: None diff --git a/jambo/parser/object_type_parser.py b/jambo/parser/object_type_parser.py index 516ffc5..b625a6e 100644 --- a/jambo/parser/object_type_parser.py +++ b/jambo/parser/object_type_parser.py @@ -23,12 +23,12 @@ class ObjectTypeParser(GenericTypeParser): ) type_properties = self.mappings_properties_builder(properties, **kwargs) - if (default_value := type_properties.get("default")) is not None: + if (default_value := type_properties.pop("default", None)) is not None: type_properties["default_factory"] = lambda: type_parsing.model_validate( default_value ) - if (example_values := type_properties.get("examples")) is not None: + if (example_values := type_properties.pop("examples", None)) is not None: type_properties["examples"] = [ type_parsing.model_validate(example) for example in example_values ] diff --git a/tests/parser/test_array_type_parser.py b/tests/parser/test_array_type_parser.py index dc9212f..d394095 100644 --- a/tests/parser/test_array_type_parser.py +++ b/tests/parser/test_array_type_parser.py @@ -109,3 +109,19 @@ class TestArrayTypeParser(TestCase): with self.assertRaises(InvalidSchemaException): parser.from_properties("placeholder", properties) + + def test_array_parser_with_examples(self): + parser = ArrayTypeParser() + + properties = { + "items": {"type": "integer"}, + "examples": [ + [1, 2, 3], + [4, 5, 6], + ], + } + + type_parsing, type_validator = parser.from_properties("placeholder", properties) + + self.assertEqual(type_parsing.__origin__, list) + self.assertEqual(type_validator["examples"], [[1, 2, 3], [4, 5, 6]]) -- 2.49.1 From 9823e69329ca7f37714bb44a09f8b749a9731e1f Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Sun, 23 Nov 2025 15:02:50 -0300 Subject: [PATCH 54/96] feat: fixes test for object example --- tests/parser/test_object_type_parser.py | 34 ++++++++----------------- 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/tests/parser/test_object_type_parser.py b/tests/parser/test_object_type_parser.py index ed50167..99ddcd6 100644 --- a/tests/parser/test_object_type_parser.py +++ b/tests/parser/test_object_type_parser.py @@ -30,35 +30,21 @@ class TestObjectTypeParser(TestCase): "properties": { "name": {"type": "string"}, "age": {"type": "integer"}, - "address": { - "type": "object", - "properties": { - "street": {"type": "string"}, - "city": {"type": "string"}, - }, - "examples": [ - { - "street": "123 Main St", - "city": "Anytown", - } - ], - }, }, + "examples": [ + { + "name": "example_name", + "age": 30, + } + ], } - parsed_type, type_validator = parser.from_properties_impl( - "placeholder", properties - ) + _, type_validator = parser.from_properties_impl("placeholder", properties) - model_schema = parsed_type.model_json_schema() + test_example = type_validator["examples"][0] - # # Check example value - address_schema = model_schema["properties"]["address"] - self.assertIn("examples", address_schema) - - example_address = address_schema["examples"][0] - self.assertEqual(example_address["street"], "123 Main St") - self.assertEqual(example_address["city"], "Anytown") + self.assertEqual(test_example.name, "example_name") + self.assertEqual(test_example.age, 30) def test_object_type_parser_with_default(self): parser = ObjectTypeParser() -- 2.49.1 From cfbe1f38c8141bb4d73d0259941f093083a8435a Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Sun, 23 Nov 2025 17:33:51 -0300 Subject: [PATCH 55/96] feat: fixes broken example property extraction in array type parser --- jambo/parser/array_type_parser.py | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/jambo/parser/array_type_parser.py b/jambo/parser/array_type_parser.py index 787e2cc..d1f5870 100644 --- a/jambo/parser/array_type_parser.py +++ b/jambo/parser/array_type_parser.py @@ -3,23 +3,14 @@ from jambo.parser._type_parser import GenericTypeParser from jambo.types.type_parser_options import TypeParserOptions from typing_extensions import ( - Callable, - Generic, Iterable, - Optional, - TypeVar, - Union, Unpack, ) import copy -V = TypeVar("V") -T = TypeVar("T", bound=list) - - -class ArrayTypeParser(GenericTypeParser, Generic[T, V]): +class ArrayTypeParser(GenericTypeParser): mapped_type = list json_schema_type = "type:array" @@ -57,18 +48,14 @@ class ArrayTypeParser(GenericTypeParser, Generic[T, V]): default_value, wrapper_type ) - if ( - example_values := mapped_properties.pop("example_values", None) - ) is not None: + if (example_values := mapped_properties.pop("examples", None)) is not None: mapped_properties["examples"] = [ wrapper_type(example) for example in example_values ] return field_type, mapped_properties - def _build_default_factory( - self, default_list: Optional[Iterable[V]], wrapper_type: type[V] - ) -> Callable[[], Union[V, None]]: + def _build_default_factory(self, default_list, wrapper_type): if default_list is None: return lambda: None -- 2.49.1 From ffbd124cf91034e6fa3cafb2fee5883423517df4 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Sun, 23 Nov 2025 18:59:47 -0300 Subject: [PATCH 56/96] feat: adds example to allOf --- jambo/parser/allof_type_parser.py | 3 ++ tests/parser/test_allof_type_parser.py | 47 ++++++++++++++++++++++++++ tests/parser/test_anyof_type_parser.py | 17 ++++++++++ 3 files changed, 67 insertions(+) diff --git a/jambo/parser/allof_type_parser.py b/jambo/parser/allof_type_parser.py index 92c5ff2..2c0af9f 100644 --- a/jambo/parser/allof_type_parser.py +++ b/jambo/parser/allof_type_parser.py @@ -27,6 +27,9 @@ class AllOfTypeParser(GenericTypeParser): sub_properties ) + if (examples := properties.get("examples")) is not None: + combined_properties["examples"] = examples + return parser().from_properties_impl(name, combined_properties, **kwargs) @staticmethod diff --git a/tests/parser/test_allof_type_parser.py b/tests/parser/test_allof_type_parser.py index f99c657..a957161 100644 --- a/tests/parser/test_allof_type_parser.py +++ b/tests/parser/test_allof_type_parser.py @@ -309,3 +309,50 @@ class TestAllOfTypeParser(TestCase): with self.assertRaises(InvalidSchemaException): AllOfTypeParser().from_properties("placeholder", properties) + + def test_all_of_with_root_examples(self): + """ + Tests the AllOfTypeParser with examples. + """ + + properties = { + "type": "object", + "allOf": [ + { + "properties": { + "name": { + "type": "string", + "minLength": 1, + } + }, + }, + { + "properties": { + "name": { + "type": "string", + "maxLength": 4, + } + }, + }, + ], + "examples": [ + {"name": "John"}, + {"name": "Jane"}, + {"name": "Doe"}, + {"name": "Jack"}, + ], + } + + type_parsed, type_properties = AllOfTypeParser().from_properties( + "placeholder", properties + ) + + self.assertEqual( + type_properties["examples"], + [ + type_parsed(name="John"), + type_parsed(name="Jane"), + type_parsed(name="Doe"), + type_parsed(name="Jack"), + ], + ) diff --git a/tests/parser/test_anyof_type_parser.py b/tests/parser/test_anyof_type_parser.py index d7740fd..811bc1b 100644 --- a/tests/parser/test_anyof_type_parser.py +++ b/tests/parser/test_anyof_type_parser.py @@ -98,3 +98,20 @@ class TestAnyOfTypeParser(TestCase): with self.assertRaises(InvalidSchemaException): AnyOfTypeParser().from_properties("placeholder", properties) + + def test_any_of_with_examples(self): + """ + Tests the AnyOfTypeParser with a string or int type and examples. + """ + + properties = { + "anyOf": [ + {"type": "string"}, + {"type": "integer"}, + ], + "examples": ["100", 100], + } + + _, type_validator = AnyOfTypeParser().from_properties("placeholder", properties) + + self.assertEqual(type_validator["examples"], ["100", 100]) -- 2.49.1 From e43e92cb9e1a06bfe78f37ac7e7c21216ea22170 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Sun, 23 Nov 2025 20:03:19 -0300 Subject: [PATCH 57/96] feat: minor adjustments to oneOf and adds tests for examples in allOf, oneOf, anyOf --- jambo/parser/oneof_type_parser.py | 3 +- tests/parser/test_anyof_type_parser.py | 28 ++++++++++- tests/parser/test_oneof_type_parser.py | 68 ++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 3 deletions(-) diff --git a/jambo/parser/oneof_type_parser.py b/jambo/parser/oneof_type_parser.py index 4713d9c..11092ea 100644 --- a/jambo/parser/oneof_type_parser.py +++ b/jambo/parser/oneof_type_parser.py @@ -45,8 +45,7 @@ class OneOfTypeParser(GenericTypeParser): # they were added by OpenAPI and not all implementations may support them, # and they do not always generate a model one-to-one to the Pydantic model # TL;DR: Discriminators were added by OpenAPI and not a Official JSON Schema feature - discriminator = properties.get("discriminator") - if discriminator is not None: + if (discriminator := properties.get("discriminator")) is not None: validated_type = self._build_type_one_of_with_discriminator( subfield_types, discriminator ) diff --git a/tests/parser/test_anyof_type_parser.py b/tests/parser/test_anyof_type_parser.py index 811bc1b..7f26a80 100644 --- a/tests/parser/test_anyof_type_parser.py +++ b/tests/parser/test_anyof_type_parser.py @@ -99,7 +99,33 @@ class TestAnyOfTypeParser(TestCase): with self.assertRaises(InvalidSchemaException): AnyOfTypeParser().from_properties("placeholder", properties) - def test_any_of_with_examples(self): + def test_anyof_with_examples(self): + """ + Tests the AnyOfTypeParser with a string or int type and examples. + """ + + properties = { + "anyOf": [ + { + "type": "string", + "examples": ["example string"], + }, + { + "type": "integer", + "examples": [123], + }, + ], + } + + parsed_type, _ = AnyOfTypeParser().from_properties("placeholder", properties) + + type_1, type_2 = get_args(parsed_type) + + self.assertEqual(get_args(type_1)[1].examples, ["example string"]) + + self.assertEqual(get_args(type_2)[1].examples, [123]) + + def test_any_of_with_root_examples(self): """ Tests the AnyOfTypeParser with a string or int type and examples. """ diff --git a/tests/parser/test_oneof_type_parser.py b/tests/parser/test_oneof_type_parser.py index 14bf942..8fa3900 100644 --- a/tests/parser/test_oneof_type_parser.py +++ b/tests/parser/test_oneof_type_parser.py @@ -532,3 +532,71 @@ class TestOneOfTypeParser(TestCase): # Invalid: Wrong properties for the type with self.assertRaises(ValidationError): Model(shape={"type": "circle", "width": 10}) + + def test_oneof_with_examples(self): + schema = { + "title": "ExampleTest", + "type": "object", + "properties": { + "value": { + "oneOf": [ + { + "type": "string", + "examples": ["example1", "example2"], + }, + { + "type": "integer", + "examples": [1, 2, 3], + }, + ] + } + }, + "required": ["value"], + } + + Model = SchemaConverter.build(schema) + + # Since Pydantic does not natively support oneOf and the validation + # is done via a custom validator, the `value` is represented using `anyOf` + model_schema = Model.model_json_schema() + + self.assertEqual( + model_schema["properties"]["value"]["anyOf"][0]["examples"], + ["example1", "example2"], + ) + + self.assertEqual( + model_schema["properties"]["value"]["anyOf"][1]["examples"], + [1, 2, 3], + ) + + def test_oneof_with_root_examples(self): + schema = { + "title": "ExampleTest", + "type": "object", + "properties": { + "value": { + "oneOf": [ + { + "type": "string", + }, + { + "type": "integer", + }, + ], + "examples": ["example1", 2], + } + }, + "required": ["value"], + } + + Model = SchemaConverter.build(schema) + + # Since Pydantic does not natively support oneOf and the validation + # is done via a custom validator, the `value` is represented using `anyOf` + model_schema = Model.model_json_schema() + + self.assertEqual( + model_schema["properties"]["value"]["examples"], + ["example1", 2], + ) -- 2.49.1 From 90639b6426d704b0afb46ca5ec43543075b002e2 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Sun, 23 Nov 2025 20:05:28 -0300 Subject: [PATCH 58/96] chore: subs typing import to typing_extensions --- jambo/parser/string_type_parser.py | 3 +-- tests/parser/test_ref_type_parser.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/jambo/parser/string_type_parser.py b/jambo/parser/string_type_parser.py index 1d81268..4c18761 100644 --- a/jambo/parser/string_type_parser.py +++ b/jambo/parser/string_type_parser.py @@ -3,11 +3,10 @@ from jambo.parser._type_parser import GenericTypeParser from jambo.types.type_parser_options import TypeParserOptions from pydantic import AnyUrl, EmailStr -from typing_extensions import Unpack +from typing_extensions import Any, Unpack from datetime import date, datetime, time, timedelta from ipaddress import IPv4Address, IPv6Address -from typing import Any from uuid import UUID diff --git a/tests/parser/test_ref_type_parser.py b/tests/parser/test_ref_type_parser.py index 44b0949..e82c9c9 100644 --- a/tests/parser/test_ref_type_parser.py +++ b/tests/parser/test_ref_type_parser.py @@ -2,8 +2,8 @@ from jambo.exceptions import InternalAssertionException, InvalidSchemaException from jambo.parser import ObjectTypeParser, RefTypeParser from pydantic import ValidationError +from typing_extensions import ForwardRef -from typing import ForwardRef from unittest import TestCase -- 2.49.1 From 503605927295dc74bee1bf76c0ebe275e4f86f20 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Sun, 23 Nov 2025 20:09:03 -0300 Subject: [PATCH 59/96] feat: adds tests for examples in ref --- tests/parser/test_ref_type_parser.py | 35 ++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/parser/test_ref_type_parser.py b/tests/parser/test_ref_type_parser.py index e82c9c9..c198a24 100644 --- a/tests/parser/test_ref_type_parser.py +++ b/tests/parser/test_ref_type_parser.py @@ -485,3 +485,38 @@ class TestRefTypeParser(TestCase): self.assertEqual(obj.name, "John") self.assertEqual(obj.age, 30) + + def test_ref_type_parser_with_def_with_examples(self): + properties = { + "title": "person", + "$ref": "#/$defs/person", + "$defs": { + "person": { + "type": "object", + "properties": { + "name": {"type": "string"}, + "age": {"type": "integer"}, + }, + } + }, + "examples": [ + {"name": "John", "age": 30}, + {"name": "Jane", "age": 25}, + ], + } + + _, type_validator = RefTypeParser().from_properties( + "person", + properties, + context=properties, + ref_cache={}, + required=True, + ) + + self.assertEqual( + type_validator.get("examples"), + [ + {"name": "John", "age": 30}, + {"name": "Jane", "age": 25}, + ], + ) -- 2.49.1 From a0d15726d4fc6e379dc79e1728fcdf15ea1eb03e Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Sun, 23 Nov 2025 20:17:16 -0300 Subject: [PATCH 60/96] feat: adds title and deprecated to the list of default mappings in the GenericTypeParser --- jambo/parser/_type_parser.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jambo/parser/_type_parser.py b/jambo/parser/_type_parser.py index 15ceb80..efca162 100644 --- a/jambo/parser/_type_parser.py +++ b/jambo/parser/_type_parser.py @@ -19,6 +19,8 @@ class GenericTypeParser(ABC, Generic[T]): "default": "default", "description": "description", "examples": "examples", + "title": "title", + "deprecated": "deprecated", } @abstractmethod -- 2.49.1 From f80a1bbda35246d198dadef9d17d4db1872ac00b Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Sun, 23 Nov 2025 21:40:54 -0300 Subject: [PATCH 61/96] feat: fixes error of multiple forwardref with same name --- jambo/parser/oneof_type_parser.py | 8 +++--- tests/test_schema_converter.py | 47 +++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/jambo/parser/oneof_type_parser.py b/jambo/parser/oneof_type_parser.py index 11092ea..3c55763 100644 --- a/jambo/parser/oneof_type_parser.py +++ b/jambo/parser/oneof_type_parser.py @@ -29,11 +29,11 @@ class OneOfTypeParser(GenericTypeParser): mapped_properties = self.mappings_properties_builder(properties, **kwargs) - sub_properties = properties["oneOf"] - sub_types = [ - GenericTypeParser.type_from_properties(name, subProperty, **kwargs) - for subProperty in sub_properties + GenericTypeParser.type_from_properties( + f"{name}_sub{i}", subProperty, **kwargs + ) + for i, subProperty in enumerate(properties["oneOf"]) ] if not kwargs.get("required", False): diff --git a/tests/test_schema_converter.py b/tests/test_schema_converter.py index 6f2b15a..f8a0f48 100644 --- a/tests/test_schema_converter.py +++ b/tests/test_schema_converter.py @@ -1,5 +1,6 @@ from jambo import SchemaConverter from jambo.exceptions import InvalidSchemaException, UnsupportedSchemaException +from jambo.types import JSONSchema from pydantic import AnyUrl, BaseModel, ValidationError @@ -791,3 +792,49 @@ class TestSchemaConverter(TestCase): with self.assertRaises(ValidationError): Model(a_thing="not none") + + def test_scoped_ref_schema(self): + schema: JSONSchema = { + "title": "Example Schema", + "type": "object", + "properties": { + "operating_system": { + "oneOf": [ + {"$ref": "#/$defs/operating_system"}, + { + "type": "object", + "properties": { + "creation": {"$ref": "#/$defs/operating_system"}, + "reinstallation": {"$ref": "#/$defs/operating_system"}, + }, + "required": ["creation", "reinstallation"], + }, + ] + }, + }, + "$defs": { + "operating_system": { + "type": "object", + "properties": { + "name": {"type": "string"}, + "version": {"type": "string"}, + }, + "required": ["name", "version"], + } + }, + } + + schema_type = SchemaConverter.build(schema) + + _ = schema_type.model_validate( + {"operating_system": {"name": "Ubuntu", "version": "20.04"}} + ) + + _ = schema_type.model_validate( + { + "operating_system": { + "creation": {"name": "Ubuntu", "version": "20.04"}, + "reinstallation": {"name": "Ubuntu", "version": "22.04"}, + } + } + ) -- 2.49.1 From f15913c58edf211de6c234a9cecffe027e93b3b9 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Sun, 23 Nov 2025 22:09:55 -0300 Subject: [PATCH 62/96] feat: tests that the generated fields in the oneOf parser have unique names and applies the same logic to the anyOf parser --- jambo/parser/anyof_type_parser.py | 6 ++++-- tests/test_schema_converter.py | 20 +++++++++----------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/jambo/parser/anyof_type_parser.py b/jambo/parser/anyof_type_parser.py index 4b961ac..9b09754 100644 --- a/jambo/parser/anyof_type_parser.py +++ b/jambo/parser/anyof_type_parser.py @@ -30,8 +30,10 @@ class AnyOfTypeParser(GenericTypeParser): sub_properties = properties["anyOf"] sub_types = [ - GenericTypeParser.type_from_properties(name, subProperty, **kwargs) - for subProperty in sub_properties + GenericTypeParser.type_from_properties( + f"{name}_sub{i}", subProperty, **kwargs + ) + for i, subProperty in enumerate(sub_properties) ] if not kwargs.get("required", False): diff --git a/tests/test_schema_converter.py b/tests/test_schema_converter.py index f8a0f48..77c88c9 100644 --- a/tests/test_schema_converter.py +++ b/tests/test_schema_converter.py @@ -3,6 +3,7 @@ from jambo.exceptions import InvalidSchemaException, UnsupportedSchemaException from jambo.types import JSONSchema from pydantic import AnyUrl, BaseModel, ValidationError +from typing_extensions import get_args from ipaddress import IPv4Address, IPv6Address from unittest import TestCase @@ -826,15 +827,12 @@ class TestSchemaConverter(TestCase): schema_type = SchemaConverter.build(schema) - _ = schema_type.model_validate( - {"operating_system": {"name": "Ubuntu", "version": "20.04"}} - ) + # check for me that the types generated by the oneOf in the typing.Annotated have different names + operating_system_field = schema_type.model_fields["operating_system"] - _ = schema_type.model_validate( - { - "operating_system": { - "creation": {"name": "Ubuntu", "version": "20.04"}, - "reinstallation": {"name": "Ubuntu", "version": "22.04"}, - } - } - ) + arg1, arg2 = get_args(operating_system_field.annotation) + + first_type = get_args(arg1)[0] + second_type = get_args(arg2)[0] + + self.assertNotEqual(first_type.__name__, second_type.__name__) -- 2.49.1 From 2da409e6df81bfc4c56481775d74ffbc0cd28989 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Mon, 24 Nov 2025 17:34:29 +0000 Subject: [PATCH 63/96] fix: fixes invalid subobject required --- jambo/parser/object_type_parser.py | 10 +++++++--- tests/test_schema_converter.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/jambo/parser/object_type_parser.py b/jambo/parser/object_type_parser.py index b625a6e..57bc177 100644 --- a/jambo/parser/object_type_parser.py +++ b/jambo/parser/object_type_parser.py @@ -23,9 +23,13 @@ class ObjectTypeParser(GenericTypeParser): ) type_properties = self.mappings_properties_builder(properties, **kwargs) - if (default_value := type_properties.pop("default", None)) is not None: - type_properties["default_factory"] = lambda: type_parsing.model_validate( - default_value + if ( + default_value := type_properties.pop("default", None) + ) is not None or not kwargs.get("required", False): + type_properties["default_factory"] = ( + lambda: type_parsing.model_validate(default_value) + if default_value is not None + else None ) if (example_values := type_properties.pop("examples", None)) is not None: diff --git a/tests/test_schema_converter.py b/tests/test_schema_converter.py index 77c88c9..cbc6f72 100644 --- a/tests/test_schema_converter.py +++ b/tests/test_schema_converter.py @@ -836,3 +836,33 @@ class TestSchemaConverter(TestCase): second_type = get_args(arg2)[0] self.assertNotEqual(first_type.__name__, second_type.__name__) + + def test_object_invalid_require(self): + # https://github.com/HideyoshiNakazone/jambo/issues/60 + object_ = SchemaConverter.build( + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "TEST", + "type": "object", + "required": ["title"], + "properties": { + "title": { + "type": "string", + "description": "The title of the object", + }, + "description": { + "type": "object", + "properties": { + "summary": { + "type": "string", + }, + "details": { + "type": "string", + }, + }, + }, + }, + } + ) + + self.assertFalse(object_.model_fields["description"].is_required()) # FAIL -- 2.49.1 From 44fa0cf16aa68b0a9e8f1a0240ed5cfda3821d36 Mon Sep 17 00:00:00 2001 From: JCHacking Date: Mon, 24 Nov 2025 19:31:16 +0100 Subject: [PATCH 64/96] ci(github-actions): correct label of bug --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 875e289..4dec07c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -2,7 +2,7 @@ name: Bug report about: Create a report to help us improve title: "[BUG] Title Here" -labels: enhancement +labels: bug assignees: HideyoshiNakazone --- -- 2.49.1 From 10bad254d7359fa711ce8f15ff2ca6e77e6e07d2 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Mon, 24 Nov 2025 01:14:40 -0300 Subject: [PATCH 65/96] feat: initial implementation of instance level ref cache --- jambo/parser/ref_type_parser.py | 3 +- jambo/schema_converter.py | 65 ++++++++++++++++++++++++++---- jambo/types/__init__.py | 3 +- jambo/types/type_parser_options.py | 7 +++- 4 files changed, 67 insertions(+), 11 deletions(-) diff --git a/jambo/parser/ref_type_parser.py b/jambo/parser/ref_type_parser.py index 123260f..dbf2cb2 100644 --- a/jambo/parser/ref_type_parser.py +++ b/jambo/parser/ref_type_parser.py @@ -1,5 +1,6 @@ from jambo.exceptions import InternalAssertionException, InvalidSchemaException from jambo.parser import GenericTypeParser +from jambo.types import RefCacheDict from jambo.types.json_schema_type import JSONSchema from jambo.types.type_parser_options import TypeParserOptions @@ -72,7 +73,7 @@ class RefTypeParser(GenericTypeParser): return mapped_type def _get_ref_from_cache( - self, ref_name: str, ref_cache: dict[str, ForwardRef | type | None] + self, ref_name: str, ref_cache: RefCacheDict ) -> RefType | type | None: try: ref_state = ref_cache[ref_name] diff --git a/jambo/schema_converter.py b/jambo/schema_converter.py index 3d38dc7..7550b34 100644 --- a/jambo/schema_converter.py +++ b/jambo/schema_converter.py @@ -1,10 +1,11 @@ from jambo.exceptions import InvalidSchemaException, UnsupportedSchemaException from jambo.parser import ObjectTypeParser, RefTypeParser -from jambo.types import JSONSchema +from jambo.types import JSONSchema, RefCacheDict from jsonschema.exceptions import SchemaError from jsonschema.validators import validator_for from pydantic import BaseModel +from typing_extensions import Optional class SchemaConverter: @@ -16,13 +17,44 @@ class SchemaConverter: fields and types. The generated model can be used for data validation and serialization. """ - @staticmethod - def build(schema: JSONSchema) -> type[BaseModel]: + def __init__(self, ref_cache: Optional[RefCacheDict] = None) -> None: + if ref_cache is None: + ref_cache = dict() + self._ref_cache = ref_cache + + def build_with_instance( + self, + schema: JSONSchema, + ref_cache: Optional[RefCacheDict] = None, + with_clean_cache: bool = True, + ) -> type[BaseModel]: """ Converts a JSON Schema to a Pydantic model. - :param schema: The JSON Schema to convert. - :return: A Pydantic model class. + :param schema: The JSON Schema to convert. + :param ref_cache: An optional reference cache to use during conversion, if provided `with_clean_cache` will be ignored. + :param with_clean_cache: Whether to use a clean reference cache for this conversion. Set to True due to API compatibility. Will be set to False in future versions. + :return: The generated Pydantic model. """ + if ref_cache is None: + ref_cache = self._ref_cache + + return self.build(schema, ref_cache, with_clean_cache) + + @staticmethod + def build( + schema: JSONSchema, + ref_cache: Optional[RefCacheDict] = None, + with_clean_cache: bool = True, + ) -> type[BaseModel]: + """ + Converts a JSON Schema to a Pydantic model. + :param schema: The JSON Schema to convert. + :param ref_cache: An optional reference cache to use during conversion, if provided `with_clean_cache` will be ignored. + :param with_clean_cache: Whether to use a clean reference cache for this conversion. Set to rue due to API compatibility. Will be set to False in future versions. + :return: The generated Pydantic model. + """ + if ref_cache is None or with_clean_cache: + ref_cache = dict() try: validator = validator_for(schema) @@ -46,7 +78,7 @@ class SchemaConverter: schema.get("properties", {}), schema.get("required", []), context=schema, - ref_cache=dict(), + ref_cache=ref_cache, required=True, ) @@ -55,7 +87,7 @@ class SchemaConverter: schema["title"], schema, context=schema, - ref_cache=dict(), + ref_cache=ref_cache, required=True, ) return parsed_model @@ -68,6 +100,25 @@ class SchemaConverter: unsupported_field=unsupported_type, ) + def clear_ref_cache(self) -> None: + """ + Clears the reference cache. + """ + self._ref_cache.clear() + + def get_cached_ref(self, ref_name: str): + """ + Gets a cached reference from the reference cache. + :param ref_name: The name of the reference to get. + :return: The cached reference, or None if not found. + """ + cached_type = self._ref_cache.get(ref_name) + + if isinstance(cached_type, type): + return cached_type + + return None + @staticmethod def _get_schema_type(schema: JSONSchema) -> str | None: """ diff --git a/jambo/types/__init__.py b/jambo/types/__init__.py index d5c88a2..35c5839 100644 --- a/jambo/types/__init__.py +++ b/jambo/types/__init__.py @@ -4,7 +4,7 @@ from .json_schema_type import ( JSONSchemaType, JSONType, ) -from .type_parser_options import TypeParserOptions +from .type_parser_options import RefCacheDict, TypeParserOptions __all__ = [ @@ -12,5 +12,6 @@ __all__ = [ "JSONSchemaNativeTypes", "JSONType", "JSONSchema", + "RefCacheDict", "TypeParserOptions", ] diff --git a/jambo/types/type_parser_options.py b/jambo/types/type_parser_options.py index baf518b..4b41f21 100644 --- a/jambo/types/type_parser_options.py +++ b/jambo/types/type_parser_options.py @@ -1,9 +1,12 @@ from jambo.types.json_schema_type import JSONSchema -from typing_extensions import ForwardRef, TypedDict +from typing_extensions import ForwardRef, MutableMapping, TypedDict + + +RefCacheDict = MutableMapping[str, ForwardRef | type | None] class TypeParserOptions(TypedDict): required: bool context: JSONSchema - ref_cache: dict[str, ForwardRef | type | None] + ref_cache: RefCacheDict -- 2.49.1 From abc8bc2e40b0ec1bff8c4416589f062220e81372 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Mon, 24 Nov 2025 01:29:21 -0300 Subject: [PATCH 66/96] feat: saves object after parsing --- jambo/parser/object_type_parser.py | 16 ++++++++++++++ tests/parser/test_allof_type_parser.py | 28 ++++++++++++++----------- tests/parser/test_object_type_parser.py | 12 ++++++++--- 3 files changed, 41 insertions(+), 15 deletions(-) diff --git a/jambo/parser/object_type_parser.py b/jambo/parser/object_type_parser.py index 57bc177..d3c27ca 100644 --- a/jambo/parser/object_type_parser.py +++ b/jambo/parser/object_type_parser.py @@ -1,3 +1,4 @@ +from jambo.exceptions import InternalAssertionException from jambo.parser._type_parser import GenericTypeParser from jambo.types.json_schema_type import JSONSchema from jambo.types.type_parser_options import TypeParserOptions @@ -6,6 +7,8 @@ from pydantic import BaseModel, ConfigDict, Field, create_model from pydantic.fields import FieldInfo from typing_extensions import Unpack +import warnings + class ObjectTypeParser(GenericTypeParser): mapped_type = object @@ -15,6 +18,12 @@ class ObjectTypeParser(GenericTypeParser): def from_properties_impl( self, name: str, properties: JSONSchema, **kwargs: Unpack[TypeParserOptions] ) -> tuple[type[BaseModel], dict]: + ref_cache = kwargs.get("ref_cache") + if ref_cache is None: + raise InternalAssertionException( + "`ref_cache` must be provided in kwargs for RefTypeParser" + ) + type_parsing = self.to_model( name, properties.get("properties", {}), @@ -37,6 +46,13 @@ class ObjectTypeParser(GenericTypeParser): type_parsing.model_validate(example) for example in example_values ] + if name in ref_cache: + warnings.warn( + f"Type '{name}' is already in the ref_cache and will be overwritten.", + UserWarning, + ) + ref_cache[name] = type_parsing + return type_parsing, type_properties @classmethod diff --git a/tests/parser/test_allof_type_parser.py b/tests/parser/test_allof_type_parser.py index a957161..29cbf62 100644 --- a/tests/parser/test_allof_type_parser.py +++ b/tests/parser/test_allof_type_parser.py @@ -42,7 +42,7 @@ class TestAllOfTypeParser(TestCase): } type_parsing, type_validator = AllOfTypeParser().from_properties( - "placeholder", properties + "placeholder", properties, ref_cache={} ) with self.assertRaises(ValidationError): @@ -87,7 +87,7 @@ class TestAllOfTypeParser(TestCase): } type_parsing, type_validator = AllOfTypeParser().from_properties( - "placeholder", properties + "placeholder", properties, ref_cache={} ) with self.assertRaises(ValidationError): @@ -116,7 +116,7 @@ class TestAllOfTypeParser(TestCase): } type_parsing, type_validator = AllOfTypeParser().from_properties( - "placeholder", properties + "placeholder", properties, ref_cache={} ) self.assertEqual(type_parsing, str) @@ -137,7 +137,7 @@ class TestAllOfTypeParser(TestCase): } type_parsing, type_validator = AllOfTypeParser().from_properties( - "placeholder", properties + "placeholder", properties, ref_cache={} ) self.assertEqual(type_parsing, str) @@ -158,7 +158,7 @@ class TestAllOfTypeParser(TestCase): } with self.assertRaises(InvalidSchemaException): - AllOfTypeParser().from_properties("placeholder", properties) + AllOfTypeParser().from_properties("placeholder", properties, ref_cache={}) def test_all_of_invalid_type_not_present(self): properties = { @@ -171,7 +171,7 @@ class TestAllOfTypeParser(TestCase): } with self.assertRaises(InvalidSchemaException): - AllOfTypeParser().from_properties("placeholder", properties) + AllOfTypeParser().from_properties("placeholder", properties, ref_cache={}) def test_all_of_invalid_type_in_fields(self): properties = { @@ -184,7 +184,7 @@ class TestAllOfTypeParser(TestCase): } with self.assertRaises(InvalidSchemaException): - AllOfTypeParser().from_properties("placeholder", properties) + AllOfTypeParser().from_properties("placeholder", properties, ref_cache={}) def test_all_of_invalid_type_not_all_equal(self): """ @@ -200,7 +200,7 @@ class TestAllOfTypeParser(TestCase): } with self.assertRaises(InvalidSchemaException): - AllOfTypeParser().from_properties("placeholder", properties) + AllOfTypeParser().from_properties("placeholder", properties, ref_cache={}) def test_all_of_description_field(self): """ @@ -237,7 +237,9 @@ class TestAllOfTypeParser(TestCase): ], } - type_parsing, _ = AllOfTypeParser().from_properties("placeholder", properties) + type_parsing, _ = AllOfTypeParser().from_properties( + "placeholder", properties, ref_cache={} + ) self.assertEqual( type_parsing.model_json_schema()["properties"]["name"]["description"], @@ -275,7 +277,9 @@ class TestAllOfTypeParser(TestCase): ], } - type_parsing, _ = AllOfTypeParser().from_properties("placeholder", properties) + type_parsing, _ = AllOfTypeParser().from_properties( + "placeholder", properties, ref_cache={} + ) obj = type_parsing() self.assertEqual(obj.name, "John") self.assertEqual(obj.age, 30) @@ -308,7 +312,7 @@ class TestAllOfTypeParser(TestCase): } with self.assertRaises(InvalidSchemaException): - AllOfTypeParser().from_properties("placeholder", properties) + AllOfTypeParser().from_properties("placeholder", properties, ref_cache={}) def test_all_of_with_root_examples(self): """ @@ -344,7 +348,7 @@ class TestAllOfTypeParser(TestCase): } type_parsed, type_properties = AllOfTypeParser().from_properties( - "placeholder", properties + "placeholder", properties, ref_cache={} ) self.assertEqual( diff --git a/tests/parser/test_object_type_parser.py b/tests/parser/test_object_type_parser.py index 99ddcd6..025149d 100644 --- a/tests/parser/test_object_type_parser.py +++ b/tests/parser/test_object_type_parser.py @@ -15,7 +15,9 @@ class TestObjectTypeParser(TestCase): }, } - Model, _args = parser.from_properties_impl("placeholder", properties) + Model, _args = parser.from_properties_impl( + "placeholder", properties, ref_cache={} + ) obj = Model(name="name", age=10) @@ -39,7 +41,9 @@ class TestObjectTypeParser(TestCase): ], } - _, type_validator = parser.from_properties_impl("placeholder", properties) + _, type_validator = parser.from_properties_impl( + "placeholder", properties, ref_cache={} + ) test_example = type_validator["examples"][0] @@ -61,7 +65,9 @@ class TestObjectTypeParser(TestCase): }, } - _, type_validator = parser.from_properties_impl("placeholder", properties) + _, type_validator = parser.from_properties_impl( + "placeholder", properties, ref_cache={} + ) # Check default value default_obj = type_validator["default_factory"]() -- 2.49.1 From 4de711075e04385dae56f5533f4f8f35638f0385 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Mon, 24 Nov 2025 01:34:41 -0300 Subject: [PATCH 67/96] feat: removes unecessary api keyword --- jambo/schema_converter.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/jambo/schema_converter.py b/jambo/schema_converter.py index 7550b34..6358786 100644 --- a/jambo/schema_converter.py +++ b/jambo/schema_converter.py @@ -26,25 +26,18 @@ class SchemaConverter: self, schema: JSONSchema, ref_cache: Optional[RefCacheDict] = None, - with_clean_cache: bool = True, ) -> type[BaseModel]: """ Converts a JSON Schema to a Pydantic model. :param schema: The JSON Schema to convert. - :param ref_cache: An optional reference cache to use during conversion, if provided `with_clean_cache` will be ignored. - :param with_clean_cache: Whether to use a clean reference cache for this conversion. Set to True due to API compatibility. Will be set to False in future versions. + :param ref_cache: An optional reference cache to use during conversion. :return: The generated Pydantic model. """ - if ref_cache is None: - ref_cache = self._ref_cache - - return self.build(schema, ref_cache, with_clean_cache) + return self.build(schema, ref_cache or self._ref_cache) @staticmethod def build( - schema: JSONSchema, - ref_cache: Optional[RefCacheDict] = None, - with_clean_cache: bool = True, + schema: JSONSchema, ref_cache: Optional[RefCacheDict] = None ) -> type[BaseModel]: """ Converts a JSON Schema to a Pydantic model. @@ -53,7 +46,7 @@ class SchemaConverter: :param with_clean_cache: Whether to use a clean reference cache for this conversion. Set to rue due to API compatibility. Will be set to False in future versions. :return: The generated Pydantic model. """ - if ref_cache is None or with_clean_cache: + if ref_cache is None: ref_cache = dict() try: -- 2.49.1 From 328eb66034ec37f9314cd3b3028f3f32bdc36971 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Mon, 24 Nov 2025 01:48:48 -0300 Subject: [PATCH 68/96] fix: fixes save object after parsing --- jambo/parser/object_type_parser.py | 5 +++-- jambo/schema_converter.py | 17 +++++++++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/jambo/parser/object_type_parser.py b/jambo/parser/object_type_parser.py index d3c27ca..92b6a9f 100644 --- a/jambo/parser/object_type_parser.py +++ b/jambo/parser/object_type_parser.py @@ -46,9 +46,10 @@ class ObjectTypeParser(GenericTypeParser): type_parsing.model_validate(example) for example in example_values ] - if name in ref_cache: + if name in ref_cache and isinstance(ref_cache[name], type): warnings.warn( - f"Type '{name}' is already in the ref_cache and will be overwritten.", + f"Type '{name}' is already in the ref_cache and will be overwritten." + " This may indicate a circular reference in the schema or a collision in the schema.", UserWarning, ) ref_cache[name] = type_parsing diff --git a/jambo/schema_converter.py b/jambo/schema_converter.py index 6358786..1ba52d6 100644 --- a/jambo/schema_converter.py +++ b/jambo/schema_converter.py @@ -26,14 +26,28 @@ class SchemaConverter: self, schema: JSONSchema, ref_cache: Optional[RefCacheDict] = None, + without_cache: bool = False, ) -> type[BaseModel]: """ Converts a JSON Schema to a Pydantic model. + This is the instance method version of `build` and uses the instance's reference cache if none is provided. + Use this method if you want to utilize the instance's reference cache. + :param schema: The JSON Schema to convert. :param ref_cache: An optional reference cache to use during conversion. + :param without_cache: Whether to use a clean reference cache for this conversion. :return: The generated Pydantic model. """ - return self.build(schema, ref_cache or self._ref_cache) + local_ref_cache: RefCacheDict + + if without_cache: + local_ref_cache = dict() + elif ref_cache is None: + local_ref_cache = self._ref_cache + else: + local_ref_cache = ref_cache + + return self.build(schema, local_ref_cache) @staticmethod def build( @@ -43,7 +57,6 @@ class SchemaConverter: Converts a JSON Schema to a Pydantic model. :param schema: The JSON Schema to convert. :param ref_cache: An optional reference cache to use during conversion, if provided `with_clean_cache` will be ignored. - :param with_clean_cache: Whether to use a clean reference cache for this conversion. Set to rue due to API compatibility. Will be set to False in future versions. :return: The generated Pydantic model. """ if ref_cache is None: -- 2.49.1 From c2b9e8daf821f3530d89ea24196b1a411abcd1bd Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Mon, 24 Nov 2025 18:20:08 -0300 Subject: [PATCH 69/96] fix: fixes implementation of save object to cache and adds tests --- jambo/parser/object_type_parser.py | 35 +++++++++++++++++------------- tests/test_schema_converter.py | 25 +++++++++++++++++++++ 2 files changed, 45 insertions(+), 15 deletions(-) diff --git a/jambo/parser/object_type_parser.py b/jambo/parser/object_type_parser.py index 92b6a9f..fc10cb5 100644 --- a/jambo/parser/object_type_parser.py +++ b/jambo/parser/object_type_parser.py @@ -18,12 +18,6 @@ class ObjectTypeParser(GenericTypeParser): def from_properties_impl( self, name: str, properties: JSONSchema, **kwargs: Unpack[TypeParserOptions] ) -> tuple[type[BaseModel], dict]: - ref_cache = kwargs.get("ref_cache") - if ref_cache is None: - raise InternalAssertionException( - "`ref_cache` must be provided in kwargs for RefTypeParser" - ) - type_parsing = self.to_model( name, properties.get("properties", {}), @@ -46,14 +40,6 @@ class ObjectTypeParser(GenericTypeParser): type_parsing.model_validate(example) for example in example_values ] - if name in ref_cache and isinstance(ref_cache[name], type): - warnings.warn( - f"Type '{name}' is already in the ref_cache and will be overwritten." - " This may indicate a circular reference in the schema or a collision in the schema.", - UserWarning, - ) - ref_cache[name] = type_parsing - return type_parsing, type_properties @classmethod @@ -71,10 +57,29 @@ class ObjectTypeParser(GenericTypeParser): :param required_keys: List of required keys in the schema. :return: A Pydantic model class. """ + ref_cache = kwargs.get("ref_cache") + if ref_cache is None: + raise InternalAssertionException( + "`ref_cache` must be provided in kwargs for ObjectTypeParser" + ) + + if (model := ref_cache.get(name)) is not None and isinstance(model, type): + return model + model_config = ConfigDict(validate_assignment=True) fields = cls._parse_properties(properties, required_keys, **kwargs) - return create_model(name, __config__=model_config, **fields) # type: ignore + model = create_model(name, __config__=model_config, **fields) # type: ignore + + if name in ref_cache and isinstance(ref_cache[name], type): + warnings.warn( + f"Type '{name}' is already in the ref_cache and will be overwritten." + " This may indicate a circular reference in the schema or a collision in the schema.", + UserWarning, + ) + ref_cache[name] = model + + return model @classmethod def _parse_properties( diff --git a/tests/test_schema_converter.py b/tests/test_schema_converter.py index cbc6f72..887a548 100644 --- a/tests/test_schema_converter.py +++ b/tests/test_schema_converter.py @@ -866,3 +866,28 @@ class TestSchemaConverter(TestCase): ) self.assertFalse(object_.model_fields["description"].is_required()) # FAIL + + def test_instance_level_ref_cache(self): + ref_cache = {} + + schema = { + "title": "Person", + "type": "object", + "properties": { + "name": {"type": "string"}, + "age": {"type": "integer"}, + "emergency_contact": { + "$ref": "#", + }, + }, + "required": ["name", "age"], + } + + converter1 = SchemaConverter(ref_cache) + model1 = converter1.build_with_instance(schema) + + converter2 = SchemaConverter(ref_cache) + model2 = converter2.build_with_instance(schema) + + self.assertIs(converter1._ref_cache, converter2._ref_cache) + self.assertIs(model1, model2) -- 2.49.1 From 5ec30cd565adebc1bd30e35a7c3158088d65241d Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Mon, 24 Nov 2025 19:32:42 -0300 Subject: [PATCH 70/96] feat: changes tests to use instance level build --- tests/test_schema_converter.py | 105 +++++++++++++++------------------ 1 file changed, 47 insertions(+), 58 deletions(-) diff --git a/tests/test_schema_converter.py b/tests/test_schema_converter.py index 887a548..1e86f52 100644 --- a/tests/test_schema_converter.py +++ b/tests/test_schema_converter.py @@ -15,6 +15,13 @@ def is_pydantic_model(cls): class TestSchemaConverter(TestCase): + def setUp(self): + self.ref_cache = {} + self.converter = SchemaConverter(ref_cache=self.ref_cache) + + def tearDown(self): + self.converter.clear_ref_cache() + def test_invalid_schema(self): schema = { "title": 1, @@ -27,7 +34,7 @@ class TestSchemaConverter(TestCase): } with self.assertRaises(InvalidSchemaException): - SchemaConverter.build(schema) + self.converter.build_with_instance(schema) def test_invalid_schema_type(self): schema = { @@ -41,7 +48,7 @@ class TestSchemaConverter(TestCase): } with self.assertRaises(InvalidSchemaException): - SchemaConverter.build(schema) + self.converter.build_with_instance(schema) def test_build_expects_title(self): schema = { @@ -54,7 +61,7 @@ class TestSchemaConverter(TestCase): } with self.assertRaises(InvalidSchemaException): - SchemaConverter.build(schema) + self.converter.build_with_instance(schema) def test_build_expects_object(self): schema = { @@ -64,7 +71,7 @@ class TestSchemaConverter(TestCase): } with self.assertRaises(UnsupportedSchemaException): - SchemaConverter.build(schema) + self.converter.build_with_instance(schema) def test_is_invalid_field(self): schema = { @@ -80,7 +87,7 @@ class TestSchemaConverter(TestCase): } with self.assertRaises(InvalidSchemaException) as context: - SchemaConverter.build(schema) + self.converter.build_with_instance(schema) self.assertTrue("Unknown type" in str(context.exception)) def test_jsonschema_to_pydantic(self): @@ -95,7 +102,7 @@ class TestSchemaConverter(TestCase): "required": ["name"], } - model = SchemaConverter.build(schema) + model = self.converter.build_with_instance(schema) self.assertTrue(is_pydantic_model(model)) @@ -116,7 +123,7 @@ class TestSchemaConverter(TestCase): "required": ["name"], } - model = SchemaConverter.build(schema) + model = self.converter.build_with_instance(schema) self.assertEqual(model(name="John", age=30).name, "John") @@ -147,7 +154,7 @@ class TestSchemaConverter(TestCase): "required": ["age"], } - model = SchemaConverter.build(schema) + model = self.converter.build_with_instance(schema) self.assertEqual(model(age=30).age, 30) @@ -172,7 +179,7 @@ class TestSchemaConverter(TestCase): "required": ["age"], } - model = SchemaConverter.build(schema) + model = self.converter.build_with_instance(schema) self.assertEqual(model(age=30).age, 30.0) @@ -193,7 +200,7 @@ class TestSchemaConverter(TestCase): "required": ["is_active"], } - model = SchemaConverter.build(schema) + model = self.converter.build_with_instance(schema) self.assertEqual(model(is_active=True).is_active, True) @@ -216,7 +223,7 @@ class TestSchemaConverter(TestCase): "required": ["friends"], } - model = SchemaConverter.build(schema) + model = self.converter.build_with_instance(schema) self.assertEqual( model(friends=["John", "Jane", "John"]).friends, {"John", "Jane"} @@ -229,26 +236,7 @@ class TestSchemaConverter(TestCase): model(friends=["John", "Jane", "Invalid"]) def test_validation_list_with_missing_items(self): - model = SchemaConverter.build( - { - "title": "Person", - "description": "A person", - "type": "object", - "properties": { - "friends": { - "type": "array", - "items": {"type": "string"}, - "minItems": 1, - "maxItems": 2, - "default": ["John", "Jane"], - }, - }, - } - ) - - self.assertEqual(model().friends, ["John", "Jane"]) - - model = SchemaConverter.build( + model = self.converter.build_with_instance( { "title": "Person", "description": "A person", @@ -286,7 +274,7 @@ class TestSchemaConverter(TestCase): "required": ["address"], } - model = SchemaConverter.build(schema) + model = self.converter.build_with_instance(schema) obj = model(address={"street": "123 Main St", "city": "Springfield"}) @@ -310,7 +298,7 @@ class TestSchemaConverter(TestCase): "required": ["name"], } - model = SchemaConverter.build(schema) + model = self.converter.build_with_instance(schema) obj = model(name="John") @@ -333,7 +321,7 @@ class TestSchemaConverter(TestCase): } with self.assertRaises(InvalidSchemaException): - SchemaConverter.build(schema_max_length) + self.converter.build_with_instance(schema_max_length) def test_default_for_list(self): schema_list = { @@ -350,10 +338,11 @@ class TestSchemaConverter(TestCase): "required": ["friends"], } - model_list = SchemaConverter.build(schema_list) + model_list = self.converter.build_with_instance(schema_list) self.assertEqual(model_list().friends, ["John", "Jane"]) + def test_default_for_list_with_unique_items(self): # Test for default with uniqueItems schema_set = { "title": "Person", @@ -370,7 +359,7 @@ class TestSchemaConverter(TestCase): "required": ["friends"], } - model_set = SchemaConverter.build(schema_set) + model_set = self.converter.build_with_instance(schema_set) self.assertEqual(model_set().friends, {"John", "Jane"}) @@ -392,7 +381,7 @@ class TestSchemaConverter(TestCase): "required": ["address"], } - model = SchemaConverter.build(schema) + model = self.converter.build_with_instance(schema) obj = model(address={"street": "123 Main St", "city": "Springfield"}) @@ -416,7 +405,7 @@ class TestSchemaConverter(TestCase): }, } - Model = SchemaConverter.build(schema) + Model = self.converter.build_with_instance(schema) obj = Model( name="J", @@ -445,7 +434,7 @@ class TestSchemaConverter(TestCase): }, } - Model = SchemaConverter.build(schema) + Model = self.converter.build_with_instance(schema) obj = Model(id=1) self.assertEqual(obj.id, 1) @@ -469,7 +458,7 @@ class TestSchemaConverter(TestCase): "properties": {"email": {"type": "string", "format": "email"}}, } - model = SchemaConverter.build(schema) + model = self.converter.build_with_instance(schema) self.assertEqual(model(email="test@example.com").email, "test@example.com") with self.assertRaises(ValidationError): @@ -482,7 +471,7 @@ class TestSchemaConverter(TestCase): "properties": {"website": {"type": "string", "format": "uri"}}, } - model = SchemaConverter.build(schema) + model = self.converter.build_with_instance(schema) self.assertEqual( model(website="https://example.com").website, AnyUrl("https://example.com") ) @@ -497,7 +486,7 @@ class TestSchemaConverter(TestCase): "properties": {"ip": {"type": "string", "format": "ipv4"}}, } - model = SchemaConverter.build(schema) + model = self.converter.build_with_instance(schema) self.assertEqual(model(ip="192.168.1.1").ip, IPv4Address("192.168.1.1")) with self.assertRaises(ValidationError): @@ -510,7 +499,7 @@ class TestSchemaConverter(TestCase): "properties": {"ip": {"type": "string", "format": "ipv6"}}, } - model = SchemaConverter.build(schema) + model = self.converter.build_with_instance(schema) self.assertEqual( model(ip="2001:0db8:85a3:0000:0000:8a2e:0370:7334").ip, IPv6Address("2001:0db8:85a3:0000:0000:8a2e:0370:7334"), @@ -526,7 +515,7 @@ class TestSchemaConverter(TestCase): "properties": {"id": {"type": "string", "format": "uuid"}}, } - model = SchemaConverter.build(schema) + model = self.converter.build_with_instance(schema) self.assertEqual( model(id="123e4567-e89b-12d3-a456-426614174000").id, @@ -543,7 +532,7 @@ class TestSchemaConverter(TestCase): "properties": {"hostname": {"type": "string", "format": "hostname"}}, } - model = SchemaConverter.build(schema) + model = self.converter.build_with_instance(schema) self.assertEqual(model(hostname="example.com").hostname, "example.com") with self.assertRaises(ValidationError): @@ -556,7 +545,7 @@ class TestSchemaConverter(TestCase): "properties": {"timestamp": {"type": "string", "format": "date-time"}}, } - model = SchemaConverter.build(schema) + model = self.converter.build_with_instance(schema) self.assertEqual( model(timestamp="2024-01-01T12:00:00Z").timestamp.isoformat(), "2024-01-01T12:00:00+00:00", @@ -572,7 +561,7 @@ class TestSchemaConverter(TestCase): "properties": {"time": {"type": "string", "format": "time"}}, } - model = SchemaConverter.build(schema) + model = self.converter.build_with_instance(schema) self.assertEqual( model(time="20:20:39+00:00").time.isoformat(), "20:20:39+00:00" ) @@ -588,7 +577,7 @@ class TestSchemaConverter(TestCase): } with self.assertRaises(InvalidSchemaException): - SchemaConverter.build(schema) + self.converter.build_with_instance(schema) def test_ref_with_root_ref(self): schema = { @@ -604,7 +593,7 @@ class TestSchemaConverter(TestCase): "required": ["name", "age"], } - model = SchemaConverter.build(schema) + model = self.converter.build_with_instance(schema) obj = model( name="John", @@ -639,7 +628,7 @@ class TestSchemaConverter(TestCase): }, } - model = SchemaConverter.build(schema) + model = self.converter.build_with_instance(schema) obj = model( name="John", @@ -678,7 +667,7 @@ class TestSchemaConverter(TestCase): }, } - Model = SchemaConverter.build(schema) + Model = self.converter.build_with_instance(schema) obj = Model( name="John", @@ -704,7 +693,7 @@ class TestSchemaConverter(TestCase): "required": ["status"], } - Model = SchemaConverter.build(schema) + Model = self.converter.build_with_instance(schema) obj = Model(status="active") self.assertEqual(obj.status.value, "active") @@ -723,7 +712,7 @@ class TestSchemaConverter(TestCase): "required": ["status"], } - Model = SchemaConverter.build(schema) + Model = self.converter.build_with_instance(schema) obj = Model() self.assertEqual(obj.status.value, "active") @@ -740,7 +729,7 @@ class TestSchemaConverter(TestCase): "required": ["name"], } - Model = SchemaConverter.build(schema) + Model = self.converter.build_with_instance(schema) obj = Model() self.assertEqual(obj.name, "United States of America") @@ -763,7 +752,7 @@ class TestSchemaConverter(TestCase): "required": ["name"], } - Model = SchemaConverter.build(schema) + Model = self.converter.build_with_instance(schema) obj = Model() self.assertEqual(obj.name, ["Brazil"]) @@ -783,7 +772,7 @@ class TestSchemaConverter(TestCase): }, } - Model = SchemaConverter.build(schema) + Model = self.converter.build_with_instance(schema) obj = Model() self.assertIsNone(obj.a_thing) @@ -825,7 +814,7 @@ class TestSchemaConverter(TestCase): }, } - schema_type = SchemaConverter.build(schema) + schema_type = self.converter.build_with_instance(schema) # check for me that the types generated by the oneOf in the typing.Annotated have different names operating_system_field = schema_type.model_fields["operating_system"] @@ -839,7 +828,7 @@ class TestSchemaConverter(TestCase): def test_object_invalid_require(self): # https://github.com/HideyoshiNakazone/jambo/issues/60 - object_ = SchemaConverter.build( + object_ = self.converter.build_with_instance( { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "TEST", -- 2.49.1 From 57f8b571deccef4164ae8f812d085ee1f30ec910 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Mon, 24 Nov 2025 19:38:53 -0300 Subject: [PATCH 71/96] feat: adds tests for SchemaConverter.get_cached_ref --- tests/test_schema_converter.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/test_schema_converter.py b/tests/test_schema_converter.py index 1e86f52..a5cd948 100644 --- a/tests/test_schema_converter.py +++ b/tests/test_schema_converter.py @@ -880,3 +880,23 @@ class TestSchemaConverter(TestCase): self.assertIs(converter1._ref_cache, converter2._ref_cache) self.assertIs(model1, model2) + + def test_get_type_from_cache(self): + schema = { + "title": "Person", + "type": "object", + "properties": { + "name": {"type": "string"}, + "age": {"type": "integer"}, + "emergency_contact": { + "$ref": "#", + }, + }, + "required": ["name", "age"], + } + + model = self.converter.build_with_instance(schema) + + cached_model = self.converter.get_cached_ref("Person") + + self.assertIs(model, cached_model) -- 2.49.1 From 3a8ca951db1d4f6850ebd7ee4c376a9a32ba75b6 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Mon, 24 Nov 2025 19:52:54 -0300 Subject: [PATCH 72/96] feat: adds tests for isolation method in ref_cache --- tests/test_schema_converter.py | 41 ++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/tests/test_schema_converter.py b/tests/test_schema_converter.py index a5cd948..ce799cb 100644 --- a/tests/test_schema_converter.py +++ b/tests/test_schema_converter.py @@ -16,8 +16,7 @@ def is_pydantic_model(cls): class TestSchemaConverter(TestCase): def setUp(self): - self.ref_cache = {} - self.converter = SchemaConverter(ref_cache=self.ref_cache) + self.converter = SchemaConverter() def tearDown(self): self.converter.clear_ref_cache() @@ -881,6 +880,44 @@ class TestSchemaConverter(TestCase): self.assertIs(converter1._ref_cache, converter2._ref_cache) self.assertIs(model1, model2) + def test_instance_level_ref_cache_isolation_via_without_cache_param(self): + schema = { + "title": "Person", + "type": "object", + "properties": { + "name": {"type": "string"}, + "age": {"type": "integer"}, + "emergency_contact": { + "$ref": "#", + }, + }, + "required": ["name", "age"], + } + + model1 = self.converter.build_with_instance(schema, without_cache=True) + model2 = self.converter.build_with_instance(schema, without_cache=True) + + self.assertIsNot(model1, model2) + + def test_instance_level_ref_cache_isolation_via_provided_cache(self): + schema = { + "title": "Person", + "type": "object", + "properties": { + "name": {"type": "string"}, + "age": {"type": "integer"}, + "emergency_contact": { + "$ref": "#", + }, + }, + "required": ["name", "age"], + } + + model1 = self.converter.build_with_instance(schema, ref_cache={}) + model2 = self.converter.build_with_instance(schema, ref_cache={}) + + self.assertIsNot(model1, model2) + def test_get_type_from_cache(self): schema = { "title": "Person", -- 2.49.1 From 9837a99ec929171b54260130242b9daf7ead7ad4 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Mon, 24 Nov 2025 19:53:29 -0300 Subject: [PATCH 73/96] feat: adds tests for type not found in ref_cache --- tests/test_schema_converter.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_schema_converter.py b/tests/test_schema_converter.py index ce799cb..6cd66ef 100644 --- a/tests/test_schema_converter.py +++ b/tests/test_schema_converter.py @@ -937,3 +937,8 @@ class TestSchemaConverter(TestCase): cached_model = self.converter.get_cached_ref("Person") self.assertIs(model, cached_model) + + def test_get_type_from_cache_not_found(self): + cached_model = self.converter.get_cached_ref("NonExistentModel") + + self.assertIsNone(cached_model) -- 2.49.1 From 4baaeed349916b902262538613d333a8a5d71920 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Mon, 24 Nov 2025 20:00:42 -0300 Subject: [PATCH 74/96] feat: adds test for ObjectTypeParser asserting for the presence of a ref_cache --- tests/parser/test_object_type_parser.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/parser/test_object_type_parser.py b/tests/parser/test_object_type_parser.py index 025149d..2021ca5 100644 --- a/tests/parser/test_object_type_parser.py +++ b/tests/parser/test_object_type_parser.py @@ -1,9 +1,24 @@ +from jambo.exceptions import InternalAssertionException from jambo.parser import ObjectTypeParser from unittest import TestCase class TestObjectTypeParser(TestCase): + def test_object_type_parser_throws_without_ref_cache(self): + parser = ObjectTypeParser() + + properties = { + "type": "object", + "properties": { + "name": {"type": "string"}, + "age": {"type": "integer"}, + }, + } + + with self.assertRaises(InternalAssertionException): + parser.from_properties_impl("placeholder", properties) + def test_object_type_parser(self): parser = ObjectTypeParser() -- 2.49.1 From 682f19654d0ec79ac769979df5e1de6525faa8d7 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Mon, 24 Nov 2025 20:52:02 -0300 Subject: [PATCH 75/96] feat: better methodology for accessing cached references of: objects, subobjects and defs --- jambo/parser/anyof_type_parser.py | 2 +- jambo/parser/object_type_parser.py | 13 +++---- tests/test_schema_converter.py | 56 ++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 7 deletions(-) diff --git a/jambo/parser/anyof_type_parser.py b/jambo/parser/anyof_type_parser.py index 9b09754..c0295ff 100644 --- a/jambo/parser/anyof_type_parser.py +++ b/jambo/parser/anyof_type_parser.py @@ -31,7 +31,7 @@ class AnyOfTypeParser(GenericTypeParser): sub_types = [ GenericTypeParser.type_from_properties( - f"{name}_sub{i}", subProperty, **kwargs + f"{name}.sub{i}", subProperty, **kwargs ) for i, subProperty in enumerate(sub_properties) ] diff --git a/jambo/parser/object_type_parser.py b/jambo/parser/object_type_parser.py index fc10cb5..475c785 100644 --- a/jambo/parser/object_type_parser.py +++ b/jambo/parser/object_type_parser.py @@ -67,7 +67,7 @@ class ObjectTypeParser(GenericTypeParser): return model model_config = ConfigDict(validate_assignment=True) - fields = cls._parse_properties(properties, required_keys, **kwargs) + fields = cls._parse_properties(name, properties, required_keys, **kwargs) model = create_model(name, __config__=model_config, **fields) # type: ignore @@ -84,6 +84,7 @@ class ObjectTypeParser(GenericTypeParser): @classmethod def _parse_properties( cls, + name: str, properties: dict[str, JSONSchema], required_keys: list[str], **kwargs: Unpack[TypeParserOptions], @@ -91,15 +92,15 @@ class ObjectTypeParser(GenericTypeParser): required_keys = required_keys or [] fields = {} - for name, prop in properties.items(): + for field_name, field_prop in properties.items(): sub_property: TypeParserOptions = kwargs.copy() - sub_property["required"] = name in required_keys + sub_property["required"] = field_name in required_keys parsed_type, parsed_properties = GenericTypeParser.type_from_properties( - name, - prop, + f"{name}.{field_name}", + field_prop, **sub_property, # type: ignore ) - fields[name] = (parsed_type, Field(**parsed_properties)) + fields[field_name] = (parsed_type, Field(**parsed_properties)) return fields diff --git a/tests/test_schema_converter.py b/tests/test_schema_converter.py index 6cd66ef..245ca8e 100644 --- a/tests/test_schema_converter.py +++ b/tests/test_schema_converter.py @@ -942,3 +942,59 @@ class TestSchemaConverter(TestCase): cached_model = self.converter.get_cached_ref("NonExistentModel") self.assertIsNone(cached_model) + + def test_get_type_from_cache_nested_type(self): + schema = { + "title": "Person", + "type": "object", + "properties": { + "name": {"type": "string"}, + "age": {"type": "integer"}, + "address": { + "type": "object", + "properties": { + "street": {"type": "string"}, + "city": {"type": "string"}, + }, + "required": ["street", "city"], + }, + }, + "required": ["name", "age", "address"], + } + + model = self.converter.build_with_instance(schema) + + cached_model = self.converter.get_cached_ref("Person.address") + + self.assertIsNotNone(cached_model) + self.assertIs(model.model_fields["address"].annotation, cached_model) + + def test_get_type_from_cache_with_def(self): + schema = { + "title": "person", + "type": "object", + "properties": { + "name": {"type": "string"}, + "age": {"type": "integer"}, + "address": {"$ref": "#/$defs/address"}, + }, + "$defs": { + "address": { + "type": "object", + "properties": { + "street": {"type": "string"}, + "city": {"type": "string"}, + }, + "required": ["street", "city"], + } + }, + } + + person_model = self.converter.build_with_instance(schema) + cached_person_model = self.converter.get_cached_ref("person") + + self.assertIs(person_model, cached_person_model) + + cached_address_model = self.converter.get_cached_ref("address") + + self.assertIsNotNone(cached_address_model) -- 2.49.1 From a3cbd5bc3d96fcf5cf12659f87c59470470922b1 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Mon, 24 Nov 2025 21:06:15 -0300 Subject: [PATCH 76/96] feat: better warning for cache colision --- jambo/parser/object_type_parser.py | 12 +++++------- tests/parser/test_object_type_parser.py | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/jambo/parser/object_type_parser.py b/jambo/parser/object_type_parser.py index 475c785..82ca587 100644 --- a/jambo/parser/object_type_parser.py +++ b/jambo/parser/object_type_parser.py @@ -64,19 +64,17 @@ class ObjectTypeParser(GenericTypeParser): ) if (model := ref_cache.get(name)) is not None and isinstance(model, type): + warnings.warn( + f"Type '{name}' is already in the ref_cache and therefore cached value will be used." + " This may indicate a namming collision in the schema or just a normal optimization," + " if this behavior is desired pass a clean ref_cache or use the param `without_cache`" + ) return model model_config = ConfigDict(validate_assignment=True) fields = cls._parse_properties(name, properties, required_keys, **kwargs) model = create_model(name, __config__=model_config, **fields) # type: ignore - - if name in ref_cache and isinstance(ref_cache[name], type): - warnings.warn( - f"Type '{name}' is already in the ref_cache and will be overwritten." - " This may indicate a circular reference in the schema or a collision in the schema.", - UserWarning, - ) ref_cache[name] = model return model diff --git a/tests/parser/test_object_type_parser.py b/tests/parser/test_object_type_parser.py index 2021ca5..975f85d 100644 --- a/tests/parser/test_object_type_parser.py +++ b/tests/parser/test_object_type_parser.py @@ -92,3 +92,18 @@ class TestObjectTypeParser(TestCase): # Chekc default factory new object id new_obj = type_validator["default_factory"]() self.assertNotEqual(id(default_obj), id(new_obj)) + + def test_object_type_parser_warns_if_object_override_in_cache(self): + ref_cache = {} + + parser = ObjectTypeParser() + + properties = {"type": "object", "properties": {}} + + with self.assertWarns(UserWarning): + _, type_validator = parser.from_properties_impl( + "placeholder", properties, ref_cache=ref_cache + ) + _, type_validator = parser.from_properties_impl( + "placeholder", properties, ref_cache=ref_cache + ) -- 2.49.1 From 20872d4a918123b9633c331ac14b541906b94185 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Tue, 25 Nov 2025 22:26:28 -0300 Subject: [PATCH 77/96] feat: stabalizes the api for cached build using instance method --- jambo/schema_converter.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jambo/schema_converter.py b/jambo/schema_converter.py index 1ba52d6..4a10c2d 100644 --- a/jambo/schema_converter.py +++ b/jambo/schema_converter.py @@ -22,7 +22,7 @@ class SchemaConverter: ref_cache = dict() self._ref_cache = ref_cache - def build_with_instance( + def build_with_cache( self, schema: JSONSchema, ref_cache: Optional[RefCacheDict] = None, @@ -55,6 +55,7 @@ class SchemaConverter: ) -> type[BaseModel]: """ Converts a JSON Schema to a Pydantic model. + This method doesn't use a reference cache if none is provided. :param schema: The JSON Schema to convert. :param ref_cache: An optional reference cache to use during conversion, if provided `with_clean_cache` will be ignored. :return: The generated Pydantic model. -- 2.49.1 From 268ac85667df79799e2308e723eaf5c21f7393a3 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Tue, 25 Nov 2025 23:50:58 -0300 Subject: [PATCH 78/96] chore: adds documentation for the new ref_cache implementation --- docs/source/usage.ref_cache.rst | 233 ++++++++++++++++++++++++++++++++ docs/source/usage.rst | 88 +++++++++++- tests/test_schema_converter.py | 96 ++++++------- 3 files changed, 362 insertions(+), 55 deletions(-) create mode 100644 docs/source/usage.ref_cache.rst diff --git a/docs/source/usage.ref_cache.rst b/docs/source/usage.ref_cache.rst new file mode 100644 index 0000000..adcf2f9 --- /dev/null +++ b/docs/source/usage.ref_cache.rst @@ -0,0 +1,233 @@ +=============== +Reference Cache +=============== + +The reference cache is named after the mechanism used to implement +the `$ref` keyword in the JSON Schema specification. + +Internally, the cache is used by both :py:meth:`SchemaConverter.build_with_cache ` +and :py:meth:`SchemaConverter.build `. +However, only :py:meth:`SchemaConverter.build_with_cache ` exposes the cache through a supported API; +:py:meth:`SchemaConverter.build ` uses the cache internally and does not provide access to it. + + +----------------------------------------- +Configuring and Using the Reference Cache +----------------------------------------- + +The reference cache can be used in three ways: + +* Without a persistent reference cache (no sharing between calls). +* Passing an explicit ``ref_cache`` dictionary to a call. +* Using the converter instance's default cache (the instance-level cache). + + +Usage Without Reference Cache +============================= + +When you run the library without a persistent reference cache, the generated +types are not stored for reuse. Each call to a build method creates fresh +Pydantic model classes (they will have different Python object identities). +Because nothing is cached, you cannot look up generated subtypes later. + +This is the default behaviour of :py:meth:`SchemaConverter.build `. +You can achieve the same behaviour with :py:meth:`SchemaConverter.build_with_cache ` by +passing ``without_cache=True``. + + +Usage: Manually Passing a Reference Cache +========================================= + +You can create and pass your own mutable mapping (typically a plain dict) +as the reference cache. This gives you full control over sharing and +lifetime of cached types. When two converters share the same dict, types +created by one converter will be reused by the other. + +.. code-block:: python + + from jambo import SchemaConverter + + # a shared cache you control + shared_cache = {} + + converter1 = SchemaConverter(shared_cache) + converter2 = SchemaConverter(shared_cache) + + model1 = converter1.build_with_cache(schema) + model2 = converter2.build_with_cache(schema) + + # Because both converters use the same cache object, the built models are the same object + assert model1 is model2 + +If you prefer a per-call cache (leaving the converter's instance cache unchanged), pass the ``ref_cache`` parameter to +:py:meth:`SchemaConverter.build_with_cache `: + +.. code-block:: python + + # pass an explicit, private cache for this call only + model_a = converter1.build_with_cache(schema, ref_cache={}) + model_b = converter1.build_with_cache(schema, ref_cache={}) + + # because each call received a fresh dict, the resulting model classes are distinct + assert model_a is not model_b + + +Usage: Using the Instance Default (Instance-level) Cache +======================================================= + +By default, a :class:`SchemaConverter` instance creates and keeps an internal +reference cache (a plain dict). Reusing the same converter instance across +multiple calls will reuse that cache and therefore reuse previously generated +model classes. + +.. code-block:: python + + converter = SchemaConverter() # has its own internal cache + + model1 = converter.build_with_cache(schema) + model2 = converter.build_with_cache(schema) + + # model1 and model2 are the same object because the instance cache persisted + assert model1 is model2 + +If you want to temporarily avoid using the instance cache for a single call, +use ``without_cache=True``. That causes :py:meth:`SchemaConverter.build_with_cache ` to +use a fresh, empty cache for the duration of that call only: + +.. code-block:: python + + model1 = converter.build_with_cache(schema, without_cache=True) + model2 = converter.build_with_cache(schema, without_cache=True) + + # each call used a fresh cache, so the models are distinct + assert model1 is not model2 + + +Inspecting and Managing the Cache +================================= + +The converter provides a small, explicit API to inspect and manage the +instance cache. + +Retrieving cached types +----------------------- + +:py:meth:`SchemaConverter.get_cached_ref `(name) — returns a cached model class or ``None``. + +Retrieving the root type of the schema +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When retrieving the root type of a schema, pass the schema's ``title`` property as the name. + +.. code-block:: python + + from jambo import SchemaConverter + + converter = SchemaConverter() + + schema = { + "title": "person", + "type": "object", + "properties": { + "name": {"type": "string"}, + "age": {"type": "integer"}, + }, + } + + person_model = converter.build_with_cache(schema) + cached_person_model = converter.get_cached_ref("person") + + +Retrieving a subtype +~~~~~~~~~~~~~~~~~~~~ + +When retrieving a subtype, pass a path string (for example, ``parent_name.field_name``) as the name. + +.. code-block:: python + + from jambo import SchemaConverter + + converter = SchemaConverter() + + schema = { + "title": "person", + "type": "object", + "properties": { + "name": {"type": "string"}, + "age": {"type": "integer"}, + "address": { + "type": "object", + "properties": { + "street": {"type": "string"}, + "city": {"type": "string"}, + }, + "required": ["street", "city"], + }, + } + } + + person_model = converter.build_with_cache(schema) + cached_address_model = converter.get_cached_ref("person.address") + + + +Retrieving a type from ``$defs`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When retrieving a type defined in ``$defs``, access it directly by its name. + +.. code-block:: python + + from jambo import SchemaConverter + + converter = SchemaConverter() + + schema = { + "title": "person", + "type": "object", + "properties": { + "name": {"type": "string"}, + "age": {"type": "integer"}, + "address": {"$ref": "#/$defs/address"}, + }, + "$defs": { + "address": { + "type": "object", + "properties": { + "street": {"type": "string"}, + "city": {"type": "string"}, + }, + "required": ["street", "city"], + } + }, + } + + person_model = converter.build_with_cache(schema) + cached_address_model = converter.get_cached_ref("address") + + +Clearing the cache +------------------ + +:py:meth:`SchemaConverter.clear_ref_cache `() — removes all entries from the instance cache. + + +Notes and Behavioural Differences +================================ + +* :py:meth:`SchemaConverter.build ` does not expose or persist an instance cache. If you call it without + providing a ``ref_cache`` it will create and use a temporary cache for that + call only; nothing from that call will be available later via + :py:meth:`SchemaConverter.get_cached_ref `. + +* :py:meth:`SchemaConverter.build_with_cache ` is the supported entry point when you want + cache control: it uses the instance cache by default, accepts an explicit + ``ref_cache`` dict for per-call control, or uses ``without_cache=True`` to + run with an ephemeral cache. + + +References in the Test Suite +============================ + +These behaviours are exercised in the project's tests; see :mod:`tests.test_schema_converter` +for examples and additional usage notes. diff --git a/docs/source/usage.rst b/docs/source/usage.rst index 3bdb2d9..5a7dc08 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -1,9 +1,15 @@ +=================== Using Jambo =================== -Jambo is designed to be easy to use, it doesn't require any complex setup or configuration. -Below a example of how to use Jambo to convert a JSON Schema into a Pydantic model. +Jambo is designed to be easy to use. It doesn't require complex setup or configuration when not needed, while providing more powerful instance methods when you do need control. +Below is an example of how to use Jambo to convert a JSON Schema into a Pydantic model. + + +------------------------- +Static Method (no config) +------------------------- .. code-block:: python @@ -15,8 +21,16 @@ Below a example of how to use Jambo to convert a JSON Schema into a Pydantic mod "properties": { "name": {"type": "string"}, "age": {"type": "integer"}, + "address": { + "type": "object", + "properties": { + "street": {"type": "string"}, + "city": {"type": "string"}, + }, + "required": ["street", "city"], + }, }, - "required": ["name"], + "required": ["name", "address"], } Person = SchemaConverter.build(schema) @@ -26,16 +40,76 @@ Below a example of how to use Jambo to convert a JSON Schema into a Pydantic mod # Output: Person(name='Alice', age=30) -The :py:meth:`SchemaConverter.build ` static method takes a JSON Schema dictionary and returns a Pydantic model class. You can then instantiate this class with the required fields, and it will automatically validate the data according to the schema. +The :py:meth:`SchemaConverter.build ` static method takes a JSON Schema dictionary and returns a Pydantic model class. -If passed a description inside the schema it will also add it to the Pydantic model using the `description` field. This is useful for AI Frameworks as: LangChain, CrewAI and others, as they use this description for passing context to LLMs. +Note: the static ``build`` method was the original public API of this library and is kept for backwards compatibility. It creates and returns a model class for the provided schema but does not expose or persist an instance cache. -For more complex schemas and types see our documentation on +-------------------------------- +Instance Method (with ref cache) +-------------------------------- + +.. code-block:: python + + from jambo import SchemaConverter + + converter = SchemaConverter() + + schema = { + "title": "Person", + "type": "object", + "properties": { + "name": {"type": "string"}, + "age": {"type": "integer"}, + "address": { + "type": "object", + "properties": { + "street": {"type": "string"}, + "city": {"type": "string"}, + }, + "required": ["street", "city"], + }, + }, + "required": ["name", "address"], + } + + # The instance API (build_with_cache) populates the converter's instance-level reference cache + Person = converter.build_with_cache(schema) + + obj = Person(name="Alice", age=30) + print(obj) + # Output: Person(name='Alice', age=30) + + # When using the converter's built-in instance cache (no ref_cache passed to the call), + # all object types parsed during the build are stored and can be retrieved via get_cached_ref. + + cached_person_model = converter.get_cached_ref("Person") + assert Person is cached_person_model # the cached class is the same object that was built + + # A nested/subobject type can also be retrieved from the instance cache + cached_address_model = converter.get_cached_ref("Person.address") + + +The :py:meth:`SchemaConverter.build_with_cache ` instance method was added after the +initial static API to make it easier to access and reuse subtypes defined in a schema. +Unlike the original static :py:meth:`SchemaConverter.build `, +the instance method persists and exposes the reference cache and provides helpers such as +:py:meth:`SchemaConverter.get_cached_ref ` and +:py:meth:`SchemaConverter.clear_ref_cache `. + +For details and examples about the reference cache and the different cache modes (instance cache, per-call cache, ephemeral cache), see: + +.. toctree:: + usage.ref_cache + + +Type System +----------- + +For a full explanation of the supported schemas and types see our documentation on types: .. toctree:: :maxdepth: 2 - :caption: Contents: usage.string usage.numeric diff --git a/tests/test_schema_converter.py b/tests/test_schema_converter.py index 245ca8e..018c412 100644 --- a/tests/test_schema_converter.py +++ b/tests/test_schema_converter.py @@ -33,7 +33,7 @@ class TestSchemaConverter(TestCase): } with self.assertRaises(InvalidSchemaException): - self.converter.build_with_instance(schema) + self.converter.build_with_cache(schema) def test_invalid_schema_type(self): schema = { @@ -47,7 +47,7 @@ class TestSchemaConverter(TestCase): } with self.assertRaises(InvalidSchemaException): - self.converter.build_with_instance(schema) + self.converter.build_with_cache(schema) def test_build_expects_title(self): schema = { @@ -60,7 +60,7 @@ class TestSchemaConverter(TestCase): } with self.assertRaises(InvalidSchemaException): - self.converter.build_with_instance(schema) + self.converter.build_with_cache(schema) def test_build_expects_object(self): schema = { @@ -70,7 +70,7 @@ class TestSchemaConverter(TestCase): } with self.assertRaises(UnsupportedSchemaException): - self.converter.build_with_instance(schema) + self.converter.build_with_cache(schema) def test_is_invalid_field(self): schema = { @@ -86,7 +86,7 @@ class TestSchemaConverter(TestCase): } with self.assertRaises(InvalidSchemaException) as context: - self.converter.build_with_instance(schema) + self.converter.build_with_cache(schema) self.assertTrue("Unknown type" in str(context.exception)) def test_jsonschema_to_pydantic(self): @@ -101,7 +101,7 @@ class TestSchemaConverter(TestCase): "required": ["name"], } - model = self.converter.build_with_instance(schema) + model = self.converter.build_with_cache(schema) self.assertTrue(is_pydantic_model(model)) @@ -122,7 +122,7 @@ class TestSchemaConverter(TestCase): "required": ["name"], } - model = self.converter.build_with_instance(schema) + model = self.converter.build_with_cache(schema) self.assertEqual(model(name="John", age=30).name, "John") @@ -153,7 +153,7 @@ class TestSchemaConverter(TestCase): "required": ["age"], } - model = self.converter.build_with_instance(schema) + model = self.converter.build_with_cache(schema) self.assertEqual(model(age=30).age, 30) @@ -178,7 +178,7 @@ class TestSchemaConverter(TestCase): "required": ["age"], } - model = self.converter.build_with_instance(schema) + model = self.converter.build_with_cache(schema) self.assertEqual(model(age=30).age, 30.0) @@ -199,7 +199,7 @@ class TestSchemaConverter(TestCase): "required": ["is_active"], } - model = self.converter.build_with_instance(schema) + model = self.converter.build_with_cache(schema) self.assertEqual(model(is_active=True).is_active, True) @@ -222,7 +222,7 @@ class TestSchemaConverter(TestCase): "required": ["friends"], } - model = self.converter.build_with_instance(schema) + model = self.converter.build_with_cache(schema) self.assertEqual( model(friends=["John", "Jane", "John"]).friends, {"John", "Jane"} @@ -235,7 +235,7 @@ class TestSchemaConverter(TestCase): model(friends=["John", "Jane", "Invalid"]) def test_validation_list_with_missing_items(self): - model = self.converter.build_with_instance( + model = self.converter.build_with_cache( { "title": "Person", "description": "A person", @@ -273,7 +273,7 @@ class TestSchemaConverter(TestCase): "required": ["address"], } - model = self.converter.build_with_instance(schema) + model = self.converter.build_with_cache(schema) obj = model(address={"street": "123 Main St", "city": "Springfield"}) @@ -297,7 +297,7 @@ class TestSchemaConverter(TestCase): "required": ["name"], } - model = self.converter.build_with_instance(schema) + model = self.converter.build_with_cache(schema) obj = model(name="John") @@ -320,7 +320,7 @@ class TestSchemaConverter(TestCase): } with self.assertRaises(InvalidSchemaException): - self.converter.build_with_instance(schema_max_length) + self.converter.build_with_cache(schema_max_length) def test_default_for_list(self): schema_list = { @@ -337,7 +337,7 @@ class TestSchemaConverter(TestCase): "required": ["friends"], } - model_list = self.converter.build_with_instance(schema_list) + model_list = self.converter.build_with_cache(schema_list) self.assertEqual(model_list().friends, ["John", "Jane"]) @@ -358,7 +358,7 @@ class TestSchemaConverter(TestCase): "required": ["friends"], } - model_set = self.converter.build_with_instance(schema_set) + model_set = self.converter.build_with_cache(schema_set) self.assertEqual(model_set().friends, {"John", "Jane"}) @@ -380,7 +380,7 @@ class TestSchemaConverter(TestCase): "required": ["address"], } - model = self.converter.build_with_instance(schema) + model = self.converter.build_with_cache(schema) obj = model(address={"street": "123 Main St", "city": "Springfield"}) @@ -404,7 +404,7 @@ class TestSchemaConverter(TestCase): }, } - Model = self.converter.build_with_instance(schema) + Model = self.converter.build_with_cache(schema) obj = Model( name="J", @@ -433,7 +433,7 @@ class TestSchemaConverter(TestCase): }, } - Model = self.converter.build_with_instance(schema) + Model = self.converter.build_with_cache(schema) obj = Model(id=1) self.assertEqual(obj.id, 1) @@ -457,7 +457,7 @@ class TestSchemaConverter(TestCase): "properties": {"email": {"type": "string", "format": "email"}}, } - model = self.converter.build_with_instance(schema) + model = self.converter.build_with_cache(schema) self.assertEqual(model(email="test@example.com").email, "test@example.com") with self.assertRaises(ValidationError): @@ -470,7 +470,7 @@ class TestSchemaConverter(TestCase): "properties": {"website": {"type": "string", "format": "uri"}}, } - model = self.converter.build_with_instance(schema) + model = self.converter.build_with_cache(schema) self.assertEqual( model(website="https://example.com").website, AnyUrl("https://example.com") ) @@ -485,7 +485,7 @@ class TestSchemaConverter(TestCase): "properties": {"ip": {"type": "string", "format": "ipv4"}}, } - model = self.converter.build_with_instance(schema) + model = self.converter.build_with_cache(schema) self.assertEqual(model(ip="192.168.1.1").ip, IPv4Address("192.168.1.1")) with self.assertRaises(ValidationError): @@ -498,7 +498,7 @@ class TestSchemaConverter(TestCase): "properties": {"ip": {"type": "string", "format": "ipv6"}}, } - model = self.converter.build_with_instance(schema) + model = self.converter.build_with_cache(schema) self.assertEqual( model(ip="2001:0db8:85a3:0000:0000:8a2e:0370:7334").ip, IPv6Address("2001:0db8:85a3:0000:0000:8a2e:0370:7334"), @@ -514,7 +514,7 @@ class TestSchemaConverter(TestCase): "properties": {"id": {"type": "string", "format": "uuid"}}, } - model = self.converter.build_with_instance(schema) + model = self.converter.build_with_cache(schema) self.assertEqual( model(id="123e4567-e89b-12d3-a456-426614174000").id, @@ -531,7 +531,7 @@ class TestSchemaConverter(TestCase): "properties": {"hostname": {"type": "string", "format": "hostname"}}, } - model = self.converter.build_with_instance(schema) + model = self.converter.build_with_cache(schema) self.assertEqual(model(hostname="example.com").hostname, "example.com") with self.assertRaises(ValidationError): @@ -544,7 +544,7 @@ class TestSchemaConverter(TestCase): "properties": {"timestamp": {"type": "string", "format": "date-time"}}, } - model = self.converter.build_with_instance(schema) + model = self.converter.build_with_cache(schema) self.assertEqual( model(timestamp="2024-01-01T12:00:00Z").timestamp.isoformat(), "2024-01-01T12:00:00+00:00", @@ -560,7 +560,7 @@ class TestSchemaConverter(TestCase): "properties": {"time": {"type": "string", "format": "time"}}, } - model = self.converter.build_with_instance(schema) + model = self.converter.build_with_cache(schema) self.assertEqual( model(time="20:20:39+00:00").time.isoformat(), "20:20:39+00:00" ) @@ -576,7 +576,7 @@ class TestSchemaConverter(TestCase): } with self.assertRaises(InvalidSchemaException): - self.converter.build_with_instance(schema) + self.converter.build_with_cache(schema) def test_ref_with_root_ref(self): schema = { @@ -592,7 +592,7 @@ class TestSchemaConverter(TestCase): "required": ["name", "age"], } - model = self.converter.build_with_instance(schema) + model = self.converter.build_with_cache(schema) obj = model( name="John", @@ -627,7 +627,7 @@ class TestSchemaConverter(TestCase): }, } - model = self.converter.build_with_instance(schema) + model = self.converter.build_with_cache(schema) obj = model( name="John", @@ -666,7 +666,7 @@ class TestSchemaConverter(TestCase): }, } - Model = self.converter.build_with_instance(schema) + Model = self.converter.build_with_cache(schema) obj = Model( name="John", @@ -692,7 +692,7 @@ class TestSchemaConverter(TestCase): "required": ["status"], } - Model = self.converter.build_with_instance(schema) + Model = self.converter.build_with_cache(schema) obj = Model(status="active") self.assertEqual(obj.status.value, "active") @@ -711,7 +711,7 @@ class TestSchemaConverter(TestCase): "required": ["status"], } - Model = self.converter.build_with_instance(schema) + Model = self.converter.build_with_cache(schema) obj = Model() self.assertEqual(obj.status.value, "active") @@ -728,7 +728,7 @@ class TestSchemaConverter(TestCase): "required": ["name"], } - Model = self.converter.build_with_instance(schema) + Model = self.converter.build_with_cache(schema) obj = Model() self.assertEqual(obj.name, "United States of America") @@ -751,7 +751,7 @@ class TestSchemaConverter(TestCase): "required": ["name"], } - Model = self.converter.build_with_instance(schema) + Model = self.converter.build_with_cache(schema) obj = Model() self.assertEqual(obj.name, ["Brazil"]) @@ -771,7 +771,7 @@ class TestSchemaConverter(TestCase): }, } - Model = self.converter.build_with_instance(schema) + Model = self.converter.build_with_cache(schema) obj = Model() self.assertIsNone(obj.a_thing) @@ -813,7 +813,7 @@ class TestSchemaConverter(TestCase): }, } - schema_type = self.converter.build_with_instance(schema) + schema_type = self.converter.build_with_cache(schema) # check for me that the types generated by the oneOf in the typing.Annotated have different names operating_system_field = schema_type.model_fields["operating_system"] @@ -827,7 +827,7 @@ class TestSchemaConverter(TestCase): def test_object_invalid_require(self): # https://github.com/HideyoshiNakazone/jambo/issues/60 - object_ = self.converter.build_with_instance( + object_ = self.converter.build_with_cache( { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "TEST", @@ -872,10 +872,10 @@ class TestSchemaConverter(TestCase): } converter1 = SchemaConverter(ref_cache) - model1 = converter1.build_with_instance(schema) + model1 = converter1.build_with_cache(schema) converter2 = SchemaConverter(ref_cache) - model2 = converter2.build_with_instance(schema) + model2 = converter2.build_with_cache(schema) self.assertIs(converter1._ref_cache, converter2._ref_cache) self.assertIs(model1, model2) @@ -894,8 +894,8 @@ class TestSchemaConverter(TestCase): "required": ["name", "age"], } - model1 = self.converter.build_with_instance(schema, without_cache=True) - model2 = self.converter.build_with_instance(schema, without_cache=True) + model1 = self.converter.build_with_cache(schema, without_cache=True) + model2 = self.converter.build_with_cache(schema, without_cache=True) self.assertIsNot(model1, model2) @@ -913,8 +913,8 @@ class TestSchemaConverter(TestCase): "required": ["name", "age"], } - model1 = self.converter.build_with_instance(schema, ref_cache={}) - model2 = self.converter.build_with_instance(schema, ref_cache={}) + model1 = self.converter.build_with_cache(schema, ref_cache={}) + model2 = self.converter.build_with_cache(schema, ref_cache={}) self.assertIsNot(model1, model2) @@ -932,7 +932,7 @@ class TestSchemaConverter(TestCase): "required": ["name", "age"], } - model = self.converter.build_with_instance(schema) + model = self.converter.build_with_cache(schema) cached_model = self.converter.get_cached_ref("Person") @@ -962,7 +962,7 @@ class TestSchemaConverter(TestCase): "required": ["name", "age", "address"], } - model = self.converter.build_with_instance(schema) + model = self.converter.build_with_cache(schema) cached_model = self.converter.get_cached_ref("Person.address") @@ -990,7 +990,7 @@ class TestSchemaConverter(TestCase): }, } - person_model = self.converter.build_with_instance(schema) + person_model = self.converter.build_with_cache(schema) cached_person_model = self.converter.get_cached_ref("person") self.assertIs(person_model, cached_person_model) -- 2.49.1 From beed4e5e9778d5f7b460b550a74005adfcbc7f41 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Wed, 26 Nov 2025 09:00:57 -0300 Subject: [PATCH 79/96] chore: improves documentation and readme --- README.md | 54 ++++++++++++++++++++++++++++++--- docs/source/usage.ref_cache.rst | 6 ++++ docs/source/usage.rst | 7 ++++- 3 files changed, 61 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 56a5962..47c2040 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Jambo - JSON Schema to Pydantic Converter -

+

- + Last commit Tests @@ -19,12 +19,13 @@

**Jambo** is a Python package that automatically converts [JSON Schema](https://json-schema.org/) definitions into [Pydantic](https://docs.pydantic.dev/) models. -It's designed to streamline schema validation and enforce type safety using Pydantic's powerful validation features. +It's designed to streamline schema validation and enforce type safety using Pydantic's validation features. -Created to simplifying the process of dynamically generating Pydantic models for AI frameworks like [LangChain](https://www.langchain.com/), [CrewAI](https://www.crewai.com/), and others. +Created to simplify the process of dynamically generating Pydantic models for AI frameworks like [LangChain](https://www.langchain.com/), [CrewAI](https://www.crewai.com/), and others. --- + ## ✨ Features - ✅ Convert JSON Schema into Pydantic models dynamically; @@ -56,10 +57,25 @@ pip install jambo ## 🚀 Usage +There are two ways to build models with Jambo: + +1. The original static API: `SchemaConverter.build(schema)` doesn't persist any reference cache between calls and doesn't require any configuration. +2. The new instance API: use a `SchemaConverter()` instance and call `build_with_cache`, which exposes and persists a reference cache and helper methods. + +The instance API is useful when you want to reuse generated subtypes, inspect cached models, or share caches between converters. See the docs for full details: https://jambo.readthedocs.io/en/latest/usage.ref_cache.html + + +> [!NOTE] +> The use of the instance API and ref cache can cause schema and type name collisions if not managed carefully, therefore +> it's recommended that each namespace or schema source uses its own `SchemaConverter` instance. +> If you don't need cache control, the static API is simpler and sufficient for most use cases. + + +### Static (compatibility) example + ```python from jambo import SchemaConverter - schema = { "title": "Person", "type": "object", @@ -70,12 +86,40 @@ schema = { "required": ["name"], } +# Old-style convenience API (kept for compatibility) Person = SchemaConverter.build(schema) obj = Person(name="Alice", age=30) print(obj) ``` +### Instance API (recommended for cache control) + +```python +from jambo import SchemaConverter + +converter = SchemaConverter() + +schema = { + "title": "Person", + "type": "object", + "properties": { + "name": {"type": "string"}, + "age": {"type": "integer"}, + "address": {"type": "object", "properties": {"street": {"type": "string"}}}, + }, + "required": ["name"], +} + +# build_with_cache populates the converter's instance-level ref cache +Person = converter.build_with_cache(schema) + +# you can retrieve cached subtypes by name/path +cached_person = converter.get_cached_ref("Person") +# clear the instance cache when needed +converter.clear_ref_cache() +``` + --- ## ✅ Example Validations diff --git a/docs/source/usage.ref_cache.rst b/docs/source/usage.ref_cache.rst index adcf2f9..7fda88e 100644 --- a/docs/source/usage.ref_cache.rst +++ b/docs/source/usage.ref_cache.rst @@ -10,6 +10,12 @@ and :py:meth:`SchemaConverter.build `. However, only :py:meth:`SchemaConverter.build_with_cache ` exposes the cache through a supported API; :py:meth:`SchemaConverter.build ` uses the cache internally and does not provide access to it. +The reference cache accepts a mutable mapping (typically a plain Python dict) +that maps reference names (strings) to generated Pydantic model classes. +Since only the reference names are stored it can cause name collisions if +multiple schemas with overlapping names are processed using the same cache. +Therefore, it's recommended that each namespace or schema source uses its own +:class:`SchemaConverter` instance. ----------------------------------------- Configuring and Using the Reference Cache diff --git a/docs/source/usage.rst b/docs/source/usage.rst index 5a7dc08..5db74f4 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -42,7 +42,7 @@ Static Method (no config) The :py:meth:`SchemaConverter.build ` static method takes a JSON Schema dictionary and returns a Pydantic model class. -Note: the static ``build`` method was the original public API of this library and is kept for backwards compatibility. It creates and returns a model class for the provided schema but does not expose or persist an instance cache. +Note: the static ``build`` method was the original public API of this library. It creates and returns a model class for the provided schema but does not expose or persist an instance cache. -------------------------------- @@ -97,6 +97,11 @@ the instance method persists and exposes the reference cache and provides helper :py:meth:`SchemaConverter.get_cached_ref ` and :py:meth:`SchemaConverter.clear_ref_cache `. +.. warning:: + The instance API with reference cache can lead to schema and type name collisions if not managed carefully. + It's recommended that each namespace or schema source uses its own `SchemaConverter` instance. + If you don't need cache control, the static API is simpler and sufficient for most use cases. + For details and examples about the reference cache and the different cache modes (instance cache, per-call cache, ephemeral cache), see: .. toctree:: -- 2.49.1 From d418ad96ad493afb3afe2175908d0abbae24644b Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Wed, 26 Nov 2025 10:42:15 -0300 Subject: [PATCH 80/96] feat: adds support for list of types --- jambo/parser/_type_parser.py | 33 +++++++++++++++++++++- jambo/schema_converter.py | 11 ++++++-- jambo/types/json_schema_type.py | 2 +- tests/test_schema_converter.py | 49 +++++++++++++++++++++++++++++++++ 4 files changed, 91 insertions(+), 4 deletions(-) diff --git a/jambo/parser/_type_parser.py b/jambo/parser/_type_parser.py index efca162..10d479a 100644 --- a/jambo/parser/_type_parser.py +++ b/jambo/parser/_type_parser.py @@ -73,9 +73,40 @@ class GenericTypeParser(ABC, Generic[T]): :param kwargs: Additional options for type parsing. :return: A tuple containing the type and its properties. """ - parser = cls._get_impl(properties) + + parser = cls._get_impl( + cls._normalize_properties(properties) + ) return parser().from_properties(name=name, properties=properties, **kwargs) + + @staticmethod + def _normalize_properties(properties: JSONSchema) -> JSONSchema: + """ + Normalizes the properties dictionary to ensure consistent structure. + :param properties: The properties to be normalized. + """ + type_value = properties.pop("type", None) + + if isinstance(type_value, str): + properties["type"] = type_value + return properties + + if isinstance(type_value, list) and len(type_value) == 0: + raise InvalidSchemaException( + "Invalid schema: 'type' list cannot be empty", invalid_field=str(properties) + ) + + + if isinstance(type_value, list) and len(type_value) == 1: + properties["type"] = type_value[0] + return properties + + if isinstance(type_value, list): + properties["anyOf"] = [{"type": t} for t in type_value] + return properties + + return properties @classmethod def _get_impl(cls, properties: JSONSchema) -> type[Self]: diff --git a/jambo/schema_converter.py b/jambo/schema_converter.py index 4a10c2d..c2c8cae 100644 --- a/jambo/schema_converter.py +++ b/jambo/schema_converter.py @@ -1,4 +1,4 @@ -from jambo.exceptions import InvalidSchemaException, UnsupportedSchemaException +from jambo.exceptions import InternalAssertionException, InvalidSchemaException, UnsupportedSchemaException from jambo.parser import ObjectTypeParser, RefTypeParser from jambo.types import JSONSchema, RefCacheDict @@ -135,5 +135,12 @@ class SchemaConverter: """ if "$ref" in schema: return "$ref" + + type_value = schema.get("type") + if isinstance(type_value, list): + raise InternalAssertionException( + "SchemaConverter._get_schema_type: 'type' field should not be a list here." + " This should have been normalized earlier." + ) - return schema.get("type") + return type_value diff --git a/jambo/types/json_schema_type.py b/jambo/types/json_schema_type.py index 3e73387..82b6762 100644 --- a/jambo/types/json_schema_type.py +++ b/jambo/types/json_schema_type.py @@ -42,7 +42,7 @@ JSONSchema = TypedDict( "description": str, "default": JSONType, "examples": List[JSONType], - "type": JSONSchemaType, + "type": JSONSchemaType|List[JSONSchemaType], "enum": List[JSONType], "const": JSONType, "properties": Dict[str, "JSONSchema"], diff --git a/tests/test_schema_converter.py b/tests/test_schema_converter.py index 018c412..01c18be 100644 --- a/tests/test_schema_converter.py +++ b/tests/test_schema_converter.py @@ -998,3 +998,52 @@ class TestSchemaConverter(TestCase): cached_address_model = self.converter.get_cached_ref("address") self.assertIsNotNone(cached_address_model) + + def test_parse_list_type_multiple_values(self): + schema = { + "title": "TestListType", + "type": "object", + "properties": { + "values": { + "type": ["string", "number"] + } + }, + } + + Model = self.converter.build_with_cache(schema) + + obj1 = Model(values="a string") + self.assertEqual(obj1.values, "a string") + + obj2 = Model(values=42) + self.assertEqual(obj2.values, 42) + + def test_parse_list_type_one_value(self): + schema = { + "title": "TestListType", + "type": "object", + "properties": { + "values": { + "type": ["string"] + } + }, + } + + Model = self.converter.build_with_cache(schema) + + obj1 = Model(values="a string") + self.assertEqual(obj1.values, "a string") + + def test_parse_list_type_empty(self): + schema = { + "title": "TestListType", + "type": "object", + "properties": { + "values": { + "type": [] + } + }, + } + + with self.assertRaises(InvalidSchemaException): + self.converter.build_with_cache(schema) \ No newline at end of file -- 2.49.1 From 40106e4765458db9952684b68dfca307e6df0d56 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Wed, 26 Nov 2025 10:52:50 -0300 Subject: [PATCH 81/96] feat: validates that top level type cannot be list --- jambo/schema_converter.py | 5 ++--- tests/test_schema_converter.py | 9 +++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/jambo/schema_converter.py b/jambo/schema_converter.py index c2c8cae..3a93bc6 100644 --- a/jambo/schema_converter.py +++ b/jambo/schema_converter.py @@ -138,9 +138,8 @@ class SchemaConverter: type_value = schema.get("type") if isinstance(type_value, list): - raise InternalAssertionException( - "SchemaConverter._get_schema_type: 'type' field should not be a list here." - " This should have been normalized earlier." + raise InvalidSchemaException( + "Invalid schema: 'type' cannot be a list at the top level", invalid_field=str(schema) ) return type_value diff --git a/tests/test_schema_converter.py b/tests/test_schema_converter.py index 01c18be..3bfe327 100644 --- a/tests/test_schema_converter.py +++ b/tests/test_schema_converter.py @@ -1045,5 +1045,14 @@ class TestSchemaConverter(TestCase): }, } + with self.assertRaises(InvalidSchemaException): + self.converter.build_with_cache(schema) + + def test_parse_list_type_root_level_throws(self): + schema = { + "title": "TestListType", + "type": ["string", "number"] + } + with self.assertRaises(InvalidSchemaException): self.converter.build_with_cache(schema) \ No newline at end of file -- 2.49.1 From 27e756dadf77b7fdc7d6129f6b702774bb621d2e Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Wed, 26 Nov 2025 10:54:42 -0300 Subject: [PATCH 82/96] feat: format and linting pre-merge --- jambo/parser/_type_parser.py | 18 ++++++++---------- jambo/schema_converter.py | 7 ++++--- jambo/types/json_schema_type.py | 2 +- tests/test_schema_converter.py | 25 +++++-------------------- 4 files changed, 18 insertions(+), 34 deletions(-) diff --git a/jambo/parser/_type_parser.py b/jambo/parser/_type_parser.py index 10d479a..bcc908b 100644 --- a/jambo/parser/_type_parser.py +++ b/jambo/parser/_type_parser.py @@ -73,13 +73,11 @@ class GenericTypeParser(ABC, Generic[T]): :param kwargs: Additional options for type parsing. :return: A tuple containing the type and its properties. """ - - parser = cls._get_impl( - cls._normalize_properties(properties) - ) + + parser = cls._get_impl(cls._normalize_properties(properties)) return parser().from_properties(name=name, properties=properties, **kwargs) - + @staticmethod def _normalize_properties(properties: JSONSchema) -> JSONSchema: """ @@ -87,21 +85,21 @@ class GenericTypeParser(ABC, Generic[T]): :param properties: The properties to be normalized. """ type_value = properties.pop("type", None) - + if isinstance(type_value, str): properties["type"] = type_value return properties - + if isinstance(type_value, list) and len(type_value) == 0: raise InvalidSchemaException( - "Invalid schema: 'type' list cannot be empty", invalid_field=str(properties) + "Invalid schema: 'type' list cannot be empty", + invalid_field=str(properties), ) - if isinstance(type_value, list) and len(type_value) == 1: properties["type"] = type_value[0] return properties - + if isinstance(type_value, list): properties["anyOf"] = [{"type": t} for t in type_value] return properties diff --git a/jambo/schema_converter.py b/jambo/schema_converter.py index 3a93bc6..37a062d 100644 --- a/jambo/schema_converter.py +++ b/jambo/schema_converter.py @@ -1,4 +1,4 @@ -from jambo.exceptions import InternalAssertionException, InvalidSchemaException, UnsupportedSchemaException +from jambo.exceptions import InvalidSchemaException, UnsupportedSchemaException from jambo.parser import ObjectTypeParser, RefTypeParser from jambo.types import JSONSchema, RefCacheDict @@ -135,11 +135,12 @@ class SchemaConverter: """ if "$ref" in schema: return "$ref" - + type_value = schema.get("type") if isinstance(type_value, list): raise InvalidSchemaException( - "Invalid schema: 'type' cannot be a list at the top level", invalid_field=str(schema) + "Invalid schema: 'type' cannot be a list at the top level", + invalid_field=str(schema), ) return type_value diff --git a/jambo/types/json_schema_type.py b/jambo/types/json_schema_type.py index 82b6762..c720106 100644 --- a/jambo/types/json_schema_type.py +++ b/jambo/types/json_schema_type.py @@ -42,7 +42,7 @@ JSONSchema = TypedDict( "description": str, "default": JSONType, "examples": List[JSONType], - "type": JSONSchemaType|List[JSONSchemaType], + "type": JSONSchemaType | List[JSONSchemaType], "enum": List[JSONType], "const": JSONType, "properties": Dict[str, "JSONSchema"], diff --git a/tests/test_schema_converter.py b/tests/test_schema_converter.py index 3bfe327..450e441 100644 --- a/tests/test_schema_converter.py +++ b/tests/test_schema_converter.py @@ -1003,11 +1003,7 @@ class TestSchemaConverter(TestCase): schema = { "title": "TestListType", "type": "object", - "properties": { - "values": { - "type": ["string", "number"] - } - }, + "properties": {"values": {"type": ["string", "number"]}}, } Model = self.converter.build_with_cache(schema) @@ -1022,11 +1018,7 @@ class TestSchemaConverter(TestCase): schema = { "title": "TestListType", "type": "object", - "properties": { - "values": { - "type": ["string"] - } - }, + "properties": {"values": {"type": ["string"]}}, } Model = self.converter.build_with_cache(schema) @@ -1038,21 +1030,14 @@ class TestSchemaConverter(TestCase): schema = { "title": "TestListType", "type": "object", - "properties": { - "values": { - "type": [] - } - }, + "properties": {"values": {"type": []}}, } with self.assertRaises(InvalidSchemaException): self.converter.build_with_cache(schema) def test_parse_list_type_root_level_throws(self): - schema = { - "title": "TestListType", - "type": ["string", "number"] - } + schema = {"title": "TestListType", "type": ["string", "number"]} with self.assertRaises(InvalidSchemaException): - self.converter.build_with_cache(schema) \ No newline at end of file + self.converter.build_with_cache(schema) -- 2.49.1 From fcea994dd68ac07eb7c3f0d130aa1ca3c5268759 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Wed, 26 Nov 2025 15:05:10 -0300 Subject: [PATCH 83/96] feat: adds caching per namespace --- jambo/schema_converter.py | 32 +++++--- tests/test_schema_converter.py | 129 ++++++++++++++++++++++++++++++++- 2 files changed, 150 insertions(+), 11 deletions(-) diff --git a/jambo/schema_converter.py b/jambo/schema_converter.py index 37a062d..0e92119 100644 --- a/jambo/schema_converter.py +++ b/jambo/schema_converter.py @@ -17,10 +17,12 @@ class SchemaConverter: fields and types. The generated model can be used for data validation and serialization. """ - def __init__(self, ref_cache: Optional[RefCacheDict] = None) -> None: - if ref_cache is None: - ref_cache = dict() - self._ref_cache = ref_cache + def __init__( + self, namespace_registry: Optional[dict[str, RefCacheDict]] = None + ) -> None: + if namespace_registry is None: + namespace_registry = dict() + self._namespace_registry = namespace_registry def build_with_cache( self, @@ -43,7 +45,8 @@ class SchemaConverter: if without_cache: local_ref_cache = dict() elif ref_cache is None: - local_ref_cache = self._ref_cache + namespace = schema.get("$id", "default") + local_ref_cache = self._namespace_registry.setdefault(namespace, dict()) else: local_ref_cache = ref_cache @@ -107,19 +110,28 @@ class SchemaConverter: unsupported_field=unsupported_type, ) - def clear_ref_cache(self) -> None: + def clear_ref_cache(self, namespace: Optional[str] = "default") -> None: """ Clears the reference cache. """ - self._ref_cache.clear() + if namespace is None: + self._namespace_registry.clear() + return - def get_cached_ref(self, ref_name: str): + if namespace in self._namespace_registry: + self._namespace_registry[namespace].clear() + + def get_cached_ref( + self, ref_name: str, namespace: str = "default" + ) -> Optional[type]: """ Gets a cached reference from the reference cache. :param ref_name: The name of the reference to get. :return: The cached reference, or None if not found. - """ - cached_type = self._ref_cache.get(ref_name) + """ + cached_type = self._namespace_registry.get( + namespace, {} + ).get(ref_name) if isinstance(cached_type, type): return cached_type diff --git a/tests/test_schema_converter.py b/tests/test_schema_converter.py index 450e441..b8da4d1 100644 --- a/tests/test_schema_converter.py +++ b/tests/test_schema_converter.py @@ -877,7 +877,6 @@ class TestSchemaConverter(TestCase): converter2 = SchemaConverter(ref_cache) model2 = converter2.build_with_cache(schema) - self.assertIs(converter1._ref_cache, converter2._ref_cache) self.assertIs(model1, model2) def test_instance_level_ref_cache_isolation_via_without_cache_param(self): @@ -1041,3 +1040,131 @@ class TestSchemaConverter(TestCase): with self.assertRaises(InvalidSchemaException): self.converter.build_with_cache(schema) + + def tests_instance_level_ref_cache_isolation_via_property_id(self): + schema1: JSONSchema = { + "$id": "http://example.com/schemas/person1.json", + "title": "Person", + "type": "object", + "properties": { + "name": {"type": "string"}, + "age": {"type": "integer"}, + "emergency_contact": { + "$ref": "#", + }, + }, + "required": ["name", "age"], + } + + model1 = self.converter.build_with_cache(schema1) + + schema2: JSONSchema = { + "$id": "http://example.com/schemas/person2.json", + "title": "Person", + "type": "object", + "properties": { + "name": {"type": "string"}, + "age": {"type": "integer"}, + "address": {"type": "string"}, + }, + "required": ["name", "age", "address"], + } + + model2 = self.converter.build_with_cache(schema2) + + self.assertIsNot(model1, model2) + + def tests_instance_level_ref_cache_colision_when_same_property_id(self): + schema1: JSONSchema = { + "$id": "http://example.com/schemas/person.json", + "title": "Person", + "type": "object", + "properties": { + "name": {"type": "string"}, + "age": {"type": "integer"}, + "emergency_contact": { + "$ref": "#", + }, + }, + "required": ["name", "age"], + } + + model1 = self.converter.build_with_cache(schema1) + + schema2: JSONSchema = { + "$id": "http://example.com/schemas/person.json", + "title": "Person", + "type": "object", + "properties": { + "name": {"type": "string"}, + "age": {"type": "integer"}, + "address": {"type": "string"}, + }, + "required": ["name", "age", "address"], + } + + model2 = self.converter.build_with_cache(schema2) + + self.assertIs(model1, model2) + + def test_namespace_isolation_via_on_call_config(self): + namespace = "namespace1" + + schema: JSONSchema = { + "$id": namespace, + "title": "Person", + "type": "object", + "properties": { + "name": {"type": "string"}, + "age": {"type": "integer"}, + "address": { + "type": "object", + "properties": { + "street": {"type": "string"}, + "city": {"type": "string"}, + }, + "required": ["street", "city"], + }, + }, + "required": ["name", "age", "address"], + } + + model = self.converter.build_with_cache(schema) + + invalid_cached_model = self.converter.get_cached_ref("Person") + self.assertIsNone(invalid_cached_model) + + cached_model = self.converter.get_cached_ref("Person", namespace=namespace) + self.assertIs(model, cached_model) + + def test_clear_namespace_registry(self): + namespace = "namespace_to_clear" + + schema: JSONSchema = { + "$id": namespace, + "title": "Person", + "type": "object", + "properties": { + "name": {"type": "string"}, + "age": {"type": "integer"}, + "address": { + "type": "object", + "properties": { + "street": {"type": "string"}, + "city": {"type": "string"}, + }, + "required": ["street", "city"], + }, + }, + "required": ["name", "age", "address"], + } + + model = self.converter.build_with_cache(schema) + + cached_model = self.converter.get_cached_ref("Person", namespace=namespace) + self.assertIs(model, cached_model) + + self.converter.clear_ref_cache(namespace=namespace) + + cleared_cached_model = self.converter.get_cached_ref("Person", namespace=namespace) + self.assertIsNone(cleared_cached_model) -- 2.49.1 From 136d68d273ae234612fb9e2ce727de054471dac1 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Wed, 26 Nov 2025 15:07:22 -0300 Subject: [PATCH 84/96] feat: alter tests to clear all namespaces on tearDown --- tests/test_schema_converter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_schema_converter.py b/tests/test_schema_converter.py index b8da4d1..dd2b32c 100644 --- a/tests/test_schema_converter.py +++ b/tests/test_schema_converter.py @@ -19,7 +19,7 @@ class TestSchemaConverter(TestCase): self.converter = SchemaConverter() def tearDown(self): - self.converter.clear_ref_cache() + self.converter.clear_ref_cache(namespace=None) def test_invalid_schema(self): schema = { -- 2.49.1 From 628abe161dbaf676c25abd7e1de6471da35b1cc6 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Wed, 26 Nov 2025 15:23:29 -0300 Subject: [PATCH 85/96] feat: adds newly added feature to the docs --- README.md | 8 +-- docs/source/usage.ref_cache.rst | 113 +++++++++++++++++++++++++++++++- docs/source/usage.rst | 6 +- 3 files changed, 115 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 47c2040..535d4a1 100644 --- a/README.md +++ b/README.md @@ -62,13 +62,7 @@ There are two ways to build models with Jambo: 1. The original static API: `SchemaConverter.build(schema)` doesn't persist any reference cache between calls and doesn't require any configuration. 2. The new instance API: use a `SchemaConverter()` instance and call `build_with_cache`, which exposes and persists a reference cache and helper methods. -The instance API is useful when you want to reuse generated subtypes, inspect cached models, or share caches between converters. See the docs for full details: https://jambo.readthedocs.io/en/latest/usage.ref_cache.html - - -> [!NOTE] -> The use of the instance API and ref cache can cause schema and type name collisions if not managed carefully, therefore -> it's recommended that each namespace or schema source uses its own `SchemaConverter` instance. -> If you don't need cache control, the static API is simpler and sufficient for most use cases. +The instance API is useful when you want to reuse generated subtypes, inspect cached models, or share caches between converters; all leveraging namespaces via the `$id` property in JSON Schema. See the docs for full details: https://jambo.readthedocs.io/en/latest/usage.ref_cache.html ### Static (compatibility) example diff --git a/docs/source/usage.ref_cache.rst b/docs/source/usage.ref_cache.rst index 7fda88e..a54c7ce 100644 --- a/docs/source/usage.ref_cache.rst +++ b/docs/source/usage.ref_cache.rst @@ -86,8 +86,32 @@ reference cache (a plain dict). Reusing the same converter instance across multiple calls will reuse that cache and therefore reuse previously generated model classes. +That cache is isolated per namespace via the `$id` property in JSON Schema, so +schemas with different `$id` values will not collide in the same cache. + .. code-block:: python + from jambo import SchemaConverter + + # no $id in this example, therefore a default namespace is used + schema = { + "title": "Person", + "type": "object", + "properties": { + "name": {"type": "string"}, + "age": {"type": "integer"}, + "address": { + "type": "object", + "properties": { + "street": {"type": "string"}, + "city": {"type": "string"}, + }, + "required": ["street", "city"], + }, + }, + "required": ["name", "address"], + } + converter = SchemaConverter() # has its own internal cache model1 = converter.build_with_cache(schema) @@ -96,6 +120,39 @@ model classes. # model1 and model2 are the same object because the instance cache persisted assert model1 is model2 +When passing a schema with a different `$id`, the instance cache keeps types +separate: + +.. code-block:: python + + schema_a = { + "$id": "namespace_a", + "title": "Person", + "type": "object", + "properties": { + "name": {"type": "string"}, + }, + "required": ["name"], + } + + schema_b = { + "$id": "namespace_b", + "title": "Person", + "type": "object", + "properties": { + "name": {"type": "string"}, + }, + "required": ["name"], + } + + converter = SchemaConverter() # has its own internal cache + + model_a = converter.build_with_cache(schema_a) + model_b = converter.build_with_cache(schema_b) + + # different $id values isolate the types in the same cache + assert model_a is not model_b + If you want to temporarily avoid using the instance cache for a single call, use ``without_cache=True``. That causes :py:meth:`SchemaConverter.build_with_cache ` to use a fresh, empty cache for the duration of that call only: @@ -118,7 +175,7 @@ instance cache. Retrieving cached types ----------------------- -:py:meth:`SchemaConverter.get_cached_ref `(name) — returns a cached model class or ``None``. +:py:meth:`SchemaConverter.get_cached_ref `(name, namespace="default") — returns a cached model class or ``None``. Retrieving the root type of the schema ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -212,10 +269,62 @@ When retrieving a type defined in ``$defs``, access it directly by its name. cached_address_model = converter.get_cached_ref("address") +Isolation by Namespace +~~~~~~~~~~~~~~~~~~~~~~ + +The instance cache is isolated per namespace via the `$id` property in JSON Schema. +When retrieving a cached type, you can specify the namespace to look in +(via the ``namespace`` parameter). By default, the ``default`` namespace is used + + +.. code-block:: python + + from jambo import SchemaConverter + + converter = SchemaConverter() + + schema_a = { + "$id": "namespace_a", + "title": "Person", + "type": "object", + "properties": { + "name": {"type": "string"}, + }, + "required": ["name"], + } + + schema_b = { + "$id": "namespace_b", + "title": "Person", + "type": "object", + "properties": { + "name": {"type": "string"}, + }, + "required": ["name"], + } + + person_a = converter.build_with_cache(schema_a) + person_b = converter.build_with_cache(schema_b) + + cached_person_a = converter.get_cached_ref("Person", namespace="namespace_a") + cached_person_b = converter.get_cached_ref("Person", namespace="namespace_b") + + assert cached_person_a is person_a + assert cached_person_b is person_b + + Clearing the cache ------------------ -:py:meth:`SchemaConverter.clear_ref_cache `() — removes all entries from the instance cache. +:py:meth:`SchemaConverter.clear_ref_cache `(namespace: Optional[str]="default") — removes all entries from the instance cache. + + +When you want to clear the instance cache, use :py:meth:`SchemaConverter.clear_ref_cache `. +You can optionally specify a ``namespace`` to clear only that namespace; +otherwise, the default namespace is cleared. + +If you want to clear all namespaces, call :py:meth:`SchemaConverter.clear_ref_cache ` passing `None` as the namespace, +which removes all entries from all namespaces. Notes and Behavioural Differences diff --git a/docs/source/usage.rst b/docs/source/usage.rst index 5db74f4..8c16fee 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -99,9 +99,9 @@ the instance method persists and exposes the reference cache and provides helper .. warning:: The instance API with reference cache can lead to schema and type name collisions if not managed carefully. - It's recommended that each namespace or schema source uses its own `SchemaConverter` instance. - If you don't need cache control, the static API is simpler and sufficient for most use cases. - + It's recommended that each schema defines its own unique namespace using the `$id` property in JSON Schema, + and that access it's ref_cache by passing it explicitly when needed. + For details and examples about the reference cache and the different cache modes (instance cache, per-call cache, ephemeral cache), see: .. toctree:: -- 2.49.1 From ab9646238e9b12cd5129ee32d5dbaef3d49b861a Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Wed, 26 Nov 2025 15:30:38 -0300 Subject: [PATCH 86/96] chore: minor adjustment of docs --- docs/source/usage.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/usage.rst b/docs/source/usage.rst index 8c16fee..9183cc7 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -100,7 +100,7 @@ the instance method persists and exposes the reference cache and provides helper .. warning:: The instance API with reference cache can lead to schema and type name collisions if not managed carefully. It's recommended that each schema defines its own unique namespace using the `$id` property in JSON Schema, - and that access it's ref_cache by passing it explicitly when needed. + and then access it's ref_cache by passing it explicitly when needed. For details and examples about the reference cache and the different cache modes (instance cache, per-call cache, ephemeral cache), see: -- 2.49.1 From d8fe98639aaeba2e6e5f22478c74a2247556beac Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Fri, 28 Nov 2025 18:28:49 -0300 Subject: [PATCH 87/96] fix: fixes annotation definition in anyof parser --- jambo/parser/anyof_type_parser.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/jambo/parser/anyof_type_parser.py b/jambo/parser/anyof_type_parser.py index c0295ff..1cca751 100644 --- a/jambo/parser/anyof_type_parser.py +++ b/jambo/parser/anyof_type_parser.py @@ -42,8 +42,12 @@ class AnyOfTypeParser(GenericTypeParser): # By defining the type as Union of Annotated type we can use the Field validator # to enforce the constraints of each union type when needed. # We use Annotated to attach the Field validators to the type. - field_types = [ - Annotated[t, Field(**v)] if v is not None else t for t, v in sub_types - ] + field_types = [] + for subType, subProp in sub_types: + default_value = subProp.pop("default", None) + if default_value is None: + default_value = ... + + field_types.append(Annotated[subType, Field(default_value, **subProp)]) return Union[(*field_types,)], mapped_properties -- 2.49.1 From dd31f62ef2fca788221f55c456e9499e2deb9159 Mon Sep 17 00:00:00 2001 From: JCHacking Date: Mon, 1 Dec 2025 17:38:31 +0100 Subject: [PATCH 88/96] feat: duration string parser --- .githooks/pre-commit-config.yaml | 2 +- jambo/parser/string_type_parser.py | 29 +++---------------------- jambo/schema_converter.py | 6 ++--- tests/parser/test_string_type_parser.py | 8 +++---- tests/test_schema_converter.py | 18 ++++++++------- 5 files changed, 19 insertions(+), 44 deletions(-) diff --git a/.githooks/pre-commit-config.yaml b/.githooks/pre-commit-config.yaml index 08560b3..9aa9bb0 100644 --- a/.githooks/pre-commit-config.yaml +++ b/.githooks/pre-commit-config.yaml @@ -1,7 +1,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.11.4 + rev: v0.14.7 hooks: # Run the linter. - id: ruff diff --git a/jambo/parser/string_type_parser.py b/jambo/parser/string_type_parser.py index 4c18761..97f3886 100644 --- a/jambo/parser/string_type_parser.py +++ b/jambo/parser/string_type_parser.py @@ -2,8 +2,8 @@ from jambo.exceptions import InvalidSchemaException from jambo.parser._type_parser import GenericTypeParser from jambo.types.type_parser_options import TypeParserOptions -from pydantic import AnyUrl, EmailStr -from typing_extensions import Any, Unpack +from pydantic import AnyUrl, EmailStr, TypeAdapter +from typing_extensions import Unpack from datetime import date, datetime, time, timedelta from ipaddress import IPv4Address, IPv6Address @@ -64,7 +64,7 @@ class StringTypeParser(GenericTypeParser): if "examples" in mapped_properties: mapped_properties["examples"] = [ - self._parse_example(example, format_type, mapped_type) + TypeAdapter(mapped_type).validate_python(example) for example in mapped_properties["examples"] ] @@ -73,26 +73,3 @@ class StringTypeParser(GenericTypeParser): mapped_properties["json_schema_extra"]["format"] = format_type return mapped_type, mapped_properties - - def _parse_example( - self, example: Any, format_type: str, mapped_type: type[Any] - ) -> Any: - """ - Parse example from JSON Schema format to python format - :param example: Example Value - :param format_type: Format Type - :param mapped_type: Type to parse - :return: Example parsed - """ - match format_type: - case "date" | "time" | "date-time": - return mapped_type.fromisoformat(example) - case "duration": - # TODO: Implement duration parser - raise NotImplementedError - case "ipv4" | "ipv6": - return mapped_type(example) - case "uuid": - return mapped_type(example) - case _: - return example diff --git a/jambo/schema_converter.py b/jambo/schema_converter.py index 0e92119..2e46828 100644 --- a/jambo/schema_converter.py +++ b/jambo/schema_converter.py @@ -128,10 +128,8 @@ class SchemaConverter: Gets a cached reference from the reference cache. :param ref_name: The name of the reference to get. :return: The cached reference, or None if not found. - """ - cached_type = self._namespace_registry.get( - namespace, {} - ).get(ref_name) + """ + cached_type = self._namespace_registry.get(namespace, {}).get(ref_name) if isinstance(cached_type, type): return cached_type diff --git a/tests/parser/test_string_type_parser.py b/tests/parser/test_string_type_parser.py index b00b487..56334ec 100644 --- a/tests/parser/test_string_type_parser.py +++ b/tests/parser/test_string_type_parser.py @@ -3,7 +3,6 @@ from jambo.parser import StringTypeParser from pydantic import AnyUrl, EmailStr -import unittest from datetime import date, datetime, time, timedelta, timezone from ipaddress import IPv4Address, IPv6Address, ip_address from unittest import TestCase @@ -121,7 +120,7 @@ class TestStringTypeParser(TestCase): type_parsing, type_validator = parser.from_properties("placeholder", properties) self.assertEqual(type_parsing, AnyUrl) - self.assertEqual(type_validator["examples"], ["test://domain/resource"]) + self.assertEqual(type_validator["examples"], [AnyUrl("test://domain/resource")]) def test_string_parser_with_ip_formats(self): parser = StringTypeParser() @@ -299,7 +298,6 @@ class TestStringTypeParser(TestCase): }, ) - @unittest.skip("Duration parsing not yet implemented") def test_string_parser_with_timedelta_format(self): parser = StringTypeParser() @@ -315,9 +313,9 @@ class TestStringTypeParser(TestCase): self.assertEqual( type_validator["examples"], [ - timedelta(days=7), + timedelta(days=428, hours=4, minutes=5, seconds=6), timedelta(minutes=30), - timedelta(hours=4, minutes=5, seconds=6), + timedelta(days=7), timedelta(seconds=0.5), ], ) diff --git a/tests/test_schema_converter.py b/tests/test_schema_converter.py index dd2b32c..e9bcb5b 100644 --- a/tests/test_schema_converter.py +++ b/tests/test_schema_converter.py @@ -1109,7 +1109,7 @@ class TestSchemaConverter(TestCase): def test_namespace_isolation_via_on_call_config(self): namespace = "namespace1" - + schema: JSONSchema = { "$id": namespace, "title": "Person", @@ -1130,16 +1130,16 @@ class TestSchemaConverter(TestCase): } model = self.converter.build_with_cache(schema) - + invalid_cached_model = self.converter.get_cached_ref("Person") self.assertIsNone(invalid_cached_model) - + cached_model = self.converter.get_cached_ref("Person", namespace=namespace) self.assertIs(model, cached_model) def test_clear_namespace_registry(self): namespace = "namespace_to_clear" - + schema: JSONSchema = { "$id": namespace, "title": "Person", @@ -1160,11 +1160,13 @@ class TestSchemaConverter(TestCase): } model = self.converter.build_with_cache(schema) - + cached_model = self.converter.get_cached_ref("Person", namespace=namespace) self.assertIs(model, cached_model) - + self.converter.clear_ref_cache(namespace=namespace) - - cleared_cached_model = self.converter.get_cached_ref("Person", namespace=namespace) + + cleared_cached_model = self.converter.get_cached_ref( + "Person", namespace=namespace + ) self.assertIsNone(cleared_cached_model) -- 2.49.1 From 422cc2efe00c32c55d4d7a79e4a68d0af30f70b2 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Fri, 5 Dec 2025 20:15:08 -0300 Subject: [PATCH 89/96] feat: captures any str validation exception and converts it into ValidationError converts any exception thrown in the str parser example validation into ValidationError so that the user knows that this is a error in the schema and not a parsing validation exception --- jambo/parser/string_type_parser.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/jambo/parser/string_type_parser.py b/jambo/parser/string_type_parser.py index 97f3886..b09cf20 100644 --- a/jambo/parser/string_type_parser.py +++ b/jambo/parser/string_type_parser.py @@ -2,7 +2,7 @@ from jambo.exceptions import InvalidSchemaException from jambo.parser._type_parser import GenericTypeParser from jambo.types.type_parser_options import TypeParserOptions -from pydantic import AnyUrl, EmailStr, TypeAdapter +from pydantic import AnyUrl, EmailStr, TypeAdapter, ValidationError from typing_extensions import Unpack from datetime import date, datetime, time, timedelta @@ -62,11 +62,16 @@ class StringTypeParser(GenericTypeParser): if format_type in self.format_pattern_mapping: mapped_properties["pattern"] = self.format_pattern_mapping[format_type] - if "examples" in mapped_properties: - mapped_properties["examples"] = [ - TypeAdapter(mapped_type).validate_python(example) - for example in mapped_properties["examples"] - ] + try: + if "examples" in mapped_properties: + mapped_properties["examples"] = [ + TypeAdapter(mapped_type).validate_python(example) + for example in mapped_properties["examples"] + ] + except ValidationError as err: + raise InvalidSchemaException( + f"Invalid example type for field {name}." + ) from err if "json_schema_extra" not in mapped_properties: mapped_properties["json_schema_extra"] = {} -- 2.49.1 From 558abf5d40e41c2424073b1728b67a7e064f5484 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Fri, 28 Nov 2025 19:45:11 -0300 Subject: [PATCH 90/96] chore: updates project to be python3.14 compatible --- .github/workflows/build.yml | 1 + .python-version | 1 + uv.lock | 1254 ++++++++++++++++++++--------------- 3 files changed, 734 insertions(+), 522 deletions(-) create mode 100644 .python-version diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 022fdcb..fc2a14c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,6 +23,7 @@ jobs: - "3.11" - "3.12" - "3.13" + - "3.14" steps: - uses: actions/checkout@v4 diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..6324d40 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.14 diff --git a/uv.lock b/uv.lock index fabfe91..2054175 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.10, <4.0" resolution-markers = [ "python_full_version >= '3.11'", @@ -26,26 +26,25 @@ wheels = [ [[package]] name = "anyio" -version = "4.9.0" +version = "4.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "idna" }, - { name = "sniffio" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" } +sdist = { url = "https://files.pythonhosted.org/packages/16/ce/8a777047513153587e5434fd752e89334ac33e379aa3497db860eeb60377/anyio-4.12.0.tar.gz", hash = "sha256:73c693b567b0c55130c104d0b43a9baf3aa6a31fc6110116509f27bf75e21ec0", size = 228266, upload-time = "2025-11-28T23:37:38.911Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, + { url = "https://files.pythonhosted.org/packages/7f/9c/36c5c37947ebfb8c7f22e0eb6e4d188ee2d53aa3880f3f2744fb894f0cb1/anyio-4.12.0-py3-none-any.whl", hash = "sha256:dad2376a628f98eeca4881fc56cd06affd18f659b17a747d3ff0307ced94b1bb", size = 113362, upload-time = "2025-11-28T23:36:57.897Z" }, ] [[package]] name = "attrs" -version = "25.3.0" +version = "25.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, ] [[package]] @@ -59,93 +58,121 @@ wheels = [ [[package]] name = "certifi" -version = "2025.6.15" +version = "2025.11.12" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/73/f7/f14b46d4bcd21092d7d3ccef689615220d8a08fb25e564b65d20738e672e/certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b", size = 158753, upload-time = "2025-06-15T02:45:51.329Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/ae/320161bd181fc06471eed047ecce67b693fd7515b16d495d8932db763426/certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057", size = 157650, upload-time = "2025-06-15T02:45:49.977Z" }, + { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" }, ] [[package]] name = "cfgv" -version = "3.4.0" +version = "3.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" }, + { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, ] [[package]] name = "charset-normalizer" -version = "3.4.2" +version = "3.4.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/95/28/9901804da60055b406e1a1c5ba7aac1276fb77f1dde635aabfc7fd84b8ab/charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941", size = 201818, upload-time = "2025-05-02T08:31:46.725Z" }, - { url = "https://files.pythonhosted.org/packages/d9/9b/892a8c8af9110935e5adcbb06d9c6fe741b6bb02608c6513983048ba1a18/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd", size = 144649, upload-time = "2025-05-02T08:31:48.889Z" }, - { url = "https://files.pythonhosted.org/packages/7b/a5/4179abd063ff6414223575e008593861d62abfc22455b5d1a44995b7c101/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6", size = 155045, upload-time = "2025-05-02T08:31:50.757Z" }, - { url = "https://files.pythonhosted.org/packages/3b/95/bc08c7dfeddd26b4be8c8287b9bb055716f31077c8b0ea1cd09553794665/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d", size = 147356, upload-time = "2025-05-02T08:31:52.634Z" }, - { url = "https://files.pythonhosted.org/packages/a8/2d/7a5b635aa65284bf3eab7653e8b4151ab420ecbae918d3e359d1947b4d61/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86", size = 149471, upload-time = "2025-05-02T08:31:56.207Z" }, - { url = "https://files.pythonhosted.org/packages/ae/38/51fc6ac74251fd331a8cfdb7ec57beba8c23fd5493f1050f71c87ef77ed0/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c", size = 151317, upload-time = "2025-05-02T08:31:57.613Z" }, - { url = "https://files.pythonhosted.org/packages/b7/17/edee1e32215ee6e9e46c3e482645b46575a44a2d72c7dfd49e49f60ce6bf/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0", size = 146368, upload-time = "2025-05-02T08:31:59.468Z" }, - { url = "https://files.pythonhosted.org/packages/26/2c/ea3e66f2b5f21fd00b2825c94cafb8c326ea6240cd80a91eb09e4a285830/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef", size = 154491, upload-time = "2025-05-02T08:32:01.219Z" }, - { url = "https://files.pythonhosted.org/packages/52/47/7be7fa972422ad062e909fd62460d45c3ef4c141805b7078dbab15904ff7/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6", size = 157695, upload-time = "2025-05-02T08:32:03.045Z" }, - { url = "https://files.pythonhosted.org/packages/2f/42/9f02c194da282b2b340f28e5fb60762de1151387a36842a92b533685c61e/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366", size = 154849, upload-time = "2025-05-02T08:32:04.651Z" }, - { url = "https://files.pythonhosted.org/packages/67/44/89cacd6628f31fb0b63201a618049be4be2a7435a31b55b5eb1c3674547a/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db", size = 150091, upload-time = "2025-05-02T08:32:06.719Z" }, - { url = "https://files.pythonhosted.org/packages/1f/79/4b8da9f712bc079c0f16b6d67b099b0b8d808c2292c937f267d816ec5ecc/charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a", size = 98445, upload-time = "2025-05-02T08:32:08.66Z" }, - { url = "https://files.pythonhosted.org/packages/7d/d7/96970afb4fb66497a40761cdf7bd4f6fca0fc7bafde3a84f836c1f57a926/charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509", size = 105782, upload-time = "2025-05-02T08:32:10.46Z" }, - { url = "https://files.pythonhosted.org/packages/05/85/4c40d00dcc6284a1c1ad5de5e0996b06f39d8232f1031cd23c2f5c07ee86/charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", size = 198794, upload-time = "2025-05-02T08:32:11.945Z" }, - { url = "https://files.pythonhosted.org/packages/41/d9/7a6c0b9db952598e97e93cbdfcb91bacd89b9b88c7c983250a77c008703c/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", size = 142846, upload-time = "2025-05-02T08:32:13.946Z" }, - { url = "https://files.pythonhosted.org/packages/66/82/a37989cda2ace7e37f36c1a8ed16c58cf48965a79c2142713244bf945c89/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", size = 153350, upload-time = "2025-05-02T08:32:15.873Z" }, - { url = "https://files.pythonhosted.org/packages/df/68/a576b31b694d07b53807269d05ec3f6f1093e9545e8607121995ba7a8313/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", size = 145657, upload-time = "2025-05-02T08:32:17.283Z" }, - { url = "https://files.pythonhosted.org/packages/92/9b/ad67f03d74554bed3aefd56fe836e1623a50780f7c998d00ca128924a499/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f", size = 147260, upload-time = "2025-05-02T08:32:18.807Z" }, - { url = "https://files.pythonhosted.org/packages/a6/e6/8aebae25e328160b20e31a7e9929b1578bbdc7f42e66f46595a432f8539e/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", size = 149164, upload-time = "2025-05-02T08:32:20.333Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f2/b3c2f07dbcc248805f10e67a0262c93308cfa149a4cd3d1fe01f593e5fd2/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", size = 144571, upload-time = "2025-05-02T08:32:21.86Z" }, - { url = "https://files.pythonhosted.org/packages/60/5b/c3f3a94bc345bc211622ea59b4bed9ae63c00920e2e8f11824aa5708e8b7/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", size = 151952, upload-time = "2025-05-02T08:32:23.434Z" }, - { url = "https://files.pythonhosted.org/packages/e2/4d/ff460c8b474122334c2fa394a3f99a04cf11c646da895f81402ae54f5c42/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", size = 155959, upload-time = "2025-05-02T08:32:24.993Z" }, - { url = "https://files.pythonhosted.org/packages/a2/2b/b964c6a2fda88611a1fe3d4c400d39c66a42d6c169c924818c848f922415/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", size = 153030, upload-time = "2025-05-02T08:32:26.435Z" }, - { url = "https://files.pythonhosted.org/packages/59/2e/d3b9811db26a5ebf444bc0fa4f4be5aa6d76fc6e1c0fd537b16c14e849b6/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", size = 148015, upload-time = "2025-05-02T08:32:28.376Z" }, - { url = "https://files.pythonhosted.org/packages/90/07/c5fd7c11eafd561bb51220d600a788f1c8d77c5eef37ee49454cc5c35575/charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", size = 98106, upload-time = "2025-05-02T08:32:30.281Z" }, - { url = "https://files.pythonhosted.org/packages/a8/05/5e33dbef7e2f773d672b6d79f10ec633d4a71cd96db6673625838a4fd532/charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", size = 105402, upload-time = "2025-05-02T08:32:32.191Z" }, - { url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936, upload-time = "2025-05-02T08:32:33.712Z" }, - { url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790, upload-time = "2025-05-02T08:32:35.768Z" }, - { url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924, upload-time = "2025-05-02T08:32:37.284Z" }, - { url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626, upload-time = "2025-05-02T08:32:38.803Z" }, - { url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567, upload-time = "2025-05-02T08:32:40.251Z" }, - { url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957, upload-time = "2025-05-02T08:32:41.705Z" }, - { url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408, upload-time = "2025-05-02T08:32:43.709Z" }, - { url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399, upload-time = "2025-05-02T08:32:46.197Z" }, - { url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815, upload-time = "2025-05-02T08:32:48.105Z" }, - { url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537, upload-time = "2025-05-02T08:32:49.719Z" }, - { url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565, upload-time = "2025-05-02T08:32:51.404Z" }, - { url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357, upload-time = "2025-05-02T08:32:53.079Z" }, - { url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776, upload-time = "2025-05-02T08:32:54.573Z" }, - { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" }, - { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" }, - { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" }, - { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" }, - { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" }, - { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" }, - { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" }, - { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" }, - { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" }, - { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" }, - { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" }, - { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" }, - { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" }, - { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/1f/b8/6d51fc1d52cbd52cd4ccedd5b5b2f0f6a11bbf6765c782298b0f3e808541/charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", size = 209709, upload-time = "2025-10-14T04:40:11.385Z" }, + { url = "https://files.pythonhosted.org/packages/5c/af/1f9d7f7faafe2ddfb6f72a2e07a548a629c61ad510fe60f9630309908fef/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", size = 148814, upload-time = "2025-10-14T04:40:13.135Z" }, + { url = "https://files.pythonhosted.org/packages/79/3d/f2e3ac2bbc056ca0c204298ea4e3d9db9b4afe437812638759db2c976b5f/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", size = 144467, upload-time = "2025-10-14T04:40:14.728Z" }, + { url = "https://files.pythonhosted.org/packages/ec/85/1bf997003815e60d57de7bd972c57dc6950446a3e4ccac43bc3070721856/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", size = 162280, upload-time = "2025-10-14T04:40:16.14Z" }, + { url = "https://files.pythonhosted.org/packages/3e/8e/6aa1952f56b192f54921c436b87f2aaf7c7a7c3d0d1a765547d64fd83c13/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", size = 159454, upload-time = "2025-10-14T04:40:17.567Z" }, + { url = "https://files.pythonhosted.org/packages/36/3b/60cbd1f8e93aa25d1c669c649b7a655b0b5fb4c571858910ea9332678558/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", size = 153609, upload-time = "2025-10-14T04:40:19.08Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/6a13396948b8fd3c4b4fd5bc74d045f5637d78c9675585e8e9fbe5636554/charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", size = 151849, upload-time = "2025-10-14T04:40:20.607Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7a/59482e28b9981d105691e968c544cc0df3b7d6133152fb3dcdc8f135da7a/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", size = 151586, upload-time = "2025-10-14T04:40:21.719Z" }, + { url = "https://files.pythonhosted.org/packages/92/59/f64ef6a1c4bdd2baf892b04cd78792ed8684fbc48d4c2afe467d96b4df57/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", size = 145290, upload-time = "2025-10-14T04:40:23.069Z" }, + { url = "https://files.pythonhosted.org/packages/6b/63/3bf9f279ddfa641ffa1962b0db6a57a9c294361cc2f5fcac997049a00e9c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", size = 163663, upload-time = "2025-10-14T04:40:24.17Z" }, + { url = "https://files.pythonhosted.org/packages/ed/09/c9e38fc8fa9e0849b172b581fd9803bdf6e694041127933934184e19f8c3/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", size = 151964, upload-time = "2025-10-14T04:40:25.368Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d1/d28b747e512d0da79d8b6a1ac18b7ab2ecfd81b2944c4c710e166d8dd09c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", size = 161064, upload-time = "2025-10-14T04:40:26.806Z" }, + { url = "https://files.pythonhosted.org/packages/bb/9a/31d62b611d901c3b9e5500c36aab0ff5eb442043fb3a1c254200d3d397d9/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", size = 155015, upload-time = "2025-10-14T04:40:28.284Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/107e008fa2bff0c8b9319584174418e5e5285fef32f79d8ee6a430d0039c/charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", size = 99792, upload-time = "2025-10-14T04:40:29.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/66/e396e8a408843337d7315bab30dbf106c38966f1819f123257f5520f8a96/charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", size = 107198, upload-time = "2025-10-14T04:40:30.644Z" }, + { url = "https://files.pythonhosted.org/packages/b5/58/01b4f815bf0312704c267f2ccb6e5d42bcc7752340cd487bc9f8c3710597/charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", size = 100262, upload-time = "2025-10-14T04:40:32.108Z" }, + { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, + { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, + { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, + { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, + { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, + { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, + { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, + { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, + { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, + { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, + { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, + { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, ] [[package]] name = "click" -version = "8.2.1" +version = "8.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, ] [[package]] @@ -159,80 +186,119 @@ wheels = [ [[package]] name = "coverage" -version = "7.8.0" +version = "7.12.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/19/4f/2251e65033ed2ce1e68f00f91a0294e0f80c80ae8c3ebbe2f12828c4cd53/coverage-7.8.0.tar.gz", hash = "sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501", size = 811872, upload-time = "2025-03-30T20:36:45.376Z" } +sdist = { url = "https://files.pythonhosted.org/packages/89/26/4a96807b193b011588099c3b5c89fbb05294e5b90e71018e065465f34eb6/coverage-7.12.0.tar.gz", hash = "sha256:fc11e0a4e372cb5f282f16ef90d4a585034050ccda536451901abfb19a57f40c", size = 819341, upload-time = "2025-11-18T13:34:20.766Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/01/1c5e6ee4ebaaa5e079db933a9a45f61172048c7efa06648445821a201084/coverage-7.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2931f66991175369859b5fd58529cd4b73582461877ecfd859b6549869287ffe", size = 211379, upload-time = "2025-03-30T20:34:53.904Z" }, - { url = "https://files.pythonhosted.org/packages/e9/16/a463389f5ff916963471f7c13585e5f38c6814607306b3cb4d6b4cf13384/coverage-7.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52a523153c568d2c0ef8826f6cc23031dc86cffb8c6aeab92c4ff776e7951b28", size = 211814, upload-time = "2025-03-30T20:34:56.959Z" }, - { url = "https://files.pythonhosted.org/packages/b8/b1/77062b0393f54d79064dfb72d2da402657d7c569cfbc724d56ac0f9c67ed/coverage-7.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c8a5c139aae4c35cbd7cadca1df02ea8cf28a911534fc1b0456acb0b14234f3", size = 240937, upload-time = "2025-03-30T20:34:58.751Z" }, - { url = "https://files.pythonhosted.org/packages/d7/54/c7b00a23150083c124e908c352db03bcd33375494a4beb0c6d79b35448b9/coverage-7.8.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a26c0c795c3e0b63ec7da6efded5f0bc856d7c0b24b2ac84b4d1d7bc578d676", size = 238849, upload-time = "2025-03-30T20:35:00.521Z" }, - { url = "https://files.pythonhosted.org/packages/f7/ec/a6b7cfebd34e7b49f844788fda94713035372b5200c23088e3bbafb30970/coverage-7.8.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:821f7bcbaa84318287115d54becb1915eece6918136c6f91045bb84e2f88739d", size = 239986, upload-time = "2025-03-30T20:35:02.307Z" }, - { url = "https://files.pythonhosted.org/packages/21/8c/c965ecef8af54e6d9b11bfbba85d4f6a319399f5f724798498387f3209eb/coverage-7.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a321c61477ff8ee705b8a5fed370b5710c56b3a52d17b983d9215861e37b642a", size = 239896, upload-time = "2025-03-30T20:35:04.141Z" }, - { url = "https://files.pythonhosted.org/packages/40/83/070550273fb4c480efa8381735969cb403fa8fd1626d74865bfaf9e4d903/coverage-7.8.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ed2144b8a78f9d94d9515963ed273d620e07846acd5d4b0a642d4849e8d91a0c", size = 238613, upload-time = "2025-03-30T20:35:05.889Z" }, - { url = "https://files.pythonhosted.org/packages/07/76/fbb2540495b01d996d38e9f8897b861afed356be01160ab4e25471f4fed1/coverage-7.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:042e7841a26498fff7a37d6fda770d17519982f5b7d8bf5278d140b67b61095f", size = 238909, upload-time = "2025-03-30T20:35:07.76Z" }, - { url = "https://files.pythonhosted.org/packages/a3/7e/76d604db640b7d4a86e5dd730b73e96e12a8185f22b5d0799025121f4dcb/coverage-7.8.0-cp310-cp310-win32.whl", hash = "sha256:f9983d01d7705b2d1f7a95e10bbe4091fabc03a46881a256c2787637b087003f", size = 213948, upload-time = "2025-03-30T20:35:09.144Z" }, - { url = "https://files.pythonhosted.org/packages/5c/a7/f8ce4aafb4a12ab475b56c76a71a40f427740cf496c14e943ade72e25023/coverage-7.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:5a570cd9bd20b85d1a0d7b009aaf6c110b52b5755c17be6962f8ccd65d1dbd23", size = 214844, upload-time = "2025-03-30T20:35:10.734Z" }, - { url = "https://files.pythonhosted.org/packages/2b/77/074d201adb8383addae5784cb8e2dac60bb62bfdf28b2b10f3a3af2fda47/coverage-7.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7ac22a0bb2c7c49f441f7a6d46c9c80d96e56f5a8bc6972529ed43c8b694e27", size = 211493, upload-time = "2025-03-30T20:35:12.286Z" }, - { url = "https://files.pythonhosted.org/packages/a9/89/7a8efe585750fe59b48d09f871f0e0c028a7b10722b2172dfe021fa2fdd4/coverage-7.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf13d564d310c156d1c8e53877baf2993fb3073b2fc9f69790ca6a732eb4bfea", size = 211921, upload-time = "2025-03-30T20:35:14.18Z" }, - { url = "https://files.pythonhosted.org/packages/e9/ef/96a90c31d08a3f40c49dbe897df4f1fd51fb6583821a1a1c5ee30cc8f680/coverage-7.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5761c70c017c1b0d21b0815a920ffb94a670c8d5d409d9b38857874c21f70d7", size = 244556, upload-time = "2025-03-30T20:35:15.616Z" }, - { url = "https://files.pythonhosted.org/packages/89/97/dcd5c2ce72cee9d7b0ee8c89162c24972fb987a111b92d1a3d1d19100c61/coverage-7.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ff52d790c7e1628241ffbcaeb33e07d14b007b6eb00a19320c7b8a7024c040", size = 242245, upload-time = "2025-03-30T20:35:18.648Z" }, - { url = "https://files.pythonhosted.org/packages/b2/7b/b63cbb44096141ed435843bbb251558c8e05cc835c8da31ca6ffb26d44c0/coverage-7.8.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d39fc4817fd67b3915256af5dda75fd4ee10621a3d484524487e33416c6f3543", size = 244032, upload-time = "2025-03-30T20:35:20.131Z" }, - { url = "https://files.pythonhosted.org/packages/97/e3/7fa8c2c00a1ef530c2a42fa5df25a6971391f92739d83d67a4ee6dcf7a02/coverage-7.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b44674870709017e4b4036e3d0d6c17f06a0e6d4436422e0ad29b882c40697d2", size = 243679, upload-time = "2025-03-30T20:35:21.636Z" }, - { url = "https://files.pythonhosted.org/packages/4f/b3/e0a59d8df9150c8a0c0841d55d6568f0a9195692136c44f3d21f1842c8f6/coverage-7.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8f99eb72bf27cbb167b636eb1726f590c00e1ad375002230607a844d9e9a2318", size = 241852, upload-time = "2025-03-30T20:35:23.525Z" }, - { url = "https://files.pythonhosted.org/packages/9b/82/db347ccd57bcef150c173df2ade97976a8367a3be7160e303e43dd0c795f/coverage-7.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b571bf5341ba8c6bc02e0baeaf3b061ab993bf372d982ae509807e7f112554e9", size = 242389, upload-time = "2025-03-30T20:35:25.09Z" }, - { url = "https://files.pythonhosted.org/packages/21/f6/3f7d7879ceb03923195d9ff294456241ed05815281f5254bc16ef71d6a20/coverage-7.8.0-cp311-cp311-win32.whl", hash = "sha256:e75a2ad7b647fd8046d58c3132d7eaf31b12d8a53c0e4b21fa9c4d23d6ee6d3c", size = 213997, upload-time = "2025-03-30T20:35:26.914Z" }, - { url = "https://files.pythonhosted.org/packages/28/87/021189643e18ecf045dbe1e2071b2747901f229df302de01c998eeadf146/coverage-7.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:3043ba1c88b2139126fc72cb48574b90e2e0546d4c78b5299317f61b7f718b78", size = 214911, upload-time = "2025-03-30T20:35:28.498Z" }, - { url = "https://files.pythonhosted.org/packages/aa/12/4792669473297f7973518bec373a955e267deb4339286f882439b8535b39/coverage-7.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bbb5cc845a0292e0c520656d19d7ce40e18d0e19b22cb3e0409135a575bf79fc", size = 211684, upload-time = "2025-03-30T20:35:29.959Z" }, - { url = "https://files.pythonhosted.org/packages/be/e1/2a4ec273894000ebedd789e8f2fc3813fcaf486074f87fd1c5b2cb1c0a2b/coverage-7.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4dfd9a93db9e78666d178d4f08a5408aa3f2474ad4d0e0378ed5f2ef71640cb6", size = 211935, upload-time = "2025-03-30T20:35:31.912Z" }, - { url = "https://files.pythonhosted.org/packages/f8/3a/7b14f6e4372786709a361729164125f6b7caf4024ce02e596c4a69bccb89/coverage-7.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f017a61399f13aa6d1039f75cd467be388d157cd81f1a119b9d9a68ba6f2830d", size = 245994, upload-time = "2025-03-30T20:35:33.455Z" }, - { url = "https://files.pythonhosted.org/packages/54/80/039cc7f1f81dcbd01ea796d36d3797e60c106077e31fd1f526b85337d6a1/coverage-7.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0915742f4c82208ebf47a2b154a5334155ed9ef9fe6190674b8a46c2fb89cb05", size = 242885, upload-time = "2025-03-30T20:35:35.354Z" }, - { url = "https://files.pythonhosted.org/packages/10/e0/dc8355f992b6cc2f9dcd5ef6242b62a3f73264893bc09fbb08bfcab18eb4/coverage-7.8.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a40fcf208e021eb14b0fac6bdb045c0e0cab53105f93ba0d03fd934c956143a", size = 245142, upload-time = "2025-03-30T20:35:37.121Z" }, - { url = "https://files.pythonhosted.org/packages/43/1b/33e313b22cf50f652becb94c6e7dae25d8f02e52e44db37a82de9ac357e8/coverage-7.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a1f406a8e0995d654b2ad87c62caf6befa767885301f3b8f6f73e6f3c31ec3a6", size = 244906, upload-time = "2025-03-30T20:35:39.07Z" }, - { url = "https://files.pythonhosted.org/packages/05/08/c0a8048e942e7f918764ccc99503e2bccffba1c42568693ce6955860365e/coverage-7.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:77af0f6447a582fdc7de5e06fa3757a3ef87769fbb0fdbdeba78c23049140a47", size = 243124, upload-time = "2025-03-30T20:35:40.598Z" }, - { url = "https://files.pythonhosted.org/packages/5b/62/ea625b30623083c2aad645c9a6288ad9fc83d570f9adb913a2abdba562dd/coverage-7.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f2d32f95922927186c6dbc8bc60df0d186b6edb828d299ab10898ef3f40052fe", size = 244317, upload-time = "2025-03-30T20:35:42.204Z" }, - { url = "https://files.pythonhosted.org/packages/62/cb/3871f13ee1130a6c8f020e2f71d9ed269e1e2124aa3374d2180ee451cee9/coverage-7.8.0-cp312-cp312-win32.whl", hash = "sha256:769773614e676f9d8e8a0980dd7740f09a6ea386d0f383db6821df07d0f08545", size = 214170, upload-time = "2025-03-30T20:35:44.216Z" }, - { url = "https://files.pythonhosted.org/packages/88/26/69fe1193ab0bfa1eb7a7c0149a066123611baba029ebb448500abd8143f9/coverage-7.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:e5d2b9be5b0693cf21eb4ce0ec8d211efb43966f6657807f6859aab3814f946b", size = 214969, upload-time = "2025-03-30T20:35:45.797Z" }, - { url = "https://files.pythonhosted.org/packages/f3/21/87e9b97b568e223f3438d93072479c2f36cc9b3f6b9f7094b9d50232acc0/coverage-7.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ac46d0c2dd5820ce93943a501ac5f6548ea81594777ca585bf002aa8854cacd", size = 211708, upload-time = "2025-03-30T20:35:47.417Z" }, - { url = "https://files.pythonhosted.org/packages/75/be/882d08b28a0d19c9c4c2e8a1c6ebe1f79c9c839eb46d4fca3bd3b34562b9/coverage-7.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:771eb7587a0563ca5bb6f622b9ed7f9d07bd08900f7589b4febff05f469bea00", size = 211981, upload-time = "2025-03-30T20:35:49.002Z" }, - { url = "https://files.pythonhosted.org/packages/7a/1d/ce99612ebd58082fbe3f8c66f6d8d5694976c76a0d474503fa70633ec77f/coverage-7.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42421e04069fb2cbcbca5a696c4050b84a43b05392679d4068acbe65449b5c64", size = 245495, upload-time = "2025-03-30T20:35:51.073Z" }, - { url = "https://files.pythonhosted.org/packages/dc/8d/6115abe97df98db6b2bd76aae395fcc941d039a7acd25f741312ced9a78f/coverage-7.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554fec1199d93ab30adaa751db68acec2b41c5602ac944bb19187cb9a41a8067", size = 242538, upload-time = "2025-03-30T20:35:52.941Z" }, - { url = "https://files.pythonhosted.org/packages/cb/74/2f8cc196643b15bc096d60e073691dadb3dca48418f08bc78dd6e899383e/coverage-7.8.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aaeb00761f985007b38cf463b1d160a14a22c34eb3f6a39d9ad6fc27cb73008", size = 244561, upload-time = "2025-03-30T20:35:54.658Z" }, - { url = "https://files.pythonhosted.org/packages/22/70/c10c77cd77970ac965734fe3419f2c98665f6e982744a9bfb0e749d298f4/coverage-7.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:581a40c7b94921fffd6457ffe532259813fc68eb2bdda60fa8cc343414ce3733", size = 244633, upload-time = "2025-03-30T20:35:56.221Z" }, - { url = "https://files.pythonhosted.org/packages/38/5a/4f7569d946a07c952688debee18c2bb9ab24f88027e3d71fd25dbc2f9dca/coverage-7.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f319bae0321bc838e205bf9e5bc28f0a3165f30c203b610f17ab5552cff90323", size = 242712, upload-time = "2025-03-30T20:35:57.801Z" }, - { url = "https://files.pythonhosted.org/packages/bb/a1/03a43b33f50475a632a91ea8c127f7e35e53786dbe6781c25f19fd5a65f8/coverage-7.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04bfec25a8ef1c5f41f5e7e5c842f6b615599ca8ba8391ec33a9290d9d2db3a3", size = 244000, upload-time = "2025-03-30T20:35:59.378Z" }, - { url = "https://files.pythonhosted.org/packages/6a/89/ab6c43b1788a3128e4d1b7b54214548dcad75a621f9d277b14d16a80d8a1/coverage-7.8.0-cp313-cp313-win32.whl", hash = "sha256:dd19608788b50eed889e13a5d71d832edc34fc9dfce606f66e8f9f917eef910d", size = 214195, upload-time = "2025-03-30T20:36:01.005Z" }, - { url = "https://files.pythonhosted.org/packages/12/12/6bf5f9a8b063d116bac536a7fb594fc35cb04981654cccb4bbfea5dcdfa0/coverage-7.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:a9abbccd778d98e9c7e85038e35e91e67f5b520776781d9a1e2ee9d400869487", size = 214998, upload-time = "2025-03-30T20:36:03.006Z" }, - { url = "https://files.pythonhosted.org/packages/2a/e6/1e9df74ef7a1c983a9c7443dac8aac37a46f1939ae3499424622e72a6f78/coverage-7.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:18c5ae6d061ad5b3e7eef4363fb27a0576012a7447af48be6c75b88494c6cf25", size = 212541, upload-time = "2025-03-30T20:36:04.638Z" }, - { url = "https://files.pythonhosted.org/packages/04/51/c32174edb7ee49744e2e81c4b1414ac9df3dacfcb5b5f273b7f285ad43f6/coverage-7.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95aa6ae391a22bbbce1b77ddac846c98c5473de0372ba5c463480043a07bff42", size = 212767, upload-time = "2025-03-30T20:36:06.503Z" }, - { url = "https://files.pythonhosted.org/packages/e9/8f/f454cbdb5212f13f29d4a7983db69169f1937e869a5142bce983ded52162/coverage-7.8.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e013b07ba1c748dacc2a80e69a46286ff145935f260eb8c72df7185bf048f502", size = 256997, upload-time = "2025-03-30T20:36:08.137Z" }, - { url = "https://files.pythonhosted.org/packages/e6/74/2bf9e78b321216d6ee90a81e5c22f912fc428442c830c4077b4a071db66f/coverage-7.8.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d766a4f0e5aa1ba056ec3496243150698dc0481902e2b8559314368717be82b1", size = 252708, upload-time = "2025-03-30T20:36:09.781Z" }, - { url = "https://files.pythonhosted.org/packages/92/4d/50d7eb1e9a6062bee6e2f92e78b0998848a972e9afad349b6cdde6fa9e32/coverage-7.8.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad80e6b4a0c3cb6f10f29ae4c60e991f424e6b14219d46f1e7d442b938ee68a4", size = 255046, upload-time = "2025-03-30T20:36:11.409Z" }, - { url = "https://files.pythonhosted.org/packages/40/9e/71fb4e7402a07c4198ab44fc564d09d7d0ffca46a9fb7b0a7b929e7641bd/coverage-7.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b87eb6fc9e1bb8f98892a2458781348fa37e6925f35bb6ceb9d4afd54ba36c73", size = 256139, upload-time = "2025-03-30T20:36:13.86Z" }, - { url = "https://files.pythonhosted.org/packages/49/1a/78d37f7a42b5beff027e807c2843185961fdae7fe23aad5a4837c93f9d25/coverage-7.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d1ba00ae33be84066cfbe7361d4e04dec78445b2b88bdb734d0d1cbab916025a", size = 254307, upload-time = "2025-03-30T20:36:16.074Z" }, - { url = "https://files.pythonhosted.org/packages/58/e9/8fb8e0ff6bef5e170ee19d59ca694f9001b2ec085dc99b4f65c128bb3f9a/coverage-7.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f3c38e4e5ccbdc9198aecc766cedbb134b2d89bf64533973678dfcf07effd883", size = 255116, upload-time = "2025-03-30T20:36:18.033Z" }, - { url = "https://files.pythonhosted.org/packages/56/b0/d968ecdbe6fe0a863de7169bbe9e8a476868959f3af24981f6a10d2b6924/coverage-7.8.0-cp313-cp313t-win32.whl", hash = "sha256:379fe315e206b14e21db5240f89dc0774bdd3e25c3c58c2c733c99eca96f1ada", size = 214909, upload-time = "2025-03-30T20:36:19.644Z" }, - { url = "https://files.pythonhosted.org/packages/87/e9/d6b7ef9fecf42dfb418d93544af47c940aa83056c49e6021a564aafbc91f/coverage-7.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e4b6b87bb0c846a9315e3ab4be2d52fac905100565f4b92f02c445c8799e257", size = 216068, upload-time = "2025-03-30T20:36:21.282Z" }, - { url = "https://files.pythonhosted.org/packages/c4/f1/1da77bb4c920aa30e82fa9b6ea065da3467977c2e5e032e38e66f1c57ffd/coverage-7.8.0-pp39.pp310.pp311-none-any.whl", hash = "sha256:b8194fb8e50d556d5849753de991d390c5a1edeeba50f68e3a9253fbd8bf8ccd", size = 203443, upload-time = "2025-03-30T20:36:41.959Z" }, - { url = "https://files.pythonhosted.org/packages/59/f1/4da7717f0063a222db253e7121bd6a56f6fb1ba439dcc36659088793347c/coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7", size = 203435, upload-time = "2025-03-30T20:36:43.61Z" }, + { url = "https://files.pythonhosted.org/packages/26/4a/0dc3de1c172d35abe512332cfdcc43211b6ebce629e4cc42e6cd25ed8f4d/coverage-7.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:32b75c2ba3f324ee37af3ccee5b30458038c50b349ad9b88cee85096132a575b", size = 217409, upload-time = "2025-11-18T13:31:53.122Z" }, + { url = "https://files.pythonhosted.org/packages/01/c3/086198b98db0109ad4f84241e8e9ea7e5fb2db8c8ffb787162d40c26cc76/coverage-7.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cb2a1b6ab9fe833714a483a915de350abc624a37149649297624c8d57add089c", size = 217927, upload-time = "2025-11-18T13:31:54.458Z" }, + { url = "https://files.pythonhosted.org/packages/5d/5f/34614dbf5ce0420828fc6c6f915126a0fcb01e25d16cf141bf5361e6aea6/coverage-7.12.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5734b5d913c3755e72f70bf6cc37a0518d4f4745cde760c5d8e12005e62f9832", size = 244678, upload-time = "2025-11-18T13:31:55.805Z" }, + { url = "https://files.pythonhosted.org/packages/55/7b/6b26fb32e8e4a6989ac1d40c4e132b14556131493b1d06bc0f2be169c357/coverage-7.12.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b527a08cdf15753279b7afb2339a12073620b761d79b81cbe2cdebdb43d90daa", size = 246507, upload-time = "2025-11-18T13:31:57.05Z" }, + { url = "https://files.pythonhosted.org/packages/06/42/7d70e6603d3260199b90fb48b537ca29ac183d524a65cc31366b2e905fad/coverage-7.12.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9bb44c889fb68004e94cab71f6a021ec83eac9aeabdbb5a5a88821ec46e1da73", size = 248366, upload-time = "2025-11-18T13:31:58.362Z" }, + { url = "https://files.pythonhosted.org/packages/2d/4a/d86b837923878424c72458c5b25e899a3c5ca73e663082a915f5b3c4d749/coverage-7.12.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4b59b501455535e2e5dde5881739897967b272ba25988c89145c12d772810ccb", size = 245366, upload-time = "2025-11-18T13:31:59.572Z" }, + { url = "https://files.pythonhosted.org/packages/e6/c2/2adec557e0aa9721875f06ced19730fdb7fc58e31b02b5aa56f2ebe4944d/coverage-7.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d8842f17095b9868a05837b7b1b73495293091bed870e099521ada176aa3e00e", size = 246408, upload-time = "2025-11-18T13:32:00.784Z" }, + { url = "https://files.pythonhosted.org/packages/5a/4b/8bd1f1148260df11c618e535fdccd1e5aaf646e55b50759006a4f41d8a26/coverage-7.12.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c5a6f20bf48b8866095c6820641e7ffbe23f2ac84a2efc218d91235e404c7777", size = 244416, upload-time = "2025-11-18T13:32:01.963Z" }, + { url = "https://files.pythonhosted.org/packages/0e/13/3a248dd6a83df90414c54a4e121fd081fb20602ca43955fbe1d60e2312a9/coverage-7.12.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:5f3738279524e988d9da2893f307c2093815c623f8d05a8f79e3eff3a7a9e553", size = 244681, upload-time = "2025-11-18T13:32:03.408Z" }, + { url = "https://files.pythonhosted.org/packages/76/30/aa833827465a5e8c938935f5d91ba055f70516941078a703740aaf1aa41f/coverage-7.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0d68c1f7eabbc8abe582d11fa393ea483caf4f44b0af86881174769f185c94d", size = 245300, upload-time = "2025-11-18T13:32:04.686Z" }, + { url = "https://files.pythonhosted.org/packages/38/24/f85b3843af1370fb3739fa7571819b71243daa311289b31214fe3e8c9d68/coverage-7.12.0-cp310-cp310-win32.whl", hash = "sha256:7670d860e18b1e3ee5930b17a7d55ae6287ec6e55d9799982aa103a2cc1fa2ef", size = 220008, upload-time = "2025-11-18T13:32:05.806Z" }, + { url = "https://files.pythonhosted.org/packages/3a/a2/c7da5b9566f7164db9eefa133d17761ecb2c2fde9385d754e5b5c80f710d/coverage-7.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:f999813dddeb2a56aab5841e687b68169da0d3f6fc78ccf50952fa2463746022", size = 220943, upload-time = "2025-11-18T13:32:07.166Z" }, + { url = "https://files.pythonhosted.org/packages/5a/0c/0dfe7f0487477d96432e4815537263363fb6dd7289743a796e8e51eabdf2/coverage-7.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa124a3683d2af98bd9d9c2bfa7a5076ca7e5ab09fdb96b81fa7d89376ae928f", size = 217535, upload-time = "2025-11-18T13:32:08.812Z" }, + { url = "https://files.pythonhosted.org/packages/9b/f5/f9a4a053a5bbff023d3bec259faac8f11a1e5a6479c2ccf586f910d8dac7/coverage-7.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d93fbf446c31c0140208dcd07c5d882029832e8ed7891a39d6d44bd65f2316c3", size = 218044, upload-time = "2025-11-18T13:32:10.329Z" }, + { url = "https://files.pythonhosted.org/packages/95/c5/84fc3697c1fa10cd8571919bf9693f693b7373278daaf3b73e328d502bc8/coverage-7.12.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:52ca620260bd8cd6027317bdd8b8ba929be1d741764ee765b42c4d79a408601e", size = 248440, upload-time = "2025-11-18T13:32:12.536Z" }, + { url = "https://files.pythonhosted.org/packages/f4/36/2d93fbf6a04670f3874aed397d5a5371948a076e3249244a9e84fb0e02d6/coverage-7.12.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f3433ffd541380f3a0e423cff0f4926d55b0cc8c1d160fdc3be24a4c03aa65f7", size = 250361, upload-time = "2025-11-18T13:32:13.852Z" }, + { url = "https://files.pythonhosted.org/packages/5d/49/66dc65cc456a6bfc41ea3d0758c4afeaa4068a2b2931bf83be6894cf1058/coverage-7.12.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7bbb321d4adc9f65e402c677cd1c8e4c2d0105d3ce285b51b4d87f1d5db5245", size = 252472, upload-time = "2025-11-18T13:32:15.068Z" }, + { url = "https://files.pythonhosted.org/packages/35/1f/ebb8a18dffd406db9fcd4b3ae42254aedcaf612470e8712f12041325930f/coverage-7.12.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:22a7aade354a72dff3b59c577bfd18d6945c61f97393bc5fb7bd293a4237024b", size = 248592, upload-time = "2025-11-18T13:32:16.328Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/67f213c06e5ea3b3d4980df7dc344d7fea88240b5fe878a5dcbdfe0e2315/coverage-7.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3ff651dcd36d2fea66877cd4a82de478004c59b849945446acb5baf9379a1b64", size = 250167, upload-time = "2025-11-18T13:32:17.687Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/e52aef68154164ea40cc8389c120c314c747fe63a04b013a5782e989b77f/coverage-7.12.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:31b8b2e38391a56e3cea39d22a23faaa7c3fc911751756ef6d2621d2a9daf742", size = 248238, upload-time = "2025-11-18T13:32:19.2Z" }, + { url = "https://files.pythonhosted.org/packages/1f/a4/4d88750bcf9d6d66f77865e5a05a20e14db44074c25fd22519777cb69025/coverage-7.12.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:297bc2da28440f5ae51c845a47c8175a4db0553a53827886e4fb25c66633000c", size = 247964, upload-time = "2025-11-18T13:32:21.027Z" }, + { url = "https://files.pythonhosted.org/packages/a7/6b/b74693158899d5b47b0bf6238d2c6722e20ba749f86b74454fac0696bb00/coverage-7.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6ff7651cc01a246908eac162a6a86fc0dbab6de1ad165dfb9a1e2ec660b44984", size = 248862, upload-time = "2025-11-18T13:32:22.304Z" }, + { url = "https://files.pythonhosted.org/packages/18/de/6af6730227ce0e8ade307b1cc4a08e7f51b419a78d02083a86c04ccceb29/coverage-7.12.0-cp311-cp311-win32.whl", hash = "sha256:313672140638b6ddb2c6455ddeda41c6a0b208298034544cfca138978c6baed6", size = 220033, upload-time = "2025-11-18T13:32:23.714Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a1/e7f63021a7c4fe20994359fcdeae43cbef4a4d0ca36a5a1639feeea5d9e1/coverage-7.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a1783ed5bd0d5938d4435014626568dc7f93e3cb99bc59188cc18857c47aa3c4", size = 220966, upload-time = "2025-11-18T13:32:25.599Z" }, + { url = "https://files.pythonhosted.org/packages/77/e8/deae26453f37c20c3aa0c4433a1e32cdc169bf415cce223a693117aa3ddd/coverage-7.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:4648158fd8dd9381b5847622df1c90ff314efbfc1df4550092ab6013c238a5fc", size = 219637, upload-time = "2025-11-18T13:32:27.265Z" }, + { url = "https://files.pythonhosted.org/packages/02/bf/638c0427c0f0d47638242e2438127f3c8ee3cfc06c7fdeb16778ed47f836/coverage-7.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:29644c928772c78512b48e14156b81255000dcfd4817574ff69def189bcb3647", size = 217704, upload-time = "2025-11-18T13:32:28.906Z" }, + { url = "https://files.pythonhosted.org/packages/08/e1/706fae6692a66c2d6b871a608bbde0da6281903fa0e9f53a39ed441da36a/coverage-7.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8638cbb002eaa5d7c8d04da667813ce1067080b9a91099801a0053086e52b736", size = 218064, upload-time = "2025-11-18T13:32:30.161Z" }, + { url = "https://files.pythonhosted.org/packages/a9/8b/eb0231d0540f8af3ffda39720ff43cb91926489d01524e68f60e961366e4/coverage-7.12.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:083631eeff5eb9992c923e14b810a179798bb598e6a0dd60586819fc23be6e60", size = 249560, upload-time = "2025-11-18T13:32:31.835Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a1/67fb52af642e974d159b5b379e4d4c59d0ebe1288677fbd04bbffe665a82/coverage-7.12.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:99d5415c73ca12d558e07776bd957c4222c687b9f1d26fa0e1b57e3598bdcde8", size = 252318, upload-time = "2025-11-18T13:32:33.178Z" }, + { url = "https://files.pythonhosted.org/packages/41/e5/38228f31b2c7665ebf9bdfdddd7a184d56450755c7e43ac721c11a4b8dab/coverage-7.12.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e949ebf60c717c3df63adb4a1a366c096c8d7fd8472608cd09359e1bd48ef59f", size = 253403, upload-time = "2025-11-18T13:32:34.45Z" }, + { url = "https://files.pythonhosted.org/packages/ec/4b/df78e4c8188f9960684267c5a4897836f3f0f20a20c51606ee778a1d9749/coverage-7.12.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6d907ddccbca819afa2cd014bc69983b146cca2735a0b1e6259b2a6c10be1e70", size = 249984, upload-time = "2025-11-18T13:32:35.747Z" }, + { url = "https://files.pythonhosted.org/packages/ba/51/bb163933d195a345c6f63eab9e55743413d064c291b6220df754075c2769/coverage-7.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b1518ecbad4e6173f4c6e6c4a46e49555ea5679bf3feda5edb1b935c7c44e8a0", size = 251339, upload-time = "2025-11-18T13:32:37.352Z" }, + { url = "https://files.pythonhosted.org/packages/15/40/c9b29cdb8412c837cdcbc2cfa054547dd83affe6cbbd4ce4fdb92b6ba7d1/coverage-7.12.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:51777647a749abdf6f6fd8c7cffab12de68ab93aab15efc72fbbb83036c2a068", size = 249489, upload-time = "2025-11-18T13:32:39.212Z" }, + { url = "https://files.pythonhosted.org/packages/c8/da/b3131e20ba07a0de4437a50ef3b47840dfabf9293675b0cd5c2c7f66dd61/coverage-7.12.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:42435d46d6461a3b305cdfcad7cdd3248787771f53fe18305548cba474e6523b", size = 249070, upload-time = "2025-11-18T13:32:40.598Z" }, + { url = "https://files.pythonhosted.org/packages/70/81/b653329b5f6302c08d683ceff6785bc60a34be9ae92a5c7b63ee7ee7acec/coverage-7.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5bcead88c8423e1855e64b8057d0544e33e4080b95b240c2a355334bb7ced937", size = 250929, upload-time = "2025-11-18T13:32:42.915Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/250ac3bca9f252a5fb1338b5ad01331ebb7b40223f72bef5b1b2cb03aa64/coverage-7.12.0-cp312-cp312-win32.whl", hash = "sha256:dcbb630ab034e86d2a0f79aefd2be07e583202f41e037602d438c80044957baa", size = 220241, upload-time = "2025-11-18T13:32:44.665Z" }, + { url = "https://files.pythonhosted.org/packages/64/1c/77e79e76d37ce83302f6c21980b45e09f8aa4551965213a10e62d71ce0ab/coverage-7.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:2fd8354ed5d69775ac42986a691fbf68b4084278710cee9d7c3eaa0c28fa982a", size = 221051, upload-time = "2025-11-18T13:32:46.008Z" }, + { url = "https://files.pythonhosted.org/packages/31/f5/641b8a25baae564f9e52cac0e2667b123de961985709a004e287ee7663cc/coverage-7.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:737c3814903be30695b2de20d22bcc5428fdae305c61ba44cdc8b3252984c49c", size = 219692, upload-time = "2025-11-18T13:32:47.372Z" }, + { url = "https://files.pythonhosted.org/packages/b8/14/771700b4048774e48d2c54ed0c674273702713c9ee7acdfede40c2666747/coverage-7.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:47324fffca8d8eae7e185b5bb20c14645f23350f870c1649003618ea91a78941", size = 217725, upload-time = "2025-11-18T13:32:49.22Z" }, + { url = "https://files.pythonhosted.org/packages/17/a7/3aa4144d3bcb719bf67b22d2d51c2d577bf801498c13cb08f64173e80497/coverage-7.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ccf3b2ede91decd2fb53ec73c1f949c3e034129d1e0b07798ff1d02ea0c8fa4a", size = 218098, upload-time = "2025-11-18T13:32:50.78Z" }, + { url = "https://files.pythonhosted.org/packages/fc/9c/b846bbc774ff81091a12a10203e70562c91ae71badda00c5ae5b613527b1/coverage-7.12.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b365adc70a6936c6b0582dc38746b33b2454148c02349345412c6e743efb646d", size = 249093, upload-time = "2025-11-18T13:32:52.554Z" }, + { url = "https://files.pythonhosted.org/packages/76/b6/67d7c0e1f400b32c883e9342de4a8c2ae7c1a0b57c5de87622b7262e2309/coverage-7.12.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bc13baf85cd8a4cfcf4a35c7bc9d795837ad809775f782f697bf630b7e200211", size = 251686, upload-time = "2025-11-18T13:32:54.862Z" }, + { url = "https://files.pythonhosted.org/packages/cc/75/b095bd4b39d49c3be4bffbb3135fea18a99a431c52dd7513637c0762fecb/coverage-7.12.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:099d11698385d572ceafb3288a5b80fe1fc58bf665b3f9d362389de488361d3d", size = 252930, upload-time = "2025-11-18T13:32:56.417Z" }, + { url = "https://files.pythonhosted.org/packages/6e/f3/466f63015c7c80550bead3093aacabf5380c1220a2a93c35d374cae8f762/coverage-7.12.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:473dc45d69694069adb7680c405fb1e81f60b2aff42c81e2f2c3feaf544d878c", size = 249296, upload-time = "2025-11-18T13:32:58.074Z" }, + { url = "https://files.pythonhosted.org/packages/27/86/eba2209bf2b7e28c68698fc13437519a295b2d228ba9e0ec91673e09fa92/coverage-7.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:583f9adbefd278e9de33c33d6846aa8f5d164fa49b47144180a0e037f0688bb9", size = 251068, upload-time = "2025-11-18T13:32:59.646Z" }, + { url = "https://files.pythonhosted.org/packages/ec/55/ca8ae7dbba962a3351f18940b359b94c6bafdd7757945fdc79ec9e452dc7/coverage-7.12.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b2089cc445f2dc0af6f801f0d1355c025b76c24481935303cf1af28f636688f0", size = 249034, upload-time = "2025-11-18T13:33:01.481Z" }, + { url = "https://files.pythonhosted.org/packages/7a/d7/39136149325cad92d420b023b5fd900dabdd1c3a0d1d5f148ef4a8cedef5/coverage-7.12.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:950411f1eb5d579999c5f66c62a40961f126fc71e5e14419f004471957b51508", size = 248853, upload-time = "2025-11-18T13:33:02.935Z" }, + { url = "https://files.pythonhosted.org/packages/fe/b6/76e1add8b87ef60e00643b0b7f8f7bb73d4bf5249a3be19ebefc5793dd25/coverage-7.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b1aab7302a87bafebfe76b12af681b56ff446dc6f32ed178ff9c092ca776e6bc", size = 250619, upload-time = "2025-11-18T13:33:04.336Z" }, + { url = "https://files.pythonhosted.org/packages/95/87/924c6dc64f9203f7a3c1832a6a0eee5a8335dbe5f1bdadcc278d6f1b4d74/coverage-7.12.0-cp313-cp313-win32.whl", hash = "sha256:d7e0d0303c13b54db495eb636bc2465b2fb8475d4c8bcec8fe4b5ca454dfbae8", size = 220261, upload-time = "2025-11-18T13:33:06.493Z" }, + { url = "https://files.pythonhosted.org/packages/91/77/dd4aff9af16ff776bf355a24d87eeb48fc6acde54c907cc1ea89b14a8804/coverage-7.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:ce61969812d6a98a981d147d9ac583a36ac7db7766f2e64a9d4d059c2fe29d07", size = 221072, upload-time = "2025-11-18T13:33:07.926Z" }, + { url = "https://files.pythonhosted.org/packages/70/49/5c9dc46205fef31b1b226a6e16513193715290584317fd4df91cdaf28b22/coverage-7.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:bcec6f47e4cb8a4c2dc91ce507f6eefc6a1b10f58df32cdc61dff65455031dfc", size = 219702, upload-time = "2025-11-18T13:33:09.631Z" }, + { url = "https://files.pythonhosted.org/packages/9b/62/f87922641c7198667994dd472a91e1d9b829c95d6c29529ceb52132436ad/coverage-7.12.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:459443346509476170d553035e4a3eed7b860f4fe5242f02de1010501956ce87", size = 218420, upload-time = "2025-11-18T13:33:11.153Z" }, + { url = "https://files.pythonhosted.org/packages/85/dd/1cc13b2395ef15dbb27d7370a2509b4aee77890a464fb35d72d428f84871/coverage-7.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:04a79245ab2b7a61688958f7a855275997134bc84f4a03bc240cf64ff132abf6", size = 218773, upload-time = "2025-11-18T13:33:12.569Z" }, + { url = "https://files.pythonhosted.org/packages/74/40/35773cc4bb1e9d4658d4fb669eb4195b3151bef3bbd6f866aba5cd5dac82/coverage-7.12.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:09a86acaaa8455f13d6a99221d9654df249b33937b4e212b4e5a822065f12aa7", size = 260078, upload-time = "2025-11-18T13:33:14.037Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ee/231bb1a6ffc2905e396557585ebc6bdc559e7c66708376d245a1f1d330fc/coverage-7.12.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:907e0df1b71ba77463687a74149c6122c3f6aac56c2510a5d906b2f368208560", size = 262144, upload-time = "2025-11-18T13:33:15.601Z" }, + { url = "https://files.pythonhosted.org/packages/28/be/32f4aa9f3bf0b56f3971001b56508352c7753915345d45fab4296a986f01/coverage-7.12.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b57e2d0ddd5f0582bae5437c04ee71c46cd908e7bc5d4d0391f9a41e812dd12", size = 264574, upload-time = "2025-11-18T13:33:17.354Z" }, + { url = "https://files.pythonhosted.org/packages/68/7c/00489fcbc2245d13ab12189b977e0cf06ff3351cb98bc6beba8bd68c5902/coverage-7.12.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:58c1c6aa677f3a1411fe6fb28ec3a942e4f665df036a3608816e0847fad23296", size = 259298, upload-time = "2025-11-18T13:33:18.958Z" }, + { url = "https://files.pythonhosted.org/packages/96/b4/f0760d65d56c3bea95b449e02570d4abd2549dc784bf39a2d4721a2d8ceb/coverage-7.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4c589361263ab2953e3c4cd2a94db94c4ad4a8e572776ecfbad2389c626e4507", size = 262150, upload-time = "2025-11-18T13:33:20.644Z" }, + { url = "https://files.pythonhosted.org/packages/c5/71/9a9314df00f9326d78c1e5a910f520d599205907432d90d1c1b7a97aa4b1/coverage-7.12.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:91b810a163ccad2e43b1faa11d70d3cf4b6f3d83f9fd5f2df82a32d47b648e0d", size = 259763, upload-time = "2025-11-18T13:33:22.189Z" }, + { url = "https://files.pythonhosted.org/packages/10/34/01a0aceed13fbdf925876b9a15d50862eb8845454301fe3cdd1df08b2182/coverage-7.12.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:40c867af715f22592e0d0fb533a33a71ec9e0f73a6945f722a0c85c8c1cbe3a2", size = 258653, upload-time = "2025-11-18T13:33:24.239Z" }, + { url = "https://files.pythonhosted.org/packages/8d/04/81d8fd64928acf1574bbb0181f66901c6c1c6279c8ccf5f84259d2c68ae9/coverage-7.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:68b0d0a2d84f333de875666259dadf28cc67858bc8fd8b3f1eae84d3c2bec455", size = 260856, upload-time = "2025-11-18T13:33:26.365Z" }, + { url = "https://files.pythonhosted.org/packages/f2/76/fa2a37bfaeaf1f766a2d2360a25a5297d4fb567098112f6517475eee120b/coverage-7.12.0-cp313-cp313t-win32.whl", hash = "sha256:73f9e7fbd51a221818fd11b7090eaa835a353ddd59c236c57b2199486b116c6d", size = 220936, upload-time = "2025-11-18T13:33:28.165Z" }, + { url = "https://files.pythonhosted.org/packages/f9/52/60f64d932d555102611c366afb0eb434b34266b1d9266fc2fe18ab641c47/coverage-7.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:24cff9d1f5743f67db7ba46ff284018a6e9aeb649b67aa1e70c396aa1b7cb23c", size = 222001, upload-time = "2025-11-18T13:33:29.656Z" }, + { url = "https://files.pythonhosted.org/packages/77/df/c303164154a5a3aea7472bf323b7c857fed93b26618ed9fc5c2955566bb0/coverage-7.12.0-cp313-cp313t-win_arm64.whl", hash = "sha256:c87395744f5c77c866d0f5a43d97cc39e17c7f1cb0115e54a2fe67ca75c5d14d", size = 220273, upload-time = "2025-11-18T13:33:31.415Z" }, + { url = "https://files.pythonhosted.org/packages/bf/2e/fc12db0883478d6e12bbd62d481210f0c8daf036102aa11434a0c5755825/coverage-7.12.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a1c59b7dc169809a88b21a936eccf71c3895a78f5592051b1af8f4d59c2b4f92", size = 217777, upload-time = "2025-11-18T13:33:32.86Z" }, + { url = "https://files.pythonhosted.org/packages/1f/c1/ce3e525d223350c6ec16b9be8a057623f54226ef7f4c2fee361ebb6a02b8/coverage-7.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8787b0f982e020adb732b9f051f3e49dd5054cebbc3f3432061278512a2b1360", size = 218100, upload-time = "2025-11-18T13:33:34.532Z" }, + { url = "https://files.pythonhosted.org/packages/15/87/113757441504aee3808cb422990ed7c8bcc2d53a6779c66c5adef0942939/coverage-7.12.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5ea5a9f7dc8877455b13dd1effd3202e0bca72f6f3ab09f9036b1bcf728f69ac", size = 249151, upload-time = "2025-11-18T13:33:36.135Z" }, + { url = "https://files.pythonhosted.org/packages/d9/1d/9529d9bd44049b6b05bb319c03a3a7e4b0a8a802d28fa348ad407e10706d/coverage-7.12.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fdba9f15849534594f60b47c9a30bc70409b54947319a7c4fd0e8e3d8d2f355d", size = 251667, upload-time = "2025-11-18T13:33:37.996Z" }, + { url = "https://files.pythonhosted.org/packages/11/bb/567e751c41e9c03dc29d3ce74b8c89a1e3396313e34f255a2a2e8b9ebb56/coverage-7.12.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a00594770eb715854fb1c57e0dea08cce6720cfbc531accdb9850d7c7770396c", size = 253003, upload-time = "2025-11-18T13:33:39.553Z" }, + { url = "https://files.pythonhosted.org/packages/e4/b3/c2cce2d8526a02fb9e9ca14a263ca6fc074449b33a6afa4892838c903528/coverage-7.12.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5560c7e0d82b42eb1951e4f68f071f8017c824ebfd5a6ebe42c60ac16c6c2434", size = 249185, upload-time = "2025-11-18T13:33:42.086Z" }, + { url = "https://files.pythonhosted.org/packages/0e/a7/967f93bb66e82c9113c66a8d0b65ecf72fc865adfba5a145f50c7af7e58d/coverage-7.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d6c2e26b481c9159c2773a37947a9718cfdc58893029cdfb177531793e375cfc", size = 251025, upload-time = "2025-11-18T13:33:43.634Z" }, + { url = "https://files.pythonhosted.org/packages/b9/b2/f2f6f56337bc1af465d5b2dc1ee7ee2141b8b9272f3bf6213fcbc309a836/coverage-7.12.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:6e1a8c066dabcde56d5d9fed6a66bc19a2883a3fe051f0c397a41fc42aedd4cc", size = 248979, upload-time = "2025-11-18T13:33:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7a/bf4209f45a4aec09d10a01a57313a46c0e0e8f4c55ff2965467d41a92036/coverage-7.12.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f7ba9da4726e446d8dd8aae5a6cd872511184a5d861de80a86ef970b5dacce3e", size = 248800, upload-time = "2025-11-18T13:33:47.546Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b7/1e01b8696fb0521810f60c5bbebf699100d6754183e6cc0679bf2ed76531/coverage-7.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e0f483ab4f749039894abaf80c2f9e7ed77bbf3c737517fb88c8e8e305896a17", size = 250460, upload-time = "2025-11-18T13:33:49.537Z" }, + { url = "https://files.pythonhosted.org/packages/71/ae/84324fb9cb46c024760e706353d9b771a81b398d117d8c1fe010391c186f/coverage-7.12.0-cp314-cp314-win32.whl", hash = "sha256:76336c19a9ef4a94b2f8dc79f8ac2da3f193f625bb5d6f51a328cd19bfc19933", size = 220533, upload-time = "2025-11-18T13:33:51.16Z" }, + { url = "https://files.pythonhosted.org/packages/e2/71/1033629deb8460a8f97f83e6ac4ca3b93952e2b6f826056684df8275e015/coverage-7.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:7c1059b600aec6ef090721f8f633f60ed70afaffe8ecab85b59df748f24b31fe", size = 221348, upload-time = "2025-11-18T13:33:52.776Z" }, + { url = "https://files.pythonhosted.org/packages/0a/5f/ac8107a902f623b0c251abdb749be282dc2ab61854a8a4fcf49e276fce2f/coverage-7.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:172cf3a34bfef42611963e2b661302a8931f44df31629e5b1050567d6b90287d", size = 219922, upload-time = "2025-11-18T13:33:54.316Z" }, + { url = "https://files.pythonhosted.org/packages/79/6e/f27af2d4da367f16077d21ef6fe796c874408219fa6dd3f3efe7751bd910/coverage-7.12.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:aa7d48520a32cb21c7a9b31f81799e8eaec7239db36c3b670be0fa2403828d1d", size = 218511, upload-time = "2025-11-18T13:33:56.343Z" }, + { url = "https://files.pythonhosted.org/packages/67/dd/65fd874aa460c30da78f9d259400d8e6a4ef457d61ab052fd248f0050558/coverage-7.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:90d58ac63bc85e0fb919f14d09d6caa63f35a5512a2205284b7816cafd21bb03", size = 218771, upload-time = "2025-11-18T13:33:57.966Z" }, + { url = "https://files.pythonhosted.org/packages/55/e0/7c6b71d327d8068cb79c05f8f45bf1b6145f7a0de23bbebe63578fe5240a/coverage-7.12.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ca8ecfa283764fdda3eae1bdb6afe58bf78c2c3ec2b2edcb05a671f0bba7b3f9", size = 260151, upload-time = "2025-11-18T13:33:59.597Z" }, + { url = "https://files.pythonhosted.org/packages/49/ce/4697457d58285b7200de6b46d606ea71066c6e674571a946a6ea908fb588/coverage-7.12.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:874fe69a0785d96bd066059cd4368022cebbec1a8958f224f0016979183916e6", size = 262257, upload-time = "2025-11-18T13:34:01.166Z" }, + { url = "https://files.pythonhosted.org/packages/2f/33/acbc6e447aee4ceba88c15528dbe04a35fb4d67b59d393d2e0d6f1e242c1/coverage-7.12.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5b3c889c0b8b283a24d721a9eabc8ccafcfc3aebf167e4cd0d0e23bf8ec4e339", size = 264671, upload-time = "2025-11-18T13:34:02.795Z" }, + { url = "https://files.pythonhosted.org/packages/87/ec/e2822a795c1ed44d569980097be839c5e734d4c0c1119ef8e0a073496a30/coverage-7.12.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8bb5b894b3ec09dcd6d3743229dc7f2c42ef7787dc40596ae04c0edda487371e", size = 259231, upload-time = "2025-11-18T13:34:04.397Z" }, + { url = "https://files.pythonhosted.org/packages/72/c5/a7ec5395bb4a49c9b7ad97e63f0c92f6bf4a9e006b1393555a02dae75f16/coverage-7.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:79a44421cd5fba96aa57b5e3b5a4d3274c449d4c622e8f76882d76635501fd13", size = 262137, upload-time = "2025-11-18T13:34:06.068Z" }, + { url = "https://files.pythonhosted.org/packages/67/0c/02c08858b764129f4ecb8e316684272972e60777ae986f3865b10940bdd6/coverage-7.12.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:33baadc0efd5c7294f436a632566ccc1f72c867f82833eb59820ee37dc811c6f", size = 259745, upload-time = "2025-11-18T13:34:08.04Z" }, + { url = "https://files.pythonhosted.org/packages/5a/04/4fd32b7084505f3829a8fe45c1a74a7a728cb251aaadbe3bec04abcef06d/coverage-7.12.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c406a71f544800ef7e9e0000af706b88465f3573ae8b8de37e5f96c59f689ad1", size = 258570, upload-time = "2025-11-18T13:34:09.676Z" }, + { url = "https://files.pythonhosted.org/packages/48/35/2365e37c90df4f5342c4fa202223744119fe31264ee2924f09f074ea9b6d/coverage-7.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e71bba6a40883b00c6d571599b4627f50c360b3d0d02bfc658168936be74027b", size = 260899, upload-time = "2025-11-18T13:34:11.259Z" }, + { url = "https://files.pythonhosted.org/packages/05/56/26ab0464ca733fa325e8e71455c58c1c374ce30f7c04cebb88eabb037b18/coverage-7.12.0-cp314-cp314t-win32.whl", hash = "sha256:9157a5e233c40ce6613dead4c131a006adfda70e557b6856b97aceed01b0e27a", size = 221313, upload-time = "2025-11-18T13:34:12.863Z" }, + { url = "https://files.pythonhosted.org/packages/da/1c/017a3e1113ed34d998b27d2c6dba08a9e7cb97d362f0ec988fcd873dcf81/coverage-7.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e84da3a0fd233aeec797b981c51af1cabac74f9bd67be42458365b30d11b5291", size = 222423, upload-time = "2025-11-18T13:34:15.14Z" }, + { url = "https://files.pythonhosted.org/packages/4c/36/bcc504fdd5169301b52568802bb1b9cdde2e27a01d39fbb3b4b508ab7c2c/coverage-7.12.0-cp314-cp314t-win_arm64.whl", hash = "sha256:01d24af36fedda51c2b1aca56e4330a3710f83b02a5ff3743a6b015ffa7c9384", size = 220459, upload-time = "2025-11-18T13:34:17.222Z" }, + { url = "https://files.pythonhosted.org/packages/ce/a3/43b749004e3c09452e39bb56347a008f0a0668aad37324a99b5c8ca91d9e/coverage-7.12.0-py3-none-any.whl", hash = "sha256:159d50c0b12e060b15ed3d39f87ed43d4f7f7ad40b8a534f4dd331adbb51104a", size = 209503, upload-time = "2025-11-18T13:34:18.892Z" }, ] [[package]] name = "distlib" -version = "0.3.9" +version = "0.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923, upload-time = "2024-10-09T18:35:47.551Z" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973, upload-time = "2024-10-09T18:35:44.272Z" }, + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, ] [[package]] name = "dnspython" -version = "2.7.0" +version = "2.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b5/4a/263763cb2ba3816dd94b08ad3a33d5fdae34ecb856678773cc40a3605829/dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1", size = 345197, upload-time = "2024-10-05T20:14:59.362Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632, upload-time = "2024-10-05T20:14:57.687Z" }, + { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" }, ] [[package]] @@ -246,15 +312,15 @@ wheels = [ [[package]] name = "email-validator" -version = "2.2.0" +version = "2.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "dnspython" }, { name = "idna" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/48/ce/13508a1ec3f8bb981ae4ca79ea40384becc868bfae97fd1c942bb3a001b1/email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7", size = 48967, upload-time = "2024-06-20T11:30:30.034Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/22/900cb125c76b7aaa450ce02fd727f452243f2e91a61af068b40adba60ea9/email_validator-2.3.0.tar.gz", hash = "sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426", size = 51238, upload-time = "2025-08-26T13:09:06.831Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/ee/bf0adb559ad3c786f12bcbc9296b3f5675f529199bef03e2df281fa1fadb/email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631", size = 33521, upload-time = "2024-06-20T11:30:28.248Z" }, + { url = "https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4", size = 35604, upload-time = "2025-08-26T13:09:05.858Z" }, ] [[package]] @@ -271,11 +337,11 @@ wheels = [ [[package]] name = "filelock" -version = "3.18.0" +version = "3.20.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075, upload-time = "2025-03-14T07:11:40.47Z" } +sdist = { url = "https://files.pythonhosted.org/packages/58/46/0028a82567109b5ef6e4d2a1f04a583fb513e6cf9527fcdd09afd817deeb/filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4", size = 18922, upload-time = "2025-10-08T18:03:50.056Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload-time = "2025-03-14T07:11:39.145Z" }, + { url = "https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2", size = 16054, upload-time = "2025-10-08T18:03:48.35Z" }, ] [[package]] @@ -289,20 +355,20 @@ wheels = [ [[package]] name = "identify" -version = "2.6.9" +version = "2.6.15" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9b/98/a71ab060daec766acc30fb47dfca219d03de34a70d616a79a38c6066c5bf/identify-2.6.9.tar.gz", hash = "sha256:d40dfe3142a1421d8518e3d3985ef5ac42890683e32306ad614a29490abeb6bf", size = 99249, upload-time = "2025-03-08T15:54:13.632Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/e7/685de97986c916a6d93b3876139e00eef26ad5bbbd61925d670ae8013449/identify-2.6.15.tar.gz", hash = "sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf", size = 99311, upload-time = "2025-10-02T17:43:40.631Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/ce/0845144ed1f0e25db5e7a79c2354c1da4b5ce392b8966449d5db8dca18f1/identify-2.6.9-py2.py3-none-any.whl", hash = "sha256:c98b4322da415a8e5a70ff6e51fbc2d2932c015532d77e9f8537b4ba7813b150", size = 99101, upload-time = "2025-03-08T15:54:12.026Z" }, + { url = "https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl", hash = "sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757", size = 99183, upload-time = "2025-10-02T17:43:39.137Z" }, ] [[package]] name = "idna" -version = "3.10" +version = "3.11" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, ] [[package]] @@ -335,7 +401,7 @@ dev = [ { name = "sphinx-autobuild", version = "2024.10.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "sphinx-autobuild", version = "2025.8.25", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "sphinx-autodoc-typehints", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "sphinx-autodoc-typehints", version = "3.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "sphinx-autodoc-typehints", version = "3.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "sphinx-rtd-theme" }, { name = "types-jsonschema" }, ] @@ -375,7 +441,7 @@ wheels = [ [[package]] name = "jsonschema" -version = "4.23.0" +version = "4.25.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, @@ -383,124 +449,225 @@ dependencies = [ { name = "referencing" }, { name = "rpds-py" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/38/2e/03362ee4034a4c917f697890ccd4aec0800ccf9ded7f511971c75451deec/jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4", size = 325778, upload-time = "2024-07-08T18:40:05.546Z" } +sdist = { url = "https://files.pythonhosted.org/packages/74/69/f7185de793a29082a9f3c7728268ffb31cb5095131a9c139a74078e27336/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85", size = 357342, upload-time = "2025-08-18T17:03:50.038Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/4a/4f9dbeb84e8850557c02365a0eee0649abe5eb1d84af92a25731c6c0f922/jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566", size = 88462, upload-time = "2024-07-08T18:40:00.165Z" }, + { url = "https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63", size = 90040, upload-time = "2025-08-18T17:03:48.373Z" }, ] [[package]] name = "jsonschema-specifications" -version = "2024.10.1" +version = "2025.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "referencing" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/10/db/58f950c996c793472e336ff3655b13fbcf1e3b359dcf52dcf3ed3b52c352/jsonschema_specifications-2024.10.1.tar.gz", hash = "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272", size = 15561, upload-time = "2024-10-08T12:29:32.068Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/0f/8910b19ac0670a0f80ce1008e5e751c4a57e14d2c4c13a482aa6079fa9d6/jsonschema_specifications-2024.10.1-py3-none-any.whl", hash = "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf", size = 18459, upload-time = "2024-10-08T12:29:30.439Z" }, + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + +[[package]] +name = "librt" +version = "0.7.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/d9/6f3d3fcf5e5543ed8a60cc70fa7d50508ed60b8a10e9af6d2058159ab54e/librt-0.7.3.tar.gz", hash = "sha256:3ec50cf65235ff5c02c5b747748d9222e564ad48597122a361269dd3aa808798", size = 144549, upload-time = "2025-12-06T19:04:45.553Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/66/79a14e672256ef58144a24eb49adb338ec02de67ff4b45320af6504682ab/librt-0.7.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2682162855a708e3270eba4b92026b93f8257c3e65278b456c77631faf0f4f7a", size = 54707, upload-time = "2025-12-06T19:03:10.881Z" }, + { url = "https://files.pythonhosted.org/packages/58/fa/b709c65a9d5eab85f7bcfe0414504d9775aaad6e78727a0327e175474caa/librt-0.7.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:440c788f707c061d237c1e83edf6164ff19f5c0f823a3bf054e88804ebf971ec", size = 56670, upload-time = "2025-12-06T19:03:12.107Z" }, + { url = "https://files.pythonhosted.org/packages/3a/56/0685a0772ec89ddad4c00e6b584603274c3d818f9a68e2c43c4eb7b39ee9/librt-0.7.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:399938edbd3d78339f797d685142dd8a623dfaded023cf451033c85955e4838a", size = 161045, upload-time = "2025-12-06T19:03:13.444Z" }, + { url = "https://files.pythonhosted.org/packages/4e/d9/863ada0c5ce48aefb89df1555e392b2209fcb6daee4c153c031339b9a89b/librt-0.7.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1975eda520957c6e0eb52d12968dd3609ffb7eef05d4223d097893d6daf1d8a7", size = 169532, upload-time = "2025-12-06T19:03:14.699Z" }, + { url = "https://files.pythonhosted.org/packages/68/a0/71da6c8724fd16c31749905ef1c9e11de206d9301b5be984bf2682b4efb3/librt-0.7.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f9da128d0edf990cf0d2ca011b02cd6f639e79286774bd5b0351245cbb5a6e51", size = 183277, upload-time = "2025-12-06T19:03:16.446Z" }, + { url = "https://files.pythonhosted.org/packages/8c/bf/9c97bf2f8338ba1914de233ea312bba2bbd7c59f43f807b3e119796bab18/librt-0.7.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e19acfde38cb532a560b98f473adc741c941b7a9bc90f7294bc273d08becb58b", size = 179045, upload-time = "2025-12-06T19:03:17.838Z" }, + { url = "https://files.pythonhosted.org/packages/b3/b1/ceea067f489e904cb4ddcca3c9b06ba20229bc3fa7458711e24a5811f162/librt-0.7.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:7b4f57f7a0c65821c5441d98c47ff7c01d359b1e12328219709bdd97fdd37f90", size = 173521, upload-time = "2025-12-06T19:03:19.17Z" }, + { url = "https://files.pythonhosted.org/packages/7a/41/6cb18f5da9c89ed087417abb0127a445a50ad4eaf1282ba5b52588187f47/librt-0.7.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:256793988bff98040de23c57cf36e1f4c2f2dc3dcd17537cdac031d3b681db71", size = 193592, upload-time = "2025-12-06T19:03:20.637Z" }, + { url = "https://files.pythonhosted.org/packages/4c/3c/fcef208746584e7c78584b7aedc617130c4a4742cb8273361bbda8b183b5/librt-0.7.3-cp310-cp310-win32.whl", hash = "sha256:fcb72249ac4ea81a7baefcbff74df7029c3cb1cf01a711113fa052d563639c9c", size = 47201, upload-time = "2025-12-06T19:03:21.764Z" }, + { url = "https://files.pythonhosted.org/packages/c4/bf/d8a6c35d1b2b789a4df9b3ddb1c8f535ea373fde2089698965a8f0d62138/librt-0.7.3-cp310-cp310-win_amd64.whl", hash = "sha256:4887c29cadbdc50640179e3861c276325ff2986791e6044f73136e6e798ff806", size = 54371, upload-time = "2025-12-06T19:03:23.231Z" }, + { url = "https://files.pythonhosted.org/packages/21/e6/f6391f5c6f158d31ed9af6bd1b1bcd3ffafdea1d816bc4219d0d90175a7f/librt-0.7.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:687403cced6a29590e6be6964463835315905221d797bc5c934a98750fe1a9af", size = 54711, upload-time = "2025-12-06T19:03:24.6Z" }, + { url = "https://files.pythonhosted.org/packages/ab/1b/53c208188c178987c081560a0fcf36f5ca500d5e21769596c845ef2f40d4/librt-0.7.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:24d70810f6e2ea853ff79338001533716b373cc0f63e2a0be5bc96129edb5fb5", size = 56664, upload-time = "2025-12-06T19:03:25.969Z" }, + { url = "https://files.pythonhosted.org/packages/cb/5c/d9da832b9a1e5f8366e8a044ec80217945385b26cb89fd6f94bfdc7d80b0/librt-0.7.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bf8c7735fbfc0754111f00edda35cf9e98a8d478de6c47b04eaa9cef4300eaa7", size = 161701, upload-time = "2025-12-06T19:03:27.035Z" }, + { url = "https://files.pythonhosted.org/packages/20/aa/1e0a7aba15e78529dd21f233076b876ee58c8b8711b1793315bdd3b263b0/librt-0.7.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32d43610dff472eab939f4d7fbdd240d1667794192690433672ae22d7af8445", size = 171040, upload-time = "2025-12-06T19:03:28.482Z" }, + { url = "https://files.pythonhosted.org/packages/69/46/3cfa325c1c2bc25775ec6ec1718cfbec9cff4ac767d37d2d3a2d1cc6f02c/librt-0.7.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:adeaa886d607fb02563c1f625cf2ee58778a2567c0c109378da8f17ec3076ad7", size = 184720, upload-time = "2025-12-06T19:03:29.599Z" }, + { url = "https://files.pythonhosted.org/packages/99/bb/e4553433d7ac47f4c75d0a7e59b13aee0e08e88ceadbee356527a9629b0a/librt-0.7.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:572a24fc5958c61431da456a0ef1eeea6b4989d81eeb18b8e5f1f3077592200b", size = 180731, upload-time = "2025-12-06T19:03:31.201Z" }, + { url = "https://files.pythonhosted.org/packages/35/89/51cd73006232981a3106d4081fbaa584ac4e27b49bc02266468d3919db03/librt-0.7.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6488e69d408b492e08bfb68f20c4a899a354b4386a446ecd490baff8d0862720", size = 174565, upload-time = "2025-12-06T19:03:32.818Z" }, + { url = "https://files.pythonhosted.org/packages/42/54/0578a78b587e5aa22486af34239a052c6366835b55fc307bc64380229e3f/librt-0.7.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ed028fc3d41adda916320712838aec289956c89b4f0a361ceadf83a53b4c047a", size = 195247, upload-time = "2025-12-06T19:03:34.434Z" }, + { url = "https://files.pythonhosted.org/packages/b5/0a/ee747cd999753dd9447e50b98fc36ee433b6c841a42dbf6d47b64b32a56e/librt-0.7.3-cp311-cp311-win32.whl", hash = "sha256:2cf9d73499486ce39eebbff5f42452518cc1f88d8b7ea4a711ab32962b176ee2", size = 47514, upload-time = "2025-12-06T19:03:35.959Z" }, + { url = "https://files.pythonhosted.org/packages/ec/af/8b13845178dec488e752878f8e290f8f89e7e34ae1528b70277aa1a6dd1e/librt-0.7.3-cp311-cp311-win_amd64.whl", hash = "sha256:35f1609e3484a649bb80431310ddbec81114cd86648f1d9482bc72a3b86ded2e", size = 54695, upload-time = "2025-12-06T19:03:36.956Z" }, + { url = "https://files.pythonhosted.org/packages/02/7a/ae59578501b1a25850266778f59279f4f3e726acc5c44255bfcb07b4bc57/librt-0.7.3-cp311-cp311-win_arm64.whl", hash = "sha256:550fdbfbf5bba6a2960b27376ca76d6aaa2bd4b1a06c4255edd8520c306fcfc0", size = 48142, upload-time = "2025-12-06T19:03:38.263Z" }, + { url = "https://files.pythonhosted.org/packages/29/90/ed8595fa4e35b6020317b5ea8d226a782dcbac7a997c19ae89fb07a41c66/librt-0.7.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0fa9ac2e49a6bee56e47573a6786cb635e128a7b12a0dc7851090037c0d397a3", size = 55687, upload-time = "2025-12-06T19:03:39.245Z" }, + { url = "https://files.pythonhosted.org/packages/dd/f6/6a20702a07b41006cb001a759440cb6b5362530920978f64a2b2ae2bf729/librt-0.7.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e980cf1ed1a2420a6424e2ed884629cdead291686f1048810a817de07b5eb18", size = 57127, upload-time = "2025-12-06T19:03:40.3Z" }, + { url = "https://files.pythonhosted.org/packages/79/f3/b0c4703d5ffe9359b67bb2ccb86c42d4e930a363cfc72262ac3ba53cff3e/librt-0.7.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e094e445c37c57e9ec612847812c301840239d34ccc5d153a982fa9814478c60", size = 165336, upload-time = "2025-12-06T19:03:41.369Z" }, + { url = "https://files.pythonhosted.org/packages/02/69/3ba05b73ab29ccbe003856232cea4049769be5942d799e628d1470ed1694/librt-0.7.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aca73d70c3f553552ba9133d4a09e767dcfeee352d8d8d3eb3f77e38a3beb3ed", size = 174237, upload-time = "2025-12-06T19:03:42.44Z" }, + { url = "https://files.pythonhosted.org/packages/22/ad/d7c2671e7bf6c285ef408aa435e9cd3fdc06fd994601e1f2b242df12034f/librt-0.7.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c634a0a6db395fdaba0361aa78395597ee72c3aad651b9a307a3a7eaf5efd67e", size = 189017, upload-time = "2025-12-06T19:03:44.01Z" }, + { url = "https://files.pythonhosted.org/packages/f4/94/d13f57193148004592b618555f296b41d2d79b1dc814ff8b3273a0bf1546/librt-0.7.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a59a69deeb458c858b8fea6acf9e2acd5d755d76cd81a655256bc65c20dfff5b", size = 183983, upload-time = "2025-12-06T19:03:45.834Z" }, + { url = "https://files.pythonhosted.org/packages/02/10/b612a9944ebd39fa143c7e2e2d33f2cb790205e025ddd903fb509a3a3bb3/librt-0.7.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d91e60ac44bbe3a77a67af4a4c13114cbe9f6d540337ce22f2c9eaf7454ca71f", size = 177602, upload-time = "2025-12-06T19:03:46.944Z" }, + { url = "https://files.pythonhosted.org/packages/1f/48/77bc05c4cc232efae6c5592c0095034390992edbd5bae8d6cf1263bb7157/librt-0.7.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:703456146dc2bf430f7832fd1341adac5c893ec3c1430194fdcefba00012555c", size = 199282, upload-time = "2025-12-06T19:03:48.069Z" }, + { url = "https://files.pythonhosted.org/packages/12/aa/05916ccd864227db1ffec2a303ae34f385c6b22d4e7ce9f07054dbcf083c/librt-0.7.3-cp312-cp312-win32.whl", hash = "sha256:b7c1239b64b70be7759554ad1a86288220bbb04d68518b527783c4ad3fb4f80b", size = 47879, upload-time = "2025-12-06T19:03:49.289Z" }, + { url = "https://files.pythonhosted.org/packages/50/92/7f41c42d31ea818b3c4b9cc1562e9714bac3c676dd18f6d5dd3d0f2aa179/librt-0.7.3-cp312-cp312-win_amd64.whl", hash = "sha256:ef59c938f72bdbc6ab52dc50f81d0637fde0f194b02d636987cea2ab30f8f55a", size = 54972, upload-time = "2025-12-06T19:03:50.335Z" }, + { url = "https://files.pythonhosted.org/packages/3f/dc/53582bbfb422311afcbc92adb75711f04e989cec052f08ec0152fbc36c9c/librt-0.7.3-cp312-cp312-win_arm64.whl", hash = "sha256:ff21c554304e8226bf80c3a7754be27c6c3549a9fec563a03c06ee8f494da8fc", size = 48338, upload-time = "2025-12-06T19:03:51.431Z" }, + { url = "https://files.pythonhosted.org/packages/93/7d/e0ce1837dfb452427db556e6d4c5301ba3b22fe8de318379fbd0593759b9/librt-0.7.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56f2a47beda8409061bc1c865bef2d4bd9ff9255219402c0817e68ab5ad89aed", size = 55742, upload-time = "2025-12-06T19:03:52.459Z" }, + { url = "https://files.pythonhosted.org/packages/be/c0/3564262301e507e1d5cf31c7d84cb12addf0d35e05ba53312494a2eba9a4/librt-0.7.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:14569ac5dd38cfccf0a14597a88038fb16811a6fede25c67b79c6d50fc2c8fdc", size = 57163, upload-time = "2025-12-06T19:03:53.516Z" }, + { url = "https://files.pythonhosted.org/packages/be/ac/245e72b7e443d24a562f6047563c7f59833384053073ef9410476f68505b/librt-0.7.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6038ccbd5968325a5d6fd393cf6e00b622a8de545f0994b89dd0f748dcf3e19e", size = 165840, upload-time = "2025-12-06T19:03:54.918Z" }, + { url = "https://files.pythonhosted.org/packages/98/af/587e4491f40adba066ba39a450c66bad794c8d92094f936a201bfc7c2b5f/librt-0.7.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d39079379a9a28e74f4d57dc6357fa310a1977b51ff12239d7271ec7e71d67f5", size = 174827, upload-time = "2025-12-06T19:03:56.082Z" }, + { url = "https://files.pythonhosted.org/packages/78/21/5b8c60ea208bc83dd00421022a3874330685d7e856404128dc3728d5d1af/librt-0.7.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8837d5a52a2d7aa9f4c3220a8484013aed1d8ad75240d9a75ede63709ef89055", size = 189612, upload-time = "2025-12-06T19:03:57.507Z" }, + { url = "https://files.pythonhosted.org/packages/da/2f/8b819169ef696421fb81cd04c6cdf225f6e96f197366001e9d45180d7e9e/librt-0.7.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:399bbd7bcc1633c3e356ae274a1deb8781c7bf84d9c7962cc1ae0c6e87837292", size = 184584, upload-time = "2025-12-06T19:03:58.686Z" }, + { url = "https://files.pythonhosted.org/packages/6c/fc/af9d225a9395b77bd7678362cb055d0b8139c2018c37665de110ca388022/librt-0.7.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8d8cf653e798ee4c4e654062b633db36984a1572f68c3aa25e364a0ddfbbb910", size = 178269, upload-time = "2025-12-06T19:03:59.769Z" }, + { url = "https://files.pythonhosted.org/packages/6c/d8/7b4fa1683b772966749d5683aa3fd605813defffe157833a8fa69cc89207/librt-0.7.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2f03484b54bf4ae80ab2e504a8d99d20d551bfe64a7ec91e218010b467d77093", size = 199852, upload-time = "2025-12-06T19:04:00.901Z" }, + { url = "https://files.pythonhosted.org/packages/77/e8/4598413aece46ca38d9260ef6c51534bd5f34b5c21474fcf210ce3a02123/librt-0.7.3-cp313-cp313-win32.whl", hash = "sha256:44b3689b040df57f492e02cd4f0bacd1b42c5400e4b8048160c9d5e866de8abe", size = 47936, upload-time = "2025-12-06T19:04:02.054Z" }, + { url = "https://files.pythonhosted.org/packages/af/80/ac0e92d5ef8c6791b3e2c62373863827a279265e0935acdf807901353b0e/librt-0.7.3-cp313-cp313-win_amd64.whl", hash = "sha256:6b407c23f16ccc36614c136251d6b32bf30de7a57f8e782378f1107be008ddb0", size = 54965, upload-time = "2025-12-06T19:04:03.224Z" }, + { url = "https://files.pythonhosted.org/packages/f1/fd/042f823fcbff25c1449bb4203a29919891ca74141b68d3a5f6612c4ce283/librt-0.7.3-cp313-cp313-win_arm64.whl", hash = "sha256:abfc57cab3c53c4546aee31859ef06753bfc136c9d208129bad23e2eca39155a", size = 48350, upload-time = "2025-12-06T19:04:04.234Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ae/c6ecc7bb97134a71b5241e8855d39964c0e5f4d96558f0d60593892806d2/librt-0.7.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:120dd21d46ff875e849f1aae19346223cf15656be489242fe884036b23d39e93", size = 55175, upload-time = "2025-12-06T19:04:05.308Z" }, + { url = "https://files.pythonhosted.org/packages/cf/bc/2cc0cb0ab787b39aa5c7645cd792433c875982bdf12dccca558b89624594/librt-0.7.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1617bea5ab31266e152871208502ee943cb349c224846928a1173c864261375e", size = 56881, upload-time = "2025-12-06T19:04:06.674Z" }, + { url = "https://files.pythonhosted.org/packages/8e/87/397417a386190b70f5bf26fcedbaa1515f19dce33366e2684c6b7ee83086/librt-0.7.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93b2a1f325fefa1482516ced160c8c7b4b8d53226763fa6c93d151fa25164207", size = 163710, upload-time = "2025-12-06T19:04:08.437Z" }, + { url = "https://files.pythonhosted.org/packages/c9/37/7338f85b80e8a17525d941211451199845093ca242b32efbf01df8531e72/librt-0.7.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d4801db8354436fd3936531e7f0e4feb411f62433a6b6cb32bb416e20b529f", size = 172471, upload-time = "2025-12-06T19:04:10.124Z" }, + { url = "https://files.pythonhosted.org/packages/3b/e0/741704edabbfae2c852fedc1b40d9ed5a783c70ed3ed8e4fe98f84b25d13/librt-0.7.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11ad45122bbed42cfc8b0597450660126ef28fd2d9ae1a219bc5af8406f95678", size = 186804, upload-time = "2025-12-06T19:04:11.586Z" }, + { url = "https://files.pythonhosted.org/packages/f4/d1/0a82129d6ba242f3be9af34815be089f35051bc79619f5c27d2c449ecef6/librt-0.7.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:6b4e7bff1d76dd2b46443078519dc75df1b5e01562345f0bb740cea5266d8218", size = 181817, upload-time = "2025-12-06T19:04:12.802Z" }, + { url = "https://files.pythonhosted.org/packages/4f/32/704f80bcf9979c68d4357c46f2af788fbf9d5edda9e7de5786ed2255e911/librt-0.7.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:d86f94743a11873317094326456b23f8a5788bad9161fd2f0e52088c33564620", size = 175602, upload-time = "2025-12-06T19:04:14.004Z" }, + { url = "https://files.pythonhosted.org/packages/f7/6d/4355cfa0fae0c062ba72f541d13db5bc575770125a7ad3d4f46f4109d305/librt-0.7.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:754a0d09997095ad764ccef050dd5bf26cbf457aab9effcba5890dad081d879e", size = 196497, upload-time = "2025-12-06T19:04:15.487Z" }, + { url = "https://files.pythonhosted.org/packages/2e/eb/ac6d8517d44209e5a712fde46f26d0055e3e8969f24d715f70bd36056230/librt-0.7.3-cp314-cp314-win32.whl", hash = "sha256:fbd7351d43b80d9c64c3cfcb50008f786cc82cba0450e8599fdd64f264320bd3", size = 44678, upload-time = "2025-12-06T19:04:16.688Z" }, + { url = "https://files.pythonhosted.org/packages/e9/93/238f026d141faf9958da588c761a0812a1a21c98cc54a76f3608454e4e59/librt-0.7.3-cp314-cp314-win_amd64.whl", hash = "sha256:d376a35c6561e81d2590506804b428fc1075fcc6298fc5bb49b771534c0ba010", size = 51689, upload-time = "2025-12-06T19:04:17.726Z" }, + { url = "https://files.pythonhosted.org/packages/52/44/43f462ad9dcf9ed7d3172fe2e30d77b980956250bd90e9889a9cca93df2a/librt-0.7.3-cp314-cp314-win_arm64.whl", hash = "sha256:cbdb3f337c88b43c3b49ca377731912c101178be91cb5071aac48faa898e6f8e", size = 44662, upload-time = "2025-12-06T19:04:18.771Z" }, + { url = "https://files.pythonhosted.org/packages/1d/35/fed6348915f96b7323241de97f26e2af481e95183b34991df12fd5ce31b1/librt-0.7.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9f0e0927efe87cd42ad600628e595a1a0aa1c64f6d0b55f7e6059079a428641a", size = 57347, upload-time = "2025-12-06T19:04:19.812Z" }, + { url = "https://files.pythonhosted.org/packages/9a/f2/045383ccc83e3fea4fba1b761796584bc26817b6b2efb6b8a6731431d16f/librt-0.7.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:020c6db391268bcc8ce75105cb572df8cb659a43fd347366aaa407c366e5117a", size = 59223, upload-time = "2025-12-06T19:04:20.862Z" }, + { url = "https://files.pythonhosted.org/packages/77/3f/c081f8455ab1d7f4a10dbe58463ff97119272ff32494f21839c3b9029c2c/librt-0.7.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7af7785f5edd1f418da09a8cdb9ec84b0213e23d597413e06525340bcce1ea4f", size = 183861, upload-time = "2025-12-06T19:04:21.963Z" }, + { url = "https://files.pythonhosted.org/packages/1d/f5/73c5093c22c31fbeaebc25168837f05ebfd8bf26ce00855ef97a5308f36f/librt-0.7.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ccadf260bb46a61b9c7e89e2218f6efea9f3eeaaab4e3d1f58571890e54858e", size = 194594, upload-time = "2025-12-06T19:04:23.14Z" }, + { url = "https://files.pythonhosted.org/packages/78/b8/d5f17d4afe16612a4a94abfded94c16c5a033f183074fb130dfe56fc1a42/librt-0.7.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9883b2d819ce83f87ba82a746c81d14ada78784db431e57cc9719179847376e", size = 206759, upload-time = "2025-12-06T19:04:24.328Z" }, + { url = "https://files.pythonhosted.org/packages/36/2e/021765c1be85ee23ffd5b5b968bb4cba7526a4db2a0fc27dcafbdfc32da7/librt-0.7.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:59cb0470612d21fa1efddfa0dd710756b50d9c7fb6c1236bbf8ef8529331dc70", size = 203210, upload-time = "2025-12-06T19:04:25.544Z" }, + { url = "https://files.pythonhosted.org/packages/77/f0/9923656e42da4fd18c594bd08cf6d7e152d4158f8b808e210d967f0dcceb/librt-0.7.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:1fe603877e1865b5fd047a5e40379509a4a60204aa7aa0f72b16f7a41c3f0712", size = 196708, upload-time = "2025-12-06T19:04:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/fc/0b/0708b886ac760e64d6fbe7e16024e4be3ad1a3629d19489a97e9cf4c3431/librt-0.7.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5460d99ed30f043595bbdc888f542bad2caeb6226b01c33cda3ae444e8f82d42", size = 217212, upload-time = "2025-12-06T19:04:27.892Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7f/12a73ff17bca4351e73d585dd9ebf46723c4a8622c4af7fe11a2e2d011ff/librt-0.7.3-cp314-cp314t-win32.whl", hash = "sha256:d09f677693328503c9e492e33e9601464297c01f9ebd966ea8fc5308f3069bfd", size = 45586, upload-time = "2025-12-06T19:04:29.116Z" }, + { url = "https://files.pythonhosted.org/packages/e2/df/8decd032ac9b995e4f5606cde783711a71094128d88d97a52e397daf2c89/librt-0.7.3-cp314-cp314t-win_amd64.whl", hash = "sha256:25711f364c64cab2c910a0247e90b51421e45dbc8910ceeb4eac97a9e132fc6f", size = 53002, upload-time = "2025-12-06T19:04:30.173Z" }, + { url = "https://files.pythonhosted.org/packages/de/0c/6605b6199de8178afe7efc77ca1d8e6db00453bc1d3349d27605c0f42104/librt-0.7.3-cp314-cp314t-win_arm64.whl", hash = "sha256:a9f9b661f82693eb56beb0605156c7fca57f535704ab91837405913417d6990b", size = 45647, upload-time = "2025-12-06T19:04:31.302Z" }, ] [[package]] name = "markupsafe" -version = "3.0.2" +version = "3.0.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357, upload-time = "2024-10-18T15:20:51.44Z" }, - { url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393, upload-time = "2024-10-18T15:20:52.426Z" }, - { url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732, upload-time = "2024-10-18T15:20:53.578Z" }, - { url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866, upload-time = "2024-10-18T15:20:55.06Z" }, - { url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964, upload-time = "2024-10-18T15:20:55.906Z" }, - { url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977, upload-time = "2024-10-18T15:20:57.189Z" }, - { url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366, upload-time = "2024-10-18T15:20:58.235Z" }, - { url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091, upload-time = "2024-10-18T15:20:59.235Z" }, - { url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065, upload-time = "2024-10-18T15:21:00.307Z" }, - { url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514, upload-time = "2024-10-18T15:21:01.122Z" }, - { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353, upload-time = "2024-10-18T15:21:02.187Z" }, - { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392, upload-time = "2024-10-18T15:21:02.941Z" }, - { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984, upload-time = "2024-10-18T15:21:03.953Z" }, - { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120, upload-time = "2024-10-18T15:21:06.495Z" }, - { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032, upload-time = "2024-10-18T15:21:07.295Z" }, - { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057, upload-time = "2024-10-18T15:21:08.073Z" }, - { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359, upload-time = "2024-10-18T15:21:09.318Z" }, - { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306, upload-time = "2024-10-18T15:21:10.185Z" }, - { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094, upload-time = "2024-10-18T15:21:11.005Z" }, - { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521, upload-time = "2024-10-18T15:21:12.911Z" }, - { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" }, - { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" }, - { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" }, - { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" }, - { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" }, - { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" }, - { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" }, - { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" }, - { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" }, - { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, - { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, - { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, - { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, - { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, - { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, - { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, - { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, - { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, - { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, - { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, - { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, - { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, - { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, - { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, - { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, - { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, - { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, - { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, - { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, - { 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/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, + { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, + { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, + { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, + { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, + { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, + { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, ] [[package]] name = "mypy" -version = "1.18.1" +version = "1.19.0" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "librt" }, { 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" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/b5/b58cdc25fadd424552804bf410855d52324183112aa004f0732c5f6324cf/mypy-1.19.0.tar.gz", hash = "sha256:f6b874ca77f733222641e5c46e4711648c4037ea13646fd0cdc814c2eaec2528", size = 3579025, upload-time = "2025-11-28T15:49:01.26Z" } 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" }, + { url = "https://files.pythonhosted.org/packages/98/8f/55fb488c2b7dabd76e3f30c10f7ab0f6190c1fcbc3e97b1e588ec625bbe2/mypy-1.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6148ede033982a8c5ca1143de34c71836a09f105068aaa8b7d5edab2b053e6c8", size = 13093239, upload-time = "2025-11-28T15:45:11.342Z" }, + { url = "https://files.pythonhosted.org/packages/72/1b/278beea978456c56b3262266274f335c3ba5ff2c8108b3b31bec1ffa4c1d/mypy-1.19.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a9ac09e52bb0f7fb912f5d2a783345c72441a08ef56ce3e17c1752af36340a39", size = 12156128, upload-time = "2025-11-28T15:46:02.566Z" }, + { url = "https://files.pythonhosted.org/packages/21/f8/e06f951902e136ff74fd7a4dc4ef9d884faeb2f8eb9c49461235714f079f/mypy-1.19.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11f7254c15ab3f8ed68f8e8f5cbe88757848df793e31c36aaa4d4f9783fd08ab", size = 12753508, upload-time = "2025-11-28T15:44:47.538Z" }, + { url = "https://files.pythonhosted.org/packages/67/5a/d035c534ad86e09cee274d53cf0fd769c0b29ca6ed5b32e205be3c06878c/mypy-1.19.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318ba74f75899b0e78b847d8c50821e4c9637c79d9a59680fc1259f29338cb3e", size = 13507553, upload-time = "2025-11-28T15:44:39.26Z" }, + { url = "https://files.pythonhosted.org/packages/6a/17/c4a5498e00071ef29e483a01558b285d086825b61cf1fb2629fbdd019d94/mypy-1.19.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cf7d84f497f78b682edd407f14a7b6e1a2212b433eedb054e2081380b7395aa3", size = 13792898, upload-time = "2025-11-28T15:44:31.102Z" }, + { url = "https://files.pythonhosted.org/packages/67/f6/bb542422b3ee4399ae1cdc463300d2d91515ab834c6233f2fd1d52fa21e0/mypy-1.19.0-cp310-cp310-win_amd64.whl", hash = "sha256:c3385246593ac2b97f155a0e9639be906e73534630f663747c71908dfbf26134", size = 10048835, upload-time = "2025-11-28T15:48:15.744Z" }, + { url = "https://files.pythonhosted.org/packages/0f/d2/010fb171ae5ac4a01cc34fbacd7544531e5ace95c35ca166dd8fd1b901d0/mypy-1.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a31e4c28e8ddb042c84c5e977e28a21195d086aaffaf08b016b78e19c9ef8106", size = 13010563, upload-time = "2025-11-28T15:48:23.975Z" }, + { url = "https://files.pythonhosted.org/packages/41/6b/63f095c9f1ce584fdeb595d663d49e0980c735a1d2004720ccec252c5d47/mypy-1.19.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34ec1ac66d31644f194b7c163d7f8b8434f1b49719d403a5d26c87fff7e913f7", size = 12077037, upload-time = "2025-11-28T15:47:51.582Z" }, + { url = "https://files.pythonhosted.org/packages/d7/83/6cb93d289038d809023ec20eb0b48bbb1d80af40511fa077da78af6ff7c7/mypy-1.19.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cb64b0ba5980466a0f3f9990d1c582bcab8db12e29815ecb57f1408d99b4bff7", size = 12680255, upload-time = "2025-11-28T15:46:57.628Z" }, + { url = "https://files.pythonhosted.org/packages/99/db/d217815705987d2cbace2edd9100926196d6f85bcb9b5af05058d6e3c8ad/mypy-1.19.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:120cffe120cca5c23c03c77f84abc0c14c5d2e03736f6c312480020082f1994b", size = 13421472, upload-time = "2025-11-28T15:47:59.655Z" }, + { url = "https://files.pythonhosted.org/packages/4e/51/d2beaca7c497944b07594f3f8aad8d2f0e8fc53677059848ae5d6f4d193e/mypy-1.19.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7a500ab5c444268a70565e374fc803972bfd1f09545b13418a5174e29883dab7", size = 13651823, upload-time = "2025-11-28T15:45:29.318Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d1/7883dcf7644db3b69490f37b51029e0870aac4a7ad34d09ceae709a3df44/mypy-1.19.0-cp311-cp311-win_amd64.whl", hash = "sha256:c14a98bc63fd867530e8ec82f217dae29d0550c86e70debc9667fff1ec83284e", size = 10049077, upload-time = "2025-11-28T15:45:39.818Z" }, + { url = "https://files.pythonhosted.org/packages/11/7e/1afa8fb188b876abeaa14460dc4983f909aaacaa4bf5718c00b2c7e0b3d5/mypy-1.19.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0fb3115cb8fa7c5f887c8a8d81ccdcb94cff334684980d847e5a62e926910e1d", size = 13207728, upload-time = "2025-11-28T15:46:26.463Z" }, + { url = "https://files.pythonhosted.org/packages/b2/13/f103d04962bcbefb1644f5ccb235998b32c337d6c13145ea390b9da47f3e/mypy-1.19.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3e19e3b897562276bb331074d64c076dbdd3e79213f36eed4e592272dabd760", size = 12202945, upload-time = "2025-11-28T15:48:49.143Z" }, + { url = "https://files.pythonhosted.org/packages/e4/93/a86a5608f74a22284a8ccea8592f6e270b61f95b8588951110ad797c2ddd/mypy-1.19.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b9d491295825182fba01b6ffe2c6fe4e5a49dbf4e2bb4d1217b6ced3b4797bc6", size = 12718673, upload-time = "2025-11-28T15:47:37.193Z" }, + { url = "https://files.pythonhosted.org/packages/3d/58/cf08fff9ced0423b858f2a7495001fda28dc058136818ee9dffc31534ea9/mypy-1.19.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6016c52ab209919b46169651b362068f632efcd5eb8ef9d1735f6f86da7853b2", size = 13608336, upload-time = "2025-11-28T15:48:32.625Z" }, + { url = "https://files.pythonhosted.org/packages/64/ed/9c509105c5a6d4b73bb08733102a3ea62c25bc02c51bca85e3134bf912d3/mypy-1.19.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f188dcf16483b3e59f9278c4ed939ec0254aa8a60e8fc100648d9ab5ee95a431", size = 13833174, upload-time = "2025-11-28T15:45:48.091Z" }, + { url = "https://files.pythonhosted.org/packages/cd/71/01939b66e35c6f8cb3e6fdf0b657f0fd24de2f8ba5e523625c8e72328208/mypy-1.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:0e3c3d1e1d62e678c339e7ade72746a9e0325de42cd2cccc51616c7b2ed1a018", size = 10112208, upload-time = "2025-11-28T15:46:41.702Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0d/a1357e6bb49e37ce26fcf7e3cc55679ce9f4ebee0cd8b6ee3a0e301a9210/mypy-1.19.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7686ed65dbabd24d20066f3115018d2dce030d8fa9db01aa9f0a59b6813e9f9e", size = 13191993, upload-time = "2025-11-28T15:47:22.336Z" }, + { url = "https://files.pythonhosted.org/packages/5d/75/8e5d492a879ec4490e6ba664b5154e48c46c85b5ac9785792a5ec6a4d58f/mypy-1.19.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fd4a985b2e32f23bead72e2fb4bbe5d6aceee176be471243bd831d5b2644672d", size = 12174411, upload-time = "2025-11-28T15:44:55.492Z" }, + { url = "https://files.pythonhosted.org/packages/71/31/ad5dcee9bfe226e8eaba777e9d9d251c292650130f0450a280aec3485370/mypy-1.19.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fc51a5b864f73a3a182584b1ac75c404396a17eced54341629d8bdcb644a5bba", size = 12727751, upload-time = "2025-11-28T15:44:14.169Z" }, + { url = "https://files.pythonhosted.org/packages/77/06/b6b8994ce07405f6039701f4b66e9d23f499d0b41c6dd46ec28f96d57ec3/mypy-1.19.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:37af5166f9475872034b56c5efdcf65ee25394e9e1d172907b84577120714364", size = 13593323, upload-time = "2025-11-28T15:46:34.699Z" }, + { url = "https://files.pythonhosted.org/packages/68/b1/126e274484cccdf099a8e328d4fda1c7bdb98a5e888fa6010b00e1bbf330/mypy-1.19.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:510c014b722308c9bd377993bcbf9a07d7e0692e5fa8fc70e639c1eb19fc6bee", size = 13818032, upload-time = "2025-11-28T15:46:18.286Z" }, + { url = "https://files.pythonhosted.org/packages/f8/56/53a8f70f562dfc466c766469133a8a4909f6c0012d83993143f2a9d48d2d/mypy-1.19.0-cp313-cp313-win_amd64.whl", hash = "sha256:cabbee74f29aa9cd3b444ec2f1e4fa5a9d0d746ce7567a6a609e224429781f53", size = 10120644, upload-time = "2025-11-28T15:47:43.99Z" }, + { url = "https://files.pythonhosted.org/packages/b0/f4/7751f32f56916f7f8c229fe902cbdba3e4dd3f3ea9e8b872be97e7fc546d/mypy-1.19.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f2e36bed3c6d9b5f35d28b63ca4b727cb0228e480826ffc8953d1892ddc8999d", size = 13185236, upload-time = "2025-11-28T15:45:20.696Z" }, + { url = "https://files.pythonhosted.org/packages/35/31/871a9531f09e78e8d145032355890384f8a5b38c95a2c7732d226b93242e/mypy-1.19.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a18d8abdda14035c5718acb748faec09571432811af129bf0d9e7b2d6699bf18", size = 12213902, upload-time = "2025-11-28T15:46:10.117Z" }, + { url = "https://files.pythonhosted.org/packages/58/b8/af221910dd40eeefa2077a59107e611550167b9994693fc5926a0b0f87c0/mypy-1.19.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f75e60aca3723a23511948539b0d7ed514dda194bc3755eae0bfc7a6b4887aa7", size = 12738600, upload-time = "2025-11-28T15:44:22.521Z" }, + { url = "https://files.pythonhosted.org/packages/11/9f/c39e89a3e319c1d9c734dedec1183b2cc3aefbab066ec611619002abb932/mypy-1.19.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f44f2ae3c58421ee05fe609160343c25f70e3967f6e32792b5a78006a9d850f", size = 13592639, upload-time = "2025-11-28T15:48:08.55Z" }, + { url = "https://files.pythonhosted.org/packages/97/6d/ffaf5f01f5e284d9033de1267e6c1b8f3783f2cf784465378a86122e884b/mypy-1.19.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:63ea6a00e4bd6822adbfc75b02ab3653a17c02c4347f5bb0cf1d5b9df3a05835", size = 13799132, upload-time = "2025-11-28T15:47:06.032Z" }, + { url = "https://files.pythonhosted.org/packages/fe/b0/c33921e73aaa0106224e5a34822411bea38046188eb781637f5a5b07e269/mypy-1.19.0-cp314-cp314-win_amd64.whl", hash = "sha256:3ad925b14a0bb99821ff6f734553294aa6a3440a8cb082fe1f5b84dfb662afb1", size = 10269832, upload-time = "2025-11-28T15:47:29.392Z" }, + { url = "https://files.pythonhosted.org/packages/09/0e/fe228ed5aeab470c6f4eb82481837fadb642a5aa95cc8215fd2214822c10/mypy-1.19.0-py3-none-any.whl", hash = "sha256:0c01c99d626380752e527d5ce8e69ffbba2046eb8a060db0329690849cf9b6f9", size = 2469714, upload-time = "2025-11-28T15:45:33.22Z" }, ] [[package]] @@ -550,30 +717,30 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.3.7" +version = "4.5.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b6/2d/7d512a3913d60623e7eb945c6d1b4f0bddf1d0b7ada5225274c87e5b53d1/platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351", size = 21291, upload-time = "2025-03-19T20:36:10.989Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/45/59578566b3275b8fd9157885918fcd0c4d74162928a5310926887b856a51/platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94", size = 18499, upload-time = "2025-03-19T20:36:09.038Z" }, + { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" }, ] [[package]] name = "poethepoet" -version = "0.33.1" +version = "0.38.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pastel" }, { name = "pyyaml" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cd/1d/ec87271390cc5fafdd5996137331ad3a7ce99b715e4ee68db554d202817f/poethepoet-0.33.1.tar.gz", hash = "sha256:8775e09b64f773278b5483659ff238a708723491efadeedd1c2cbf773558cb4c", size = 62536, upload-time = "2025-03-15T20:38:56.746Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/14/d1f795f314c4bf3ad6d64216e370bdfda73093ed76e979485778b655a7ac/poethepoet-0.38.0.tar.gz", hash = "sha256:aeeb2f0a2cf0d3afa833976eff3ac7b8f5e472ae64171824900d79d3c68163c7", size = 77339, upload-time = "2025-11-23T13:51:28.246Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/34/ea/c476bfec360eb6831ce46df2719f76d1132b9a87da11c302081a9def5fce/poethepoet-0.33.1-py3-none-any.whl", hash = "sha256:b86d80a81b2ca4e4ce8e8f716cc6004a1a1cdead027778bc07d1c26cb3664770", size = 83512, upload-time = "2025-03-15T20:38:55.312Z" }, + { url = "https://files.pythonhosted.org/packages/38/89/2bf7d43ef4b0d60f446933ae9d3649f95c2c45c47b6736d121b602c28361/poethepoet-0.38.0-py3-none-any.whl", hash = "sha256:214bd9fcb348ff3dfd1466579d67e0c02242451a7044aced1a79641adef9cad0", size = 101938, upload-time = "2025-11-23T13:51:26.518Z" }, ] [[package]] name = "pre-commit" -version = "4.2.0" +version = "4.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cfgv" }, @@ -582,14 +749,14 @@ dependencies = [ { name = "pyyaml" }, { name = "virtualenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/08/39/679ca9b26c7bb2999ff122d50faa301e49af82ca9c066ec061cfbc0c6784/pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146", size = 193424, upload-time = "2025-03-18T21:35:20.987Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f4/9b/6a4ffb4ed980519da959e1cf3122fc6cb41211daa58dbae1c73c0e519a37/pre_commit-4.5.0.tar.gz", hash = "sha256:dc5a065e932b19fc1d4c653c6939068fe54325af8e741e74e88db4d28a4dd66b", size = 198428, upload-time = "2025-11-22T21:02:42.304Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707, upload-time = "2025-03-18T21:35:19.343Z" }, + { url = "https://files.pythonhosted.org/packages/5d/c4/b2d28e9d2edf4f1713eb3c29307f1a63f3d67cf09bdda29715a36a68921a/pre_commit-4.5.0-py2.py3-none-any.whl", hash = "sha256:25e2ce09595174d9c97860a95609f9f852c0614ba602de3561e267547f2335e1", size = 226429, upload-time = "2025-11-22T21:02:40.836Z" }, ] [[package]] name = "pydantic" -version = "2.12.4" +version = "2.12.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, @@ -597,9 +764,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/96/ad/a17bc283d7d81837c061c49e3eaa27a45991759a1b7eae1031921c6bd924/pydantic-2.12.4.tar.gz", hash = "sha256:0f8cb9555000a4b5b617f66bfd2566264c4984b27589d3b845685983e8ea85ac", size = 821038, upload-time = "2025-11-05T10:50:08.59Z" } +sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/82/2f/e68750da9b04856e2a7ec56fc6f034a5a79775e9b9a81882252789873798/pydantic-2.12.4-py3-none-any.whl", hash = "sha256:92d3d202a745d46f9be6df459ac5a064fdaa3c1c4cd8adcfa332ccf3c05f871e", size = 463400, upload-time = "2025-11-05T10:50:06.732Z" }, + { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, ] [[package]] @@ -722,74 +889,94 @@ wheels = [ [[package]] name = "pygments" -version = "2.19.1" +version = "2.19.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" }, + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] [[package]] name = "pyyaml" -version = "6.0.2" +version = "6.0.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199, upload-time = "2024-08-06T20:31:40.178Z" }, - { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758, upload-time = "2024-08-06T20:31:42.173Z" }, - { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463, upload-time = "2024-08-06T20:31:44.263Z" }, - { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280, upload-time = "2024-08-06T20:31:50.199Z" }, - { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239, upload-time = "2024-08-06T20:31:52.292Z" }, - { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802, upload-time = "2024-08-06T20:31:53.836Z" }, - { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527, upload-time = "2024-08-06T20:31:55.565Z" }, - { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052, upload-time = "2024-08-06T20:31:56.914Z" }, - { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774, upload-time = "2024-08-06T20:31:58.304Z" }, - { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload-time = "2024-08-06T20:32:03.408Z" }, - { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload-time = "2024-08-06T20:32:04.926Z" }, - { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload-time = "2024-08-06T20:32:06.459Z" }, - { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload-time = "2024-08-06T20:32:08.338Z" }, - { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload-time = "2024-08-06T20:32:14.124Z" }, - { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload-time = "2024-08-06T20:32:16.17Z" }, - { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload-time = "2024-08-06T20:32:18.555Z" }, - { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" }, - { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" }, - { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" }, - { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" }, - { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" }, - { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" }, - { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" }, - { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" }, - { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" }, - { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" }, - { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" }, - { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, - { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, - { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, - { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, - { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, - { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, - { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, - { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, - { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, + { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, ] [[package]] name = "referencing" -version = "0.36.2" +version = "0.37.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, { name = "rpds-py" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744, upload-time = "2025-01-25T08:48:16.138Z" } +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775, upload-time = "2025-01-25T08:48:14.241Z" }, + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, ] [[package]] name = "requests" -version = "2.32.4" +version = "2.32.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -797,9 +984,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, ] [[package]] @@ -813,132 +1000,150 @@ wheels = [ [[package]] name = "rpds-py" -version = "0.24.0" +version = "0.30.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0b/b3/52b213298a0ba7097c7ea96bee95e1947aa84cc816d48cebb539770cdf41/rpds_py-0.24.0.tar.gz", hash = "sha256:772cc1b2cd963e7e17e6cc55fe0371fb9c704d63e44cacec7b9b7f523b78919e", size = 26863, upload-time = "2025-03-26T14:56:01.518Z" } +sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/21/cbc43b220c9deb536b07fbd598c97d463bbb7afb788851891252fc920742/rpds_py-0.24.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:006f4342fe729a368c6df36578d7a348c7c716be1da0a1a0f86e3021f8e98724", size = 377531, upload-time = "2025-03-26T14:52:41.754Z" }, - { url = "https://files.pythonhosted.org/packages/42/15/cc4b09ef160483e49c3aab3b56f3d375eadf19c87c48718fb0147e86a446/rpds_py-0.24.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2d53747da70a4e4b17f559569d5f9506420966083a31c5fbd84e764461c4444b", size = 362273, upload-time = "2025-03-26T14:52:44.341Z" }, - { url = "https://files.pythonhosted.org/packages/8c/a2/67718a188a88dbd5138d959bed6efe1cc7413a4caa8283bd46477ed0d1ad/rpds_py-0.24.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8acd55bd5b071156bae57b555f5d33697998752673b9de554dd82f5b5352727", size = 388111, upload-time = "2025-03-26T14:52:46.944Z" }, - { url = "https://files.pythonhosted.org/packages/e5/e6/cbf1d3163405ad5f4a1a6d23f80245f2204d0c743b18525f34982dec7f4d/rpds_py-0.24.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7e80d375134ddb04231a53800503752093dbb65dad8dabacce2c84cccc78e964", size = 394447, upload-time = "2025-03-26T14:52:48.753Z" }, - { url = "https://files.pythonhosted.org/packages/21/bb/4fe220ccc8a549b38b9e9cec66212dc3385a82a5ee9e37b54411cce4c898/rpds_py-0.24.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60748789e028d2a46fc1c70750454f83c6bdd0d05db50f5ae83e2db500b34da5", size = 448028, upload-time = "2025-03-26T14:52:50.757Z" }, - { url = "https://files.pythonhosted.org/packages/a5/41/d2d6e0fd774818c4cadb94185d30cf3768de1c2a9e0143fc8bc6ce59389e/rpds_py-0.24.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6e1daf5bf6c2be39654beae83ee6b9a12347cb5aced9a29eecf12a2d25fff664", size = 447410, upload-time = "2025-03-26T14:52:52.292Z" }, - { url = "https://files.pythonhosted.org/packages/a7/a7/6d04d438f53d8bb2356bb000bea9cf5c96a9315e405b577117e344cc7404/rpds_py-0.24.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b221c2457d92a1fb3c97bee9095c874144d196f47c038462ae6e4a14436f7bc", size = 389531, upload-time = "2025-03-26T14:52:54.233Z" }, - { url = "https://files.pythonhosted.org/packages/23/be/72e6df39bd7ca5a66799762bf54d8e702483fdad246585af96723109d486/rpds_py-0.24.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:66420986c9afff67ef0c5d1e4cdc2d0e5262f53ad11e4f90e5e22448df485bf0", size = 420099, upload-time = "2025-03-26T14:52:56.135Z" }, - { url = "https://files.pythonhosted.org/packages/8c/c9/ca100cd4688ee0aa266197a5cb9f685231676dd7d573041ca53787b23f4e/rpds_py-0.24.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:43dba99f00f1d37b2a0265a259592d05fcc8e7c19d140fe51c6e6f16faabeb1f", size = 564950, upload-time = "2025-03-26T14:52:57.583Z" }, - { url = "https://files.pythonhosted.org/packages/05/98/908cd95686d33b3ac8ac2e582d7ae38e2c3aa2c0377bf1f5663bafd1ffb2/rpds_py-0.24.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a88c0d17d039333a41d9bf4616bd062f0bd7aa0edeb6cafe00a2fc2a804e944f", size = 591778, upload-time = "2025-03-26T14:52:59.518Z" }, - { url = "https://files.pythonhosted.org/packages/7b/ac/e143726f1dd3215efcb974b50b03bd08a8a1556b404a0a7872af6d197e57/rpds_py-0.24.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc31e13ce212e14a539d430428cd365e74f8b2d534f8bc22dd4c9c55b277b875", size = 560421, upload-time = "2025-03-26T14:53:01.422Z" }, - { url = "https://files.pythonhosted.org/packages/60/28/add1c1d2fcd5aa354f7225d036d4492261759a22d449cff14841ef36a514/rpds_py-0.24.0-cp310-cp310-win32.whl", hash = "sha256:fc2c1e1b00f88317d9de6b2c2b39b012ebbfe35fe5e7bef980fd2a91f6100a07", size = 222089, upload-time = "2025-03-26T14:53:02.859Z" }, - { url = "https://files.pythonhosted.org/packages/b0/ac/81f8066c6de44c507caca488ba336ae30d35d57f61fe10578824d1a70196/rpds_py-0.24.0-cp310-cp310-win_amd64.whl", hash = "sha256:c0145295ca415668420ad142ee42189f78d27af806fcf1f32a18e51d47dd2052", size = 234622, upload-time = "2025-03-26T14:53:04.676Z" }, - { url = "https://files.pythonhosted.org/packages/80/e6/c1458bbfb257448fdb2528071f1f4e19e26798ed5ef6d47d7aab0cb69661/rpds_py-0.24.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:2d3ee4615df36ab8eb16c2507b11e764dcc11fd350bbf4da16d09cda11fcedef", size = 377679, upload-time = "2025-03-26T14:53:06.557Z" }, - { url = "https://files.pythonhosted.org/packages/dd/26/ea4181ef78f58b2c167548c6a833d7dc22408e5b3b181bda9dda440bb92d/rpds_py-0.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e13ae74a8a3a0c2f22f450f773e35f893484fcfacb00bb4344a7e0f4f48e1f97", size = 362571, upload-time = "2025-03-26T14:53:08.439Z" }, - { url = "https://files.pythonhosted.org/packages/56/fa/1ec54dd492c64c280a2249a047fc3369e2789dc474eac20445ebfc72934b/rpds_py-0.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf86f72d705fc2ef776bb7dd9e5fbba79d7e1f3e258bf9377f8204ad0fc1c51e", size = 388012, upload-time = "2025-03-26T14:53:10.314Z" }, - { url = "https://files.pythonhosted.org/packages/3a/be/bad8b0e0f7e58ef4973bb75e91c472a7d51da1977ed43b09989264bf065c/rpds_py-0.24.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c43583ea8517ed2e780a345dd9960896afc1327e8cf3ac8239c167530397440d", size = 394730, upload-time = "2025-03-26T14:53:11.953Z" }, - { url = "https://files.pythonhosted.org/packages/35/56/ab417fc90c21826df048fc16e55316ac40876e4b790104ececcbce813d8f/rpds_py-0.24.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4cd031e63bc5f05bdcda120646a0d32f6d729486d0067f09d79c8db5368f4586", size = 448264, upload-time = "2025-03-26T14:53:13.42Z" }, - { url = "https://files.pythonhosted.org/packages/b6/75/4c63862d5c05408589196c8440a35a14ea4ae337fa70ded1f03638373f06/rpds_py-0.24.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34d90ad8c045df9a4259c47d2e16a3f21fdb396665c94520dbfe8766e62187a4", size = 446813, upload-time = "2025-03-26T14:53:15.036Z" }, - { url = "https://files.pythonhosted.org/packages/e7/0c/91cf17dffa9a38835869797a9f041056091ebba6a53963d3641207e3d467/rpds_py-0.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e838bf2bb0b91ee67bf2b889a1a841e5ecac06dd7a2b1ef4e6151e2ce155c7ae", size = 389438, upload-time = "2025-03-26T14:53:17.037Z" }, - { url = "https://files.pythonhosted.org/packages/1b/b0/60e6c72727c978276e02851819f3986bc40668f115be72c1bc4d922c950f/rpds_py-0.24.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04ecf5c1ff4d589987b4d9882872f80ba13da7d42427234fce8f22efb43133bc", size = 420416, upload-time = "2025-03-26T14:53:18.671Z" }, - { url = "https://files.pythonhosted.org/packages/a1/d7/f46f85b9f863fb59fd3c534b5c874c48bee86b19e93423b9da8784605415/rpds_py-0.24.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:630d3d8ea77eabd6cbcd2ea712e1c5cecb5b558d39547ac988351195db433f6c", size = 565236, upload-time = "2025-03-26T14:53:20.357Z" }, - { url = "https://files.pythonhosted.org/packages/2a/d1/1467620ded6dd70afc45ec822cdf8dfe7139537780d1f3905de143deb6fd/rpds_py-0.24.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ebcb786b9ff30b994d5969213a8430cbb984cdd7ea9fd6df06663194bd3c450c", size = 592016, upload-time = "2025-03-26T14:53:22.216Z" }, - { url = "https://files.pythonhosted.org/packages/5d/13/fb1ded2e6adfaa0c0833106c42feb290973f665300f4facd5bf5d7891d9c/rpds_py-0.24.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:174e46569968ddbbeb8a806d9922f17cd2b524aa753b468f35b97ff9c19cb718", size = 560123, upload-time = "2025-03-26T14:53:23.733Z" }, - { url = "https://files.pythonhosted.org/packages/1e/df/09fc1857ac7cc2eb16465a7199c314cbce7edde53c8ef21d615410d7335b/rpds_py-0.24.0-cp311-cp311-win32.whl", hash = "sha256:5ef877fa3bbfb40b388a5ae1cb00636a624690dcb9a29a65267054c9ea86d88a", size = 222256, upload-time = "2025-03-26T14:53:25.217Z" }, - { url = "https://files.pythonhosted.org/packages/ff/25/939b40bc4d54bf910e5ee60fb5af99262c92458f4948239e8c06b0b750e7/rpds_py-0.24.0-cp311-cp311-win_amd64.whl", hash = "sha256:e274f62cbd274359eff63e5c7e7274c913e8e09620f6a57aae66744b3df046d6", size = 234718, upload-time = "2025-03-26T14:53:26.631Z" }, - { url = "https://files.pythonhosted.org/packages/1a/e0/1c55f4a3be5f1ca1a4fd1f3ff1504a1478c1ed48d84de24574c4fa87e921/rpds_py-0.24.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:d8551e733626afec514b5d15befabea0dd70a343a9f23322860c4f16a9430205", size = 366945, upload-time = "2025-03-26T14:53:28.149Z" }, - { url = "https://files.pythonhosted.org/packages/39/1b/a3501574fbf29118164314dbc800d568b8c1c7b3258b505360e8abb3902c/rpds_py-0.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e374c0ce0ca82e5b67cd61fb964077d40ec177dd2c4eda67dba130de09085c7", size = 351935, upload-time = "2025-03-26T14:53:29.684Z" }, - { url = "https://files.pythonhosted.org/packages/dc/47/77d3d71c55f6a374edde29f1aca0b2e547325ed00a9da820cabbc9497d2b/rpds_py-0.24.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d69d003296df4840bd445a5d15fa5b6ff6ac40496f956a221c4d1f6f7b4bc4d9", size = 390817, upload-time = "2025-03-26T14:53:31.177Z" }, - { url = "https://files.pythonhosted.org/packages/4e/ec/1e336ee27484379e19c7f9cc170f4217c608aee406d3ae3a2e45336bff36/rpds_py-0.24.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8212ff58ac6dfde49946bea57474a386cca3f7706fc72c25b772b9ca4af6b79e", size = 401983, upload-time = "2025-03-26T14:53:33.163Z" }, - { url = "https://files.pythonhosted.org/packages/07/f8/39b65cbc272c635eaea6d393c2ad1ccc81c39eca2db6723a0ca4b2108fce/rpds_py-0.24.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:528927e63a70b4d5f3f5ccc1fa988a35456eb5d15f804d276709c33fc2f19bda", size = 451719, upload-time = "2025-03-26T14:53:34.721Z" }, - { url = "https://files.pythonhosted.org/packages/32/05/05c2b27dd9c30432f31738afed0300659cb9415db0ff7429b05dfb09bbde/rpds_py-0.24.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a824d2c7a703ba6daaca848f9c3d5cb93af0505be505de70e7e66829affd676e", size = 442546, upload-time = "2025-03-26T14:53:36.26Z" }, - { url = "https://files.pythonhosted.org/packages/7d/e0/19383c8b5d509bd741532a47821c3e96acf4543d0832beba41b4434bcc49/rpds_py-0.24.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44d51febb7a114293ffd56c6cf4736cb31cd68c0fddd6aa303ed09ea5a48e029", size = 393695, upload-time = "2025-03-26T14:53:37.728Z" }, - { url = "https://files.pythonhosted.org/packages/9d/15/39f14e96d94981d0275715ae8ea564772237f3fa89bc3c21e24de934f2c7/rpds_py-0.24.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3fab5f4a2c64a8fb64fc13b3d139848817a64d467dd6ed60dcdd6b479e7febc9", size = 427218, upload-time = "2025-03-26T14:53:39.326Z" }, - { url = "https://files.pythonhosted.org/packages/22/b9/12da7124905a680f690da7a9de6f11de770b5e359f5649972f7181c8bf51/rpds_py-0.24.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9be4f99bee42ac107870c61dfdb294d912bf81c3c6d45538aad7aecab468b6b7", size = 568062, upload-time = "2025-03-26T14:53:40.885Z" }, - { url = "https://files.pythonhosted.org/packages/88/17/75229017a2143d915f6f803721a6d721eca24f2659c5718a538afa276b4f/rpds_py-0.24.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:564c96b6076a98215af52f55efa90d8419cc2ef45d99e314fddefe816bc24f91", size = 596262, upload-time = "2025-03-26T14:53:42.544Z" }, - { url = "https://files.pythonhosted.org/packages/aa/64/8e8a1d8bd1b6b638d6acb6d41ab2cec7f2067a5b8b4c9175703875159a7c/rpds_py-0.24.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:75a810b7664c17f24bf2ffd7f92416c00ec84b49bb68e6a0d93e542406336b56", size = 564306, upload-time = "2025-03-26T14:53:44.2Z" }, - { url = "https://files.pythonhosted.org/packages/68/1c/a7eac8d8ed8cb234a9b1064647824c387753343c3fab6ed7c83481ed0be7/rpds_py-0.24.0-cp312-cp312-win32.whl", hash = "sha256:f6016bd950be4dcd047b7475fdf55fb1e1f59fc7403f387be0e8123e4a576d30", size = 224281, upload-time = "2025-03-26T14:53:45.769Z" }, - { url = "https://files.pythonhosted.org/packages/bb/46/b8b5424d1d21f2f2f3f2d468660085318d4f74a8df8289e3dd6ad224d488/rpds_py-0.24.0-cp312-cp312-win_amd64.whl", hash = "sha256:998c01b8e71cf051c28f5d6f1187abbdf5cf45fc0efce5da6c06447cba997034", size = 239719, upload-time = "2025-03-26T14:53:47.187Z" }, - { url = "https://files.pythonhosted.org/packages/9d/c3/3607abc770395bc6d5a00cb66385a5479fb8cd7416ddef90393b17ef4340/rpds_py-0.24.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3d2d8e4508e15fc05b31285c4b00ddf2e0eb94259c2dc896771966a163122a0c", size = 367072, upload-time = "2025-03-26T14:53:48.686Z" }, - { url = "https://files.pythonhosted.org/packages/d8/35/8c7ee0fe465793e3af3298dc5a9f3013bd63e7a69df04ccfded8293a4982/rpds_py-0.24.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0f00c16e089282ad68a3820fd0c831c35d3194b7cdc31d6e469511d9bffc535c", size = 351919, upload-time = "2025-03-26T14:53:50.229Z" }, - { url = "https://files.pythonhosted.org/packages/91/d3/7e1b972501eb5466b9aca46a9c31bcbbdc3ea5a076e9ab33f4438c1d069d/rpds_py-0.24.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:951cc481c0c395c4a08639a469d53b7d4afa252529a085418b82a6b43c45c240", size = 390360, upload-time = "2025-03-26T14:53:51.909Z" }, - { url = "https://files.pythonhosted.org/packages/a2/a8/ccabb50d3c91c26ad01f9b09a6a3b03e4502ce51a33867c38446df9f896b/rpds_py-0.24.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c9ca89938dff18828a328af41ffdf3902405a19f4131c88e22e776a8e228c5a8", size = 400704, upload-time = "2025-03-26T14:53:53.47Z" }, - { url = "https://files.pythonhosted.org/packages/53/ae/5fa5bf0f3bc6ce21b5ea88fc0ecd3a439e7cb09dd5f9ffb3dbe1b6894fc5/rpds_py-0.24.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed0ef550042a8dbcd657dfb284a8ee00f0ba269d3f2286b0493b15a5694f9fe8", size = 450839, upload-time = "2025-03-26T14:53:55.005Z" }, - { url = "https://files.pythonhosted.org/packages/e3/ac/c4e18b36d9938247e2b54f6a03746f3183ca20e1edd7d3654796867f5100/rpds_py-0.24.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b2356688e5d958c4d5cb964af865bea84db29971d3e563fb78e46e20fe1848b", size = 441494, upload-time = "2025-03-26T14:53:57.047Z" }, - { url = "https://files.pythonhosted.org/packages/bf/08/b543969c12a8f44db6c0f08ced009abf8f519191ca6985509e7c44102e3c/rpds_py-0.24.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78884d155fd15d9f64f5d6124b486f3d3f7fd7cd71a78e9670a0f6f6ca06fb2d", size = 393185, upload-time = "2025-03-26T14:53:59.032Z" }, - { url = "https://files.pythonhosted.org/packages/da/7e/f6eb6a7042ce708f9dfc781832a86063cea8a125bbe451d663697b51944f/rpds_py-0.24.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6a4a535013aeeef13c5532f802708cecae8d66c282babb5cd916379b72110cf7", size = 426168, upload-time = "2025-03-26T14:54:00.661Z" }, - { url = "https://files.pythonhosted.org/packages/38/b0/6cd2bb0509ac0b51af4bb138e145b7c4c902bb4b724d6fd143689d6e0383/rpds_py-0.24.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:84e0566f15cf4d769dade9b366b7b87c959be472c92dffb70462dd0844d7cbad", size = 567622, upload-time = "2025-03-26T14:54:02.312Z" }, - { url = "https://files.pythonhosted.org/packages/64/b0/c401f4f077547d98e8b4c2ec6526a80e7cb04f519d416430ec1421ee9e0b/rpds_py-0.24.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:823e74ab6fbaa028ec89615ff6acb409e90ff45580c45920d4dfdddb069f2120", size = 595435, upload-time = "2025-03-26T14:54:04.388Z" }, - { url = "https://files.pythonhosted.org/packages/9f/ec/7993b6e803294c87b61c85bd63e11142ccfb2373cf88a61ec602abcbf9d6/rpds_py-0.24.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c61a2cb0085c8783906b2f8b1f16a7e65777823c7f4d0a6aaffe26dc0d358dd9", size = 563762, upload-time = "2025-03-26T14:54:06.422Z" }, - { url = "https://files.pythonhosted.org/packages/1f/29/4508003204cb2f461dc2b83dd85f8aa2b915bc98fe6046b9d50d4aa05401/rpds_py-0.24.0-cp313-cp313-win32.whl", hash = "sha256:60d9b630c8025b9458a9d114e3af579a2c54bd32df601c4581bd054e85258143", size = 223510, upload-time = "2025-03-26T14:54:08.344Z" }, - { url = "https://files.pythonhosted.org/packages/f9/12/09e048d1814195e01f354155fb772fb0854bd3450b5f5a82224b3a319f0e/rpds_py-0.24.0-cp313-cp313-win_amd64.whl", hash = "sha256:6eea559077d29486c68218178ea946263b87f1c41ae7f996b1f30a983c476a5a", size = 239075, upload-time = "2025-03-26T14:54:09.992Z" }, - { url = "https://files.pythonhosted.org/packages/d2/03/5027cde39bb2408d61e4dd0cf81f815949bb629932a6c8df1701d0257fc4/rpds_py-0.24.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:d09dc82af2d3c17e7dd17120b202a79b578d79f2b5424bda209d9966efeed114", size = 362974, upload-time = "2025-03-26T14:54:11.484Z" }, - { url = "https://files.pythonhosted.org/packages/bf/10/24d374a2131b1ffafb783e436e770e42dfdb74b69a2cd25eba8c8b29d861/rpds_py-0.24.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5fc13b44de6419d1e7a7e592a4885b323fbc2f46e1f22151e3a8ed3b8b920405", size = 348730, upload-time = "2025-03-26T14:54:13.145Z" }, - { url = "https://files.pythonhosted.org/packages/7a/d1/1ef88d0516d46cd8df12e5916966dbf716d5ec79b265eda56ba1b173398c/rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c347a20d79cedc0a7bd51c4d4b7dbc613ca4e65a756b5c3e57ec84bd43505b47", size = 387627, upload-time = "2025-03-26T14:54:14.711Z" }, - { url = "https://files.pythonhosted.org/packages/4e/35/07339051b8b901ecefd449ebf8e5522e92bcb95e1078818cbfd9db8e573c/rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20f2712bd1cc26a3cc16c5a1bfee9ed1abc33d4cdf1aabd297fe0eb724df4272", size = 394094, upload-time = "2025-03-26T14:54:16.961Z" }, - { url = "https://files.pythonhosted.org/packages/dc/62/ee89ece19e0ba322b08734e95441952062391065c157bbd4f8802316b4f1/rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aad911555286884be1e427ef0dc0ba3929e6821cbeca2194b13dc415a462c7fd", size = 449639, upload-time = "2025-03-26T14:54:19.047Z" }, - { url = "https://files.pythonhosted.org/packages/15/24/b30e9f9e71baa0b9dada3a4ab43d567c6b04a36d1cb531045f7a8a0a7439/rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0aeb3329c1721c43c58cae274d7d2ca85c1690d89485d9c63a006cb79a85771a", size = 438584, upload-time = "2025-03-26T14:54:20.722Z" }, - { url = "https://files.pythonhosted.org/packages/28/d9/49f7b8f3b4147db13961e19d5e30077cd0854ccc08487026d2cb2142aa4a/rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a0f156e9509cee987283abd2296ec816225145a13ed0391df8f71bf1d789e2d", size = 391047, upload-time = "2025-03-26T14:54:22.426Z" }, - { url = "https://files.pythonhosted.org/packages/49/b0/e66918d0972c33a259ba3cd7b7ff10ed8bd91dbcfcbec6367b21f026db75/rpds_py-0.24.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:aa6800adc8204ce898c8a424303969b7aa6a5e4ad2789c13f8648739830323b7", size = 418085, upload-time = "2025-03-26T14:54:23.949Z" }, - { url = "https://files.pythonhosted.org/packages/e1/6b/99ed7ea0a94c7ae5520a21be77a82306aac9e4e715d4435076ead07d05c6/rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a18fc371e900a21d7392517c6f60fe859e802547309e94313cd8181ad9db004d", size = 564498, upload-time = "2025-03-26T14:54:25.573Z" }, - { url = "https://files.pythonhosted.org/packages/28/26/1cacfee6b800e6fb5f91acecc2e52f17dbf8b0796a7c984b4568b6d70e38/rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9168764133fd919f8dcca2ead66de0105f4ef5659cbb4fa044f7014bed9a1797", size = 590202, upload-time = "2025-03-26T14:54:27.569Z" }, - { url = "https://files.pythonhosted.org/packages/a9/9e/57bd2f9fba04a37cef673f9a66b11ca8c43ccdd50d386c455cd4380fe461/rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5f6e3cec44ba05ee5cbdebe92d052f69b63ae792e7d05f1020ac5e964394080c", size = 561771, upload-time = "2025-03-26T14:54:29.615Z" }, - { url = "https://files.pythonhosted.org/packages/9f/cf/b719120f375ab970d1c297dbf8de1e3c9edd26fe92c0ed7178dd94b45992/rpds_py-0.24.0-cp313-cp313t-win32.whl", hash = "sha256:8ebc7e65ca4b111d928b669713865f021b7773350eeac4a31d3e70144297baba", size = 221195, upload-time = "2025-03-26T14:54:31.581Z" }, - { url = "https://files.pythonhosted.org/packages/2d/e5/22865285789f3412ad0c3d7ec4dc0a3e86483b794be8a5d9ed5a19390900/rpds_py-0.24.0-cp313-cp313t-win_amd64.whl", hash = "sha256:675269d407a257b8c00a6b58205b72eec8231656506c56fd429d924ca00bb350", size = 237354, upload-time = "2025-03-26T14:54:33.199Z" }, - { url = "https://files.pythonhosted.org/packages/99/48/11dae46d0c7f7e156ca0971a83f89c510af0316cd5d42c771b7cef945f0c/rpds_py-0.24.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:619ca56a5468f933d940e1bf431c6f4e13bef8e688698b067ae68eb4f9b30e3a", size = 378224, upload-time = "2025-03-26T14:54:58.78Z" }, - { url = "https://files.pythonhosted.org/packages/33/18/e8398d255369e35d312942f3bb8ecaff013c44968904891be2ab63b3aa94/rpds_py-0.24.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:4b28e5122829181de1898c2c97f81c0b3246d49f585f22743a1246420bb8d399", size = 363252, upload-time = "2025-03-26T14:55:00.359Z" }, - { url = "https://files.pythonhosted.org/packages/17/39/dd73ba691f4df3e6834bf982de214086ac3359ab3ac035adfb30041570e3/rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e5ab32cf9eb3647450bc74eb201b27c185d3857276162c101c0f8c6374e098", size = 388871, upload-time = "2025-03-26T14:55:02.253Z" }, - { url = "https://files.pythonhosted.org/packages/2f/2e/da0530b25cabd0feca2a759b899d2df325069a94281eeea8ac44c6cfeff7/rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:208b3a70a98cf3710e97cabdc308a51cd4f28aa6e7bb11de3d56cd8b74bab98d", size = 394766, upload-time = "2025-03-26T14:55:04.05Z" }, - { url = "https://files.pythonhosted.org/packages/4c/ee/dd1c5040a431beb40fad4a5d7868acf343444b0bc43e627c71df2506538b/rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbc4362e06f950c62cad3d4abf1191021b2ffaf0b31ac230fbf0526453eee75e", size = 448712, upload-time = "2025-03-26T14:55:06.03Z" }, - { url = "https://files.pythonhosted.org/packages/f5/ec/6b93ffbb686be948e4d91ec76f4e6757f8551034b2a8176dd848103a1e34/rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ebea2821cdb5f9fef44933617be76185b80150632736f3d76e54829ab4a3b4d1", size = 447150, upload-time = "2025-03-26T14:55:08.098Z" }, - { url = "https://files.pythonhosted.org/packages/55/d5/a1c23760adad85b432df074ced6f910dd28f222b8c60aeace5aeb9a6654e/rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a4df06c35465ef4d81799999bba810c68d29972bf1c31db61bfdb81dd9d5bb", size = 390662, upload-time = "2025-03-26T14:55:09.781Z" }, - { url = "https://files.pythonhosted.org/packages/a5/f3/419cb1f9bfbd3a48c256528c156e00f3349e3edce5ad50cbc141e71f66a5/rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d3aa13bdf38630da298f2e0d77aca967b200b8cc1473ea05248f6c5e9c9bdb44", size = 421351, upload-time = "2025-03-26T14:55:11.477Z" }, - { url = "https://files.pythonhosted.org/packages/98/8e/62d1a55078e5ede0b3b09f35e751fa35924a34a0d44d7c760743383cd54a/rpds_py-0.24.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:041f00419e1da7a03c46042453598479f45be3d787eb837af382bfc169c0db33", size = 566074, upload-time = "2025-03-26T14:55:13.386Z" }, - { url = "https://files.pythonhosted.org/packages/fc/69/b7d1003166d78685da032b3c4ff1599fa536a3cfe6e5ce2da87c9c431906/rpds_py-0.24.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:d8754d872a5dfc3c5bf9c0e059e8107451364a30d9fd50f1f1a85c4fb9481164", size = 592398, upload-time = "2025-03-26T14:55:15.202Z" }, - { url = "https://files.pythonhosted.org/packages/ea/a8/1c98bc99338c37faadd28dd667d336df7409d77b4da999506a0b6b1c0aa2/rpds_py-0.24.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:896c41007931217a343eff197c34513c154267636c8056fb409eafd494c3dcdc", size = 561114, upload-time = "2025-03-26T14:55:17.072Z" }, - { url = "https://files.pythonhosted.org/packages/2b/41/65c91443685a4c7b5f1dd271beadc4a3e063d57c3269221548dd9416e15c/rpds_py-0.24.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:92558d37d872e808944c3c96d0423b8604879a3d1c86fdad508d7ed91ea547d5", size = 235548, upload-time = "2025-03-26T14:55:18.707Z" }, - { url = "https://files.pythonhosted.org/packages/65/53/40bcc246a8354530d51a26d2b5b9afd1deacfb0d79e67295cc74df362f52/rpds_py-0.24.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f9e0057a509e096e47c87f753136c9b10d7a91842d8042c2ee6866899a717c0d", size = 378386, upload-time = "2025-03-26T14:55:20.381Z" }, - { url = "https://files.pythonhosted.org/packages/80/b0/5ea97dd2f53e3618560aa1f9674e896e63dff95a9b796879a201bc4c1f00/rpds_py-0.24.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6e109a454412ab82979c5b1b3aee0604eca4bbf9a02693bb9df027af2bfa91a", size = 363440, upload-time = "2025-03-26T14:55:22.121Z" }, - { url = "https://files.pythonhosted.org/packages/57/9d/259b6eada6f747cdd60c9a5eb3efab15f6704c182547149926c38e5bd0d5/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc1c892b1ec1f8cbd5da8de287577b455e388d9c328ad592eabbdcb6fc93bee5", size = 388816, upload-time = "2025-03-26T14:55:23.737Z" }, - { url = "https://files.pythonhosted.org/packages/94/c1/faafc7183712f89f4b7620c3c15979ada13df137d35ef3011ae83e93b005/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c39438c55983d48f4bb3487734d040e22dad200dab22c41e331cee145e7a50d", size = 395058, upload-time = "2025-03-26T14:55:25.468Z" }, - { url = "https://files.pythonhosted.org/packages/6c/96/d7fa9d2a7b7604a61da201cc0306a355006254942093779d7121c64700ce/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d7e8ce990ae17dda686f7e82fd41a055c668e13ddcf058e7fb5e9da20b57793", size = 448692, upload-time = "2025-03-26T14:55:27.535Z" }, - { url = "https://files.pythonhosted.org/packages/96/37/a3146c6eebc65d6d8c96cc5ffdcdb6af2987412c789004213227fbe52467/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9ea7f4174d2e4194289cb0c4e172d83e79a6404297ff95f2875cf9ac9bced8ba", size = 446462, upload-time = "2025-03-26T14:55:29.299Z" }, - { url = "https://files.pythonhosted.org/packages/1f/13/6481dfd9ac7de43acdaaa416e3a7da40bc4bb8f5c6ca85e794100aa54596/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb2954155bb8f63bb19d56d80e5e5320b61d71084617ed89efedb861a684baea", size = 390460, upload-time = "2025-03-26T14:55:31.017Z" }, - { url = "https://files.pythonhosted.org/packages/61/e1/37e36bce65e109543cc4ff8d23206908649023549604fa2e7fbeba5342f7/rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04f2b712a2206e13800a8136b07aaedc23af3facab84918e7aa89e4be0260032", size = 421609, upload-time = "2025-03-26T14:55:32.84Z" }, - { url = "https://files.pythonhosted.org/packages/20/dd/1f1a923d6cd798b8582176aca8a0784676f1a0449fb6f07fce6ac1cdbfb6/rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:eda5c1e2a715a4cbbca2d6d304988460942551e4e5e3b7457b50943cd741626d", size = 565818, upload-time = "2025-03-26T14:55:34.538Z" }, - { url = "https://files.pythonhosted.org/packages/56/ec/d8da6df6a1eb3a418944a17b1cb38dd430b9e5a2e972eafd2b06f10c7c46/rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:9abc80fe8c1f87218db116016de575a7998ab1629078c90840e8d11ab423ee25", size = 592627, upload-time = "2025-03-26T14:55:36.26Z" }, - { url = "https://files.pythonhosted.org/packages/b3/14/c492b9c7d5dd133e13f211ddea6bb9870f99e4f73932f11aa00bc09a9be9/rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6a727fd083009bc83eb83d6950f0c32b3c94c8b80a9b667c87f4bd1274ca30ba", size = 560885, upload-time = "2025-03-26T14:55:38Z" }, + { url = "https://files.pythonhosted.org/packages/06/0c/0c411a0ec64ccb6d104dcabe0e713e05e153a9a2c3c2bd2b32ce412166fe/rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288", size = 370490, upload-time = "2025-11-30T20:21:33.256Z" }, + { url = "https://files.pythonhosted.org/packages/19/6a/4ba3d0fb7297ebae71171822554abe48d7cab29c28b8f9f2c04b79988c05/rpds_py-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00", size = 359751, upload-time = "2025-11-30T20:21:34.591Z" }, + { url = "https://files.pythonhosted.org/packages/cd/7c/e4933565ef7f7a0818985d87c15d9d273f1a649afa6a52ea35ad011195ea/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389a2d49eded1896c3d48b0136ead37c48e221b391c052fba3f4055c367f60a6", size = 389696, upload-time = "2025-11-30T20:21:36.122Z" }, + { url = "https://files.pythonhosted.org/packages/5e/01/6271a2511ad0815f00f7ed4390cf2567bec1d4b1da39e2c27a41e6e3b4de/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:32c8528634e1bf7121f3de08fa85b138f4e0dc47657866630611b03967f041d7", size = 403136, upload-time = "2025-11-30T20:21:37.728Z" }, + { url = "https://files.pythonhosted.org/packages/55/64/c857eb7cd7541e9b4eee9d49c196e833128a55b89a9850a9c9ac33ccf897/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f207f69853edd6f6700b86efb84999651baf3789e78a466431df1331608e5324", size = 524699, upload-time = "2025-11-30T20:21:38.92Z" }, + { url = "https://files.pythonhosted.org/packages/9c/ed/94816543404078af9ab26159c44f9e98e20fe47e2126d5d32c9d9948d10a/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:67b02ec25ba7a9e8fa74c63b6ca44cf5707f2fbfadae3ee8e7494297d56aa9df", size = 412022, upload-time = "2025-11-30T20:21:40.407Z" }, + { url = "https://files.pythonhosted.org/packages/61/b5/707f6cf0066a6412aacc11d17920ea2e19e5b2f04081c64526eb35b5c6e7/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0e95f6819a19965ff420f65578bacb0b00f251fefe2c8b23347c37174271f3", size = 390522, upload-time = "2025-11-30T20:21:42.17Z" }, + { url = "https://files.pythonhosted.org/packages/13/4e/57a85fda37a229ff4226f8cbcf09f2a455d1ed20e802ce5b2b4a7f5ed053/rpds_py-0.30.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:a452763cc5198f2f98898eb98f7569649fe5da666c2dc6b5ddb10fde5a574221", size = 404579, upload-time = "2025-11-30T20:21:43.769Z" }, + { url = "https://files.pythonhosted.org/packages/f9/da/c9339293513ec680a721e0e16bf2bac3db6e5d7e922488de471308349bba/rpds_py-0.30.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0b65193a413ccc930671c55153a03ee57cecb49e6227204b04fae512eb657a7", size = 421305, upload-time = "2025-11-30T20:21:44.994Z" }, + { url = "https://files.pythonhosted.org/packages/f9/be/522cb84751114f4ad9d822ff5a1aa3c98006341895d5f084779b99596e5c/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:858738e9c32147f78b3ac24dc0edb6610000e56dc0f700fd5f651d0a0f0eb9ff", size = 572503, upload-time = "2025-11-30T20:21:46.91Z" }, + { url = "https://files.pythonhosted.org/packages/a2/9b/de879f7e7ceddc973ea6e4629e9b380213a6938a249e94b0cdbcc325bb66/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:da279aa314f00acbb803da1e76fa18666778e8a8f83484fba94526da5de2cba7", size = 598322, upload-time = "2025-11-30T20:21:48.709Z" }, + { url = "https://files.pythonhosted.org/packages/48/ac/f01fc22efec3f37d8a914fc1b2fb9bcafd56a299edbe96406f3053edea5a/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7c64d38fb49b6cdeda16ab49e35fe0da2e1e9b34bc38bd78386530f218b37139", size = 560792, upload-time = "2025-11-30T20:21:50.024Z" }, + { url = "https://files.pythonhosted.org/packages/e2/da/4e2b19d0f131f35b6146425f846563d0ce036763e38913d917187307a671/rpds_py-0.30.0-cp310-cp310-win32.whl", hash = "sha256:6de2a32a1665b93233cde140ff8b3467bdb9e2af2b91079f0333a0974d12d464", size = 221901, upload-time = "2025-11-30T20:21:51.32Z" }, + { url = "https://files.pythonhosted.org/packages/96/cb/156d7a5cf4f78a7cc571465d8aec7a3c447c94f6749c5123f08438bcf7bc/rpds_py-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:1726859cd0de969f88dc8673bdd954185b9104e05806be64bcd87badbe313169", size = 235823, upload-time = "2025-11-30T20:21:52.505Z" }, + { url = "https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425", size = 370157, upload-time = "2025-11-30T20:21:53.789Z" }, + { url = "https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d", size = 359676, upload-time = "2025-11-30T20:21:55.475Z" }, + { url = "https://files.pythonhosted.org/packages/84/86/04dbba1b087227747d64d80c3b74df946b986c57af0a9f0c98726d4d7a3b/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4", size = 389938, upload-time = "2025-11-30T20:21:57.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/bb/1463f0b1722b7f45431bdd468301991d1328b16cffe0b1c2918eba2c4eee/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f", size = 402932, upload-time = "2025-11-30T20:21:58.47Z" }, + { url = "https://files.pythonhosted.org/packages/99/ee/2520700a5c1f2d76631f948b0736cdf9b0acb25abd0ca8e889b5c62ac2e3/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4", size = 525830, upload-time = "2025-11-30T20:21:59.699Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ad/bd0331f740f5705cc555a5e17fdf334671262160270962e69a2bdef3bf76/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97", size = 412033, upload-time = "2025-11-30T20:22:00.991Z" }, + { url = "https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89", size = 390828, upload-time = "2025-11-30T20:22:02.723Z" }, + { url = "https://files.pythonhosted.org/packages/ab/2b/d88bb33294e3e0c76bc8f351a3721212713629ffca1700fa94979cb3eae8/rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d", size = 404683, upload-time = "2025-11-30T20:22:04.367Z" }, + { url = "https://files.pythonhosted.org/packages/50/32/c759a8d42bcb5289c1fac697cd92f6fe01a018dd937e62ae77e0e7f15702/rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038", size = 421583, upload-time = "2025-11-30T20:22:05.814Z" }, + { url = "https://files.pythonhosted.org/packages/2b/81/e729761dbd55ddf5d84ec4ff1f47857f4374b0f19bdabfcf929164da3e24/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7", size = 572496, upload-time = "2025-11-30T20:22:07.713Z" }, + { url = "https://files.pythonhosted.org/packages/14/f6/69066a924c3557c9c30baa6ec3a0aa07526305684c6f86c696b08860726c/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed", size = 598669, upload-time = "2025-11-30T20:22:09.312Z" }, + { url = "https://files.pythonhosted.org/packages/5f/48/905896b1eb8a05630d20333d1d8ffd162394127b74ce0b0784ae04498d32/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85", size = 561011, upload-time = "2025-11-30T20:22:11.309Z" }, + { url = "https://files.pythonhosted.org/packages/22/16/cd3027c7e279d22e5eb431dd3c0fbc677bed58797fe7581e148f3f68818b/rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c", size = 221406, upload-time = "2025-11-30T20:22:13.101Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825", size = 236024, upload-time = "2025-11-30T20:22:14.853Z" }, + { url = "https://files.pythonhosted.org/packages/14/a6/364bba985e4c13658edb156640608f2c9e1d3ea3c81b27aa9d889fff0e31/rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229", size = 229069, upload-time = "2025-11-30T20:22:16.577Z" }, + { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, + { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, + { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, + { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, + { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, + { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, + { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, + { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, + { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, + { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, + { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, + { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, + { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, + { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, + { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, + { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, + { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, + { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, + { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, + { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, + { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, + { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, + { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, + { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, + { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, + { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, + { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, + { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, + { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, + { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, + { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, + { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, + { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" }, + { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" }, + { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" }, + { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" }, + { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" }, + { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" }, + { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" }, + { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" }, + { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" }, + { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" }, + { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" }, + { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" }, + { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" }, + { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" }, + { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" }, + { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" }, + { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" }, + { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" }, + { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" }, + { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" }, + { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, + { url = "https://files.pythonhosted.org/packages/69/71/3f34339ee70521864411f8b6992e7ab13ac30d8e4e3309e07c7361767d91/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58", size = 372292, upload-time = "2025-11-30T20:24:16.537Z" }, + { url = "https://files.pythonhosted.org/packages/57/09/f183df9b8f2d66720d2ef71075c59f7e1b336bec7ee4c48f0a2b06857653/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a", size = 362128, upload-time = "2025-11-30T20:24:18.086Z" }, + { url = "https://files.pythonhosted.org/packages/7a/68/5c2594e937253457342e078f0cc1ded3dd7b2ad59afdbf2d354869110a02/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb", size = 391542, upload-time = "2025-11-30T20:24:20.092Z" }, + { url = "https://files.pythonhosted.org/packages/49/5c/31ef1afd70b4b4fbdb2800249f34c57c64beb687495b10aec0365f53dfc4/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c", size = 404004, upload-time = "2025-11-30T20:24:22.231Z" }, + { url = "https://files.pythonhosted.org/packages/e3/63/0cfbea38d05756f3440ce6534d51a491d26176ac045e2707adc99bb6e60a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3", size = 527063, upload-time = "2025-11-30T20:24:24.302Z" }, + { url = "https://files.pythonhosted.org/packages/42/e6/01e1f72a2456678b0f618fc9a1a13f882061690893c192fcad9f2926553a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5", size = 413099, upload-time = "2025-11-30T20:24:25.916Z" }, + { url = "https://files.pythonhosted.org/packages/b8/25/8df56677f209003dcbb180765520c544525e3ef21ea72279c98b9aa7c7fb/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738", size = 392177, upload-time = "2025-11-30T20:24:27.834Z" }, + { url = "https://files.pythonhosted.org/packages/4a/b4/0a771378c5f16f8115f796d1f437950158679bcd2a7c68cf251cfb00ed5b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f", size = 406015, upload-time = "2025-11-30T20:24:29.457Z" }, + { url = "https://files.pythonhosted.org/packages/36/d8/456dbba0af75049dc6f63ff295a2f92766b9d521fa00de67a2bd6427d57a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877", size = 423736, upload-time = "2025-11-30T20:24:31.22Z" }, + { url = "https://files.pythonhosted.org/packages/13/64/b4d76f227d5c45a7e0b796c674fd81b0a6c4fbd48dc29271857d8219571c/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a", size = 573981, upload-time = "2025-11-30T20:24:32.934Z" }, + { url = "https://files.pythonhosted.org/packages/20/91/092bacadeda3edf92bf743cc96a7be133e13a39cdbfd7b5082e7ab638406/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4", size = 599782, upload-time = "2025-11-30T20:24:35.169Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191, upload-time = "2025-11-30T20:24:36.853Z" }, ] [[package]] name = "ruff" -version = "0.11.4" +version = "0.14.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e8/5b/3ae20f89777115944e89c2d8c2e795dcc5b9e04052f76d5347e35e0da66e/ruff-0.11.4.tar.gz", hash = "sha256:f45bd2fb1a56a5a85fae3b95add03fb185a0b30cf47f5edc92aa0355ca1d7407", size = 3933063, upload-time = "2025-04-04T18:24:52.197Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/d9/f7a0c4b3a2bf2556cd5d99b05372c29980249ef71e8e32669ba77428c82c/ruff-0.14.8.tar.gz", hash = "sha256:774ed0dd87d6ce925e3b8496feb3a00ac564bea52b9feb551ecd17e0a23d1eed", size = 5765385, upload-time = "2025-12-04T15:06:17.669Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/db/baee59ac88f57527fcbaad3a7b309994e42329c6bc4d4d2b681a3d7b5426/ruff-0.11.4-py3-none-linux_armv6l.whl", hash = "sha256:d9f4a761ecbde448a2d3e12fb398647c7f0bf526dbc354a643ec505965824ed2", size = 10106493, upload-time = "2025-04-04T18:23:56.751Z" }, - { url = "https://files.pythonhosted.org/packages/c1/d6/9a0962cbb347f4ff98b33d699bf1193ff04ca93bed4b4222fd881b502154/ruff-0.11.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8c1747d903447d45ca3d40c794d1a56458c51e5cc1bc77b7b64bd2cf0b1626cc", size = 10876382, upload-time = "2025-04-04T18:24:02.391Z" }, - { url = "https://files.pythonhosted.org/packages/3a/8f/62bab0c7d7e1ae3707b69b157701b41c1ccab8f83e8501734d12ea8a839f/ruff-0.11.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:51a6494209cacca79e121e9b244dc30d3414dac8cc5afb93f852173a2ecfc906", size = 10237050, upload-time = "2025-04-04T18:24:05.387Z" }, - { url = "https://files.pythonhosted.org/packages/09/96/e296965ae9705af19c265d4d441958ed65c0c58fc4ec340c27cc9d2a1f5b/ruff-0.11.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f171605f65f4fc49c87f41b456e882cd0c89e4ac9d58e149a2b07930e1d466f", size = 10424984, upload-time = "2025-04-04T18:24:08.134Z" }, - { url = "https://files.pythonhosted.org/packages/e5/56/644595eb57d855afed6e54b852e2df8cd5ca94c78043b2f29bdfb29882d5/ruff-0.11.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ebf99ea9af918878e6ce42098981fc8c1db3850fef2f1ada69fb1dcdb0f8e79e", size = 9957438, upload-time = "2025-04-04T18:24:11.061Z" }, - { url = "https://files.pythonhosted.org/packages/86/83/9d3f3bed0118aef3e871ded9e5687fb8c5776bde233427fd9ce0a45db2d4/ruff-0.11.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edad2eac42279df12e176564a23fc6f4aaeeb09abba840627780b1bb11a9d223", size = 11547282, upload-time = "2025-04-04T18:24:13.739Z" }, - { url = "https://files.pythonhosted.org/packages/40/e6/0c6e4f5ae72fac5ccb44d72c0111f294a5c2c8cc5024afcb38e6bda5f4b3/ruff-0.11.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f103a848be9ff379fc19b5d656c1f911d0a0b4e3e0424f9532ececf319a4296e", size = 12182020, upload-time = "2025-04-04T18:24:16.799Z" }, - { url = "https://files.pythonhosted.org/packages/b5/92/4aed0e460aeb1df5ea0c2fbe8d04f9725cccdb25d8da09a0d3f5b8764bf8/ruff-0.11.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:193e6fac6eb60cc97b9f728e953c21cc38a20077ed64f912e9d62b97487f3f2d", size = 11679154, upload-time = "2025-04-04T18:24:19.797Z" }, - { url = "https://files.pythonhosted.org/packages/1b/d3/7316aa2609f2c592038e2543483eafbc62a0e1a6a6965178e284808c095c/ruff-0.11.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7af4e5f69b7c138be8dcffa5b4a061bf6ba6a3301f632a6bce25d45daff9bc99", size = 13905985, upload-time = "2025-04-04T18:24:24.542Z" }, - { url = "https://files.pythonhosted.org/packages/63/80/734d3d17546e47ff99871f44ea7540ad2bbd7a480ed197fe8a1c8a261075/ruff-0.11.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:126b1bf13154aa18ae2d6c3c5efe144ec14b97c60844cfa6eb960c2a05188222", size = 11348343, upload-time = "2025-04-04T18:24:27.742Z" }, - { url = "https://files.pythonhosted.org/packages/04/7b/70fc7f09a0161dce9613a4671d198f609e653d6f4ff9eee14d64c4c240fb/ruff-0.11.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e8806daaf9dfa881a0ed603f8a0e364e4f11b6ed461b56cae2b1c0cab0645304", size = 10308487, upload-time = "2025-04-04T18:24:30.59Z" }, - { url = "https://files.pythonhosted.org/packages/1a/22/1cdd62dabd678d75842bf4944fd889cf794dc9e58c18cc547f9eb28f95ed/ruff-0.11.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5d94bb1cc2fc94a769b0eb975344f1b1f3d294da1da9ddbb5a77665feb3a3019", size = 9929091, upload-time = "2025-04-04T18:24:33.24Z" }, - { url = "https://files.pythonhosted.org/packages/9f/20/40e0563506332313148e783bbc1e4276d657962cc370657b2fff20e6e058/ruff-0.11.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:995071203d0fe2183fc7a268766fd7603afb9996785f086b0d76edee8755c896", size = 10924659, upload-time = "2025-04-04T18:24:36.728Z" }, - { url = "https://files.pythonhosted.org/packages/b5/41/eef9b7aac8819d9e942f617f9db296f13d2c4576806d604aba8db5a753f1/ruff-0.11.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7a37ca937e307ea18156e775a6ac6e02f34b99e8c23fe63c1996185a4efe0751", size = 11428160, upload-time = "2025-04-04T18:24:40.08Z" }, - { url = "https://files.pythonhosted.org/packages/ff/61/c488943414fb2b8754c02f3879de003e26efdd20f38167ded3fb3fc1cda3/ruff-0.11.4-py3-none-win32.whl", hash = "sha256:0e9365a7dff9b93af933dab8aebce53b72d8f815e131796268709890b4a83270", size = 10311496, upload-time = "2025-04-04T18:24:42.94Z" }, - { url = "https://files.pythonhosted.org/packages/b6/2b/2a1c8deb5f5dfa3871eb7daa41492c4d2b2824a74d2b38e788617612a66d/ruff-0.11.4-py3-none-win_amd64.whl", hash = "sha256:5a9fa1c69c7815e39fcfb3646bbfd7f528fa8e2d4bebdcf4c2bd0fa037a255fb", size = 11399146, upload-time = "2025-04-04T18:24:45.651Z" }, - { url = "https://files.pythonhosted.org/packages/4f/03/3aec4846226d54a37822e4c7ea39489e4abd6f88388fba74e3d4abe77300/ruff-0.11.4-py3-none-win_arm64.whl", hash = "sha256:d435db6b9b93d02934cf61ef332e66af82da6d8c69aefdea5994c89997c7a0fc", size = 10450306, upload-time = "2025-04-04T18:24:49.603Z" }, -] - -[[package]] -name = "sniffio" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, + { url = "https://files.pythonhosted.org/packages/48/b8/9537b52010134b1d2b72870cc3f92d5fb759394094741b09ceccae183fbe/ruff-0.14.8-py3-none-linux_armv6l.whl", hash = "sha256:ec071e9c82eca417f6111fd39f7043acb53cd3fde9b1f95bbed745962e345afb", size = 13441540, upload-time = "2025-12-04T15:06:14.896Z" }, + { url = "https://files.pythonhosted.org/packages/24/00/99031684efb025829713682012b6dd37279b1f695ed1b01725f85fd94b38/ruff-0.14.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8cdb162a7159f4ca36ce980a18c43d8f036966e7f73f866ac8f493b75e0c27e9", size = 13669384, upload-time = "2025-12-04T15:06:51.809Z" }, + { url = "https://files.pythonhosted.org/packages/72/64/3eb5949169fc19c50c04f28ece2c189d3b6edd57e5b533649dae6ca484fe/ruff-0.14.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2e2fcbefe91f9fad0916850edf0854530c15bd1926b6b779de47e9ab619ea38f", size = 12806917, upload-time = "2025-12-04T15:06:08.925Z" }, + { url = "https://files.pythonhosted.org/packages/c4/08/5250babb0b1b11910f470370ec0cbc67470231f7cdc033cee57d4976f941/ruff-0.14.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9d70721066a296f45786ec31916dc287b44040f553da21564de0ab4d45a869b", size = 13256112, upload-time = "2025-12-04T15:06:23.498Z" }, + { url = "https://files.pythonhosted.org/packages/78/4c/6c588e97a8e8c2d4b522c31a579e1df2b4d003eddfbe23d1f262b1a431ff/ruff-0.14.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2c87e09b3cd9d126fc67a9ecd3b5b1d3ded2b9c7fce3f16e315346b9d05cfb52", size = 13227559, upload-time = "2025-12-04T15:06:33.432Z" }, + { url = "https://files.pythonhosted.org/packages/23/ce/5f78cea13eda8eceac71b5f6fa6e9223df9b87bb2c1891c166d1f0dce9f1/ruff-0.14.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d62cb310c4fbcb9ee4ac023fe17f984ae1e12b8a4a02e3d21489f9a2a5f730c", size = 13896379, upload-time = "2025-12-04T15:06:02.687Z" }, + { url = "https://files.pythonhosted.org/packages/cf/79/13de4517c4dadce9218a20035b21212a4c180e009507731f0d3b3f5df85a/ruff-0.14.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1af35c2d62633d4da0521178e8a2641c636d2a7153da0bac1b30cfd4ccd91344", size = 15372786, upload-time = "2025-12-04T15:06:29.828Z" }, + { url = "https://files.pythonhosted.org/packages/00/06/33df72b3bb42be8a1c3815fd4fae83fa2945fc725a25d87ba3e42d1cc108/ruff-0.14.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:25add4575ffecc53d60eed3f24b1e934493631b48ebbc6ebaf9d8517924aca4b", size = 14990029, upload-time = "2025-12-04T15:06:36.812Z" }, + { url = "https://files.pythonhosted.org/packages/64/61/0f34927bd90925880394de0e081ce1afab66d7b3525336f5771dcf0cb46c/ruff-0.14.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4c943d847b7f02f7db4201a0600ea7d244d8a404fbb639b439e987edcf2baf9a", size = 14407037, upload-time = "2025-12-04T15:06:39.979Z" }, + { url = "https://files.pythonhosted.org/packages/96/bc/058fe0aefc0fbf0d19614cb6d1a3e2c048f7dc77ca64957f33b12cfdc5ef/ruff-0.14.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb6e8bf7b4f627548daa1b69283dac5a296bfe9ce856703b03130732e20ddfe2", size = 14102390, upload-time = "2025-12-04T15:06:46.372Z" }, + { url = "https://files.pythonhosted.org/packages/af/a4/e4f77b02b804546f4c17e8b37a524c27012dd6ff05855d2243b49a7d3cb9/ruff-0.14.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:7aaf2974f378e6b01d1e257c6948207aec6a9b5ba53fab23d0182efb887a0e4a", size = 14230793, upload-time = "2025-12-04T15:06:20.497Z" }, + { url = "https://files.pythonhosted.org/packages/3f/52/bb8c02373f79552e8d087cedaffad76b8892033d2876c2498a2582f09dcf/ruff-0.14.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e5758ca513c43ad8a4ef13f0f081f80f08008f410790f3611a21a92421ab045b", size = 13160039, upload-time = "2025-12-04T15:06:49.06Z" }, + { url = "https://files.pythonhosted.org/packages/1f/ad/b69d6962e477842e25c0b11622548df746290cc6d76f9e0f4ed7456c2c31/ruff-0.14.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f74f7ba163b6e85a8d81a590363bf71618847e5078d90827749bfda1d88c9cdf", size = 13205158, upload-time = "2025-12-04T15:06:54.574Z" }, + { url = "https://files.pythonhosted.org/packages/06/63/54f23da1315c0b3dfc1bc03fbc34e10378918a20c0b0f086418734e57e74/ruff-0.14.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:eed28f6fafcc9591994c42254f5a5c5ca40e69a30721d2ab18bb0bb3baac3ab6", size = 13469550, upload-time = "2025-12-04T15:05:59.209Z" }, + { url = "https://files.pythonhosted.org/packages/70/7d/a4d7b1961e4903bc37fffb7ddcfaa7beb250f67d97cfd1ee1d5cddb1ec90/ruff-0.14.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:21d48fa744c9d1cb8d71eb0a740c4dd02751a5de9db9a730a8ef75ca34cf138e", size = 14211332, upload-time = "2025-12-04T15:06:06.027Z" }, + { url = "https://files.pythonhosted.org/packages/5d/93/2a5063341fa17054e5c86582136e9895db773e3c2ffb770dde50a09f35f0/ruff-0.14.8-py3-none-win32.whl", hash = "sha256:15f04cb45c051159baebb0f0037f404f1dc2f15a927418f29730f411a79bc4e7", size = 13151890, upload-time = "2025-12-04T15:06:11.668Z" }, + { url = "https://files.pythonhosted.org/packages/02/1c/65c61a0859c0add13a3e1cbb6024b42de587456a43006ca2d4fd3d1618fe/ruff-0.14.8-py3-none-win_amd64.whl", hash = "sha256:9eeb0b24242b5bbff3011409a739929f497f3fb5fe3b5698aba5e77e8c833097", size = 14537826, upload-time = "2025-12-04T15:06:26.409Z" }, + { url = "https://files.pythonhosted.org/packages/6d/63/8b41cea3afd7f58eb64ac9251668ee0073789a3bc9ac6f816c8c6fef986d/ruff-0.14.8-py3-none-win_arm64.whl", hash = "sha256:965a582c93c63fe715fd3e3f8aa37c4b776777203d8e1d8aa3cc0c14424a4b99", size = 13634522, upload-time = "2025-12-04T15:06:43.212Z" }, ] [[package]] @@ -1069,7 +1274,7 @@ wheels = [ [[package]] name = "sphinx-autodoc-typehints" -version = "3.2.0" +version = "3.5.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11'", @@ -1077,9 +1282,9 @@ resolution-markers = [ dependencies = [ { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/93/68/a388a9b8f066cd865d9daa65af589d097efbfab9a8c302d2cb2daa43b52e/sphinx_autodoc_typehints-3.2.0.tar.gz", hash = "sha256:107ac98bc8b4837202c88c0736d59d6da44076e65a0d7d7d543a78631f662a9b", size = 36724, upload-time = "2025-04-25T16:53:25.872Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/4f/4fd5583678bb7dc8afa69e9b309e6a99ee8d79ad3a4728f4e52fd7cb37c7/sphinx_autodoc_typehints-3.5.2.tar.gz", hash = "sha256:5fcd4a3eb7aa89424c1e2e32bedca66edc38367569c9169a80f4b3e934171fdb", size = 37839, upload-time = "2025-10-16T00:50:15.743Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/c7/8aab362e86cbf887e58be749a78d20ad743e1eb2c73c2b13d4761f39a104/sphinx_autodoc_typehints-3.2.0-py3-none-any.whl", hash = "sha256:884b39be23b1d884dcc825d4680c9c6357a476936e3b381a67ae80091984eb49", size = 20563, upload-time = "2025-04-25T16:53:24.492Z" }, + { url = "https://files.pythonhosted.org/packages/05/f2/9657c98a66973b7c35bfd48ba65d1922860de9598fbb535cd96e3f58a908/sphinx_autodoc_typehints-3.5.2-py3-none-any.whl", hash = "sha256:0accd043619f53c86705958e323b419e41667917045ac9215d7be1b493648d8c", size = 21184, upload-time = "2025-10-16T00:50:13.973Z" }, ] [[package]] @@ -1166,14 +1371,15 @@ wheels = [ [[package]] name = "starlette" -version = "0.47.0" +version = "0.50.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8b/d0/0332bd8a25779a0e2082b0e179805ad39afad642938b371ae0882e7f880d/starlette-0.47.0.tar.gz", hash = "sha256:1f64887e94a447fed5f23309fb6890ef23349b7e478faa7b24a851cd4eb844af", size = 2582856, upload-time = "2025-05-29T15:45:27.628Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/b8/73a0e6a6e079a9d9cfa64113d771e421640b6f679a52eeb9b32f72d871a1/starlette-0.50.0.tar.gz", hash = "sha256:a2a17b22203254bcbc2e1f926d2d55f3f9497f769416b3190768befe598fa3ca", size = 2646985, upload-time = "2025-11-01T15:25:27.516Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/81/c60b35fe9674f63b38a8feafc414fca0da378a9dbd5fa1e0b8d23fcc7a9b/starlette-0.47.0-py3-none-any.whl", hash = "sha256:9d052d4933683af40ffd47c7465433570b4949dc937e20ad1d73b34e72f10c37", size = 72796, upload-time = "2025-05-29T15:45:26.305Z" }, + { url = "https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", size = 74033, upload-time = "2025-11-01T15:25:25.461Z" }, ] [[package]] @@ -1227,14 +1433,14 @@ wheels = [ [[package]] name = "types-jsonschema" -version = "4.25.1.20250822" +version = "4.25.1.20251009" 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" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/da/5b901088da5f710690b422137e8ae74197fb1ca471e4aa84dd3ef0d6e295/types_jsonschema-4.25.1.20251009.tar.gz", hash = "sha256:75d0f5c5dd18dc23b664437a0c1a625743e8d2e665ceaf3aecb29841f3a5f97f", size = 15661, upload-time = "2025-10-09T02:54:36.963Z" } 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" }, + { url = "https://files.pythonhosted.org/packages/7f/6a/e5146754c0dfc272f176db9c245bc43cc19030262d891a5a85d472797e60/types_jsonschema-4.25.1.20251009-py3-none-any.whl", hash = "sha256:f30b329037b78e7a60146b1146feb0b6fb0b71628637584409bada83968dad3e", size = 15925, upload-time = "2025-10-09T02:54:35.847Z" }, ] [[package]] @@ -1260,139 +1466,143 @@ wheels = [ [[package]] name = "urllib3" -version = "2.5.0" +version = "2.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/43/554c2569b62f49350597348fc3ac70f786e3c32e7f19d266e19817812dd3/urllib3-2.6.0.tar.gz", hash = "sha256:cb9bcef5a4b345d5da5d145dc3e30834f58e8018828cbc724d30b4cb7d4d49f1", size = 432585, upload-time = "2025-12-05T15:08:47.885Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, + { url = "https://files.pythonhosted.org/packages/56/1a/9ffe814d317c5224166b23e7c47f606d6e473712a2fad0f704ea9b99f246/urllib3-2.6.0-py3-none-any.whl", hash = "sha256:c90f7a39f716c572c4e3e58509581ebd83f9b59cced005b7db7ad2d22b0db99f", size = 131083, upload-time = "2025-12-05T15:08:45.983Z" }, ] [[package]] name = "uvicorn" -version = "0.34.3" +version = "0.38.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/de/ad/713be230bcda622eaa35c28f0d328c3675c371238470abdea52417f17a8e/uvicorn-0.34.3.tar.gz", hash = "sha256:35919a9a979d7a59334b6b10e05d77c1d0d574c50e0fc98b8b1a0f165708b55a", size = 76631, upload-time = "2025-06-01T07:48:17.531Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/ce/f06b84e2697fef4688ca63bdb2fdf113ca0a3be33f94488f2cadb690b0cf/uvicorn-0.38.0.tar.gz", hash = "sha256:fd97093bdd120a2609fc0d3afe931d4d4ad688b6e75f0f929fde1bc36fe0e91d", size = 80605, upload-time = "2025-10-18T13:46:44.63Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/0d/8adfeaa62945f90d19ddc461c55f4a50c258af7662d34b6a3d5d1f8646f6/uvicorn-0.34.3-py3-none-any.whl", hash = "sha256:16246631db62bdfbf069b0645177d6e8a77ba950cfedbfd093acef9444e4d885", size = 62431, upload-time = "2025-06-01T07:48:15.664Z" }, + { url = "https://files.pythonhosted.org/packages/ee/d9/d88e73ca598f4f6ff671fb5fde8a32925c2e08a637303a1d12883c7305fa/uvicorn-0.38.0-py3-none-any.whl", hash = "sha256:48c0afd214ceb59340075b4a052ea1ee91c16fbc2a9b1469cca0e54566977b02", size = 68109, upload-time = "2025-10-18T13:46:42.958Z" }, ] [[package]] name = "virtualenv" -version = "20.29.3" +version = "20.35.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, { name = "filelock" }, { name = "platformdirs" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c7/9c/57d19fa093bcf5ac61a48087dd44d00655f85421d1aa9722f8befbf3f40a/virtualenv-20.29.3.tar.gz", hash = "sha256:95e39403fcf3940ac45bc717597dba16110b74506131845d9b687d5e73d947ac", size = 4320280, upload-time = "2025-03-06T19:54:19.055Z" } +sdist = { url = "https://files.pythonhosted.org/packages/20/28/e6f1a6f655d620846bd9df527390ecc26b3805a0c5989048c210e22c5ca9/virtualenv-20.35.4.tar.gz", hash = "sha256:643d3914d73d3eeb0c552cbb12d7e82adf0e504dbf86a3182f8771a153a1971c", size = 6028799, upload-time = "2025-10-29T06:57:40.511Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/eb/c6db6e3001d58c6a9e67c74bb7b4206767caa3ccc28c6b9eaf4c23fb4e34/virtualenv-20.29.3-py3-none-any.whl", hash = "sha256:3e3d00f5807e83b234dfb6122bf37cfadf4be216c53a49ac059d02414f819170", size = 4301458, upload-time = "2025-03-06T19:54:16.923Z" }, + { url = "https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl", hash = "sha256:c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b", size = 6005095, upload-time = "2025-10-29T06:57:37.598Z" }, ] [[package]] name = "watchfiles" -version = "1.1.0" +version = "1.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2a/9a/d451fcc97d029f5812e898fd30a53fd8c15c7bbd058fd75cfc6beb9bd761/watchfiles-1.1.0.tar.gz", hash = "sha256:693ed7ec72cbfcee399e92c895362b6e66d63dac6b91e2c11ae03d10d503e575", size = 94406, upload-time = "2025-06-15T19:06:59.42Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/c9/8869df9b2a2d6c59d79220a4db37679e74f807c559ffe5265e08b227a210/watchfiles-1.1.1.tar.gz", hash = "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2", size = 94440, upload-time = "2025-10-14T15:06:21.08Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/dd/579d1dc57f0f895426a1211c4ef3b0cb37eb9e642bb04bdcd962b5df206a/watchfiles-1.1.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:27f30e14aa1c1e91cb653f03a63445739919aef84c8d2517997a83155e7a2fcc", size = 405757, upload-time = "2025-06-15T19:04:51.058Z" }, - { url = "https://files.pythonhosted.org/packages/1c/a0/7a0318cd874393344d48c34d53b3dd419466adf59a29ba5b51c88dd18b86/watchfiles-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3366f56c272232860ab45c77c3ca7b74ee819c8e1f6f35a7125556b198bbc6df", size = 397511, upload-time = "2025-06-15T19:04:52.79Z" }, - { url = "https://files.pythonhosted.org/packages/06/be/503514656d0555ec2195f60d810eca29b938772e9bfb112d5cd5ad6f6a9e/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8412eacef34cae2836d891836a7fff7b754d6bcac61f6c12ba5ca9bc7e427b68", size = 450739, upload-time = "2025-06-15T19:04:54.203Z" }, - { url = "https://files.pythonhosted.org/packages/4e/0d/a05dd9e5f136cdc29751816d0890d084ab99f8c17b86f25697288ca09bc7/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df670918eb7dd719642e05979fc84704af913d563fd17ed636f7c4783003fdcc", size = 458106, upload-time = "2025-06-15T19:04:55.607Z" }, - { url = "https://files.pythonhosted.org/packages/f1/fa/9cd16e4dfdb831072b7ac39e7bea986e52128526251038eb481effe9f48e/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d7642b9bc4827b5518ebdb3b82698ada8c14c7661ddec5fe719f3e56ccd13c97", size = 484264, upload-time = "2025-06-15T19:04:57.009Z" }, - { url = "https://files.pythonhosted.org/packages/32/04/1da8a637c7e2b70e750a0308e9c8e662ada0cca46211fa9ef24a23937e0b/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:199207b2d3eeaeb80ef4411875a6243d9ad8bc35b07fc42daa6b801cc39cc41c", size = 597612, upload-time = "2025-06-15T19:04:58.409Z" }, - { url = "https://files.pythonhosted.org/packages/30/01/109f2762e968d3e58c95731a206e5d7d2a7abaed4299dd8a94597250153c/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a479466da6db5c1e8754caee6c262cd373e6e6c363172d74394f4bff3d84d7b5", size = 477242, upload-time = "2025-06-15T19:04:59.786Z" }, - { url = "https://files.pythonhosted.org/packages/b5/b8/46f58cf4969d3b7bc3ca35a98e739fa4085b0657a1540ccc29a1a0bc016f/watchfiles-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:935f9edd022ec13e447e5723a7d14456c8af254544cefbc533f6dd276c9aa0d9", size = 453148, upload-time = "2025-06-15T19:05:01.103Z" }, - { url = "https://files.pythonhosted.org/packages/a5/cd/8267594263b1770f1eb76914940d7b2d03ee55eca212302329608208e061/watchfiles-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8076a5769d6bdf5f673a19d51da05fc79e2bbf25e9fe755c47595785c06a8c72", size = 626574, upload-time = "2025-06-15T19:05:02.582Z" }, - { url = "https://files.pythonhosted.org/packages/a1/2f/7f2722e85899bed337cba715723e19185e288ef361360718973f891805be/watchfiles-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:86b1e28d4c37e89220e924305cd9f82866bb0ace666943a6e4196c5df4d58dcc", size = 624378, upload-time = "2025-06-15T19:05:03.719Z" }, - { url = "https://files.pythonhosted.org/packages/bf/20/64c88ec43d90a568234d021ab4b2a6f42a5230d772b987c3f9c00cc27b8b/watchfiles-1.1.0-cp310-cp310-win32.whl", hash = "sha256:d1caf40c1c657b27858f9774d5c0e232089bca9cb8ee17ce7478c6e9264d2587", size = 279829, upload-time = "2025-06-15T19:05:04.822Z" }, - { url = "https://files.pythonhosted.org/packages/39/5c/a9c1ed33de7af80935e4eac09570de679c6e21c07070aa99f74b4431f4d6/watchfiles-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:a89c75a5b9bc329131115a409d0acc16e8da8dfd5867ba59f1dd66ae7ea8fa82", size = 292192, upload-time = "2025-06-15T19:05:06.348Z" }, - { url = "https://files.pythonhosted.org/packages/8b/78/7401154b78ab484ccaaeef970dc2af0cb88b5ba8a1b415383da444cdd8d3/watchfiles-1.1.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:c9649dfc57cc1f9835551deb17689e8d44666315f2e82d337b9f07bd76ae3aa2", size = 405751, upload-time = "2025-06-15T19:05:07.679Z" }, - { url = "https://files.pythonhosted.org/packages/76/63/e6c3dbc1f78d001589b75e56a288c47723de28c580ad715eb116639152b5/watchfiles-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:406520216186b99374cdb58bc48e34bb74535adec160c8459894884c983a149c", size = 397313, upload-time = "2025-06-15T19:05:08.764Z" }, - { url = "https://files.pythonhosted.org/packages/6c/a2/8afa359ff52e99af1632f90cbf359da46184207e893a5f179301b0c8d6df/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb45350fd1dc75cd68d3d72c47f5b513cb0578da716df5fba02fff31c69d5f2d", size = 450792, upload-time = "2025-06-15T19:05:09.869Z" }, - { url = "https://files.pythonhosted.org/packages/1d/bf/7446b401667f5c64972a57a0233be1104157fc3abf72c4ef2666c1bd09b2/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:11ee4444250fcbeb47459a877e5e80ed994ce8e8d20283857fc128be1715dac7", size = 458196, upload-time = "2025-06-15T19:05:11.91Z" }, - { url = "https://files.pythonhosted.org/packages/58/2f/501ddbdfa3fa874ea5597c77eeea3d413579c29af26c1091b08d0c792280/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bda8136e6a80bdea23e5e74e09df0362744d24ffb8cd59c4a95a6ce3d142f79c", size = 484788, upload-time = "2025-06-15T19:05:13.373Z" }, - { url = "https://files.pythonhosted.org/packages/61/1e/9c18eb2eb5c953c96bc0e5f626f0e53cfef4bd19bd50d71d1a049c63a575/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b915daeb2d8c1f5cee4b970f2e2c988ce6514aace3c9296e58dd64dc9aa5d575", size = 597879, upload-time = "2025-06-15T19:05:14.725Z" }, - { url = "https://files.pythonhosted.org/packages/8b/6c/1467402e5185d89388b4486745af1e0325007af0017c3384cc786fff0542/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed8fc66786de8d0376f9f913c09e963c66e90ced9aa11997f93bdb30f7c872a8", size = 477447, upload-time = "2025-06-15T19:05:15.775Z" }, - { url = "https://files.pythonhosted.org/packages/2b/a1/ec0a606bde4853d6c4a578f9391eeb3684a9aea736a8eb217e3e00aa89a1/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe4371595edf78c41ef8ac8df20df3943e13defd0efcb732b2e393b5a8a7a71f", size = 453145, upload-time = "2025-06-15T19:05:17.17Z" }, - { url = "https://files.pythonhosted.org/packages/90/b9/ef6f0c247a6a35d689fc970dc7f6734f9257451aefb30def5d100d6246a5/watchfiles-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b7c5f6fe273291f4d414d55b2c80d33c457b8a42677ad14b4b47ff025d0893e4", size = 626539, upload-time = "2025-06-15T19:05:18.557Z" }, - { url = "https://files.pythonhosted.org/packages/34/44/6ffda5537085106ff5aaa762b0d130ac6c75a08015dd1621376f708c94de/watchfiles-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7738027989881e70e3723c75921f1efa45225084228788fc59ea8c6d732eb30d", size = 624472, upload-time = "2025-06-15T19:05:19.588Z" }, - { url = "https://files.pythonhosted.org/packages/c3/e3/71170985c48028fa3f0a50946916a14055e741db11c2e7bc2f3b61f4d0e3/watchfiles-1.1.0-cp311-cp311-win32.whl", hash = "sha256:622d6b2c06be19f6e89b1d951485a232e3b59618def88dbeda575ed8f0d8dbf2", size = 279348, upload-time = "2025-06-15T19:05:20.856Z" }, - { url = "https://files.pythonhosted.org/packages/89/1b/3e39c68b68a7a171070f81fc2561d23ce8d6859659406842a0e4bebf3bba/watchfiles-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:48aa25e5992b61debc908a61ab4d3f216b64f44fdaa71eb082d8b2de846b7d12", size = 292607, upload-time = "2025-06-15T19:05:21.937Z" }, - { url = "https://files.pythonhosted.org/packages/61/9f/2973b7539f2bdb6ea86d2c87f70f615a71a1fc2dba2911795cea25968aea/watchfiles-1.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:00645eb79a3faa70d9cb15c8d4187bb72970b2470e938670240c7998dad9f13a", size = 285056, upload-time = "2025-06-15T19:05:23.12Z" }, - { url = "https://files.pythonhosted.org/packages/f6/b8/858957045a38a4079203a33aaa7d23ea9269ca7761c8a074af3524fbb240/watchfiles-1.1.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9dc001c3e10de4725c749d4c2f2bdc6ae24de5a88a339c4bce32300a31ede179", size = 402339, upload-time = "2025-06-15T19:05:24.516Z" }, - { url = "https://files.pythonhosted.org/packages/80/28/98b222cca751ba68e88521fabd79a4fab64005fc5976ea49b53fa205d1fa/watchfiles-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d9ba68ec283153dead62cbe81872d28e053745f12335d037de9cbd14bd1877f5", size = 394409, upload-time = "2025-06-15T19:05:25.469Z" }, - { url = "https://files.pythonhosted.org/packages/86/50/dee79968566c03190677c26f7f47960aff738d32087087bdf63a5473e7df/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:130fc497b8ee68dce163e4254d9b0356411d1490e868bd8790028bc46c5cc297", size = 450939, upload-time = "2025-06-15T19:05:26.494Z" }, - { url = "https://files.pythonhosted.org/packages/40/45/a7b56fb129700f3cfe2594a01aa38d033b92a33dddce86c8dfdfc1247b72/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:50a51a90610d0845a5931a780d8e51d7bd7f309ebc25132ba975aca016b576a0", size = 457270, upload-time = "2025-06-15T19:05:27.466Z" }, - { url = "https://files.pythonhosted.org/packages/b5/c8/fa5ef9476b1d02dc6b5e258f515fcaaecf559037edf8b6feffcbc097c4b8/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc44678a72ac0910bac46fa6a0de6af9ba1355669b3dfaf1ce5f05ca7a74364e", size = 483370, upload-time = "2025-06-15T19:05:28.548Z" }, - { url = "https://files.pythonhosted.org/packages/98/68/42cfcdd6533ec94f0a7aab83f759ec11280f70b11bfba0b0f885e298f9bd/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a543492513a93b001975ae283a51f4b67973662a375a403ae82f420d2c7205ee", size = 598654, upload-time = "2025-06-15T19:05:29.997Z" }, - { url = "https://files.pythonhosted.org/packages/d3/74/b2a1544224118cc28df7e59008a929e711f9c68ce7d554e171b2dc531352/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ac164e20d17cc285f2b94dc31c384bc3aa3dd5e7490473b3db043dd70fbccfd", size = 478667, upload-time = "2025-06-15T19:05:31.172Z" }, - { url = "https://files.pythonhosted.org/packages/8c/77/e3362fe308358dc9f8588102481e599c83e1b91c2ae843780a7ded939a35/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7590d5a455321e53857892ab8879dce62d1f4b04748769f5adf2e707afb9d4f", size = 452213, upload-time = "2025-06-15T19:05:32.299Z" }, - { url = "https://files.pythonhosted.org/packages/6e/17/c8f1a36540c9a1558d4faf08e909399e8133599fa359bf52ec8fcee5be6f/watchfiles-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:37d3d3f7defb13f62ece99e9be912afe9dd8a0077b7c45ee5a57c74811d581a4", size = 626718, upload-time = "2025-06-15T19:05:33.415Z" }, - { url = "https://files.pythonhosted.org/packages/26/45/fb599be38b4bd38032643783d7496a26a6f9ae05dea1a42e58229a20ac13/watchfiles-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7080c4bb3efd70a07b1cc2df99a7aa51d98685be56be6038c3169199d0a1c69f", size = 623098, upload-time = "2025-06-15T19:05:34.534Z" }, - { url = "https://files.pythonhosted.org/packages/a1/e7/fdf40e038475498e160cd167333c946e45d8563ae4dd65caf757e9ffe6b4/watchfiles-1.1.0-cp312-cp312-win32.whl", hash = "sha256:cbcf8630ef4afb05dc30107bfa17f16c0896bb30ee48fc24bf64c1f970f3b1fd", size = 279209, upload-time = "2025-06-15T19:05:35.577Z" }, - { url = "https://files.pythonhosted.org/packages/3f/d3/3ae9d5124ec75143bdf088d436cba39812122edc47709cd2caafeac3266f/watchfiles-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:cbd949bdd87567b0ad183d7676feb98136cde5bb9025403794a4c0db28ed3a47", size = 292786, upload-time = "2025-06-15T19:05:36.559Z" }, - { url = "https://files.pythonhosted.org/packages/26/2f/7dd4fc8b5f2b34b545e19629b4a018bfb1de23b3a496766a2c1165ca890d/watchfiles-1.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:0a7d40b77f07be87c6faa93d0951a0fcd8cbca1ddff60a1b65d741bac6f3a9f6", size = 284343, upload-time = "2025-06-15T19:05:37.5Z" }, - { url = "https://files.pythonhosted.org/packages/d3/42/fae874df96595556a9089ade83be34a2e04f0f11eb53a8dbf8a8a5e562b4/watchfiles-1.1.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5007f860c7f1f8df471e4e04aaa8c43673429047d63205d1630880f7637bca30", size = 402004, upload-time = "2025-06-15T19:05:38.499Z" }, - { url = "https://files.pythonhosted.org/packages/fa/55/a77e533e59c3003d9803c09c44c3651224067cbe7fb5d574ddbaa31e11ca/watchfiles-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:20ecc8abbd957046f1fe9562757903f5eaf57c3bce70929fda6c7711bb58074a", size = 393671, upload-time = "2025-06-15T19:05:39.52Z" }, - { url = "https://files.pythonhosted.org/packages/05/68/b0afb3f79c8e832e6571022611adbdc36e35a44e14f129ba09709aa4bb7a/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2f0498b7d2a3c072766dba3274fe22a183dbea1f99d188f1c6c72209a1063dc", size = 449772, upload-time = "2025-06-15T19:05:40.897Z" }, - { url = "https://files.pythonhosted.org/packages/ff/05/46dd1f6879bc40e1e74c6c39a1b9ab9e790bf1f5a2fe6c08b463d9a807f4/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:239736577e848678e13b201bba14e89718f5c2133dfd6b1f7846fa1b58a8532b", size = 456789, upload-time = "2025-06-15T19:05:42.045Z" }, - { url = "https://files.pythonhosted.org/packages/8b/ca/0eeb2c06227ca7f12e50a47a3679df0cd1ba487ea19cf844a905920f8e95/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eff4b8d89f444f7e49136dc695599a591ff769300734446c0a86cba2eb2f9895", size = 482551, upload-time = "2025-06-15T19:05:43.781Z" }, - { url = "https://files.pythonhosted.org/packages/31/47/2cecbd8694095647406645f822781008cc524320466ea393f55fe70eed3b/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12b0a02a91762c08f7264e2e79542f76870c3040bbc847fb67410ab81474932a", size = 597420, upload-time = "2025-06-15T19:05:45.244Z" }, - { url = "https://files.pythonhosted.org/packages/d9/7e/82abc4240e0806846548559d70f0b1a6dfdca75c1b4f9fa62b504ae9b083/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29e7bc2eee15cbb339c68445959108803dc14ee0c7b4eea556400131a8de462b", size = 477950, upload-time = "2025-06-15T19:05:46.332Z" }, - { url = "https://files.pythonhosted.org/packages/25/0d/4d564798a49bf5482a4fa9416dea6b6c0733a3b5700cb8a5a503c4b15853/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9481174d3ed982e269c090f780122fb59cee6c3796f74efe74e70f7780ed94c", size = 451706, upload-time = "2025-06-15T19:05:47.459Z" }, - { url = "https://files.pythonhosted.org/packages/81/b5/5516cf46b033192d544102ea07c65b6f770f10ed1d0a6d388f5d3874f6e4/watchfiles-1.1.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:80f811146831c8c86ab17b640801c25dc0a88c630e855e2bef3568f30434d52b", size = 625814, upload-time = "2025-06-15T19:05:48.654Z" }, - { url = "https://files.pythonhosted.org/packages/0c/dd/7c1331f902f30669ac3e754680b6edb9a0dd06dea5438e61128111fadd2c/watchfiles-1.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:60022527e71d1d1fda67a33150ee42869042bce3d0fcc9cc49be009a9cded3fb", size = 622820, upload-time = "2025-06-15T19:05:50.088Z" }, - { url = "https://files.pythonhosted.org/packages/1b/14/36d7a8e27cd128d7b1009e7715a7c02f6c131be9d4ce1e5c3b73d0e342d8/watchfiles-1.1.0-cp313-cp313-win32.whl", hash = "sha256:32d6d4e583593cb8576e129879ea0991660b935177c0f93c6681359b3654bfa9", size = 279194, upload-time = "2025-06-15T19:05:51.186Z" }, - { url = "https://files.pythonhosted.org/packages/25/41/2dd88054b849aa546dbeef5696019c58f8e0774f4d1c42123273304cdb2e/watchfiles-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:f21af781a4a6fbad54f03c598ab620e3a77032c5878f3d780448421a6e1818c7", size = 292349, upload-time = "2025-06-15T19:05:52.201Z" }, - { url = "https://files.pythonhosted.org/packages/c8/cf/421d659de88285eb13941cf11a81f875c176f76a6d99342599be88e08d03/watchfiles-1.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:5366164391873ed76bfdf618818c82084c9db7fac82b64a20c44d335eec9ced5", size = 283836, upload-time = "2025-06-15T19:05:53.265Z" }, - { url = "https://files.pythonhosted.org/packages/45/10/6faf6858d527e3599cc50ec9fcae73590fbddc1420bd4fdccfebffeedbc6/watchfiles-1.1.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:17ab167cca6339c2b830b744eaf10803d2a5b6683be4d79d8475d88b4a8a4be1", size = 400343, upload-time = "2025-06-15T19:05:54.252Z" }, - { url = "https://files.pythonhosted.org/packages/03/20/5cb7d3966f5e8c718006d0e97dfe379a82f16fecd3caa7810f634412047a/watchfiles-1.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:328dbc9bff7205c215a7807da7c18dce37da7da718e798356212d22696404339", size = 392916, upload-time = "2025-06-15T19:05:55.264Z" }, - { url = "https://files.pythonhosted.org/packages/8c/07/d8f1176328fa9e9581b6f120b017e286d2a2d22ae3f554efd9515c8e1b49/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7208ab6e009c627b7557ce55c465c98967e8caa8b11833531fdf95799372633", size = 449582, upload-time = "2025-06-15T19:05:56.317Z" }, - { url = "https://files.pythonhosted.org/packages/66/e8/80a14a453cf6038e81d072a86c05276692a1826471fef91df7537dba8b46/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a8f6f72974a19efead54195bc9bed4d850fc047bb7aa971268fd9a8387c89011", size = 456752, upload-time = "2025-06-15T19:05:57.359Z" }, - { url = "https://files.pythonhosted.org/packages/5a/25/0853b3fe0e3c2f5af9ea60eb2e781eade939760239a72c2d38fc4cc335f6/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d181ef50923c29cf0450c3cd47e2f0557b62218c50b2ab8ce2ecaa02bd97e670", size = 481436, upload-time = "2025-06-15T19:05:58.447Z" }, - { url = "https://files.pythonhosted.org/packages/fe/9e/4af0056c258b861fbb29dcb36258de1e2b857be4a9509e6298abcf31e5c9/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adb4167043d3a78280d5d05ce0ba22055c266cf8655ce942f2fb881262ff3cdf", size = 596016, upload-time = "2025-06-15T19:05:59.59Z" }, - { url = "https://files.pythonhosted.org/packages/c5/fa/95d604b58aa375e781daf350897aaaa089cff59d84147e9ccff2447c8294/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5701dc474b041e2934a26d31d39f90fac8a3dee2322b39f7729867f932b1d4", size = 476727, upload-time = "2025-06-15T19:06:01.086Z" }, - { url = "https://files.pythonhosted.org/packages/65/95/fe479b2664f19be4cf5ceeb21be05afd491d95f142e72d26a42f41b7c4f8/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b067915e3c3936966a8607f6fe5487df0c9c4afb85226613b520890049deea20", size = 451864, upload-time = "2025-06-15T19:06:02.144Z" }, - { url = "https://files.pythonhosted.org/packages/d3/8a/3c4af14b93a15ce55901cd7a92e1a4701910f1768c78fb30f61d2b79785b/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:9c733cda03b6d636b4219625a4acb5c6ffb10803338e437fb614fef9516825ef", size = 625626, upload-time = "2025-06-15T19:06:03.578Z" }, - { url = "https://files.pythonhosted.org/packages/da/f5/cf6aa047d4d9e128f4b7cde615236a915673775ef171ff85971d698f3c2c/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:cc08ef8b90d78bfac66f0def80240b0197008e4852c9f285907377b2947ffdcb", size = 622744, upload-time = "2025-06-15T19:06:05.066Z" }, - { url = "https://files.pythonhosted.org/packages/2c/00/70f75c47f05dea6fd30df90f047765f6fc2d6eb8b5a3921379b0b04defa2/watchfiles-1.1.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:9974d2f7dc561cce3bb88dfa8eb309dab64c729de85fba32e98d75cf24b66297", size = 402114, upload-time = "2025-06-15T19:06:06.186Z" }, - { url = "https://files.pythonhosted.org/packages/53/03/acd69c48db4a1ed1de26b349d94077cca2238ff98fd64393f3e97484cae6/watchfiles-1.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c68e9f1fcb4d43798ad8814c4c1b61547b014b667216cb754e606bfade587018", size = 393879, upload-time = "2025-06-15T19:06:07.369Z" }, - { url = "https://files.pythonhosted.org/packages/2f/c8/a9a2a6f9c8baa4eceae5887fecd421e1b7ce86802bcfc8b6a942e2add834/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95ab1594377effac17110e1352989bdd7bdfca9ff0e5eeccd8c69c5389b826d0", size = 450026, upload-time = "2025-06-15T19:06:08.476Z" }, - { url = "https://files.pythonhosted.org/packages/fe/51/d572260d98388e6e2b967425c985e07d47ee6f62e6455cefb46a6e06eda5/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fba9b62da882c1be1280a7584ec4515d0a6006a94d6e5819730ec2eab60ffe12", size = 457917, upload-time = "2025-06-15T19:06:09.988Z" }, - { url = "https://files.pythonhosted.org/packages/c6/2d/4258e52917bf9f12909b6ec314ff9636276f3542f9d3807d143f27309104/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3434e401f3ce0ed6b42569128b3d1e3af773d7ec18751b918b89cd49c14eaafb", size = 483602, upload-time = "2025-06-15T19:06:11.088Z" }, - { url = "https://files.pythonhosted.org/packages/84/99/bee17a5f341a4345fe7b7972a475809af9e528deba056f8963d61ea49f75/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa257a4d0d21fcbca5b5fcba9dca5a78011cb93c0323fb8855c6d2dfbc76eb77", size = 596758, upload-time = "2025-06-15T19:06:12.197Z" }, - { url = "https://files.pythonhosted.org/packages/40/76/e4bec1d59b25b89d2b0716b41b461ed655a9a53c60dc78ad5771fda5b3e6/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fd1b3879a578a8ec2076c7961076df540b9af317123f84569f5a9ddee64ce92", size = 477601, upload-time = "2025-06-15T19:06:13.391Z" }, - { url = "https://files.pythonhosted.org/packages/1f/fa/a514292956f4a9ce3c567ec0c13cce427c158e9f272062685a8a727d08fc/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62cc7a30eeb0e20ecc5f4bd113cd69dcdb745a07c68c0370cea919f373f65d9e", size = 451936, upload-time = "2025-06-15T19:06:14.656Z" }, - { url = "https://files.pythonhosted.org/packages/32/5d/c3bf927ec3bbeb4566984eba8dd7a8eb69569400f5509904545576741f88/watchfiles-1.1.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:891c69e027748b4a73847335d208e374ce54ca3c335907d381fde4e41661b13b", size = 626243, upload-time = "2025-06-15T19:06:16.232Z" }, - { url = "https://files.pythonhosted.org/packages/e6/65/6e12c042f1a68c556802a84d54bb06d35577c81e29fba14019562479159c/watchfiles-1.1.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:12fe8eaffaf0faa7906895b4f8bb88264035b3f0243275e0bf24af0436b27259", size = 623073, upload-time = "2025-06-15T19:06:17.457Z" }, - { url = "https://files.pythonhosted.org/packages/89/ab/7f79d9bf57329e7cbb0a6fd4c7bd7d0cee1e4a8ef0041459f5409da3506c/watchfiles-1.1.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:bfe3c517c283e484843cb2e357dd57ba009cff351edf45fb455b5fbd1f45b15f", size = 400872, upload-time = "2025-06-15T19:06:18.57Z" }, - { url = "https://files.pythonhosted.org/packages/df/d5/3f7bf9912798e9e6c516094db6b8932df53b223660c781ee37607030b6d3/watchfiles-1.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a9ccbf1f129480ed3044f540c0fdbc4ee556f7175e5ab40fe077ff6baf286d4e", size = 392877, upload-time = "2025-06-15T19:06:19.55Z" }, - { url = "https://files.pythonhosted.org/packages/0d/c5/54ec7601a2798604e01c75294770dbee8150e81c6e471445d7601610b495/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba0e3255b0396cac3cc7bbace76404dd72b5438bf0d8e7cefa2f79a7f3649caa", size = 449645, upload-time = "2025-06-15T19:06:20.66Z" }, - { url = "https://files.pythonhosted.org/packages/0a/04/c2f44afc3b2fce21ca0b7802cbd37ed90a29874f96069ed30a36dfe57c2b/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4281cd9fce9fc0a9dbf0fc1217f39bf9cf2b4d315d9626ef1d4e87b84699e7e8", size = 457424, upload-time = "2025-06-15T19:06:21.712Z" }, - { url = "https://files.pythonhosted.org/packages/9f/b0/eec32cb6c14d248095261a04f290636da3df3119d4040ef91a4a50b29fa5/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d2404af8db1329f9a3c9b79ff63e0ae7131986446901582067d9304ae8aaf7f", size = 481584, upload-time = "2025-06-15T19:06:22.777Z" }, - { url = "https://files.pythonhosted.org/packages/d1/e2/ca4bb71c68a937d7145aa25709e4f5d68eb7698a25ce266e84b55d591bbd/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e78b6ed8165996013165eeabd875c5dfc19d41b54f94b40e9fff0eb3193e5e8e", size = 596675, upload-time = "2025-06-15T19:06:24.226Z" }, - { url = "https://files.pythonhosted.org/packages/a1/dd/b0e4b7fb5acf783816bc950180a6cd7c6c1d2cf7e9372c0ea634e722712b/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:249590eb75ccc117f488e2fabd1bfa33c580e24b96f00658ad88e38844a040bb", size = 477363, upload-time = "2025-06-15T19:06:25.42Z" }, - { url = "https://files.pythonhosted.org/packages/69/c4/088825b75489cb5b6a761a4542645718893d395d8c530b38734f19da44d2/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d05686b5487cfa2e2c28ff1aa370ea3e6c5accfe6435944ddea1e10d93872147", size = 452240, upload-time = "2025-06-15T19:06:26.552Z" }, - { url = "https://files.pythonhosted.org/packages/10/8c/22b074814970eeef43b7c44df98c3e9667c1f7bf5b83e0ff0201b0bd43f9/watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:d0e10e6f8f6dc5762adee7dece33b722282e1f59aa6a55da5d493a97282fedd8", size = 625607, upload-time = "2025-06-15T19:06:27.606Z" }, - { url = "https://files.pythonhosted.org/packages/32/fa/a4f5c2046385492b2273213ef815bf71a0d4c1943b784fb904e184e30201/watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:af06c863f152005c7592df1d6a7009c836a247c9d8adb78fef8575a5a98699db", size = 623315, upload-time = "2025-06-15T19:06:29.076Z" }, - { url = "https://files.pythonhosted.org/packages/be/7c/a3d7c55cfa377c2f62c4ae3c6502b997186bc5e38156bafcb9b653de9a6d/watchfiles-1.1.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3a6fd40bbb50d24976eb275ccb55cd1951dfb63dbc27cae3066a6ca5f4beabd5", size = 406748, upload-time = "2025-06-15T19:06:44.2Z" }, - { url = "https://files.pythonhosted.org/packages/38/d0/c46f1b2c0ca47f3667b144de6f0515f6d1c670d72f2ca29861cac78abaa1/watchfiles-1.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9f811079d2f9795b5d48b55a37aa7773680a5659afe34b54cc1d86590a51507d", size = 398801, upload-time = "2025-06-15T19:06:45.774Z" }, - { url = "https://files.pythonhosted.org/packages/70/9c/9a6a42e97f92eeed77c3485a43ea96723900aefa3ac739a8c73f4bff2cd7/watchfiles-1.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2726d7bfd9f76158c84c10a409b77a320426540df8c35be172444394b17f7ea", size = 451528, upload-time = "2025-06-15T19:06:46.791Z" }, - { url = "https://files.pythonhosted.org/packages/51/7b/98c7f4f7ce7ff03023cf971cd84a3ee3b790021ae7584ffffa0eb2554b96/watchfiles-1.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df32d59cb9780f66d165a9a7a26f19df2c7d24e3bd58713108b41d0ff4f929c6", size = 454095, upload-time = "2025-06-15T19:06:48.211Z" }, - { url = "https://files.pythonhosted.org/packages/8c/6b/686dcf5d3525ad17b384fd94708e95193529b460a1b7bf40851f1328ec6e/watchfiles-1.1.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0ece16b563b17ab26eaa2d52230c9a7ae46cf01759621f4fbbca280e438267b3", size = 406910, upload-time = "2025-06-15T19:06:49.335Z" }, - { url = "https://files.pythonhosted.org/packages/f3/d3/71c2dcf81dc1edcf8af9f4d8d63b1316fb0a2dd90cbfd427e8d9dd584a90/watchfiles-1.1.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:51b81e55d40c4b4aa8658427a3ee7ea847c591ae9e8b81ef94a90b668999353c", size = 398816, upload-time = "2025-06-15T19:06:50.433Z" }, - { url = "https://files.pythonhosted.org/packages/b8/fa/12269467b2fc006f8fce4cd6c3acfa77491dd0777d2a747415f28ccc8c60/watchfiles-1.1.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2bcdc54ea267fe72bfc7d83c041e4eb58d7d8dc6f578dfddb52f037ce62f432", size = 451584, upload-time = "2025-06-15T19:06:51.834Z" }, - { url = "https://files.pythonhosted.org/packages/bd/d3/254cea30f918f489db09d6a8435a7de7047f8cb68584477a515f160541d6/watchfiles-1.1.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:923fec6e5461c42bd7e3fd5ec37492c6f3468be0499bc0707b4bbbc16ac21792", size = 454009, upload-time = "2025-06-15T19:06:52.896Z" }, + { url = "https://files.pythonhosted.org/packages/a7/1a/206e8cf2dd86fddf939165a57b4df61607a1e0add2785f170a3f616b7d9f/watchfiles-1.1.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:eef58232d32daf2ac67f42dea51a2c80f0d03379075d44a587051e63cc2e368c", size = 407318, upload-time = "2025-10-14T15:04:18.753Z" }, + { url = "https://files.pythonhosted.org/packages/b3/0f/abaf5262b9c496b5dad4ed3c0e799cbecb1f8ea512ecb6ddd46646a9fca3/watchfiles-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:03fa0f5237118a0c5e496185cafa92878568b652a2e9a9382a5151b1a0380a43", size = 394478, upload-time = "2025-10-14T15:04:20.297Z" }, + { url = "https://files.pythonhosted.org/packages/b1/04/9cc0ba88697b34b755371f5ace8d3a4d9a15719c07bdc7bd13d7d8c6a341/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ca65483439f9c791897f7db49202301deb6e15fe9f8fe2fed555bf986d10c31", size = 449894, upload-time = "2025-10-14T15:04:21.527Z" }, + { url = "https://files.pythonhosted.org/packages/d2/9c/eda4615863cd8621e89aed4df680d8c3ec3da6a4cf1da113c17decd87c7f/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f0ab1c1af0cb38e3f598244c17919fb1a84d1629cc08355b0074b6d7f53138ac", size = 459065, upload-time = "2025-10-14T15:04:22.795Z" }, + { url = "https://files.pythonhosted.org/packages/84/13/f28b3f340157d03cbc8197629bc109d1098764abe1e60874622a0be5c112/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bc570d6c01c206c46deb6e935a260be44f186a2f05179f52f7fcd2be086a94d", size = 488377, upload-time = "2025-10-14T15:04:24.138Z" }, + { url = "https://files.pythonhosted.org/packages/86/93/cfa597fa9389e122488f7ffdbd6db505b3b915ca7435ecd7542e855898c2/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e84087b432b6ac94778de547e08611266f1f8ffad28c0ee4c82e028b0fc5966d", size = 595837, upload-time = "2025-10-14T15:04:25.057Z" }, + { url = "https://files.pythonhosted.org/packages/57/1e/68c1ed5652b48d89fc24d6af905d88ee4f82fa8bc491e2666004e307ded1/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:620bae625f4cb18427b1bb1a2d9426dc0dd5a5ba74c7c2cdb9de405f7b129863", size = 473456, upload-time = "2025-10-14T15:04:26.497Z" }, + { url = "https://files.pythonhosted.org/packages/d5/dc/1a680b7458ffa3b14bb64878112aefc8f2e4f73c5af763cbf0bd43100658/watchfiles-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:544364b2b51a9b0c7000a4b4b02f90e9423d97fbbf7e06689236443ebcad81ab", size = 455614, upload-time = "2025-10-14T15:04:27.539Z" }, + { url = "https://files.pythonhosted.org/packages/61/a5/3d782a666512e01eaa6541a72ebac1d3aae191ff4a31274a66b8dd85760c/watchfiles-1.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:bbe1ef33d45bc71cf21364df962af171f96ecaeca06bd9e3d0b583efb12aec82", size = 630690, upload-time = "2025-10-14T15:04:28.495Z" }, + { url = "https://files.pythonhosted.org/packages/9b/73/bb5f38590e34687b2a9c47a244aa4dd50c56a825969c92c9c5fc7387cea1/watchfiles-1.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1a0bb430adb19ef49389e1ad368450193a90038b5b752f4ac089ec6942c4dff4", size = 622459, upload-time = "2025-10-14T15:04:29.491Z" }, + { url = "https://files.pythonhosted.org/packages/f1/ac/c9bb0ec696e07a20bd58af5399aeadaef195fb2c73d26baf55180fe4a942/watchfiles-1.1.1-cp310-cp310-win32.whl", hash = "sha256:3f6d37644155fb5beca5378feb8c1708d5783145f2a0f1c4d5a061a210254844", size = 272663, upload-time = "2025-10-14T15:04:30.435Z" }, + { url = "https://files.pythonhosted.org/packages/11/a0/a60c5a7c2ec59fa062d9a9c61d02e3b6abd94d32aac2d8344c4bdd033326/watchfiles-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:a36d8efe0f290835fd0f33da35042a1bb5dc0e83cbc092dcf69bce442579e88e", size = 287453, upload-time = "2025-10-14T15:04:31.53Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f8/2c5f479fb531ce2f0564eda479faecf253d886b1ab3630a39b7bf7362d46/watchfiles-1.1.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f57b396167a2565a4e8b5e56a5a1c537571733992b226f4f1197d79e94cf0ae5", size = 406529, upload-time = "2025-10-14T15:04:32.899Z" }, + { url = "https://files.pythonhosted.org/packages/fe/cd/f515660b1f32f65df671ddf6f85bfaca621aee177712874dc30a97397977/watchfiles-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:421e29339983e1bebc281fab40d812742268ad057db4aee8c4d2bce0af43b741", size = 394384, upload-time = "2025-10-14T15:04:33.761Z" }, + { url = "https://files.pythonhosted.org/packages/7b/c3/28b7dc99733eab43fca2d10f55c86e03bd6ab11ca31b802abac26b23d161/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e43d39a741e972bab5d8100b5cdacf69db64e34eb19b6e9af162bccf63c5cc6", size = 448789, upload-time = "2025-10-14T15:04:34.679Z" }, + { url = "https://files.pythonhosted.org/packages/4a/24/33e71113b320030011c8e4316ccca04194bf0cbbaeee207f00cbc7d6b9f5/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f537afb3276d12814082a2e9b242bdcf416c2e8fd9f799a737990a1dbe906e5b", size = 460521, upload-time = "2025-10-14T15:04:35.963Z" }, + { url = "https://files.pythonhosted.org/packages/f4/c3/3c9a55f255aa57b91579ae9e98c88704955fa9dac3e5614fb378291155df/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2cd9e04277e756a2e2d2543d65d1e2166d6fd4c9b183f8808634fda23f17b14", size = 488722, upload-time = "2025-10-14T15:04:37.091Z" }, + { url = "https://files.pythonhosted.org/packages/49/36/506447b73eb46c120169dc1717fe2eff07c234bb3232a7200b5f5bd816e9/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f3f58818dc0b07f7d9aa7fe9eb1037aecb9700e63e1f6acfed13e9fef648f5d", size = 596088, upload-time = "2025-10-14T15:04:38.39Z" }, + { url = "https://files.pythonhosted.org/packages/82/ab/5f39e752a9838ec4d52e9b87c1e80f1ee3ccdbe92e183c15b6577ab9de16/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bb9f66367023ae783551042d31b1d7fd422e8289eedd91f26754a66f44d5cff", size = 472923, upload-time = "2025-10-14T15:04:39.666Z" }, + { url = "https://files.pythonhosted.org/packages/af/b9/a419292f05e302dea372fa7e6fda5178a92998411f8581b9830d28fb9edb/watchfiles-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aebfd0861a83e6c3d1110b78ad54704486555246e542be3e2bb94195eabb2606", size = 456080, upload-time = "2025-10-14T15:04:40.643Z" }, + { url = "https://files.pythonhosted.org/packages/b0/c3/d5932fd62bde1a30c36e10c409dc5d54506726f08cb3e1d8d0ba5e2bc8db/watchfiles-1.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5fac835b4ab3c6487b5dbad78c4b3724e26bcc468e886f8ba8cc4306f68f6701", size = 629432, upload-time = "2025-10-14T15:04:41.789Z" }, + { url = "https://files.pythonhosted.org/packages/f7/77/16bddd9779fafb795f1a94319dc965209c5641db5bf1edbbccace6d1b3c0/watchfiles-1.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:399600947b170270e80134ac854e21b3ccdefa11a9529a3decc1327088180f10", size = 623046, upload-time = "2025-10-14T15:04:42.718Z" }, + { url = "https://files.pythonhosted.org/packages/46/ef/f2ecb9a0f342b4bfad13a2787155c6ee7ce792140eac63a34676a2feeef2/watchfiles-1.1.1-cp311-cp311-win32.whl", hash = "sha256:de6da501c883f58ad50db3a32ad397b09ad29865b5f26f64c24d3e3281685849", size = 271473, upload-time = "2025-10-14T15:04:43.624Z" }, + { url = "https://files.pythonhosted.org/packages/94/bc/f42d71125f19731ea435c3948cad148d31a64fccde3867e5ba4edee901f9/watchfiles-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:35c53bd62a0b885bf653ebf6b700d1bf05debb78ad9292cf2a942b23513dc4c4", size = 287598, upload-time = "2025-10-14T15:04:44.516Z" }, + { url = "https://files.pythonhosted.org/packages/57/c9/a30f897351f95bbbfb6abcadafbaca711ce1162f4db95fc908c98a9165f3/watchfiles-1.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:57ca5281a8b5e27593cb7d82c2ac927ad88a96ed406aa446f6344e4328208e9e", size = 277210, upload-time = "2025-10-14T15:04:45.883Z" }, + { url = "https://files.pythonhosted.org/packages/74/d5/f039e7e3c639d9b1d09b07ea412a6806d38123f0508e5f9b48a87b0a76cc/watchfiles-1.1.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:8c89f9f2f740a6b7dcc753140dd5e1ab9215966f7a3530d0c0705c83b401bd7d", size = 404745, upload-time = "2025-10-14T15:04:46.731Z" }, + { url = "https://files.pythonhosted.org/packages/a5/96/a881a13aa1349827490dab2d363c8039527060cfcc2c92cc6d13d1b1049e/watchfiles-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd404be08018c37350f0d6e34676bd1e2889990117a2b90070b3007f172d0610", size = 391769, upload-time = "2025-10-14T15:04:48.003Z" }, + { url = "https://files.pythonhosted.org/packages/4b/5b/d3b460364aeb8da471c1989238ea0e56bec24b6042a68046adf3d9ddb01c/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8526e8f916bb5b9a0a777c8317c23ce65de259422bba5b31325a6fa6029d33af", size = 449374, upload-time = "2025-10-14T15:04:49.179Z" }, + { url = "https://files.pythonhosted.org/packages/b9/44/5769cb62d4ed055cb17417c0a109a92f007114a4e07f30812a73a4efdb11/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2edc3553362b1c38d9f06242416a5d8e9fe235c204a4072e988ce2e5bb1f69f6", size = 459485, upload-time = "2025-10-14T15:04:50.155Z" }, + { url = "https://files.pythonhosted.org/packages/19/0c/286b6301ded2eccd4ffd0041a1b726afda999926cf720aab63adb68a1e36/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30f7da3fb3f2844259cba4720c3fc7138eb0f7b659c38f3bfa65084c7fc7abce", size = 488813, upload-time = "2025-10-14T15:04:51.059Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2b/8530ed41112dd4a22f4dcfdb5ccf6a1baad1ff6eed8dc5a5f09e7e8c41c7/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8979280bdafff686ba5e4d8f97840f929a87ed9cdf133cbbd42f7766774d2aa", size = 594816, upload-time = "2025-10-14T15:04:52.031Z" }, + { url = "https://files.pythonhosted.org/packages/ce/d2/f5f9fb49489f184f18470d4f99f4e862a4b3e9ac2865688eb2099e3d837a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dcc5c24523771db3a294c77d94771abcfcb82a0e0ee8efd910c37c59ec1b31bb", size = 475186, upload-time = "2025-10-14T15:04:53.064Z" }, + { url = "https://files.pythonhosted.org/packages/cf/68/5707da262a119fb06fbe214d82dd1fe4a6f4af32d2d14de368d0349eb52a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db5d7ae38ff20153d542460752ff397fcf5c96090c1230803713cf3147a6803", size = 456812, upload-time = "2025-10-14T15:04:55.174Z" }, + { url = "https://files.pythonhosted.org/packages/66/ab/3cbb8756323e8f9b6f9acb9ef4ec26d42b2109bce830cc1f3468df20511d/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:28475ddbde92df1874b6c5c8aaeb24ad5be47a11f87cde5a28ef3835932e3e94", size = 630196, upload-time = "2025-10-14T15:04:56.22Z" }, + { url = "https://files.pythonhosted.org/packages/78/46/7152ec29b8335f80167928944a94955015a345440f524d2dfe63fc2f437b/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:36193ed342f5b9842edd3532729a2ad55c4160ffcfa3700e0d54be496b70dd43", size = 622657, upload-time = "2025-10-14T15:04:57.521Z" }, + { url = "https://files.pythonhosted.org/packages/0a/bf/95895e78dd75efe9a7f31733607f384b42eb5feb54bd2eb6ed57cc2e94f4/watchfiles-1.1.1-cp312-cp312-win32.whl", hash = "sha256:859e43a1951717cc8de7f4c77674a6d389b106361585951d9e69572823f311d9", size = 272042, upload-time = "2025-10-14T15:04:59.046Z" }, + { url = "https://files.pythonhosted.org/packages/87/0a/90eb755f568de2688cb220171c4191df932232c20946966c27a59c400850/watchfiles-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:91d4c9a823a8c987cce8fa2690923b069966dabb196dd8d137ea2cede885fde9", size = 288410, upload-time = "2025-10-14T15:05:00.081Z" }, + { url = "https://files.pythonhosted.org/packages/36/76/f322701530586922fbd6723c4f91ace21364924822a8772c549483abed13/watchfiles-1.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:a625815d4a2bdca61953dbba5a39d60164451ef34c88d751f6c368c3ea73d404", size = 278209, upload-time = "2025-10-14T15:05:01.168Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f4/f750b29225fe77139f7ae5de89d4949f5a99f934c65a1f1c0b248f26f747/watchfiles-1.1.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:130e4876309e8686a5e37dba7d5e9bc77e6ed908266996ca26572437a5271e18", size = 404321, upload-time = "2025-10-14T15:05:02.063Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f9/f07a295cde762644aa4c4bb0f88921d2d141af45e735b965fb2e87858328/watchfiles-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f3bde70f157f84ece3765b42b4a52c6ac1a50334903c6eaf765362f6ccca88a", size = 391783, upload-time = "2025-10-14T15:05:03.052Z" }, + { url = "https://files.pythonhosted.org/packages/bc/11/fc2502457e0bea39a5c958d86d2cb69e407a4d00b85735ca724bfa6e0d1a/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e0b1fe858430fc0251737ef3824c54027bedb8c37c38114488b8e131cf8219", size = 449279, upload-time = "2025-10-14T15:05:04.004Z" }, + { url = "https://files.pythonhosted.org/packages/e3/1f/d66bc15ea0b728df3ed96a539c777acfcad0eb78555ad9efcaa1274688f0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f27db948078f3823a6bb3b465180db8ebecf26dd5dae6f6180bd87383b6b4428", size = 459405, upload-time = "2025-10-14T15:05:04.942Z" }, + { url = "https://files.pythonhosted.org/packages/be/90/9f4a65c0aec3ccf032703e6db02d89a157462fbb2cf20dd415128251cac0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:059098c3a429f62fc98e8ec62b982230ef2c8df68c79e826e37b895bc359a9c0", size = 488976, upload-time = "2025-10-14T15:05:05.905Z" }, + { url = "https://files.pythonhosted.org/packages/37/57/ee347af605d867f712be7029bb94c8c071732a4b44792e3176fa3c612d39/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfb5862016acc9b869bb57284e6cb35fdf8e22fe59f7548858e2f971d045f150", size = 595506, upload-time = "2025-10-14T15:05:06.906Z" }, + { url = "https://files.pythonhosted.org/packages/a8/78/cc5ab0b86c122047f75e8fc471c67a04dee395daf847d3e59381996c8707/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:319b27255aacd9923b8a276bb14d21a5f7ff82564c744235fc5eae58d95422ae", size = 474936, upload-time = "2025-10-14T15:05:07.906Z" }, + { url = "https://files.pythonhosted.org/packages/62/da/def65b170a3815af7bd40a3e7010bf6ab53089ef1b75d05dd5385b87cf08/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c755367e51db90e75b19454b680903631d41f9e3607fbd941d296a020c2d752d", size = 456147, upload-time = "2025-10-14T15:05:09.138Z" }, + { url = "https://files.pythonhosted.org/packages/57/99/da6573ba71166e82d288d4df0839128004c67d2778d3b566c138695f5c0b/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c22c776292a23bfc7237a98f791b9ad3144b02116ff10d820829ce62dff46d0b", size = 630007, upload-time = "2025-10-14T15:05:10.117Z" }, + { url = "https://files.pythonhosted.org/packages/a8/51/7439c4dd39511368849eb1e53279cd3454b4a4dbace80bab88feeb83c6b5/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3a476189be23c3686bc2f4321dd501cb329c0a0469e77b7b534ee10129ae6374", size = 622280, upload-time = "2025-10-14T15:05:11.146Z" }, + { url = "https://files.pythonhosted.org/packages/95/9c/8ed97d4bba5db6fdcdb2b298d3898f2dd5c20f6b73aee04eabe56c59677e/watchfiles-1.1.1-cp313-cp313-win32.whl", hash = "sha256:bf0a91bfb5574a2f7fc223cf95eeea79abfefa404bf1ea5e339c0c1560ae99a0", size = 272056, upload-time = "2025-10-14T15:05:12.156Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/c14e28429f744a260d8ceae18bf58c1d5fa56b50d006a7a9f80e1882cb0d/watchfiles-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:52e06553899e11e8074503c8e716d574adeeb7e68913115c4b3653c53f9bae42", size = 288162, upload-time = "2025-10-14T15:05:13.208Z" }, + { url = "https://files.pythonhosted.org/packages/dc/61/fe0e56c40d5cd29523e398d31153218718c5786b5e636d9ae8ae79453d27/watchfiles-1.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:ac3cc5759570cd02662b15fbcd9d917f7ecd47efe0d6b40474eafd246f91ea18", size = 277909, upload-time = "2025-10-14T15:05:14.49Z" }, + { url = "https://files.pythonhosted.org/packages/79/42/e0a7d749626f1e28c7108a99fb9bf524b501bbbeb9b261ceecde644d5a07/watchfiles-1.1.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:563b116874a9a7ce6f96f87cd0b94f7faf92d08d0021e837796f0a14318ef8da", size = 403389, upload-time = "2025-10-14T15:05:15.777Z" }, + { url = "https://files.pythonhosted.org/packages/15/49/08732f90ce0fbbc13913f9f215c689cfc9ced345fb1bcd8829a50007cc8d/watchfiles-1.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3ad9fe1dae4ab4212d8c91e80b832425e24f421703b5a42ef2e4a1e215aff051", size = 389964, upload-time = "2025-10-14T15:05:16.85Z" }, + { url = "https://files.pythonhosted.org/packages/27/0d/7c315d4bd5f2538910491a0393c56bf70d333d51bc5b34bee8e68e8cea19/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce70f96a46b894b36eba678f153f052967a0d06d5b5a19b336ab0dbbd029f73e", size = 448114, upload-time = "2025-10-14T15:05:17.876Z" }, + { url = "https://files.pythonhosted.org/packages/c3/24/9e096de47a4d11bc4df41e9d1e61776393eac4cb6eb11b3e23315b78b2cc/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cb467c999c2eff23a6417e58d75e5828716f42ed8289fe6b77a7e5a91036ca70", size = 460264, upload-time = "2025-10-14T15:05:18.962Z" }, + { url = "https://files.pythonhosted.org/packages/cc/0f/e8dea6375f1d3ba5fcb0b3583e2b493e77379834c74fd5a22d66d85d6540/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:836398932192dae4146c8f6f737d74baeac8b70ce14831a239bdb1ca882fc261", size = 487877, upload-time = "2025-10-14T15:05:20.094Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/df24cfc6424a12deb41503b64d42fbea6b8cb357ec62ca84a5a3476f654a/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:743185e7372b7bc7c389e1badcc606931a827112fbbd37f14c537320fca08620", size = 595176, upload-time = "2025-10-14T15:05:21.134Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b5/853b6757f7347de4e9b37e8cc3289283fb983cba1ab4d2d7144694871d9c/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afaeff7696e0ad9f02cbb8f56365ff4686ab205fcf9c4c5b6fdfaaa16549dd04", size = 473577, upload-time = "2025-10-14T15:05:22.306Z" }, + { url = "https://files.pythonhosted.org/packages/e1/f7/0a4467be0a56e80447c8529c9fce5b38eab4f513cb3d9bf82e7392a5696b/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7eb7da0eb23aa2ba036d4f616d46906013a68caf61b7fdbe42fc8b25132e77", size = 455425, upload-time = "2025-10-14T15:05:23.348Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e0/82583485ea00137ddf69bc84a2db88bd92ab4a6e3c405e5fb878ead8d0e7/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:831a62658609f0e5c64178211c942ace999517f5770fe9436be4c2faeba0c0ef", size = 628826, upload-time = "2025-10-14T15:05:24.398Z" }, + { url = "https://files.pythonhosted.org/packages/28/9a/a785356fccf9fae84c0cc90570f11702ae9571036fb25932f1242c82191c/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f9a2ae5c91cecc9edd47e041a930490c31c3afb1f5e6d71de3dc671bfaca02bf", size = 622208, upload-time = "2025-10-14T15:05:25.45Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f4/0872229324ef69b2c3edec35e84bd57a1289e7d3fe74588048ed8947a323/watchfiles-1.1.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:d1715143123baeeaeadec0528bb7441103979a1d5f6fd0e1f915383fea7ea6d5", size = 404315, upload-time = "2025-10-14T15:05:26.501Z" }, + { url = "https://files.pythonhosted.org/packages/7b/22/16d5331eaed1cb107b873f6ae1b69e9ced582fcf0c59a50cd84f403b1c32/watchfiles-1.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:39574d6370c4579d7f5d0ad940ce5b20db0e4117444e39b6d8f99db5676c52fd", size = 390869, upload-time = "2025-10-14T15:05:27.649Z" }, + { url = "https://files.pythonhosted.org/packages/b2/7e/5643bfff5acb6539b18483128fdc0ef2cccc94a5b8fbda130c823e8ed636/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7365b92c2e69ee952902e8f70f3ba6360d0d596d9299d55d7d386df84b6941fb", size = 449919, upload-time = "2025-10-14T15:05:28.701Z" }, + { url = "https://files.pythonhosted.org/packages/51/2e/c410993ba5025a9f9357c376f48976ef0e1b1aefb73b97a5ae01a5972755/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bfff9740c69c0e4ed32416f013f3c45e2ae42ccedd1167ef2d805c000b6c71a5", size = 460845, upload-time = "2025-10-14T15:05:30.064Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a4/2df3b404469122e8680f0fcd06079317e48db58a2da2950fb45020947734/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b27cf2eb1dda37b2089e3907d8ea92922b673c0c427886d4edc6b94d8dfe5db3", size = 489027, upload-time = "2025-10-14T15:05:31.064Z" }, + { url = "https://files.pythonhosted.org/packages/ea/84/4587ba5b1f267167ee715b7f66e6382cca6938e0a4b870adad93e44747e6/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526e86aced14a65a5b0ec50827c745597c782ff46b571dbfe46192ab9e0b3c33", size = 595615, upload-time = "2025-10-14T15:05:32.074Z" }, + { url = "https://files.pythonhosted.org/packages/6a/0f/c6988c91d06e93cd0bb3d4a808bcf32375ca1904609835c3031799e3ecae/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04e78dd0b6352db95507fd8cb46f39d185cf8c74e4cf1e4fbad1d3df96faf510", size = 474836, upload-time = "2025-10-14T15:05:33.209Z" }, + { url = "https://files.pythonhosted.org/packages/b4/36/ded8aebea91919485b7bbabbd14f5f359326cb5ec218cd67074d1e426d74/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c85794a4cfa094714fb9c08d4a218375b2b95b8ed1666e8677c349906246c05", size = 455099, upload-time = "2025-10-14T15:05:34.189Z" }, + { url = "https://files.pythonhosted.org/packages/98/e0/8c9bdba88af756a2fce230dd365fab2baf927ba42cd47521ee7498fd5211/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:74d5012b7630714b66be7b7b7a78855ef7ad58e8650c73afc4c076a1f480a8d6", size = 630626, upload-time = "2025-10-14T15:05:35.216Z" }, + { url = "https://files.pythonhosted.org/packages/2a/84/a95db05354bf2d19e438520d92a8ca475e578c647f78f53197f5a2f17aaf/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:8fbe85cb3201c7d380d3d0b90e63d520f15d6afe217165d7f98c9c649654db81", size = 622519, upload-time = "2025-10-14T15:05:36.259Z" }, + { url = "https://files.pythonhosted.org/packages/1d/ce/d8acdc8de545de995c339be67711e474c77d643555a9bb74a9334252bd55/watchfiles-1.1.1-cp314-cp314-win32.whl", hash = "sha256:3fa0b59c92278b5a7800d3ee7733da9d096d4aabcfabb9a928918bd276ef9b9b", size = 272078, upload-time = "2025-10-14T15:05:37.63Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c9/a74487f72d0451524be827e8edec251da0cc1fcf111646a511ae752e1a3d/watchfiles-1.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:c2047d0b6cea13b3316bdbafbfa0c4228ae593d995030fda39089d36e64fc03a", size = 287664, upload-time = "2025-10-14T15:05:38.95Z" }, + { url = "https://files.pythonhosted.org/packages/df/b8/8ac000702cdd496cdce998c6f4ee0ca1f15977bba51bdf07d872ebdfc34c/watchfiles-1.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:842178b126593addc05acf6fce960d28bc5fae7afbaa2c6c1b3a7b9460e5be02", size = 277154, upload-time = "2025-10-14T15:05:39.954Z" }, + { url = "https://files.pythonhosted.org/packages/47/a8/e3af2184707c29f0f14b1963c0aace6529f9d1b8582d5b99f31bbf42f59e/watchfiles-1.1.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:88863fbbc1a7312972f1c511f202eb30866370ebb8493aef2812b9ff28156a21", size = 403820, upload-time = "2025-10-14T15:05:40.932Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/e47e307c2f4bd75f9f9e8afbe3876679b18e1bcec449beca132a1c5ffb2d/watchfiles-1.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:55c7475190662e202c08c6c0f4d9e345a29367438cf8e8037f3155e10a88d5a5", size = 390510, upload-time = "2025-10-14T15:05:41.945Z" }, + { url = "https://files.pythonhosted.org/packages/d5/a0/ad235642118090f66e7b2f18fd5c42082418404a79205cdfca50b6309c13/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f53fa183d53a1d7a8852277c92b967ae99c2d4dcee2bfacff8868e6e30b15f7", size = 448408, upload-time = "2025-10-14T15:05:43.385Z" }, + { url = "https://files.pythonhosted.org/packages/df/85/97fa10fd5ff3332ae17e7e40e20784e419e28521549780869f1413742e9d/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6aae418a8b323732fa89721d86f39ec8f092fc2af67f4217a2b07fd3e93c6101", size = 458968, upload-time = "2025-10-14T15:05:44.404Z" }, + { url = "https://files.pythonhosted.org/packages/47/c2/9059c2e8966ea5ce678166617a7f75ecba6164375f3b288e50a40dc6d489/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f096076119da54a6080e8920cbdaac3dbee667eb91dcc5e5b78840b87415bd44", size = 488096, upload-time = "2025-10-14T15:05:45.398Z" }, + { url = "https://files.pythonhosted.org/packages/94/44/d90a9ec8ac309bc26db808a13e7bfc0e4e78b6fc051078a554e132e80160/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00485f441d183717038ed2e887a7c868154f216877653121068107b227a2f64c", size = 596040, upload-time = "2025-10-14T15:05:46.502Z" }, + { url = "https://files.pythonhosted.org/packages/95/68/4e3479b20ca305cfc561db3ed207a8a1c745ee32bf24f2026a129d0ddb6e/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a55f3e9e493158d7bfdb60a1165035f1cf7d320914e7b7ea83fe22c6023b58fc", size = 473847, upload-time = "2025-10-14T15:05:47.484Z" }, + { url = "https://files.pythonhosted.org/packages/4f/55/2af26693fd15165c4ff7857e38330e1b61ab8c37d15dc79118cdba115b7a/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c91ed27800188c2ae96d16e3149f199d62f86c7af5f5f4d2c61a3ed8cd3666c", size = 455072, upload-time = "2025-10-14T15:05:48.928Z" }, + { url = "https://files.pythonhosted.org/packages/66/1d/d0d200b10c9311ec25d2273f8aad8c3ef7cc7ea11808022501811208a750/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:311ff15a0bae3714ffb603e6ba6dbfba4065ab60865d15a6ec544133bdb21099", size = 629104, upload-time = "2025-10-14T15:05:49.908Z" }, + { url = "https://files.pythonhosted.org/packages/e3/bd/fa9bb053192491b3867ba07d2343d9f2252e00811567d30ae8d0f78136fe/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a916a2932da8f8ab582f242c065f5c81bed3462849ca79ee357dd9551b0e9b01", size = 622112, upload-time = "2025-10-14T15:05:50.941Z" }, + { url = "https://files.pythonhosted.org/packages/ba/4c/a888c91e2e326872fa4705095d64acd8aa2fb9c1f7b9bd0588f33850516c/watchfiles-1.1.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:17ef139237dfced9da49fb7f2232c86ca9421f666d78c264c7ffca6601d154c3", size = 409611, upload-time = "2025-10-14T15:06:05.809Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c7/5420d1943c8e3ce1a21c0a9330bcf7edafb6aa65d26b21dbb3267c9e8112/watchfiles-1.1.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:672b8adf25b1a0d35c96b5888b7b18699d27d4194bac8beeae75be4b7a3fc9b2", size = 396889, upload-time = "2025-10-14T15:06:07.035Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e5/0072cef3804ce8d3aaddbfe7788aadff6b3d3f98a286fdbee9fd74ca59a7/watchfiles-1.1.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77a13aea58bc2b90173bc69f2a90de8e282648939a00a602e1dc4ee23e26b66d", size = 451616, upload-time = "2025-10-14T15:06:08.072Z" }, + { url = "https://files.pythonhosted.org/packages/83/4e/b87b71cbdfad81ad7e83358b3e447fedd281b880a03d64a760fe0a11fc2e/watchfiles-1.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b495de0bb386df6a12b18335a0285dda90260f51bdb505503c02bcd1ce27a8b", size = 458413, upload-time = "2025-10-14T15:06:09.209Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8e/e500f8b0b77be4ff753ac94dc06b33d8f0d839377fee1b78e8c8d8f031bf/watchfiles-1.1.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:db476ab59b6765134de1d4fe96a1a9c96ddf091683599be0f26147ea1b2e4b88", size = 408250, upload-time = "2025-10-14T15:06:10.264Z" }, + { url = "https://files.pythonhosted.org/packages/bd/95/615e72cd27b85b61eec764a5ca51bd94d40b5adea5ff47567d9ebc4d275a/watchfiles-1.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:89eef07eee5e9d1fda06e38822ad167a044153457e6fd997f8a858ab7564a336", size = 396117, upload-time = "2025-10-14T15:06:11.28Z" }, + { url = "https://files.pythonhosted.org/packages/c9/81/e7fe958ce8a7fb5c73cc9fb07f5aeaf755e6aa72498c57d760af760c91f8/watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce19e06cbda693e9e7686358af9cd6f5d61312ab8b00488bc36f5aabbaf77e24", size = 450493, upload-time = "2025-10-14T15:06:12.321Z" }, + { url = "https://files.pythonhosted.org/packages/6e/d4/ed38dd3b1767193de971e694aa544356e63353c33a85d948166b5ff58b9e/watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e6f39af2eab0118338902798b5aa6664f46ff66bc0280de76fca67a7f262a49", size = 457546, upload-time = "2025-10-14T15:06:13.372Z" }, ] [[package]] -- 2.49.1 From dd2e7d221ce5052ce59a9b67d739ac9b72dd113c Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Sat, 6 Dec 2025 17:05:42 -0300 Subject: [PATCH 91/96] chore: updates uv version in github action file --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fc2a14c..d794683 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,7 +32,7 @@ jobs: uses: astral-sh/setup-uv@v5 with: # Install a specific version of uv. - version: "0.6.14" + version: "0.9.15" enable-cache: true cache-dependency-glob: "uv.lock" python-version: ${{ matrix.python-version }} -- 2.49.1 From eee32a02ae91093170874265efa598de2674b7fb Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Mon, 8 Dec 2025 18:56:01 -0300 Subject: [PATCH 92/96] chore: adds python3.14 to metadata --- jambo/schema_converter.py | 8 +++++--- pyproject.toml | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/jambo/schema_converter.py b/jambo/schema_converter.py index 2e46828..00819b4 100644 --- a/jambo/schema_converter.py +++ b/jambo/schema_converter.py @@ -5,7 +5,7 @@ from jambo.types import JSONSchema, RefCacheDict from jsonschema.exceptions import SchemaError from jsonschema.validators import validator_for from pydantic import BaseModel -from typing_extensions import Optional +from typing_extensions import MutableMapping, Optional class SchemaConverter: @@ -17,8 +17,10 @@ class SchemaConverter: fields and types. The generated model can be used for data validation and serialization. """ + _namespace_registry: MutableMapping[str, RefCacheDict] + def __init__( - self, namespace_registry: Optional[dict[str, RefCacheDict]] = None + self, namespace_registry: Optional[MutableMapping[str, RefCacheDict]] = None ) -> None: if namespace_registry is None: namespace_registry = dict() @@ -64,7 +66,7 @@ class SchemaConverter: :return: The generated Pydantic model. """ if ref_cache is None: - ref_cache = dict() + ref_cache: RefCacheDict = dict() try: validator = validator_for(schema) diff --git a/pyproject.toml b/pyproject.toml index 199edcd..2f1ca77 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", ] license = "MIT" readme = "README.md" -- 2.49.1 From 02a28c95863f8e78fb7170ee13d73c633bd189e4 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Mon, 8 Dec 2025 18:56:55 -0300 Subject: [PATCH 93/96] chore: updates uv version in ci --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d794683..a7a9c63 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -69,7 +69,7 @@ jobs: uses: astral-sh/setup-uv@v5 with: # Install a specific version of uv. - version: "0.6.14" + version: "0.9.15" enable-cache: true cache-dependency-glob: "uv.lock" -- 2.49.1 From 19d1f72951eb25e4f0dfafb2737f9305dbb86c69 Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Mon, 8 Dec 2025 19:04:08 -0300 Subject: [PATCH 94/96] fix: minor fix in internal typing --- jambo/schema_converter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jambo/schema_converter.py b/jambo/schema_converter.py index 00819b4..302b190 100644 --- a/jambo/schema_converter.py +++ b/jambo/schema_converter.py @@ -66,7 +66,7 @@ class SchemaConverter: :return: The generated Pydantic model. """ if ref_cache is None: - ref_cache: RefCacheDict = dict() + ref_cache = dict() try: validator = validator_for(schema) -- 2.49.1 From e12396477ff0044f4e177d1560f0ca4320525bb2 Mon Sep 17 00:00:00 2001 From: Michael Terry Date: Wed, 14 Jan 2026 13:14:48 -0500 Subject: [PATCH 95/96] feat: support object-level descriptions By saving them as a pydantic model docstring. Fixes https://github.com/HideyoshiNakazone/jambo/issues/77 --- jambo/parser/object_type_parser.py | 6 +++++- jambo/schema_converter.py | 1 + tests/parser/test_object_type_parser.py | 2 ++ tests/test_schema_converter.py | 1 + 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/jambo/parser/object_type_parser.py b/jambo/parser/object_type_parser.py index 82ca587..5d611cd 100644 --- a/jambo/parser/object_type_parser.py +++ b/jambo/parser/object_type_parser.py @@ -22,6 +22,7 @@ class ObjectTypeParser(GenericTypeParser): name, properties.get("properties", {}), properties.get("required", []), + description=properties.get("description"), **kwargs, ) type_properties = self.mappings_properties_builder(properties, **kwargs) @@ -48,6 +49,7 @@ class ObjectTypeParser(GenericTypeParser): name: str, properties: dict[str, JSONSchema], required_keys: list[str], + description: str | None = None, **kwargs: Unpack[TypeParserOptions], ) -> type[BaseModel]: """ @@ -74,7 +76,9 @@ class ObjectTypeParser(GenericTypeParser): model_config = ConfigDict(validate_assignment=True) fields = cls._parse_properties(name, properties, required_keys, **kwargs) - model = create_model(name, __config__=model_config, **fields) # type: ignore + model = create_model( + name, __config__=model_config, __doc__=description, **fields + ) # type: ignore ref_cache[name] = model return model diff --git a/jambo/schema_converter.py b/jambo/schema_converter.py index 302b190..638c068 100644 --- a/jambo/schema_converter.py +++ b/jambo/schema_converter.py @@ -89,6 +89,7 @@ class SchemaConverter: schema["title"], schema.get("properties", {}), schema.get("required", []), + description=schema.get("description"), context=schema, ref_cache=ref_cache, required=True, diff --git a/tests/parser/test_object_type_parser.py b/tests/parser/test_object_type_parser.py index 975f85d..3bfd73a 100644 --- a/tests/parser/test_object_type_parser.py +++ b/tests/parser/test_object_type_parser.py @@ -24,6 +24,7 @@ class TestObjectTypeParser(TestCase): properties = { "type": "object", + "description": "obj desc", "properties": { "name": {"type": "string"}, "age": {"type": "integer"}, @@ -33,6 +34,7 @@ class TestObjectTypeParser(TestCase): Model, _args = parser.from_properties_impl( "placeholder", properties, ref_cache={} ) + self.assertEqual(Model.__doc__, "obj desc") obj = Model(name="name", age=10) diff --git a/tests/test_schema_converter.py b/tests/test_schema_converter.py index e9bcb5b..112d861 100644 --- a/tests/test_schema_converter.py +++ b/tests/test_schema_converter.py @@ -274,6 +274,7 @@ class TestSchemaConverter(TestCase): } model = self.converter.build_with_cache(schema) + self.assertEqual(model.__doc__, "A person") obj = model(address={"street": "123 Main St", "city": "Springfield"}) -- 2.49.1 From 2fd092cd8eb1e1376c9e3cc6b14865944712a7da Mon Sep 17 00:00:00 2001 From: Michael Terry Date: Wed, 14 Jan 2026 13:58:56 -0500 Subject: [PATCH 96/96] feat: support enum-level descriptions By saving them as a enum class docstring. --- jambo/parser/enum_type_parser.py | 2 ++ tests/parser/test_enum_type_parser.py | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/jambo/parser/enum_type_parser.py b/jambo/parser/enum_type_parser.py index 3d0fee3..e96aff5 100644 --- a/jambo/parser/enum_type_parser.py +++ b/jambo/parser/enum_type_parser.py @@ -36,6 +36,8 @@ class EnumTypeParser(GenericTypeParser): # Create a new Enum type dynamically enum_type = Enum(name, {str(value).upper(): value for value in enum_values}) # type: ignore + enum_type.__doc__ = properties.get("description") + parsed_properties = self.mappings_properties_builder(properties, **kwargs) if "default" in parsed_properties and parsed_properties["default"] is not None: diff --git a/tests/parser/test_enum_type_parser.py b/tests/parser/test_enum_type_parser.py index 7665007..e180abf 100644 --- a/tests/parser/test_enum_type_parser.py +++ b/tests/parser/test_enum_type_parser.py @@ -49,6 +49,20 @@ class TestEnumTypeParser(TestCase): ) self.assertEqual(parsed_properties, {"default": None}) + def test_enum_type_parser_creates_enum_with_description(self): + parser = EnumTypeParser() + + schema = { + "description": "an enum", + "enum": ["value1"], + } + + parsed_type, parsed_properties = parser.from_properties_impl( + "TestEnum", + schema, + ) + self.assertEqual(parsed_type.__doc__, "an enum") + def test_enum_type_parser_creates_enum_with_default(self): parser = EnumTypeParser() -- 2.49.1