diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 574a1a9..8040418 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: ["3.10", "3.11", "3.12", "3.13", "3.14.0-beta.2"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14.0-beta.4"] os: ["ubuntu-latest", "windows-latest", "macos-latest"] @@ -26,7 +26,7 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v5 with: - version: "0.6.11" + version: "0.8.0" - name: Run ruff run: | uvx ruff check @@ -39,7 +39,7 @@ jobs: uv build - name: Installing run: | - uv pip install dist/paramclasses-0.3.7.dev0-py3-none-any.whl + uv pip install dist/paramclasses-0.4.0-py3-none-any.whl - name: Run pytest with coverage run: | uv run pytest --cov-report=xml diff --git a/README.md b/README.md index 7cd32a3..ae1ccdb 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ![OS Independant](https://img.shields.io/badge/OS_Independant-%E2%9C%93-blue) -[![python versions](https://img.shields.io/badge/python-3.10%20|%203.11%20|%203.12%20|%203.13%20|%203.14.0b2-blue)](https://devguide.python.org/versions/) +[![python versions](https://img.shields.io/badge/python-3.10%20|%203.11%20|%203.12%20|%203.13%20|%203.14.0b4-blue)](https://devguide.python.org/versions/) [![license MIT](https://img.shields.io/github/license/eliegoudout/paramclasses)](https://opensource.org/licenses/MIT) [![pypi](https://img.shields.io/pypi/v/paramclasses)](https://pypi.org/project/paramclasses/) [![pipeline status](https://github.com/eliegoudout/paramclasses/actions/workflows/ci.yml/badge.svg)](https://github.com/eliegoudout/paramclasses/actions) @@ -214,20 +214,26 @@ is triggered. For example, it can be used to `unfit` and estimator on specific m #### Instantiation logic with `__post_init__` -Similarly to [dataclasses](https://docs.python.org/3/library/dataclasses.html), a `__post_init__` method can be defined to complete instantiation after the initial setting of parameter values. It must have signature -```python -def __post_init__(self, *args: object, **kwargs: object) -> None -``` -and is called as follows by `__init__`. +Similarly to [dataclasses](https://docs.python.org/3/library/dataclasses.html), a `__post_init__` method can be defined to complete instantiation after the initial setting of parameter values. It must **always** return `None`. + +In general, it as called as follows by `__init__`. + ```python -# Close equivalent to actual implementation @protected -def __init__(self, args: list[object] = [], kwargs: dict[str, object] = {}, /, **param_values: object) -> None: - self.set_params(**param_values) - self.__post_init__(*args, **kwargs) - +def __init__( + self, + post_init_args: list[object] = [], + post_init_kwargs: dict[str, object] = {}, + /, + **param_values: object, +) -> None: + """Close equivalent to actual implementation""" + self.set_params(**param_values) + self.__post_init__(*args, **kwargs) ``` +**Note however** that if `__post_init__` does not accept positional (_resp._ keyword) arguments, then `post_init_args`(_resp._ `post_init_kwargs`) is removed from `__init__`'s signature. In any case, you can check the signature with `inspect.signature(your_paramclass)`. + Since parameter values are set before `__post_init__` is called, they are accessible when it executes. Note that even if a _paramclass_ does not define `__post_init__`, its bases might, in which case it is used. Additionally, both `@staticmethod` and `@classmethod` decorators are supported decorators for `__post_init__` declaration. In other cases, the `__signature__` property may fail. @@ -237,13 +243,16 @@ Additionally, both `@staticmethod` and `@classmethod` decorators are supported d #### Abstract methods The base `ParamClass` already inherits `ABC` functionalities, so `@abstractmethod` can be used. + ```python from abc import abstractmethod class A(ParamClass): @abstractmethod def next(self): ... + ``` + ```pycon >>> A() diff --git a/paramclasses/paramclasses.py b/paramclasses/paramclasses.py index 92b5a6e..a47fcc2 100644 --- a/paramclasses/paramclasses.py +++ b/paramclasses/paramclasses.py @@ -12,7 +12,7 @@ import sys from abc import ABCMeta -from collections.abc import Callable +from collections.abc import Callable, Mapping from dataclasses import dataclass from functools import wraps from inspect import Parameter, Signature, getattr_static, signature @@ -82,7 +82,7 @@ def _func_runs_once(*args: _P.args, **kwargs: _P.kwargs) -> _T: try: del flag except NameError: - msg = f"Function '{func.__name__}' should only be called once: {reason}" + msg = f"Function {func.__name__!r} should only be called once: {reason}" raise RuntimeError(msg) from None return func(*args, **kwargs) @@ -117,21 +117,21 @@ def _assert_unprotected(attr: str, protected: dict[str, type | None]) -> None: """Assert that `attr not in protected`.""" if attr in protected: owner = protected[attr] - msg = f"'{attr}' is protected by {_repr_owner(owner)}" + msg = f"{attr!r} is protected by {_repr_owner(owner)}" raise ProtectedError(msg) def _assert_valid_param(attr: str) -> None: """Assert that `attr` is authorized as parameter name.""" if attr.startswith("__") and attr.endswith("__"): - msg = f"Dunder parameters ('{attr}') are forbidden" + msg = f"Dunder parameters ({attr!r}) are forbidden" raise AttributeError(msg) def _dont_assign_missing(attr: str, val: object) -> None: """Forbid assigning the special 'missing value'.""" if val is MISSING: - msg = f"Assigning special missing value (attribute '{attr}') is forbidden" + msg = f"Assigning special missing value (attribute {attr!r}) is forbidden" raise ValueError(msg) @@ -141,7 +141,7 @@ def _repr_owner(*bases: type | None) -> str: def _mono_repr(cls: type | None) -> str: if cls is None: return "" - return f"'{cls.__name__}'" + return f"{cls.__name__!r}" return ", ".join(sorted(map(_mono_repr, bases))) @@ -158,7 +158,7 @@ def _get_namespace_annotations( if "__annotations__" in namespace: # from __future__ import annotations return cast("dict[str, object]", namespace["__annotations__"]) - from annotationlib import ( # type: ignore[import-not-found] + from annotationlib import ( # type: ignore[import-not-found] # noqa: PLC0415 (import top-level) Format, call_annotate_function, get_annotate_from_class_namespace, @@ -181,7 +181,7 @@ def _update_while_checking_consistency(orig: dict, update: MappingProxyType) -> orig[attr] = val continue if (previous := orig[attr]) is not val: - msg = f"'{attr}' protection conflict: {_repr_owner(val, previous)}" + msg = f"{attr!r} protection conflict: {_repr_owner(val, previous)}" raise ProtectedError(msg) @@ -235,7 +235,7 @@ def _check_valid_mro(tail: tuple[type, ...], bases: tuple[type, ...]) -> None: msg = ( "Invalid method resolution order (MRO) for bases " f"{', '.join(base.__name__ for base in bases)}: nonparamclass " - f"'{cls1.__name__}' would come before paramclass '{cls2.__name__}'" + f"{cls1.__name__!r} would come before paramclass {cls2.__name__!r}" ) raise TypeError(msg) @@ -244,12 +244,68 @@ def _check_valid_mro(tail: tuple[type, ...], bases: tuple[type, ...]) -> None: raise TypeError(msg) +def _post_init_accepts_args_kwargs(cls: type) -> tuple[bool, bool]: + """Whether :meth:`__post_init__` method accepts args and/or kwargs. + + Arguments + --------- + cls: ``type`` + The class to analyze. It must define :meth:`__post_init__`, + either a normal method, a ``classmethod`` or a ``staticmethod``. + + Returns + ------- + accepts_args: ``bool`` + Explicit. + accepts_kwargs: ``bool`` + Explicit. + + Raises + ------ + ValueError: + if ``cls`` has no attribute ``__post_init__``. + TypeError: + If :meth:`__post_init__` is not ``Callable``. + + """ + cls_attr = getattr_static(cls, "__post_init__", None) + __post_init__ = cast("Callable", getattr(cls, "__post_init__", None)) + if not callable(__post_init__): + msg = "'__post_init__' attribute must be callable" + raise TypeError(msg) + + raw_signature = signature(__post_init__) + parameters = list(raw_signature.parameters.values()) + if not isinstance(cls_attr, (classmethod, staticmethod)): + parameters.pop(0) + + kinds = {parameter.kind for parameter in parameters} + accepts_args = bool( + kinds + & { + Parameter.POSITIONAL_OR_KEYWORD, + Parameter.VAR_POSITIONAL, + Parameter.POSITIONAL_ONLY, + }, + ) + accepts_kwargs = bool( + kinds + & { + Parameter.POSITIONAL_OR_KEYWORD, + Parameter.KEYWORD_ONLY, + Parameter.VAR_KEYWORD, + }, + ) + + return accepts_args, accepts_kwargs + + @final class _MetaParamClass(ABCMeta, metaclass=_MetaFrozen): """Specifically implemented as `RawParamClass`'s metaclass. Implements class-level protection behaviour and parameters - identification, with annotations. Also subclasses `ABCMeta` to be + identification, with annotations. Also subclasses ``ABCMeta`` to be compatible with its functionality. """ @@ -258,9 +314,9 @@ def __new__(mcs, name: str, bases: tuple, namespace: dict[str, object]) -> type: It essentially does the following. 1. Retrieves parameters and protected attributes from bases. - 2. Inspects `namespace` and its annotations to infer new + 2. Inspects ``namespace`` and its annotations to infer new parameters and newly protected attributes. - 3. Stores those in `IMPL` class attribute. + 3. Stores those in ``IMPL`` class attribute. """ class Impl(NamedTuple): @@ -282,7 +338,7 @@ class Impl(NamedTuple): if attr in protected_special: continue if attr in protected and (owner := protected[attr]) is not base: - msg = f"'{attr}' protection conflict: {_repr_owner(base, owner)}" + msg = f"{attr!r} protection conflict: {_repr_owner(base, owner)}" raise ProtectedError(msg) # # Namespace: handle slots, protect, store parameters @@ -292,7 +348,7 @@ class Impl(NamedTuple): protect_then_slot = set(protected).intersection(slots) if protect_then_slot: msg = "Cannot slot the following protected attributes: " + ", ".join( - f"'{attr}' (from {_repr_owner(protected[attr])})" + f"{attr!r} (from {_repr_owner(protected[attr])})" for attr in sorted(protect_then_slot) # sort for pytest output ) raise ProtectedError(msg) @@ -350,7 +406,7 @@ def __getattribute__(cls, attr: str) -> object: return vars_base[attr] # Not found - msg = f"type object '{cls.__name__}' has no attribute '{attr}'" + msg = f"type object {cls.__name__!r} has no attribute {attr!r}" raise AttributeError(msg) def __setattr__(cls, attr: str, val_potentially_protected: object) -> None: @@ -360,7 +416,7 @@ def __setattr__(cls, attr: str, val_potentially_protected: object) -> None: _dont_assign_missing(attr, val) if was_protected: warn( - f"Cannot protect attribute '{attr}' after class creation. Ignored", + f"Cannot protect attribute {attr!r} after class creation. Ignored", stacklevel=2, ) return ABCMeta.__setattr__(cls, attr, val) @@ -372,43 +428,18 @@ def __delattr__(cls, attr: str) -> None: @property def __signature__(cls) -> Signature: - # Include `__post_init__` signature, if defined. Specifically, - # adds a positional-only `post_init_args` (resp. - # `post_init_kwargs`) if `__post_init__` accepts any positional - # (resp. keyword) argument. Handles @staticmethod and - # @classmethod. - post_init = [] - cls_attr = getattr_static(cls, "__post_init__", None) - if cls_attr is None: - post_init_parameters = [] + # Retrieve :meth:`__post_init__` signature part + if hasattr(cls, "__post_init__"): + accept_args, accepts_kwargs = _post_init_accepts_args_kwargs(cls) else: - __post_init__ = cast("Callable", cls.__post_init__) - if not callable(__post_init__): - msg = "'__post_init__' attribute must be callable" - raise TypeError(msg) - - post_init_raw_signature = signature(__post_init__) - post_init_parameters = list(post_init_raw_signature.parameters.values()) - if not isinstance(cls_attr, (classmethod, staticmethod)): - post_init_parameters.pop(0) + accept_args, accepts_kwargs = False, False - kinds = {parameter.kind for parameter in post_init_parameters} - # If `__post_init__` accepts positional arguments - if kinds & { - Parameter.POSITIONAL_OR_KEYWORD, - Parameter.POSITIONAL_ONLY, - Parameter.VAR_POSITIONAL, - }: + post_init = [] + if accept_args: post_init.append( Parameter("post_init_args", Parameter.POSITIONAL_ONLY, default=[]), ) - - # If `__post_init__` accepts keyword arguments - if kinds & { - Parameter.POSITIONAL_OR_KEYWORD, - Parameter.KEYWORD_ONLY, - Parameter.VAR_KEYWORD, - }: + if accepts_kwargs: post_init.append( Parameter("post_init_kwargs", Parameter.POSITIONAL_ONLY, default={}), ) @@ -457,32 +488,21 @@ def __str__(self) -> str: # ================================================================================== @protected # type: ignore[misc] # mypy is fooled - def __init__( + def __init__( # noqa: C901 # I prefer keeping the complexity here self, - args: list[object] | None = None, - kwargs: dict[str, object] | None = None, - /, + *args_kwargs: object, **param_values: object, ) -> None: - """Set parameters and call `__post_init__` if defined. - - Arguments: - args (list[object] | None): If not `None`, unpacked as - positional arguments for `__post_init__` -- if not - defined, raises `TypeError`. - kwargs (dict[str, object] | None): If not `None`, unpacked - as keyword arguments for `__post_init__` -- if not - defined, raises `TypeError`. - **param_values (object): Assigned parameter values at - instantiation. + """Set parameters and call ``__post_init__`` if defined. - """ - if not hasattr(self, "__post_init__") and ( - args is not None or kwargs is not None - ): - msg = "Unexpected positional arguments (no `__post_init__` is defined)" - raise TypeError(msg) + Arguments + --------- + args_kwargs: ``object`` + To do. + **param_values: ``object`` + Assigned parameter values at instantiation. + """ # Set params: KEEP UP-TO-DATE with `ParamClass.set_params`! wrong = set(param_values) - set(getattr(self, IMPL).annotations) if wrong: @@ -492,15 +512,49 @@ def __init__( for attr, val in param_values.items(): setattr(self, attr, val) - # Call `__post_init__` - if not hasattr(self, "__post_init__"): - return + # Handle case without :meth:`__post_init__` + cls = type(self) + given = len(args_kwargs) + if not hasattr(cls, "__post_init__"): + if not given: + return + msg = "Unexpected positional arguments (no '__post_init__' is defined)" + raise TypeError(msg) - if args is None: - args = [] - if kwargs is None: - kwargs = {} - self.__post_init__(*args, **kwargs) # type: ignore[operator] # github.com/eliegoudout/paramclasses/issues/34 + # Sanitize :meth:`__post_init__` arguments + accepts_args, accepts_kwargs = _post_init_accepts_args_kwargs(cls) + n_accepted = accepts_args + accepts_kwargs + args: list[object] + kwargs: dict[str, object] + if given == 0: + args, kwargs = [], {} + elif given > n_accepted: + msg = ( + "Invalid '__post_init__' arguments. Signature: " + f"{cls.__name__}{signature(cls)}" + ) + raise TypeError(msg) + elif accepts_args and accepts_kwargs: + args, kwargs = args_kwargs if given == n_accepted else (*args_kwargs, {}) # type: ignore[assignment] + elif accepts_args and not accepts_kwargs: + args, kwargs = *args_kwargs, {} # type: ignore[assignment] + if isinstance(args, Mapping): + msg = ( + "To avoid confusion, passing 'post_init_args' as a mapping is not " + "supported. Use 'iter(your_mapping)' instead" + ) + raise TypeError(msg) + elif not accepts_args and accepts_kwargs: + args, kwargs = [], *args_kwargs # type: ignore[assignment] + else: # pragma: no cover + msg = "Unexpected error while sanitizing '__post_init__' arguments" + raise RuntimeError(msg) + + # Call :meth:`__post_init__` + out = self.__post_init__(*args, **kwargs) # type: ignore[operator] # github.com/eliegoudout/paramclasses/issues/34 + if out is not None: + msg = f"'__post_init__' should return 'None' (got {out!r})" + raise TypeError(msg) @protected def __getattribute__(self, attr: str) -> object: # type: ignore[override] # mypy is fooled @@ -532,7 +586,7 @@ def __getattribute__(self, attr: str) -> object: # type: ignore[override] # my return vars(base)[attr] # Not found - msg = f"'{cls.__name__}' object has no attribute '{attr}'" + msg = f"{cls.__name__!r} object has no attribute {attr!r}" raise AttributeError(msg) @protected @@ -548,7 +602,7 @@ def __setattr__(self, attr: str, val_potentially_protected: object) -> None: # _dont_assign_missing(attr, val) if was_protected: warn( - f"Cannot protect attribute '{attr}' on instance assignment. Ignored", + f"Cannot protect attribute {attr!r} on instance assignment. Ignored", stacklevel=2, ) diff --git a/pyproject.toml b/pyproject.toml index b28cb3e..164efa6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "paramclasses" -version = "0.3.7dev0" +version = "0.4.0" description = "Parameter-holding classes with robust subclassing protection" readme = "README.md" requires-python = ">=3.10, <3.15" @@ -29,7 +29,7 @@ Issues = "https://github.com/eliegoudout/paramclasses/issues" [dependency-groups] dev = [ - "mypy>=1.14.0", + "mypy>=1.17.0", "pytest>=8.3.4", "pytest-cov>=6.0.0", ] diff --git a/test/paramclasses/conftest.py b/test/paramclasses/conftest.py index ad9c5bb..00fd919 100644 --- a/test/paramclasses/conftest.py +++ b/test/paramclasses/conftest.py @@ -6,12 +6,12 @@ import re from collections.abc import Callable, Generator, Iterable from itertools import chain, compress, pairwise, product, repeat -from types import MappingProxyType -from typing import Literal, NamedTuple +from types import FunctionType, MappingProxyType +from typing import Literal, NamedTuple, cast import pytest -from paramclasses import ParamClass, ProtectedError, protected +from paramclasses import ParamClass, ProtectedError, isparamclass, protected @pytest.fixture(scope="session") @@ -117,11 +117,11 @@ def _assert_same_behaviour( for i, collected_op in enumerate(zip(*collected, strict=False)): names, exception_flags, blueprints = zip(*collected_op, strict=False) ctxt = lambda j: "\n".join([ # noqa: E731 - f"attr: '{attr}'", - f"classes: '{names[j]}', '{names[j + 1]}'", # noqa: B023 - f"blueprints: '{blueprints[j]}', '{blueprints[j + 1]}'", # noqa: B023 + f"attr: {attr!r}", + f"classes: {names[j]!r}, {names[j + 1]!r}", # noqa: B023 + f"blueprints: {blueprints[j]!r}, {blueprints[j + 1]!r}", # noqa: B023 ]) - ops_str = f"'{' > '.join(ops[: i + 1])}'" + ops_str = f"{' > '.join(ops[: i + 1])!r}" # All exceptions or all outputs are_exceptions = _assert_consistency( @@ -206,7 +206,7 @@ def attributes_kinds(*filters: str) -> Generator[tuple[str, _AttributeKind], Non filtered.has_set.discard(discard) filtered.has_delete.discard(discard) else: - msg = f"Invalid filter '{filter_}'. Consider adding it if necessary" + msg = f"Invalid filter {filter_!r}. Consider adding it if necessary" raise ValueError(msg) # General unfiltered constraints (slots, missing, others) @@ -442,3 +442,101 @@ def fn(attr_or_kind) -> str: return attr_or_kind if isinstance(attr_or_kind, str) else "" return pytest.mark.parametrize(("attr", "kind"), attributes_kinds(*filters), ids=fn) + + +def parametrize_bool(argnames: str) -> pytest.MarkDecorator: + """Parametrize each argument to be ``True`` or ``False``. + + Arguments + --------- + argnames: ``str`` + See :func:`pytest.mark.parametrize`. + + """ + n_args = len(argnames.split(",")) + return pytest.mark.parametrize(argnames, product([True, False], repeat=n_args)) + + +PostInitType = FunctionType | staticmethod | classmethod + + +def make_with_post_init( # noqa: PLR0913 + *, + pos_only: bool, + pos_or_kw: bool, + var_pos: bool, + kw_only: bool, + var_kw: bool, + kind: Literal["normal", "static", "class"], +) -> type: + """Create a factory paramclass with `__post_init__`. + + Warning + ------- + Uses ``exec`` with controlled ``globals`` and ``locals``. + + Example + ------- + With every type of arguments and ``kind="class"``, the code is + :: + + class ParamWithPostInit(ParamClass): + @classmethod + def __post_init__(cls, pos_only, /, pos_or_kw, *var_pos, kw_only, **var_kw): + assert pos_only == "pos_only" + assert pos_or_kw == "pos_or_kw" + assert var_pos == ("var_pos",) + assert kw_only == "kw_only" + assert var_kw == {"var_kw": "var_kw"} + + """ + # Decorator + argnames: list[str] = [] + if kind == "normal": + decorator = "" + argnames.append("self") + elif kind == "static": + decorator = "@staticmethod" + elif kind == "class": + decorator = "@classmethod" + argnames.append("cls") + else: # pragma: no cover + msg = f"Invalid kind {kind!r}" + raise ValueError(msg) + + # Signature and body + lines = [] + if pos_only: + argnames.extend(["pos_only", "/"]) + lines.append('assert pos_only == "pos_only"') + if pos_or_kw: + argnames.extend(["pos_or_kw"]) + lines.append('assert pos_or_kw == "pos_or_kw"') + if var_pos: + argnames.extend(["*var_pos"]) + lines.append('assert var_pos == ("var_pos",)') + if kw_only: + argnames.extend(["kw_only"] if var_pos else ["*", "kw_only"]) + lines.append('assert kw_only == "kw_only"') + if var_kw: + argnames.extend(["**var_kw"]) + lines.append('assert var_kw == {"var_kw": "var_kw"}') + + sig = ", ".join(argnames) + body = "\n".join(f" {line}" for line in lines) or " return" + + # Code + code = ( + "class ParamWithPostInit(ParamClass):\n" + f" {decorator}\n" + f" def __post_init__({sig}):\n" + f"{body}\n" + ) + + # Create + container: dict[str, object] = {} + exec(code, {"ParamClass": ParamClass}, container) # noqa: S102 # very controlled use + cls = cast("type", container.pop("ParamWithPostInit")) + assert isparamclass(cls) + + return cls diff --git a/test/paramclasses/test_breaking_protection.py b/test/paramclasses/test_breaking_protection.py index 8443a56..c8c417c 100644 --- a/test/paramclasses/test_breaking_protection.py +++ b/test/paramclasses/test_breaking_protection.py @@ -1,5 +1,7 @@ """Easy ways of breaking the potection.""" +import re + import pytest from paramclasses import IMPL, ParamClass, ProtectedError, protected @@ -52,7 +54,7 @@ def test_modify_mappingproxy(monkeypatch): """ m = monkeypatch - class Exploit: + class Exploit: # noqa: PLW1641 (no __hash__) def __eq__(self, other: dict) -> None: # type:ignore[override] m.delitem(other, "params") @@ -96,12 +98,12 @@ class A(ParamClass): # Protection works assert A.x is null - regex = r"^'x' is protected by 'A'$" - with pytest.raises(ProtectedError, match=regex): + msg = "'x' is protected by 'A'" + with pytest.raises(ProtectedError, match=f"^{re.escape(msg)}$"): del A.x # Delete `A.x` despite protection type.__delattr__(A, "x") - regex = r"^type object 'A' has no attribute 'x'$" - with pytest.raises(AttributeError, match=regex): + msg = "type object 'A' has no attribute 'x'" + with pytest.raises(AttributeError, match=f"^{re.escape(msg)}$"): A.x # noqa: B018 (not "useless") diff --git a/test/paramclasses/test_conftest.py b/test/paramclasses/test_conftest.py index 8d7b82b..add23ea 100644 --- a/test/paramclasses/test_conftest.py +++ b/test/paramclasses/test_conftest.py @@ -1,5 +1,7 @@ """Test some nontrivial fixtures.""" +import re + import pytest from .conftest import attributes_kinds, kinds @@ -73,15 +75,15 @@ def test_attributes_kinds_num_results(): def test_attributes_kinds_raises_when_empty(): """Raises `AttributeError` on zero match.""" filters = ("parameter", "nonparameter") - regex = r"^No factory attribute matches \('parameter', 'nonparameter'\)$" - with pytest.raises(ValueError, match=regex): + msg = f"No factory attribute matches {filters}" + with pytest.raises(ValueError, match=f"^{re.escape(msg)}$"): next(attributes_kinds(*filters)) def test_attributes_kinds_raises_unknown_filter(): """Raises `AttributeError` on zero match.""" - regex = r"^Invalid filter 'unknown'. Consider adding it if necessary$" - with pytest.raises(ValueError, match=regex): + msg = "Invalid filter 'unknown'. Consider adding it if necessary" + with pytest.raises(ValueError, match=f"^{re.escape(msg)}$"): next(attributes_kinds("unknown")) diff --git a/test/paramclasses/test_getsetdel_behaviour.py b/test/paramclasses/test_getsetdel_behaviour.py index 2168e7b..2b810fc 100644 --- a/test/paramclasses/test_getsetdel_behaviour.py +++ b/test/paramclasses/test_getsetdel_behaviour.py @@ -33,6 +33,7 @@ Only simple inheritance is tested here. """ +import re import sys import pytest @@ -53,9 +54,9 @@ def test_behaviour_set_del_protected_class_and_instances( "Param, param, param_fill, ParamChild, paramchild, paramchild_fill", kind, ) - regex = f"^'{attr}' is protected by '{objs[0].__name__}'" + msg = f"{attr!r} is protected by {objs[0].__name__!r}" for obj in objs: - assert_set_del_is_protected(obj, attr, regex) + assert_set_del_is_protected(obj, attr, f"^{re.escape(msg)}$") # ====================================================================================== @@ -236,14 +237,14 @@ def test_behaviour_get_parameter_missing(attr, kind, make, null): # Class for cls in (Param, ParamChild): - regex = f"^type object '{cls.__name__}' has no attribute '{attr}'$" - with pytest.raises(AttributeError, match=regex): + msg = f"type object {cls.__name__!r} has no attribute {attr!r}" + with pytest.raises(AttributeError, match=f"^{re.escape(msg)}$"): getattr(cls, attr) # Empty instance for obj in (param, paramchild): - regex = f"^'{type(obj).__name__}' object has no attribute '{attr}'$" - with pytest.raises(AttributeError, match=regex): + msg = f"{type(obj).__name__!r} object has no attribute {attr!r}" + with pytest.raises(AttributeError, match=f"^{re.escape(msg)}$"): getattr(obj, attr) # Filled instance @@ -277,9 +278,9 @@ def test_delete_behaviour_unprotected_parameter_class_level(attr, kind, make): old = sys.version_info < (3, 11) prefix = "" if old else f"type object '{cls.__name__}' has no attribute '" suffix = "" if old else "'" - regex = f"^{prefix}{attr}{suffix}$" + msg = f"{prefix}{attr}{suffix}" - with pytest.raises(AttributeError, match=regex): + with pytest.raises(AttributeError, match=f"^{re.escape(msg)}$"): delattr(cls, attr) diff --git a/test/paramclasses/test_paramclasses.py b/test/paramclasses/test_paramclasses.py index c115665..140c8ba 100644 --- a/test/paramclasses/test_paramclasses.py +++ b/test/paramclasses/test_paramclasses.py @@ -1,5 +1,7 @@ """Miscellaneous tests not directly related to protection.""" +import re + import pytest from paramclasses import MISSING, ParamClass, RawParamClass, isparamclass @@ -52,8 +54,8 @@ def test_missing_params_property(make): def test_cannot_define_double_dunder_parameter(): """Dunder parameters are forbidden.""" - regex = r"^Dunder parameters \('__'\) are forbidden$" - with pytest.raises(AttributeError, match=regex): + msg = "Dunder parameters ('__') are forbidden" + with pytest.raises(AttributeError, match=f"^{re.escape(msg)}$"): class A(ParamClass): __: ... # type:ignore[annotation-unchecked] @@ -61,13 +63,13 @@ class A(ParamClass): def test_cannot_assign_special_missing_value_at_class_creation(): """Missing value can never be assigned.""" - regex = r"^Assigning special missing value \(attribute 'x'\) is forbidden$" - with pytest.raises(ValueError, match=regex): + msg = "Assigning special missing value (attribute 'x') is forbidden" + with pytest.raises(ValueError, match=f"^{re.escape(msg)}$"): class A(ParamClass): x = MISSING - with pytest.raises(ValueError, match=regex): + with pytest.raises(ValueError, match=f"^{re.escape(msg)}$"): class B(ParamClass): x: ... = MISSING # type:ignore[annotation-unchecked] @@ -76,25 +78,13 @@ class B(ParamClass): @parametrize_attr_kind("unprotected") def test_cannot_assign_special_missing_value_after_class_creation(attr, kind, make): """Missing value can never be assigned.""" - regex = rf"^Assigning special missing value \(attribute '{attr}'\) is forbidden$" + msg = f"Assigning special missing value (attribute {attr!r}) is forbidden" for obj in make("param, Param", kind): - with pytest.raises(ValueError, match=regex): + with pytest.raises(ValueError, match=f"^{re.escape(msg)}$"): setattr(obj, attr, MISSING) -@parametrize_attr_kind("unprotected", "parameter") -def test_init_and_set_params_work(attr, kind, make, null): - """For parameters, `set_params` works fine.""" - Param, param_set_params = make("Param, param", kind) - kw = {attr: null} - param_init = Param(**kw) - param_set_params.set_params(**kw) - - assert getattr(param_init, attr) is null - assert getattr(param_set_params, attr) is null - - @parametrize_attr_kind() def test_params_property(attr, kind, make, null): """Test `params` property, before and afer assignment.""" @@ -115,21 +105,6 @@ def test_params_property(attr, kind, make, null): assert param.params == expected_after -@parametrize_attr_kind("nonparameter") -def test_init_and_set_params_raise_on_nonparameter(attr, kind, make, null): - """Using `set_params` on nonparameters fails.""" - Param, param_set_params = make("Param, param", kind) - kw = {attr: null} - - # Check error and match regex - regex = rf"^Invalid parameters: {{'{attr}'}}. Operation cancelled$" - with pytest.raises(AttributeError, match=regex): - Param(**kw) - - with pytest.raises(AttributeError, match=regex): - param_set_params.set_params(**kw) - - def test_isparamclass_works_even_against_virtual(make): """Test `isparamclass`, also against virtual subclassing.""" Param, Vanilla = make("Param, Vanilla") @@ -164,33 +139,28 @@ class A(ParamClass): assert str(a) == "A()" -def test_post_init(): - """Test trivial `__post_init__` use.""" +def test_post_init_must_be_callable(): + """Test `__signature__` error when `__post_init__` not callable.""" class A(ParamClass): - def __post_init__(self, arg1, arg2) -> None: - self.arg1 = arg1 - self.arg2 = arg2 + __post_init__ = 0 - arg1, arg2 = object(), object() + msg = "'__post_init__' attribute must be callable" + with pytest.raises(TypeError, match=f"^{re.escape(msg)}$"): + A.__signature__ # noqa: B018 (not useless) - for a in ( - A([arg1, arg2]), - A([arg1], {"arg2": arg2}), - A([], {"arg1": arg1, "arg2": arg2}), - A(None, {"arg1": arg1, "arg2": arg2}), - ): - assert a.arg1 is arg1 - assert a.arg2 is arg2 +def test_post_init_should_return_none(): + """Test `__signature__` error when `__post_init__` not callable.""" + null = object() -def test_unexpected_post_init_arguments(make): - """Check that provided arguments raise error when no post-init.""" - Param = make("Param") + class A(ParamClass): + def __post_init__(self) -> None: + return null - regex = r"^Unexpected positional arguments \(no `__post_init__` is defined\)$" - with pytest.raises(TypeError, match=regex): - Param(1) + msg = f"'__post_init__' should return 'None' (got {null!r})" + with pytest.raises(TypeError, match=f"^{re.escape(msg)}$"): + A() def test_invalid_mro(): @@ -200,31 +170,31 @@ class A(ParamClass): ... class B: ... - regex = ( - r"^Invalid method resolution order \(MRO\) for bases B, A: nonparamclass 'B'" - r" would come before paramclass 'A'$" + msg = ( + "Invalid method resolution order (MRO) for bases B, A: nonparamclass 'B'" + " would come before paramclass 'A'" ) - with pytest.raises(TypeError, match=regex): + with pytest.raises(TypeError, match=f"^{re.escape(msg)}$"): class C(B, A): ... def test_cannot_use_metaclass_alone(): """Forbid simple metaclass without inheritance.""" - regex = ( - r"^Function '_skip_mro_check' should only be called once: metaclass" + msg = ( + "Function '_skip_mro_check' should only be called once: metaclass" " '_MetaParamClass' should never be explicitly passed except when constructing" - r" 'RawParamClass'$" + " 'RawParamClass'" ) - with pytest.raises(RuntimeError, match=regex): + with pytest.raises(RuntimeError, match=f"^{re.escape(msg)}$"): class A(metaclass=type(ParamClass)): ... def test_metaclass_requires_inheriting_from_rawparamclass(): """Check that paramclasses must inherit from RawParamClass.""" - regex = r"^Paramclasses must always inherit from 'RawParamClass'$" - with pytest.raises(TypeError, match=regex): + msg = "Paramclasses must always inherit from 'RawParamClass'" + with pytest.raises(TypeError, match=f"^{re.escape(msg)}$"): class A(int, metaclass=type(ParamClass)): ... diff --git a/test/paramclasses/test_protection.py b/test/paramclasses/test_protection.py index 4aa51b6..8912427 100644 --- a/test/paramclasses/test_protection.py +++ b/test/paramclasses/test_protection.py @@ -1,5 +1,7 @@ """Tests directly related to @protected.""" +import re + import pytest from paramclasses import IMPL, ParamClass, ProtectedError, protected @@ -12,18 +14,18 @@ def test_mcs_is_frozen(assert_set_del_is_protected): """ mcs = type(ParamClass) attr = "random_attribute" - regex = r"^'_MetaParamClass' attributes are frozen$" - assert_set_del_is_protected(mcs, attr, regex) + msg = f"{mcs.__name__!r} attributes are frozen" + assert_set_del_is_protected(mcs, attr, f"^{re.escape(msg)}$") def test_cannot_subclass_mcs(): """Cannot subclass `_MetaParamClass' (without altering its meta).""" mcs = type(ParamClass) - regex = ( - r"^Function '__new__' should only be called once: '_MetaFrozen' should only" - r" construct '_MetaParamClass'$" + msg = ( + f"Function '__new__' should only be called once: {type(mcs).__name__!r} should" + f" only construct {mcs.__name__!r}" ) - with pytest.raises(RuntimeError, match=regex): + with pytest.raises(RuntimeError, match=f"^{re.escape(msg)}$"): class Sub(mcs): ... @@ -42,8 +44,8 @@ def method(self) -> None: ... def test_simple_protection_inheritance(): """Subclass cannot override protected.""" - regex = r"^'params' is protected by 'ParamClass'$" - with pytest.raises(ProtectedError, match=regex): + msg = "'params' is protected by 'ParamClass'" + with pytest.raises(ProtectedError, match=f"^{re.escape(msg)}$"): class A(ParamClass): params = 0 @@ -66,8 +68,8 @@ class C: class D(A, BC): ... # Incoherent protection order - regex = rf"^'x' protection conflict: 'A', '{BC.__name__}'$" - with pytest.raises(ProtectedError, match=regex): + msg = f"'x' protection conflict: 'A', {BC.__name__!r}" + with pytest.raises(ProtectedError, match=f"^{re.escape(msg)}$"): class D(BC, A): ... @@ -81,8 +83,8 @@ class A(ParamClass): class B(ParamClass): x = protected(0) - regex = "^'x' protection conflict: 'A', 'B'$" - with pytest.raises(ProtectedError, match=regex): + msg = "'x' protection conflict: 'A', 'B'" + with pytest.raises(ProtectedError, match=f"^{re.escape(msg)}$"): class C(A, B): ... @@ -105,17 +107,17 @@ class D(C, B): ... def test_cannot_slot_previously_protected(): """Cannot slot previously protected attribute.""" - regex = ( - rf"^Cannot slot the following protected attributes: '{IMPL}' \(from " - r"\)$" + msg = ( + f"Cannot slot the following protected attributes: {IMPL!r} (from " + ")" ) - with pytest.raises(ProtectedError, match=regex): + with pytest.raises(ProtectedError, match=f"^{re.escape(msg)}$"): class A(ParamClass): __slots__ = (IMPL,) # Not necessarily given as iterable of strings - with pytest.raises(ProtectedError, match=regex): + with pytest.raises(ProtectedError, match=f"^{re.escape(msg)}$"): class A(ParamClass): __slots__ = IMPL @@ -127,15 +129,15 @@ def test_post_creation_protection(): class A(ParamClass): ... # Class-level - regex = "^Cannot protect attribute 'x' after class creation. Ignored$" - with pytest.warns(UserWarning, match=regex): + msg = "Cannot protect attribute 'x' after class creation. Ignored" + with pytest.warns(UserWarning, match=f"^{re.escape(msg)}$"): A.x = protected(0) assert A.x == 0 # Instance-level a = A() - regex = "^Cannot protect attribute 'x' on instance assignment. Ignored$" - with pytest.warns(UserWarning, match=regex): + msg = "Cannot protect attribute 'x' on instance assignment. Ignored" + with pytest.warns(UserWarning, match=f"^{re.escape(msg)}$"): a.x = protected(1) assert a.x == 1 @@ -147,8 +149,8 @@ def test_dict_is_protected(): def test_cannot_turn_previously_protected_into_param(): """Cannot make non-param protected into parameter.""" - regex = "^'params' is protected by 'ParamClass'$" - with pytest.raises(ProtectedError, match=regex): + msg = "'params' is protected by 'ParamClass'" + with pytest.raises(ProtectedError, match=f"^{re.escape(msg)}$"): class A(ParamClass): params: dict[str, object] # type:ignore[annotation-unchecked] diff --git a/test/paramclasses/test_signature.py b/test/paramclasses/test_signature.py index b00b6fe..9608f19 100644 --- a/test/paramclasses/test_signature.py +++ b/test/paramclasses/test_signature.py @@ -1,30 +1,212 @@ -"""Test the `__signature__` property. +"""Test the runtime signature and `__signature__` property. When dropping 3.12, replace `repr(Signature)` with `Signature.format()`. """ +import re from inspect import signature import pytest from paramclasses import ParamClass +from .conftest import make_with_post_init, parametrize_attr_kind, parametrize_bool + + +@parametrize_attr_kind("unprotected", "parameter") +def test_signature_call_and_set_params_on_parameter(attr, kind, make, null): + """For parameters, `set_params` works fine.""" + Param, param_set_params = make("Param, param", kind) + kw = {attr: null} + param_init = Param(**kw) + param_set_params.set_params(**kw) + + assert getattr(param_init, attr) is null + assert getattr(param_set_params, attr) is null + + +@parametrize_attr_kind("nonparameter") +def test_signature_call_and_set_params_on_nonparameter(attr, kind, make, null): + """Using `set_params` on nonparameters fails.""" + Param, param_set_params = make("Param, param", kind) + kw = {attr: null} + + msg = f"Invalid parameters: { {attr} }. Operation cancelled" + with pytest.raises(AttributeError, match=f"^{re.escape(msg)}$"): + Param(**kw) + + with pytest.raises(AttributeError, match=f"^{re.escape(msg)}$"): + param_set_params.set_params(**kw) + + +def test_signature_call_no_post_init(make): + """Check that provided arguments raise error when no post-init.""" + Param = make("Param") + + msg = "Unexpected positional arguments (no '__post_init__' is defined)" + with pytest.raises(TypeError, match=f"^{re.escape(msg)}$"): + Param(None) + + +@pytest.mark.parametrize("kind", ["normal", "static", "class"]) +@parametrize_bool("pos_only, pos_or_kw, var_pos, kw_only, var_kw, prefer_kw") +def test_signature_call_valid( + pos_only, + pos_or_kw, + var_pos, + kw_only, + var_kw, + prefer_kw, + kind, +): + """Test runtime call consistent with `__signature__` property.""" + ParamWithPostInit = make_with_post_init( + pos_only=pos_only, + pos_or_kw=pos_or_kw, + var_pos=var_pos, + kw_only=kw_only, + var_kw=var_kw, + kind=kind, + ) -def test_signature_no_post_init(): - """Test `__signature__` property.""" + # Prepare valid arguments + args = [] + kwargs = {} + if pos_only: + args.append("pos_only") + if pos_or_kw: + if prefer_kw and not var_pos: + kwargs["pos_or_kw"] = "pos_or_kw" + else: + args.append("pos_or_kw") + if var_pos: + args.append("var_pos") + if kw_only: + kwargs["kw_only"] = "kw_only" + if var_kw: + kwargs["var_kw"] = "var_kw" + + # Test valid call + accepts_args = pos_only or pos_or_kw or var_pos + accepts_kwargs = pos_or_kw or kw_only or var_kw + + args_kwargs = [] + if accepts_args: + args_kwargs.append(args) + if accepts_kwargs: + args_kwargs.append(kwargs) + + ParamWithPostInit(*args_kwargs) # Works + + +@pytest.mark.parametrize("kind", ["normal", "static", "class"]) +@parametrize_bool("pos_only, pos_or_kw, var_pos, kw_only, var_kw") +def test_signature_call_too_many_arguments( + pos_only, + pos_or_kw, + var_pos, + kw_only, + var_kw, + kind, +): + """Test runtime call consistent with `__signature__` property.""" + ParamWithPostInit = make_with_post_init( + pos_only=pos_only, + pos_or_kw=pos_or_kw, + var_pos=var_pos, + kw_only=kw_only, + var_kw=var_kw, + kind=kind, + ) - class A(ParamClass): - x: float # type:ignore[annotation-unchecked] - y: int = 0 # type:ignore[annotation-unchecked] - z: str = 0 # type:ignore[annotation-unchecked] - t = 0 + accepts_args = pos_only or pos_or_kw or var_pos + accepts_kwargs = pos_or_kw or kw_only or var_kw + n_expexcted = accepts_args + accepts_kwargs - expected = "(*, x: float = ?, y: int = 0, z: str = 0)" - assert repr(signature(A)) == f"" + msg = ( + f"Invalid '__post_init__' arguments. Signature: {ParamWithPostInit.__name__}" + f"{signature(ParamWithPostInit)}" + ) + with pytest.raises(TypeError, match=f"^{re.escape(msg)}$"): + ParamWithPostInit(*range(n_expexcted + 1)) + + +@pytest.mark.parametrize("kind", ["normal", "static", "class"]) +@parametrize_bool("pos_only, pos_or_kw, var_pos, kw_only, var_kw") +def test_signature_call_non_unpackable( + pos_only, + pos_or_kw, + var_pos, + kw_only, + var_kw, + kind, +): + """Test runtime call consistent with `__signature__` property.""" + ParamWithPostInit = make_with_post_init( + pos_only=pos_only, + pos_or_kw=pos_or_kw, + var_pos=var_pos, + kw_only=kw_only, + var_kw=var_kw, + kind=kind, + ) + accepts_args = pos_only or pos_or_kw or var_pos + accepts_kwargs = pos_or_kw or kw_only or var_kw + + # Non-unpackable ``args`` + if accepts_args: + msg = ( + f"{ParamWithPostInit.__name__}.__post_init__() argument after * must be an" + " iterable, not NoneType" + ) + args_kwargs = [None, {}] if accepts_kwargs else [None] + with pytest.raises(TypeError, match=f"^{re.escape(msg)}$"): + ParamWithPostInit(*args_kwargs) + + # Non-unpackable ``kwargs`` + if accepts_kwargs: + msg = ( + f"{ParamWithPostInit.__name__}.__post_init__() argument after ** must be a" + " mapping, not NoneType" + ) + args_kwargs = [[], None] if accepts_args else [None] + with pytest.raises(TypeError, match=f"^{re.escape(msg)}$"): + ParamWithPostInit(*args_kwargs) + + +@pytest.mark.parametrize("kind", ["normal", "static", "class"]) +@parametrize_bool("pos_only, var_pos") +def test_signature_call_args_as_mapping( + pos_only, + var_pos, + kind, +): + """Test runtime call consistent with `__signature__` property.""" + # Filter accept args + if not pos_only and not var_pos: + return + + ParamWithPostInit = make_with_post_init( + pos_only=pos_only, + pos_or_kw=False, + var_pos=var_pos, + kw_only=False, + var_kw=False, + kind=kind, + ) -def test_signature_with_post_init(): - """Test `__signature__` property.""" + msg = ( + "To avoid confusion, passing 'post_init_args' as a mapping is not supported. " + "Use 'iter(your_mapping)' instead" + ) + args = {"pos_arg": "from mapping key"} + with pytest.raises(TypeError, match=f"^{re.escape(msg)}$"): + ParamWithPostInit(args) + + +def test_signature_property_explicit(): + """Test `__signature__` property on explicit example with params.""" class A(ParamClass): x: float # type:ignore[annotation-unchecked] @@ -42,112 +224,52 @@ def __post_init__(self, a, b, c) -> None: assert repr(signature(A)) == f"" -def test_signature_post_init_pos_and_kw(): - """Test `__signature__` property with `__post_init__`. - - Test classical method, staticmethod and classmethod. - """ - - class A(ParamClass): - def __post_init__(self, a, /, b, *, c) -> None: - """Test with standard method.""" - - class B(ParamClass): - @classmethod - def __post_init__(cls, a, /, b, *, c) -> None: - """Test with classmethod.""" - - class C(ParamClass): - @staticmethod - def __post_init__(a, /, b, *, c) -> None: - """Test with staticmethod.""" - - expected = "(post_init_args=[], post_init_kwargs={}, /)" - for cls in [A, B, C]: - assert repr(signature(cls)) == f"" - - -def test_signature_post_init_pos_only(): - """Test `__signature__` property with `__post_init__`. - - Test classical method, staticmethod and classmethod. - """ - - class A(ParamClass): - def __post_init__(self, a, /) -> None: - """Test with standard method.""" - - class B(ParamClass): - @classmethod - def __post_init__(cls, a, /) -> None: - """Test with classmethod.""" - - class C(ParamClass): - @staticmethod - def __post_init__(a, /) -> None: - """Test with staticmethod.""" - - expected = "(post_init_args=[], /)" - for cls in [A, B, C]: - assert repr(signature(cls)) == f"" - - -def test_signature_post_init_kw_only(): - """Test `__signature__` property with `__post_init__`. - - Test classical method, staticmethod and classmethod. - """ +def test_signature_property_no_post_init(): + """Test `__signature__` property.""" class A(ParamClass): - def __post_init__(self, *, c) -> None: - """Test with standard method.""" - - class B(ParamClass): - @classmethod - def __post_init__(cls, *, c) -> None: - """Test with classmethod.""" - - class C(ParamClass): - @staticmethod - def __post_init__(*, c) -> None: - """Test with staticmethod.""" + x: float # type:ignore[annotation-unchecked] + y: int = 0 # type:ignore[annotation-unchecked] + z: str = 0 # type:ignore[annotation-unchecked] + t = 0 - expected = "(post_init_kwargs={}, /)" - for cls in [A, B, C]: - assert repr(signature(cls)) == f"" + expected = "(*, x: float = ?, y: int = 0, z: str = 0)" + assert repr(signature(A)) == f"" -def test_signature_post_init_argless(): - """Test `__signature__` property with `__post_init__`. +@pytest.mark.parametrize("kind", ["normal", "static", "class"]) +@parametrize_bool("pos_only, pos_or_kw, var_pos, kw_only, var_kw") +def test_signature_property_post_init( + pos_only, + pos_or_kw, + var_pos, + kw_only, + var_kw, + kind, +): + """Test `__signature__` property with all possible `__post_init__`. - Test classical method, staticmethod and classmethod. + Test normal method, staticmethod and classmethod., no parameters. """ + ParamWithPostInit = make_with_post_init( + pos_only=pos_only, + pos_or_kw=pos_or_kw, + var_pos=var_pos, + kw_only=kw_only, + var_kw=var_kw, + kind=kind, + ) - class A(ParamClass): - def __post_init__(self) -> None: - """Test with standard method.""" - - class B(ParamClass): - @classmethod - def __post_init__(cls) -> None: - """Test with classmethod.""" - - class C(ParamClass): - @staticmethod - def __post_init__() -> None: - """Test with staticmethod.""" - - expected = "()" - for cls in [A, B, C]: - assert repr(signature(cls)) == f"" - - -def test_post_init_must_be_callable(): - """Test `__signature__` error when `__post_init__` not callable.""" - - class A(ParamClass): - __post_init__ = 0 - - regex = r"^'__post_init__' attribute must be callable$" - with pytest.raises(TypeError, match=regex): - A.__signature__ # noqa: B018 (not useless) + # Compute expected signature + accepts_args = pos_only or pos_or_kw or var_pos + accepts_kwargs = pos_or_kw or kw_only or var_kw + argnames = [] + if accepts_args: + argnames.append("post_init_args=[]") + if accepts_kwargs: + argnames.append("post_init_kwargs={}") + if argnames: + argnames.append("/") + + expected = f"" + assert repr(signature(ParamWithPostInit)) == expected diff --git a/uv.lock b/uv.lock index bd91776..d04ca12 100644 --- a/uv.lock +++ b/uv.lock @@ -13,66 +13,66 @@ wheels = [ [[package]] name = "coverage" -version = "7.8.2" +version = "7.9.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/07/998afa4a0ecdf9b1981ae05415dad2d4e7716e1b1f00abbd91691ac09ac9/coverage-7.8.2.tar.gz", hash = "sha256:a886d531373a1f6ff9fad2a2ba4a045b68467b779ae729ee0b3b10ac20033b27", size = 812759, upload-time = "2025-05-23T11:39:57.856Z" } +sdist = { url = "https://files.pythonhosted.org/packages/04/b7/c0465ca253df10a9e8dae0692a4ae6e9726d245390aaef92360e1d6d3832/coverage-7.9.2.tar.gz", hash = "sha256:997024fa51e3290264ffd7492ec97d0690293ccd2b45a6cd7d82d945a4a80c8b", size = 813556, upload-time = "2025-07-03T10:54:15.101Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/26/6b/7dd06399a5c0b81007e3a6af0395cd60e6a30f959f8d407d3ee04642e896/coverage-7.8.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bd8ec21e1443fd7a447881332f7ce9d35b8fbd2849e761bb290b584535636b0a", size = 211573, upload-time = "2025-05-23T11:37:47.207Z" }, - { url = "https://files.pythonhosted.org/packages/f0/df/2b24090820a0bac1412955fb1a4dade6bc3b8dcef7b899c277ffaf16916d/coverage-7.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4c26c2396674816deaeae7ded0e2b42c26537280f8fe313335858ffff35019be", size = 212006, upload-time = "2025-05-23T11:37:50.289Z" }, - { url = "https://files.pythonhosted.org/packages/c5/c4/e4e3b998e116625562a872a342419652fa6ca73f464d9faf9f52f1aff427/coverage-7.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1aec326ed237e5880bfe69ad41616d333712c7937bcefc1343145e972938f9b3", size = 241128, upload-time = "2025-05-23T11:37:52.229Z" }, - { url = "https://files.pythonhosted.org/packages/b1/67/b28904afea3e87a895da850ba587439a61699bf4b73d04d0dfd99bbd33b4/coverage-7.8.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5e818796f71702d7a13e50c70de2a1924f729228580bcba1607cccf32eea46e6", size = 239026, upload-time = "2025-05-23T11:37:53.846Z" }, - { url = "https://files.pythonhosted.org/packages/8c/0f/47bf7c5630d81bc2cd52b9e13043685dbb7c79372a7f5857279cc442b37c/coverage-7.8.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:546e537d9e24efc765c9c891328f30f826e3e4808e31f5d0f87c4ba12bbd1622", size = 240172, upload-time = "2025-05-23T11:37:55.711Z" }, - { url = "https://files.pythonhosted.org/packages/ba/38/af3eb9d36d85abc881f5aaecf8209383dbe0fa4cac2d804c55d05c51cb04/coverage-7.8.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ab9b09a2349f58e73f8ebc06fac546dd623e23b063e5398343c5270072e3201c", size = 240086, upload-time = "2025-05-23T11:37:57.724Z" }, - { url = "https://files.pythonhosted.org/packages/9e/64/c40c27c2573adeba0fe16faf39a8aa57368a1f2148865d6bb24c67eadb41/coverage-7.8.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fd51355ab8a372d89fb0e6a31719e825cf8df8b6724bee942fb5b92c3f016ba3", size = 238792, upload-time = "2025-05-23T11:37:59.737Z" }, - { url = "https://files.pythonhosted.org/packages/8e/ab/b7c85146f15457671c1412afca7c25a5696d7625e7158002aa017e2d7e3c/coverage-7.8.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0774df1e093acb6c9e4d58bce7f86656aeed6c132a16e2337692c12786b32404", size = 239096, upload-time = "2025-05-23T11:38:01.693Z" }, - { url = "https://files.pythonhosted.org/packages/d3/50/9446dad1310905fb1dc284d60d4320a5b25d4e3e33f9ea08b8d36e244e23/coverage-7.8.2-cp310-cp310-win32.whl", hash = "sha256:00f2e2f2e37f47e5f54423aeefd6c32a7dbcedc033fcd3928a4f4948e8b96af7", size = 214144, upload-time = "2025-05-23T11:38:03.68Z" }, - { url = "https://files.pythonhosted.org/packages/23/ed/792e66ad7b8b0df757db8d47af0c23659cdb5a65ef7ace8b111cacdbee89/coverage-7.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:145b07bea229821d51811bf15eeab346c236d523838eda395ea969d120d13347", size = 215043, upload-time = "2025-05-23T11:38:05.217Z" }, - { url = "https://files.pythonhosted.org/packages/6a/4d/1ff618ee9f134d0de5cc1661582c21a65e06823f41caf801aadf18811a8e/coverage-7.8.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b99058eef42e6a8dcd135afb068b3d53aff3921ce699e127602efff9956457a9", size = 211692, upload-time = "2025-05-23T11:38:08.485Z" }, - { url = "https://files.pythonhosted.org/packages/96/fa/c3c1b476de96f2bc7a8ca01a9f1fcb51c01c6b60a9d2c3e66194b2bdb4af/coverage-7.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5feb7f2c3e6ea94d3b877def0270dff0947b8d8c04cfa34a17be0a4dc1836879", size = 212115, upload-time = "2025-05-23T11:38:09.989Z" }, - { url = "https://files.pythonhosted.org/packages/f7/c2/5414c5a1b286c0f3881ae5adb49be1854ac5b7e99011501f81c8c1453065/coverage-7.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:670a13249b957bb9050fab12d86acef7bf8f6a879b9d1a883799276e0d4c674a", size = 244740, upload-time = "2025-05-23T11:38:11.947Z" }, - { url = "https://files.pythonhosted.org/packages/cd/46/1ae01912dfb06a642ef3dd9cf38ed4996fda8fe884dab8952da616f81a2b/coverage-7.8.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0bdc8bf760459a4a4187b452213e04d039990211f98644c7292adf1e471162b5", size = 242429, upload-time = "2025-05-23T11:38:13.955Z" }, - { url = "https://files.pythonhosted.org/packages/06/58/38c676aec594bfe2a87c7683942e5a30224791d8df99bcc8439fde140377/coverage-7.8.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07a989c867986c2a75f158f03fdb413128aad29aca9d4dbce5fc755672d96f11", size = 244218, upload-time = "2025-05-23T11:38:15.631Z" }, - { url = "https://files.pythonhosted.org/packages/80/0c/95b1023e881ce45006d9abc250f76c6cdab7134a1c182d9713878dfefcb2/coverage-7.8.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2db10dedeb619a771ef0e2949ccba7b75e33905de959c2643a4607bef2f3fb3a", size = 243865, upload-time = "2025-05-23T11:38:17.622Z" }, - { url = "https://files.pythonhosted.org/packages/57/37/0ae95989285a39e0839c959fe854a3ae46c06610439350d1ab860bf020ac/coverage-7.8.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e6ea7dba4e92926b7b5f0990634b78ea02f208d04af520c73a7c876d5a8d36cb", size = 242038, upload-time = "2025-05-23T11:38:19.966Z" }, - { url = "https://files.pythonhosted.org/packages/4d/82/40e55f7c0eb5e97cc62cbd9d0746fd24e8caf57be5a408b87529416e0c70/coverage-7.8.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ef2f22795a7aca99fc3c84393a55a53dd18ab8c93fb431004e4d8f0774150f54", size = 242567, upload-time = "2025-05-23T11:38:21.912Z" }, - { url = "https://files.pythonhosted.org/packages/f9/35/66a51adc273433a253989f0d9cc7aa6bcdb4855382cf0858200afe578861/coverage-7.8.2-cp311-cp311-win32.whl", hash = "sha256:641988828bc18a6368fe72355df5f1703e44411adbe49bba5644b941ce6f2e3a", size = 214194, upload-time = "2025-05-23T11:38:23.571Z" }, - { url = "https://files.pythonhosted.org/packages/f6/8f/a543121f9f5f150eae092b08428cb4e6b6d2d134152c3357b77659d2a605/coverage-7.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:8ab4a51cb39dc1933ba627e0875046d150e88478dbe22ce145a68393e9652975", size = 215109, upload-time = "2025-05-23T11:38:25.137Z" }, - { url = "https://files.pythonhosted.org/packages/77/65/6cc84b68d4f35186463cd7ab1da1169e9abb59870c0f6a57ea6aba95f861/coverage-7.8.2-cp311-cp311-win_arm64.whl", hash = "sha256:8966a821e2083c74d88cca5b7dcccc0a3a888a596a04c0b9668a891de3a0cc53", size = 213521, upload-time = "2025-05-23T11:38:27.123Z" }, - { url = "https://files.pythonhosted.org/packages/8d/2a/1da1ada2e3044fcd4a3254fb3576e160b8fe5b36d705c8a31f793423f763/coverage-7.8.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e2f6fe3654468d061942591aef56686131335b7a8325684eda85dacdf311356c", size = 211876, upload-time = "2025-05-23T11:38:29.01Z" }, - { url = "https://files.pythonhosted.org/packages/70/e9/3d715ffd5b6b17a8be80cd14a8917a002530a99943cc1939ad5bb2aa74b9/coverage-7.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76090fab50610798cc05241bf83b603477c40ee87acd358b66196ab0ca44ffa1", size = 212130, upload-time = "2025-05-23T11:38:30.675Z" }, - { url = "https://files.pythonhosted.org/packages/a0/02/fdce62bb3c21649abfd91fbdcf041fb99be0d728ff00f3f9d54d97ed683e/coverage-7.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bd0a0a5054be160777a7920b731a0570284db5142abaaf81bcbb282b8d99279", size = 246176, upload-time = "2025-05-23T11:38:32.395Z" }, - { url = "https://files.pythonhosted.org/packages/a7/52/decbbed61e03b6ffe85cd0fea360a5e04a5a98a7423f292aae62423b8557/coverage-7.8.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da23ce9a3d356d0affe9c7036030b5c8f14556bd970c9b224f9c8205505e3b99", size = 243068, upload-time = "2025-05-23T11:38:33.989Z" }, - { url = "https://files.pythonhosted.org/packages/38/6c/d0e9c0cce18faef79a52778219a3c6ee8e336437da8eddd4ab3dbd8fadff/coverage-7.8.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9392773cffeb8d7e042a7b15b82a414011e9d2b5fdbbd3f7e6a6b17d5e21b20", size = 245328, upload-time = "2025-05-23T11:38:35.568Z" }, - { url = "https://files.pythonhosted.org/packages/f0/70/f703b553a2f6b6c70568c7e398ed0789d47f953d67fbba36a327714a7bca/coverage-7.8.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:876cbfd0b09ce09d81585d266c07a32657beb3eaec896f39484b631555be0fe2", size = 245099, upload-time = "2025-05-23T11:38:37.627Z" }, - { url = "https://files.pythonhosted.org/packages/ec/fb/4cbb370dedae78460c3aacbdad9d249e853f3bc4ce5ff0e02b1983d03044/coverage-7.8.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3da9b771c98977a13fbc3830f6caa85cae6c9c83911d24cb2d218e9394259c57", size = 243314, upload-time = "2025-05-23T11:38:39.238Z" }, - { url = "https://files.pythonhosted.org/packages/39/9f/1afbb2cb9c8699b8bc38afdce00a3b4644904e6a38c7bf9005386c9305ec/coverage-7.8.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9a990f6510b3292686713bfef26d0049cd63b9c7bb17e0864f133cbfd2e6167f", size = 244489, upload-time = "2025-05-23T11:38:40.845Z" }, - { url = "https://files.pythonhosted.org/packages/79/fa/f3e7ec7d220bff14aba7a4786ae47043770cbdceeea1803083059c878837/coverage-7.8.2-cp312-cp312-win32.whl", hash = "sha256:bf8111cddd0f2b54d34e96613e7fbdd59a673f0cf5574b61134ae75b6f5a33b8", size = 214366, upload-time = "2025-05-23T11:38:43.551Z" }, - { url = "https://files.pythonhosted.org/packages/54/aa/9cbeade19b7e8e853e7ffc261df885d66bf3a782c71cba06c17df271f9e6/coverage-7.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:86a323a275e9e44cdf228af9b71c5030861d4d2610886ab920d9945672a81223", size = 215165, upload-time = "2025-05-23T11:38:45.148Z" }, - { url = "https://files.pythonhosted.org/packages/c4/73/e2528bf1237d2448f882bbebaec5c3500ef07301816c5c63464b9da4d88a/coverage-7.8.2-cp312-cp312-win_arm64.whl", hash = "sha256:820157de3a589e992689ffcda8639fbabb313b323d26388d02e154164c57b07f", size = 213548, upload-time = "2025-05-23T11:38:46.74Z" }, - { url = "https://files.pythonhosted.org/packages/1a/93/eb6400a745ad3b265bac36e8077fdffcf0268bdbbb6c02b7220b624c9b31/coverage-7.8.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ea561010914ec1c26ab4188aef8b1567272ef6de096312716f90e5baa79ef8ca", size = 211898, upload-time = "2025-05-23T11:38:49.066Z" }, - { url = "https://files.pythonhosted.org/packages/1b/7c/bdbf113f92683024406a1cd226a199e4200a2001fc85d6a6e7e299e60253/coverage-7.8.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cb86337a4fcdd0e598ff2caeb513ac604d2f3da6d53df2c8e368e07ee38e277d", size = 212171, upload-time = "2025-05-23T11:38:51.207Z" }, - { url = "https://files.pythonhosted.org/packages/91/22/594513f9541a6b88eb0dba4d5da7d71596dadef6b17a12dc2c0e859818a9/coverage-7.8.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26a4636ddb666971345541b59899e969f3b301143dd86b0ddbb570bd591f1e85", size = 245564, upload-time = "2025-05-23T11:38:52.857Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f4/2860fd6abeebd9f2efcfe0fd376226938f22afc80c1943f363cd3c28421f/coverage-7.8.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5040536cf9b13fb033f76bcb5e1e5cb3b57c4807fef37db9e0ed129c6a094257", size = 242719, upload-time = "2025-05-23T11:38:54.529Z" }, - { url = "https://files.pythonhosted.org/packages/89/60/f5f50f61b6332451520e6cdc2401700c48310c64bc2dd34027a47d6ab4ca/coverage-7.8.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc67994df9bcd7e0150a47ef41278b9e0a0ea187caba72414b71dc590b99a108", size = 244634, upload-time = "2025-05-23T11:38:57.326Z" }, - { url = "https://files.pythonhosted.org/packages/3b/70/7f4e919039ab7d944276c446b603eea84da29ebcf20984fb1fdf6e602028/coverage-7.8.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e6c86888fd076d9e0fe848af0a2142bf606044dc5ceee0aa9eddb56e26895a0", size = 244824, upload-time = "2025-05-23T11:38:59.421Z" }, - { url = "https://files.pythonhosted.org/packages/26/45/36297a4c0cea4de2b2c442fe32f60c3991056c59cdc3cdd5346fbb995c97/coverage-7.8.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:684ca9f58119b8e26bef860db33524ae0365601492e86ba0b71d513f525e7050", size = 242872, upload-time = "2025-05-23T11:39:01.049Z" }, - { url = "https://files.pythonhosted.org/packages/a4/71/e041f1b9420f7b786b1367fa2a375703889ef376e0d48de9f5723fb35f11/coverage-7.8.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8165584ddedb49204c4e18da083913bdf6a982bfb558632a79bdaadcdafd0d48", size = 244179, upload-time = "2025-05-23T11:39:02.709Z" }, - { url = "https://files.pythonhosted.org/packages/bd/db/3c2bf49bdc9de76acf2491fc03130c4ffc51469ce2f6889d2640eb563d77/coverage-7.8.2-cp313-cp313-win32.whl", hash = "sha256:34759ee2c65362163699cc917bdb2a54114dd06d19bab860725f94ef45a3d9b7", size = 214393, upload-time = "2025-05-23T11:39:05.457Z" }, - { url = "https://files.pythonhosted.org/packages/c6/dc/947e75d47ebbb4b02d8babb1fad4ad381410d5bc9da7cfca80b7565ef401/coverage-7.8.2-cp313-cp313-win_amd64.whl", hash = "sha256:2f9bc608fbafaee40eb60a9a53dbfb90f53cc66d3d32c2849dc27cf5638a21e3", size = 215194, upload-time = "2025-05-23T11:39:07.171Z" }, - { url = "https://files.pythonhosted.org/packages/90/31/a980f7df8a37eaf0dc60f932507fda9656b3a03f0abf188474a0ea188d6d/coverage-7.8.2-cp313-cp313-win_arm64.whl", hash = "sha256:9fe449ee461a3b0c7105690419d0b0aba1232f4ff6d120a9e241e58a556733f7", size = 213580, upload-time = "2025-05-23T11:39:08.862Z" }, - { url = "https://files.pythonhosted.org/packages/8a/6a/25a37dd90f6c95f59355629417ebcb74e1c34e38bb1eddf6ca9b38b0fc53/coverage-7.8.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8369a7c8ef66bded2b6484053749ff220dbf83cba84f3398c84c51a6f748a008", size = 212734, upload-time = "2025-05-23T11:39:11.109Z" }, - { url = "https://files.pythonhosted.org/packages/36/8b/3a728b3118988725f40950931abb09cd7f43b3c740f4640a59f1db60e372/coverage-7.8.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:159b81df53a5fcbc7d45dae3adad554fdbde9829a994e15227b3f9d816d00b36", size = 212959, upload-time = "2025-05-23T11:39:12.751Z" }, - { url = "https://files.pythonhosted.org/packages/53/3c/212d94e6add3a3c3f412d664aee452045ca17a066def8b9421673e9482c4/coverage-7.8.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6fcbbd35a96192d042c691c9e0c49ef54bd7ed865846a3c9d624c30bb67ce46", size = 257024, upload-time = "2025-05-23T11:39:15.569Z" }, - { url = "https://files.pythonhosted.org/packages/a4/40/afc03f0883b1e51bbe804707aae62e29c4e8c8bbc365c75e3e4ddeee9ead/coverage-7.8.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05364b9cc82f138cc86128dc4e2e1251c2981a2218bfcd556fe6b0fbaa3501be", size = 252867, upload-time = "2025-05-23T11:39:17.64Z" }, - { url = "https://files.pythonhosted.org/packages/18/a2/3699190e927b9439c6ded4998941a3c1d6fa99e14cb28d8536729537e307/coverage-7.8.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46d532db4e5ff3979ce47d18e2fe8ecad283eeb7367726da0e5ef88e4fe64740", size = 255096, upload-time = "2025-05-23T11:39:19.328Z" }, - { url = "https://files.pythonhosted.org/packages/b4/06/16e3598b9466456b718eb3e789457d1a5b8bfb22e23b6e8bbc307df5daf0/coverage-7.8.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4000a31c34932e7e4fa0381a3d6deb43dc0c8f458e3e7ea6502e6238e10be625", size = 256276, upload-time = "2025-05-23T11:39:21.077Z" }, - { url = "https://files.pythonhosted.org/packages/a7/d5/4b5a120d5d0223050a53d2783c049c311eea1709fa9de12d1c358e18b707/coverage-7.8.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:43ff5033d657cd51f83015c3b7a443287250dc14e69910577c3e03bd2e06f27b", size = 254478, upload-time = "2025-05-23T11:39:22.838Z" }, - { url = "https://files.pythonhosted.org/packages/ba/85/f9ecdb910ecdb282b121bfcaa32fa8ee8cbd7699f83330ee13ff9bbf1a85/coverage-7.8.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94316e13f0981cbbba132c1f9f365cac1d26716aaac130866ca812006f662199", size = 255255, upload-time = "2025-05-23T11:39:24.644Z" }, - { url = "https://files.pythonhosted.org/packages/50/63/2d624ac7d7ccd4ebbd3c6a9eba9d7fc4491a1226071360d59dd84928ccb2/coverage-7.8.2-cp313-cp313t-win32.whl", hash = "sha256:3f5673888d3676d0a745c3d0e16da338c5eea300cb1f4ada9c872981265e76d8", size = 215109, upload-time = "2025-05-23T11:39:26.722Z" }, - { url = "https://files.pythonhosted.org/packages/22/5e/7053b71462e970e869111c1853afd642212568a350eba796deefdfbd0770/coverage-7.8.2-cp313-cp313t-win_amd64.whl", hash = "sha256:2c08b05ee8d7861e45dc5a2cc4195c8c66dca5ac613144eb6ebeaff2d502e73d", size = 216268, upload-time = "2025-05-23T11:39:28.429Z" }, - { url = "https://files.pythonhosted.org/packages/07/69/afa41aa34147655543dbe96994f8a246daf94b361ccf5edfd5df62ce066a/coverage-7.8.2-cp313-cp313t-win_arm64.whl", hash = "sha256:1e1448bb72b387755e1ff3ef1268a06617afd94188164960dba8d0245a46004b", size = 214071, upload-time = "2025-05-23T11:39:30.55Z" }, - { url = "https://files.pythonhosted.org/packages/69/2f/572b29496d8234e4a7773200dd835a0d32d9e171f2d974f3fe04a9dbc271/coverage-7.8.2-pp39.pp310.pp311-none-any.whl", hash = "sha256:ec455eedf3ba0bbdf8f5a570012617eb305c63cb9f03428d39bf544cb2b94837", size = 203636, upload-time = "2025-05-23T11:39:52.002Z" }, - { url = "https://files.pythonhosted.org/packages/a0/1a/0b9c32220ad694d66062f571cc5cedfa9997b64a591e8a500bb63de1bd40/coverage-7.8.2-py3-none-any.whl", hash = "sha256:726f32ee3713f7359696331a18daf0c3b3a70bb0ae71141b9d3c52be7c595e32", size = 203623, upload-time = "2025-05-23T11:39:53.846Z" }, + { url = "https://files.pythonhosted.org/packages/a1/0d/5c2114fd776c207bd55068ae8dc1bef63ecd1b767b3389984a8e58f2b926/coverage-7.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:66283a192a14a3854b2e7f3418d7db05cdf411012ab7ff5db98ff3b181e1f912", size = 212039, upload-time = "2025-07-03T10:52:38.955Z" }, + { url = "https://files.pythonhosted.org/packages/cf/ad/dc51f40492dc2d5fcd31bb44577bc0cc8920757d6bc5d3e4293146524ef9/coverage-7.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4e01d138540ef34fcf35c1aa24d06c3de2a4cffa349e29a10056544f35cca15f", size = 212428, upload-time = "2025-07-03T10:52:41.36Z" }, + { url = "https://files.pythonhosted.org/packages/a2/a3/55cb3ff1b36f00df04439c3993d8529193cdf165a2467bf1402539070f16/coverage-7.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f22627c1fe2745ee98d3ab87679ca73a97e75ca75eb5faee48660d060875465f", size = 241534, upload-time = "2025-07-03T10:52:42.956Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c9/a8410b91b6be4f6e9c2e9f0dce93749b6b40b751d7065b4410bf89cb654b/coverage-7.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b1c2d8363247b46bd51f393f86c94096e64a1cf6906803fa8d5a9d03784bdbf", size = 239408, upload-time = "2025-07-03T10:52:44.199Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c4/6f3e56d467c612b9070ae71d5d3b114c0b899b5788e1ca3c93068ccb7018/coverage-7.9.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c10c882b114faf82dbd33e876d0cbd5e1d1ebc0d2a74ceef642c6152f3f4d547", size = 240552, upload-time = "2025-07-03T10:52:45.477Z" }, + { url = "https://files.pythonhosted.org/packages/fd/20/04eda789d15af1ce79bce5cc5fd64057c3a0ac08fd0576377a3096c24663/coverage-7.9.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:de3c0378bdf7066c3988d66cd5232d161e933b87103b014ab1b0b4676098fa45", size = 240464, upload-time = "2025-07-03T10:52:46.809Z" }, + { url = "https://files.pythonhosted.org/packages/a9/5a/217b32c94cc1a0b90f253514815332d08ec0812194a1ce9cca97dda1cd20/coverage-7.9.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1e2f097eae0e5991e7623958a24ced3282676c93c013dde41399ff63e230fcf2", size = 239134, upload-time = "2025-07-03T10:52:48.149Z" }, + { url = "https://files.pythonhosted.org/packages/34/73/1d019c48f413465eb5d3b6898b6279e87141c80049f7dbf73fd020138549/coverage-7.9.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28dc1f67e83a14e7079b6cea4d314bc8b24d1aed42d3582ff89c0295f09b181e", size = 239405, upload-time = "2025-07-03T10:52:49.687Z" }, + { url = "https://files.pythonhosted.org/packages/49/6c/a2beca7aa2595dad0c0d3f350382c381c92400efe5261e2631f734a0e3fe/coverage-7.9.2-cp310-cp310-win32.whl", hash = "sha256:bf7d773da6af9e10dbddacbf4e5cab13d06d0ed93561d44dae0188a42c65be7e", size = 214519, upload-time = "2025-07-03T10:52:51.036Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c8/91e5e4a21f9a51e2c7cdd86e587ae01a4fcff06fc3fa8cde4d6f7cf68df4/coverage-7.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:0c0378ba787681ab1897f7c89b415bd56b0b2d9a47e5a3d8dc0ea55aac118d6c", size = 215400, upload-time = "2025-07-03T10:52:52.313Z" }, + { url = "https://files.pythonhosted.org/packages/39/40/916786453bcfafa4c788abee4ccd6f592b5b5eca0cd61a32a4e5a7ef6e02/coverage-7.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a7a56a2964a9687b6aba5b5ced6971af308ef6f79a91043c05dd4ee3ebc3e9ba", size = 212152, upload-time = "2025-07-03T10:52:53.562Z" }, + { url = "https://files.pythonhosted.org/packages/9f/66/cc13bae303284b546a030762957322bbbff1ee6b6cb8dc70a40f8a78512f/coverage-7.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:123d589f32c11d9be7fe2e66d823a236fe759b0096f5db3fb1b75b2fa414a4fa", size = 212540, upload-time = "2025-07-03T10:52:55.196Z" }, + { url = "https://files.pythonhosted.org/packages/0f/3c/d56a764b2e5a3d43257c36af4a62c379df44636817bb5f89265de4bf8bd7/coverage-7.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:333b2e0ca576a7dbd66e85ab402e35c03b0b22f525eed82681c4b866e2e2653a", size = 245097, upload-time = "2025-07-03T10:52:56.509Z" }, + { url = "https://files.pythonhosted.org/packages/b1/46/bd064ea8b3c94eb4ca5d90e34d15b806cba091ffb2b8e89a0d7066c45791/coverage-7.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:326802760da234baf9f2f85a39e4a4b5861b94f6c8d95251f699e4f73b1835dc", size = 242812, upload-time = "2025-07-03T10:52:57.842Z" }, + { url = "https://files.pythonhosted.org/packages/43/02/d91992c2b29bc7afb729463bc918ebe5f361be7f1daae93375a5759d1e28/coverage-7.9.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19e7be4cfec248df38ce40968c95d3952fbffd57b400d4b9bb580f28179556d2", size = 244617, upload-time = "2025-07-03T10:52:59.239Z" }, + { url = "https://files.pythonhosted.org/packages/b7/4f/8fadff6bf56595a16d2d6e33415841b0163ac660873ed9a4e9046194f779/coverage-7.9.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0b4a4cb73b9f2b891c1788711408ef9707666501ba23684387277ededab1097c", size = 244263, upload-time = "2025-07-03T10:53:00.601Z" }, + { url = "https://files.pythonhosted.org/packages/9b/d2/e0be7446a2bba11739edb9f9ba4eff30b30d8257370e237418eb44a14d11/coverage-7.9.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2c8937fa16c8c9fbbd9f118588756e7bcdc7e16a470766a9aef912dd3f117dbd", size = 242314, upload-time = "2025-07-03T10:53:01.932Z" }, + { url = "https://files.pythonhosted.org/packages/9d/7d/dcbac9345000121b8b57a3094c2dfcf1ccc52d8a14a40c1d4bc89f936f80/coverage-7.9.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:42da2280c4d30c57a9b578bafd1d4494fa6c056d4c419d9689e66d775539be74", size = 242904, upload-time = "2025-07-03T10:53:03.478Z" }, + { url = "https://files.pythonhosted.org/packages/41/58/11e8db0a0c0510cf31bbbdc8caf5d74a358b696302a45948d7c768dfd1cf/coverage-7.9.2-cp311-cp311-win32.whl", hash = "sha256:14fa8d3da147f5fdf9d298cacc18791818f3f1a9f542c8958b80c228320e90c6", size = 214553, upload-time = "2025-07-03T10:53:05.174Z" }, + { url = "https://files.pythonhosted.org/packages/3a/7d/751794ec8907a15e257136e48dc1021b1f671220ecccfd6c4eaf30802714/coverage-7.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:549cab4892fc82004f9739963163fd3aac7a7b0df430669b75b86d293d2df2a7", size = 215441, upload-time = "2025-07-03T10:53:06.472Z" }, + { url = "https://files.pythonhosted.org/packages/62/5b/34abcedf7b946c1c9e15b44f326cb5b0da852885312b30e916f674913428/coverage-7.9.2-cp311-cp311-win_arm64.whl", hash = "sha256:c2667a2b913e307f06aa4e5677f01a9746cd08e4b35e14ebcde6420a9ebb4c62", size = 213873, upload-time = "2025-07-03T10:53:07.699Z" }, + { url = "https://files.pythonhosted.org/packages/53/d7/7deefc6fd4f0f1d4c58051f4004e366afc9e7ab60217ac393f247a1de70a/coverage-7.9.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ae9eb07f1cfacd9cfe8eaee6f4ff4b8a289a668c39c165cd0c8548484920ffc0", size = 212344, upload-time = "2025-07-03T10:53:09.3Z" }, + { url = "https://files.pythonhosted.org/packages/95/0c/ee03c95d32be4d519e6a02e601267769ce2e9a91fc8faa1b540e3626c680/coverage-7.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9ce85551f9a1119f02adc46d3014b5ee3f765deac166acf20dbb851ceb79b6f3", size = 212580, upload-time = "2025-07-03T10:53:11.52Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9f/826fa4b544b27620086211b87a52ca67592622e1f3af9e0a62c87aea153a/coverage-7.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8f6389ac977c5fb322e0e38885fbbf901743f79d47f50db706e7644dcdcb6e1", size = 246383, upload-time = "2025-07-03T10:53:13.134Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b3/4477aafe2a546427b58b9c540665feff874f4db651f4d3cb21b308b3a6d2/coverage-7.9.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff0d9eae8cdfcd58fe7893b88993723583a6ce4dfbfd9f29e001922544f95615", size = 243400, upload-time = "2025-07-03T10:53:14.614Z" }, + { url = "https://files.pythonhosted.org/packages/f8/c2/efffa43778490c226d9d434827702f2dfbc8041d79101a795f11cbb2cf1e/coverage-7.9.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fae939811e14e53ed8a9818dad51d434a41ee09df9305663735f2e2d2d7d959b", size = 245591, upload-time = "2025-07-03T10:53:15.872Z" }, + { url = "https://files.pythonhosted.org/packages/c6/e7/a59888e882c9a5f0192d8627a30ae57910d5d449c80229b55e7643c078c4/coverage-7.9.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:31991156251ec202c798501e0a42bbdf2169dcb0f137b1f5c0f4267f3fc68ef9", size = 245402, upload-time = "2025-07-03T10:53:17.124Z" }, + { url = "https://files.pythonhosted.org/packages/92/a5/72fcd653ae3d214927edc100ce67440ed8a0a1e3576b8d5e6d066ed239db/coverage-7.9.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d0d67963f9cbfc7c7f96d4ac74ed60ecbebd2ea6eeb51887af0f8dce205e545f", size = 243583, upload-time = "2025-07-03T10:53:18.781Z" }, + { url = "https://files.pythonhosted.org/packages/5c/f5/84e70e4df28f4a131d580d7d510aa1ffd95037293da66fd20d446090a13b/coverage-7.9.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:49b752a2858b10580969ec6af6f090a9a440a64a301ac1528d7ca5f7ed497f4d", size = 244815, upload-time = "2025-07-03T10:53:20.168Z" }, + { url = "https://files.pythonhosted.org/packages/39/e7/d73d7cbdbd09fdcf4642655ae843ad403d9cbda55d725721965f3580a314/coverage-7.9.2-cp312-cp312-win32.whl", hash = "sha256:88d7598b8ee130f32f8a43198ee02edd16d7f77692fa056cb779616bbea1b355", size = 214719, upload-time = "2025-07-03T10:53:21.521Z" }, + { url = "https://files.pythonhosted.org/packages/9f/d6/7486dcc3474e2e6ad26a2af2db7e7c162ccd889c4c68fa14ea8ec189c9e9/coverage-7.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:9dfb070f830739ee49d7c83e4941cc767e503e4394fdecb3b54bfdac1d7662c0", size = 215509, upload-time = "2025-07-03T10:53:22.853Z" }, + { url = "https://files.pythonhosted.org/packages/b7/34/0439f1ae2593b0346164d907cdf96a529b40b7721a45fdcf8b03c95fcd90/coverage-7.9.2-cp312-cp312-win_arm64.whl", hash = "sha256:4e2c058aef613e79df00e86b6d42a641c877211384ce5bd07585ed7ba71ab31b", size = 213910, upload-time = "2025-07-03T10:53:24.472Z" }, + { url = "https://files.pythonhosted.org/packages/94/9d/7a8edf7acbcaa5e5c489a646226bed9591ee1c5e6a84733c0140e9ce1ae1/coverage-7.9.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:985abe7f242e0d7bba228ab01070fde1d6c8fa12f142e43debe9ed1dde686038", size = 212367, upload-time = "2025-07-03T10:53:25.811Z" }, + { url = "https://files.pythonhosted.org/packages/e8/9e/5cd6f130150712301f7e40fb5865c1bc27b97689ec57297e568d972eec3c/coverage-7.9.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82c3939264a76d44fde7f213924021ed31f55ef28111a19649fec90c0f109e6d", size = 212632, upload-time = "2025-07-03T10:53:27.075Z" }, + { url = "https://files.pythonhosted.org/packages/a8/de/6287a2c2036f9fd991c61cefa8c64e57390e30c894ad3aa52fac4c1e14a8/coverage-7.9.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae5d563e970dbe04382f736ec214ef48103d1b875967c89d83c6e3f21706d5b3", size = 245793, upload-time = "2025-07-03T10:53:28.408Z" }, + { url = "https://files.pythonhosted.org/packages/06/cc/9b5a9961d8160e3cb0b558c71f8051fe08aa2dd4b502ee937225da564ed1/coverage-7.9.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdd612e59baed2a93c8843c9a7cb902260f181370f1d772f4842987535071d14", size = 243006, upload-time = "2025-07-03T10:53:29.754Z" }, + { url = "https://files.pythonhosted.org/packages/49/d9/4616b787d9f597d6443f5588619c1c9f659e1f5fc9eebf63699eb6d34b78/coverage-7.9.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:256ea87cb2a1ed992bcdfc349d8042dcea1b80436f4ddf6e246d6bee4b5d73b6", size = 244990, upload-time = "2025-07-03T10:53:31.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/83/801cdc10f137b2d02b005a761661649ffa60eb173dcdaeb77f571e4dc192/coverage-7.9.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f44ae036b63c8ea432f610534a2668b0c3aee810e7037ab9d8ff6883de480f5b", size = 245157, upload-time = "2025-07-03T10:53:32.717Z" }, + { url = "https://files.pythonhosted.org/packages/c8/a4/41911ed7e9d3ceb0ffb019e7635468df7499f5cc3edca5f7dfc078e9c5ec/coverage-7.9.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:82d76ad87c932935417a19b10cfe7abb15fd3f923cfe47dbdaa74ef4e503752d", size = 243128, upload-time = "2025-07-03T10:53:34.009Z" }, + { url = "https://files.pythonhosted.org/packages/10/41/344543b71d31ac9cb00a664d5d0c9ef134a0fe87cb7d8430003b20fa0b7d/coverage-7.9.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:619317bb86de4193debc712b9e59d5cffd91dc1d178627ab2a77b9870deb2868", size = 244511, upload-time = "2025-07-03T10:53:35.434Z" }, + { url = "https://files.pythonhosted.org/packages/d5/81/3b68c77e4812105e2a060f6946ba9e6f898ddcdc0d2bfc8b4b152a9ae522/coverage-7.9.2-cp313-cp313-win32.whl", hash = "sha256:0a07757de9feb1dfafd16ab651e0f628fd7ce551604d1bf23e47e1ddca93f08a", size = 214765, upload-time = "2025-07-03T10:53:36.787Z" }, + { url = "https://files.pythonhosted.org/packages/06/a2/7fac400f6a346bb1a4004eb2a76fbff0e242cd48926a2ce37a22a6a1d917/coverage-7.9.2-cp313-cp313-win_amd64.whl", hash = "sha256:115db3d1f4d3f35f5bb021e270edd85011934ff97c8797216b62f461dd69374b", size = 215536, upload-time = "2025-07-03T10:53:38.188Z" }, + { url = "https://files.pythonhosted.org/packages/08/47/2c6c215452b4f90d87017e61ea0fd9e0486bb734cb515e3de56e2c32075f/coverage-7.9.2-cp313-cp313-win_arm64.whl", hash = "sha256:48f82f889c80af8b2a7bb6e158d95a3fbec6a3453a1004d04e4f3b5945a02694", size = 213943, upload-time = "2025-07-03T10:53:39.492Z" }, + { url = "https://files.pythonhosted.org/packages/a3/46/e211e942b22d6af5e0f323faa8a9bc7c447a1cf1923b64c47523f36ed488/coverage-7.9.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:55a28954545f9d2f96870b40f6c3386a59ba8ed50caf2d949676dac3ecab99f5", size = 213088, upload-time = "2025-07-03T10:53:40.874Z" }, + { url = "https://files.pythonhosted.org/packages/d2/2f/762551f97e124442eccd907bf8b0de54348635b8866a73567eb4e6417acf/coverage-7.9.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cdef6504637731a63c133bb2e6f0f0214e2748495ec15fe42d1e219d1b133f0b", size = 213298, upload-time = "2025-07-03T10:53:42.218Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b7/76d2d132b7baf7360ed69be0bcab968f151fa31abe6d067f0384439d9edb/coverage-7.9.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcd5ebe66c7a97273d5d2ddd4ad0ed2e706b39630ed4b53e713d360626c3dbb3", size = 256541, upload-time = "2025-07-03T10:53:43.823Z" }, + { url = "https://files.pythonhosted.org/packages/a0/17/392b219837d7ad47d8e5974ce5f8dc3deb9f99a53b3bd4d123602f960c81/coverage-7.9.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9303aed20872d7a3c9cb39c5d2b9bdbe44e3a9a1aecb52920f7e7495410dfab8", size = 252761, upload-time = "2025-07-03T10:53:45.19Z" }, + { url = "https://files.pythonhosted.org/packages/d5/77/4256d3577fe1b0daa8d3836a1ebe68eaa07dd2cbaf20cf5ab1115d6949d4/coverage-7.9.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc18ea9e417a04d1920a9a76fe9ebd2f43ca505b81994598482f938d5c315f46", size = 254917, upload-time = "2025-07-03T10:53:46.931Z" }, + { url = "https://files.pythonhosted.org/packages/53/99/fc1a008eef1805e1ddb123cf17af864743354479ea5129a8f838c433cc2c/coverage-7.9.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6406cff19880aaaadc932152242523e892faff224da29e241ce2fca329866584", size = 256147, upload-time = "2025-07-03T10:53:48.289Z" }, + { url = "https://files.pythonhosted.org/packages/92/c0/f63bf667e18b7f88c2bdb3160870e277c4874ced87e21426128d70aa741f/coverage-7.9.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d0d4f6ecdf37fcc19c88fec3e2277d5dee740fb51ffdd69b9579b8c31e4232e", size = 254261, upload-time = "2025-07-03T10:53:49.99Z" }, + { url = "https://files.pythonhosted.org/packages/8c/32/37dd1c42ce3016ff8ec9e4b607650d2e34845c0585d3518b2a93b4830c1a/coverage-7.9.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c33624f50cf8de418ab2b4d6ca9eda96dc45b2c4231336bac91454520e8d1fac", size = 255099, upload-time = "2025-07-03T10:53:51.354Z" }, + { url = "https://files.pythonhosted.org/packages/da/2e/af6b86f7c95441ce82f035b3affe1cd147f727bbd92f563be35e2d585683/coverage-7.9.2-cp313-cp313t-win32.whl", hash = "sha256:1df6b76e737c6a92210eebcb2390af59a141f9e9430210595251fbaf02d46926", size = 215440, upload-time = "2025-07-03T10:53:52.808Z" }, + { url = "https://files.pythonhosted.org/packages/4d/bb/8a785d91b308867f6b2e36e41c569b367c00b70c17f54b13ac29bcd2d8c8/coverage-7.9.2-cp313-cp313t-win_amd64.whl", hash = "sha256:f5fd54310b92741ebe00d9c0d1d7b2b27463952c022da6d47c175d246a98d1bd", size = 216537, upload-time = "2025-07-03T10:53:54.273Z" }, + { url = "https://files.pythonhosted.org/packages/1d/a0/a6bffb5e0f41a47279fd45a8f3155bf193f77990ae1c30f9c224b61cacb0/coverage-7.9.2-cp313-cp313t-win_arm64.whl", hash = "sha256:c48c2375287108c887ee87d13b4070a381c6537d30e8487b24ec721bf2a781cb", size = 214398, upload-time = "2025-07-03T10:53:56.715Z" }, + { url = "https://files.pythonhosted.org/packages/d7/85/f8bbefac27d286386961c25515431482a425967e23d3698b75a250872924/coverage-7.9.2-pp39.pp310.pp311-none-any.whl", hash = "sha256:8a1166db2fb62473285bcb092f586e081e92656c7dfa8e9f62b4d39d7e6b5050", size = 204013, upload-time = "2025-07-03T10:54:12.084Z" }, + { url = "https://files.pythonhosted.org/packages/3c/38/bbe2e63902847cf79036ecc75550d0698af31c91c7575352eb25190d0fb3/coverage-7.9.2-py3-none-any.whl", hash = "sha256:e425cd5b00f6fc0ed7cdbd766c70be8baab4b7839e4d4fe5fac48581dd968ea4", size = 204005, upload-time = "2025-07-03T10:54:13.491Z" }, ] [package.optional-dependencies] @@ -103,7 +103,7 @@ wheels = [ [[package]] name = "mypy" -version = "1.16.0" +version = "1.17.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mypy-extensions" }, @@ -111,33 +111,33 @@ dependencies = [ { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d4/38/13c2f1abae94d5ea0354e146b95a1be9b2137a0d506728e0da037c4276f6/mypy-1.16.0.tar.gz", hash = "sha256:84b94283f817e2aa6350a14b4a8fb2a35a53c286f97c9d30f53b63620e7af8ab", size = 3323139, upload-time = "2025-05-29T13:46:12.532Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/e3/034322d5a779685218ed69286c32faa505247f1f096251ef66c8fd203b08/mypy-1.17.0.tar.gz", hash = "sha256:e5d7ccc08ba089c06e2f5629c660388ef1fee708444f1dee0b9203fa031dee03", size = 3352114, upload-time = "2025-07-14T20:34:30.181Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/64/5e/a0485f0608a3d67029d3d73cec209278b025e3493a3acfda3ef3a88540fd/mypy-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7909541fef256527e5ee9c0a7e2aeed78b6cda72ba44298d1334fe7881b05c5c", size = 10967416, upload-time = "2025-05-29T13:34:17.783Z" }, - { url = "https://files.pythonhosted.org/packages/4b/53/5837c221f74c0d53a4bfc3003296f8179c3a2a7f336d7de7bbafbe96b688/mypy-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e71d6f0090c2256c713ed3d52711d01859c82608b5d68d4fa01a3fe30df95571", size = 10087654, upload-time = "2025-05-29T13:32:37.878Z" }, - { url = "https://files.pythonhosted.org/packages/29/59/5fd2400352c3093bed4c09017fe671d26bc5bb7e6ef2d4bf85f2a2488104/mypy-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:936ccfdd749af4766be824268bfe22d1db9eb2f34a3ea1d00ffbe5b5265f5491", size = 11875192, upload-time = "2025-05-29T13:34:54.281Z" }, - { url = "https://files.pythonhosted.org/packages/ad/3e/4bfec74663a64c2012f3e278dbc29ffe82b121bc551758590d1b6449ec0c/mypy-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4086883a73166631307fdd330c4a9080ce24913d4f4c5ec596c601b3a4bdd777", size = 12612939, upload-time = "2025-05-29T13:33:14.766Z" }, - { url = "https://files.pythonhosted.org/packages/88/1f/fecbe3dcba4bf2ca34c26ca016383a9676711907f8db4da8354925cbb08f/mypy-1.16.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:feec38097f71797da0231997e0de3a58108c51845399669ebc532c815f93866b", size = 12874719, upload-time = "2025-05-29T13:21:52.09Z" }, - { url = "https://files.pythonhosted.org/packages/f3/51/c2d280601cd816c43dfa512a759270d5a5ef638d7ac9bea9134c8305a12f/mypy-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:09a8da6a0ee9a9770b8ff61b39c0bb07971cda90e7297f4213741b48a0cc8d93", size = 9487053, upload-time = "2025-05-29T13:33:29.797Z" }, - { url = "https://files.pythonhosted.org/packages/24/c4/ff2f79db7075c274fe85b5fff8797d29c6b61b8854c39e3b7feb556aa377/mypy-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9f826aaa7ff8443bac6a494cf743f591488ea940dd360e7dd330e30dd772a5ab", size = 10884498, upload-time = "2025-05-29T13:18:54.066Z" }, - { url = "https://files.pythonhosted.org/packages/02/07/12198e83006235f10f6a7808917376b5d6240a2fd5dce740fe5d2ebf3247/mypy-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:82d056e6faa508501af333a6af192c700b33e15865bda49611e3d7d8358ebea2", size = 10011755, upload-time = "2025-05-29T13:34:00.851Z" }, - { url = "https://files.pythonhosted.org/packages/f1/9b/5fd5801a72b5d6fb6ec0105ea1d0e01ab2d4971893076e558d4b6d6b5f80/mypy-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:089bedc02307c2548eb51f426e085546db1fa7dd87fbb7c9fa561575cf6eb1ff", size = 11800138, upload-time = "2025-05-29T13:32:55.082Z" }, - { url = "https://files.pythonhosted.org/packages/2e/81/a117441ea5dfc3746431e51d78a4aca569c677aa225bca2cc05a7c239b61/mypy-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6a2322896003ba66bbd1318c10d3afdfe24e78ef12ea10e2acd985e9d684a666", size = 12533156, upload-time = "2025-05-29T13:19:12.963Z" }, - { url = "https://files.pythonhosted.org/packages/3f/38/88ec57c6c86014d3f06251e00f397b5a7daa6888884d0abf187e4f5f587f/mypy-1.16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:021a68568082c5b36e977d54e8f1de978baf401a33884ffcea09bd8e88a98f4c", size = 12742426, upload-time = "2025-05-29T13:20:22.72Z" }, - { url = "https://files.pythonhosted.org/packages/bd/53/7e9d528433d56e6f6f77ccf24af6ce570986c2d98a5839e4c2009ef47283/mypy-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:54066fed302d83bf5128632d05b4ec68412e1f03ef2c300434057d66866cea4b", size = 9478319, upload-time = "2025-05-29T13:21:17.582Z" }, - { url = "https://files.pythonhosted.org/packages/70/cf/158e5055e60ca2be23aec54a3010f89dcffd788732634b344fc9cb1e85a0/mypy-1.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c5436d11e89a3ad16ce8afe752f0f373ae9620841c50883dc96f8b8805620b13", size = 11062927, upload-time = "2025-05-29T13:35:52.328Z" }, - { url = "https://files.pythonhosted.org/packages/94/34/cfff7a56be1609f5d10ef386342ce3494158e4d506516890142007e6472c/mypy-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f2622af30bf01d8fc36466231bdd203d120d7a599a6d88fb22bdcb9dbff84090", size = 10083082, upload-time = "2025-05-29T13:35:33.378Z" }, - { url = "https://files.pythonhosted.org/packages/b3/7f/7242062ec6288c33d8ad89574df87c3903d394870e5e6ba1699317a65075/mypy-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d045d33c284e10a038f5e29faca055b90eee87da3fc63b8889085744ebabb5a1", size = 11828306, upload-time = "2025-05-29T13:21:02.164Z" }, - { url = "https://files.pythonhosted.org/packages/6f/5f/b392f7b4f659f5b619ce5994c5c43caab3d80df2296ae54fa888b3d17f5a/mypy-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b4968f14f44c62e2ec4a038c8797a87315be8df7740dc3ee8d3bfe1c6bf5dba8", size = 12702764, upload-time = "2025-05-29T13:20:42.826Z" }, - { url = "https://files.pythonhosted.org/packages/9b/c0/7646ef3a00fa39ac9bc0938626d9ff29d19d733011be929cfea59d82d136/mypy-1.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eb14a4a871bb8efb1e4a50360d4e3c8d6c601e7a31028a2c79f9bb659b63d730", size = 12896233, upload-time = "2025-05-29T13:18:37.446Z" }, - { url = "https://files.pythonhosted.org/packages/6d/38/52f4b808b3fef7f0ef840ee8ff6ce5b5d77381e65425758d515cdd4f5bb5/mypy-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:bd4e1ebe126152a7bbaa4daedd781c90c8f9643c79b9748caa270ad542f12bec", size = 9565547, upload-time = "2025-05-29T13:20:02.836Z" }, - { url = "https://files.pythonhosted.org/packages/97/9c/ca03bdbefbaa03b264b9318a98950a9c683e06472226b55472f96ebbc53d/mypy-1.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a9e056237c89f1587a3be1a3a70a06a698d25e2479b9a2f57325ddaaffc3567b", size = 11059753, upload-time = "2025-05-29T13:18:18.167Z" }, - { url = "https://files.pythonhosted.org/packages/36/92/79a969b8302cfe316027c88f7dc6fee70129490a370b3f6eb11d777749d0/mypy-1.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b07e107affb9ee6ce1f342c07f51552d126c32cd62955f59a7db94a51ad12c0", size = 10073338, upload-time = "2025-05-29T13:19:48.079Z" }, - { url = "https://files.pythonhosted.org/packages/14/9b/a943f09319167da0552d5cd722104096a9c99270719b1afeea60d11610aa/mypy-1.16.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c6fb60cbd85dc65d4d63d37cb5c86f4e3a301ec605f606ae3a9173e5cf34997b", size = 11827764, upload-time = "2025-05-29T13:46:04.47Z" }, - { url = "https://files.pythonhosted.org/packages/ec/64/ff75e71c65a0cb6ee737287c7913ea155845a556c64144c65b811afdb9c7/mypy-1.16.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7e32297a437cc915599e0578fa6bc68ae6a8dc059c9e009c628e1c47f91495d", size = 12701356, upload-time = "2025-05-29T13:35:13.553Z" }, - { url = "https://files.pythonhosted.org/packages/0a/ad/0e93c18987a1182c350f7a5fab70550852f9fabe30ecb63bfbe51b602074/mypy-1.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:afe420c9380ccec31e744e8baff0d406c846683681025db3531b32db56962d52", size = 12900745, upload-time = "2025-05-29T13:17:24.409Z" }, - { url = "https://files.pythonhosted.org/packages/28/5d/036c278d7a013e97e33f08c047fe5583ab4f1fc47c9a49f985f1cdd2a2d7/mypy-1.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:55f9076c6ce55dd3f8cd0c6fff26a008ca8e5131b89d5ba6d86bd3f47e736eeb", size = 9572200, upload-time = "2025-05-29T13:33:44.92Z" }, - { url = "https://files.pythonhosted.org/packages/99/a3/6ed10530dec8e0fdc890d81361260c9ef1f5e5c217ad8c9b21ecb2b8366b/mypy-1.16.0-py3-none-any.whl", hash = "sha256:29e1499864a3888bca5c1542f2d7232c6e586295183320caa95758fc84034031", size = 2265773, upload-time = "2025-05-29T13:35:18.762Z" }, + { url = "https://files.pythonhosted.org/packages/6a/31/e762baa3b73905c856d45ab77b4af850e8159dffffd86a52879539a08c6b/mypy-1.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8e08de6138043108b3b18f09d3f817a4783912e48828ab397ecf183135d84d6", size = 10998313, upload-time = "2025-07-14T20:33:24.519Z" }, + { url = "https://files.pythonhosted.org/packages/1c/c1/25b2f0d46fb7e0b5e2bee61ec3a47fe13eff9e3c2f2234f144858bbe6485/mypy-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce4a17920ec144647d448fc43725b5873548b1aae6c603225626747ededf582d", size = 10128922, upload-time = "2025-07-14T20:34:06.414Z" }, + { url = "https://files.pythonhosted.org/packages/02/78/6d646603a57aa8a2886df1b8881fe777ea60f28098790c1089230cd9c61d/mypy-1.17.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ff25d151cc057fdddb1cb1881ef36e9c41fa2a5e78d8dd71bee6e4dcd2bc05b", size = 11913524, upload-time = "2025-07-14T20:33:19.109Z" }, + { url = "https://files.pythonhosted.org/packages/4f/19/dae6c55e87ee426fb76980f7e78484450cad1c01c55a1dc4e91c930bea01/mypy-1.17.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93468cf29aa9a132bceb103bd8475f78cacde2b1b9a94fd978d50d4bdf616c9a", size = 12650527, upload-time = "2025-07-14T20:32:44.095Z" }, + { url = "https://files.pythonhosted.org/packages/86/e1/f916845a235235a6c1e4d4d065a3930113767001d491b8b2e1b61ca56647/mypy-1.17.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:98189382b310f16343151f65dd7e6867386d3e35f7878c45cfa11383d175d91f", size = 12897284, upload-time = "2025-07-14T20:33:38.168Z" }, + { url = "https://files.pythonhosted.org/packages/ae/dc/414760708a4ea1b096bd214d26a24e30ac5e917ef293bc33cdb6fe22d2da/mypy-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:c004135a300ab06a045c1c0d8e3f10215e71d7b4f5bb9a42ab80236364429937", size = 9506493, upload-time = "2025-07-14T20:34:01.093Z" }, + { url = "https://files.pythonhosted.org/packages/d4/24/82efb502b0b0f661c49aa21cfe3e1999ddf64bf5500fc03b5a1536a39d39/mypy-1.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9d4fe5c72fd262d9c2c91c1117d16aac555e05f5beb2bae6a755274c6eec42be", size = 10914150, upload-time = "2025-07-14T20:31:51.985Z" }, + { url = "https://files.pythonhosted.org/packages/03/96/8ef9a6ff8cedadff4400e2254689ca1dc4b420b92c55255b44573de10c54/mypy-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d96b196e5c16f41b4f7736840e8455958e832871990c7ba26bf58175e357ed61", size = 10039845, upload-time = "2025-07-14T20:32:30.527Z" }, + { url = "https://files.pythonhosted.org/packages/df/32/7ce359a56be779d38021d07941cfbb099b41411d72d827230a36203dbb81/mypy-1.17.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:73a0ff2dd10337ceb521c080d4147755ee302dcde6e1a913babd59473904615f", size = 11837246, upload-time = "2025-07-14T20:32:01.28Z" }, + { url = "https://files.pythonhosted.org/packages/82/16/b775047054de4d8dbd668df9137707e54b07fe18c7923839cd1e524bf756/mypy-1.17.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24cfcc1179c4447854e9e406d3af0f77736d631ec87d31c6281ecd5025df625d", size = 12571106, upload-time = "2025-07-14T20:34:26.942Z" }, + { url = "https://files.pythonhosted.org/packages/a1/cf/fa33eaf29a606102c8d9ffa45a386a04c2203d9ad18bf4eef3e20c43ebc8/mypy-1.17.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c56f180ff6430e6373db7a1d569317675b0a451caf5fef6ce4ab365f5f2f6c3", size = 12759960, upload-time = "2025-07-14T20:33:42.882Z" }, + { url = "https://files.pythonhosted.org/packages/94/75/3f5a29209f27e739ca57e6350bc6b783a38c7621bdf9cac3ab8a08665801/mypy-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:eafaf8b9252734400f9b77df98b4eee3d2eecab16104680d51341c75702cad70", size = 9503888, upload-time = "2025-07-14T20:32:34.392Z" }, + { url = "https://files.pythonhosted.org/packages/12/e9/e6824ed620bbf51d3bf4d6cbbe4953e83eaf31a448d1b3cfb3620ccb641c/mypy-1.17.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f986f1cab8dbec39ba6e0eaa42d4d3ac6686516a5d3dccd64be095db05ebc6bb", size = 11086395, upload-time = "2025-07-14T20:34:11.452Z" }, + { url = "https://files.pythonhosted.org/packages/ba/51/a4afd1ae279707953be175d303f04a5a7bd7e28dc62463ad29c1c857927e/mypy-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:51e455a54d199dd6e931cd7ea987d061c2afbaf0960f7f66deef47c90d1b304d", size = 10120052, upload-time = "2025-07-14T20:33:09.897Z" }, + { url = "https://files.pythonhosted.org/packages/8a/71/19adfeac926ba8205f1d1466d0d360d07b46486bf64360c54cb5a2bd86a8/mypy-1.17.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3204d773bab5ff4ebbd1f8efa11b498027cd57017c003ae970f310e5b96be8d8", size = 11861806, upload-time = "2025-07-14T20:32:16.028Z" }, + { url = "https://files.pythonhosted.org/packages/0b/64/d6120eca3835baf7179e6797a0b61d6c47e0bc2324b1f6819d8428d5b9ba/mypy-1.17.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1051df7ec0886fa246a530ae917c473491e9a0ba6938cfd0ec2abc1076495c3e", size = 12744371, upload-time = "2025-07-14T20:33:33.503Z" }, + { url = "https://files.pythonhosted.org/packages/1f/dc/56f53b5255a166f5bd0f137eed960e5065f2744509dfe69474ff0ba772a5/mypy-1.17.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f773c6d14dcc108a5b141b4456b0871df638eb411a89cd1c0c001fc4a9d08fc8", size = 12914558, upload-time = "2025-07-14T20:33:56.961Z" }, + { url = "https://files.pythonhosted.org/packages/69/ac/070bad311171badc9add2910e7f89271695a25c136de24bbafc7eded56d5/mypy-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:1619a485fd0e9c959b943c7b519ed26b712de3002d7de43154a489a2d0fd817d", size = 9585447, upload-time = "2025-07-14T20:32:20.594Z" }, + { url = "https://files.pythonhosted.org/packages/be/7b/5f8ab461369b9e62157072156935cec9d272196556bdc7c2ff5f4c7c0f9b/mypy-1.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c41aa59211e49d717d92b3bb1238c06d387c9325d3122085113c79118bebb06", size = 11070019, upload-time = "2025-07-14T20:32:07.99Z" }, + { url = "https://files.pythonhosted.org/packages/9c/f8/c49c9e5a2ac0badcc54beb24e774d2499748302c9568f7f09e8730e953fa/mypy-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e69db1fb65b3114f98c753e3930a00514f5b68794ba80590eb02090d54a5d4a", size = 10114457, upload-time = "2025-07-14T20:33:47.285Z" }, + { url = "https://files.pythonhosted.org/packages/89/0c/fb3f9c939ad9beed3e328008b3fb90b20fda2cddc0f7e4c20dbefefc3b33/mypy-1.17.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:03ba330b76710f83d6ac500053f7727270b6b8553b0423348ffb3af6f2f7b889", size = 11857838, upload-time = "2025-07-14T20:33:14.462Z" }, + { url = "https://files.pythonhosted.org/packages/4c/66/85607ab5137d65e4f54d9797b77d5a038ef34f714929cf8ad30b03f628df/mypy-1.17.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:037bc0f0b124ce46bfde955c647f3e395c6174476a968c0f22c95a8d2f589bba", size = 12731358, upload-time = "2025-07-14T20:32:25.579Z" }, + { url = "https://files.pythonhosted.org/packages/73/d0/341dbbfb35ce53d01f8f2969facbb66486cee9804048bf6c01b048127501/mypy-1.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c38876106cb6132259683632b287238858bd58de267d80defb6f418e9ee50658", size = 12917480, upload-time = "2025-07-14T20:34:21.868Z" }, + { url = "https://files.pythonhosted.org/packages/64/63/70c8b7dbfc520089ac48d01367a97e8acd734f65bd07813081f508a8c94c/mypy-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:d30ba01c0f151998f367506fab31c2ac4527e6a7b2690107c7a7f9e3cb419a9c", size = 9589666, upload-time = "2025-07-14T20:34:16.841Z" }, + { url = "https://files.pythonhosted.org/packages/e3/fc/ee058cc4316f219078464555873e99d170bde1d9569abd833300dbeb484a/mypy-1.17.0-py3-none-any.whl", hash = "sha256:15d9d0018237ab058e5de3d8fce61b6fa72cc59cc78fd91f1b474bce12abf496", size = 2283195, upload-time = "2025-07-14T20:31:54.753Z" }, ] [[package]] @@ -174,7 +174,7 @@ dev = [ [package.metadata.requires-dev] dev = [ - { name = "mypy", specifier = ">=1.14.0" }, + { name = "mypy", specifier = ">=1.17.0" }, { name = "pytest", specifier = ">=8.3.4" }, { name = "pytest-cov", specifier = ">=6.0.0" }, ] @@ -199,16 +199,16 @@ wheels = [ [[package]] name = "pygments" -version = "2.19.1" +version = "2.19.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" }, + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] [[package]] name = "pytest" -version = "8.4.0" +version = "8.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -219,22 +219,23 @@ dependencies = [ { name = "pygments" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fb/aa/405082ce2749be5398045152251ac69c0f3578c7077efc53431303af97ce/pytest-8.4.0.tar.gz", hash = "sha256:14d920b48472ea0dbf68e45b96cd1ffda4705f33307dcc86c676c1b5104838a6", size = 1515232, upload-time = "2025-06-02T17:36:30.03Z" } +sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/de/afa024cbe022b1b318a3d224125aa24939e99b4ff6f22e0ba639a2eaee47/pytest-8.4.0-py3-none-any.whl", hash = "sha256:f40f825768ad76c0977cbacdf1fd37c6f7a468e460ea6a0636078f8972d4517e", size = 363797, upload-time = "2025-06-02T17:36:27.859Z" }, + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, ] [[package]] name = "pytest-cov" -version = "6.1.1" +version = "6.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "coverage", extra = ["toml"] }, + { name = "pluggy" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/25/69/5f1e57f6c5a39f81411b550027bf72842c4567ff5fd572bed1edc9e4b5d9/pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a", size = 66857, upload-time = "2025-04-05T14:07:51.592Z" } +sdist = { url = "https://files.pythonhosted.org/packages/18/99/668cade231f434aaa59bbfbf49469068d2ddd945000621d3d165d2e7dd7b/pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2", size = 69432, upload-time = "2025-06-12T10:47:47.684Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/28/d0/def53b4a790cfb21483016430ed828f64830dd981ebe1089971cd10cab25/pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde", size = 23841, upload-time = "2025-04-05T14:07:49.641Z" }, + { url = "https://files.pythonhosted.org/packages/bc/16/4ea354101abb1287856baa4af2732be351c7bee728065aed451b678153fd/pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5", size = 24644, upload-time = "2025-06-12T10:47:45.932Z" }, ] [[package]] @@ -278,9 +279,9 @@ wheels = [ [[package]] name = "typing-extensions" -version = "4.14.0" +version = "4.14.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d1/bc/51647cd02527e87d05cb083ccc402f93e441606ff1f01739a62c8ad09ba5/typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", size = 107423, upload-time = "2025-06-02T14:52:11.399Z" } +sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af", size = 43839, upload-time = "2025-06-02T14:52:10.026Z" }, + { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, ]