From 6f1f9a3e68c95f307d647c727f9e4fe1c25005d3 Mon Sep 17 00:00:00 2001 From: Noelle Leigh <5957867+noelleleigh@users.noreply.github.com> Date: Sat, 21 Oct 2023 14:04:09 -0400 Subject: [PATCH] Improve BaseManager, Manager types (#205) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR makes a series of improvements to methods and properties of `BaseManager` and `Manager`: - `BaseManager.deconstruct()`: Most of the values in the return tuple can be null ([see implementation](https://github.com/django/django/blob/a576ef98aea2709741f32a863cff3c7a54172ded/django/db/models/manager.py#L42)) - `BaseManager.from_queryset()`: `queryset_class` is now generic, and the return type is made more specific ([see implementation](https://github.com/django/django/blob/a576ef98aea2709741f32a863cff3c7a54172ded/django/db/models/manager.py#L108)). This allows code like this to receive type information: ```python class CustomQuerySet(models.QuerySet["User"]): ... class CustomManager(models.Manager.from_queryset(CustomQuerySet)): def get_queryset(self): qs = super().get_queryset() # ← Type checker knows get_queryset exists in the parent user = qs.get() # ← Inferred type is "User" return qs ``` - `BaseManager._get_queryset_methods()`: Return dictionary values are functions ([see implementation](https://github.com/django/django/blob/a576ef98aea2709741f32a863cff3c7a54172ded/django/db/models/manager.py#L83)). - `Manager._queryset_class`: This property is assigned dynamically by `BaseManager.from_queryset()` ([see implementation](https://github.com/django/django/blob/a576ef98aea2709741f32a863cff3c7a54172ded/django/db/models/manager.py#L115)) --- django-stubs/db/models/manager.pyi | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/django-stubs/db/models/manager.pyi b/django-stubs/db/models/manager.pyi index 78fdcaa58..3ed250aaa 100644 --- a/django-stubs/db/models/manager.pyi +++ b/django-stubs/db/models/manager.pyi @@ -1,5 +1,5 @@ from collections.abc import Iterable, MutableMapping -from typing import Any, Generic, TypeVar +from typing import Any, Callable, Generic, TypeVar from typing_extensions import Self from django.db.models.base import Model @@ -19,21 +19,24 @@ class BaseManager(QuerySet[_T]): def __init__(self) -> None: ... def deconstruct( self, - ) -> tuple[bool, str, None, tuple[Any, ...], dict[str, int]]: ... + ) -> tuple[bool, str | None, str | None, tuple[Any, ...] | None, dict[str, Any] | None]: ... def check(self, **kwargs: Any) -> list[Any]: ... @classmethod def from_queryset( - cls, queryset_class: type[QuerySet[Any]], class_name: str | None = ... - ) -> Any: ... + cls, queryset_class: type[QuerySet[_T]], class_name: str | None = ... + ) -> type[Manager[_T]]: ... @classmethod - def _get_queryset_methods(cls, queryset_class: type) -> dict[str, Any]: ... + def _get_queryset_methods( + cls, queryset_class: type[QuerySet[_T]] + ) -> dict[str, Callable[..., Any]]: ... def contribute_to_class(self, model: type[Model], name: str) -> None: ... def db_manager( self, using: str | None = ..., hints: dict[str, Model] | None = ... ) -> Self: ... def get_queryset(self) -> QuerySet[_T]: ... -class Manager(BaseManager[_T]): ... +class Manager(BaseManager[_T]): + _queryset_class: type[QuerySet[_T]] class RelatedManager(Manager[_T]): related_val: tuple[int, ...]