From d5aa7f428ffced5418f0692be1f3b875881ea00f Mon Sep 17 00:00:00 2001 From: Petter Friberg Date: Thu, 2 Nov 2023 10:08:19 +0100 Subject: [PATCH 1/2] Move `RelatedManager` to `django.db.models.fields.related_descriptors` Aligns with the module `RelatedManager` is locally declared --- .../db/models/fields/related_descriptors.pyi | 14 ++++++- django-stubs/db/models/manager.pyi | 13 ------ mypy_django_plugin/lib/fullnames.py | 2 +- scripts/stubtest/allowlist.txt | 2 + scripts/stubtest/allowlist_todo.txt | 1 - tests/typecheck/fields/test_related.yml | 40 +++++++++---------- .../typecheck/models/test_related_fields.yml | 4 +- 7 files changed, 38 insertions(+), 38 deletions(-) diff --git a/django-stubs/db/models/fields/related_descriptors.pyi b/django-stubs/db/models/fields/related_descriptors.pyi index ccabc4f36..5c6e02b11 100644 --- a/django-stubs/db/models/fields/related_descriptors.pyi +++ b/django-stubs/db/models/fields/related_descriptors.pyi @@ -6,7 +6,7 @@ from django.db.models.base import Model from django.db.models.fields import Field from django.db.models.fields.related import ForeignKey, ManyToManyField, RelatedField from django.db.models.fields.reverse_related import ManyToManyRel, ManyToOneRel, OneToOneRel -from django.db.models.manager import BaseManager, RelatedManager +from django.db.models.manager import BaseManager from django.db.models.query import QuerySet from django.db.models.query_utils import DeferredAttribute from typing_extensions import Self @@ -86,6 +86,18 @@ class ReverseManyToOneDescriptor: def __get__(self, instance: Model, cls: Any = ...) -> type[RelatedManager[Any]]: ... def __set__(self, instance: Any, value: Any) -> NoReturn: ... +class RelatedManager(BaseManager[_M], Generic[_M]): + related_val: tuple[int, ...] + def add(self, *objs: _M | int, bulk: bool = ...) -> None: ... + async def aadd(self, *objs: _M | int, bulk: bool = ...) -> None: ... + def remove(self, *objs: _M | int, bulk: bool = ...) -> None: ... + async def aremove(self, *objs: _M | int, bulk: bool = ...) -> None: ... + def set(self, objs: QuerySet[_M] | Iterable[_M | int], *, bulk: bool = ..., clear: bool = ...) -> None: ... + async def aset(self, objs: QuerySet[_M] | Iterable[_M | int], *, bulk: bool = ..., clear: bool = ...) -> None: ... + def clear(self) -> None: ... + async def aclear(self) -> None: ... + def __call__(self, *, manager: str) -> RelatedManager[_M]: ... + def create_reverse_many_to_one_manager( superclass: type[BaseManager[_M]], rel: ManyToOneRel ) -> type[RelatedManager[_M]]: ... diff --git a/django-stubs/db/models/manager.pyi b/django-stubs/db/models/manager.pyi index ef402fec0..1e5bd3eb4 100644 --- a/django-stubs/db/models/manager.pyi +++ b/django-stubs/db/models/manager.pyi @@ -143,19 +143,6 @@ class BaseManager(Generic[_T]): class Manager(BaseManager[_T]): ... -# Fake to make ManyToMany work -class RelatedManager(Manager[_T]): - related_val: tuple[int, ...] - def add(self, *objs: _T | int, bulk: bool = ...) -> None: ... - async def aadd(self, *objs: _T | int, bulk: bool = ...) -> None: ... - def remove(self, *objs: _T | int, bulk: bool = ...) -> None: ... - async def aremove(self, *objs: _T | int, bulk: bool = ...) -> None: ... - def set(self, objs: QuerySet[_T] | Iterable[_T | int], *, bulk: bool = ..., clear: bool = ...) -> None: ... - async def aset(self, objs: QuerySet[_T] | Iterable[_T | int], *, bulk: bool = ..., clear: bool = ...) -> None: ... - def clear(self) -> None: ... - async def aclear(self) -> None: ... - def __call__(self, *, manager: str) -> RelatedManager[_T]: ... - class ManagerDescriptor: manager: BaseManager def __init__(self, manager: BaseManager) -> None: ... diff --git a/mypy_django_plugin/lib/fullnames.py b/mypy_django_plugin/lib/fullnames.py index 2505e3f8c..33e746236 100644 --- a/mypy_django_plugin/lib/fullnames.py +++ b/mypy_django_plugin/lib/fullnames.py @@ -17,7 +17,7 @@ QUERYSET_CLASS_FULLNAME = "django.db.models.query._QuerySet" BASE_MANAGER_CLASS_FULLNAME = "django.db.models.manager.BaseManager" MANAGER_CLASS_FULLNAME = "django.db.models.manager.Manager" -RELATED_MANAGER_CLASS = "django.db.models.manager.RelatedManager" +RELATED_MANAGER_CLASS = "django.db.models.fields.related_descriptors.RelatedManager" WITH_ANNOTATIONS_FULLNAME = "django_stubs_ext.WithAnnotations" ANNOTATIONS_FULLNAME = "django_stubs_ext.annotations.Annotations" diff --git a/scripts/stubtest/allowlist.txt b/scripts/stubtest/allowlist.txt index a5099d676..273a51ef5 100644 --- a/scripts/stubtest/allowlist.txt +++ b/scripts/stubtest/allowlist.txt @@ -19,6 +19,8 @@ django.core.files.storage.default_storage # 'ManyRelatedManager' does exist and is declared locally, inside a function body django.db.models.fields.related_descriptors.ManyRelatedManager +# 'RelatedManager' does exist and is declared locally, inside a function body +django.db.models.fields.related_descriptors.RelatedManager # '_RelatedManager' entries are plugin generated and these subclasses only exist # _locally/dynamically_ runtime -- Created via diff --git a/scripts/stubtest/allowlist_todo.txt b/scripts/stubtest/allowlist_todo.txt index 2a5bf39c6..49767ff74 100644 --- a/scripts/stubtest/allowlist_todo.txt +++ b/scripts/stubtest/allowlist_todo.txt @@ -1708,7 +1708,6 @@ django.db.models.manager.BaseManager.using django.db.models.manager.BaseManager.values django.db.models.manager.BaseManager.values_list django.db.models.manager.Manager.__slotnames__ -django.db.models.manager.RelatedManager django.db.models.options.Options.base_manager django.db.models.options.Options.concrete_fields django.db.models.options.Options.db_returning_fields diff --git a/tests/typecheck/fields/test_related.yml b/tests/typecheck/fields/test_related.yml index 154334422..36543b6c7 100644 --- a/tests/typecheck/fields/test_related.yml +++ b/tests/typecheck/fields/test_related.yml @@ -4,7 +4,7 @@ book = Book() reveal_type(book.publisher) # N: Revealed type is "myapp.models.Publisher" publisher = Publisher() - reveal_type(publisher.books) # N: Revealed type is "django.db.models.manager.RelatedManager[myapp.models.Book]" + reveal_type(publisher.books) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.Book]" installed_apps: - myapp files: @@ -70,8 +70,8 @@ reveal_type(book.publisher2) # N: Revealed type is "myapp.models.Publisher" publisher = Publisher() - reveal_type(publisher.books) # N: Revealed type is "django.db.models.manager.RelatedManager[myapp.models.Book]" - reveal_type(publisher.books2) # N: Revealed type is "django.db.models.manager.RelatedManager[myapp.models.Book]" + reveal_type(publisher.books) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.Book]" + reveal_type(publisher.books2) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.Book]" installed_apps: - myapp files: @@ -137,9 +137,9 @@ from django.db import models class App(models.Model): def method(self) -> None: - reveal_type(self.views) # N: Revealed type is "django.db.models.manager.RelatedManager[myapp.models.View]" - reveal_type(self.members) # N: Revealed type is "django.db.models.manager.RelatedManager[myapp.models.Member]" - reveal_type(self.sheets) # N: Revealed type is "django.db.models.manager.RelatedManager[myapp.models.Sheet]" + reveal_type(self.views) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.View]" + reveal_type(self.members) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.Member]" + reveal_type(self.sheets) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.Sheet]" reveal_type(self.profile) # N: Revealed type is "myapp.models.Profile" class View(models.Model): app = models.ForeignKey(to=App, related_name='views', on_delete=models.CASCADE) @@ -153,7 +153,7 @@ - case: test_circular_dependency_in_imports_with_string_based main: | from myapp.models import View - reveal_type(View().app.views) # N: Revealed type is "django.db.models.manager.RelatedManager[myapp.models.View]" + reveal_type(View().app.views) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.View]" View().app.unknown # E: "App" has no attribute "unknown" [attr-defined] installed_apps: - myapp @@ -172,13 +172,13 @@ from django.db import models class App(models.Model): def method(self) -> None: - reveal_type(self.views) # N: Revealed type is "django.db.models.manager.RelatedManager[myapp.models.View]" + reveal_type(self.views) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.View]" - case: models_related_managers_work_with_direct_model_inheritance_and_with_inheritance_from_other_model main: | from myapp.models import App - reveal_type(App().views) # N: Revealed type is "django.db.models.manager.RelatedManager[myapp.models.View]" - reveal_type(App().views2) # N: Revealed type is "django.db.models.manager.RelatedManager[myapp.models.View2]" + reveal_type(App().views) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.View]" + reveal_type(App().views2) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.View2]" installed_apps: - myapp files: @@ -196,7 +196,7 @@ - case: models_imported_inside_init_file_foreign_key main: | from myapp2.models import View - reveal_type(View().app.views) # N: Revealed type is "django.db.models.manager.RelatedManager[myapp2.models.View]" + reveal_type(View().app.views) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp2.models.View]" installed_apps: - myapp - myapp2 @@ -280,11 +280,11 @@ from myapp.models import App, Member reveal_type(Member().apps) # N: Revealed type is "django.db.models.fields.related_descriptors.ManyRelatedManager[myapp.models.App]" reveal_type(Member().apps.get()) # N: Revealed type is "myapp.models.App" - reveal_type(App().members) # N: Revealed type is "django.db.models.manager.RelatedManager[myapp.models.Member]" + reveal_type(App().members) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.Member]" reveal_type(App().members.get()) # N: Revealed type is "myapp.models.Member" reveal_type(Member.apps.field) # N: Revealed type is "django.db.models.fields.related.ManyToManyField[Any, myapp.models.Member_apps]" # XXX the following is not correct: - reveal_type(App.members) # N: Revealed type is "django.db.models.manager.RelatedManager[myapp.models.Member]" + reveal_type(App.members) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.Member]" installed_apps: - myapp files: @@ -372,7 +372,7 @@ - case: if_no_related_name_is_passed_create_default_related_managers main: | from myapp.models import Publisher - reveal_type(Publisher().book_set) # N: Revealed type is "django.db.models.manager.RelatedManager[myapp.models.Book]" + reveal_type(Publisher().book_set) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.Book]" installed_apps: - myapp files: @@ -474,7 +474,7 @@ publisher = Publisher() reveal_type(publisher.books) - reveal_type(publisher.books2) # N: Revealed type is "django.db.models.manager.RelatedManager[myapp.models.Book]" + reveal_type(publisher.books2) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.Book]" out: | main:6: error: "Publisher" has no attribute "books"; maybe "books2"? [attr-defined] main:6: note: Revealed type is "Any" @@ -564,8 +564,8 @@ - case: related_manager_name_defined_by_pattern main: | from myapp.models import Publisher - reveal_type(Publisher().books) # N: Revealed type is "django.db.models.manager.RelatedManager[myapp.models.Book]" - reveal_type(Publisher().articles) # N: Revealed type is "django.db.models.manager.RelatedManager[myapp.models.Article]" + reveal_type(Publisher().books) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.Book]" + reveal_type(Publisher().articles) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.Article]" installed_apps: - myapp files: @@ -620,8 +620,8 @@ reveal_type(Article().registered_by_user) # N: Revealed type is "myapp.models.MyUser" user = MyUser() - reveal_type(user.book_set) # N: Revealed type is "django.db.models.manager.RelatedManager[myapp.models.Book]" - reveal_type(user.article_set) # N: Revealed type is "django.db.models.manager.RelatedManager[myapp.models.Article]" + reveal_type(user.book_set) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.Book]" + reveal_type(user.article_set) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.Article]" installed_apps: - myapp files: @@ -736,7 +736,7 @@ transaction = models.ForeignKey(Transaction, on_delete=models.CASCADE) out: | myapp/models:11: error: Could not resolve manager type for "myapp.models.Transaction.objects" [django-manager-missing] - myapp/models:13: note: Revealed type is "django.db.models.manager.RelatedManager[myapp.models.TransactionLog]" + myapp/models:13: note: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.TransactionLog]" myapp/models:15: note: Revealed type is "myapp.models.UnknownManager[myapp.models.Transaction]" myapp/models:16: note: Revealed type is "Any" diff --git a/tests/typecheck/models/test_related_fields.yml b/tests/typecheck/models/test_related_fields.yml index 49ba27b25..ff94c5ac9 100644 --- a/tests/typecheck/models/test_related_fields.yml +++ b/tests/typecheck/models/test_related_fields.yml @@ -94,8 +94,8 @@ reveal_type(Model2.model_1.field) # N: Revealed type is "django.db.models.fields.related.ForeignObject[app1.models.Model1, app1.models.Model1]" reveal_type(Model2().model_1) # N: Revealed type is "app1.models.Model1" - reveal_type(Model1.model_2s) # N: Revealed type is "django.db.models.manager.RelatedManager[app1.models.Model2]" - reveal_type(Model1().model_2s) # N: Revealed type is "django.db.models.manager.RelatedManager[app1.models.Model2]" + reveal_type(Model1.model_2s) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[app1.models.Model2]" + reveal_type(Model1().model_2s) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[app1.models.Model2]" installed_apps: - app1 From 4a063c7dd6421da70f14db63e778832c20805816 Mon Sep 17 00:00:00 2001 From: Petter Friberg Date: Fri, 3 Nov 2023 08:19:31 +0100 Subject: [PATCH 2/2] fixup! Move `RelatedManager` to `django.db.models.fields.related_descriptors` --- django-stubs/db/models/fields/related_descriptors.pyi | 2 ++ 1 file changed, 2 insertions(+) diff --git a/django-stubs/db/models/fields/related_descriptors.pyi b/django-stubs/db/models/fields/related_descriptors.pyi index 5c6e02b11..f5b2db95b 100644 --- a/django-stubs/db/models/fields/related_descriptors.pyi +++ b/django-stubs/db/models/fields/related_descriptors.pyi @@ -86,6 +86,7 @@ class ReverseManyToOneDescriptor: def __get__(self, instance: Model, cls: Any = ...) -> type[RelatedManager[Any]]: ... def __set__(self, instance: Any, value: Any) -> NoReturn: ... +# Fake class, Django defines 'RelatedManager' inside a function body class RelatedManager(BaseManager[_M], Generic[_M]): related_val: tuple[int, ...] def add(self, *objs: _M | int, bulk: bool = ...) -> None: ... @@ -123,6 +124,7 @@ class ManyToManyDescriptor(ReverseManyToOneDescriptor, Generic[_M]): @property def related_manager_cls(self) -> type[ManyRelatedManager[Any]]: ... # type: ignore[override] +# Fake class, Django defines 'ManyRelatedManager' inside a function body class ManyRelatedManager(BaseManager[_M], Generic[_M]): related_val: tuple[int, ...] def add(self, *objs: _M | int, bulk: bool = ...) -> None: ...