From fb081bc983db99bdc4d5bf816403216a5c8fc61b Mon Sep 17 00:00:00 2001 From: felixeisenmenger Date: Wed, 24 Jun 2026 14:51:39 +0200 Subject: [PATCH 1/9] - performance improvements by moving to a dict instead of using a list - create combined annotations over all existing functions with the same name --- Improvements.md | 40 +++++++ pyproject.toml | 1 + src/strongtyping_pyoverload/class_tools.py | 116 +++++++++++++++++---- tests/module_based_test_file.py | 11 +- tests/test_ai_metadata.py | 42 ++++++++ tests/test_annotated_support.py | 57 ++++++++++ tests/test_namespace_inheritance.py | 48 +++++++++ tests/test_performance_optimization.py | 61 +++++++++++ tests/test_pydantic_integration.py | 54 ++++++++++ uv.lock | 113 ++++++++++++++++++++ 10 files changed, 521 insertions(+), 22 deletions(-) create mode 100644 Improvements.md create mode 100644 tests/test_ai_metadata.py create mode 100644 tests/test_annotated_support.py create mode 100644 tests/test_namespace_inheritance.py create mode 100644 tests/test_performance_optimization.py create mode 100644 tests/test_pydantic_integration.py diff --git a/Improvements.md b/Improvements.md new file mode 100644 index 0000000..5a12b27 --- /dev/null +++ b/Improvements.md @@ -0,0 +1,40 @@ +### Project Analysis & Suggestions for `strongtyping-pyoverload` + +As a seasoned Software Developer and Open Source Contributor, I've analyzed `strongtyping-pyoverload`. The project addresses a genuine gap in Python—true runtime polymorphism that goes beyond what `typing.overload` (static only) and `functools.singledispatch` (limited to the first argument) offer. + +To make this project more valuable in the current ecosystem dominated by **Pydantic**, **FastAPI**, and **AI-driven development**, I suggest the following enhancements: + +--- + +#### 1. Native Pydantic Integration (Validation-First Overloading) +Today's developers use Pydantic to ensure data integrity. Instead of just checking types, `overload` could leverage Pydantic's validation logic. +- **Suggestion:** If a parameter is hinted with a Pydantic model, the dispatcher should attempt validation. If it fails, it moves to the next overload. +- **Value:** This allows for "Schema-based Overloading." +- **Example:** +```python +@overload +def create_user(data: UserCreateSchema): # Pydantic Model + ... + +@overload +def create_user(data: dict): + ... +``` + +#### 2. Performance Optimization for FastAPI (Production Readiness) +FastAPI's strength is speed. Runtime type checking via `inspect` and string parsing (like `extract_class_name_from_func`) is expensive. +- **Suggestion:** + - Move from a global list `__override_items__` to a more structured, hashed registry (e.g., a dictionary keyed by `(module, qualname, arg_count)`). + - Implement a "Warm-up" phase or JIT-style dispatching where the best match is mapped after the first call to avoid repeated logic. +- **Value:** Makes the library viable for high-throughput API endpoints. + +#### 3. "AI-Friendly" Explicit Metadata +AI coding tools (Copilot, Cursor) and IDEs often struggle with dynamic decorators that hide signatures. +- **Suggestion:** + - Ensure `inner` properly updates `__annotations__` and `__signature__` to reflect a merged Union of all overloads. + - Provide a PEP 561 `py.typed` marker if not already present to help static analyzers understand the runtime behavior. +- **Value:** Improves AI's ability to suggest the correct overload while typing. + + +### Summary of Strategic Direction +The goal should be to transform `strongtyping-pyoverload` from a "utility for cleaner code" into a **"Type-Safe Dispatch Engine"** that feels like a native part of the Pydantic/FastAPI stack. This shift from simple `isinstance` checks to "Validation-based Dispatch" would make it a unique and powerful tool in the modern Pythonista's arsenal. diff --git a/pyproject.toml b/pyproject.toml index e23f769..ec816d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ dependencies = [ [dependency-groups] dev = [ + "pydantic>=2.13.4", "pytest>=9.0.3", "pytest-cov>=7.1.0", "ruff>=0.15.12", diff --git a/src/strongtyping_pyoverload/class_tools.py b/src/strongtyping_pyoverload/class_tools.py index c50cceb..fd0e674 100644 --- a/src/strongtyping_pyoverload/class_tools.py +++ b/src/strongtyping_pyoverload/class_tools.py @@ -1,5 +1,6 @@ import inspect import pprint +import typing from collections import defaultdict from functools import wraps from types import MethodType @@ -7,7 +8,7 @@ from strongtyping.cached_dict import CachedDict from strongtyping.strong_typing_utils import check_type -__override_items__ = [] +__override_items__ = defaultdict(list) ANY = object() IGNORE_CHARS = " str: return self.func_name_ + @property + def lookup_key(self) -> tuple[str, str]: + return self.cls_name_, self.func_name_ + @property def is_keyword_only(self): if not self.params_: @@ -175,23 +179,18 @@ def generate_parameter_infos(func: MethodType): return func_params.values() -def generate_docstring(func_name: str): +def generate_docstring(lookup_key: tuple[str, str]): return "\n".join( obj.func_.__doc__ - for obj in __override_items__ - if obj.name == func_name and obj.func_.__doc__ + for obj in __override_items__[lookup_key] + if obj.func_.__doc__ ) def find_corresponding_func(func_name, cls_name, args, kwargs): pos_or_kwarg_funcs = [] - data = defaultdict(list) - for obj in __override_items__: - if obj.name == func_name: - data[obj.cls_name_].append(obj) - subclass = data.pop(cls_name, []) - [subclass.extend(obj) for obj in list(data.values())] - for info in subclass: + data = __override_items__[(cls_name, func_name)] + for info in data: if info.is_keyword_only: if info == kwargs: return info.func_ @@ -222,15 +221,35 @@ def handle_error(is_module_function, func_class_name, cls_, args, kwargs, /): raise AttributeError(f"No function was found which matches your parameters `{info}`") +def generate_annotations(lookup_key): + data = __override_items__[lookup_key] + res = data[0].func_.__annotations__ + for obj in data[1:]: + for key, val in obj.func_.__annotations__.items(): + try: + res[key] = set([*res[key], val]) + except TypeError: + res[key] = set([res[key], val]) + except KeyError: + res[key] = val + for key, val in res.items(): + try: + if len(val) > 1: + res[key] = typing.Union[*val] + except TypeError: + continue + return res + + def overload(func): func_info = FuncInfo(func, generate_parameter_infos(func)) - __override_items__.append(func_info) + __override_items__[func_info.lookup_key].append(func_info) cached_dict = CachedDict() @wraps(func) def inner(cls_=None, *args, **kwargs): is_module_function = is_module(func, cls_) if cls_ is not None else False - func_class_name = FuncInfo.extract_class_name_from_func(str(func)) + func_class_name = FuncInfo.extract_class_name_from_func(func) if is_module_function: cached_key = f"{func.__name__}_{func_class_name}_{args}_{kwargs}" else: @@ -261,5 +280,64 @@ def inner(cls_=None, *args, **kwargs): else: raise - inner.__doc__ = generate_docstring(func.__name__) + inner.__doc__ = generate_docstring(func_info.lookup_key) + inner.__annotations__ = generate_annotations(func_info.lookup_key) + inner.__signature__ = generate_signature(func_info.lookup_key, inner.__annotations__) return inner + + +def generate_signature(lookup_key, merged_annotations): + data = __override_items__[lookup_key] + # Collect parameter names in order of first appearance across overloads, + # skipping 'self'. + seen = [] + kinds = {} + defaults = {} + has_self = False + for info in data: + try: + sig = inspect.signature(info.func_) + except (TypeError, ValueError): + continue + for name, param in sig.parameters.items(): + if name == "self": + has_self = True + continue + if name not in seen: + seen.append(name) + kinds[name] = param.kind + if param.default is not inspect.Parameter.empty: + defaults[name] = param.default + + parameters = [] + if has_self: + parameters.append( + inspect.Parameter("self", inspect.Parameter.POSITIONAL_OR_KEYWORD) + ) + for name in seen: + kind = kinds.get(name, inspect.Parameter.POSITIONAL_OR_KEYWORD) + if kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD): + annotation = inspect.Parameter.empty + else: + annotation = merged_annotations.get(name, inspect.Parameter.empty) + if name in defaults: + parameters.append( + inspect.Parameter(name, kind, default=defaults[name], annotation=annotation) + ) + else: + parameters.append(inspect.Parameter(name, kind, annotation=annotation)) + + return_annotation = merged_annotations.get("return", inspect.Signature.empty) + try: + return inspect.Signature(parameters=parameters, return_annotation=return_annotation) + except ValueError: + # Fallback: sort parameters by kind to satisfy ordering constraints. + kind_order = { + inspect.Parameter.POSITIONAL_ONLY: 0, + inspect.Parameter.POSITIONAL_OR_KEYWORD: 1, + inspect.Parameter.VAR_POSITIONAL: 2, + inspect.Parameter.KEYWORD_ONLY: 3, + inspect.Parameter.VAR_KEYWORD: 4, + } + parameters.sort(key=lambda p: kind_order[p.kind]) + return inspect.Signature(parameters=parameters, return_annotation=return_annotation) diff --git a/tests/module_based_test_file.py b/tests/module_based_test_file.py index 209a0cb..9837877 100644 --- a/tests/module_based_test_file.py +++ b/tests/module_based_test_file.py @@ -10,19 +10,24 @@ def module_func(): @overload def module_func(a: int, b: int): - return 1 + return a * b @overload def module_func(a: str, b: str): - return 2 + return a + b @overload def module_func(a: int, b: str): - return 3 + return b * a @overload def module_func(a: int, b: str, c: int): return 4 + + +if __name__ == "__main__": + print(module_func(1, 2)) + print(module_func) diff --git a/tests/test_ai_metadata.py b/tests/test_ai_metadata.py new file mode 100644 index 0000000..8995efd --- /dev/null +++ b/tests/test_ai_metadata.py @@ -0,0 +1,42 @@ +import inspect +from typing import Union +from strongtyping_pyoverload import overload + +class MetadataExample: + @overload + def action(self, x: int) -> int: + """Handle integer.""" + return x + + @overload + def action(self, x: str) -> str: + """Handle string.""" + return x + +def test_signature_merging(): + example = MetadataExample() + sig = inspect.signature(example.action) + + # The signature should reflect that it can take int or str + # and return int or str (or a Union of them) + params = sig.parameters + assert "x" in params + + # Ideally, the annotation should be a Union of the overloads + # This helps AI tools like Copilot/Cursor provide better suggestions + annotation = params["x"].annotation + assert annotation in (Union[int, str], "Union[int, str]", int | str) + +def test_docstring_aggregation(): + example = MetadataExample() + doc = example.action.__doc__ + + # Docstring should contain info from all overloads + assert "Handle integer." in doc + assert "Handle string." in doc + +def test_annotations_update(): + example = MetadataExample() + # __annotations__ should be present and accurate on the wrapped function + expected_keys = {"x", "return"} + assert all(k in example.action.__annotations__ for k in expected_keys) diff --git a/tests/test_annotated_support.py b/tests/test_annotated_support.py new file mode 100644 index 0000000..44aee84 --- /dev/null +++ b/tests/test_annotated_support.py @@ -0,0 +1,57 @@ +import pytest +from typing import Annotated +from strongtyping_pyoverload import overload + +# Simulating validators or using actual ones if available +def gt_zero(v): return v > 0 +def lt_zero(v): return v < 0 + +class GuardedExample: + @overload + def process(self, x: Annotated[int, gt_zero]): + return "Positive" + + @overload + def process(self, x: Annotated[int, lt_zero]): + return "Negative" + + @overload + def process(self, x: int): + return "Zero" + +def test_annotated_guard_dispatch(): + ex = GuardedExample() + assert ex.process(10) == "Positive" + assert ex.process(-5) == "Negative" + assert ex.process(0) == "Zero" + +def test_complex_annotated_types(): + class ComplexService: + @overload + def handle(self, data: Annotated[list[int], "length > 0"]): + return sum(data) + + @overload + def handle(self, data: list): + return 0 + + service = ComplexService() + assert service.handle([1, 2, 3]) == 6 + assert service.handle([]) == 0 + +def test_annotated_with_metadata_fallback(): + # Ensure that if Annotated is used but no specific guard matches, + # it still respects the base type + class FallbackExample: + @overload + def do(self, val: Annotated[str, "priority"]): + return f"Priority {val}" + + @overload + def do(self, val: str): + return f"Normal {val}" + + ex = FallbackExample() + # If the dispatcher can't evaluate the string "priority", + # it should at least match str + assert "Normal test" in ex.do("test") or "Priority test" in ex.do("test") diff --git a/tests/test_namespace_inheritance.py b/tests/test_namespace_inheritance.py new file mode 100644 index 0000000..c8994dc --- /dev/null +++ b/tests/test_namespace_inheritance.py @@ -0,0 +1,48 @@ +import pytest +from strongtyping_pyoverload import overload + +class Outer: + class Inner: + @overload + def method(self, x: int): + return f"Inner int {x}" + + @overload + def method(self, x: str): + return f"Inner str {x}" + +class Base: + @overload + def calc(self, x: int): + return x + 1 + +class Derived(Base): + @overload + def calc(self, x: str): + return f"Derived {x}" + + # Base.calc(int) should still be accessible if not overridden + # Currently the implementation might struggle with this depending on how it collects overloads + +def test_nested_class_overload(): + inner = Outer.Inner() + assert inner.method(1) == "Inner int 1" + assert inner.method("a") == "Inner str a" + +def test_inheritance_cross_module_or_complex(): + d = Derived() + assert d.calc("test") == "Derived test" + # Testing if it can find the base class overload correctly without explicit redeclaration + try: + assert d.calc(1) == 2 + except AttributeError: + pytest.fail("Should have found Base.calc(int)") + +def test_qualname_consistency(): + # Verify that we use __qualname__ instead of string parsing for better reliability + from strongtyping_pyoverload.class_tools import FuncInfo + + def dummy(): pass + # This test checks the intent of moving to better naming + assert hasattr(dummy, "__qualname__") + # In a real implementation, FuncInfo would store and use __qualname__ diff --git a/tests/test_performance_optimization.py b/tests/test_performance_optimization.py new file mode 100644 index 0000000..3995b2a --- /dev/null +++ b/tests/test_performance_optimization.py @@ -0,0 +1,61 @@ +import time +import pytest +from strongtyping_pyoverload import overload + +class HeavyService: + @overload + def compute(self, x: int): + return x * x + + @overload + def compute(self, x: str): + return x.upper() + +def test_dispatch_latency_reduction(): + service = HeavyService() + + # First call: slower due to inspection/discovery + start_first = time.perf_counter() + service.compute(10) + end_first = time.perf_counter() + first_duration = end_first - start_first + + # Subsequent calls: should be significantly faster due to hashed registry/JIT dispatch + latencies = [] + for _ in range(100): + start = time.perf_counter() + service.compute(10) + latencies.append(time.perf_counter() - start) + + avg_subsequent_latency = sum(latencies) / len(latencies) + + # This is a soft assertion but represents the goal + assert avg_subsequent_latency < first_duration / 2 + +def test_warmup_phase(): + service = HeavyService() + # Hypothetical API for manual warmup + if hasattr(service.compute, "warmup"): + service.compute.warmup(int) + + start = time.perf_counter() + service.compute(10) + duration = time.perf_counter() - start + + # Should be fast immediately + assert duration < 0.001 + +def test_registry_efficiency(): + # Verify that we are not doing full list scans on every call + # This might require internal inspection if the feature is implemented + from strongtyping_pyoverload.class_tools import __override_items__ + + # Before improvement, this is a list. After, it should ideally be a more efficient structure + # or the dispatcher should use a cache (which it currently does partially, but let's test efficiency) + service = HeavyService() + + # Calling with many different types to ensure registry scales well + # (Simplified for now) + for i in range(10): + service.compute(i) + service.compute(str(i)) diff --git a/tests/test_pydantic_integration.py b/tests/test_pydantic_integration.py new file mode 100644 index 0000000..1f69de9 --- /dev/null +++ b/tests/test_pydantic_integration.py @@ -0,0 +1,54 @@ +import pytest +from pydantic import BaseModel, Field, ValidationError +from strongtyping_pyoverload import overload + +class UserCreateSchema(BaseModel): + name: str + age: int + +class UserUpdateSchema(BaseModel): + id: int + name: str | None = None + +class DataHandler: + @overload + def process(self, data: UserCreateSchema): + return f"Created user {data.name}" + + @overload + def process(self, data: UserUpdateSchema): + return f"Updated user {data.id}" + + @overload + def process(self, data: dict): + return "Processed dict" + +def test_pydantic_schema_dispatching(): + handler = DataHandler() + + # Test dispatch to UserCreateSchema + res1 = handler.process({"name": "Alice", "age": 30}) + assert res1 == "Created user Alice" + + # Test dispatch to UserUpdateSchema + res2 = handler.process({"id": 1, "name": "Bob"}) + assert res2 == "Updated user 1" + + # Test dispatch to dict (when it doesn't match schemas) + res3 = handler.process({"something": "else"}) + assert res3 == "Processed dict" + +def test_pydantic_validation_error_fallback(): + # If it looks like a schema but fails validation, it should fallback to dict overload + handler = DataHandler() + + # Invalid for both schemas (missing required fields) + res = handler.process({"name": "Only Name"}) + assert res == "Processed dict" + +def test_pydantic_direct_model_instances(): + handler = DataHandler() + + user = UserCreateSchema(name="Charlie", age=25) + res = handler.process(user) + assert res == "Created user Charlie" diff --git a/uv.lock b/uv.lock index 1c0ffd1..2d4aab5 100644 --- a/uv.lock +++ b/uv.lock @@ -7,6 +7,15 @@ resolution-markers = [ "python_full_version < '3.13'", ] +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -187,6 +196,96 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] +[[package]] +name = "pydantic" +version = "2.13.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/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.46.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" }, + { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" }, + { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" }, + { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" }, + { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" }, + { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" }, + { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" }, + { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" }, + { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" }, + { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" }, + { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" }, + { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" }, + { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" }, + { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" }, + { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" }, + { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" }, + { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" }, + { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" }, + { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" }, + { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" }, + { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" }, + { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" }, + { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" }, + { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" }, + { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" }, + { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" }, + { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" }, + { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" }, + { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" }, + { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" }, + { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" }, + { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" }, + { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" }, + { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" }, + { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" }, + { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" }, + { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" }, + { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" }, + { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" }, + { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" }, + { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" }, +] + [[package]] name = "pygments" version = "2.20.0" @@ -319,6 +418,7 @@ dependencies = [ [package.dev-dependencies] dev = [ + { name = "pydantic" }, { name = "pytest" }, { name = "pytest-cov" }, { name = "ruff" }, @@ -329,6 +429,7 @@ requires-dist = [{ name = "strongtyping", specifier = ">=3.11.4" }] [package.metadata.requires-dev] dev = [ + { name = "pydantic", specifier = ">=2.13.4" }, { name = "pytest", specifier = ">=9.0.3" }, { name = "pytest-cov", specifier = ">=7.1.0" }, { name = "ruff", specifier = ">=0.15.12" }, @@ -343,6 +444,18 @@ wheels = [ { 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]] name = "ujson" version = "5.12.0" From 6df176618eb0c422d492281f3d61f0301470e013 Mon Sep 17 00:00:00 2001 From: felixeisenmenger Date: Wed, 24 Jun 2026 15:59:15 +0200 Subject: [PATCH 2/9] WIP --- src/strongtyping_pyoverload/class_tools.py | 49 ++++++++++++++++------ 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/src/strongtyping_pyoverload/class_tools.py b/src/strongtyping_pyoverload/class_tools.py index fd0e674..60322e5 100644 --- a/src/strongtyping_pyoverload/class_tools.py +++ b/src/strongtyping_pyoverload/class_tools.py @@ -5,9 +5,17 @@ from functools import wraps from types import MethodType +import itertools from strongtyping.cached_dict import CachedDict from strongtyping.strong_typing_utils import check_type +try: + from pydantic import BaseModel +except (ModuleNotFoundError, ImportError): + PYDANTIC_INSTALLED = False +else: + PYDANTIC_INSTALLED = True + __override_items__ = defaultdict(list) ANY = object() IGNORE_CHARS = " Date: Wed, 24 Jun 2026 23:29:26 +0200 Subject: [PATCH 3/9] WIP --- pyproject.toml | 4 +- src/strongtyping_pyoverload/class_tools.py | 223 +++++---------------- src/strongtyping_pyoverload/func_info.py | 157 +++++++++++++++ tests/test_override.py | 8 +- uv.lock | 203 ++++++------------- 5 files changed, 283 insertions(+), 312 deletions(-) create mode 100644 src/strongtyping_pyoverload/func_info.py diff --git a/pyproject.toml b/pyproject.toml index ec816d7..be118b6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ authors = [ ] description = "Runtime method overload decorator to simulate C/C++ behavior of having multiple functions with the same name but different parameters." readme = "README.md" -requires-python = ">=3.12" +requires-python = ">=3.13" classifiers = [ "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3.13", @@ -15,7 +15,7 @@ classifiers = [ ] license = { text = "MIT License" } dependencies = [ - "strongtyping>=3.11.4" + "strongtyping>=3.13.0", ] [dependency-groups] diff --git a/src/strongtyping_pyoverload/class_tools.py b/src/strongtyping_pyoverload/class_tools.py index 60322e5..ac0f86c 100644 --- a/src/strongtyping_pyoverload/class_tools.py +++ b/src/strongtyping_pyoverload/class_tools.py @@ -9,8 +9,10 @@ from strongtyping.cached_dict import CachedDict from strongtyping.strong_typing_utils import check_type +from strongtyping_pyoverload.func_info import FuncInfo + try: - from pydantic import BaseModel + from pydantic import BaseModel, ValidationError except (ModuleNotFoundError, ImportError): PYDANTIC_INSTALLED = False else: @@ -22,157 +24,6 @@ START_IDX = len(IGNORE_CHARS) -class FuncInfo: - __slots__ = ("func_", "params_", "func_name_", "cls_name_") - - def __init__(self, func_, params_: list): - self.func_ = func_ - self.func_name_ = func_.__name__ - self.params_ = params_ - self.cls_name_ = self.extract_class_name_from_func(func_) - - @staticmethod - def extract_class_name_from_func(function: object): - try: - return function.__qualname__.split(".")[-2] - except IndexError: - return "" - - @property - def name(self) -> str: - return self.func_name_ - - @property - def lookup_key(self) -> tuple[str, str]: - return self.cls_name_, self.func_name_ - - @property - def is_keyword_only(self): - if not self.params_: - return False - return all(obj[1] == "KEYWORD_ONLY" for obj in self.params_) - - @property - def is_positional_only(self): - if not self.params_: - return False - return all(obj[1] == "POSITIONAL_ONLY" for obj in self.params_) - - @property - def contains_args(self): - if not self.params_: - return False - return any(obj[1] == "VAR_POSITIONAL" for obj in self.params_) - - @property - def contains_kwargs(self): - if not self.params_: - return False - return any(obj[1] == "VAR_KEYWORD" for obj in self.params_) - - @property - def no_parameter(self): - return len(self.params_) == 0 - - @property - def first_var_positional_pos(self): - return [obj[1] == "VAR_POSITIONAL" for obj in self.params_].index(True) - - @property - def first_var_keyword_pos(self): - return [obj[1] == "VAR_KEYWORD" for obj in self.params_].index(True) - - def __str__(self): - params_txt = "_".join(str(param) for param in self.params_) - return f"{self.func_}_{params_txt}" - - def __repr__(self): - return f"{self.cls_name_}-{self.func_name_}" - - def _validated_keyword_only(self, other): - if len(self.params_) != len(other): - return False - for param in self.params_: - if obj := other.get(param[2]): - if param[0] != str(ANY): - if not check_type(obj, param[0]): - return False - else: - return False - return True - - def _validate_positional_only(self, other): - if len(self.params_) != len(other): - return False - for param, arg in zip(self.params_, other): - if param[0] != str(ANY): - if not check_type(arg, param[0]): - return False - return True - - def _validate_general(self, args_, kwargs_): - pos_args = [] - for param in self.params_: - if obj := kwargs_.get(param[2]): - if param[0] != str(ANY): - if not check_type(obj, param[0]): - return False - else: - pos_args.append(param) - for arg, param in zip(args_, pos_args): - if param[0] != str(ANY): - if not check_type(arg, param[0]): - return False - return True - - def _validate_with_var_positional(self, args_: tuple, kwargs_: dict): - arg_values = args_[: self.first_var_positional_pos] - return self._validate_general(arg_values, kwargs_) - - def _validate_with_var_keyword(self, args_: tuple, kwargs_: dict): - kwarg_values = [obj[2] for obj in list(self.params_)[: self.first_var_keyword_pos]] - if not any(obj in kwargs_ for obj in kwarg_values) and not args_: - if kwarg_values: - return False - return True - return self._validate_general( - args_, {key: kwargs_[key] for key in kwarg_values if key in kwargs_} - ) - - def _validate_with_var_pos_and_keyword(self, args_: tuple, kwargs_: dict): - if kv := (set([obj[2] for obj in self.params_]) & set(kwargs_.keys())): - return self._validate_general(tuple(), {key: kwargs_[key] for key in kv}) - else: - return self._validate_general(args_[: self.first_var_positional_pos], kwargs_) - - def __eq__(self, other): - if self.is_keyword_only: - return self._validated_keyword_only(other) - elif self.is_positional_only: - return self._validate_positional_only(other) - else: - if self.no_parameter and other: - return False - args_, kwargs_ = other - if len(self.params_) != len(args_) + len(kwargs_): - if self.contains_args and self.contains_kwargs: - return self._validate_with_var_pos_and_keyword(args_, kwargs_) - elif self.contains_args: - if len(self.params_) == 1 and not kwargs_: - return True - elif len(self.params_) == 1 and kwargs_: - return False - return self._validate_with_var_positional(args_, kwargs_) - elif self.contains_kwargs: - if len(self.params_) == 1 and not args_: - return True - elif len(self.params_) == 1 and args_: - return False - return self._validate_with_var_keyword(args_, kwargs_) - return False - return self._validate_general(args_, kwargs_) - - def generate_parameter_infos(func: MethodType): params = inspect.signature(func).parameters annotations = func.__annotations__ @@ -193,7 +44,7 @@ def generate_docstring(lookup_key: tuple[str, str]): ) -def find_corresponding_func(func_name, cls_name: str | list[str], args, kwargs): +def find_corresponding_func(func_name, cls_name: str | list[str], args: tuple, kwargs: dict) -> FuncInfo | None: pos_or_kwarg_funcs = [] if isinstance(cls_name, str): data = __override_items__[(cls_name, func_name)] @@ -209,25 +60,53 @@ def find_corresponding_func(func_name, cls_name: str | list[str], args, kwargs): for info in data: if info.is_keyword_only: if info == kwargs: - return info.func_ + return info elif info.is_positional_only: if info == args: - return info.func_ + return info elif info.no_parameter and not args and not kwargs: - return info.func_ + return info else: pos_or_kwarg_funcs.append(info) for info in pos_or_kwarg_funcs: if PYDANTIC_INSTALLED: - for param in info.params_: - # param[0] can be a Pydantic Schema, best would be to check for the "validate" function - # we need to loop over the args and kwargs and call "validate" to be sure that we can use this function - print(dir(param[0])) + res = check_pydantic_model(info, args, kwargs) + if res is not None and res: + return info if info == (args, kwargs): - return info.func_ + return info return None +def check_pydantic_model(func_info, args, kwargs) -> bool | None: + if not PYDANTIC_INSTALLED: + return False + is_valid = True + if any(isinstance(param[0], type) and issubclass(param[0], BaseModel) for param in func_info.params_): + for idx, param in enumerate(func_info.params_): + annotation = param[0] + try: + # Check if it's a Pydantic BaseModel subclass + if isinstance(annotation, type) and issubclass(annotation, BaseModel): + # Pydantic v2: use model_validate(); raises ValidationError on failure + if args: + model = annotation.model_validate(args[idx]) + else: + model = annotation.model_validate(kwargs[param[2]]) + else: + is_valid = False + except (AttributeError, TypeError, ValidationError): + return False + else: + if args: + func_info.pydantic_params_.append(model) + else: + func_info.pydantic_kwargs_.append(model) + else: + return None + return is_valid + + def is_module(func_, cls_): return cls_.__class__.__name__ in func_.__qualname__ @@ -280,29 +159,35 @@ def inner(cls_=None, *args, **kwargs): return cached_result try: - class_names = [obj.__name__ for obj in cls_.__class__.__mro__] + class_names = [obj.__name__ for obj in cls_.__class__.__mro__] if cls_ else [] + class_names.append(func_class_name) + class_names = set(class_names) except (AttributeError, TypeError): class_names = func_class_name if is_module_function or cls_ is None: - required_function = find_corresponding_func(func.__name__, class_names, args, kwargs) + func_info = find_corresponding_func(func.__name__, class_names, args, kwargs) else: - required_function = find_corresponding_func( + func_info = find_corresponding_func( func.__name__, class_names, (cls_, *args), kwargs ) - if not required_function: - raise + if not func_info: + raise AttributeError( + f"No function was found which matches your parameters `{args}_{kwargs}`" + ) try: + arg_values = func_info.pydantic_params_ if func_info.pydantic_params_ else args + kwarg_values = func_info.pydantic_kwargs_ if func_info.pydantic_kwargs_ else kwargs if cls_ is None: - result = required_function(*args, **kwargs) + result = func_info.func_(*arg_values, **kwarg_values) else: - result = required_function(cls_, *args, **kwargs) + result = func_info.func_(cls_, *arg_values, **kwarg_values) cached_dict[cached_key] = result return result except KeyError: handle_error(is_module_function, func_class_name, cls_, args, kwargs) except TypeError: - if required_function is None: + if func_info is None: handle_error(is_module_function, func_class_name, cls_, args, kwargs) else: raise diff --git a/src/strongtyping_pyoverload/func_info.py b/src/strongtyping_pyoverload/func_info.py new file mode 100644 index 0000000..c2819c8 --- /dev/null +++ b/src/strongtyping_pyoverload/func_info.py @@ -0,0 +1,157 @@ +from strongtyping.strong_typing_utils import check_type +ANY = object() + + +class FuncInfo: + pydantic_args_: list + pydantic_kwargs_: dict + __slots__ = ("func_", "params_", "func_name_", "cls_name_", "pydantic_params_", "pydantic_kwargs_") + + def __init__(self, func_, params_: list): + self.func_ = func_ + self.func_name_ = func_.__name__ + self.params_ = params_ + self.cls_name_ = self.extract_class_name_from_func(func_) + self.pydantic_params_ = [] + self.pydantic_kwargs_ = {} + + @staticmethod + def extract_class_name_from_func(function: object): + try: + return function.__qualname__.split(".")[-2] + except IndexError: + return "" + + @property + def name(self) -> str: + return self.func_name_ + + @property + def lookup_key(self) -> tuple[str, str]: + return self.cls_name_, self.func_name_ + + @property + def is_keyword_only(self): + if not self.params_: + return False + return all(obj[1] == "KEYWORD_ONLY" for obj in self.params_) + + @property + def is_positional_only(self): + if not self.params_: + return False + return all(obj[1] == "POSITIONAL_ONLY" for obj in self.params_) + + @property + def contains_args(self): + if not self.params_: + return False + return any(obj[1] == "VAR_POSITIONAL" for obj in self.params_) + + @property + def contains_kwargs(self): + if not self.params_: + return False + return any(obj[1] == "VAR_KEYWORD" for obj in self.params_) + + @property + def no_parameter(self): + return len(self.params_) == 0 + + @property + def first_var_positional_pos(self): + return [obj[1] == "VAR_POSITIONAL" for obj in self.params_].index(True) + + @property + def first_var_keyword_pos(self): + return [obj[1] == "VAR_KEYWORD" for obj in self.params_].index(True) + + def __str__(self): + params_txt = "_".join(str(param) for param in self.params_) + return f"{self.func_}_{params_txt}" + + def __repr__(self): + return f"{self.cls_name_}-{self.func_name_}" + + def _validated_keyword_only(self, other): + if len(self.params_) != len(other): + return False + for param in self.params_: + if obj := other.get(param[2]): + if param[0] != str(ANY): + if not check_type(obj, param[0]): + return False + else: + return False + return True + + def _validate_positional_only(self, other): + if len(self.params_) != len(other): + return False + for param, arg in zip(self.params_, other): + if param[0] != str(ANY): + if not check_type(arg, param[0]): + return False + return True + + def _validate_general(self, args_, kwargs_): + pos_args = [] + for param in self.params_: + if obj := kwargs_.get(param[2]): + if param[0] != str(ANY): + if not check_type(obj, param[0]): + return False + else: + pos_args.append(param) + for arg, param in zip(args_, pos_args): + if param[0] != str(ANY): + if not check_type(arg, param[0]): + return False + return True + + def _validate_with_var_positional(self, args_: tuple, kwargs_: dict): + arg_values = args_[: self.first_var_positional_pos] + return self._validate_general(arg_values, kwargs_) + + def _validate_with_var_keyword(self, args_: tuple, kwargs_: dict): + kwarg_values = [obj[2] for obj in list(self.params_)[: self.first_var_keyword_pos]] + if not any(obj in kwargs_ for obj in kwarg_values) and not args_: + if kwarg_values: + return False + return True + return self._validate_general( + args_, {key: kwargs_[key] for key in kwarg_values if key in kwargs_} + ) + + def _validate_with_var_pos_and_keyword(self, args_: tuple, kwargs_: dict): + if kv := (set([obj[2] for obj in self.params_]) & set(kwargs_.keys())): + return self._validate_general(tuple(), {key: kwargs_[key] for key in kv}) + else: + return self._validate_general(args_[: self.first_var_positional_pos], kwargs_) + + def __eq__(self, other): + if self.is_keyword_only: + return self._validated_keyword_only(other) + elif self.is_positional_only: + return self._validate_positional_only(other) + else: + if self.no_parameter and other: + return False + args_, kwargs_ = other + if len(self.params_) != len(args_) + len(kwargs_): + if self.contains_args and self.contains_kwargs: + return self._validate_with_var_pos_and_keyword(args_, kwargs_) + elif self.contains_args: + if len(self.params_) == 1 and not kwargs_: + return True + elif len(self.params_) == 1 and kwargs_: + return False + return self._validate_with_var_positional(args_, kwargs_) + elif self.contains_kwargs: + if len(self.params_) == 1 and not args_: + return True + elif len(self.params_) == 1 and args_: + return False + return self._validate_with_var_keyword(args_, kwargs_) + return False + return self._validate_general(args_, kwargs_) diff --git a/tests/test_override.py b/tests/test_override.py index 531ff4e..fd8f813 100644 --- a/tests/test_override.py +++ b/tests/test_override.py @@ -131,10 +131,10 @@ def test_on_module_level(): from .module_based_test_file import module_func assert module_func() == 0 - assert module_func(1, 2) == 1 - assert module_func("1", "2") == 2 - assert module_func(1, "2") == 3 - assert module_func(b=10, a=20) == 1 + assert module_func(1, 2) == 2 + assert module_func("1", "2") == "12" + assert module_func(1, "2") == "2" + assert module_func(b=10, a=20) == 200 assert module_func(10, "20", c=2) == 4 diff --git a/uv.lock b/uv.lock index 2d4aab5..26fa5a4 100644 --- a/uv.lock +++ b/uv.lock @@ -1,10 +1,9 @@ version = 1 revision = 3 -requires-python = ">=3.12" +requires-python = ">=3.13" resolution-markers = [ "python_full_version >= '3.14'", - "python_full_version == '3.13.*'", - "python_full_version < '3.13'", + "python_full_version < '3.14'", ] [[package]] @@ -31,21 +30,6 @@ version = "7.14.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/23/7f/d0720730a397a999ffc0fd3f5bebef347338e3a47b727da66fbb228e2ff2/coverage-7.14.0.tar.gz", hash = "sha256:057a6af2f160a85384cde4ab36f0d2777bae1057bae255f95413cdd382aa5c74", size = 919489, upload-time = "2026-05-10T18:02:31.397Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/09/1e/2f996b2c8415cbb6f54b0f5ec1ee850c96d7911961afb4fc05f4a89d8c58/coverage-7.14.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7ffd19fc8aed057fd686a17a4935eef5f9859d69208f96310e893e64b9b6ccf5", size = 219967, upload-time = "2026-05-10T18:00:13.756Z" }, - { url = "https://files.pythonhosted.org/packages/34/23/35c7aea1274aef7525bdd2dc92f710bdde6d11652239d71d1ec450067939/coverage-7.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:829994cfe1aeb773ca27bf246d4badc1e764893e3bfb98fff820fcecd1ca4662", size = 220329, upload-time = "2026-05-10T18:00:15.264Z" }, - { url = "https://files.pythonhosted.org/packages/75/cf/a8f4b43a16e194b0261257ad28ded5853ec052570afef4a84e1d81189f3b/coverage-7.14.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b4f07cf7edcb7ec39431a5074d7ea83b29a9f71fcfc494f0f40af4e65180420f", size = 251839, upload-time = "2026-05-10T18:00:17.16Z" }, - { url = "https://files.pythonhosted.org/packages/69/ff/6699e7b71e60d3049eb2bdcbc95ee3f35707b2b0e48f32e9e63d3ce30c08/coverage-7.14.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ca3d9cf2c32b521bd9518385608787fa86f38daf993695307531822c3430ed67", size = 254576, upload-time = "2026-05-10T18:00:18.829Z" }, - { url = "https://files.pythonhosted.org/packages/22/ec/c936d495fcd67f48f03a9c4ad3297ff80d1f222a5df3980f15b34c186c21/coverage-7.14.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92af52828e7f29d827346b0294e5a0853fa206db77db0395b282918d41e28db9", size = 255690, upload-time = "2026-05-10T18:00:20.648Z" }, - { url = "https://files.pythonhosted.org/packages/5c/42/5af63f636cc62a4a2b1b3ba9146f6ee6f53a35a50d5cefc54d5670f60999/coverage-7.14.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7b2bb6c9d7e769360d0f20a0f219603fd64f0c8f97de17ab25853261602be0fb", size = 257949, upload-time = "2026-05-10T18:00:22.28Z" }, - { url = "https://files.pythonhosted.org/packages/26/d3/a225317bd2012132a27e1176d51660b826f99bb975876463c44ea0d7ee5a/coverage-7.14.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1c9ed6ef99f88fb8c14aa8e2bf8eb0fe55fa2edfea68f8675d78741df1a5ac0e", size = 252242, upload-time = "2026-05-10T18:00:24.076Z" }, - { url = "https://files.pythonhosted.org/packages/f1/7f/9e65495298c3ea414742998539c37d048b5e81cc818fb1828cc6b51d10bf/coverage-7.14.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8231ade007f37959fbf58acc677f26b922c02eda6f0428ea307da0fd39681bf3", size = 253608, upload-time = "2026-05-10T18:00:25.588Z" }, - { url = "https://files.pythonhosted.org/packages/94/46/1522b524a35bdad22b2b8c4f9d32d0a104b524726ec380b2db68db1746f5/coverage-7.14.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d8b013632cc1ce1d09dbe4f32667b4d320ec2f54fc326ebeffcd0b0bcc2bb6c4", size = 251753, upload-time = "2026-05-10T18:00:27.104Z" }, - { url = "https://files.pythonhosted.org/packages/f3/e9/cdf00d38817742c541ade405e115a3f7bf36e6f2a8b99d4f209861b85a2d/coverage-7.14.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1733198802d71ec4c524f322e2867ee05c62e9e75df86bdca545407a221827d1", size = 255823, upload-time = "2026-05-10T18:00:29.038Z" }, - { url = "https://files.pythonhosted.org/packages/38/fc/5e7877cf5f902d08a17ff1c532511476d87e1bea355bd5028cb97f902e79/coverage-7.14.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:72a305291fa8ee01332f1aaf38b348ca34097f6aa0b0ef627eef2837e57bbba5", size = 251323, upload-time = "2026-05-10T18:00:30.647Z" }, - { url = "https://files.pythonhosted.org/packages/18/9d/50f05a72dff8487464fdd4178dda5daed642a060e60afb644e3d45123559/coverage-7.14.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fcaba850dd317c65423a9d63d88f9573c53b00354d6dd95724576cc98a131595", size = 253197, upload-time = "2026-05-10T18:00:32.211Z" }, - { url = "https://files.pythonhosted.org/packages/00/3f/6f61ffe6439df266c3cf60f5c99cfaa21103d0210d706a42fc6c30683ff8/coverage-7.14.0-cp312-cp312-win32.whl", hash = "sha256:5ac83957a80d0701310e96d8bec68cdcf4f90a7674b7d13f15a344315b41ab27", size = 222515, upload-time = "2026-05-10T18:00:33.717Z" }, - { url = "https://files.pythonhosted.org/packages/85/19/93853133df2cb371083285ef6a93982a0173e7a233b0f61373ba9fd30eb2/coverage-7.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:70390b0da32cb90b501953716302906e8bcce087cb283e70d8c97729f22e92b2", size = 223324, upload-time = "2026-05-10T18:00:35.172Z" }, - { url = "https://files.pythonhosted.org/packages/74/18/9f7fe62f659f24b7a82a0be56bf94c1bd0a89e0ae7ab4c668f6e82404294/coverage-7.14.0-cp312-cp312-win_arm64.whl", hash = "sha256:91b993743d959b8be85b4abf9d5478216a69329c321efe5be0433c1a841d691d", size = 221944, upload-time = "2026-05-10T18:00:37.014Z" }, { url = "https://files.pythonhosted.org/packages/6b/76/b7c66ee3c66e1b0f9d894c8125983aa0c03fb2336f2fd16559f9c966157f/coverage-7.14.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f2bbb8254370eb4c628ff3d6fa8a7f74ddc40565394d4f7ab791d1fe568e37ef", size = 219990, upload-time = "2026-05-10T18:00:38.887Z" }, { url = "https://files.pythonhosted.org/packages/b3/af/e567cbad5ba69c013a50146dfa886dc7193361fda77521f51274ff620e1b/coverage-7.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:23b81107f46d3f21d0cbce30664fcec0f5d9f585638a67081750f99738f6bf66", size = 220365, upload-time = "2026-05-10T18:00:40.864Z" }, { url = "https://files.pythonhosted.org/packages/44/6f/9ad575d505b4d805b254febc8a5b338a2efe278f8786e56ff1cb8413f9c3/coverage-7.14.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:22a7e06a5f11a757cdfe79018e9095f9f69ae283c5cd8123774c788deec8717b", size = 251363, upload-time = "2026-05-10T18:00:42.489Z" }, @@ -124,19 +108,6 @@ version = "0.11.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/40/08/9e7f6b5d2b5bed6ad055cdd5925f192bb403a51280f86b56554d9d0699a2/librt-0.11.0.tar.gz", hash = "sha256:075dc3ef4458a278e0195cbf6ac9d38808d9b906c5a6c7f7f79c3888276a3fb1", size = 200139, upload-time = "2026-05-10T18:17:25.138Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/d0/07c77e067f0838949b43bd89232c29d72efebb9d2801a9750184eb706b71/librt-0.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b87504f1690a23b9a2cca841191a04f83895d4fc2dd04df91d82b1a04ca2ad46", size = 144147, upload-time = "2026-05-10T18:15:53.227Z" }, - { url = "https://files.pythonhosted.org/packages/7a/24/8493538fa4f62f982686398a5b8f68008138a75086abdea19ade64bf4255/librt-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40071fc5fe0ce8daa6de616702314a01e1250711682b0523d6ab8d4525910cb3", size = 143614, upload-time = "2026-05-10T18:15:54.657Z" }, - { url = "https://files.pythonhosted.org/packages/ff/1e/f8bad050810d9171f34a1648ed910e56814c2ba61639f2bd53c6377ae24b/librt-0.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:137e79445c896a0ea7b265f52d23954e05b64222ee1af69e2cb34219067cbb67", size = 485538, upload-time = "2026-05-10T18:15:56.117Z" }, - { url = "https://files.pythonhosted.org/packages/c0/fe/3594ebfbaf03084ba4b120c9ba5c3183fd938a48725e9bbe6ff0a5159ad8/librt-0.11.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:cca6644054e78746d8d4ef238681f9c34ff8b584fe6b988ecebb8db3b15e622a", size = 479623, upload-time = "2026-05-10T18:15:57.544Z" }, - { url = "https://files.pythonhosted.org/packages/b0/da/5d1876984b3746c85dbd219dbfcb73c85f54ee263fd32e5b2a632ec14571/librt-0.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5b0eea49f5562861ee8d757a32ef7d559c1d35be2aaaa1ec28941d74c9ffc8a", size = 513082, upload-time = "2026-05-10T18:15:58.805Z" }, - { url = "https://files.pythonhosted.org/packages/19/6e/55bdf5d5ca00c3e18430690bf2c953d8d3ffd3c337418173d33dec985dc9/librt-0.11.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0d1029d7e1ae1a7e647ed6fb5df8c4ce2dffefb7a9f5fd1376a4554d96dac09f", size = 508105, upload-time = "2026-05-10T18:16:00.2Z" }, - { url = "https://files.pythonhosted.org/packages/07/10/f1f23a7c595ee90ece4d35c851e5d104b1311a887ed1b4ac4c35bbd13da8/librt-0.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bc3ce6b33c5828d9e80592011a5c584cb2ce86edbc4088405f70da47dc1d1b3b", size = 522268, upload-time = "2026-05-10T18:16:01.708Z" }, - { url = "https://files.pythonhosted.org/packages/b6/02/5720f5697a7f54b78b3aefbe20df3a48cedcff1276618c4aa481177942ed/librt-0.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:936c5995f3514a42111f20099397d8177c79b4d7e70961e396c6f5a0a3566766", size = 527348, upload-time = "2026-05-10T18:16:03.496Z" }, - { url = "https://files.pythonhosted.org/packages/50/db/b4a47c6f91db4ff76348a0b3dd0cc65e090a078b765a810a62ff9434c3d3/librt-0.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9bc0ca6ad9381cbe8e4aa6e5726e4c80c78115a6e9723c599ed1d73e092bc49d", size = 516294, upload-time = "2026-05-10T18:16:05.173Z" }, - { url = "https://files.pythonhosted.org/packages/9e/58/9384b2f4eb1ed1d273d40948a7c5c4b2360213b402ef3be4641c06299f9c/librt-0.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:070aa8c26c0a74774317a72df8851facc7f0f012a5b406557ac56992d92e1ec8", size = 553608, upload-time = "2026-05-10T18:16:06.839Z" }, - { url = "https://files.pythonhosted.org/packages/21/7b/5aa8848a7c6a9278c79375146da1812e695754ceec5f005e6043461a7315/librt-0.11.0-cp312-cp312-win32.whl", hash = "sha256:6bf14feb84b05ae945277395451998c89c54d0def4070eb5c08de544930b245a", size = 101879, upload-time = "2026-05-10T18:16:08.103Z" }, - { url = "https://files.pythonhosted.org/packages/37/33/8a745436944947575b584231750a41417de1a38cf6a2e9251d1065651c09/librt-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:75672f0bc524ede266287d532d7923dbce94c7514ad07627bac3d0c6d92cc4d9", size = 119831, upload-time = "2026-05-10T18:16:09.174Z" }, - { url = "https://files.pythonhosted.org/packages/59/67/a6739ac96e28b7855808bdb0370e250606104a859750d209e5a0716fe7ab/librt-0.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:2f10cf143e4a9bb0f4f5af568a00df94a2d69ef41c2579584454bb0fe5cc642c", size = 103470, upload-time = "2026-05-10T18:16:10.369Z" }, { url = "https://files.pythonhosted.org/packages/82/61/e59168d4d0bf2bf90f4f0caf7a001bfc60254c3af4586013b04dc3ef517b/librt-0.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:78dc31f7fdfe9c9d0eb0e8f42d139db230e826415bbcabd9f0e9faaaee909894", size = 144119, upload-time = "2026-05-10T18:16:11.771Z" }, { url = "https://files.pythonhosted.org/packages/61/fd/caa1d60b12f7dd79ccea23054e06eeaebe266a5f52c40a6b651069200ce5/librt-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fa475675db22290c3158e1d42326d0f5a65f04f44a0e68c3630a25b53560fb9c", size = 143565, upload-time = "2026-05-10T18:16:13.334Z" }, { url = "https://files.pythonhosted.org/packages/b8/a9/dc744f5c2b4978d48db970be29f22716d3413d28b14ad99740817315cf2c/librt-0.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:621db29691044bdeda22e789e482e1b0f3a985d90e3426c9c6d17606416205ea", size = 485395, upload-time = "2026-05-10T18:16:14.729Z" }, @@ -220,21 +191,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" }, - { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" }, - { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" }, - { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" }, - { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" }, - { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" }, - { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" }, - { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" }, - { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" }, - { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" }, - { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" }, - { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" }, - { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" }, - { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" }, - { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" }, { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" }, { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" }, { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" }, @@ -280,10 +236,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" }, { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" }, { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, - { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" }, - { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" }, - { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" }, - { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" }, ] [[package]] @@ -352,43 +304,31 @@ wheels = [ [[package]] name = "strongtyping" -version = "3.12.1" +version = "3.13.10" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.13'", -] -sdist = { url = "https://files.pythonhosted.org/packages/7c/d9/0479985eee2725e6ca7d054d96d36c8ba3fc215a042d6eaa4a76d926d305/strongtyping-3.12.1.tar.gz", hash = "sha256:fc66b1e3fd547fcc3e5bc4588e47d454ad66c3bc3a4037eae176b7ad1bc5b84e", size = 18853, upload-time = "2023-12-04T20:58:33.884Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/13/16/e83de5b4a2bd677569c27daa9f8932b04d6bc556b699906b88e8ef8cd470/strongtyping-3.12.1-py3-none-any.whl", hash = "sha256:250ecaa3f5f5f0b3e0aea6efc535479c6c1dc4128364bb1b86ce893d456e88e4", size = 22938, upload-time = "2023-12-04T20:58:25.513Z" }, -] - -[[package]] -name = "strongtyping" -version = "3.13.9" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version == '3.13.*'", + "python_full_version < '3.14'", ] dependencies = [ - { name = "librt", marker = "python_full_version == '3.13.*'" }, - { name = "typing-extensions", marker = "python_full_version == '3.13.*'" }, - { name = "ujson", marker = "python_full_version == '3.13.*'" }, + { name = "librt", marker = "python_full_version < '3.14'" }, + { name = "typing-extensions", marker = "python_full_version < '3.14'" }, + { name = "ujson", marker = "python_full_version < '3.14'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fe/5c/18a050734c519c8014db1aa024b4a577a4141b655a017455bc9d35a10b82/strongtyping-3.13.9.tar.gz", hash = "sha256:8559fd17ff5a0f4a6902c6460e10b998c80ebe7ba592b03de6fc4892902767f3", size = 44148, upload-time = "2026-05-11T21:09:28.074Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f4/ec/5f84762b5a05999d2f702327f339e013043440c69dccbb8c99b971701f56/strongtyping-3.13.10.tar.gz", hash = "sha256:dcec72ee6e42509c0fe502c27124ef6616eba1745b21c2dd4541bf3f0c23f925", size = 44143, upload-time = "2026-05-13T12:55:02.006Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/f6/a39c28b438e600acbceb0ede96ea6213e6a029361a02903f18c7b76e22ba/strongtyping-3.13.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e2051463f486e69981bdf898d80a2fc684a7fa853bf3608c15a4fd1d95c69489", size = 139372, upload-time = "2026-05-11T21:09:17.418Z" }, - { url = "https://files.pythonhosted.org/packages/ec/30/fe1a6c980d8aeeef3d090bb6bc50f794d5bac4107586d3f24fb1c805d1bd/strongtyping-3.13.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:21715130b5ad4a4dec5cd4e52e992e46c27c28ce2469f80637e34f87bf0ebe94", size = 134048, upload-time = "2026-05-11T21:09:18.698Z" }, - { url = "https://files.pythonhosted.org/packages/33/96/b0a7bda5b2d9efbdd1d7d05a13dbbfe6b6267f26406e11b687a994c060f2/strongtyping-3.13.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9a39c0a226224cfa1297fb7afdc857eac691cbf39616adff2e4867cb1e8e154e", size = 214699, upload-time = "2026-05-11T21:09:19.903Z" }, - { url = "https://files.pythonhosted.org/packages/79/a1/5d86a3adbb6030250036884f359ce9922bd71a996fd19e5768b31ef15a27/strongtyping-3.13.9-cp313-cp313-win_amd64.whl", hash = "sha256:c7a3ffa7767b4a8a7929330fb87563f18db7c97233eb1ad50db7e74b3d14f060", size = 89124, upload-time = "2026-05-11T21:09:21.464Z" }, - { url = "https://files.pythonhosted.org/packages/57/4e/12e0074942c30d18cba7931deca0255b8ff0efa46a5d837f92a002a3199a/strongtyping-3.13.9-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e738ef78f2f5cd4523dee492b5f55555b7b6c8031b03d2cf006bf36e22f79b35", size = 139203, upload-time = "2026-05-11T21:09:22.723Z" }, - { url = "https://files.pythonhosted.org/packages/b2/05/35560657a61c6f46f55f5bf4fa4eb6f894456e966a527c291a1d06fac7d8/strongtyping-3.13.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8d235620049a1dfd9c57acb6842504ccd6567ac2c6eac0cbc025fcf3cdb31d14", size = 134201, upload-time = "2026-05-11T21:09:24.227Z" }, - { url = "https://files.pythonhosted.org/packages/c5/9f/c51209bad052a7b1ef95b7d3c67f1b869eacc7b8b3deb7b8b1f003e2e460/strongtyping-3.13.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b252ee9a358202ad1399871431b26f9093e6bcaeab40f1c9bdd8d753fea6e17b", size = 214605, upload-time = "2026-05-11T21:09:25.557Z" }, - { url = "https://files.pythonhosted.org/packages/d1/f3/f59cde81c5d2586c14d7cf0bb418713d1bed764d3a040b447062a61f188b/strongtyping-3.13.9-cp314-cp314-win_amd64.whl", hash = "sha256:680487d5ff9cde2566d7214f397823cef1647525891c8f5d6727573e9328c30a", size = 89787, upload-time = "2026-05-11T21:09:26.831Z" }, + { url = "https://files.pythonhosted.org/packages/75/db/caa9c8a1ab99b7b04cb23cdb6870603bcf399e8f193afd729e7010de33c6/strongtyping-3.13.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7a923840c3198b0ac8339f12ba05f64af4d81578ed06bbc9785d58072582edcc", size = 139381, upload-time = "2026-05-13T12:54:51.413Z" }, + { url = "https://files.pythonhosted.org/packages/45/dc/681fa28a12ea7152f19cc2b9d3e7b8af3e6c2e41944e918458962c883524/strongtyping-3.13.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:23cb744adafb5499c301a02b2914e7da3ab984e256eb1a109670d6c23246ecc0", size = 134060, upload-time = "2026-05-13T12:54:52.743Z" }, + { url = "https://files.pythonhosted.org/packages/4d/58/0767fe35d7fb40f487e89ed1cde21d51f107aa957bc478f93fc9cc19a2d1/strongtyping-3.13.10-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:68de8b94aa2449d99eb336628ed72f1b4bc5737ee28dc17147a81f95745c8873", size = 214710, upload-time = "2026-05-13T12:54:54.163Z" }, + { url = "https://files.pythonhosted.org/packages/9b/87/e097ea385dd340e6b6cd6cd8508e72ae83b25a225fb2d6a08ad74ef22e16/strongtyping-3.13.10-cp313-cp313-win_amd64.whl", hash = "sha256:485d214d2adfa6b68ea7401a0354a4370d280e414d211dbf4d91a2e233724b94", size = 89131, upload-time = "2026-05-13T12:54:55.618Z" }, + { url = "https://files.pythonhosted.org/packages/af/57/b4ac73e591176fe2da680415610bd029cc7fc5b9d42346d01caad349e1f4/strongtyping-3.13.10-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f8c9cf72857d22534f0a334f4b67538210fa03a88392a2da45d71b6bd266eb9f", size = 139215, upload-time = "2026-05-13T12:54:57.015Z" }, + { url = "https://files.pythonhosted.org/packages/5b/43/5671e550fd5f869185bbfdb7c77a404d42ad0e771b971ed075d423eaa903/strongtyping-3.13.10-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3c025448807d3b2b49ba96b3fe031bd90ec087d932fa976017ee304c456ff163", size = 134214, upload-time = "2026-05-13T12:54:58.171Z" }, + { url = "https://files.pythonhosted.org/packages/bc/01/8e12e432da4e786b3020289cb2b9761e1e23f2041c0e1fee9034969405e1/strongtyping-3.13.10-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9158553d2888bc9f42420febf5bc5ec378110c9bde16a5ea4163d1685d520e81", size = 214617, upload-time = "2026-05-13T12:54:59.256Z" }, + { url = "https://files.pythonhosted.org/packages/62/ea/4e81cc3e94639d3baef6bdba3ed43def95ca0d20689564369f22fed82fe7/strongtyping-3.13.10-cp314-cp314-win_amd64.whl", hash = "sha256:5721e0b2e064f8e8e0d084c190616f4685d478a039ca3178fb4561008065225d", size = 89796, upload-time = "2026-05-13T12:55:00.61Z" }, ] [[package]] name = "strongtyping" -version = "3.14.2" +version = "3.14.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14'", @@ -398,12 +338,12 @@ dependencies = [ { name = "typing-extensions", marker = "python_full_version >= '3.14'" }, { name = "ujson", marker = "python_full_version >= '3.14'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0f/d2/8dd62b9a3cced04407394bc70f841e6c30df5f6d447c1f70d8f62f2f5752/strongtyping-3.14.2.tar.gz", hash = "sha256:b36b1e7610a7bb925f2557377d0788c2e5d15d228d6572816b15a17aeb76bdfb", size = 44203, upload-time = "2026-05-11T21:10:26.488Z" } +sdist = { url = "https://files.pythonhosted.org/packages/af/3f/fe786a51851cc2f37ccf56613bea197b56cc870a48d496548cecfc94c9ce/strongtyping-3.14.3.tar.gz", hash = "sha256:c8b7ab29e310dd42813c4b268cfae4974eb4f014f23436c7f7358aecfeb78bba", size = 44145, upload-time = "2026-05-13T12:58:42.721Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/6e/a940228b5020d7ccef212e9112cbe8b60864b25a675e5e681eac7fe08e5a/strongtyping-3.14.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:71c0e9940e0d6b0b8e88b1dc7478e911e3ba100b5e610da77eed751556a899c3", size = 139204, upload-time = "2026-05-11T21:10:20.965Z" }, - { url = "https://files.pythonhosted.org/packages/3f/d8/3433e90b8adc1d12a5a9090aeb29ecbbf23b1cbf24236a8404c861e533c3/strongtyping-3.14.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3bf5dfb91520da2984395ebdb707435aaaee698749fe49131eaa14fcc171377d", size = 134206, upload-time = "2026-05-11T21:10:22.621Z" }, - { url = "https://files.pythonhosted.org/packages/29/d9/ef5ef7578290250bc2bd9f1184685ad40f717b60cb6692c82d889288d045/strongtyping-3.14.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fc8541a176a4866b365dd0a22baf5208b48e8629c3a3d2db2411c6adf4d0dd3f", size = 214609, upload-time = "2026-05-11T21:10:24.096Z" }, - { url = "https://files.pythonhosted.org/packages/44/72/9f48c4713b6f78a981ac67332eab5cd58cde3d9bc358180a2c62241d96c8/strongtyping-3.14.2-cp314-cp314-win_amd64.whl", hash = "sha256:e069029c0d72c97c9a65fecdee459f403606aec6c0b355b807f4ac8b4ace8719", size = 89788, upload-time = "2026-05-11T21:10:25.424Z" }, + { url = "https://files.pythonhosted.org/packages/17/c9/2ed2d9502fd74a9501669ab001e9e9f53cd1b26e05abc32b6e4c6bfaa835/strongtyping-3.14.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2855d954745705d620962e6fcb3ec86a0b3f57f5c2334a2a2a769a96f67a0ba0", size = 139182, upload-time = "2026-05-13T12:58:37.677Z" }, + { url = "https://files.pythonhosted.org/packages/bc/31/35fa7e11467edf201015e5011aa6ce171646f4c7c9c99ec537347439f608/strongtyping-3.14.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6e13c40e0c54634cb01f2e032e446f80943ad94622079eaaf3c9d4cb8fd7729c", size = 134183, upload-time = "2026-05-13T12:58:38.914Z" }, + { url = "https://files.pythonhosted.org/packages/5f/3f/c39e5c47f6b630f4fddb71a9126b7aed88f78d2cafc47e85e584205c6e53/strongtyping-3.14.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e253ae8814b47bb574d008a77d7c9fa3135fa3fd43100bc5811597270036ed43", size = 214587, upload-time = "2026-05-13T12:58:40.418Z" }, + { url = "https://files.pythonhosted.org/packages/86/4f/14e9581d9a759bd85e8235db1220fbd9f237fda442b13d27a63038fe1724/strongtyping-3.14.3-cp314-cp314-win_amd64.whl", hash = "sha256:921736e94fe641ed87ba9cc5224c9679a8423a1a6c22b88f20197718a6f9051e", size = 89765, upload-time = "2026-05-13T12:58:41.683Z" }, ] [[package]] @@ -411,9 +351,8 @@ name = "strongtyping-pyoverload" version = "0.4.4.post4" source = { editable = "." } dependencies = [ - { name = "strongtyping", version = "3.12.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, - { name = "strongtyping", version = "3.13.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.13.*'" }, - { name = "strongtyping", version = "3.14.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14'" }, + { name = "strongtyping", version = "3.13.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14'" }, + { name = "strongtyping", version = "3.14.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14'" }, ] [package.dev-dependencies] @@ -425,7 +364,7 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "strongtyping", specifier = ">=3.11.4" }] +requires-dist = [{ name = "strongtyping", specifier = ">=3.13.0" }] [package.metadata.requires-dev] dev = [ @@ -458,57 +397,47 @@ wheels = [ [[package]] name = "ujson" -version = "5.12.0" +version = "5.13.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cb/3e/c35530c5ffc25b71c59ae0cd7b8f99df37313daa162ce1e2f7925f7c2877/ujson-5.12.0.tar.gz", hash = "sha256:14b2e1eb528d77bc0f4c5bd1a7ebc05e02b5b41beefb7e8567c9675b8b13bcf4", size = 7158451, upload-time = "2026-03-11T22:19:30.397Z" } +sdist = { url = "https://files.pythonhosted.org/packages/89/7a/c8bb37c8f6f3623d60c33d15d18cd6d6655d0f9c3eb31a9969f76361b199/ujson-5.13.0.tar.gz", hash = "sha256:d62e3d7625384c08082abad81a077af587fdef2761bb14c3822f4234b8d07d75", size = 7166784, upload-time = "2026-06-14T22:36:50.209Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/f6/ac763d2108d28f3a40bb3ae7d2fafab52ca31b36c2908a4ad02cd3ceba2a/ujson-5.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:09b4beff9cc91d445d5818632907b85fb06943b61cb346919ce202668bf6794a", size = 56326, upload-time = "2026-03-11T22:18:18.467Z" }, - { url = "https://files.pythonhosted.org/packages/25/46/d0b3af64dcdc549f9996521c8be6d860ac843a18a190ffc8affeb7259687/ujson-5.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ca0c7ce828bb76ab78b3991904b477c2fd0f711d7815c252d1ef28ff9450b052", size = 53910, upload-time = "2026-03-11T22:18:19.502Z" }, - { url = "https://files.pythonhosted.org/packages/9a/10/853c723bcabc3e9825a079019055fc99e71b85c6bae600607a2b9d31d18d/ujson-5.12.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2d79c6635ccffcbfc1d5c045874ba36b594589be81d50d43472570bb8de9c57", size = 57754, upload-time = "2026-03-11T22:18:20.874Z" }, - { url = "https://files.pythonhosted.org/packages/f9/c6/6e024830d988f521f144ead641981c1f7a82c17ad1927c22de3242565f5c/ujson-5.12.0-cp312-cp312-manylinux_2_24_i686.manylinux_2_28_i686.whl", hash = "sha256:7e07f6f644d2c44d53b7a320a084eef98063651912c1b9449b5f45fcbdc6ccd2", size = 59936, upload-time = "2026-03-11T22:18:21.924Z" }, - { url = "https://files.pythonhosted.org/packages/34/c9/c5f236af5abe06b720b40b88819d00d10182d2247b1664e487b3ed9229cf/ujson-5.12.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:085b6ce182cdd6657481c7c4003a417e0655c4f6e58b76f26ee18f0ae21db827", size = 57463, upload-time = "2026-03-11T22:18:22.924Z" }, - { url = "https://files.pythonhosted.org/packages/ae/04/41342d9ef68e793a87d84e4531a150c2b682f3bcedfe59a7a5e3f73e9213/ujson-5.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:16b4fe9c97dc605f5e1887a9e1224287291e35c56cbc379f8aa44b6b7bcfe2bb", size = 1037239, upload-time = "2026-03-11T22:18:24.04Z" }, - { url = "https://files.pythonhosted.org/packages/d4/81/dc2b7617d5812670d4ff4a42f6dd77926430ee52df0dedb2aec7990b2034/ujson-5.12.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0d2e8db5ade3736a163906154ca686203acc7d1d30736cbf577c730d13653d84", size = 1196713, upload-time = "2026-03-11T22:18:25.391Z" }, - { url = "https://files.pythonhosted.org/packages/b6/9c/80acff0504f92459ed69e80a176286e32ca0147ac6a8252cd0659aad3227/ujson-5.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:93bc91fdadcf046da37a214eaa714574e7e9b1913568e93bb09527b2ceb7f759", size = 1089742, upload-time = "2026-03-11T22:18:26.738Z" }, - { url = "https://files.pythonhosted.org/packages/e3/f0/123ffaac17e45ef2b915e3e3303f8f4ea78bb8d42afad828844e08622b1e/ujson-5.12.0-cp312-cp312-win32.whl", hash = "sha256:2a248750abce1c76fbd11b2e1d88b95401e72819295c3b851ec73399d6849b3d", size = 39773, upload-time = "2026-03-11T22:18:28.244Z" }, - { url = "https://files.pythonhosted.org/packages/b5/20/f3bd2b069c242c2b22a69e033bfe224d1d15d3649e6cd7cc7085bb1412ff/ujson-5.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:1b5c6ceb65fecd28a1d20d1eba9dbfa992612b86594e4b6d47bb580d2dd6bcb3", size = 44040, upload-time = "2026-03-11T22:18:29.236Z" }, - { url = "https://files.pythonhosted.org/packages/f0/a7/01b5a0bcded14cd2522b218f2edc3533b0fcbccdea01f3e14a2b699071aa/ujson-5.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:9a5fcbe7b949f2e95c47ea8a80b410fcdf2da61c98553b45a4ee875580418b68", size = 38526, upload-time = "2026-03-11T22:18:30.551Z" }, - { url = "https://files.pythonhosted.org/packages/3f/f1/0ef0eeab1db8493e1833c8b440fe32cf7538f7afa6e7f7c7e9f62cef464d/ujson-5.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:15d416440148f3e56b9b244fdaf8a09fcf5a72e4944b8e119f5bf60417a2bfc8", size = 56331, upload-time = "2026-03-11T22:18:31.539Z" }, - { url = "https://files.pythonhosted.org/packages/b0/2f/9159f6f399b3f572d20847a2b80d133e3a03c14712b0da4971a36879fb64/ujson-5.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e0dd3676ea0837cd70ea1879765e9e9f6be063be0436de9b3ea4b775caf83654", size = 53910, upload-time = "2026-03-11T22:18:32.829Z" }, - { url = "https://files.pythonhosted.org/packages/e5/a9/f96376818d71495d1a4be19a0ab6acf0cc01dd8826553734c3d4dac685b2/ujson-5.12.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7bbf05c38debc90d1a195b11340cc85cb43ab3e753dc47558a3a84a38cbc72da", size = 57757, upload-time = "2026-03-11T22:18:33.866Z" }, - { url = "https://files.pythonhosted.org/packages/98/8d/dd4a151caac6fdcb77f024fbe7f09d465ebf347a628ed6dd581a0a7f6364/ujson-5.12.0-cp313-cp313-manylinux_2_24_i686.manylinux_2_28_i686.whl", hash = "sha256:3c2f947e55d3c7cfe124dd4521ee481516f3007d13c6ad4bf6aeb722e190eb1b", size = 59940, upload-time = "2026-03-11T22:18:35.276Z" }, - { url = "https://files.pythonhosted.org/packages/c7/17/0d36c2fee0a8d8dc37b011ccd5bbdcfaff8b8ec2bcfc5be998661cdc935b/ujson-5.12.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ea6206043385343aff0b7da65cf73677f6f5e50de8f1c879e557f4298cac36a", size = 57465, upload-time = "2026-03-11T22:18:36.644Z" }, - { url = "https://files.pythonhosted.org/packages/8c/04/b0ee4a4b643a01ba398441da1e357480595edb37c6c94c508dbe0eb9eb60/ujson-5.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bb349dbba57c76eec25e5917e07f35aabaf0a33b9e67fc13d188002500106487", size = 1037236, upload-time = "2026-03-11T22:18:37.743Z" }, - { url = "https://files.pythonhosted.org/packages/2d/08/0e7780d0bbb48fe57ded91f550144bcc99c03b5360bf2886dd0dae0ea8f5/ujson-5.12.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:937794042342006f707837f38d721426b11b0774d327a2a45c0bd389eb750a87", size = 1196717, upload-time = "2026-03-11T22:18:39.101Z" }, - { url = "https://files.pythonhosted.org/packages/ba/4c/e0e34107715bb4dd2d4dcc1ce244d2f074638837adf38aff85a37506efe4/ujson-5.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6ad57654570464eb1b040b5c353dee442608e06cff9102b8fcb105565a44c9ed", size = 1089748, upload-time = "2026-03-11T22:18:40.473Z" }, - { url = "https://files.pythonhosted.org/packages/72/43/814f4e2b5374d0d505c254ba4bed43eb25d2d046f19f5fd88555f81a7bd0/ujson-5.12.0-cp313-cp313-win32.whl", hash = "sha256:76bf3e7406cf23a3e1ca6a23fb1fb9ea82f4f6bd226fe226e09146b0194f85dc", size = 39778, upload-time = "2026-03-11T22:18:41.791Z" }, - { url = "https://files.pythonhosted.org/packages/0f/fe/19310d848ebe93315b6cb171277e4ce29f47ef9d46caabd63ff05d5be548/ujson-5.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:15e555c4caca42411270b2ed2b2ebc7b3a42bb04138cef6c956e1f1d49709fe2", size = 44038, upload-time = "2026-03-11T22:18:43.094Z" }, - { url = "https://files.pythonhosted.org/packages/3f/e4/7a39103d7634691601a02bd1ca7268fba4da47ed586365e6ee68168f575a/ujson-5.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:bd03472c36fa3a386a6deb887113b9e3fa40efba8203eb4fe786d3c0ccc724f6", size = 38529, upload-time = "2026-03-11T22:18:44.167Z" }, - { url = "https://files.pythonhosted.org/packages/10/bd/9a8d693254bada62bfea75a507e014afcfdb6b9d047b6f8dd134bfefaf67/ujson-5.12.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:85833bca01aa5cae326ac759276dc175c5fa3f7b3733b7d543cf27f2df12d1ef", size = 56499, upload-time = "2026-03-11T22:18:45.431Z" }, - { url = "https://files.pythonhosted.org/packages/bd/2d/285a83df8176e18dcd675d1a4cff8f7620f003f30903ea43929406e98986/ujson-5.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d22cad98c2a10bbf6aa083a8980db6ed90d4285a841c4de892890c2b28286ef9", size = 53998, upload-time = "2026-03-11T22:18:47.184Z" }, - { url = "https://files.pythonhosted.org/packages/bf/8b/e2f09e16dabfa91f6a84555df34a4329fa7621e92ed054d170b9054b9bb2/ujson-5.12.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99cc80facad240b0c2fb5a633044420878aac87a8e7c348b9486450cba93f27c", size = 57783, upload-time = "2026-03-11T22:18:48.271Z" }, - { url = "https://files.pythonhosted.org/packages/68/fb/ba1d06f3658a0c36d0ab3869ec3914f202bad0a9bde92654e41516c7bb13/ujson-5.12.0-cp314-cp314-manylinux_2_24_i686.manylinux_2_28_i686.whl", hash = "sha256:d1831c07bd4dce53c4b666fa846c7eba4b7c414f2e641a4585b7f50b72f502dc", size = 60011, upload-time = "2026-03-11T22:18:49.284Z" }, - { url = "https://files.pythonhosted.org/packages/64/2b/3e322bf82d926d9857206cd5820438d78392d1f523dacecb8bd899952f73/ujson-5.12.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e00cec383eab2406c9e006bd4edb55d284e94bb943fda558326048178d26961", size = 57465, upload-time = "2026-03-11T22:18:50.584Z" }, - { url = "https://files.pythonhosted.org/packages/e9/fd/af72d69603f9885e5136509a529a4f6d88bf652b457263ff96aefcd3ab7d/ujson-5.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f19b3af31d02a2e79c5f9a6deaab0fb3c116456aeb9277d11720ad433de6dfc6", size = 1037275, upload-time = "2026-03-11T22:18:51.998Z" }, - { url = "https://files.pythonhosted.org/packages/9c/a7/a2411ec81aef7872578e56304c3e41b3a544a9809e95c8e1df46923fc40b/ujson-5.12.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:bacbd3c69862478cbe1c7ed4325caedec580d8acf31b8ee1b9a1e02a56295cad", size = 1196758, upload-time = "2026-03-11T22:18:53.548Z" }, - { url = "https://files.pythonhosted.org/packages/ed/85/aa18ae175dd03a118555aa14304d4f466f9db61b924c97c6f84388ecacb1/ujson-5.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94c5f1621cbcab83c03be46441f090b68b9f307b6c7ec44d4e3f6d5997383df4", size = 1089760, upload-time = "2026-03-11T22:18:55.336Z" }, - { url = "https://files.pythonhosted.org/packages/d3/d4/4b40b67ac7e916ebffc3041ae2320c5c0b8a045300d4c542b6e50930cca5/ujson-5.12.0-cp314-cp314-win32.whl", hash = "sha256:e6369ac293d2cc40d52577e4fa3d75a70c1aae2d01fa3580a34a4e6eff9286b9", size = 41043, upload-time = "2026-03-11T22:18:56.505Z" }, - { url = "https://files.pythonhosted.org/packages/24/38/a1496d2a3428981f2b3a2ffbb4656c2b05be6cc406301d6b10a6445f6481/ujson-5.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:31348a0ffbfc815ce78daac569d893349d85a0b57e1cd2cdbba50b7f333784da", size = 45303, upload-time = "2026-03-11T22:18:57.454Z" }, - { url = "https://files.pythonhosted.org/packages/85/d3/39dbd3159543d9c57ec3a82d36226152cf0d710784894ce5aa24b8220ac1/ujson-5.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:6879aed770557f0961b252648d36f6fdaab41079d37a2296b5649fd1b35608e0", size = 39860, upload-time = "2026-03-11T22:18:58.578Z" }, - { url = "https://files.pythonhosted.org/packages/c3/71/9b4dacb177d3509077e50497222d39eec04c8b41edb1471efc764d645237/ujson-5.12.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7ddb08b3c2f9213df1f2e3eb2fbea4963d80ec0f8de21f0b59898e34f3b3d96d", size = 56845, upload-time = "2026-03-11T22:18:59.629Z" }, - { url = "https://files.pythonhosted.org/packages/24/c2/8abffa3be1f3d605c4a62445fab232b3e7681512ce941c6b23014f404d36/ujson-5.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0a3ae28f0b209be5af50b54ca3e2123a3de3a57d87b75f1e5aa3d7961e041983", size = 54463, upload-time = "2026-03-11T22:19:00.697Z" }, - { url = "https://files.pythonhosted.org/packages/db/2e/60114a35d1d6796eb428f7affcba00a921831ff604a37d9142c3d8bbe5c5/ujson-5.12.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d30ad4359413c8821cc7b3707f7ca38aa8bc852ba3b9c5a759ee2d7740157315", size = 58689, upload-time = "2026-03-11T22:19:01.739Z" }, - { url = "https://files.pythonhosted.org/packages/c8/ad/010925c2116c21ce119f9c2ff18d01f48a19ade3ff4c5795da03ce5829fc/ujson-5.12.0-cp314-cp314t-manylinux_2_24_i686.manylinux_2_28_i686.whl", hash = "sha256:02f93da7a4115e24f886b04fd56df1ee8741c2ce4ea491b7ab3152f744ad8f8e", size = 60618, upload-time = "2026-03-11T22:19:03.101Z" }, - { url = "https://files.pythonhosted.org/packages/9b/74/db7f638bf20282b1dccf454386cbd483faaaed3cdbb9cb27e06f74bb109e/ujson-5.12.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3ff4ede90ed771140caa7e1890de17431763a483c54b3c1f88bd30f0cc1affc0", size = 58151, upload-time = "2026-03-11T22:19:04.175Z" }, - { url = "https://files.pythonhosted.org/packages/9c/7e/3ebaecfa70a2e8ce623db8e21bd5cb05d42a5ef943bcbb3309d71b5de68d/ujson-5.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bf9cc97f05048ac8f3e02cd58f0fe62b901453c24345bfde287f4305dcc31c", size = 1038117, upload-time = "2026-03-11T22:19:05.558Z" }, - { url = "https://files.pythonhosted.org/packages/2e/aa/e073eda7f0036c2973b28db7bb99faba17a932e7b52d801f9bb3e726271f/ujson-5.12.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:2324d9a0502317ffc35d38e153c1b2fa9610ae03775c9d0f8d0cca7b8572b04e", size = 1197434, upload-time = "2026-03-11T22:19:06.92Z" }, - { url = "https://files.pythonhosted.org/packages/1c/01/b9a13f058fdd50c746b192c4447ca8d6352e696dcda912ccee10f032ff85/ujson-5.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:50524f4f6a1c839714dbaff5386a1afb245d2d5ec8213a01fbc99cea7307811e", size = 1090401, upload-time = "2026-03-11T22:19:08.383Z" }, - { url = "https://files.pythonhosted.org/packages/c4/37/3d1b4e0076b6e43379600b5229a5993db8a759ff2e1830ea635d876f6644/ujson-5.12.0-cp314-cp314t-win32.whl", hash = "sha256:f7a0430d765f9bda043e6aefaba5944d5f21ec43ff4774417d7e296f61917382", size = 41880, upload-time = "2026-03-11T22:19:09.671Z" }, - { url = "https://files.pythonhosted.org/packages/b1/c5/3c2a262a138b9f0014fe1134a6b5fdc2c54245030affbaac2fcbc0632138/ujson-5.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:ccbfd94e59aad4a2566c71912b55f0547ac1680bfac25eb138e6703eb3dd434e", size = 46365, upload-time = "2026-03-11T22:19:10.662Z" }, - { url = "https://files.pythonhosted.org/packages/83/40/956dc20b7e00dc0ff3259871864f18dab211837fce3478778bedb3132ac1/ujson-5.12.0-cp314-cp314t-win_arm64.whl", hash = "sha256:42d875388fbd091c7ea01edfff260f839ba303038ffb23475ef392012e4d63dd", size = 40398, upload-time = "2026-03-11T22:19:11.666Z" }, - { url = "https://files.pythonhosted.org/packages/95/3c/5ee154d505d1aad2debc4ba38b1a60ae1949b26cdb5fa070e85e320d6b64/ujson-5.12.0-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:bf85a00ac3b56a1e7a19c5be7b02b5180a0895ac4d3c234d717a55e86960691c", size = 54494, upload-time = "2026-03-11T22:19:13.035Z" }, - { url = "https://files.pythonhosted.org/packages/ce/b3/9496ec399ec921e434a93b340bd5052999030b7ac364be4cbe5365ac6b20/ujson-5.12.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:64df53eef4ac857eb5816a56e2885ccf0d7dff6333c94065c93b39c51063e01d", size = 57999, upload-time = "2026-03-11T22:19:14.385Z" }, - { url = "https://files.pythonhosted.org/packages/0e/da/e9ae98133336e7c0d50b43626c3f2327937cecfa354d844e02ac17379ed1/ujson-5.12.0-graalpy312-graalpy250_312_native-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c0aed6a4439994c9666fb8a5b6c4eac94d4ef6ddc95f9b806a599ef83547e3b", size = 54518, upload-time = "2026-03-11T22:19:15.4Z" }, - { url = "https://files.pythonhosted.org/packages/58/10/978d89dded6bb1558cd46ba78f4351198bd2346db8a8ee1a94119022ce40/ujson-5.12.0-graalpy312-graalpy250_312_native-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efae5df7a8cc8bdb1037b0f786b044ce281081441df5418c3a0f0e1f86fe7bb3", size = 55736, upload-time = "2026-03-11T22:19:16.496Z" }, - { url = "https://files.pythonhosted.org/packages/80/25/1df8e6217c92e57a1266bf5be750b1dddc126ee96e53fe959d5693503bc6/ujson-5.12.0-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:8712b61eb1b74a4478cfd1c54f576056199e9f093659334aeb5c4a6b385338e5", size = 44615, upload-time = "2026-03-11T22:19:17.53Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f1/fe8a467d8ff5821e076b96f398d3acfe3cd568d900e6ccb41b215592b152/ujson-5.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:46998fc8d11aec34a20e2010905e7059732a3d192d9a3c3fe4f9ffd146c87ec8", size = 56746, upload-time = "2026-06-14T22:35:43.398Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c0/c7ab82d6471dfa7e4fd68ae6ff2c6a50d077c05d6ecdea0cec8af635b2c4/ujson-5.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ee03ce288ba25b05cf0de87203165642277a25caa4f00a437e13152e5214e310", size = 54388, upload-time = "2026-06-14T22:35:44.586Z" }, + { url = "https://files.pythonhosted.org/packages/10/e6/4e9e998d991ff88bbc93b21daa63bba2baa61c6f952dbcec937cf7304ebe/ujson-5.13.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:cdf33b588a81b05d0b585c66f83050c49cb670623424d10e4d1ad37ba2f7eed9", size = 60051, upload-time = "2026-06-14T22:35:45.567Z" }, + { url = "https://files.pythonhosted.org/packages/9c/11/876dff43f05417a01c6119f0fa10e01f1226631c5927ef08f56876b2bb67/ujson-5.13.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4cabd73c114ce93c21d7db2e2d8e16217fd8a5b2b3ec754629eebef5c262d47f", size = 53438, upload-time = "2026-06-14T22:35:46.623Z" }, + { url = "https://files.pythonhosted.org/packages/09/02/f9dbf6c3e46d700eb1d9ed637567221a06eeb1ec289633be992ef54d7a34/ujson-5.13.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ffc61fc756a64f4d169a78cc638d769e3c324f45fc51997626abf4e5e5dd6460", size = 55060, upload-time = "2026-06-14T22:35:47.647Z" }, + { url = "https://files.pythonhosted.org/packages/bc/3d/7e49a70265a1e5ed1b5e8edd5f54d57ae41e2134faeae9b16f6f5a0eae20/ujson-5.13.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c00323c13a35822c9a67a26c0b2a0787510bf1ef490922b58009b362d1a3e21", size = 58189, upload-time = "2026-06-14T22:35:48.617Z" }, + { url = "https://files.pythonhosted.org/packages/66/34/b64278f67e19052f09810576c7e50b3da8d4f5218b226046324d4d5c24b4/ujson-5.13.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:496e662a6b46d5f936d77fb68259cece19213bb2301ddd520dbd75ac7c90c5f4", size = 57941, upload-time = "2026-06-14T22:35:49.674Z" }, + { url = "https://files.pythonhosted.org/packages/a9/8c/51513357a5c75bf3e5bae46accfdb3e6e6f5caeb72ca8b253ec45ba853fb/ujson-5.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7fd41b86444df14f8b4b7afaaa9f27bacfbf8c18380872317aeab6cd125dcede", size = 1037688, upload-time = "2026-06-14T22:35:50.699Z" }, + { url = "https://files.pythonhosted.org/packages/54/5a/dc6afe071d6b977390d2dc41e15800a2716f317988dd03187cffe7b4d624/ujson-5.13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bf3c2c4ea55d4187903fcdc689a9bf5b0fc72d8c0eaff39db18c1f337c8832c1", size = 1197141, upload-time = "2026-06-14T22:35:52.052Z" }, + { url = "https://files.pythonhosted.org/packages/50/23/b473d101412c68527cb502a8728f96ab307aa7bfa75d6ea2037e2c7f74e8/ujson-5.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b6eca7751d61045a9b1e7f9a8c97ac24b164f085b60bef1c4668654bb2338011", size = 1090235, upload-time = "2026-06-14T22:35:53.589Z" }, + { url = "https://files.pythonhosted.org/packages/fa/0a/8e583cce90f9f91ca1bedb3e628b6f5642aed91feb29b197431268d4c4be/ujson-5.13.0-cp313-cp313-win32.whl", hash = "sha256:b63d3820f978bc8e98cc3f1fe26a33b0d2ea237733a23fe5e9cb5d51f466bd97", size = 40069, upload-time = "2026-06-14T22:35:55.019Z" }, + { url = "https://files.pythonhosted.org/packages/b4/b5/1fe203bc294e98fdd65606883692ad8dc0aaac73838b89c99c3513404424/ujson-5.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:17a59d5cf23ef98f7c9314524976b4b288374d83200add01d953024fb06404f9", size = 41098, upload-time = "2026-06-14T22:35:55.966Z" }, + { url = "https://files.pythonhosted.org/packages/50/5e/aceadce24fdb7cbc67f02286b1d4e91a575aaef5afb876c9908d6e6e5769/ujson-5.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:b49516fbe803ff30d6caa9ccc3799ec7f968992747ce3099eae4758928577b53", size = 38877, upload-time = "2026-06-14T22:35:56.936Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9a/b5139d696f5328f3cab70b9ec046f15e3f49497a4de6280974640602f539/ujson-5.13.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:cc9dfd41fed397ab03bb9d9fe1cbd83301211c772a17536033ce7d68877ac82b", size = 56897, upload-time = "2026-06-14T22:35:57.974Z" }, + { url = "https://files.pythonhosted.org/packages/53/55/477183aeddfdf0f88ae039ffee0ed866cfb993da0c0c9aa915807554aef8/ujson-5.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ca7ef2fa6c408a7c0f558e4d33d93b32ddc35ed6d3cfc505747931a64b7465d5", size = 54451, upload-time = "2026-06-14T22:35:58.932Z" }, + { url = "https://files.pythonhosted.org/packages/ea/63/55e5f23e156b4c8bca095d828b4cd3180c0b42aa3501ef88836d79606fea/ujson-5.13.0-cp314-cp314-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:a554b2e5bee85030369514cef8b0b913cebe1a4c2c0c13541966d50bcba22b1a", size = 60053, upload-time = "2026-06-14T22:35:59.969Z" }, + { url = "https://files.pythonhosted.org/packages/26/b6/08c6cf5548bd6f4bb557c9fa7e8edf87324bb04c17249d1966028d61dde0/ujson-5.13.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ea939ff629ab03ae970d03eca6d1febd8ed55ba38ca44aec64ce997537cd3fa0", size = 53481, upload-time = "2026-06-14T22:36:01.007Z" }, + { url = "https://files.pythonhosted.org/packages/6e/b3/0ac9a03551467784067f505df1bb875c639ba32f1da79ce467ab15911ada/ujson-5.13.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b98bf2faa5e37ecfe752226ea08290031e375a0c43d425a0b955fb3e702a2a71", size = 55058, upload-time = "2026-06-14T22:36:02.297Z" }, + { url = "https://files.pythonhosted.org/packages/ba/be/ec91029aec067174473d022fa0f6c3c1431a173f888d7599739f05c668eb/ujson-5.13.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a4b92344b16e414aeb609e57f62c466500e53c94f1698f5b149dc0b7223ec3e", size = 58225, upload-time = "2026-06-14T22:36:03.321Z" }, + { url = "https://files.pythonhosted.org/packages/29/33/a948f329252ece3f9c93d177243de6e677927ebc6ac44256742dbbef3c39/ujson-5.13.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7df805aad707507a1fa165fb716218ca3a89f142125dc4b23c9fcc08fa402d97", size = 57930, upload-time = "2026-06-14T22:36:04.385Z" }, + { url = "https://files.pythonhosted.org/packages/f1/0c/c33655218b8e0a8adbf066de0b999cae5c324061f3eaa4dda17423145d9e/ujson-5.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7576bdbef327c3528f011002a2d74486f6fe4e33289bdb7a042b7f1a6e9d8285", size = 1037728, upload-time = "2026-06-14T22:36:05.467Z" }, + { url = "https://files.pythonhosted.org/packages/2c/bd/d286947525ea7ce3f2d8dc55c15b9ffbe425bc455c96af7b8f8a402599a9/ujson-5.13.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:6eee5d7cce3f32a468905f9ff61807a60287a90258d849460f6fa826e810870d", size = 1197146, upload-time = "2026-06-14T22:36:06.839Z" }, + { url = "https://files.pythonhosted.org/packages/3c/3c/9eb916377050b0785f048a34588c1c390ddd41ae00b78db68ee1ad022356/ujson-5.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:144e9d8a454cfa727e0f755e1863738ed68068583bda5463052cb446835bd56c", size = 1090223, upload-time = "2026-06-14T22:36:08.329Z" }, + { url = "https://files.pythonhosted.org/packages/dd/5f/242fd97a2628b842d4bfaa9b18e1f68187f934d67503291ebbaab1254637/ujson-5.13.0-cp314-cp314-win32.whl", hash = "sha256:576f35c35b918d67d41b933878062ec0a5c9f4d1e9e14e04aeef35384963feae", size = 41223, upload-time = "2026-06-14T22:36:09.644Z" }, + { url = "https://files.pythonhosted.org/packages/23/f3/7f2bd9ca0c507142d0c22347b3d6f8803be1d8851c31707e57f5923fdbea/ujson-5.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:d5e206e9f849ead27e51ef8da44e52b38da7c6dbd929a7340ab44533edcda8d7", size = 42265, upload-time = "2026-06-14T22:36:11.043Z" }, + { url = "https://files.pythonhosted.org/packages/b0/29/3e9a8fba321c031315f6d263510969a5d01f41fc471b5be107e413c1b2f8/ujson-5.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:dc470179775468f9a007d3a6a2734624248c94bf47c6645e808c7e50a5070d1a", size = 40205, upload-time = "2026-06-14T22:36:12.286Z" }, + { url = "https://files.pythonhosted.org/packages/12/e9/1c543837c6a3c6672361882a0fa269bd02daf9cc4c0ca88a9dccd9df98d9/ujson-5.13.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:69b4e36bb7d5f413ba8c00c8006b2ec627cc5ace97301462f6aadb66ec9d2979", size = 57402, upload-time = "2026-06-14T22:36:13.238Z" }, + { url = "https://files.pythonhosted.org/packages/99/e4/39862f0f7174ff07cfd1e2d0c9065ded34aeebdb7db8daf2f0e5bf89b46f/ujson-5.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8b644d50f66de5490c1823c7176618cead5e8e8a88cba9f40a6308ca52e79267", size = 54973, upload-time = "2026-06-14T22:36:14.432Z" }, + { url = "https://files.pythonhosted.org/packages/02/66/f53d3b32c3f177f846ca6b624e832f29000d8a213a2d8768e254bd470ced/ujson-5.13.0-cp314-cp314t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:15107aaa4f559d55201165ec32abb35c283a861be1fa67229578cb7d93fcd93a", size = 60683, upload-time = "2026-06-14T22:36:15.806Z" }, + { url = "https://files.pythonhosted.org/packages/e6/d4/dddc4646d2633c85c938c2ded7d5a9711cdad5be1e13b31b7dad76f61c83/ujson-5.13.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6e343c5f0c058523f1edbf6ae4eceb4e0d934205a53bbdd8d9a945c83324662a", size = 54167, upload-time = "2026-06-14T22:36:16.952Z" }, + { url = "https://files.pythonhosted.org/packages/5f/c0/d8608c3f4d3f05e6441364b63fde1d279700135c1a6577a773662c07fbcc/ujson-5.13.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:02200035bc80e830f076ffc1b329a94c295aee6d9de8c9043647cb9a7bd4f76f", size = 55568, upload-time = "2026-06-14T22:36:17.975Z" }, + { url = "https://files.pythonhosted.org/packages/22/8e/dd12b735aaba0806c3d70c18184d50e1f9712e0757c7c0a4f376450cfe28/ujson-5.13.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d7f19b81b73ff28f5c5022ee794f94122bfcda07a76423078e349465d71223a1", size = 59086, upload-time = "2026-06-14T22:36:19.071Z" }, + { url = "https://files.pythonhosted.org/packages/48/43/ad41e8752d5ec3a590a5e7b426a54e36b7aab911d9b5a4f7384dc62507ab/ujson-5.13.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:82e1393e6dbe3c95fdfc95c6c528890e191351a1f024ef51126cf1f22543af52", size = 58667, upload-time = "2026-06-14T22:36:20.171Z" }, + { url = "https://files.pythonhosted.org/packages/e1/8e/b44a6afb77b94118655c029081b7932d64bb4c5b1c8ba2b7f5808b5d0bc2/ujson-5.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:38afcf994b28ed85ea2420e2a8d79a37d0a77348b3daf53850c16edda66f942d", size = 1038553, upload-time = "2026-06-14T22:36:21.245Z" }, + { url = "https://files.pythonhosted.org/packages/7e/93/fab1d786174c8780eb3e386c73f1925a435e97fbf77c957fea4fca83994d/ujson-5.13.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:1bdf2518971586f2b413156c49d9dd8b56cc990a8647081e1bd00af60564d469", size = 1197938, upload-time = "2026-06-14T22:36:22.585Z" }, + { url = "https://files.pythonhosted.org/packages/f3/bc/2f073bb708f9d128f5d1cb39063a5f6421b1ce94c61be8661c55a189f407/ujson-5.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:751ad01042472f1c7c02f5c597c7aee79834e82a6cc384ca302173bbc8e8deb8", size = 1090938, upload-time = "2026-06-14T22:36:23.947Z" }, + { url = "https://files.pythonhosted.org/packages/c4/67/cdaa50bba29d7dc9eb19212755b09bb96f56596e75957c3717c6b85454de/ujson-5.13.0-cp314-cp314t-win32.whl", hash = "sha256:74f3dd61aeb01b7b2a6754e400224e819279041b3867935a55ccf57fb86a43b2", size = 41802, upload-time = "2026-06-14T22:36:25.418Z" }, + { url = "https://files.pythonhosted.org/packages/bd/66/a6e669e90083febdf6c0600d3807f6017fd4d3962d5bd6ddc605c73a06e5/ujson-5.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:5c31317d5e4504dae8f98795358b6082fc0ef96e7394806db0a76a4a8717f446", size = 42790, upload-time = "2026-06-14T22:36:26.614Z" }, + { url = "https://files.pythonhosted.org/packages/3d/5f/fcc6c6a9d711fd8b020ca8ff65148212f0a712c809d173cd949e58de68c6/ujson-5.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aefd3c9c95f9b62348956396ff7b31818476f8f54dc4a4e64cbd4f0491db6fca", size = 40708, upload-time = "2026-06-14T22:36:27.721Z" }, ] From 926e27c5bb13387f68977708e87b0e36cf09f8e9 Mon Sep 17 00:00:00 2001 From: felixeisenmenger Date: Thu, 25 Jun 2026 08:53:26 +0200 Subject: [PATCH 4/9] - integrate Pydantic validation into overloads - improve function dispatch performance by restructuring parameter handling and optimizing class name resolution logic - add new test cases for complex inheritance, mixins, and Pydantic edge cases --- Improvements.md | 40 --------- src/strongtyping_pyoverload/class_tools.py | 25 +++--- src/strongtyping_pyoverload/func_info.py | 2 +- tests/test_edge_cases.py | 100 +++++++++++++++++++++ tests/test_pydantic_integration.py | 16 ++++ 5 files changed, 129 insertions(+), 54 deletions(-) delete mode 100644 Improvements.md create mode 100644 tests/test_edge_cases.py diff --git a/Improvements.md b/Improvements.md deleted file mode 100644 index 5a12b27..0000000 --- a/Improvements.md +++ /dev/null @@ -1,40 +0,0 @@ -### Project Analysis & Suggestions for `strongtyping-pyoverload` - -As a seasoned Software Developer and Open Source Contributor, I've analyzed `strongtyping-pyoverload`. The project addresses a genuine gap in Python—true runtime polymorphism that goes beyond what `typing.overload` (static only) and `functools.singledispatch` (limited to the first argument) offer. - -To make this project more valuable in the current ecosystem dominated by **Pydantic**, **FastAPI**, and **AI-driven development**, I suggest the following enhancements: - ---- - -#### 1. Native Pydantic Integration (Validation-First Overloading) -Today's developers use Pydantic to ensure data integrity. Instead of just checking types, `overload` could leverage Pydantic's validation logic. -- **Suggestion:** If a parameter is hinted with a Pydantic model, the dispatcher should attempt validation. If it fails, it moves to the next overload. -- **Value:** This allows for "Schema-based Overloading." -- **Example:** -```python -@overload -def create_user(data: UserCreateSchema): # Pydantic Model - ... - -@overload -def create_user(data: dict): - ... -``` - -#### 2. Performance Optimization for FastAPI (Production Readiness) -FastAPI's strength is speed. Runtime type checking via `inspect` and string parsing (like `extract_class_name_from_func`) is expensive. -- **Suggestion:** - - Move from a global list `__override_items__` to a more structured, hashed registry (e.g., a dictionary keyed by `(module, qualname, arg_count)`). - - Implement a "Warm-up" phase or JIT-style dispatching where the best match is mapped after the first call to avoid repeated logic. -- **Value:** Makes the library viable for high-throughput API endpoints. - -#### 3. "AI-Friendly" Explicit Metadata -AI coding tools (Copilot, Cursor) and IDEs often struggle with dynamic decorators that hide signatures. -- **Suggestion:** - - Ensure `inner` properly updates `__annotations__` and `__signature__` to reflect a merged Union of all overloads. - - Provide a PEP 561 `py.typed` marker if not already present to help static analyzers understand the runtime behavior. -- **Value:** Improves AI's ability to suggest the correct overload while typing. - - -### Summary of Strategic Direction -The goal should be to transform `strongtyping-pyoverload` from a "utility for cleaner code" into a **"Type-Safe Dispatch Engine"** that feels like a native part of the Pydantic/FastAPI stack. This shift from simple `isinstance` checks to "Validation-based Dispatch" would make it a unique and powerful tool in the modern Pythonista's arsenal. diff --git a/src/strongtyping_pyoverload/class_tools.py b/src/strongtyping_pyoverload/class_tools.py index ac0f86c..c49f95f 100644 --- a/src/strongtyping_pyoverload/class_tools.py +++ b/src/strongtyping_pyoverload/class_tools.py @@ -7,8 +7,6 @@ import itertools from strongtyping.cached_dict import CachedDict -from strongtyping.strong_typing_utils import check_type - from strongtyping_pyoverload.func_info import FuncInfo try: @@ -28,7 +26,7 @@ def generate_parameter_infos(func: MethodType): params = inspect.signature(func).parameters annotations = func.__annotations__ func_params = { - val.name: (str(ANY), val.kind.name, val.name) + val.name: (typing.Any, val.kind.name, val.name) for key, val in params.items() if val.name != "self" } @@ -86,20 +84,18 @@ def check_pydantic_model(func_info, args, kwargs) -> bool | None: for idx, param in enumerate(func_info.params_): annotation = param[0] try: - # Check if it's a Pydantic BaseModel subclass if isinstance(annotation, type) and issubclass(annotation, BaseModel): - # Pydantic v2: use model_validate(); raises ValidationError on failure - if args: + if args and idx < len(args): model = annotation.model_validate(args[idx]) else: model = annotation.model_validate(kwargs[param[2]]) else: - is_valid = False + continue except (AttributeError, TypeError, ValidationError): return False else: if args: - func_info.pydantic_params_.append(model) + func_info.pydantic_params_.append((idx, model)) else: func_info.pydantic_kwargs_.append(model) else: @@ -159,9 +155,10 @@ def inner(cls_=None, *args, **kwargs): return cached_result try: - class_names = [obj.__name__ for obj in cls_.__class__.__mro__] if cls_ else [] - class_names.append(func_class_name) - class_names = set(class_names) + class_names = [func_class_name] + for obj in cls_.__class__.__mro__: + if obj.__name__ not in class_names: + class_names.append(obj.__name__) except (AttributeError, TypeError): class_names = func_class_name @@ -176,8 +173,10 @@ def inner(cls_=None, *args, **kwargs): f"No function was found which matches your parameters `{args}_{kwargs}`" ) try: - arg_values = func_info.pydantic_params_ if func_info.pydantic_params_ else args - kwarg_values = func_info.pydantic_kwargs_ if func_info.pydantic_kwargs_ else kwargs + arg_values = list(args) + for idx, model in func_info.pydantic_params_: + arg_values[idx] = model + kwarg_values = kwargs | func_info.pydantic_kwargs_ if cls_ is None: result = func_info.func_(*arg_values, **kwarg_values) else: diff --git a/src/strongtyping_pyoverload/func_info.py b/src/strongtyping_pyoverload/func_info.py index c2819c8..2786284 100644 --- a/src/strongtyping_pyoverload/func_info.py +++ b/src/strongtyping_pyoverload/func_info.py @@ -12,7 +12,7 @@ def __init__(self, func_, params_: list): self.func_name_ = func_.__name__ self.params_ = params_ self.cls_name_ = self.extract_class_name_from_func(func_) - self.pydantic_params_ = [] + self.pydantic_params_: list[tuple[int, object]] = [] self.pydantic_kwargs_ = {} @staticmethod diff --git a/tests/test_edge_cases.py b/tests/test_edge_cases.py new file mode 100644 index 0000000..eec0322 --- /dev/null +++ b/tests/test_edge_cases.py @@ -0,0 +1,100 @@ +import pytest +from strongtyping_pyoverload import overload + +class Base: + @overload + def process(self, x: int): + return f"Base int: {x}" + +class Derived(Base): + @overload + def process(self, x: str): + return f"Derived str: {x}" + +class SubDerived(Derived): + @overload + def process(self, x: float): + return f"SubDerived float: {x}" + +def test_deeper_inheritance(): + obj = SubDerived() + assert obj.process(1) == "Base int: 1" + assert obj.process("hello") == "Derived str: hello" + assert obj.process(1.5) == "SubDerived float: 1.5" + +class Mixin1: + @overload + def handle(self, x: int): + return f"Mixin1 int: {x}" + +class Mixin2: + @overload + def handle(self, x: str): + return f"Mixin2 str: {x}" + +class Combined(Mixin1, Mixin2): + @overload + def handle(self, x: float): + return f"Combined float: {x}" + +def test_mixins(): + obj = Combined() + assert obj.handle(1) == "Mixin1 int: 1" + assert obj.handle("hello") == "Mixin2 str: hello" + assert obj.handle(1.5) == "Combined float: 1.5" + +class PartialTyping: + @overload + def compute(self, x: int, y): + return f"int, any: {x}, {y}" + + @overload + def compute(self, x: str, y: int): + return f"str, int: {x}, {y}" + +def test_partial_typing(): + obj = PartialTyping() + assert obj.compute(1, "any") == "int, any: 1, any" + assert obj.compute("hello", 2) == "str, int: hello, 2" + +class KeywordOnly: + @overload + def find(self, *, name: str): + return f"name: {name}" + + @overload + def find(self, *, id: int): + return f"id: {id}" + +def test_keyword_only(): + obj = KeywordOnly() + assert obj.find(name="Alice") == "name: Alice" + assert obj.find(id=42) == "id: 42" + with pytest.raises(AttributeError): + obj.find("Alice") + +class MixedArgs: + @overload + def info(self, name: str, *, age: int): + return f"str, age={age}" + + @overload + def info(self, id: int, *, active: bool): + return f"int, active={active}" + +def test_mixed_args(): + obj = MixedArgs() + assert obj.info("Bob", age=30) == "str, age=30" + assert obj.info(1, active=True) == "int, active=True" + +class DeepInheritancePartialTyping(SubDerived): + @overload + def process(self, x: int, y): + return f"Deep int, any: {x}, {y}" + +def test_deep_inheritance_partial_typing(): + obj = DeepInheritancePartialTyping() + assert obj.process(1) == "Base int: 1" + assert obj.process("hello") == "Derived str: hello" + assert obj.process(1.5) == "SubDerived float: 1.5" + assert obj.process(1, "extra") == "Deep int, any: 1, extra" diff --git a/tests/test_pydantic_integration.py b/tests/test_pydantic_integration.py index 1f69de9..74f8a0c 100644 --- a/tests/test_pydantic_integration.py +++ b/tests/test_pydantic_integration.py @@ -52,3 +52,19 @@ def test_pydantic_direct_model_instances(): user = UserCreateSchema(name="Charlie", age=25) res = handler.process(user) assert res == "Created user Charlie" + +class ComplexPydanticHandler(DataHandler): + @overload + def process(self, data: UserCreateSchema, *, priority: int = 0): + return f"Created user {data.name} with priority {priority}" + +def test_complex_pydantic_dispatching(): + handler = ComplexPydanticHandler() + + # Matches ComplexPydanticHandler.process because of keyword arg + assert handler.process({"name": "Alice", "age": 30}, priority=1) == "Created user Alice with priority 1" + + # Matches ComplexPydanticHandler.process even without priority because it has a default value + # and it is defined in the subclass, which usually takes precedence if it matches. + assert handler.process({"name": "Alice", "age": 30}) == "Created user Alice with priority 0" + assert handler.process({"id": 1, "name": "Bob"}) == "Updated user 1" From 0f00a61660c8d42b279af60369072d1223947043 Mon Sep 17 00:00:00 2001 From: felixeisenmenger Date: Thu, 25 Jun 2026 08:56:00 +0200 Subject: [PATCH 5/9] fix formating --- src/strongtyping_pyoverload/class_tools.py | 13 ++++++++----- src/strongtyping_pyoverload/func_info.py | 10 +++++++++- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/strongtyping_pyoverload/class_tools.py b/src/strongtyping_pyoverload/class_tools.py index c49f95f..0f8d79b 100644 --- a/src/strongtyping_pyoverload/class_tools.py +++ b/src/strongtyping_pyoverload/class_tools.py @@ -42,7 +42,9 @@ def generate_docstring(lookup_key: tuple[str, str]): ) -def find_corresponding_func(func_name, cls_name: str | list[str], args: tuple, kwargs: dict) -> FuncInfo | None: +def find_corresponding_func( + func_name, cls_name: str | list[str], args: tuple, kwargs: dict +) -> FuncInfo | None: pos_or_kwarg_funcs = [] if isinstance(cls_name, str): data = __override_items__[(cls_name, func_name)] @@ -80,7 +82,10 @@ def check_pydantic_model(func_info, args, kwargs) -> bool | None: if not PYDANTIC_INSTALLED: return False is_valid = True - if any(isinstance(param[0], type) and issubclass(param[0], BaseModel) for param in func_info.params_): + if any( + isinstance(param[0], type) and issubclass(param[0], BaseModel) + for param in func_info.params_ + ): for idx, param in enumerate(func_info.params_): annotation = param[0] try: @@ -165,9 +170,7 @@ def inner(cls_=None, *args, **kwargs): if is_module_function or cls_ is None: func_info = find_corresponding_func(func.__name__, class_names, args, kwargs) else: - func_info = find_corresponding_func( - func.__name__, class_names, (cls_, *args), kwargs - ) + func_info = find_corresponding_func(func.__name__, class_names, (cls_, *args), kwargs) if not func_info: raise AttributeError( f"No function was found which matches your parameters `{args}_{kwargs}`" diff --git a/src/strongtyping_pyoverload/func_info.py b/src/strongtyping_pyoverload/func_info.py index 2786284..4c1bbbb 100644 --- a/src/strongtyping_pyoverload/func_info.py +++ b/src/strongtyping_pyoverload/func_info.py @@ -1,11 +1,19 @@ from strongtyping.strong_typing_utils import check_type + ANY = object() class FuncInfo: pydantic_args_: list pydantic_kwargs_: dict - __slots__ = ("func_", "params_", "func_name_", "cls_name_", "pydantic_params_", "pydantic_kwargs_") + __slots__ = ( + "func_", + "params_", + "func_name_", + "cls_name_", + "pydantic_params_", + "pydantic_kwargs_", + ) def __init__(self, func_, params_: list): self.func_ = func_ From 31d2a1f725f27141b974a73e9cd4a6e3f4e9734a Mon Sep 17 00:00:00 2001 From: felixeisenmenger Date: Thu, 25 Jun 2026 09:01:19 +0200 Subject: [PATCH 6/9] add missing package to tox dependencies --- pyproject.toml | 10 +++++----- tox.ini | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index be118b6..df554ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,15 +15,15 @@ classifiers = [ ] license = { text = "MIT License" } dependencies = [ - "strongtyping>=3.13.0", + "strongtyping>=3.13.10", ] [dependency-groups] dev = [ - "pydantic>=2.13.4", - "pytest>=9.0.3", - "pytest-cov>=7.1.0", - "ruff>=0.15.12", + "pydantic==2.13.4", + "pytest==9.1.1", + "pytest-cov==7.1.0", + "ruff==0.15.19", ] [project.urls] diff --git a/tox.ini b/tox.ini index 87eba85..64610ba 100644 --- a/tox.ini +++ b/tox.ini @@ -5,6 +5,7 @@ envlist = py312,py313,py314 deps = pytest pytest-cov ujson + pydantic py3-strongtyping312: strongtyping>=3.12,<3.13 py3-strongtyping313: strongtyping>=3.13 py3-strongtyping314: strongtyping>=3.13 From 10d8d378e13daf041c1da9d71111c0df0361ff6b Mon Sep 17 00:00:00 2001 From: felixeisenmenger Date: Thu, 25 Jun 2026 09:09:59 +0200 Subject: [PATCH 7/9] drop support for py3.12 within tox --- tox.ini | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index 64610ba..9cf6386 100644 --- a/tox.ini +++ b/tox.ini @@ -1,12 +1,11 @@ [tox] -envlist = py312,py313,py314 +envlist = py313,py314 [testenv] deps = pytest pytest-cov ujson - pydantic - py3-strongtyping312: strongtyping>=3.12,<3.13 + pydantic‚ py3-strongtyping313: strongtyping>=3.13 py3-strongtyping314: strongtyping>=3.13 From e36704885a31973b7ba171c22a0fffff4a1acc73 Mon Sep 17 00:00:00 2001 From: felixeisenmenger Date: Thu, 25 Jun 2026 09:12:02 +0200 Subject: [PATCH 8/9] fixup! typo --- .github/workflows/python-tox.yml | 5 ----- tox.ini | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/python-tox.yml b/.github/workflows/python-tox.yml index db78de1..db31108 100644 --- a/.github/workflows/python-tox.yml +++ b/.github/workflows/python-tox.yml @@ -20,11 +20,6 @@ jobs: with: python-version: '3.13' - - name: Set up Python 3.12 - uses: actions/setup-python@v6 - with: - python-version: '3.12' - - name: Install dependencies run: | pip install --upgrade pip diff --git a/tox.ini b/tox.ini index 9cf6386..2948bae 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ envlist = py313,py314 deps = pytest pytest-cov ujson - pydantic‚ + pydantic py3-strongtyping313: strongtyping>=3.13 py3-strongtyping314: strongtyping>=3.13 From cd53e7a4a1c4994185692b03cbe1a7f602d9cf9a Mon Sep 17 00:00:00 2001 From: felixeisenmenger Date: Thu, 25 Jun 2026 11:34:24 +0200 Subject: [PATCH 9/9] update documentation for v0.4.4: add changelog, Python 3.13/3.14 support, and Pydantic integration details --- HowTo.md | 122 ++++++++++++++----------------------------- README.md | 32 +++++++++--- docs/CHANGELOG.md | 8 +++ docs/class_level.md | 63 +++++++++++++--------- docs/index.md | 106 ++++++++----------------------------- docs/install.md | 8 ++- docs/module_level.md | 4 +- mkdocs.yml | 1 + 8 files changed, 142 insertions(+), 202 deletions(-) diff --git a/HowTo.md b/HowTo.md index 1bd0ef9..4d3b5ea 100644 --- a/HowTo.md +++ b/HowTo.md @@ -1,106 +1,62 @@ -# How to write cleaner Pythoncode with `strongtyping-pyoverload` +# How to write cleaner Python code with `strongtyping-pyoverload` -- With starting of Type-Hints in Python we can now better define what input we expect -we can say we only want to get a specific kind of Type, or we allow multiple once -```python -def func_a(a: int): - ... +Python's flexibility is one of its greatest strengths, but as your codebase grows, managing complex function logic based on varying input types can quickly turn into a messy web of `if isinstance(...)` checks. -# in python 3.10 we can also write `str | int` instead of Union -def func(a: Union[str, int]): - ... -``` -- the same works on class level too -```python -class Foo: - def func_a(self, a: list): - ... - - # in python 3.10 we can also write `str | int` instead of Union - def func(self, a: Union[list, tuple]): - if isinstance(a, list): - ... - if isinstance(a, tuple): - ... -``` -- wouldn't it not be nice to have a dedicated method for each parameter without renaming it -```python -class Foo: - def func(self, a: str): - print("Called with `str`") +What if you could write cleaner, more expressive code by defining multiple versions of the same function, each tailored to specific types? - def func(self, a: list): - print("Called with `list`") +### ✨ The Solution: Elegant Overloading +With `strongtyping-pyoverload`, you can separate these concerns into distinct, beautifully typed methods. The decorator handles the dispatching logic at runtime, ensuring the right code runs for the right data. - def func(self, a: tuple): - print("Called with `tuple`") -``` -- Python will raise no error if you do this but if you call the function -```python ->>> foo = Foo() ->>> foo.func([1, 2, 3]) -"Called with `tuple`" ->>> foo.func((1, 2, 3)) -"Called with `tuple`" -``` -- Python will always use the latest definition for both cases -- This is where the `overload` decorator from `strongtyping-pyoverload` comes into play ```python from strongtyping_pyoverload import overload - -class Foo: - +class DataProcessor: @overload - def func(self, a: str): - print("Called with `str`") + def process(self, data: str): + return data.upper() @overload - def func(self, a: list): - print("Called with `list`") + def process(self, data: list): + return [item * 2 for item in data] @overload - def func(self, a: tuple): - print("Called with `tuple`") + def process(self, data: MyPydanticModel): + return data.model_dump() +``` +### 🛡️ Native Pydantic Integration +Stop manually validating dictionaries. You can define overloads that take Pydantic models. If you pass a dictionary that matches a model's schema, `strongtyping-pyoverload` can automatically validate it and dispatch to the correct handler. ->>> foo = Foo() ->>> foo.func("hello") -"Called with `str`" ->>> foo.func(list("hello")) -"Called with `list`" ->>> foo.func(tuple("hello")) -"Called with `tuple`" -``` -- The same works on module level too ```python -# module_a.py +from pydantic import BaseModel from strongtyping_pyoverload import overload +class UserCreate(BaseModel): + name: str + age: int + +class UserHandler: + @overload + def process(self, data: UserCreate): + return f"Creating user {data.name}" -@overload -def module_func(): - return 0 + @overload + def process(self, data: dict): + return "Processing raw dictionary" +handler = UserHandler() +# Automatically validates dict against UserCreate schema +print(handler.process({"name": "Alice", "age": 30})) # Output: Creating user Alice +``` -@overload -def module_func(a: int, b: int): - return a * b +### 🧬 Deep Inheritance & Mixin Support +It plays well with others. Whether you're using deep class hierarchies or mixing in functionality from multiple sources, the `overload` decorator respects the Method Resolution Order (MRO), ensuring that the most specific implementation is always found. +### 🤖 AI-Ready Code +By utilizing `__signature__` and `__annotations__` metadata, this library makes your code more "readable" for AI coding assistants and modern IDEs. Your tools will understand exactly which version of a function is being called, providing better autocompletion and insights. -@overload -def module_func(a: str, b: str): - return a + b +### ⚡ High Performance +The library uses a structured registry and optimized lookup logic to ensure that the overhead of runtime dispatching is kept to an absolute minimum. -... -# module_b.py -from module_a import module_func ->>> module_func() -0 ->>> module_func(2, 2) -4 ->>> module_func("foo", "bar") -"foobar" -``` -- I think this will help to write cleaner code as a function now will only be called if the parameters are matching -- Otherwise you will get an `AttributeError` +### 🐍 Support for Modern Python Features +Full support for `typing.Annotated`, `Keyword-Only` parameters, and Python 3.13+. It’s built for the modern Python ecosystem. diff --git a/README.md b/README.md index b3a7d14..350ce17 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,4 @@ # strongtyping-pyoverload -[![Python 3.11](https://img.shields.io/badge/python-3.11-blue.svg)](https://www.python.org/downloads/release/python-3110/) -[![Python 3.12](https://img.shields.io/badge/python-3.12-blue.svg)](https://www.python.org/downloads/release/python-3120/) [![Python 3.13](https://img.shields.io/badge/python-3.13-blue.svg)](https://www.python.org/downloads/release/python-3130/) [![Python 3.14](https://img.shields.io/badge/python-3.14-blue.svg)](https://www.python.org/downloads/release/python-3140/) ![Python application](https://github.com/FelixTheC/py-overload/workflows/Python%20application/badge.svg) @@ -10,10 +8,30 @@ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) [![AI Agents](https://img.shields.io/badge/AI_Agents-SKILL.md-blue?logo=robotframework&logoColor=white)](SKILL.md) -## A Runtime method overload decorator which add overloading capacity similar to C++ -- there is a `override` decorator from `typing` which works only for static type checking -- this decorator works on `runtime` +## Runtime method overloading for Python +`strongtyping-pyoverload` provides a powerful `overload` decorator that brings true runtime method overloading to Python, similar to C++. -## Documentation can be found here -### [readthedocs](https://strongtyping-pyoverload.readthedocs.io/en/latest/) +### Key Features +- **Native Pydantic Integration**: Automatically validate and dispatch based on Pydantic models. +- **Deep Inheritance & Mixin Support**: Respects Method Resolution Order (MRO) for complex class hierarchies. +- **AI-Ready Metadata**: Sets `__signature__` and `__annotations__` for better IDE and AI assistant support. +- **High Performance**: Optimized lookup logic with caching for minimal overhead. +- **Modern Python**: Full support for `typing.Annotated`, keyword-only parameters, and Python 3.13+. + +### Quick Start +```python +from strongtyping_pyoverload import overload + +class DataProcessor: + @overload + def process(self, data: str): + return data.upper() + + @overload + def process(self, data: list): + return [item * 2 for item in data] +``` + +## Documentation +Full documentation can be found at [readthedocs](https://strongtyping-pyoverload.readthedocs.io/en/latest/). diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index c80cde0..d9e79c5 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,3 +1,11 @@ +## 0.4.4 +- Native Pydantic Integration: Automatically validate and dispatch based on Pydantic models. +- Performance Optimization: Implemented optimized registry and lookup logic with caching. +- AI-Ready Metadata: Added `__signature__` and `__annotations__` to overloaded functions for better IDE/AI support. +- Deep Inheritance & Mixin Support: Properly respect MRO during dispatching. +- Modern Python: Added support for `typing.Annotated` and keyword-only parameters. +- Support for Python 3.13 and 3.14. + ## 0.3.0 - support *args and **kwargs - extend Documentation diff --git a/docs/class_level.md b/docs/class_level.md index 664eb74..7c93f3c 100644 --- a/docs/class_level.md +++ b/docs/class_level.md @@ -30,43 +30,54 @@ Called with `tuple` ``` ### Subclasses/Inheritance -can overwrite an existing method __but__ these __must match the exact type definition__ of the __original method__ +The `overload` decorator respects the Method Resolution Order (MRO), allowing you to extend or override functionality in subclasses. + ```python from strongtyping_pyoverload import overload +class Base: + @overload + def process(self, x: int): + return f"Base int: {x}" -class Example: +class Derived(Base): @overload - def other_func(self): - return 0 + def process(self, x: str): + return f"Derived str: {x}" +class SubDerived(Derived): @overload - def other_func(self, a: int, b: int): - return (a * a) / b + def process(self, x: float): + return f"SubDerived float: {x}" +obj = SubDerived() +print(obj.process(1)) # Base int: 1 +print(obj.process("hi")) # Derived str: hi +print(obj.process(1.5)) # SubDerived float: 1.5 +``` -class Other(Example): +### Mixins +You can also combine functionality from multiple mixin classes. +```python +class Mixin1: @overload - def other_func(self, a): - return a ** a + a + def handle(self, x: int): + return f"Mixin1: {x}" +class Mixin2: @overload - def other_func(self, a: int, b: int): # the parameters and everything are exact the same - return ((a * a) / b) + a -``` -```pycon ->>> example = Example() ->>> example.other_func(2, 3) -1.333333333333333 ->>> ->>> other = Other() ->>> other.other_func() -0 ->>> other.other_func(2) -6 ->>> other.other_func(2, 3) -3.333333333333333 + def handle(self, x: str): + return f"Mixin2: {x}" + +class Combined(Mixin1, Mixin2): + @overload + def handle(self, x: float): + return f"Combined: {x}" + +obj = Combined() +print(obj.handle(1)) # Mixin1: 1 +print(obj.handle("hi")) # Mixin2: hi ``` ### A type hint for each parameter?? @@ -130,7 +141,7 @@ class Other: ``` ### No function matches -when no function matches an `AttributError` will be raised +When no function matches an `AttributeError` will be raised. ```python from strongtyping_pyoverload import overload @@ -145,5 +156,5 @@ class Example: >>> example.other_func("Not", "Supported") Traceback (most recent call last): ... -AttributeError: `Example` has no function which matches with your parameters `('Not', 'Supported')` +AttributeError: `Example` has no function which matches with your parameters `('Not', 'Supported'), {}` ``` \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 2e9ba03..a57f204 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,7 +1,6 @@ # strongtyping-pyoverload -[![Python 3.9](https://img.shields.io/badge/python-3.9-blue.svg)](https://www.python.org/downloads/release/python-390/) -[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) -[![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336)](https://pycqa.github.io/isort/) +[![Python 3.13](https://img.shields.io/badge/python-3.13-blue.svg)](https://www.python.org/downloads/release/python-3130/) +[![Python 3.14](https://img.shields.io/badge/python-3.14-blue.svg)](https://www.python.org/downloads/release/python-3140/) ![Python application](https://github.com/FelixTheC/py-overload/workflows/Python%20application/badge.svg) ![Python tox](https://github.com/FelixTheC/py-overload/workflows/Python%20tox/badge.svg) ![image](https://codecov.io/gh/FelixTheC/py-overload/graph/badge.svg) @@ -11,97 +10,38 @@ ### The Problem With the introduction of Type-Hints in Python, now one is able to define the intended input types of a certain function. -This works pretty well, but often we end up in one of the following or similar situation. +This works pretty well, but often we end up in a situation where we need to handle multiple types in a single function using `isinstance` checks. ```python -def func(a: Union[str, int]): - if isinstance(a, str): - ... - else: - ... +def process(data): + if isinstance(data, str): + return data.upper() + elif isinstance(data, list): + return [item * 2 for item in data] + # ... it grows and becomes hard to maintain ``` -or - -```python -def _func_str(a: str): - ... - -def _func_int(a: int): - ... - -def func(a: Union[str, int]): - if isinstance(a, str): - _func_str(a) - else: - _func_int(a) -``` -we define functions and sometimes allow multiple parameters because the end result will be more or less the same, -but we need to make some additional parsing or so. To have cleaner code we now create also some helper functions. - - ### The Solution -with the `overload` decorator you can define dedicated functions with the same name. -```python -from typing import List +With the `overload` decorator you can define dedicated functions with the same name, each tailored to specific types. +```python from strongtyping_pyoverload import overload - -class Example: - @overload - def my_func(self): - return 0 - - @overload - def my_func(self, a: int, b: int): - return a * b - +class DataProcessor: @overload - def my_func(self, a: int, b: int, c: int): - return a * b * c + def process(self, data: str): + return data.upper() @overload - def my_func(self, *, val: int, other_val: int): - return val, other_val - - @overload - def my_func(self, val: List[int], other_val, /): - return [other_val * v for v in val] -``` -If you now investigate the class with `dir()` you will see (besides a lot of other methods) only one method for `my_func` `['_class_', '_delattr_', ..., '_weakref_', 'my_func']`. - -This also works with pure functions inside of a module - -_module_a.py_ -```python -from strongtyping_pyoverload import overload - - -@overload -def module_func(): - return 0 - - -@overload -def module_func(a: int, b: int): - return a * b - - -@overload -def module_func(a: str, b: str): - return a + b -``` -```pycon ->>> from module_a import module_func ->>> module_func() -0 ->>> module_func(2, 2) -4 ->>> module_func("foo", "bar") -"foobar" + def process(self, data: list): + return [item * 2 for item in data] ``` -_With this behavior we can get rid of the Union typehint in some cases. and allow more control over the intended function behaviour_ +### Key Benefits +- **Native Pydantic Integration**: Automatically validate and dispatch based on Pydantic models. +- **Deep Inheritance & Mixin Support**: Respects Method Resolution Order (MRO) for complex class hierarchies. +- **AI-Ready Metadata**: Sets `__signature__` and `__annotations__` for better IDE and AI assistant support. +- **High Performance**: Optimized lookup logic with caching for minimal overhead. +- **Modern Python**: Full support for `typing.Annotated`, keyword-only parameters, and Python 3.13+. -Detailed information can be found in the **User's guide** section \ No newline at end of file +Detailed information can be found in the **User's guide** section. \ No newline at end of file diff --git a/docs/install.md b/docs/install.md index d194389..12927f0 100644 --- a/docs/install.md +++ b/docs/install.md @@ -1,5 +1,11 @@ -Install this package simply with +Install this package via pip: ```shell pip install strongtyping-pyoverload +``` + +Or using uv: + +```shell +uv add strongtyping-pyoverload ``` \ No newline at end of file diff --git a/docs/module_level.md b/docs/module_level.md index d4a57b9..3797590 100644 --- a/docs/module_level.md +++ b/docs/module_level.md @@ -31,9 +31,9 @@ def module_func(a: str, b: str): ``` ### No function matches -when no function matches an `AttributError` will be raised +When no function matches an `AttributeError` will be raised. ```pycon >>> from module_a import module_func >>> module_func(21) -AttributeError: No function was found which matches your parameters `(21,)` +AttributeError: No function was found which matches your parameters `(21,), {}` ``` \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index c5279b9..7e1b9cf 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -8,6 +8,7 @@ nav: - 'Class level': 'class_level.md' - 'Module level': 'module_level.md' - 'Defaults': 'defaults.md' + - 'Pydantic Integration': 'pydantic.md' - Examples: - 'Django': 'django.md' - 'Multiple __init__': 'multi_init.md'