diff --git a/docs/api/polymorphic.managers.rst b/docs/api/polymorphic.managers.rst index d7cc448..a45cb07 100644 --- a/docs/api/polymorphic.managers.rst +++ b/docs/api/polymorphic.managers.rst @@ -19,3 +19,36 @@ The ``PolymorphicQuerySet`` class .. autoclass:: polymorphic.managers.PolymorphicQuerySet :members: :show-inheritance: + + +.. _type_hint_descriptors: + +Type Hint Descriptors +--------------------- + +.. autodata:: polymorphic.managers._All +.. autodata:: polymorphic.managers._Base +.. autodata:: polymorphic.managers._Through +.. autodata:: polymorphic.managers._Nullable + +.. autodata:: polymorphic.managers.Nullable + +.. autoclass:: polymorphic.managers.PolymorphicForwardManyToOneDescriptor + :members: + :show-inheritance: + +.. autoclass:: polymorphic.managers.PolymorphicReverseManyToOneDescriptor + :members: + :show-inheritance: + +.. autoclass:: polymorphic.managers.PolymorphicForwardOneToOneDescriptor + :members: + :show-inheritance: + +.. autoclass:: polymorphic.managers.PolymorphicReverseOneToOneDescriptor + :members: + :show-inheritance: + +.. autoclass:: polymorphic.managers.PolymorphicManyToManyDescriptor + :members: + :show-inheritance: diff --git a/docs/conf.py b/docs/conf.py index f530f30..c667b25 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -282,6 +282,7 @@ intersphinx_mapping = { "https://docs.djangoproject.com/en/stable/_objects/", ), "python": ("https://docs.python.org/3", None), + "typing-extensions": ("https://typing-extensions.readthedocs.io/en/latest/", None), "django-guardian": ("https://django-guardian.readthedocs.io/en/stable", None), "django-reversion": ("https://django-reversion.readthedocs.io/en/stable", None), "django-extra-views": ("https://django-extra-views.readthedocs.io/en/stable", None), diff --git a/docs/typing.rst b/docs/typing.rst index e619600..4747a71 100644 --- a/docs/typing.rst +++ b/docs/typing.rst @@ -4,12 +4,14 @@ Type Hints .. versionadded:: 4.11 :pypi:`django-polymorphic` is now fully typed, and ships with type hints for all public -APIs. Typing is checked against :pypi:`mypy` and :pypi:`pyright` in the CI pipeline. +APIs. Typing is checked with :pypi:`mypy` and :pypi:`pyright` in the CI pipeline. -The utility and power of the Django ORM derives from its dynamism. This however makes static type -checking more difficult. To use the type hints effectively, it is recommended to use -:pypi:`django-stubs` and :pypi:`django-stubs-ext` which provides type hints for the Django ORM -itself.` +The utility and power of the Django ORM derives from its dynamism but this makes static typing +more difficult. There are *no additional runtime dependencies* but **to use the packaged type hint +classes and descriptors, you must install the following in your type checking context**: + +* :pypi:`django-stubs` (required) +* :pypi:`django-stubs-ext` (optional) .. tip:: @@ -20,6 +22,80 @@ itself.` Correct type hints for polymorphic managers and querysets cannot be automatically inferred - you will have to add them explicitly if you want them: -.. literalinclude:: ../src/polymorphic/tests/examples/type_hints/models.py +.. _typing_managers: + +Managers +-------- + +You can type your managers like this. It might not always be the case that you can add hints for all +child model types, especially if they are included in dependent apps. You can alleviate some of this +complexity with forward type references but strict typing may not always be appropriate. + +.. literalinclude:: ../src/polymorphic/tests/examples/type_hints/managers/models.py + :language: python + :linenos: + + +.. code-block:: python + + ParentModel.objects.all() # type: PolymorphicQuerySet[ParentModel | Child1 | Child2] + ParentModel.objects.instance_of(Child1) # type: PolymorphicQuerySet[Child1] + Child1.objects.non_polymorphic() # type: QuerySet[Child1] + +.. _typing_foreign_key: + +Foreign Key +----------- + +:pypi:`django-polymorphic` includes several :ref:`type hint descriptors `. +You can use them to type your forward and reverse relationship fields. For foreign key relationships +we provide :class:`~polymorphic.managers.PolymorphicForwardManyToOneDescriptor` and +:class:`~polymorphic.managers.PolymorphicReverseManyToOneDescriptor`: + +.. literalinclude:: ../src/polymorphic/tests/examples/type_hints/fk/models.py + :language: python + :linenos: + +.. code-block:: python + + RelatedModel().parent: Optional[ParentModel | Child1 | Child2] + RelatedModel().children.all(): PolymorphicQuerySet[ParentModel | Child1 | Child2] + +.. _typing_one_to_one: + +One to One +---------- +For foreign key relationships +we provide :class:`~polymorphic.managers.PolymorphicForwardOneToOneDescriptor` and +:class:`~polymorphic.managers.PolymorphicReverseOneToOneDescriptor`: + +.. literalinclude:: ../src/polymorphic/tests/examples/type_hints/one2one/models.py + :language: python + :linenos: + +.. code-block:: python + + RelatedModel().parent_forward: ParentModel | Child1 | Child2 | None + RelatedModel().parent_reverse: ParentModel | Child1 | Child2 + +.. _typing_many_to_many: + +Many to Many +------------ + +You can use the same :class:`~polymorphic.managers.PolymorphicManyToManyDescriptor` for +both forward and reverse :class:`~django.db.models.ManyToManyField` relationships. + +The following example shows two :class:`~django.db.models.ManyToManyField` relationships: + +1. :class:`~polymorphic.models.PolymorphicModel` -> :class:`~django.db.models.Model` + (with a custom through model) +2. :class:`~django.db.models.Model` -> :class:`~polymorphic.models.PolymorphicModel` + (with the default through model) + +For the custom through model you will need to annotate using the :ref:`foreign key descriptors +` as well. + +.. literalinclude:: ../src/polymorphic/tests/examples/type_hints/m2m/models.py :language: python :linenos: diff --git a/justfile b/justfile index 34cb793..5c302d5 100644 --- a/justfile +++ b/justfile @@ -37,12 +37,12 @@ setup python="python": # install git pre-commit hooks install-precommit: - @just run --no-default-groups --group precommit --exact pre-commit install + @just run --no-default-groups --group precommit --exact --isolated pre-commit install # update and install development dependencies install *OPTS: - uv sync {{ OPTS }} @just install-precommit + uv sync {{ OPTS }} # install playwright dependencies install-playwright: @@ -113,6 +113,7 @@ remake-test-migrations: - rm src/polymorphic/tests/other/migrations/00*.py - rm src/polymorphic/tests/examples/**/migrations/00*.py - rm src/polymorphic/tests/examples/integrations/**/migrations/00*.py + - rm src/polymorphic/tests/examples/type_hints/**/migrations/00*.py uv run --no-default-groups --exact --isolated --resolution lowest-direct --group integrations --script ./manage.py makemigrations # open the html documentation diff --git a/pyproject.toml b/pyproject.toml index 8add622..1de24cb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -206,7 +206,7 @@ typing = [ "django-stubs-ext>=5.1.1", "djangorestframework-stubs>=3.16.1", "pyright>=1.1.390", - "mypy>=1.14.1", + "mypy>=1.19.1", ] lint = [ "ruff>=0.9.8", diff --git a/src/polymorphic/base.py b/src/polymorphic/base.py index 5de2511..f86e14d 100644 --- a/src/polymorphic/base.py +++ b/src/polymorphic/base.py @@ -52,7 +52,7 @@ def polymorphic_base_manager(self): and mgr.__class__ is models.Manager and mgr.auto_created ): - manager = PolymorphicManager() + manager: PolymorphicManager = PolymorphicManager() manager.name = "_base_manager" manager.model = self.model manager.auto_created = True diff --git a/src/polymorphic/managers.py b/src/polymorphic/managers.py index 54c9730..488f97a 100644 --- a/src/polymorphic/managers.py +++ b/src/polymorphic/managers.py @@ -5,11 +5,18 @@ The manager class for use in the models. from __future__ import annotations from collections.abc import Iterable -from typing import TYPE_CHECKING, Any, cast, overload +from typing import TYPE_CHECKING, Any, Generic, Literal, TypeAlias, cast, overload from django.contrib.contenttypes.models import ContentType from django.db import DEFAULT_DB_ALIAS, models, transaction -from typing_extensions import Generic, Self, TypeVar +from django.db.models.fields.related_descriptors import ( + ForwardManyToOneDescriptor, + ForwardOneToOneDescriptor, + ManyToManyDescriptor, + ReverseManyToOneDescriptor, + ReverseOneToOneDescriptor, +) +from typing_extensions import Self, TypeVar from polymorphic.query import PolymorphicQuerySet @@ -17,11 +24,43 @@ if TYPE_CHECKING: from .models import PolymorphicModel # noqa: F401 -__all__ = ["PolymorphicManager", "PolymorphicQuerySet"] +__all__ = [ + "PolymorphicManager", + "PolymorphicQuerySet", + "PolymorphicManyToManyDescriptor", + "PolymorphicReverseManyToOneDescriptor", + "PolymorphicForwardManyToOneDescriptor", + "PolymorphicForwardOneToOneDescriptor", + "PolymorphicReverseOneToOneDescriptor", + "Nullable", +] -_All = TypeVar("_All", bound="PolymorphicModel", default="PolymorphicModel", covariant=True) -_Base = TypeVar("_Base", bound="PolymorphicModel", default=_All, covariant=True) -_Through = TypeVar("_Through", bound="PolymorphicModel", default=_Base, covariant=True) +_All = TypeVar("_All", bound="PolymorphicModel", covariant=True) +""" +This :class:`~typing.TypeVar` represents the union of all possible polymorphic types +that a manager may return. All models must derive from +:class:`~polymorphic.models.PolymorphicModel` +""" + +_Base = TypeVar("_Base", bound="PolymorphicModel", default="PolymorphicModel", covariant=True) +""" +This :class:`~typing.TypeVar` represents the base model type from which polymorphic +models derive. For managers on a :class:`~polymorphic.models.PolymorphicModel` subclass, +you will likely want to use :data:`~typing.Self`. +""" + +_Through = TypeVar("_Through", bound=models.Model, default=models.Model, covariant=True) +""" +This :class:`~typing.TypeVar` represents the "through" model type for many-to-many +relations. By default it is just a regular :class:`~django.db.models.Model`, which +will lack the foreign key relations to the linked models. +""" + +_Nullable = TypeVar("_Nullable", Literal[True], Literal[False], default=Literal[False]) +""" +Provided for nullable relations - should be set to ``Literal[True]`` if the relation is +nullable, otherwise can be left as the default ``Literal[False]``. +""" _A = TypeVar("_A", bound="PolymorphicModel") _B = TypeVar("_B", bound="PolymorphicModel") @@ -29,6 +68,10 @@ _C = TypeVar("_C", bound="PolymorphicModel") _D = TypeVar("_D", bound="PolymorphicModel") +Nullable: TypeAlias = Literal[True] +"""A more readable type hint alias to indicate that a relation is nullable.""" + + class PolymorphicManager(models.Manager[_All], Generic[_All, _Base]): """ Manager for PolymorphicModel @@ -42,10 +85,11 @@ class PolymorphicManager(models.Manager[_All], Generic[_All, _Base]): if TYPE_CHECKING: def all(self) -> PolymorphicQuerySet[_All, _Base]: ... + def filter(self, *args: Any, **kwargs: Any) -> PolymorphicQuerySet[_All, _Base]: ... @classmethod def from_queryset( - cls, queryset_class: type[models.query.QuerySet[_All, _All]], class_name: str | None = None + cls, queryset_class: type[models.query.QuerySet[_All]], class_name: str | None = None ) -> type[Self]: manager = super().from_queryset(queryset_class, class_name=class_name) # also set our version, Django uses _queryset_class @@ -100,7 +144,7 @@ class PolymorphicManager(models.Manager[_All], Generic[_All, _Base]): def get_real_instances(self, base_result_objects: Iterable[_All] | None = None) -> list[_All]: return self.all().get_real_instances(base_result_objects=base_result_objects) - def create_from_super(self, obj: models.Model, **kwargs: Any) -> _All: + def create_from_super(self, obj: models.Model, **kwargs: Any) -> _Base: """ Create an instance of this manager's model class from the given instance of a parent class. @@ -130,7 +174,7 @@ class PolymorphicManager(models.Manager[_All], Generic[_All, _Base]): ctype = ContentType.objects.db_manager( using=(obj._state.db or DEFAULT_DB_ALIAS) ).get_for_model(self.model) - nobj = self.model(**kwargs, polymorphic_ctype=ctype) + nobj: _Base = self.model(**kwargs, polymorphic_ctype=ctype) # type: ignore[assignment] nobj.save_base(raw=True, using=obj._state.db or DEFAULT_DB_ALIAS, force_insert=True) # force update the content type, but first we need to # retrieve a clean copy from the db to fill in the null @@ -149,31 +193,256 @@ class PolymorphicManager(models.Manager[_All], Generic[_All, _Base]): if TYPE_CHECKING: from django.db.models.fields.related_descriptors import ( ManyRelatedManager, - ManyToManyDescriptor, RelatedManager, ) class PolymorphicManyRelatedManager( # type: ignore[type-var] + PolymorphicManager[_All, _Base], ManyRelatedManager[_All, _Through], # pyright: ignore[reportInvalidTypeArguments] + Generic[_All, _Base, _Through], + ): ... + + class PolymorphicRelatedManager( # type: ignore[type-var] + PolymorphicManager[_All, _Base], + RelatedManager[_All], # pyright: ignore[reportInvalidTypeArguments] + Generic[_All, _Base], + ): ... + +else: + + class PolymorphicRelatedManager(PolymorphicManager[_All, _Base], Generic[_All, _Base]): ... + + class PolymorphicManyRelatedManager( PolymorphicManager[_All, _Base], Generic[_All, _Base, _Through], ): ... - class PolymorphicManyToManyDescriptor(ManyToManyDescriptor, Generic[_All, _Base, _Through]): - @overload # type: ignore[override] - def __get__(self, instance: None, cls: Any | None = None, /) -> Self: ... - @overload - def __get__( - self, instance: models.Model, cls: Any | None = None, / - ) -> PolymorphicManyRelatedManager[_All, _Base, _Through]: ... +class PolymorphicManyToManyDescriptor(ManyToManyDescriptor, Generic[_All, _Base, _Through]): + """ + Use this descriptor class as a type hint for your forward and reverse + :class:`~django.db.models.ManyToManyField` relations to/from polymorphic models. + For example: - def __get__( - self, instance: models.Model | None, cls: Any | None = None, / - ) -> Self | PolymorphicManyRelatedManager[_All, _Base, _Through]: ... + .. code-block:: python - class PolymorphicRelatedManager( # type: ignore[type-var] - RelatedManager[_All], # pyright: ignore[reportInvalidTypeArguments] - PolymorphicManager[_All, _Base], - Generic[_All, _Base], - ): ... + to_parents: PolymorphicManyToManyDescriptor[ + ParentModel | Child1 | Child2, # all possible polymorphic types + ParentModel, # the base type (for non_polymorphic) + ThroughModel # if custom through model + ] = models.ManyToManyField( # type: ignore[assignment] + "ParentModel", + related_name="to_parents_reverse" + ) + + """ + + @overload # type: ignore[override] + def __get__(self, instance: None, cls: Any | None = None, /) -> Self: ... + + @overload + def __get__( + self, instance: models.Model, cls: Any | None = None, / + ) -> PolymorphicManyRelatedManager[_All, _Base, _Through]: ... + + def __get__( + self, instance: models.Model | None, cls: Any | None = None, / + ) -> Self | PolymorphicManyRelatedManager[_All, _Base, _Through]: + return cast( # pragma: no cover + Self | PolymorphicManyRelatedManager[_All, _Base, _Through], + super().__get__(instance, cls), + ) + + +class PolymorphicReverseManyToOneDescriptor( + ReverseManyToOneDescriptor, + Generic[_All, _Base], +): + """ + Use this descriptor class as a type hint for your reverse + :class:`~django.db.models.ForeignKey` relations to polymorphic models. For example: + + + .. code-block:: python + + class ParentModel(PolymorphicModel): + models.ForeignKey( + RelatedModel, + on_delete=models.CASCADE, + null=True, + related_name="reverse" + ) + + class RelatedModel(models.Model): + + reverse: PolymorphicReverseManyToOneDescriptor[ + ParentModel | Child1 | Child2, + ParentModel + ] + """ + + @overload + def __get__(self, instance: None, cls: Any | None = None, /) -> Self: ... + + @overload + def __get__( + self, instance: models.Model, cls: Any | None = None, / + ) -> PolymorphicRelatedManager[_All, _Base]: ... + + def __get__( + self, instance: models.Model | None, cls: Any | None = None, / + ) -> Self | PolymorphicRelatedManager[_All, _Base]: + return cast( # pragma: no cover + Self | PolymorphicRelatedManager[_All, _Base], super().__get__(instance, cls) + ) + + +class PolymorphicForwardManyToOneDescriptor( + ForwardManyToOneDescriptor, + Generic[_All, _Base, _Nullable], +): + """ + Use this descriptor class as a type hint for your + :class:`~django.db.models.ForeignKey` relations to polymorphic models. For example: + + .. note:: + + Your typing system will likely flag an assignment error on the class attribute + - this is unfortunate but unavoidable - we suggest you add a + `# type: ignore[assignment]`. + + .. code-block:: python + + parent: PolymorphicForwardManyToOneDescriptor[ + ParentModel | Child1 | Child2, + ParentModel, + Nullable, + ] = models.ForeignKey( + ParentModel, + on_delete=models.CASCADE, + null=True, + ) + """ + + @overload # type: ignore[override] + def __get__(self, instance: None, cls: Any | None = None, /) -> Self: ... + + @overload + def __get__( + self: PolymorphicForwardManyToOneDescriptor[_All, _Base, Literal[False]], + instance: models.Model, + cls: Any | None = None, + /, + ) -> _All: ... + + @overload + def __get__( + self: PolymorphicForwardManyToOneDescriptor[_All, _Base, Literal[True]], + instance: models.Model, + cls: Any | None = None, + /, + ) -> _All | None: ... + + def __get__( + self, instance: models.Model | None, cls: Any | None = None, / + ) -> Self | _All | None: + return cast( # pragma: no cover + Self | _All | None, super().__get__(instance, cls) + ) + + def get_queryset(self, **hints: Any) -> PolymorphicQuerySet[_All, _Base]: + return cast( # pragma: no cover + PolymorphicQuerySet[_All, _Base], + super().get_queryset(**hints), + ) + + +class PolymorphicForwardOneToOneDescriptor( + ForwardOneToOneDescriptor, + PolymorphicForwardManyToOneDescriptor[_All, _Base, _Nullable], + Generic[_All, _Base, _Nullable], +): + """ + Use this descriptor class as a type hint for your + :class:`~django.db.models.OneToOneField` relations to polymorphic models. For + example: + + .. note:: + + Your typing system will likely flag an assignment error on the class attribute + - this is unfortunate but unavoidable - we suggest you add a + `# type: ignore[assignment]`. + + .. code-block:: python + + parent: PolymorphicForwardOneToOneDescriptor[ + ParentModel | Child1 | Child2, + ParentModel, + Nullable, + ] = models.OneToOneField( + ParentModel, + on_delete=models.CASCADE, + null=True, + ) + """ + + +class PolymorphicReverseOneToOneDescriptor( + ReverseOneToOneDescriptor, + Generic[_All, _Base, _Nullable], +): + """ + Use this descriptor class as a type hint for your reverse + :class:`~django.db.models.OneToOneField` relations to polymorphic models. For + example: + + .. code-block:: python + + class ParentModel(PolymorphicModel): + models.OneToOneField( + RelatedModel, + on_delete=models.CASCADE, + null=True, + related_name="reverse" + ) + + class RelatedModel(models.Model): + + reverse: PolymorphicReverseOneToOneDescriptor[ + ParentModel | Child1 | Child2, + ParentModel, + Nullable, + ] + """ + + @overload + def __get__(self, instance: None, cls: Any | None = None, /) -> Self: ... + + @overload + def __get__( + self: PolymorphicReverseOneToOneDescriptor[_All, _Base, Literal[False]], + instance: models.Model, + cls: Any | None = None, + /, + ) -> _All: ... + + @overload + def __get__( + self: PolymorphicReverseOneToOneDescriptor[_All, _Base, Literal[True]], + instance: models.Model, + cls: Any | None = None, + /, + ) -> _All | None: ... + + def __get__( + self, instance: models.Model | None, cls: Any | None = None, / + ) -> Self | _All | None: + return cast( # pragma: no cover + Self | _All | None, super().__get__(instance, cls) + ) + + def get_queryset(self, **hints: Any) -> PolymorphicQuerySet[_All, _Base]: + return cast( # pragma: no cover + PolymorphicQuerySet[_All, _Base], + super().get_queryset(**hints), + ) diff --git a/src/polymorphic/query.py b/src/polymorphic/query.py index e4c90df..ffb2268 100644 --- a/src/polymorphic/query.py +++ b/src/polymorphic/query.py @@ -30,7 +30,7 @@ if TYPE_CHECKING: from .models import PolymorphicModel # noqa: F401 _All = TypeVar("_All", bound="PolymorphicModel", covariant=True) -_Base = TypeVar("_Base", bound="PolymorphicModel", covariant=True) +_Base = TypeVar("_Base", bound="PolymorphicModel", default="PolymorphicModel", covariant=True) _A = TypeVar("_A", bound="PolymorphicModel") _B = TypeVar("_B", bound="PolymorphicModel") @@ -143,7 +143,7 @@ def transmogrify(cls: type[_All], obj: models.Model) -> _All: # PolymorphicQuerySet -class PolymorphicQuerySet(QuerySet[_All, _All], Generic[_All, _Base]): +class PolymorphicQuerySet(QuerySet[_All], Generic[_All, _Base]): """ QuerySet for PolymorphicModel @@ -187,7 +187,7 @@ class PolymorphicQuerySet(QuerySet[_All, _All], Generic[_All, _Base]): from .managers import PolymorphicManager - manager = PolymorphicManager[_All].from_queryset(cls)() + manager = PolymorphicManager[_All, _Base].from_queryset(cls)() setattr(manager, "_built_with_as_manager", True) return manager diff --git a/src/polymorphic/tests/deletion/migrations/0001_initial.py b/src/polymorphic/tests/deletion/migrations/0001_initial.py index da2520d..ea1285a 100644 --- a/src/polymorphic/tests/deletion/migrations/0001_initial.py +++ b/src/polymorphic/tests/deletion/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2 on 2026-02-02 17:01 +# Generated by Django 4.2 on 2026-02-05 14:47 from decimal import Decimal from django.conf import settings diff --git a/src/polymorphic/tests/examples/integrations/drf/migrations/0001_initial.py b/src/polymorphic/tests/examples/integrations/drf/migrations/0001_initial.py index 361fe8d..6412211 100644 --- a/src/polymorphic/tests/examples/integrations/drf/migrations/0001_initial.py +++ b/src/polymorphic/tests/examples/integrations/drf/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2 on 2026-02-02 17:01 +# Generated by Django 4.2 on 2026-02-05 14:47 from django.conf import settings from django.db import migrations, models diff --git a/src/polymorphic/tests/examples/integrations/migrations/0001_initial.py b/src/polymorphic/tests/examples/integrations/migrations/0001_initial.py index 78ec627..dd9f99c 100644 --- a/src/polymorphic/tests/examples/integrations/migrations/0001_initial.py +++ b/src/polymorphic/tests/examples/integrations/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2 on 2026-02-02 17:01 +# Generated by Django 4.2 on 2026-02-05 14:47 from django.db import migrations, models import django.db.models.deletion diff --git a/src/polymorphic/tests/examples/type_hints/apps.py b/src/polymorphic/tests/examples/type_hints/apps.py deleted file mode 100644 index 04ef313..0000000 --- a/src/polymorphic/tests/examples/type_hints/apps.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.apps import AppConfig - - -class TypeHintsConfig(AppConfig): - name = "polymorphic.tests.examples.type_hints" - label = "type_hints" - verbose_name = "Type Hints Examples" diff --git a/src/polymorphic/tests/examples/type_hints/migrations/__init__.py b/src/polymorphic/tests/examples/type_hints/fk/__init__.py similarity index 100% rename from src/polymorphic/tests/examples/type_hints/migrations/__init__.py rename to src/polymorphic/tests/examples/type_hints/fk/__init__.py diff --git a/src/polymorphic/tests/examples/type_hints/fk/apps.py b/src/polymorphic/tests/examples/type_hints/fk/apps.py new file mode 100644 index 0000000..2e5d980 --- /dev/null +++ b/src/polymorphic/tests/examples/type_hints/fk/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class TypeHintsFKConfig(AppConfig): + name = "polymorphic.tests.examples.type_hints.fk" + label = "type_hints_fk" + verbose_name = "Type Hints Foreign Key Example" diff --git a/src/polymorphic/tests/examples/type_hints/fk/migrations/0001_initial.py b/src/polymorphic/tests/examples/type_hints/fk/migrations/0001_initial.py new file mode 100644 index 0000000..690b28b --- /dev/null +++ b/src/polymorphic/tests/examples/type_hints/fk/migrations/0001_initial.py @@ -0,0 +1,58 @@ +# Generated by Django 4.2 on 2026-02-05 14:47 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ] + + operations = [ + migrations.CreateModel( + name='ParentModel', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Child1', + fields=[ + ('parentmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='type_hints_fk.parentmodel')), + ], + options={ + 'abstract': False, + }, + bases=('type_hints_fk.parentmodel',), + ), + migrations.CreateModel( + name='RelatedModel', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('parent', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='type_hints_fk.parentmodel')), + ], + ), + migrations.AddField( + model_name='parentmodel', + name='related_forward', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='parents_reverse', to='type_hints_fk.relatedmodel'), + ), + migrations.CreateModel( + name='Child2', + fields=[ + ('child1_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='type_hints_fk.child1')), + ], + options={ + 'abstract': False, + }, + bases=('type_hints_fk.child1',), + ), + ] diff --git a/src/polymorphic/tests/examples/type_hints/fk/migrations/__init__.py b/src/polymorphic/tests/examples/type_hints/fk/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/polymorphic/tests/examples/type_hints/fk/models.py b/src/polymorphic/tests/examples/type_hints/fk/models.py new file mode 100644 index 0000000..56cfc0c --- /dev/null +++ b/src/polymorphic/tests/examples/type_hints/fk/models.py @@ -0,0 +1,47 @@ +from django.db import models +from polymorphic.models import PolymorphicModel +from polymorphic.managers import ( + PolymorphicForwardManyToOneDescriptor, + PolymorphicReverseManyToOneDescriptor, + Nullable, # Alias for typing.Literal[True] +) + + +class ParentModel(PolymorphicModel): + related_forward = models.ForeignKey( + "RelatedModel", on_delete=models.CASCADE, related_name="parents_reverse" + ) + + +class Child1(ParentModel): + pass + + +class Child2(Child1): + pass + + +class RelatedModel(models.Model): + # fmt: off + # Foreign Key Descriptor Type Hint: + # 1. Class Attribute: ForwardManyToOneDescriptor + # 2. Instance attribute: Union of all listed model types or None + parent: PolymorphicForwardManyToOneDescriptor[ + ParentModel | Child1 | Child2, # all possible polymorphic types + ParentModel, # the base type (for non_polymorphic) + Nullable, # when null=True + ] = models.ForeignKey( # type: ignore[assignment] + ParentModel, + on_delete=models.CASCADE, + null=True, + ) + + # Reverse FK Relation Descriptor Type Hint: + # 1. Class Attribute: ReverseManyToOneDescriptor + # 2. Instance attribute: Union of all listed model types + parents_reverse: PolymorphicReverseManyToOneDescriptor[ + ParentModel | Child1 | Child2, # all possible polymorphic types + ParentModel, # the base type (for non_polymorphic) + # nullable defaults to False + ] + # fmt: on diff --git a/src/polymorphic/tests/examples/type_hints/fk/test.py b/src/polymorphic/tests/examples/type_hints/fk/test.py new file mode 100644 index 0000000..1d3757b --- /dev/null +++ b/src/polymorphic/tests/examples/type_hints/fk/test.py @@ -0,0 +1,41 @@ +import typing as t +from typing_extensions import assert_type +from django.test import TestCase +from .models import ParentModel, Child1, Child2, RelatedModel + + +class TypeHintsFKTest(TestCase): + def test_type_hints(self): + related = RelatedModel.objects.create() + parent = ParentModel.objects.create(related_forward=related) + child1 = Child1.objects.create(related_forward=related) + child2 = Child2.objects.create(related_forward=related) + + assert_type(related.parent, t.Optional[ParentModel | Child1 | Child2]) + + related.parent = child1 + related.save() + + if t.TYPE_CHECKING: + from django.db.models.fields.related import ForeignKey + from django.db.models.fields.reverse_related import ManyToOneRel + + assert_type( + RelatedModel.parents_reverse.field, ForeignKey[t.Any, t.Any] + ) + assert_type(RelatedModel.parents_reverse.rel, ManyToOneRel) + + _1: t.Optional[ParentModel | Child1 | Child2] = ( + related.parents_reverse.first() + ) + assert _1 == parent + + _2: t.Optional[ParentModel | Child1 | Child2] = ( + related.parents_reverse.filter().first() + ) + assert _2 == parent + + _3: t.Optional[ParentModel] = ( + related.parents_reverse.non_polymorphic().last() + ) + assert _3 == ParentModel.objects.non_polymorphic().get(pk=child2.pk) diff --git a/src/polymorphic/tests/examples/type_hints/m2m/__init__.py b/src/polymorphic/tests/examples/type_hints/m2m/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/polymorphic/tests/examples/type_hints/m2m/apps.py b/src/polymorphic/tests/examples/type_hints/m2m/apps.py new file mode 100644 index 0000000..80fbbf9 --- /dev/null +++ b/src/polymorphic/tests/examples/type_hints/m2m/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class TypeHintsM2MConfig(AppConfig): + name = "polymorphic.tests.examples.type_hints.m2m" + label = "type_hints_m2m" + verbose_name = "Type Hints M2M Example" diff --git a/src/polymorphic/tests/examples/type_hints/m2m/migrations/0001_initial.py b/src/polymorphic/tests/examples/type_hints/m2m/migrations/0001_initial.py new file mode 100644 index 0000000..7bf8626 --- /dev/null +++ b/src/polymorphic/tests/examples/type_hints/m2m/migrations/0001_initial.py @@ -0,0 +1,84 @@ +# Generated by Django 4.2 on 2026-02-05 14:47 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ] + + operations = [ + migrations.CreateModel( + name='ParentModel', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='PolyThrough', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('parent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='parents', to='type_hints_m2m.parentmodel')), + ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Child1', + fields=[ + ('parentmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='type_hints_m2m.parentmodel')), + ], + options={ + 'abstract': False, + }, + bases=('type_hints_m2m.parentmodel',), + ), + migrations.CreateModel( + name='ThroughChild', + fields=[ + ('polythrough_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='type_hints_m2m.polythrough')), + ], + options={ + 'abstract': False, + }, + bases=('type_hints_m2m.polythrough',), + ), + migrations.CreateModel( + name='RelatedModel', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('to_parents', models.ManyToManyField(related_name='to_parents_reverse', to='type_hints_m2m.parentmodel')), + ], + ), + migrations.AddField( + model_name='polythrough', + name='related', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='type_hints_m2m.relatedmodel'), + ), + migrations.AddField( + model_name='parentmodel', + name='to_related', + field=models.ManyToManyField(related_name='to_related_reverse', through='type_hints_m2m.PolyThrough', to='type_hints_m2m.relatedmodel'), + ), + migrations.CreateModel( + name='Child2', + fields=[ + ('child1_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='type_hints_m2m.child1')), + ], + options={ + 'abstract': False, + }, + bases=('type_hints_m2m.child1',), + ), + ] diff --git a/src/polymorphic/tests/examples/type_hints/m2m/migrations/__init__.py b/src/polymorphic/tests/examples/type_hints/m2m/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/polymorphic/tests/examples/type_hints/m2m/models.py b/src/polymorphic/tests/examples/type_hints/m2m/models.py new file mode 100644 index 0000000..f9655f7 --- /dev/null +++ b/src/polymorphic/tests/examples/type_hints/m2m/models.py @@ -0,0 +1,75 @@ +from __future__ import annotations +from typing import ClassVar +from django.db import models +from polymorphic.models import PolymorphicModel +from polymorphic.managers import ( + PolymorphicManager, + PolymorphicManyToManyDescriptor, + PolymorphicForwardManyToOneDescriptor, + PolymorphicReverseManyToOneDescriptor, +) + + +class ParentModel(PolymorphicModel): + to_related = models.ManyToManyField( # type: ignore[var-annotated] + "RelatedModel", related_name="to_related_reverse", through="PolyThrough" + ) + + parents: PolymorphicReverseManyToOneDescriptor[ + ParentModel | Child1 | Child2, ParentModel + ] + + objects: ClassVar[ + PolymorphicManager[ParentModel | Child1 | Child2, ParentModel] + ] + + +class Child1(ParentModel): + pass + + +class Child2(Child1): + pass + + +class PolyThrough(PolymorphicModel): + parent: PolymorphicForwardManyToOneDescriptor[ + ParentModel | Child1 | Child2, ParentModel + ] = models.ForeignKey( # type: ignore[assignment] + ParentModel, on_delete=models.CASCADE, related_name="parents" + ) + + related = models.ForeignKey("RelatedModel", on_delete=models.CASCADE) + + objects: ClassVar[ + PolymorphicManager[PolyThrough | ThroughChild, PolyThrough] + ] + + +class ThroughChild(PolyThrough): + pass + + +class RelatedModel(models.Model): + # fmt: off + # ManyToMany Descriptor Type Hint: + # 1. Class Attribute: ManyToManyDescriptor + # 2. Instance attribute: PolymorphicManager for related type(s) + to_parents: PolymorphicManyToManyDescriptor[ + ParentModel | Child1 | Child2, # all possible polymorphic types + ParentModel, # the base type (for non_polymorphic) + # no custom through model + ] = models.ManyToManyField( # type: ignore[assignment] + "ParentModel", + related_name="to_parents_reverse" + ) + + # ManyToMany Descriptor for the reverse relation + # 1. Class Attribute: ManyToManyDescriptor + # 2. Instance attribute: PolymorphicManager for related type(s) + to_related_reverse: PolymorphicManyToManyDescriptor[ + ParentModel | Child1 | Child2, + ParentModel, + PolyThrough # custom through model (may be polymorphic!) + ] + # fmt: on diff --git a/src/polymorphic/tests/examples/type_hints/m2m/test.py b/src/polymorphic/tests/examples/type_hints/m2m/test.py new file mode 100644 index 0000000..7f5edbc --- /dev/null +++ b/src/polymorphic/tests/examples/type_hints/m2m/test.py @@ -0,0 +1,89 @@ +import typing as t +from django.test import TestCase +from django.db import models + +# from django.db import models +from .models import ( + ParentModel, + Child1, + Child2, + RelatedModel, + PolyThrough, + ThroughChild, +) + + +class TypeHintsM2MTest(TestCase): + def test_type_hints(self): + parent = ParentModel.objects.create() + child1 = Child1.objects.create() + child2 = Child2.objects.create() + related1 = RelatedModel.objects.create() + related2 = RelatedModel.objects.create() + + _t1 = PolyThrough.objects.create(parent=child1, related=related1) + + _t2 = ThroughChild.objects.create(parent=child2, related=related2) + + related1.to_parents.add(parent) + related1.to_related_reverse.add(child2) + related2.to_parents.add(child2) + + related1.refresh_from_db() + assert set(related1.to_parents.all()) == {parent} + assert set(related2.to_parents.all()) == {child2} + assert set(related1.to_related_reverse.all()) == {child1, child2} + assert set(related2.to_related_reverse.all()) == {child2} + + assert parent.to_related.count() == 0 + assert set(child1.to_related.all()) == {related1} + assert set(child2.to_related.all()) == {related1, related2} + + through1: type[PolyThrough] = parent.to_related.through + assert through1.objects.count() == 3 + + tlist1: t.List[PolyThrough | ThroughChild] = list( + parent.to_related.through.objects.all() + ) + assert len(tlist1) == 3 + + _1: t.List[PolyThrough] = list( + parent.to_related.through.objects.non_polymorphic() + ) + assert len(_1) == 3 + + _2: t.List[ParentModel | Child1 | Child2] = list( + related1.to_related_reverse.all() + ) + assert set(_2) == {child1, child2} + + _3: t.List[ParentModel | Child1 | Child2] = list( + related1.to_parents.all() + ) + + assert set(_3) == {parent} + + _through2: type[models.Model] = related1.to_parents.through + _through3: type[PolyThrough] = related1.to_related_reverse.through + + assert _through2.objects.count() == 2 # type: ignore[attr-defined] + + assert set(_through3.objects.all()) == { + _t1, + _t2, + PolyThrough.objects.last(), + } + + _4: t.List[ParentModel] = list( + related1.to_related_reverse.non_polymorphic() + ) + assert set(_4) == set( + ParentModel.objects.non_polymorphic().filter( + pk__in=[child1.pk, child2.pk] + ) + ) + + _5: t.List[ParentModel] = list( + related1.to_parents.all().non_polymorphic() + ) + assert set(_5) == {parent} diff --git a/src/polymorphic/tests/examples/type_hints/managers/__init__.py b/src/polymorphic/tests/examples/type_hints/managers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/polymorphic/tests/examples/type_hints/managers/apps.py b/src/polymorphic/tests/examples/type_hints/managers/apps.py new file mode 100644 index 0000000..8018094 --- /dev/null +++ b/src/polymorphic/tests/examples/type_hints/managers/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class TypeHintsManagersConfig(AppConfig): + name = "polymorphic.tests.examples.type_hints.managers" + label = "type_hints_managers" + verbose_name = "Type Hints Managers Example" diff --git a/src/polymorphic/tests/examples/type_hints/managers/migrations/0001_initial.py b/src/polymorphic/tests/examples/type_hints/managers/migrations/0001_initial.py new file mode 100644 index 0000000..6abc070 --- /dev/null +++ b/src/polymorphic/tests/examples/type_hints/managers/migrations/0001_initial.py @@ -0,0 +1,46 @@ +# Generated by Django 4.2 on 2026-02-05 14:47 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ] + + operations = [ + migrations.CreateModel( + name='ParentModel', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Child1', + fields=[ + ('parentmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='type_hints_managers.parentmodel')), + ], + options={ + 'abstract': False, + }, + bases=('type_hints_managers.parentmodel',), + ), + migrations.CreateModel( + name='Child2', + fields=[ + ('child1_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='type_hints_managers.child1')), + ], + options={ + 'abstract': False, + }, + bases=('type_hints_managers.child1',), + ), + ] diff --git a/src/polymorphic/tests/examples/type_hints/managers/migrations/__init__.py b/src/polymorphic/tests/examples/type_hints/managers/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/polymorphic/tests/examples/type_hints/managers/models.py b/src/polymorphic/tests/examples/type_hints/managers/models.py new file mode 100644 index 0000000..a34109a --- /dev/null +++ b/src/polymorphic/tests/examples/type_hints/managers/models.py @@ -0,0 +1,28 @@ +from __future__ import annotations +import typing as t +from typing_extensions import Self +from polymorphic.models import PolymorphicModel +from polymorphic.managers import PolymorphicManager + + +class ParentModel(PolymorphicModel): + # fmt: off + # If you want a polymorphic manager with type hint support you can + # override the default one like this: + objects: t.ClassVar[ + PolymorphicManager[ + Self | Child1 | Child2, # union of all polymorphic types + Self, # the base type (for non_polymorphic) + ] + ] = PolymorphicManager() + # fmt: on + + +class Child1(ParentModel): + # you may also override the type hints for the child default + # managers to narrow the filter returns at this level + objects: t.ClassVar[PolymorphicManager[Self | Child2, Self]] + + +class Child2(Child1): + objects: t.ClassVar[PolymorphicManager[Self, Self]] diff --git a/src/polymorphic/tests/examples/type_hints/managers/test.py b/src/polymorphic/tests/examples/type_hints/managers/test.py new file mode 100644 index 0000000..499abd2 --- /dev/null +++ b/src/polymorphic/tests/examples/type_hints/managers/test.py @@ -0,0 +1,83 @@ +import typing as t +from django.test import TestCase +from .models import ParentModel, Child1, Child2 + + +class TypeHintsManagersTest(TestCase): + def test_type_hints(self): + + parent = ParentModel.objects.create() + child1 = Child1.objects.create() + child2 = Child2.objects.create() + + _1: t.Optional[ParentModel | Child1 | Child2] = ( + ParentModel.objects.first() + ) + assert _1 == parent or _1 == child1 or _1 == child2 + assert _1 == parent + _2: t.Optional[ParentModel | Child1 | Child2] = ( + ParentModel.objects.order_by("pk").first() + ) + assert _2 == parent + _3: t.Optional[Child1 | Child2] = Child1.objects.first() + assert _3 == child1 or _3 == child2 + _4: Child1 | Child2 = Child1.objects.filter().all().get(pk=child1.pk) + assert _4 == child1 + _5: t.Optional[Child2] = Child2.objects.first() + assert _5 == child2 + _6: Child2 = Child2.objects.filter().get(pk=child2.pk) + assert _6 == child2 + + assert ParentModel.objects.count() == 3 + assert Child1.objects.count() == 2 + assert Child2.objects.count() == 1 + + # mypy has trouble with these - they work on pyright/pylance. I consider this + # a failing of mypy type inference not a deficiency in our typing - for now + # we can ignore the errors + _7: t.Optional[ParentModel] = ( + ParentModel.objects.non_polymorphic().first() # type: ignore[assignment] + ) + assert _7 == parent + _8: t.Optional[ParentModel] = ( + ParentModel.objects.all().non_polymorphic().first() # type: ignore[assignment] + ) + assert _8 == parent + _9: t.Optional[Child1] = Child1.objects.non_polymorphic().first() # type: ignore[assignment] + assert _9 == child1 + _10: t.Optional[Child1] = ( + Child1.objects.filter().non_polymorphic().all().first() # type: ignore[assignment] + ) + assert _10 == child1 + _11: t.Optional[Child2] = Child2.objects.non_polymorphic().first() + assert _11 == child2 + _12: Child2 = ( + Child2.objects.filter().non_polymorphic().get(pk=child2.pk) + ) + assert _12 == child2 + + _13: t.Optional[ParentModel] = ParentModel.objects.instance_of( + ParentModel + ).first() + assert _13 == parent + _14: t.Optional[ParentModel] = ( + ParentModel.objects.all().instance_of(ParentModel).first() + ) + assert _14 == parent + + _15: t.Optional[Child1] = ParentModel.objects.instance_of( + Child1 + ).first() + assert _15 == child1 + _16: t.Optional[Child1] = ( + ParentModel.objects.all().instance_of(Child1).first() + ) + assert _16 == child1 + _17: t.Optional[Child2] = ParentModel.objects.instance_of( + Child2 + ).first() + assert _17 == child2 + _18: t.Optional[Child2] = ( + ParentModel.objects.all().instance_of(Child2).first() + ) + assert _18 == child2 diff --git a/src/polymorphic/tests/examples/type_hints/migrations/0001_initial.py b/src/polymorphic/tests/examples/type_hints/migrations/0001_initial.py deleted file mode 100644 index 8b82dfc..0000000 --- a/src/polymorphic/tests/examples/type_hints/migrations/0001_initial.py +++ /dev/null @@ -1,125 +0,0 @@ -# Generated by Django 4.2 on 2026-02-02 17:01 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('contenttypes', '0002_remove_content_type_name'), - ] - - operations = [ - migrations.CreateModel( - name='Article', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('title', models.CharField(max_length=100)), - ], - options={ - 'abstract': False, - }, - ), - migrations.CreateModel( - name='MediaOutlet', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=100)), - ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')), - ], - options={ - 'abstract': False, - }, - ), - migrations.CreateModel( - name='Topic', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=50)), - ('articles', models.ManyToManyField(related_name='topics', to='type_hints.article')), - ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')), - ], - options={ - 'abstract': False, - }, - ), - migrations.CreateModel( - name='BlogPost', - fields=[ - ('article_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='type_hints.article')), - ('author', models.CharField(max_length=100)), - ], - options={ - 'abstract': False, - }, - bases=('type_hints.article',), - ), - migrations.CreateModel( - name='EditorialTopic', - fields=[ - ('topic_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='type_hints.topic')), - ('editor', models.CharField(max_length=100)), - ], - options={ - 'abstract': False, - }, - bases=('type_hints.topic',), - ), - migrations.CreateModel( - name='LocationTopic', - fields=[ - ('topic_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='type_hints.topic')), - ('location', models.CharField(max_length=100)), - ], - options={ - 'abstract': False, - }, - bases=('type_hints.topic',), - ), - migrations.CreateModel( - name='NewsArticle', - fields=[ - ('article_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='type_hints.article')), - ('source', models.CharField(max_length=100)), - ], - options={ - 'abstract': False, - }, - bases=('type_hints.article',), - ), - migrations.CreateModel( - name='Newspaper', - fields=[ - ('mediaoutlet_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='type_hints.mediaoutlet')), - ('service_area', models.CharField(max_length=100)), - ], - options={ - 'abstract': False, - }, - bases=('type_hints.mediaoutlet',), - ), - migrations.CreateModel( - name='OnlineBlog', - fields=[ - ('mediaoutlet_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='type_hints.mediaoutlet')), - ('url', models.URLField()), - ], - options={ - 'abstract': False, - }, - bases=('type_hints.mediaoutlet',), - ), - migrations.AddField( - model_name='article', - name='outlet', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='articles', to='type_hints.mediaoutlet'), - ), - migrations.AddField( - model_name='article', - name='polymorphic_ctype', - field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype'), - ), - ] diff --git a/src/polymorphic/tests/examples/type_hints/models.py b/src/polymorphic/tests/examples/type_hints/models.py index a652220..6303777 100644 --- a/src/polymorphic/tests/examples/type_hints/models.py +++ b/src/polymorphic/tests/examples/type_hints/models.py @@ -1,73 +1,76 @@ -from __future__ import annotations -import typing as t -from typing_extensions import Self -from django.db import models -from polymorphic.models import PolymorphicModel -from polymorphic.managers import PolymorphicManager +# from __future__ import annotations +# import typing as t +# from typing_extensions import Self +# from django.db import models +# from polymorphic.models import PolymorphicModel +# from polymorphic.managers import PolymorphicManager -if t.TYPE_CHECKING: - from polymorphic.managers import ( - PolymorphicManyToManyDescriptor, - PolymorphicRelatedManager, - ) +# if t.TYPE_CHECKING: +# # The polymorphic manager and related descriptors type hints are only available +# # during type checking +# from polymorphic.managers import ( +# PolymorphicManyToManyDescriptor, +# PolymorphicRelatedManager, +# ) -class Article(PolymorphicModel): - title = models.CharField(max_length=100) +# class Article(PolymorphicModel): +# title = models.CharField(max_length=100) - outlet: "MediaOutlet" | "Newspaper" | "OnlineBlog" = models.ForeignKey( # type: ignore[assignment] - "MediaOutlet", on_delete=models.CASCADE, related_name="articles" - ) +# outlet: "MediaOutlet" | "Newspaper" | "OnlineBlog" = models.ForeignKey( # type: ignore[assignment] +# "MediaOutlet", on_delete=models.CASCADE, related_name="articles" +# ) - objects: t.ClassVar[ - PolymorphicManager[Self | "BlogPost" | "NewsArticle", Self] - ] = PolymorphicManager() +# objects: t.ClassVar[ +# PolymorphicManager[Self | "BlogPost" | "NewsArticle", Self] +# ] = PolymorphicManager() - topics: PolymorphicManyToManyDescriptor[ - "Topic" | "LocationTopic" | "EditorialTopic", "Topic" - ] +# topics: PolymorphicManyToManyDescriptor[ +# "Topic" | "LocationTopic" | "EditorialTopic", "Topic" +# ] -class BlogPost(Article): - author = models.CharField(max_length=100) +# class BlogPost(Article): +# author = models.CharField(max_length=100) -class NewsArticle(Article): - source = models.CharField(max_length=100) +# class NewsArticle(Article): +# source = models.CharField(max_length=100) -class Topic(PolymorphicModel): - name = models.CharField(max_length=50) - articles: PolymorphicManyToManyDescriptor[ - Article | BlogPost | NewsArticle, Article - ] = models.ManyToManyField(Article, related_name="topics") # type: ignore[assignment] +# class Topic(PolymorphicModel): +# name = models.CharField(max_length=50) - articles2 = models.ManyToManyField(Article, related_name="topics2") - if t.TYPE_CHECKING: - objects: t.ClassVar[ - PolymorphicManager[Self | "LocationTopic" | "EditorialTopic", Self] - ] +# articles: PolymorphicManyToManyDescriptor[ +# Article | BlogPost | NewsArticle, Article +# ] = models.ManyToManyField(Article, related_name="topics") # type: ignore[assignment] + +# articles2 = models.ManyToManyField(Article, related_name="topics2") +# if t.TYPE_CHECKING: +# objects: t.ClassVar[ +# PolymorphicManager[Self | "LocationTopic" | "EditorialTopic", Self] +# ] -class LocationTopic(Topic): - location = models.CharField(max_length=100) +# class LocationTopic(Topic): +# location = models.CharField(max_length=100) -class EditorialTopic(Topic): - editor = models.CharField(max_length=100) +# class EditorialTopic(Topic): +# editor = models.CharField(max_length=100) -class MediaOutlet(PolymorphicModel): - name = models.CharField(max_length=100) +# class MediaOutlet(PolymorphicModel): +# name = models.CharField(max_length=100) - articles: PolymorphicRelatedManager[ - Article | NewsArticle | BlogPost, "Article" - ] +# articles: PolymorphicRelatedManager[ +# Article | NewsArticle | BlogPost, "Article" +# ] -class Newspaper(MediaOutlet): - service_area = models.CharField(max_length=100) +# class Newspaper(MediaOutlet): +# service_area = models.CharField(max_length=100) -class OnlineBlog(MediaOutlet): - url = models.URLField() +# class OnlineBlog(MediaOutlet): +# url = models.URLField() diff --git a/src/polymorphic/tests/examples/type_hints/one2one/__init__.py b/src/polymorphic/tests/examples/type_hints/one2one/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/polymorphic/tests/examples/type_hints/one2one/apps.py b/src/polymorphic/tests/examples/type_hints/one2one/apps.py new file mode 100644 index 0000000..2f866d4 --- /dev/null +++ b/src/polymorphic/tests/examples/type_hints/one2one/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + + +class TypeHintsOne2OneConfig(AppConfig): + name = "polymorphic.tests.examples.type_hints.one2one" + label = "type_hints_one2one" + verbose_name = "Type Hints One2One Example" diff --git a/src/polymorphic/tests/examples/type_hints/one2one/migrations/0001_initial.py b/src/polymorphic/tests/examples/type_hints/one2one/migrations/0001_initial.py new file mode 100644 index 0000000..617c848 --- /dev/null +++ b/src/polymorphic/tests/examples/type_hints/one2one/migrations/0001_initial.py @@ -0,0 +1,58 @@ +# Generated by Django 4.2 on 2026-02-05 14:47 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ] + + operations = [ + migrations.CreateModel( + name='ParentModel', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Child1', + fields=[ + ('parentmodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='type_hints_one2one.parentmodel')), + ], + options={ + 'abstract': False, + }, + bases=('type_hints_one2one.parentmodel',), + ), + migrations.CreateModel( + name='RelatedModel', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('parent_forward', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='type_hints_one2one.parentmodel')), + ], + ), + migrations.AddField( + model_name='parentmodel', + name='related_forward', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='parent_reverse', to='type_hints_one2one.relatedmodel'), + ), + migrations.CreateModel( + name='Child2', + fields=[ + ('child1_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='type_hints_one2one.child1')), + ], + options={ + 'abstract': False, + }, + bases=('type_hints_one2one.child1',), + ), + ] diff --git a/src/polymorphic/tests/examples/type_hints/one2one/migrations/__init__.py b/src/polymorphic/tests/examples/type_hints/one2one/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/polymorphic/tests/examples/type_hints/one2one/models.py b/src/polymorphic/tests/examples/type_hints/one2one/models.py new file mode 100644 index 0000000..b15caa1 --- /dev/null +++ b/src/polymorphic/tests/examples/type_hints/one2one/models.py @@ -0,0 +1,47 @@ +from django.db import models +from polymorphic.models import PolymorphicModel +from polymorphic.managers import ( + PolymorphicForwardOneToOneDescriptor, + PolymorphicReverseOneToOneDescriptor, + Nullable, # Alias for typing.Literal[True] +) + + +class ParentModel(PolymorphicModel): + related_forward = models.OneToOneField( + "RelatedModel", on_delete=models.CASCADE, related_name="parent_reverse" + ) + + +class Child1(ParentModel): + pass + + +class Child2(Child1): + pass + + +class RelatedModel(models.Model): + # fmt: off + # Forward Relation Descriptor Type Hint: + # 1. Class Attribute: ForwardOneToOneDescriptor + # 2. Instance attribute: Union of all listed model types or None + parent_forward: PolymorphicForwardOneToOneDescriptor[ + ParentModel | Child1 | Child2, # all possible polymorphic types + ParentModel, # the base type (for non_polymorphic) + Nullable, # when null=True + ] = models.OneToOneField( # type: ignore[assignment] + "ParentModel", + on_delete=models.CASCADE, + null=True + ) + + # Reverse Relation Descriptor Type Hint: + # 1. Class Attribute: ReverseOneToOneDescriptor + # 2. Instance attribute: Union of all listed model types + parent_reverse: PolymorphicReverseOneToOneDescriptor[ + ParentModel | Child1 | Child2, # possible polymorphic types + ParentModel, # the base type (for non_polymorphic) + # nullable defaults to False + ] + # fmt: on diff --git a/src/polymorphic/tests/examples/type_hints/one2one/test.py b/src/polymorphic/tests/examples/type_hints/one2one/test.py new file mode 100644 index 0000000..34d4d66 --- /dev/null +++ b/src/polymorphic/tests/examples/type_hints/one2one/test.py @@ -0,0 +1,43 @@ +import typing as t +from django.db.models.fields.reverse_related import OneToOneRel +from typing_extensions import assert_type +from django.test import TestCase +from polymorphic.managers import PolymorphicQuerySet + +# from django.db import models +from .models import ParentModel, Child1, Child2, RelatedModel + + +class TypeHintsOne2OneTest(TestCase): + def test_type_hints(self): + related1 = RelatedModel.objects.create() + related2 = RelatedModel.objects.create() + related3 = RelatedModel.objects.create() + parent = ParentModel.objects.create(related_forward=related1) + child1 = Child1.objects.create(related_forward=related2) + Child2.objects.create(related_forward=related3) + assert_type( + related1.parent_forward, t.Optional[ParentModel | Child1 | Child2] + ) + assert_type(related1.parent_reverse, ParentModel | Child1 | Child2) + + related1.parent_forward = child1 + related1.save() + + assert_type(RelatedModel.parent_reverse.related, OneToOneRel) + _1: PolymorphicQuerySet[ParentModel | Child1 | Child2, ParentModel] = ( + RelatedModel.parent_reverse.get_queryset() + ) + assert _1.all().count() == 3 + + # assert_type(RelatedModel.parent_forward.related, OneToOneRel) + _2: PolymorphicQuerySet[ParentModel | Child1 | Child2, ParentModel] = ( + RelatedModel.parent_forward.get_queryset() + ) + assert _2.all().count() == 3 + + _3: ParentModel | Child1 | Child2 = related1.parent_reverse + assert _3 == parent + + _4: t.Optional[ParentModel | Child1 | Child2] = related1.parent_forward + assert _4 == child1 diff --git a/src/polymorphic/tests/examples/type_hints/test.py b/src/polymorphic/tests/examples/type_hints/test.py deleted file mode 100644 index 2f1b8b8..0000000 --- a/src/polymorphic/tests/examples/type_hints/test.py +++ /dev/null @@ -1,126 +0,0 @@ -from typing_extensions import assert_type -from django.test import TestCase -from .models import ( - Article, - BlogPost, - NewsArticle, - Topic, - LocationTopic, - EditorialTopic, - MediaOutlet, - Newspaper, - OnlineBlog, -) - - -class TypeHintsTest(TestCase): - def test_type_hints_example_models(self): - # Just a placeholder test to ensure the example models file is included in test - # runs - daily = Newspaper.objects.create( - name="The Daily Times", service_area="New York" - ) - rg = OnlineBlog.objects.create(name="ReplyGuy") - - article = Article.objects.create(title="Test Article", outlet=daily) - bp = BlogPost.objects.create( - title="Test Blog Post", author="Author A", outlet=rg - ) - na = NewsArticle.objects.create( - title="Test News Article", source="Source A", outlet=daily - ) - - general_int = Topic.objects.create(name="General Interest") - location_topic = LocationTopic.objects.create( - name="Food", location="Los Angeles" - ) - editorial_topic = EditorialTopic.objects.create( - name="Politics", editor="BCK" - ) - - general_int.articles.add(article, bp, na) - location_topic.articles.add(na) - editorial_topic.articles.add(bp) - - objs: list[Article | BlogPost | NewsArticle] = list( - Article.objects.all() - ) - objs0: list[Article | BlogPost | NewsArticle] = list( - Article.objects.all().all() - ) - objs01: list[Article | BlogPost | NewsArticle] = list( - Article.objects.filter() - ) - - objs2: list[BlogPost] = list( - Article.objects.instance_of(BlogPost).all() - ) - objs3: list[BlogPost | NewsArticle] = list( - Article.objects.instance_of(NewsArticle, BlogPost).all() - ) - - for art in Article.objects.instance_of(NewsArticle, BlogPost): - assert_type(art, NewsArticle | BlogPost) - - objs4: list[BlogPost] = list( - Article.objects.all().instance_of(BlogPost).all() - ) - objs5: list[BlogPost | NewsArticle] = list( - Article.objects.all().instance_of(NewsArticle, BlogPost).all() - ) - - assert len(objs) == len(objs0) == len(objs01) == 3 - assert len(objs2) == 1 - assert len(objs3) == 2 - assert len(objs4) == 1 - assert len(objs5) == 2 - - topics: list[Topic | LocationTopic | EditorialTopic] = list( - article.topics.all() - ) - topics2: list[Topic | LocationTopic | EditorialTopic] = list( - article.topics.all().all() - ) - - non_poly_topics: list[Topic] = list(article.topics.non_polymorphic()) - - for tpc1 in article.topics.all(): - assert_type(tpc1, Topic | LocationTopic | EditorialTopic) - - for tpc2 in article.topics.all().all(): - assert_type(tpc2, Topic | LocationTopic | EditorialTopic) - for tpc3 in article.topics.filter(): - assert_type(tpc3, Topic | LocationTopic | EditorialTopic) - - for tpc4 in article.topics.non_polymorphic(): - assert_type(tpc4, Topic) - - for tpc5 in article.topics.all().non_polymorphic(): - assert_type(tpc5, Topic) - - for tpc6 in article.topics.all().instance_of(BlogPost): - assert_type(tpc6, BlogPost) - - assert len(topics) == 1 - assert len(topics2) == 1 - assert len(non_poly_topics) == 1 - - assert bp.topics.count() == 2 - - for art2 in general_int.articles.all(): - assert_type(art2, Article | BlogPost | NewsArticle) - - assert_type(article.outlet, Newspaper | OnlineBlog | MediaOutlet) - assert_type(bp.outlet, Newspaper | OnlineBlog | MediaOutlet) - assert_type(na.outlet, Newspaper | OnlineBlog | MediaOutlet) - - for art3 in daily.articles.all(): - assert_type(art3, Article | BlogPost | NewsArticle) - - for art4 in daily.articles.filter().all(): - assert_type(art4, Article | BlogPost | NewsArticle) - - # reveal_type(Topic.articles) - # reveal_type(Topic.articles2) - - # reveal_type(Topic.objects.first().articles2) diff --git a/src/polymorphic/tests/examples/views/migrations/0001_initial.py b/src/polymorphic/tests/examples/views/migrations/0001_initial.py index 59fbdf0..681e2f8 100644 --- a/src/polymorphic/tests/examples/views/migrations/0001_initial.py +++ b/src/polymorphic/tests/examples/views/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2 on 2026-02-02 17:01 +# Generated by Django 4.2 on 2026-02-05 14:47 from django.db import migrations, models import django.db.models.deletion diff --git a/src/polymorphic/tests/migrations/0001_initial.py b/src/polymorphic/tests/migrations/0001_initial.py index 0087adc..bbde917 100644 --- a/src/polymorphic/tests/migrations/0001_initial.py +++ b/src/polymorphic/tests/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2 on 2026-02-02 17:01 +# Generated by Django 4.2 on 2026-02-05 14:47 from django.conf import settings from django.db import migrations, models @@ -14,8 +14,8 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('auth', '0012_alter_user_first_name_max_length'), ('contenttypes', '0002_remove_content_type_name'), + ('auth', '0012_alter_user_first_name_max_length'), ] operations = [ diff --git a/src/polymorphic/tests/other/migrations/0001_initial.py b/src/polymorphic/tests/other/migrations/0001_initial.py index e9c73a7..6617323 100644 --- a/src/polymorphic/tests/other/migrations/0001_initial.py +++ b/src/polymorphic/tests/other/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2 on 2026-02-02 17:01 +# Generated by Django 4.2 on 2026-02-05 14:47 from django.db import migrations, models import django.db.models.deletion diff --git a/src/polymorphic/tests/settings.py b/src/polymorphic/tests/settings.py index ceacc83..a4a8ef3 100644 --- a/src/polymorphic/tests/settings.py +++ b/src/polymorphic/tests/settings.py @@ -105,7 +105,10 @@ elif rdbms == "oracle": # pragma: no cover } DEFAULT_AUTO_FIELD = "django.db.models.AutoField" INSTALLED_APPS = [ - "polymorphic.tests.examples.type_hints", + "polymorphic.tests.examples.type_hints.managers", + "polymorphic.tests.examples.type_hints.one2one", + "polymorphic.tests.examples.type_hints.m2m", + "polymorphic.tests.examples.type_hints.fk", "polymorphic.tests.examples.integrations", "polymorphic.tests", "polymorphic.tests.deletion", diff --git a/uv.lock b/uv.lock index 168fefb..b64d538 100644 --- a/uv.lock +++ b/uv.lock @@ -44,14 +44,14 @@ wheels = [ [[package]] name = "asgiref" -version = "3.11.0" +version = "3.11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/76/b9/4db2509eabd14b4a8c71d1b24c8d5734c52b8560a7b1e1a8b56c8d25568b/asgiref-3.11.0.tar.gz", hash = "sha256:13acff32519542a1736223fb79a715acdebe24286d98e8b164a73085f40da2c4", size = 37969, upload-time = "2025-11-19T15:32:20.106Z" } +sdist = { url = "https://files.pythonhosted.org/packages/63/40/f03da1264ae8f7cfdbf9146542e5e7e8100a4c66ab48e791df9a03d3f6c0/asgiref-3.11.1.tar.gz", hash = "sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce", size = 38550, upload-time = "2026-02-03T13:30:14.33Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/91/be/317c2c55b8bbec407257d45f5c8d1b6867abc76d12043f2d3d58c538a4ea/asgiref-3.11.0-py3-none-any.whl", hash = "sha256:1db9021efadb0d9512ce8ffaf72fcef601c7b73a8807a1bb2ef143dc6b14846d", size = 24096, upload-time = "2025-11-19T15:32:19.004Z" }, + { url = "https://files.pythonhosted.org/packages/5c/0a/a72d10ed65068e115044937873362e6e32fab1b7dce0046aeb224682c989/asgiref-3.11.1-py3-none-any.whl", hash = "sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133", size = 24345, upload-time = "2026-02-03T13:30:13.039Z" }, ] [[package]] @@ -350,101 +350,101 @@ wheels = [ [[package]] name = "coverage" -version = "7.13.2" +version = "7.13.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ad/49/349848445b0e53660e258acbcc9b0d014895b6739237920886672240f84b/coverage-7.13.2.tar.gz", hash = "sha256:044c6951ec37146b72a50cc81ef02217d27d4c3640efd2640311393cbbf143d3", size = 826523, upload-time = "2026-01-25T13:00:04.889Z" } +sdist = { url = "https://files.pythonhosted.org/packages/11/43/3e4ac666cc35f231fa70c94e9f38459299de1a152813f9d2f60fc5f3ecaf/coverage-7.13.3.tar.gz", hash = "sha256:f7f6182d3dfb8802c1747eacbfe611b669455b69b7c037484bb1efbbb56711ac", size = 826832, upload-time = "2026-02-03T14:02:30.944Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/2d/63e37369c8e81a643afe54f76073b020f7b97ddbe698c5c944b51b0a2bc5/coverage-7.13.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f4af3b01763909f477ea17c962e2cca8f39b350a4e46e3a30838b2c12e31b81b", size = 218842, upload-time = "2026-01-25T12:57:15.3Z" }, - { url = "https://files.pythonhosted.org/packages/57/06/86ce882a8d58cbcb3030e298788988e618da35420d16a8c66dac34f138d0/coverage-7.13.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:36393bd2841fa0b59498f75466ee9bdec4f770d3254f031f23e8fd8e140ffdd2", size = 219360, upload-time = "2026-01-25T12:57:17.572Z" }, - { url = "https://files.pythonhosted.org/packages/cd/84/70b0eb1ee19ca4ef559c559054c59e5b2ae4ec9af61398670189e5d276e9/coverage-7.13.2-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9cc7573518b7e2186bd229b1a0fe24a807273798832c27032c4510f47ffdb896", size = 246123, upload-time = "2026-01-25T12:57:19.087Z" }, - { url = "https://files.pythonhosted.org/packages/35/fb/05b9830c2e8275ebc031e0019387cda99113e62bb500ab328bb72578183b/coverage-7.13.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ca9566769b69a5e216a4e176d54b9df88f29d750c5b78dbb899e379b4e14b30c", size = 247930, upload-time = "2026-01-25T12:57:20.929Z" }, - { url = "https://files.pythonhosted.org/packages/81/aa/3f37858ca2eed4f09b10ca3c6ddc9041be0a475626cd7fd2712f4a2d526f/coverage-7.13.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c9bdea644e94fd66d75a6f7e9a97bb822371e1fe7eadae2cacd50fcbc28e4dc", size = 249804, upload-time = "2026-01-25T12:57:22.904Z" }, - { url = "https://files.pythonhosted.org/packages/b6/b3/c904f40c56e60a2d9678a5ee8df3d906d297d15fb8bec5756c3b0a67e2df/coverage-7.13.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5bd447332ec4f45838c1ad42268ce21ca87c40deb86eabd59888859b66be22a5", size = 246815, upload-time = "2026-01-25T12:57:24.314Z" }, - { url = "https://files.pythonhosted.org/packages/41/91/ddc1c5394ca7fd086342486440bfdd6b9e9bda512bf774599c7c7a0081e0/coverage-7.13.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7c79ad5c28a16a1277e1187cf83ea8dafdcc689a784228a7d390f19776db7c31", size = 247843, upload-time = "2026-01-25T12:57:26.544Z" }, - { url = "https://files.pythonhosted.org/packages/87/d2/cdff8f4cd33697883c224ea8e003e9c77c0f1a837dc41d95a94dd26aad67/coverage-7.13.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:76e06ccacd1fb6ada5d076ed98a8c6f66e2e6acd3df02819e2ee29fd637b76ad", size = 245850, upload-time = "2026-01-25T12:57:28.507Z" }, - { url = "https://files.pythonhosted.org/packages/f5/42/e837febb7866bf2553ab53dd62ed52f9bb36d60c7e017c55376ad21fbb05/coverage-7.13.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:49d49e9a5e9f4dc3d3dac95278a020afa6d6bdd41f63608a76fa05a719d5b66f", size = 246116, upload-time = "2026-01-25T12:57:30.16Z" }, - { url = "https://files.pythonhosted.org/packages/09/b1/4a3f935d7df154df02ff4f71af8d61298d713a7ba305d050ae475bfbdde2/coverage-7.13.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ed2bce0e7bfa53f7b0b01c722da289ef6ad4c18ebd52b1f93704c21f116360c8", size = 246720, upload-time = "2026-01-25T12:57:32.165Z" }, - { url = "https://files.pythonhosted.org/packages/e1/fe/538a6fd44c515f1c5197a3f078094cbaf2ce9f945df5b44e29d95c864bff/coverage-7.13.2-cp310-cp310-win32.whl", hash = "sha256:1574983178b35b9af4db4a9f7328a18a14a0a0ce76ffaa1c1bacb4cc82089a7c", size = 221465, upload-time = "2026-01-25T12:57:33.511Z" }, - { url = "https://files.pythonhosted.org/packages/5e/09/4b63a024295f326ec1a40ec8def27799300ce8775b1cbf0d33b1790605c4/coverage-7.13.2-cp310-cp310-win_amd64.whl", hash = "sha256:a360a8baeb038928ceb996f5623a4cd508728f8f13e08d4e96ce161702f3dd99", size = 222397, upload-time = "2026-01-25T12:57:34.927Z" }, - { url = "https://files.pythonhosted.org/packages/6c/01/abca50583a8975bb6e1c59eff67ed8e48bb127c07dad5c28d9e96ccc09ec/coverage-7.13.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:060ebf6f2c51aff5ba38e1f43a2095e087389b1c69d559fde6049a4b0001320e", size = 218971, upload-time = "2026-01-25T12:57:36.953Z" }, - { url = "https://files.pythonhosted.org/packages/eb/0e/b6489f344d99cd1e5b4d5e1be52dfd3f8a3dc5112aa6c33948da8cabad4e/coverage-7.13.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c1ea8ca9db5e7469cd364552985e15911548ea5b69c48a17291f0cac70484b2e", size = 219473, upload-time = "2026-01-25T12:57:38.934Z" }, - { url = "https://files.pythonhosted.org/packages/17/11/db2f414915a8e4ec53f60b17956c27f21fb68fcf20f8a455ce7c2ccec638/coverage-7.13.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b780090d15fd58f07cf2011943e25a5f0c1c894384b13a216b6c86c8a8a7c508", size = 249896, upload-time = "2026-01-25T12:57:40.365Z" }, - { url = "https://files.pythonhosted.org/packages/80/06/0823fe93913663c017e508e8810c998c8ebd3ec2a5a85d2c3754297bdede/coverage-7.13.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:88a800258d83acb803c38175b4495d293656d5fac48659c953c18e5f539a274b", size = 251810, upload-time = "2026-01-25T12:57:42.045Z" }, - { url = "https://files.pythonhosted.org/packages/61/dc/b151c3cc41b28cdf7f0166c5fa1271cbc305a8ec0124cce4b04f74791a18/coverage-7.13.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6326e18e9a553e674d948536a04a80d850a5eeefe2aae2e6d7cf05d54046c01b", size = 253920, upload-time = "2026-01-25T12:57:44.026Z" }, - { url = "https://files.pythonhosted.org/packages/2d/35/e83de0556e54a4729a2b94ea816f74ce08732e81945024adee46851c2264/coverage-7.13.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:59562de3f797979e1ff07c587e2ac36ba60ca59d16c211eceaa579c266c5022f", size = 250025, upload-time = "2026-01-25T12:57:45.624Z" }, - { url = "https://files.pythonhosted.org/packages/39/67/af2eb9c3926ce3ea0d58a0d2516fcbdacf7a9fc9559fe63076beaf3f2596/coverage-7.13.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:27ba1ed6f66b0e2d61bfa78874dffd4f8c3a12f8e2b5410e515ab345ba7bc9c3", size = 251612, upload-time = "2026-01-25T12:57:47.713Z" }, - { url = "https://files.pythonhosted.org/packages/26/62/5be2e25f3d6c711d23b71296f8b44c978d4c8b4e5b26871abfc164297502/coverage-7.13.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8be48da4d47cc68754ce643ea50b3234557cbefe47c2f120495e7bd0a2756f2b", size = 249670, upload-time = "2026-01-25T12:57:49.378Z" }, - { url = "https://files.pythonhosted.org/packages/b3/51/400d1b09a8344199f9b6a6fc1868005d766b7ea95e7882e494fa862ca69c/coverage-7.13.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2a47a4223d3361b91176aedd9d4e05844ca67d7188456227b6bf5e436630c9a1", size = 249395, upload-time = "2026-01-25T12:57:50.86Z" }, - { url = "https://files.pythonhosted.org/packages/e0/36/f02234bc6e5230e2f0a63fd125d0a2093c73ef20fdf681c7af62a140e4e7/coverage-7.13.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c6f141b468740197d6bd38f2b26ade124363228cc3f9858bd9924ab059e00059", size = 250298, upload-time = "2026-01-25T12:57:52.287Z" }, - { url = "https://files.pythonhosted.org/packages/b0/06/713110d3dd3151b93611c9cbfc65c15b4156b44f927fced49ac0b20b32a4/coverage-7.13.2-cp311-cp311-win32.whl", hash = "sha256:89567798404af067604246e01a49ef907d112edf2b75ef814b1364d5ce267031", size = 221485, upload-time = "2026-01-25T12:57:53.876Z" }, - { url = "https://files.pythonhosted.org/packages/16/0c/3ae6255fa1ebcb7dec19c9a59e85ef5f34566d1265c70af5b2fc981da834/coverage-7.13.2-cp311-cp311-win_amd64.whl", hash = "sha256:21dd57941804ae2ac7e921771a5e21bbf9aabec317a041d164853ad0a96ce31e", size = 222421, upload-time = "2026-01-25T12:57:55.433Z" }, - { url = "https://files.pythonhosted.org/packages/b5/37/fabc3179af4d61d89ea47bd04333fec735cd5e8b59baad44fed9fc4170d7/coverage-7.13.2-cp311-cp311-win_arm64.whl", hash = "sha256:10758e0586c134a0bafa28f2d37dd2cdb5e4a90de25c0fc0c77dabbad46eca28", size = 221088, upload-time = "2026-01-25T12:57:57.41Z" }, - { url = "https://files.pythonhosted.org/packages/46/39/e92a35f7800222d3f7b2cbb7bbc3b65672ae8d501cb31801b2d2bd7acdf1/coverage-7.13.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f106b2af193f965d0d3234f3f83fc35278c7fb935dfbde56ae2da3dd2c03b84d", size = 219142, upload-time = "2026-01-25T12:58:00.448Z" }, - { url = "https://files.pythonhosted.org/packages/45/7a/8bf9e9309c4c996e65c52a7c5a112707ecdd9fbaf49e10b5a705a402bbb4/coverage-7.13.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78f45d21dc4d5d6bd29323f0320089ef7eae16e4bef712dff79d184fa7330af3", size = 219503, upload-time = "2026-01-25T12:58:02.451Z" }, - { url = "https://files.pythonhosted.org/packages/87/93/17661e06b7b37580923f3f12406ac91d78aeed293fb6da0b69cc7957582f/coverage-7.13.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fae91dfecd816444c74531a9c3d6ded17a504767e97aa674d44f638107265b99", size = 251006, upload-time = "2026-01-25T12:58:04.059Z" }, - { url = "https://files.pythonhosted.org/packages/12/f0/f9e59fb8c310171497f379e25db060abef9fa605e09d63157eebec102676/coverage-7.13.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:264657171406c114787b441484de620e03d8f7202f113d62fcd3d9688baa3e6f", size = 253750, upload-time = "2026-01-25T12:58:05.574Z" }, - { url = "https://files.pythonhosted.org/packages/e5/b1/1935e31add2232663cf7edd8269548b122a7d100047ff93475dbaaae673e/coverage-7.13.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae47d8dcd3ded0155afbb59c62bd8ab07ea0fd4902e1c40567439e6db9dcaf2f", size = 254862, upload-time = "2026-01-25T12:58:07.647Z" }, - { url = "https://files.pythonhosted.org/packages/af/59/b5e97071ec13df5f45da2b3391b6cdbec78ba20757bc92580a5b3d5fa53c/coverage-7.13.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8a0b33e9fd838220b007ce8f299114d406c1e8edb21336af4c97a26ecfd185aa", size = 251420, upload-time = "2026-01-25T12:58:09.309Z" }, - { url = "https://files.pythonhosted.org/packages/3f/75/9495932f87469d013dc515fb0ce1aac5fa97766f38f6b1a1deb1ee7b7f3a/coverage-7.13.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b3becbea7f3ce9a2d4d430f223ec15888e4deb31395840a79e916368d6004cce", size = 252786, upload-time = "2026-01-25T12:58:10.909Z" }, - { url = "https://files.pythonhosted.org/packages/6a/59/af550721f0eb62f46f7b8cb7e6f1860592189267b1c411a4e3a057caacee/coverage-7.13.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f819c727a6e6eeb8711e4ce63d78c620f69630a2e9d53bc95ca5379f57b6ba94", size = 250928, upload-time = "2026-01-25T12:58:12.449Z" }, - { url = "https://files.pythonhosted.org/packages/9b/b1/21b4445709aae500be4ab43bbcfb4e53dc0811c3396dcb11bf9f23fd0226/coverage-7.13.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:4f7b71757a3ab19f7ba286e04c181004c1d61be921795ee8ba6970fd0ec91da5", size = 250496, upload-time = "2026-01-25T12:58:14.047Z" }, - { url = "https://files.pythonhosted.org/packages/ba/b1/0f5d89dfe0392990e4f3980adbde3eb34885bc1effb2dc369e0bf385e389/coverage-7.13.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b7fc50d2afd2e6b4f6f2f403b70103d280a8e0cb35320cbbe6debcda02a1030b", size = 252373, upload-time = "2026-01-25T12:58:15.976Z" }, - { url = "https://files.pythonhosted.org/packages/01/c9/0cf1a6a57a9968cc049a6b896693faa523c638a5314b1fc374eb2b2ac904/coverage-7.13.2-cp312-cp312-win32.whl", hash = "sha256:292250282cf9bcf206b543d7608bda17ca6fc151f4cbae949fc7e115112fbd41", size = 221696, upload-time = "2026-01-25T12:58:17.517Z" }, - { url = "https://files.pythonhosted.org/packages/4d/05/d7540bf983f09d32803911afed135524570f8c47bb394bf6206c1dc3a786/coverage-7.13.2-cp312-cp312-win_amd64.whl", hash = "sha256:eeea10169fac01549a7921d27a3e517194ae254b542102267bef7a93ed38c40e", size = 222504, upload-time = "2026-01-25T12:58:19.115Z" }, - { url = "https://files.pythonhosted.org/packages/15/8b/1a9f037a736ced0a12aacf6330cdaad5008081142a7070bc58b0f7930cbc/coverage-7.13.2-cp312-cp312-win_arm64.whl", hash = "sha256:2a5b567f0b635b592c917f96b9a9cb3dbd4c320d03f4bf94e9084e494f2e8894", size = 221120, upload-time = "2026-01-25T12:58:21.334Z" }, - { url = "https://files.pythonhosted.org/packages/a7/f0/3d3eac7568ab6096ff23791a526b0048a1ff3f49d0e236b2af6fb6558e88/coverage-7.13.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ed75de7d1217cf3b99365d110975f83af0528c849ef5180a12fd91b5064df9d6", size = 219168, upload-time = "2026-01-25T12:58:23.376Z" }, - { url = "https://files.pythonhosted.org/packages/a3/a6/f8b5cfeddbab95fdef4dcd682d82e5dcff7a112ced57a959f89537ee9995/coverage-7.13.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:97e596de8fa9bada4d88fde64a3f4d37f1b6131e4faa32bad7808abc79887ddc", size = 219537, upload-time = "2026-01-25T12:58:24.932Z" }, - { url = "https://files.pythonhosted.org/packages/7b/e6/8d8e6e0c516c838229d1e41cadcec91745f4b1031d4db17ce0043a0423b4/coverage-7.13.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:68c86173562ed4413345410c9480a8d64864ac5e54a5cda236748031e094229f", size = 250528, upload-time = "2026-01-25T12:58:26.567Z" }, - { url = "https://files.pythonhosted.org/packages/8e/78/befa6640f74092b86961f957f26504c8fba3d7da57cc2ab7407391870495/coverage-7.13.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7be4d613638d678b2b3773b8f687537b284d7074695a43fe2fbbfc0e31ceaed1", size = 253132, upload-time = "2026-01-25T12:58:28.251Z" }, - { url = "https://files.pythonhosted.org/packages/9d/10/1630db1edd8ce675124a2ee0f7becc603d2bb7b345c2387b4b95c6907094/coverage-7.13.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d7f63ce526a96acd0e16c4af8b50b64334239550402fb1607ce6a584a6d62ce9", size = 254374, upload-time = "2026-01-25T12:58:30.294Z" }, - { url = "https://files.pythonhosted.org/packages/ed/1d/0d9381647b1e8e6d310ac4140be9c428a0277330991e0c35bdd751e338a4/coverage-7.13.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:406821f37f864f968e29ac14c3fccae0fec9fdeba48327f0341decf4daf92d7c", size = 250762, upload-time = "2026-01-25T12:58:32.036Z" }, - { url = "https://files.pythonhosted.org/packages/43/e4/5636dfc9a7c871ee8776af83ee33b4c26bc508ad6cee1e89b6419a366582/coverage-7.13.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ee68e5a4e3e5443623406b905db447dceddffee0dceb39f4e0cd9ec2a35004b5", size = 252502, upload-time = "2026-01-25T12:58:33.961Z" }, - { url = "https://files.pythonhosted.org/packages/02/2a/7ff2884d79d420cbb2d12fed6fff727b6d0ef27253140d3cdbbd03187ee0/coverage-7.13.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2ee0e58cca0c17dd9c6c1cdde02bb705c7b3fbfa5f3b0b5afeda20d4ebff8ef4", size = 250463, upload-time = "2026-01-25T12:58:35.529Z" }, - { url = "https://files.pythonhosted.org/packages/91/c0/ba51087db645b6c7261570400fc62c89a16278763f36ba618dc8657a187b/coverage-7.13.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:6e5bbb5018bf76a56aabdb64246b5288d5ae1b7d0dd4d0534fe86df2c2992d1c", size = 250288, upload-time = "2026-01-25T12:58:37.226Z" }, - { url = "https://files.pythonhosted.org/packages/03/07/44e6f428551c4d9faf63ebcefe49b30e5c89d1be96f6a3abd86a52da9d15/coverage-7.13.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a55516c68ef3e08e134e818d5e308ffa6b1337cc8b092b69b24287bf07d38e31", size = 252063, upload-time = "2026-01-25T12:58:38.821Z" }, - { url = "https://files.pythonhosted.org/packages/c2/67/35b730ad7e1859dd57e834d1bc06080d22d2f87457d53f692fce3f24a5a9/coverage-7.13.2-cp313-cp313-win32.whl", hash = "sha256:5b20211c47a8abf4abc3319d8ce2464864fa9f30c5fcaf958a3eed92f4f1fef8", size = 221716, upload-time = "2026-01-25T12:58:40.484Z" }, - { url = "https://files.pythonhosted.org/packages/0d/82/e5fcf5a97c72f45fc14829237a6550bf49d0ab882ac90e04b12a69db76b4/coverage-7.13.2-cp313-cp313-win_amd64.whl", hash = "sha256:14f500232e521201cf031549fb1ebdfc0a40f401cf519157f76c397e586c3beb", size = 222522, upload-time = "2026-01-25T12:58:43.247Z" }, - { url = "https://files.pythonhosted.org/packages/b1/f1/25d7b2f946d239dd2d6644ca2cc060d24f97551e2af13b6c24c722ae5f97/coverage-7.13.2-cp313-cp313-win_arm64.whl", hash = "sha256:9779310cb5a9778a60c899f075a8514c89fa6d10131445c2207fc893e0b14557", size = 221145, upload-time = "2026-01-25T12:58:45Z" }, - { url = "https://files.pythonhosted.org/packages/9e/f7/080376c029c8f76fadfe43911d0daffa0cbdc9f9418a0eead70c56fb7f4b/coverage-7.13.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e64fa5a1e41ce5df6b547cbc3d3699381c9e2c2c369c67837e716ed0f549d48e", size = 219861, upload-time = "2026-01-25T12:58:46.586Z" }, - { url = "https://files.pythonhosted.org/packages/42/11/0b5e315af5ab35f4c4a70e64d3314e4eec25eefc6dec13be3a7d5ffe8ac5/coverage-7.13.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b01899e82a04085b6561eb233fd688474f57455e8ad35cd82286463ba06332b7", size = 220207, upload-time = "2026-01-25T12:58:48.277Z" }, - { url = "https://files.pythonhosted.org/packages/b2/0c/0874d0318fb1062117acbef06a09cf8b63f3060c22265adaad24b36306b7/coverage-7.13.2-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:838943bea48be0e2768b0cf7819544cdedc1bbb2f28427eabb6eb8c9eb2285d3", size = 261504, upload-time = "2026-01-25T12:58:49.904Z" }, - { url = "https://files.pythonhosted.org/packages/83/5e/1cd72c22ecb30751e43a72f40ba50fcef1b7e93e3ea823bd9feda8e51f9a/coverage-7.13.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:93d1d25ec2b27e90bcfef7012992d1f5121b51161b8bffcda756a816cf13c2c3", size = 263582, upload-time = "2026-01-25T12:58:51.582Z" }, - { url = "https://files.pythonhosted.org/packages/9b/da/8acf356707c7a42df4d0657020308e23e5a07397e81492640c186268497c/coverage-7.13.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93b57142f9621b0d12349c43fc7741fe578e4bc914c1e5a54142856cfc0bf421", size = 266008, upload-time = "2026-01-25T12:58:53.234Z" }, - { url = "https://files.pythonhosted.org/packages/41/41/ea1730af99960309423c6ea8d6a4f1fa5564b2d97bd1d29dda4b42611f04/coverage-7.13.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f06799ae1bdfff7ccb8665d75f8291c69110ba9585253de254688aa8a1ccc6c5", size = 260762, upload-time = "2026-01-25T12:58:55.372Z" }, - { url = "https://files.pythonhosted.org/packages/22/fa/02884d2080ba71db64fdc127b311db60e01fe6ba797d9c8363725e39f4d5/coverage-7.13.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7f9405ab4f81d490811b1d91c7a20361135a2df4c170e7f0b747a794da5b7f23", size = 263571, upload-time = "2026-01-25T12:58:57.52Z" }, - { url = "https://files.pythonhosted.org/packages/d2/6b/4083aaaeba9b3112f55ac57c2ce7001dc4d8fa3fcc228a39f09cc84ede27/coverage-7.13.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f9ab1d5b86f8fbc97a5b3cd6280a3fd85fef3b028689d8a2c00918f0d82c728c", size = 261200, upload-time = "2026-01-25T12:58:59.255Z" }, - { url = "https://files.pythonhosted.org/packages/e9/d2/aea92fa36d61955e8c416ede9cf9bf142aa196f3aea214bb67f85235a050/coverage-7.13.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:f674f59712d67e841525b99e5e2b595250e39b529c3bda14764e4f625a3fa01f", size = 260095, upload-time = "2026-01-25T12:59:01.066Z" }, - { url = "https://files.pythonhosted.org/packages/0d/ae/04ffe96a80f107ea21b22b2367175c621da920063260a1c22f9452fd7866/coverage-7.13.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c6cadac7b8ace1ba9144feb1ae3cb787a6065ba6d23ffc59a934b16406c26573", size = 262284, upload-time = "2026-01-25T12:59:02.802Z" }, - { url = "https://files.pythonhosted.org/packages/1c/7a/6f354dcd7dfc41297791d6fb4e0d618acb55810bde2c1fd14b3939e05c2b/coverage-7.13.2-cp313-cp313t-win32.whl", hash = "sha256:14ae4146465f8e6e6253eba0cccd57423e598a4cb925958b240c805300918343", size = 222389, upload-time = "2026-01-25T12:59:04.563Z" }, - { url = "https://files.pythonhosted.org/packages/8d/d5/080ad292a4a3d3daf411574be0a1f56d6dee2c4fdf6b005342be9fac807f/coverage-7.13.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9074896edd705a05769e3de0eac0a8388484b503b68863dd06d5e473f874fd47", size = 223450, upload-time = "2026-01-25T12:59:06.677Z" }, - { url = "https://files.pythonhosted.org/packages/88/96/df576fbacc522e9fb8d1c4b7a7fc62eb734be56e2cba1d88d2eabe08ea3f/coverage-7.13.2-cp313-cp313t-win_arm64.whl", hash = "sha256:69e526e14f3f854eda573d3cf40cffd29a1a91c684743d904c33dbdcd0e0f3e7", size = 221707, upload-time = "2026-01-25T12:59:08.363Z" }, - { url = "https://files.pythonhosted.org/packages/55/53/1da9e51a0775634b04fcc11eb25c002fc58ee4f92ce2e8512f94ac5fc5bf/coverage-7.13.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:387a825f43d680e7310e6f325b2167dd093bc8ffd933b83e9aa0983cf6e0a2ef", size = 219213, upload-time = "2026-01-25T12:59:11.909Z" }, - { url = "https://files.pythonhosted.org/packages/46/35/b3caac3ebbd10230fea5a33012b27d19e999a17c9285c4228b4b2e35b7da/coverage-7.13.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f0d7fea9d8e5d778cd5a9e8fc38308ad688f02040e883cdc13311ef2748cb40f", size = 219549, upload-time = "2026-01-25T12:59:13.638Z" }, - { url = "https://files.pythonhosted.org/packages/76/9c/e1cf7def1bdc72c1907e60703983a588f9558434a2ff94615747bd73c192/coverage-7.13.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e080afb413be106c95c4ee96b4fffdc9e2fa56a8bbf90b5c0918e5c4449412f5", size = 250586, upload-time = "2026-01-25T12:59:15.808Z" }, - { url = "https://files.pythonhosted.org/packages/ba/49/f54ec02ed12be66c8d8897270505759e057b0c68564a65c429ccdd1f139e/coverage-7.13.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a7fc042ba3c7ce25b8a9f097eb0f32a5ce1ccdb639d9eec114e26def98e1f8a4", size = 253093, upload-time = "2026-01-25T12:59:17.491Z" }, - { url = "https://files.pythonhosted.org/packages/fb/5e/aaf86be3e181d907e23c0f61fccaeb38de8e6f6b47aed92bf57d8fc9c034/coverage-7.13.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d0ba505e021557f7f8173ee8cd6b926373d8653e5ff7581ae2efce1b11ef4c27", size = 254446, upload-time = "2026-01-25T12:59:19.752Z" }, - { url = "https://files.pythonhosted.org/packages/28/c8/a5fa01460e2d75b0c853b392080d6829d3ca8b5ab31e158fa0501bc7c708/coverage-7.13.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7de326f80e3451bd5cc7239ab46c73ddb658fe0b7649476bc7413572d36cd548", size = 250615, upload-time = "2026-01-25T12:59:21.928Z" }, - { url = "https://files.pythonhosted.org/packages/86/0b/6d56315a55f7062bb66410732c24879ccb2ec527ab6630246de5fe45a1df/coverage-7.13.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:abaea04f1e7e34841d4a7b343904a3f59481f62f9df39e2cd399d69a187a9660", size = 252452, upload-time = "2026-01-25T12:59:23.592Z" }, - { url = "https://files.pythonhosted.org/packages/30/19/9bc550363ebc6b0ea121977ee44d05ecd1e8bf79018b8444f1028701c563/coverage-7.13.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9f93959ee0c604bccd8e0697be21de0887b1f73efcc3aa73a3ec0fd13feace92", size = 250418, upload-time = "2026-01-25T12:59:25.392Z" }, - { url = "https://files.pythonhosted.org/packages/1f/53/580530a31ca2f0cc6f07a8f2ab5460785b02bb11bdf815d4c4d37a4c5169/coverage-7.13.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:13fe81ead04e34e105bf1b3c9f9cdf32ce31736ee5d90a8d2de02b9d3e1bcb82", size = 250231, upload-time = "2026-01-25T12:59:27.888Z" }, - { url = "https://files.pythonhosted.org/packages/e2/42/dd9093f919dc3088cb472893651884bd675e3df3d38a43f9053656dca9a2/coverage-7.13.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d6d16b0f71120e365741bca2cb473ca6fe38930bc5431c5e850ba949f708f892", size = 251888, upload-time = "2026-01-25T12:59:29.636Z" }, - { url = "https://files.pythonhosted.org/packages/fa/a6/0af4053e6e819774626e133c3d6f70fae4d44884bfc4b126cb647baee8d3/coverage-7.13.2-cp314-cp314-win32.whl", hash = "sha256:9b2f4714bb7d99ba3790ee095b3b4ac94767e1347fe424278a0b10acb3ff04fe", size = 221968, upload-time = "2026-01-25T12:59:31.424Z" }, - { url = "https://files.pythonhosted.org/packages/c4/cc/5aff1e1f80d55862442855517bb8ad8ad3a68639441ff6287dde6a58558b/coverage-7.13.2-cp314-cp314-win_amd64.whl", hash = "sha256:e4121a90823a063d717a96e0a0529c727fb31ea889369a0ee3ec00ed99bf6859", size = 222783, upload-time = "2026-01-25T12:59:33.118Z" }, - { url = "https://files.pythonhosted.org/packages/de/20/09abafb24f84b3292cc658728803416c15b79f9ee5e68d25238a895b07d9/coverage-7.13.2-cp314-cp314-win_arm64.whl", hash = "sha256:6873f0271b4a15a33e7590f338d823f6f66f91ed147a03938d7ce26efd04eee6", size = 221348, upload-time = "2026-01-25T12:59:34.939Z" }, - { url = "https://files.pythonhosted.org/packages/b6/60/a3820c7232db63be060e4019017cd3426751c2699dab3c62819cdbcea387/coverage-7.13.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f61d349f5b7cd95c34017f1927ee379bfbe9884300d74e07cf630ccf7a610c1b", size = 219950, upload-time = "2026-01-25T12:59:36.624Z" }, - { url = "https://files.pythonhosted.org/packages/fd/37/e4ef5975fdeb86b1e56db9a82f41b032e3d93a840ebaf4064f39e770d5c5/coverage-7.13.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a43d34ce714f4ca674c0d90beb760eb05aad906f2c47580ccee9da8fe8bfb417", size = 220209, upload-time = "2026-01-25T12:59:38.339Z" }, - { url = "https://files.pythonhosted.org/packages/54/df/d40e091d00c51adca1e251d3b60a8b464112efa3004949e96a74d7c19a64/coverage-7.13.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bff1b04cb9d4900ce5c56c4942f047dc7efe57e2608cb7c3c8936e9970ccdbee", size = 261576, upload-time = "2026-01-25T12:59:40.446Z" }, - { url = "https://files.pythonhosted.org/packages/c5/44/5259c4bed54e3392e5c176121af9f71919d96dde853386e7730e705f3520/coverage-7.13.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6ae99e4560963ad8e163e819e5d77d413d331fd00566c1e0856aa252303552c1", size = 263704, upload-time = "2026-01-25T12:59:42.346Z" }, - { url = "https://files.pythonhosted.org/packages/16/bd/ae9f005827abcbe2c70157459ae86053971c9fa14617b63903abbdce26d9/coverage-7.13.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e79a8c7d461820257d9aa43716c4efc55366d7b292e46b5b37165be1d377405d", size = 266109, upload-time = "2026-01-25T12:59:44.073Z" }, - { url = "https://files.pythonhosted.org/packages/a2/c0/8e279c1c0f5b1eaa3ad9b0fb7a5637fc0379ea7d85a781c0fe0bb3cfc2ab/coverage-7.13.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:060ee84f6a769d40c492711911a76811b4befb6fba50abb450371abb720f5bd6", size = 260686, upload-time = "2026-01-25T12:59:45.804Z" }, - { url = "https://files.pythonhosted.org/packages/b2/47/3a8112627e9d863e7cddd72894171c929e94491a597811725befdcd76bce/coverage-7.13.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3bca209d001fd03ea2d978f8a4985093240a355c93078aee3f799852c23f561a", size = 263568, upload-time = "2026-01-25T12:59:47.929Z" }, - { url = "https://files.pythonhosted.org/packages/92/bc/7ea367d84afa3120afc3ce6de294fd2dcd33b51e2e7fbe4bbfd200f2cb8c/coverage-7.13.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:6b8092aa38d72f091db61ef83cb66076f18f02da3e1a75039a4f218629600e04", size = 261174, upload-time = "2026-01-25T12:59:49.717Z" }, - { url = "https://files.pythonhosted.org/packages/33/b7/f1092dcecb6637e31cc2db099581ee5c61a17647849bae6b8261a2b78430/coverage-7.13.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4a3158dc2dcce5200d91ec28cd315c999eebff355437d2765840555d765a6e5f", size = 260017, upload-time = "2026-01-25T12:59:51.463Z" }, - { url = "https://files.pythonhosted.org/packages/2b/cd/f3d07d4b95fbe1a2ef0958c15da614f7e4f557720132de34d2dc3aa7e911/coverage-7.13.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3973f353b2d70bd9796cc12f532a05945232ccae966456c8ed7034cb96bbfd6f", size = 262337, upload-time = "2026-01-25T12:59:53.407Z" }, - { url = "https://files.pythonhosted.org/packages/e0/db/b0d5b2873a07cb1e06a55d998697c0a5a540dcefbf353774c99eb3874513/coverage-7.13.2-cp314-cp314t-win32.whl", hash = "sha256:79f6506a678a59d4ded048dc72f1859ebede8ec2b9a2d509ebe161f01c2879d3", size = 222749, upload-time = "2026-01-25T12:59:56.316Z" }, - { url = "https://files.pythonhosted.org/packages/e5/2f/838a5394c082ac57d85f57f6aba53093b30d9089781df72412126505716f/coverage-7.13.2-cp314-cp314t-win_amd64.whl", hash = "sha256:196bfeabdccc5a020a57d5a368c681e3a6ceb0447d153aeccc1ab4d70a5032ba", size = 223857, upload-time = "2026-01-25T12:59:58.201Z" }, - { url = "https://files.pythonhosted.org/packages/44/d4/b608243e76ead3a4298824b50922b89ef793e50069ce30316a65c1b4d7ef/coverage-7.13.2-cp314-cp314t-win_arm64.whl", hash = "sha256:69269ab58783e090bfbf5b916ab3d188126e22d6070bbfc93098fdd474ef937c", size = 221881, upload-time = "2026-01-25T13:00:00.449Z" }, - { url = "https://files.pythonhosted.org/packages/d2/db/d291e30fdf7ea617a335531e72294e0c723356d7fdde8fba00610a76bda9/coverage-7.13.2-py3-none-any.whl", hash = "sha256:40ce1ea1e25125556d8e76bd0b61500839a07944cc287ac21d5626f3e620cad5", size = 210943, upload-time = "2026-01-25T13:00:02.388Z" }, + { url = "https://files.pythonhosted.org/packages/ab/07/1c8099563a8a6c389a31c2d0aa1497cee86d6248bb4b9ba5e779215db9f9/coverage-7.13.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0b4f345f7265cdbdb5ec2521ffff15fa49de6d6c39abf89fc7ad68aa9e3a55f0", size = 219143, upload-time = "2026-02-03T13:59:40.459Z" }, + { url = "https://files.pythonhosted.org/packages/69/39/a892d44af7aa092cab70e0cc5cdbba18eeccfe1d6930695dab1742eef9e9/coverage-7.13.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:96c3be8bae9d0333e403cc1a8eb078a7f928b5650bae94a18fb4820cc993fb9b", size = 219663, upload-time = "2026-02-03T13:59:41.951Z" }, + { url = "https://files.pythonhosted.org/packages/9a/25/9669dcf4c2bb4c3861469e6db20e52e8c11908cf53c14ec9b12e9fd4d602/coverage-7.13.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d6f4a21328ea49d38565b55599e1c02834e76583a6953e5586d65cb1efebd8f8", size = 246424, upload-time = "2026-02-03T13:59:43.418Z" }, + { url = "https://files.pythonhosted.org/packages/f3/68/d9766c4e298aca62ea5d9543e1dd1e4e1439d7284815244d8b7db1840bfb/coverage-7.13.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fc970575799a9d17d5c3fafd83a0f6ccf5d5117cdc9ad6fbd791e9ead82418b0", size = 248228, upload-time = "2026-02-03T13:59:44.816Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e2/eea6cb4a4bd443741adf008d4cccec83a1f75401df59b6559aca2bdd9710/coverage-7.13.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:87ff33b652b3556b05e204ae20793d1f872161b0fa5ec8a9ac76f8430e152ed6", size = 250103, upload-time = "2026-02-03T13:59:46.271Z" }, + { url = "https://files.pythonhosted.org/packages/db/77/664280ecd666c2191610842177e2fab9e5dbdeef97178e2078fed46a3d2c/coverage-7.13.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7df8759ee57b9f3f7b66799b7660c282f4375bef620ade1686d6a7b03699e75f", size = 247107, upload-time = "2026-02-03T13:59:48.53Z" }, + { url = "https://files.pythonhosted.org/packages/2b/df/2a672eab99e0d0eba52d8a63e47dc92245eee26954d1b2d3c8f7d372151f/coverage-7.13.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f45c9bcb16bee25a798ccba8a2f6a1251b19de6a0d617bb365d7d2f386c4e20e", size = 248143, upload-time = "2026-02-03T13:59:50.027Z" }, + { url = "https://files.pythonhosted.org/packages/a5/dc/a104e7a87c13e57a358b8b9199a8955676e1703bb372d79722b54978ae45/coverage-7.13.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:318b2e4753cbf611061e01b6cc81477e1cdfeb69c36c4a14e6595e674caadb56", size = 246148, upload-time = "2026-02-03T13:59:52.025Z" }, + { url = "https://files.pythonhosted.org/packages/2b/89/e113d3a58dc20b03b7e59aed1e53ebc9ca6167f961876443e002b10e3ae9/coverage-7.13.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:24db3959de8ee394eeeca89ccb8ba25305c2da9a668dd44173394cbd5aa0777f", size = 246414, upload-time = "2026-02-03T13:59:53.859Z" }, + { url = "https://files.pythonhosted.org/packages/3f/60/a3fd0a6e8d89b488396019a2268b6a1f25ab56d6d18f3be50f35d77b47dc/coverage-7.13.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:be14d0622125edef21b3a4d8cd2d138c4872bf6e38adc90fd92385e3312f406a", size = 247023, upload-time = "2026-02-03T13:59:55.454Z" }, + { url = "https://files.pythonhosted.org/packages/19/fa/de4840bb939dbb22ba0648a6d8069fa91c9cf3b3fca8b0d1df461e885b3d/coverage-7.13.3-cp310-cp310-win32.whl", hash = "sha256:53be4aab8ddef18beb6188f3a3fdbf4d1af2277d098d4e618be3a8e6c88e74be", size = 221751, upload-time = "2026-02-03T13:59:57.383Z" }, + { url = "https://files.pythonhosted.org/packages/de/87/233ff8b7ef62fb63f58c78623b50bef69681111e0c4d43504f422d88cda4/coverage-7.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:bfeee64ad8b4aae3233abb77eb6b52b51b05fa89da9645518671b9939a78732b", size = 222686, upload-time = "2026-02-03T13:59:58.825Z" }, + { url = "https://files.pythonhosted.org/packages/ec/09/1ac74e37cf45f17eb41e11a21854f7f92a4c2d6c6098ef4a1becb0c6d8d3/coverage-7.13.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5907605ee20e126eeee2abe14aae137043c2c8af2fa9b38d2ab3b7a6b8137f73", size = 219276, upload-time = "2026-02-03T14:00:00.296Z" }, + { url = "https://files.pythonhosted.org/packages/2e/cb/71908b08b21beb2c437d0d5870c4ec129c570ca1b386a8427fcdb11cf89c/coverage-7.13.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a88705500988c8acad8b8fd86c2a933d3aa96bec1ddc4bc5cb256360db7bbd00", size = 219776, upload-time = "2026-02-03T14:00:02.414Z" }, + { url = "https://files.pythonhosted.org/packages/09/85/c4f3dd69232887666a2c0394d4be21c60ea934d404db068e6c96aa59cd87/coverage-7.13.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7bbb5aa9016c4c29e3432e087aa29ebee3f8fda089cfbfb4e6d64bd292dcd1c2", size = 250196, upload-time = "2026-02-03T14:00:04.197Z" }, + { url = "https://files.pythonhosted.org/packages/9c/cc/560ad6f12010344d0778e268df5ba9aa990aacccc310d478bf82bf3d302c/coverage-7.13.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0c2be202a83dde768937a61cdc5d06bf9fb204048ca199d93479488e6247656c", size = 252111, upload-time = "2026-02-03T14:00:05.639Z" }, + { url = "https://files.pythonhosted.org/packages/f0/66/3193985fb2c58e91f94cfbe9e21a6fdf941e9301fe2be9e92c072e9c8f8c/coverage-7.13.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f45e32ef383ce56e0ca099b2e02fcdf7950be4b1b56afaab27b4ad790befe5b", size = 254217, upload-time = "2026-02-03T14:00:07.738Z" }, + { url = "https://files.pythonhosted.org/packages/c5/78/f0f91556bf1faa416792e537c523c5ef9db9b1d32a50572c102b3d7c45b3/coverage-7.13.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6ed2e787249b922a93cd95c671cc9f4c9797a106e81b455c83a9ddb9d34590c0", size = 250318, upload-time = "2026-02-03T14:00:09.224Z" }, + { url = "https://files.pythonhosted.org/packages/6f/aa/fc654e45e837d137b2c1f3a2cc09b4aea1e8b015acd2f774fa0f3d2ddeba/coverage-7.13.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:05dd25b21afffe545e808265897c35f32d3e4437663923e0d256d9ab5031fb14", size = 251909, upload-time = "2026-02-03T14:00:10.712Z" }, + { url = "https://files.pythonhosted.org/packages/73/4d/ab53063992add8a9ca0463c9d92cce5994a29e17affd1c2daa091b922a93/coverage-7.13.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:46d29926349b5c4f1ea4fca95e8c892835515f3600995a383fa9a923b5739ea4", size = 249971, upload-time = "2026-02-03T14:00:12.402Z" }, + { url = "https://files.pythonhosted.org/packages/29/25/83694b81e46fcff9899694a1b6f57573429cdd82b57932f09a698f03eea5/coverage-7.13.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:fae6a21537519c2af00245e834e5bf2884699cc7c1055738fd0f9dc37a3644ad", size = 249692, upload-time = "2026-02-03T14:00:13.868Z" }, + { url = "https://files.pythonhosted.org/packages/d4/ef/d68fc304301f4cb4bf6aefa0045310520789ca38dabdfba9dbecd3f37919/coverage-7.13.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c672d4e2f0575a4ca2bf2aa0c5ced5188220ab806c1bb6d7179f70a11a017222", size = 250597, upload-time = "2026-02-03T14:00:15.461Z" }, + { url = "https://files.pythonhosted.org/packages/8d/85/240ad396f914df361d0f71e912ddcedb48130c71b88dc4193fe3c0306f00/coverage-7.13.3-cp311-cp311-win32.whl", hash = "sha256:fcda51c918c7a13ad93b5f89a58d56e3a072c9e0ba5c231b0ed81404bf2648fb", size = 221773, upload-time = "2026-02-03T14:00:17.462Z" }, + { url = "https://files.pythonhosted.org/packages/2f/71/165b3a6d3d052704a9ab52d11ea64ef3426745de517dda44d872716213a7/coverage-7.13.3-cp311-cp311-win_amd64.whl", hash = "sha256:d1a049b5c51b3b679928dd35e47c4a2235e0b6128b479a7596d0ef5b42fa6301", size = 222711, upload-time = "2026-02-03T14:00:19.449Z" }, + { url = "https://files.pythonhosted.org/packages/51/d0/0ddc9c5934cdd52639c5df1f1eb0fdab51bb52348f3a8d1c7db9c600d93a/coverage-7.13.3-cp311-cp311-win_arm64.whl", hash = "sha256:79f2670c7e772f4917895c3d89aad59e01f3dbe68a4ed2d0373b431fad1dcfba", size = 221377, upload-time = "2026-02-03T14:00:20.968Z" }, + { url = "https://files.pythonhosted.org/packages/94/44/330f8e83b143f6668778ed61d17ece9dc48459e9e74669177de02f45fec5/coverage-7.13.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ed48b4170caa2c4420e0cd27dc977caaffc7eecc317355751df8373dddcef595", size = 219441, upload-time = "2026-02-03T14:00:22.585Z" }, + { url = "https://files.pythonhosted.org/packages/08/e7/29db05693562c2e65bdf6910c0af2fd6f9325b8f43caf7a258413f369e30/coverage-7.13.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8f2adf4bcffbbec41f366f2e6dffb9d24e8172d16e91da5799c9b7ed6b5716e6", size = 219801, upload-time = "2026-02-03T14:00:24.186Z" }, + { url = "https://files.pythonhosted.org/packages/90/ae/7f8a78249b02b0818db46220795f8ac8312ea4abd1d37d79ea81db5cae81/coverage-7.13.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:01119735c690786b6966a1e9f098da4cd7ca9174c4cfe076d04e653105488395", size = 251306, upload-time = "2026-02-03T14:00:25.798Z" }, + { url = "https://files.pythonhosted.org/packages/62/71/a18a53d1808e09b2e9ebd6b47dad5e92daf4c38b0686b4c4d1b2f3e42b7f/coverage-7.13.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8bb09e83c603f152d855f666d70a71765ca8e67332e5829e62cb9466c176af23", size = 254051, upload-time = "2026-02-03T14:00:27.474Z" }, + { url = "https://files.pythonhosted.org/packages/4a/0a/eb30f6455d04c5a3396d0696cad2df0269ae7444bb322f86ffe3376f7bf9/coverage-7.13.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b607a40cba795cfac6d130220d25962931ce101f2f478a29822b19755377fb34", size = 255160, upload-time = "2026-02-03T14:00:29.024Z" }, + { url = "https://files.pythonhosted.org/packages/7b/7e/a45baac86274ce3ed842dbb84f14560c673ad30535f397d89164ec56c5df/coverage-7.13.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:44f14a62f5da2e9aedf9080e01d2cda61df39197d48e323538ec037336d68da8", size = 251709, upload-time = "2026-02-03T14:00:30.641Z" }, + { url = "https://files.pythonhosted.org/packages/c0/df/dd0dc12f30da11349993f3e218901fdf82f45ee44773596050c8f5a1fb25/coverage-7.13.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:debf29e0b157769843dff0981cc76f79e0ed04e36bb773c6cac5f6029054bd8a", size = 253083, upload-time = "2026-02-03T14:00:32.14Z" }, + { url = "https://files.pythonhosted.org/packages/ab/32/fc764c8389a8ce95cb90eb97af4c32f392ab0ac23ec57cadeefb887188d3/coverage-7.13.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:824bb95cd71604031ae9a48edb91fd6effde669522f960375668ed21b36e3ec4", size = 251227, upload-time = "2026-02-03T14:00:34.721Z" }, + { url = "https://files.pythonhosted.org/packages/dd/ca/d025e9da8f06f24c34d2da9873957cfc5f7e0d67802c3e34d0caa8452130/coverage-7.13.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8f1010029a5b52dc427c8e2a8dbddb2303ddd180b806687d1acd1bb1d06649e7", size = 250794, upload-time = "2026-02-03T14:00:36.278Z" }, + { url = "https://files.pythonhosted.org/packages/45/c7/76bf35d5d488ec8f68682eb8e7671acc50a6d2d1c1182de1d2b6d4ffad3b/coverage-7.13.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cd5dee4fd7659d8306ffa79eeaaafd91fa30a302dac3af723b9b469e549247e0", size = 252671, upload-time = "2026-02-03T14:00:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/bf/10/1921f1a03a7c209e1cb374f81a6b9b68b03cdb3ecc3433c189bc90e2a3d5/coverage-7.13.3-cp312-cp312-win32.whl", hash = "sha256:f7f153d0184d45f3873b3ad3ad22694fd73aadcb8cdbc4337ab4b41ea6b4dff1", size = 221986, upload-time = "2026-02-03T14:00:40.442Z" }, + { url = "https://files.pythonhosted.org/packages/3c/7c/f5d93297f8e125a80c15545edc754d93e0ed8ba255b65e609b185296af01/coverage-7.13.3-cp312-cp312-win_amd64.whl", hash = "sha256:03a6e5e1e50819d6d7436f5bc40c92ded7e484e400716886ac921e35c133149d", size = 222793, upload-time = "2026-02-03T14:00:42.106Z" }, + { url = "https://files.pythonhosted.org/packages/43/59/c86b84170015b4555ebabca8649bdf9f4a1f737a73168088385ed0f947c4/coverage-7.13.3-cp312-cp312-win_arm64.whl", hash = "sha256:51c4c42c0e7d09a822b08b6cf79b3c4db8333fffde7450da946719ba0d45730f", size = 221410, upload-time = "2026-02-03T14:00:43.726Z" }, + { url = "https://files.pythonhosted.org/packages/81/f3/4c333da7b373e8c8bfb62517e8174a01dcc373d7a9083698e3b39d50d59c/coverage-7.13.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:853c3d3c79ff0db65797aad79dee6be020efd218ac4510f15a205f1e8d13ce25", size = 219468, upload-time = "2026-02-03T14:00:45.829Z" }, + { url = "https://files.pythonhosted.org/packages/d6/31/0714337b7d23630c8de2f4d56acf43c65f8728a45ed529b34410683f7217/coverage-7.13.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f75695e157c83d374f88dcc646a60cb94173304a9258b2e74ba5a66b7614a51a", size = 219839, upload-time = "2026-02-03T14:00:47.407Z" }, + { url = "https://files.pythonhosted.org/packages/12/99/bd6f2a2738144c98945666f90cae446ed870cecf0421c767475fcf42cdbe/coverage-7.13.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2d098709621d0819039f3f1e471ee554f55a0b2ac0d816883c765b14129b5627", size = 250828, upload-time = "2026-02-03T14:00:49.029Z" }, + { url = "https://files.pythonhosted.org/packages/6f/99/97b600225fbf631e6f5bfd3ad5bcaf87fbb9e34ff87492e5a572ff01bbe2/coverage-7.13.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16d23d6579cf80a474ad160ca14d8b319abaa6db62759d6eef53b2fc979b58c8", size = 253432, upload-time = "2026-02-03T14:00:50.655Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5c/abe2b3490bda26bd4f5e3e799be0bdf00bd81edebedc2c9da8d3ef288fa8/coverage-7.13.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00d34b29a59d2076e6f318b30a00a69bf63687e30cd882984ed444e753990cc1", size = 254672, upload-time = "2026-02-03T14:00:52.757Z" }, + { url = "https://files.pythonhosted.org/packages/31/ba/5d1957c76b40daff53971fe0adb84d9c2162b614280031d1d0653dd010c1/coverage-7.13.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ab6d72bffac9deb6e6cb0f61042e748de3f9f8e98afb0375a8e64b0b6e11746b", size = 251050, upload-time = "2026-02-03T14:00:54.332Z" }, + { url = "https://files.pythonhosted.org/packages/69/dc/dffdf3bfe9d32090f047d3c3085378558cb4eb6778cda7de414ad74581ed/coverage-7.13.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e129328ad1258e49cae0123a3b5fcb93d6c2fa90d540f0b4c7cdcdc019aaa3dc", size = 252801, upload-time = "2026-02-03T14:00:56.121Z" }, + { url = "https://files.pythonhosted.org/packages/87/51/cdf6198b0f2746e04511a30dc9185d7b8cdd895276c07bdb538e37f1cd50/coverage-7.13.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2213a8d88ed35459bda71597599d4eec7c2ebad201c88f0bfc2c26fd9b0dd2ea", size = 250763, upload-time = "2026-02-03T14:00:58.719Z" }, + { url = "https://files.pythonhosted.org/packages/d7/1a/596b7d62218c1d69f2475b69cc6b211e33c83c902f38ee6ae9766dd422da/coverage-7.13.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:00dd3f02de6d5f5c9c3d95e3e036c3c2e2a669f8bf2d3ceb92505c4ce7838f67", size = 250587, upload-time = "2026-02-03T14:01:01.197Z" }, + { url = "https://files.pythonhosted.org/packages/f7/46/52330d5841ff660f22c130b75f5e1dd3e352c8e7baef5e5fef6b14e3e991/coverage-7.13.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f9bada7bc660d20b23d7d312ebe29e927b655cf414dadcdb6335a2075695bd86", size = 252358, upload-time = "2026-02-03T14:01:02.824Z" }, + { url = "https://files.pythonhosted.org/packages/36/8a/e69a5be51923097ba7d5cff9724466e74fe486e9232020ba97c809a8b42b/coverage-7.13.3-cp313-cp313-win32.whl", hash = "sha256:75b3c0300f3fa15809bd62d9ca8b170eb21fcf0100eb4b4154d6dc8b3a5bbd43", size = 222007, upload-time = "2026-02-03T14:01:04.876Z" }, + { url = "https://files.pythonhosted.org/packages/0a/09/a5a069bcee0d613bdd48ee7637fa73bc09e7ed4342b26890f2df97cc9682/coverage-7.13.3-cp313-cp313-win_amd64.whl", hash = "sha256:a2f7589c6132c44c53f6e705e1a6677e2b7821378c22f7703b2cf5388d0d4587", size = 222812, upload-time = "2026-02-03T14:01:07.296Z" }, + { url = "https://files.pythonhosted.org/packages/3d/4f/d62ad7dfe32f9e3d4a10c178bb6f98b10b083d6e0530ca202b399371f6c1/coverage-7.13.3-cp313-cp313-win_arm64.whl", hash = "sha256:123ceaf2b9d8c614f01110f908a341e05b1b305d6b2ada98763b9a5a59756051", size = 221433, upload-time = "2026-02-03T14:01:09.156Z" }, + { url = "https://files.pythonhosted.org/packages/04/b2/4876c46d723d80b9c5b695f1a11bf5f7c3dabf540ec00d6edc076ff025e6/coverage-7.13.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:cc7fd0f726795420f3678ac82ff882c7fc33770bd0074463b5aef7293285ace9", size = 220162, upload-time = "2026-02-03T14:01:11.409Z" }, + { url = "https://files.pythonhosted.org/packages/fc/04/9942b64a0e0bdda2c109f56bda42b2a59d9d3df4c94b85a323c1cae9fc77/coverage-7.13.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d358dc408edc28730aed5477a69338e444e62fba0b7e9e4a131c505fadad691e", size = 220510, upload-time = "2026-02-03T14:01:13.038Z" }, + { url = "https://files.pythonhosted.org/packages/5a/82/5cfe1e81eae525b74669f9795f37eb3edd4679b873d79d1e6c1c14ee6c1c/coverage-7.13.3-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5d67b9ed6f7b5527b209b24b3df9f2e5bf0198c1bbf99c6971b0e2dcb7e2a107", size = 261801, upload-time = "2026-02-03T14:01:14.674Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ec/a553d7f742fd2cd12e36a16a7b4b3582d5934b496ef2b5ea8abeb10903d4/coverage-7.13.3-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:59224bfb2e9b37c1335ae35d00daa3a5b4e0b1a20f530be208fff1ecfa436f43", size = 263882, upload-time = "2026-02-03T14:01:16.343Z" }, + { url = "https://files.pythonhosted.org/packages/e1/58/8f54a2a93e3d675635bc406de1c9ac8d551312142ff52c9d71b5e533ad45/coverage-7.13.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9306b5299e31e31e0d3b908c66bcb6e7e3ddca143dea0266e9ce6c667346d3", size = 266306, upload-time = "2026-02-03T14:01:18.02Z" }, + { url = "https://files.pythonhosted.org/packages/1a/be/e593399fd6ea1f00aee79ebd7cc401021f218d34e96682a92e1bae092ff6/coverage-7.13.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:343aaeb5f8bb7bcd38620fd7bc56e6ee8207847d8c6103a1e7b72322d381ba4a", size = 261051, upload-time = "2026-02-03T14:01:19.757Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e5/e9e0f6138b21bcdebccac36fbfde9cf15eb1bbcea9f5b1f35cd1f465fb91/coverage-7.13.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b2182129f4c101272ff5f2f18038d7b698db1bf8e7aa9e615cb48440899ad32e", size = 263868, upload-time = "2026-02-03T14:01:21.487Z" }, + { url = "https://files.pythonhosted.org/packages/9a/bf/de72cfebb69756f2d4a2dde35efcc33c47d85cd3ebdf844b3914aac2ef28/coverage-7.13.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:94d2ac94bd0cc57c5626f52f8c2fffed1444b5ae8c9fc68320306cc2b255e155", size = 261498, upload-time = "2026-02-03T14:01:23.097Z" }, + { url = "https://files.pythonhosted.org/packages/f2/91/4a2d313a70fc2e98ca53afd1c8ce67a89b1944cd996589a5b1fe7fbb3e5c/coverage-7.13.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:65436cde5ecabe26fb2f0bf598962f0a054d3f23ad529361326ac002c61a2a1e", size = 260394, upload-time = "2026-02-03T14:01:24.949Z" }, + { url = "https://files.pythonhosted.org/packages/40/83/25113af7cf6941e779eb7ed8de2a677865b859a07ccee9146d4cc06a03e3/coverage-7.13.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:db83b77f97129813dbd463a67e5335adc6a6a91db652cc085d60c2d512746f96", size = 262579, upload-time = "2026-02-03T14:01:26.703Z" }, + { url = "https://files.pythonhosted.org/packages/1e/19/a5f2b96262977e82fb9aabbe19b4d83561f5d063f18dde3e72f34ffc3b2f/coverage-7.13.3-cp313-cp313t-win32.whl", hash = "sha256:dfb428e41377e6b9ba1b0a32df6db5409cb089a0ed1d0a672dc4953ec110d84f", size = 222679, upload-time = "2026-02-03T14:01:28.553Z" }, + { url = "https://files.pythonhosted.org/packages/81/82/ef1747b88c87a5c7d7edc3704799ebd650189a9158e680a063308b6125ef/coverage-7.13.3-cp313-cp313t-win_amd64.whl", hash = "sha256:5badd7e596e6b0c89aa8ec6d37f4473e4357f982ce57f9a2942b0221cd9cf60c", size = 223740, upload-time = "2026-02-03T14:01:30.776Z" }, + { url = "https://files.pythonhosted.org/packages/1c/4c/a67c7bb5b560241c22736a9cb2f14c5034149ffae18630323fde787339e4/coverage-7.13.3-cp313-cp313t-win_arm64.whl", hash = "sha256:989aa158c0eb19d83c76c26f4ba00dbb272485c56e452010a3450bdbc9daafd9", size = 221996, upload-time = "2026-02-03T14:01:32.495Z" }, + { url = "https://files.pythonhosted.org/packages/5e/b3/677bb43427fed9298905106f39c6520ac75f746f81b8f01104526a8026e4/coverage-7.13.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c6f6169bbdbdb85aab8ac0392d776948907267fcc91deeacf6f9d55f7a83ae3b", size = 219513, upload-time = "2026-02-03T14:01:34.29Z" }, + { url = "https://files.pythonhosted.org/packages/42/53/290046e3bbf8986cdb7366a42dab3440b9983711eaff044a51b11006c67b/coverage-7.13.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2f5e731627a3d5ef11a2a35aa0c6f7c435867c7ccbc391268eb4f2ca5dbdcc10", size = 219850, upload-time = "2026-02-03T14:01:35.984Z" }, + { url = "https://files.pythonhosted.org/packages/ea/2b/ab41f10345ba2e49d5e299be8663be2b7db33e77ac1b85cd0af985ea6406/coverage-7.13.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9db3a3285d91c0b70fab9f39f0a4aa37d375873677efe4e71e58d8321e8c5d39", size = 250886, upload-time = "2026-02-03T14:01:38.287Z" }, + { url = "https://files.pythonhosted.org/packages/72/2d/b3f6913ee5a1d5cdd04106f257e5fac5d048992ffc2d9995d07b0f17739f/coverage-7.13.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:06e49c5897cb12e3f7ecdc111d44e97c4f6d0557b81a7a0204ed70a8b038f86f", size = 253393, upload-time = "2026-02-03T14:01:40.118Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f6/b1f48810ffc6accf49a35b9943636560768f0812330f7456aa87dc39aff5/coverage-7.13.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb25061a66802df9fc13a9ba1967d25faa4dae0418db469264fd9860a921dde4", size = 254740, upload-time = "2026-02-03T14:01:42.413Z" }, + { url = "https://files.pythonhosted.org/packages/57/d0/e59c54f9be0b61808f6bc4c8c4346bd79f02dd6bbc3f476ef26124661f20/coverage-7.13.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:99fee45adbb1caeb914da16f70e557fb7ff6ddc9e4b14de665bd41af631367ef", size = 250905, upload-time = "2026-02-03T14:01:44.163Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f7/5291bcdf498bafbee3796bb32ef6966e9915aebd4d0954123c8eae921c32/coverage-7.13.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:318002f1fd819bdc1651c619268aa5bc853c35fa5cc6d1e8c96bd9cd6c828b75", size = 252753, upload-time = "2026-02-03T14:01:45.974Z" }, + { url = "https://files.pythonhosted.org/packages/a0/a9/1dcafa918c281554dae6e10ece88c1add82db685be123e1b05c2056ff3fb/coverage-7.13.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:71295f2d1d170b9977dc386d46a7a1b7cbb30e5405492529b4c930113a33f895", size = 250716, upload-time = "2026-02-03T14:01:48.844Z" }, + { url = "https://files.pythonhosted.org/packages/44/bb/4ea4eabcce8c4f6235df6e059fbc5db49107b24c4bdffc44aee81aeca5a8/coverage-7.13.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5b1ad2e0dc672625c44bc4fe34514602a9fd8b10d52ddc414dc585f74453516c", size = 250530, upload-time = "2026-02-03T14:01:50.793Z" }, + { url = "https://files.pythonhosted.org/packages/6d/31/4a6c9e6a71367e6f923b27b528448c37f4e959b7e4029330523014691007/coverage-7.13.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b2beb64c145593a50d90db5c7178f55daeae129123b0d265bdb3cbec83e5194a", size = 252186, upload-time = "2026-02-03T14:01:52.607Z" }, + { url = "https://files.pythonhosted.org/packages/27/92/e1451ef6390a4f655dc42da35d9971212f7abbbcad0bdb7af4407897eb76/coverage-7.13.3-cp314-cp314-win32.whl", hash = "sha256:3d1aed4f4e837a832df2f3b4f68a690eede0de4560a2dbc214ea0bc55aabcdb4", size = 222253, upload-time = "2026-02-03T14:01:55.071Z" }, + { url = "https://files.pythonhosted.org/packages/8a/98/78885a861a88de020c32a2693487c37d15a9873372953f0c3c159d575a43/coverage-7.13.3-cp314-cp314-win_amd64.whl", hash = "sha256:9f9efbbaf79f935d5fbe3ad814825cbce4f6cdb3054384cb49f0c0f496125fa0", size = 223069, upload-time = "2026-02-03T14:01:56.95Z" }, + { url = "https://files.pythonhosted.org/packages/eb/fb/3784753a48da58a5337972abf7ca58b1fb0f1bda21bc7b4fae992fd28e47/coverage-7.13.3-cp314-cp314-win_arm64.whl", hash = "sha256:31b6e889c53d4e6687ca63706148049494aace140cffece1c4dc6acadb70a7b3", size = 221633, upload-time = "2026-02-03T14:01:58.758Z" }, + { url = "https://files.pythonhosted.org/packages/40/f9/75b732d9674d32cdbffe801ed5f770786dd1c97eecedef2125b0d25102dc/coverage-7.13.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c5e9787cec750793a19a28df7edd85ac4e49d3fb91721afcdc3b86f6c08d9aa8", size = 220243, upload-time = "2026-02-03T14:02:01.109Z" }, + { url = "https://files.pythonhosted.org/packages/cf/7e/2868ec95de5a65703e6f0c87407ea822d1feb3619600fbc3c1c4fa986090/coverage-7.13.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e5b86db331c682fd0e4be7098e6acee5e8a293f824d41487c667a93705d415ca", size = 220515, upload-time = "2026-02-03T14:02:02.862Z" }, + { url = "https://files.pythonhosted.org/packages/7d/eb/9f0d349652fced20bcaea0f67fc5777bd097c92369f267975732f3dc5f45/coverage-7.13.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:edc7754932682d52cf6e7a71806e529ecd5ce660e630e8bd1d37109a2e5f63ba", size = 261874, upload-time = "2026-02-03T14:02:04.727Z" }, + { url = "https://files.pythonhosted.org/packages/ee/a5/6619bc4a6c7b139b16818149a3e74ab2e21599ff9a7b6811b6afde99f8ec/coverage-7.13.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3a16d6398666510a6886f67f43d9537bfd0e13aca299688a19daa84f543122f", size = 264004, upload-time = "2026-02-03T14:02:06.634Z" }, + { url = "https://files.pythonhosted.org/packages/29/b7/90aa3fc645a50c6f07881fca4fd0ba21e3bfb6ce3a7078424ea3a35c74c9/coverage-7.13.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:303d38b19626c1981e1bb067a9928236d88eb0e4479b18a74812f05a82071508", size = 266408, upload-time = "2026-02-03T14:02:09.037Z" }, + { url = "https://files.pythonhosted.org/packages/62/55/08bb2a1e4dcbae384e638f0effef486ba5987b06700e481691891427d879/coverage-7.13.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:284e06eadfe15ddfee2f4ee56631f164ef897a7d7d5a15bca5f0bb88889fc5ba", size = 260977, upload-time = "2026-02-03T14:02:11.755Z" }, + { url = "https://files.pythonhosted.org/packages/9b/76/8bd4ae055a42d8fb5dd2230e5cf36ff2e05f85f2427e91b11a27fea52ed7/coverage-7.13.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d401f0864a1d3198422816878e4e84ca89ec1c1bf166ecc0ae01380a39b888cd", size = 263868, upload-time = "2026-02-03T14:02:13.565Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f9/ba000560f11e9e32ec03df5aa8477242c2d95b379c99ac9a7b2e7fbacb1a/coverage-7.13.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3f379b02c18a64de78c4ccdddf1c81c2c5ae1956c72dacb9133d7dd7809794ab", size = 261474, upload-time = "2026-02-03T14:02:16.069Z" }, + { url = "https://files.pythonhosted.org/packages/90/4b/4de4de8f9ca7af4733bfcf4baa440121b7dbb3856daf8428ce91481ff63b/coverage-7.13.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:7a482f2da9086971efb12daca1d6547007ede3674ea06e16d7663414445c683e", size = 260317, upload-time = "2026-02-03T14:02:17.996Z" }, + { url = "https://files.pythonhosted.org/packages/05/71/5cd8436e2c21410ff70be81f738c0dddea91bcc3189b1517d26e0102ccb3/coverage-7.13.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:562136b0d401992118d9b49fbee5454e16f95f85b120a4226a04d816e33fe024", size = 262635, upload-time = "2026-02-03T14:02:20.405Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f8/2834bb45bdd70b55a33ec354b8b5f6062fc90e5bb787e14385903a979503/coverage-7.13.3-cp314-cp314t-win32.whl", hash = "sha256:ca46e5c3be3b195098dd88711890b8011a9fa4feca942292bb84714ce5eab5d3", size = 223035, upload-time = "2026-02-03T14:02:22.323Z" }, + { url = "https://files.pythonhosted.org/packages/26/75/f8290f0073c00d9ae14056d2b84ab92dff21d5370e464cb6cb06f52bf580/coverage-7.13.3-cp314-cp314t-win_amd64.whl", hash = "sha256:06d316dbb3d9fd44cca05b2dbcfbef22948493d63a1f28e828d43e6cc505fed8", size = 224142, upload-time = "2026-02-03T14:02:24.143Z" }, + { url = "https://files.pythonhosted.org/packages/03/01/43ac78dfea8946c4a9161bbc034b5549115cb2b56781a4b574927f0d141a/coverage-7.13.3-cp314-cp314t-win_arm64.whl", hash = "sha256:299d66e9218193f9dc6e4880629ed7c4cd23486005166247c283fb98531656c3", size = 222166, upload-time = "2026-02-03T14:02:26.005Z" }, + { url = "https://files.pythonhosted.org/packages/7d/fb/70af542d2d938c778c9373ce253aa4116dbe7c0a5672f78b2b2ae0e1b94b/coverage-7.13.3-py3-none-any.whl", hash = "sha256:90a8af9dba6429b2573199622d72e0ebf024d6276f16abce394ad4d181bb0910", size = 211237, upload-time = "2026-02-03T14:02:27.986Z" }, ] [package.optional-dependencies] @@ -543,7 +543,7 @@ wheels = [ [[package]] name = "django" -version = "5.2.10" +version = "5.2.11" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version == '3.11.*'", @@ -554,14 +554,14 @@ dependencies = [ { name = "sqlparse", marker = "python_full_version < '3.12'" }, { name = "tzdata", marker = "python_full_version < '3.12' and sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e6/e5/2671df24bf0ded831768ef79532e5a7922485411a5696f6d979568591a37/django-5.2.10.tar.gz", hash = "sha256:74df100784c288c50a2b5cad59631d71214f40f72051d5af3fdf220c20bdbbbe", size = 10880754, upload-time = "2026-01-06T18:55:26.817Z" } +sdist = { url = "https://files.pythonhosted.org/packages/17/f2/3e57ef696b95067e05ae206171e47a8e53b9c84eec56198671ef9eaa51a6/django-5.2.11.tar.gz", hash = "sha256:7f2d292ad8b9ee35e405d965fbbad293758b858c34bbf7f3df551aeeac6f02d3", size = 10885017, upload-time = "2026-02-03T13:52:50.554Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/de/f1a7cd896daec85832136ab509d9b2a6daed4939dbe26313af3e95fc5f5e/django-5.2.10-py3-none-any.whl", hash = "sha256:cf85067a64250c95d5f9067b056c5eaa80591929f7e16fbcd997746e40d6c45c", size = 8290820, upload-time = "2026-01-06T18:55:20.009Z" }, + { url = "https://files.pythonhosted.org/packages/91/a7/2b112ab430575bf3135b8304ac372248500d99c352f777485f53fdb9537e/django-5.2.11-py3-none-any.whl", hash = "sha256:e7130df33ada9ab5e5e929bc19346a20fe383f5454acb2cc004508f242ee92c0", size = 8291375, upload-time = "2026-02-03T13:52:42.47Z" }, ] [[package]] name = "django" -version = "6.0.1" +version = "6.0.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.12'", @@ -571,9 +571,9 @@ dependencies = [ { name = "sqlparse", marker = "python_full_version >= '3.12'" }, { name = "tzdata", marker = "python_full_version >= '3.12' and sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b5/9b/016f7e55e855ee738a352b05139d4f8b278d0b451bd01ebef07456ef3b0e/django-6.0.1.tar.gz", hash = "sha256:ed76a7af4da21551573b3d9dfc1f53e20dd2e6c7d70a3adc93eedb6338130a5f", size = 11069565, upload-time = "2026-01-06T18:55:53.069Z" } +sdist = { url = "https://files.pythonhosted.org/packages/26/3e/a1c4207c5dea4697b7a3387e26584919ba987d8f9320f59dc0b5c557a4eb/django-6.0.2.tar.gz", hash = "sha256:3046a53b0e40d4b676c3b774c73411d7184ae2745fe8ce5e45c0f33d3ddb71a7", size = 10886874, upload-time = "2026-02-03T13:50:31.596Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/95/b5/814ed98bd21235c116fd3436a7ed44d47560329a6d694ec8aac2982dbb93/django-6.0.1-py3-none-any.whl", hash = "sha256:a92a4ff14f664a896f9849009cb8afaca7abe0d6fc53325f3d1895a15253433d", size = 8338791, upload-time = "2026-01-06T18:55:46.175Z" }, + { url = "https://files.pythonhosted.org/packages/96/ba/a6e2992bc5b8c688249c00ea48cb1b7a9bc09839328c81dc603671460928/django-6.0.2-py3-none-any.whl", hash = "sha256:610dd3b13d15ec3f1e1d257caedd751db8033c5ad8ea0e2d1219a8acf446ecc6", size = 8339381, upload-time = "2026-02-03T13:50:15.501Z" }, ] [[package]] @@ -581,8 +581,8 @@ name = "django-extra-views" version = "0.16.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "django", version = "5.2.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "django", version = "6.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "django", version = "5.2.11", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "django", version = "6.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ac/61/c6d2ced11fc4235d2aeb422ad5af3b516effd942e2be5a263a5d93dcb1e2/django_extra_views-0.16.0.tar.gz", hash = "sha256:7f8e07bd6c9388816a7c08d752661172d4078758c2079fc0cadddfcf5cd38ae3", size = 13235, upload-time = "2025-04-22T15:10:58.475Z" } wheels = [ @@ -594,8 +594,8 @@ name = "django-filter" version = "25.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "django", version = "5.2.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "django", version = "6.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "django", version = "5.2.11", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "django", version = "6.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2c/e4/465d2699cd388c0005fb8d6ae6709f239917c6d8790ac35719676fffdcf3/django_filter-25.2.tar.gz", hash = "sha256:760e984a931f4468d096f5541787efb8998c61217b73006163bf2f9523fe8f23", size = 143818, upload-time = "2025-10-05T09:51:31.521Z" } wheels = [ @@ -607,8 +607,8 @@ name = "django-guardian" version = "3.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "django", version = "5.2.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "django", version = "6.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "django", version = "5.2.11", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "django", version = "6.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e2/f9/bcff6a931298b9eb55e1550b55ab964fab747f594ba6d2d81cbe19736c5f/django_guardian-3.2.0.tar.gz", hash = "sha256:9e18ecd2e211b665972690c2d03d27bce0ea4932b5efac24a4bb9d526950a69e", size = 99940, upload-time = "2025-09-16T10:35:53.609Z" } @@ -621,8 +621,8 @@ name = "django-polymorphic" version = "4.11.0" source = { editable = "." } dependencies = [ - { name = "django", version = "5.2.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "django", version = "6.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "django", version = "5.2.11", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "django", version = "6.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "typing-extensions" }, ] @@ -752,7 +752,7 @@ dev = [ { name = "furo", specifier = ">=2025.7.19" }, { name = "ipdb", specifier = ">=0.13.13" }, { name = "ipython", specifier = ">=8.18.1" }, - { name = "mypy", specifier = ">=1.14.1" }, + { name = "mypy", specifier = ">=1.19.1" }, { name = "pre-commit", specifier = ">=3.5.0" }, { name = "pyright", specifier = ">=1.1.390" }, { name = "pytest", specifier = ">=8.3.4" }, @@ -809,7 +809,7 @@ typing = [ { name = "django-stubs", specifier = ">=5.1.1" }, { name = "django-stubs-ext", specifier = ">=5.1.1" }, { name = "djangorestframework-stubs", specifier = ">=3.16.1" }, - { name = "mypy", specifier = ">=1.14.1" }, + { name = "mypy", specifier = ">=1.19.1" }, { name = "pyright", specifier = ">=1.1.390" }, ] @@ -818,8 +818,8 @@ name = "django-reversion" version = "6.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "django", version = "5.2.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "django", version = "6.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "django", version = "5.2.11", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "django", version = "6.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9b/8b/72000ad3ba05cb72134f62ad093267e3d83d8fdf1406102d2a1a2ec7d55e/django_reversion-6.1.0.tar.gz", hash = "sha256:31dd7fee02e2a21af7c2d0a61c1c0f27ba07df2317c9a0a3f31d3ee40069025d", size = 76140, upload-time = "2025-12-12T20:23:51.948Z" } wheels = [ @@ -831,8 +831,8 @@ name = "django-stubs" version = "5.2.9" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "django", version = "5.2.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "django", version = "6.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "django", version = "5.2.11", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "django", version = "6.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "django-stubs-ext" }, { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "types-pyyaml" }, @@ -848,8 +848,8 @@ name = "django-stubs-ext" version = "5.2.9" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "django", version = "5.2.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "django", version = "6.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "django", version = "5.2.11", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "django", version = "6.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/55/03/9c2be939490d2282328db4611bc5956899f5ff7eabc3e88bd4b964a87373/django_stubs_ext-5.2.9.tar.gz", hash = "sha256:6db4054d1580657b979b7d391474719f1a978773e66c7070a5e246cd445a25a9", size = 6497, upload-time = "2026-01-20T23:58:59.462Z" } @@ -874,8 +874,8 @@ name = "djangorestframework" version = "3.16.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "django", version = "5.2.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "django", version = "6.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "django", version = "5.2.11", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "django", version = "6.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/8a/95/5376fe618646fde6899b3cdc85fd959716bb67542e273a76a80d9f326f27/djangorestframework-3.16.1.tar.gz", hash = "sha256:166809528b1aced0a17dc66c24492af18049f2c9420dbd0be29422029cfc3ff7", size = 1089735, upload-time = "2025-08-06T17:50:53.251Z" } wheels = [ @@ -884,17 +884,16 @@ wheels = [ [[package]] name = "djangorestframework-stubs" -version = "3.16.7" +version = "3.16.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "django-stubs" }, { name = "types-pyyaml" }, - { name = "types-requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/35/50/889b1121dc0831aa9f6ece8409d41a5f4667da2a963172516841f343fd35/djangorestframework_stubs-3.16.7.tar.gz", hash = "sha256:e53bc346e9950ebdd1bb2bbc19d7e5c8b7acc894e381df55da69248f47ab78ff", size = 32296, upload-time = "2026-01-13T11:42:48.3Z" } +sdist = { url = "https://files.pythonhosted.org/packages/78/d5/87166a827833eb39703856ef957ca0fb4e9d15285331251186a2e738c20c/djangorestframework_stubs-3.16.8.tar.gz", hash = "sha256:f6d464b54fa2f929610e957446c04e6ac29558265418e0a2d9f653a4cdd410b5", size = 32312, upload-time = "2026-02-03T22:35:53.182Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/99/7c969728d66388e22fdaba94e1a9c56490954e2f12f598416e380a53b26d/djangorestframework_stubs-3.16.7-py3-none-any.whl", hash = "sha256:70f80050144875f80ce8ac823ff8628f6e3eb7336495394bb9803251721d9358", size = 56522, upload-time = "2026-01-13T11:42:46.118Z" }, + { url = "https://files.pythonhosted.org/packages/ea/e9/d9c363b08d07d975c21793fe821b2020dfd3627ac4ce19c5c12df94ce9d0/djangorestframework_stubs-3.16.8-py3-none-any.whl", hash = "sha256:c5bf61def0f330a071dd5f470f05710189d06c467b3f3e186b32c5a23d4952fb", size = 56517, upload-time = "2026-02-03T22:35:50.67Z" }, ] [[package]] @@ -1911,28 +1910,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.14.14" +version = "0.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2e/06/f71e3a86b2df0dfa2d2f72195941cd09b44f87711cb7fa5193732cb9a5fc/ruff-0.14.14.tar.gz", hash = "sha256:2d0f819c9a90205f3a867dbbd0be083bee9912e170fd7d9704cc8ae45824896b", size = 4515732, upload-time = "2026-01-22T22:30:17.527Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c8/39/5cee96809fbca590abea6b46c6d1c586b49663d1d2830a751cc8fc42c666/ruff-0.15.0.tar.gz", hash = "sha256:6bdea47cdbea30d40f8f8d7d69c0854ba7c15420ec75a26f463290949d7f7e9a", size = 4524893, upload-time = "2026-02-03T17:53:35.357Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/89/20a12e97bc6b9f9f68343952da08a8099c57237aef953a56b82711d55edd/ruff-0.14.14-py3-none-linux_armv6l.whl", hash = "sha256:7cfe36b56e8489dee8fbc777c61959f60ec0f1f11817e8f2415f429552846aed", size = 10467650, upload-time = "2026-01-22T22:30:08.578Z" }, - { url = "https://files.pythonhosted.org/packages/a3/b1/c5de3fd2d5a831fcae21beda5e3589c0ba67eec8202e992388e4b17a6040/ruff-0.14.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6006a0082336e7920b9573ef8a7f52eec837add1265cc74e04ea8a4368cd704c", size = 10883245, upload-time = "2026-01-22T22:30:04.155Z" }, - { url = "https://files.pythonhosted.org/packages/b8/7c/3c1db59a10e7490f8f6f8559d1db8636cbb13dccebf18686f4e3c9d7c772/ruff-0.14.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:026c1d25996818f0bf498636686199d9bd0d9d6341c9c2c3b62e2a0198b758de", size = 10231273, upload-time = "2026-01-22T22:30:34.642Z" }, - { url = "https://files.pythonhosted.org/packages/a1/6e/5e0e0d9674be0f8581d1f5e0f0a04761203affce3232c1a1189d0e3b4dad/ruff-0.14.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f666445819d31210b71e0a6d1c01e24447a20b85458eea25a25fe8142210ae0e", size = 10585753, upload-time = "2026-01-22T22:30:31.781Z" }, - { url = "https://files.pythonhosted.org/packages/23/09/754ab09f46ff1884d422dc26d59ba18b4e5d355be147721bb2518aa2a014/ruff-0.14.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c0f18b922c6d2ff9a5e6c3ee16259adc513ca775bcf82c67ebab7cbd9da5bc8", size = 10286052, upload-time = "2026-01-22T22:30:24.827Z" }, - { url = "https://files.pythonhosted.org/packages/c8/cc/e71f88dd2a12afb5f50733851729d6b571a7c3a35bfdb16c3035132675a0/ruff-0.14.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1629e67489c2dea43e8658c3dba659edbfd87361624b4040d1df04c9740ae906", size = 11043637, upload-time = "2026-01-22T22:30:13.239Z" }, - { url = "https://files.pythonhosted.org/packages/67/b2/397245026352494497dac935d7f00f1468c03a23a0c5db6ad8fc49ca3fb2/ruff-0.14.14-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:27493a2131ea0f899057d49d303e4292b2cae2bb57253c1ed1f256fbcd1da480", size = 12194761, upload-time = "2026-01-22T22:30:22.542Z" }, - { url = "https://files.pythonhosted.org/packages/5b/06/06ef271459f778323112c51b7587ce85230785cd64e91772034ddb88f200/ruff-0.14.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01ff589aab3f5b539e35db38425da31a57521efd1e4ad1ae08fc34dbe30bd7df", size = 12005701, upload-time = "2026-01-22T22:30:20.499Z" }, - { url = "https://files.pythonhosted.org/packages/41/d6/99364514541cf811ccc5ac44362f88df66373e9fec1b9d1c4cc830593fe7/ruff-0.14.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc12d74eef0f29f51775f5b755913eb523546b88e2d733e1d701fe65144e89b", size = 11282455, upload-time = "2026-01-22T22:29:59.679Z" }, - { url = "https://files.pythonhosted.org/packages/ca/71/37daa46f89475f8582b7762ecd2722492df26421714a33e72ccc9a84d7a5/ruff-0.14.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb8481604b7a9e75eff53772496201690ce2687067e038b3cc31aaf16aa0b974", size = 11215882, upload-time = "2026-01-22T22:29:57.032Z" }, - { url = "https://files.pythonhosted.org/packages/2c/10/a31f86169ec91c0705e618443ee74ede0bdd94da0a57b28e72db68b2dbac/ruff-0.14.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:14649acb1cf7b5d2d283ebd2f58d56b75836ed8c6f329664fa91cdea19e76e66", size = 11180549, upload-time = "2026-01-22T22:30:27.175Z" }, - { url = "https://files.pythonhosted.org/packages/fd/1e/c723f20536b5163adf79bdd10c5f093414293cdf567eed9bdb7b83940f3f/ruff-0.14.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e8058d2145566510790eab4e2fad186002e288dec5e0d343a92fe7b0bc1b3e13", size = 10543416, upload-time = "2026-01-22T22:30:01.964Z" }, - { url = "https://files.pythonhosted.org/packages/3e/34/8a84cea7e42c2d94ba5bde1d7a4fae164d6318f13f933d92da6d7c2041ff/ruff-0.14.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e651e977a79e4c758eb807f0481d673a67ffe53cfa92209781dfa3a996cf8412", size = 10285491, upload-time = "2026-01-22T22:30:29.51Z" }, - { url = "https://files.pythonhosted.org/packages/55/ef/b7c5ea0be82518906c978e365e56a77f8de7678c8bb6651ccfbdc178c29f/ruff-0.14.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cc8b22da8d9d6fdd844a68ae937e2a0adf9b16514e9a97cc60355e2d4b219fc3", size = 10733525, upload-time = "2026-01-22T22:30:06.499Z" }, - { url = "https://files.pythonhosted.org/packages/6a/5b/aaf1dfbcc53a2811f6cc0a1759de24e4b03e02ba8762daabd9b6bd8c59e3/ruff-0.14.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:16bc890fb4cc9781bb05beb5ab4cd51be9e7cb376bf1dd3580512b24eb3fda2b", size = 11315626, upload-time = "2026-01-22T22:30:36.848Z" }, - { url = "https://files.pythonhosted.org/packages/2c/aa/9f89c719c467dfaf8ad799b9bae0df494513fb21d31a6059cb5870e57e74/ruff-0.14.14-py3-none-win32.whl", hash = "sha256:b530c191970b143375b6a68e6f743800b2b786bbcf03a7965b06c4bf04568167", size = 10502442, upload-time = "2026-01-22T22:30:38.93Z" }, - { url = "https://files.pythonhosted.org/packages/87/44/90fa543014c45560cae1fffc63ea059fb3575ee6e1cb654562197e5d16fb/ruff-0.14.14-py3-none-win_amd64.whl", hash = "sha256:3dde1435e6b6fe5b66506c1dff67a421d0b7f6488d466f651c07f4cab3bf20fd", size = 11630486, upload-time = "2026-01-22T22:30:10.852Z" }, - { url = "https://files.pythonhosted.org/packages/9e/6a/40fee331a52339926a92e17ae748827270b288a35ef4a15c9c8f2ec54715/ruff-0.14.14-py3-none-win_arm64.whl", hash = "sha256:56e6981a98b13a32236a72a8da421d7839221fa308b223b9283312312e5ac76c", size = 10920448, upload-time = "2026-01-22T22:30:15.417Z" }, + { url = "https://files.pythonhosted.org/packages/bc/88/3fd1b0aa4b6330d6aaa63a285bc96c9f71970351579152d231ed90914586/ruff-0.15.0-py3-none-linux_armv6l.whl", hash = "sha256:aac4ebaa612a82b23d45964586f24ae9bc23ca101919f5590bdb368d74ad5455", size = 10354332, upload-time = "2026-02-03T17:52:54.892Z" }, + { url = "https://files.pythonhosted.org/packages/72/f6/62e173fbb7eb75cc29fe2576a1e20f0a46f671a2587b5f604bfb0eaf5f6f/ruff-0.15.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:dcd4be7cc75cfbbca24a98d04d0b9b36a270d0833241f776b788d59f4142b14d", size = 10767189, upload-time = "2026-02-03T17:53:19.778Z" }, + { url = "https://files.pythonhosted.org/packages/99/e4/968ae17b676d1d2ff101d56dc69cf333e3a4c985e1ec23803df84fc7bf9e/ruff-0.15.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d747e3319b2bce179c7c1eaad3d884dc0a199b5f4d5187620530adf9105268ce", size = 10075384, upload-time = "2026-02-03T17:53:29.241Z" }, + { url = "https://files.pythonhosted.org/packages/a2/bf/9843c6044ab9e20af879c751487e61333ca79a2c8c3058b15722386b8cae/ruff-0.15.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:650bd9c56ae03102c51a5e4b554d74d825ff3abe4db22b90fd32d816c2e90621", size = 10481363, upload-time = "2026-02-03T17:52:43.332Z" }, + { url = "https://files.pythonhosted.org/packages/55/d9/4ada5ccf4cd1f532db1c8d44b6f664f2208d3d93acbeec18f82315e15193/ruff-0.15.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6664b7eac559e3048223a2da77769c2f92b43a6dfd4720cef42654299a599c9", size = 10187736, upload-time = "2026-02-03T17:53:00.522Z" }, + { url = "https://files.pythonhosted.org/packages/86/e2/f25eaecd446af7bb132af0a1d5b135a62971a41f5366ff41d06d25e77a91/ruff-0.15.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f811f97b0f092b35320d1556f3353bf238763420ade5d9e62ebd2b73f2ff179", size = 10968415, upload-time = "2026-02-03T17:53:15.705Z" }, + { url = "https://files.pythonhosted.org/packages/e7/dc/f06a8558d06333bf79b497d29a50c3a673d9251214e0d7ec78f90b30aa79/ruff-0.15.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:761ec0a66680fab6454236635a39abaf14198818c8cdf691e036f4bc0f406b2d", size = 11809643, upload-time = "2026-02-03T17:53:23.031Z" }, + { url = "https://files.pythonhosted.org/packages/dd/45/0ece8db2c474ad7df13af3a6d50f76e22a09d078af63078f005057ca59eb/ruff-0.15.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:940f11c2604d317e797b289f4f9f3fa5555ffe4fb574b55ed006c3d9b6f0eb78", size = 11234787, upload-time = "2026-02-03T17:52:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/8a/d9/0e3a81467a120fd265658d127db648e4d3acfe3e4f6f5d4ea79fac47e587/ruff-0.15.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcbca3d40558789126da91d7ef9a7c87772ee107033db7191edefa34e2c7f1b4", size = 11112797, upload-time = "2026-02-03T17:52:49.274Z" }, + { url = "https://files.pythonhosted.org/packages/b2/cb/8c0b3b0c692683f8ff31351dfb6241047fa873a4481a76df4335a8bff716/ruff-0.15.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9a121a96db1d75fa3eb39c4539e607f628920dd72ff1f7c5ee4f1b768ac62d6e", size = 11033133, upload-time = "2026-02-03T17:53:33.105Z" }, + { url = "https://files.pythonhosted.org/packages/f8/5e/23b87370cf0f9081a8c89a753e69a4e8778805b8802ccfe175cc410e50b9/ruff-0.15.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5298d518e493061f2eabd4abd067c7e4fb89e2f63291c94332e35631c07c3662", size = 10442646, upload-time = "2026-02-03T17:53:06.278Z" }, + { url = "https://files.pythonhosted.org/packages/e1/9a/3c94de5ce642830167e6d00b5c75aacd73e6347b4c7fc6828699b150a5ee/ruff-0.15.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:afb6e603d6375ff0d6b0cee563fa21ab570fd15e65c852cb24922cef25050cf1", size = 10195750, upload-time = "2026-02-03T17:53:26.084Z" }, + { url = "https://files.pythonhosted.org/packages/30/15/e396325080d600b436acc970848d69df9c13977942fb62bb8722d729bee8/ruff-0.15.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:77e515f6b15f828b94dc17d2b4ace334c9ddb7d9468c54b2f9ed2b9c1593ef16", size = 10676120, upload-time = "2026-02-03T17:53:09.363Z" }, + { url = "https://files.pythonhosted.org/packages/8d/c9/229a23d52a2983de1ad0fb0ee37d36e0257e6f28bfd6b498ee2c76361874/ruff-0.15.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6f6e80850a01eb13b3e42ee0ebdf6e4497151b48c35051aab51c101266d187a3", size = 11201636, upload-time = "2026-02-03T17:52:57.281Z" }, + { url = "https://files.pythonhosted.org/packages/6f/b0/69adf22f4e24f3677208adb715c578266842e6e6a3cc77483f48dd999ede/ruff-0.15.0-py3-none-win32.whl", hash = "sha256:238a717ef803e501b6d51e0bdd0d2c6e8513fe9eec14002445134d3907cd46c3", size = 10465945, upload-time = "2026-02-03T17:53:12.591Z" }, + { url = "https://files.pythonhosted.org/packages/51/ad/f813b6e2c97e9b4598be25e94a9147b9af7e60523b0cb5d94d307c15229d/ruff-0.15.0-py3-none-win_amd64.whl", hash = "sha256:dd5e4d3301dc01de614da3cdffc33d4b1b96fb89e45721f1598e5532ccf78b18", size = 11564657, upload-time = "2026-02-03T17:52:51.893Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b0/2d823f6e77ebe560f4e397d078487e8d52c1516b331e3521bc75db4272ca/ruff-0.15.0-py3-none-win_arm64.whl", hash = "sha256:c480d632cc0ca3f0727acac8b7d053542d9e114a462a145d0b00e7cd658c515a", size = 10865753, upload-time = "2026-02-03T17:53:03.014Z" }, ] [[package]] @@ -2125,8 +2123,8 @@ name = "sphinxcontrib-django" version = "2.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "django", version = "5.2.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, - { name = "django", version = "6.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "django", version = "5.2.11", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "django", version = "6.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "pprintpp" }, { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, @@ -2308,18 +2306,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bd/e0/1eed384f02555dde685fff1a1ac805c1c7dcb6dd019c916fe659b1c1f9ec/types_pyyaml-6.0.12.20250915-py3-none-any.whl", hash = "sha256:e7d4d9e064e89a3b3cae120b4990cd370874d2bf12fa5f46c97018dd5d3c9ab6", size = 20338, upload-time = "2025-09-15T03:00:59.218Z" }, ] -[[package]] -name = "types-requests" -version = "2.32.4.20260107" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0f/f3/a0663907082280664d745929205a89d41dffb29e89a50f753af7d57d0a96/types_requests-2.32.4.20260107.tar.gz", hash = "sha256:018a11ac158f801bfa84857ddec1650750e393df8a004a8a9ae2a9bec6fcb24f", size = 23165, upload-time = "2026-01-07T03:20:54.091Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1c/12/709ea261f2bf91ef0a26a9eed20f2623227a8ed85610c1e54c5805692ecb/types_requests-2.32.4.20260107-py3-none-any.whl", hash = "sha256:b703fe72f8ce5b31ef031264fe9395cac8f46a04661a79f7ed31a80fb308730d", size = 20676, upload-time = "2026-01-07T03:20:52.929Z" }, -] - [[package]] name = "typing-extensions" version = "4.15.0"