From fdb8aca779acf1cf6c8626e68218b244d277ac72 Mon Sep 17 00:00:00 2001 From: Nick Pope Date: Sat, 9 Nov 2024 12:46:11 +0000 Subject: [PATCH] Fix typing of related manager methods (#2439) Looking at the [related manager documentation][0] it isn't very clear that the `bulk` argument only applies to `RelatedManager` and the `through_defaults` argument only applies to `ManyRelatedManager`. Based on the [related descriptors source][1], the following changes have been made in this commit: - The `bulk` argument has been added to `.clear()` and `.aclear()` for `RelatedManager` - The `bulk` argument has been removed from all methods of `ManyRelatedManager` - Additional methods using `through_defaults` have been added to `ManyRelatedManager`. - Updated typing of `through_defaults` to `Mapping[str, Any] | None` [0]: https://docs.djangoproject.com/en/stable/ref/models/relations/ [1]: https://github.com/django/django/blob/042b381e2e37c0c37b8a8f6cc9947f1a2ebfa0dd/django/db/models/fields/related_descriptors.py --- .../db/models/fields/related_descriptors.pyi | 86 +++++++++++++++---- tests/typecheck/fields/test_related.yml | 2 +- 2 files changed, 72 insertions(+), 16 deletions(-) diff --git a/django-stubs/db/models/fields/related_descriptors.pyi b/django-stubs/db/models/fields/related_descriptors.pyi index 4deec2d44..0d5782a32 100644 --- a/django-stubs/db/models/fields/related_descriptors.pyi +++ b/django-stubs/db/models/fields/related_descriptors.pyi @@ -1,4 +1,4 @@ -from collections.abc import Callable, Iterable +from collections.abc import Callable, Iterable, Mapping from typing import Any, Generic, NoReturn, TypeVar, overload, type_check_only from django.core.exceptions import ObjectDoesNotExist @@ -102,10 +102,22 @@ class RelatedManager(Manager[_To], Generic[_To]): async def aadd(self, *objs: _To | int, bulk: bool = ...) -> None: ... def remove(self, *objs: _To | int, bulk: bool = ...) -> None: ... async def aremove(self, *objs: _To | int, bulk: bool = ...) -> None: ... - def set(self, objs: QuerySet[_To] | Iterable[_To | int], *, bulk: bool = ..., clear: bool = ...) -> None: ... - async def aset(self, objs: QuerySet[_To] | Iterable[_To | int], *, bulk: bool = ..., clear: bool = ...) -> None: ... - def clear(self) -> None: ... - async def aclear(self) -> None: ... + def clear(self, *, clear: bool = ...) -> None: ... + async def aclear(self, *, clear: bool = ...) -> None: ... + def set( + self, + objs: QuerySet[_To] | Iterable[_To | int], + *, + bulk: bool = ..., + clear: bool = ..., + ) -> None: ... + async def aset( + self, + objs: QuerySet[_To] | Iterable[_To | int], + *, + bulk: bool = ..., + clear: bool = ..., + ) -> None: ... def __call__(self, *, manager: str) -> RelatedManager[_To]: ... def create_reverse_many_to_one_manager( @@ -142,28 +154,72 @@ class ManyToManyDescriptor(ReverseManyToOneDescriptor, Generic[_To, _Through]): class ManyRelatedManager(Manager[_To], Generic[_To, _Through]): related_val: tuple[int, ...] through: type[_Through] - def add(self, *objs: _To | int, bulk: bool = ..., through_defaults: dict[str, Any] | None = ...) -> None: ... - async def aadd(self, *objs: _To | int, bulk: bool = ..., through_defaults: dict[str, Any] | None = ...) -> None: ... - def remove(self, *objs: _To | int, bulk: bool = ...) -> None: ... - async def aremove(self, *objs: _To | int, bulk: bool = ...) -> None: ... + def add( + self, + *objs: _To | int, + through_defaults: Mapping[str, Any] | None = ..., + ) -> None: ... + async def aadd( + self, + *objs: _To | int, + through_defaults: Mapping[str, Any] | None = ..., + ) -> None: ... + def remove(self, *objs: _To | int) -> None: ... + async def aremove(self, *objs: _To | int) -> None: ... + def clear(self) -> None: ... + async def aclear(self) -> None: ... def set( self, objs: QuerySet[_To] | Iterable[_To | int], *, - bulk: bool = ..., clear: bool = ..., - through_defaults: dict[str, Any] | None = ..., + through_defaults: Mapping[str, Any] | None = ..., ) -> None: ... async def aset( self, objs: QuerySet[_To] | Iterable[_To | int], *, - bulk: bool = ..., clear: bool = ..., - through_defaults: dict[str, Any] | None = ..., + through_defaults: Mapping[str, Any] | None = ..., ) -> None: ... - def clear(self) -> None: ... - async def aclear(self) -> None: ... + def create( + self, + *, + through_defaults: Mapping[str, Any] | None = ..., + **kwargs: Any, + ) -> _To: ... + async def acreate( + self, + *, + through_defaults: Mapping[str, Any] | None = ..., + **kwargs: Any, + ) -> _To: ... + def get_or_create( + self, + defaults: Mapping[str, Any] | None = ..., + through_defaults: Mapping[str, Any] | None = ..., + **kwargs: Any, + ) -> tuple[_To, bool]: ... + async def aget_or_create( + self, + defaults: Mapping[str, Any] | None = ..., + through_defaults: Mapping[str, Any] | None = ..., + **kwargs: Any, + ) -> tuple[_To, bool]: ... + def update_or_create( + self, + defaults: Mapping[str, Any] | None = ..., + create_defaults: Mapping[str, Any] | None = ..., + through_defaults: Mapping[str, Any] | None = ..., + **kwargs: Any, + ) -> tuple[_To, bool]: ... + async def aupdate_or_create( + self, + defaults: Mapping[str, Any] | None = ..., + create_defaults: Mapping[str, Any] | None = ..., + through_defaults: Mapping[str, Any] | None = ..., + **kwargs: Any, + ) -> tuple[_To, bool]: ... def __call__(self, *, manager: str) -> ManyRelatedManager[_To, _Through]: ... def create_forward_many_to_many_manager( diff --git a/tests/typecheck/fields/test_related.yml b/tests/typecheck/fields/test_related.yml index 5ffb2eef9..bb911d6f3 100644 --- a/tests/typecheck/fields/test_related.yml +++ b/tests/typecheck/fields/test_related.yml @@ -1226,7 +1226,7 @@ main:26: note: Revealed type is "myapp.models.MyModel_auto_through" main:28: note: Revealed type is "builtins.str" main:29: note: Revealed type is "builtins.str" - main:30: note: Revealed type is "def (*objs: Union[myapp.models.MyModel, builtins.int], bulk: builtins.bool =, through_defaults: Union[builtins.dict[builtins.str, Any], None] =)" + main:30: note: Revealed type is "def (*objs: Union[myapp.models.MyModel, builtins.int], through_defaults: Union[typing.Mapping[builtins.str, Any], None] =)" main:32: note: Revealed type is "django.db.models.manager.Manager[myapp.models.MyModel_auto_through]" main:33: note: Revealed type is "django.db.models.manager.Manager[myapp.models.MyModel_auto_through]" main:35: note: Revealed type is "myapp.models.Other_ManyRelatedManager[myapp.models.MyModel_auto_through]"