diff --git a/README.md b/README.md index 5eb17a76a..762c18d04 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ option to get extra information about the error. ### I cannot use QuerySet or Manager with type annotations You can get a `TypeError: 'type' object is not subscriptable` -when you will try to use `QuerySet[MyModel]`, `Manager[MyModel]` or some other Django-based Generic types. +when you will try to use `QuerySet[MyModel]`, `Manager[MyModel, MyQuerySet]` or some other Django-based Generic types. This happens because these Django classes do not support [`__class_getitem__`](https://www.python.org/dev/peps/pep-0560/#class-getitem) magic method in runtime. @@ -237,6 +237,20 @@ class MyManager(models.Manager["MyModel"]): ... ``` +If your manager also declare a custom queryset via `get_queryset`, you might face a similar error + +> Return type "MyQuerySet[MyModel, MyModel]" of "get_queryset" incompatible with return type "_QS" in supertype "django.db.models.manager.BaseManager" + +To fix this issue, you have to properly pass custom `QuerySet` and `Manager` generic params: +```python +class MyQuerySet(models.QuerySet["MyModel"]): + ... + +class MyStaffManager(models.Manager["MyModel", MyQuerySet]): + def get_queryset(self) -> MyQuerySet: + return MyQuerySet(self.model, using=self._db) +``` + ### How do I annotate cases where I called QuerySet.annotate? Django-stubs provides a special type, `django_stubs_ext.WithAnnotations[Model, ]`, which indicates that diff --git a/django-stubs/contrib/auth/base_user.pyi b/django-stubs/contrib/auth/base_user.pyi index a1d029239..c01f2b8f6 100644 --- a/django-stubs/contrib/auth/base_user.pyi +++ b/django-stubs/contrib/auth/base_user.pyi @@ -5,10 +5,12 @@ from django.db import models from django.db.models.base import Model from django.db.models.expressions import Combinable from django.db.models.fields import BooleanField +from django.db.models.query import QuerySet _T = TypeVar("_T", bound=Model) +_QS = TypeVar("_QS", bound=QuerySet[Any], covariant=True, default=QuerySet[_T]) -class BaseUserManager(models.Manager[_T]): +class BaseUserManager(models.Manager[_T, _QS]): @classmethod def normalize_email(cls, email: str | None) -> str: ... def get_by_natural_key(self, username: str | None) -> _T: ... diff --git a/django-stubs/db/models/base.pyi b/django-stubs/db/models/base.pyi index 3cdce76b8..39a873731 100644 --- a/django-stubs/db/models/base.pyi +++ b/django-stubs/db/models/base.pyi @@ -28,9 +28,9 @@ class ModelState: class ModelBase(type): @property - def _default_manager(cls: type[_Self]) -> Manager[_Self]: ... # type: ignore[misc] + def _default_manager(cls: type[_Self]) -> Manager[_Self, QuerySet[_Self]]: ... # type: ignore[misc] @property - def _base_manager(cls: type[_Self]) -> Manager[_Self]: ... # type: ignore[misc] + def _base_manager(cls: type[_Self]) -> Manager[_Self, QuerySet[_Self]]: ... # type: ignore[misc] class Model(metaclass=ModelBase): # Note: these two metaclass generated attributes don't really exist on the 'Model' diff --git a/django-stubs/db/models/fields/related_descriptors.pyi b/django-stubs/db/models/fields/related_descriptors.pyi index 94a970c1f..bf1b1e09a 100644 --- a/django-stubs/db/models/fields/related_descriptors.pyi +++ b/django-stubs/db/models/fields/related_descriptors.pyi @@ -13,10 +13,12 @@ from django.utils.functional import cached_property from typing_extensions import Self, deprecated _M = TypeVar("_M", bound=Model) +_M_QS = TypeVar("_M_QS", bound=QuerySet[Any], covariant=True, default=QuerySet[_M]) _F = TypeVar("_F", bound=Field) _From = TypeVar("_From", bound=Model) _Through = TypeVar("_Through", bound=Model, default=Model) _To = TypeVar("_To", bound=Model) +_To_QS = TypeVar("_To_QS", bound=QuerySet[Any], covariant=True, default=QuerySet[_To]) class ForeignKeyDeferredAttribute(DeferredAttribute): field: RelatedField @@ -79,7 +81,7 @@ class ReverseOneToOneDescriptor(Generic[_From, _To]): def __set__(self, instance: _From, value: _To | None) -> None: ... def __reduce__(self) -> tuple[Callable[..., Any], tuple[type[_To], str]]: ... -class ReverseManyToOneDescriptor(Generic[_To]): +class ReverseManyToOneDescriptor(Generic[_To, _To_QS]): """ In the example:: @@ -93,16 +95,16 @@ class ReverseManyToOneDescriptor(Generic[_To]): field: ForeignKey[_To, _To] def __init__(self, rel: ManyToOneRel) -> None: ... @cached_property - def related_manager_cls(self) -> type[RelatedManager[_To]]: ... + def related_manager_cls(self) -> type[RelatedManager[_To, _To_QS]]: ... @overload - def __get__(self, instance: None, cls: Any | None = None) -> Self: ... + def __get__(self, instance: None, cls: Any | None = None) -> ReverseManyToOneDescriptor[_To, _To_QS]: ... @overload - def __get__(self, instance: Model, cls: Any | None = None) -> RelatedManager[_To]: ... + def __get__(self, instance: Model, cls: Any | None = None) -> RelatedManager[_To, _To_QS]: ... def __set__(self, instance: Any, value: Any) -> NoReturn: ... # Fake class, Django defines 'RelatedManager' inside a function body @type_check_only -class RelatedManager(Manager[_To], Generic[_To]): +class RelatedManager(Manager[_To, _To_QS], Generic[_To, _To_QS]): related_val: tuple[int, ...] def add(self, *objs: _To | int, bulk: bool = ...) -> None: ... async def aadd(self, *objs: _To | int, bulk: bool = ...) -> None: ... @@ -112,23 +114,23 @@ class RelatedManager(Manager[_To], Generic[_To]): async def aclear(self, *, clear: bool = ...) -> None: ... def set( self, - objs: QuerySet[_To] | Iterable[_To | int], + objs: _To_QS | Iterable[_To | int], *, bulk: bool = ..., clear: bool = ..., ) -> None: ... async def aset( self, - objs: QuerySet[_To] | Iterable[_To | int], + objs: _To_QS | Iterable[_To | int], *, bulk: bool = ..., clear: bool = ..., ) -> None: ... - def __call__(self, *, manager: str) -> RelatedManager[_To]: ... + def __call__(self, *, manager: str) -> RelatedManager[_To, _To_QS]: ... def create_reverse_many_to_one_manager( - superclass: type[BaseManager[_M]], rel: ManyToOneRel -) -> type[RelatedManager[_M]]: ... + superclass: type[BaseManager[_M, _M_QS]], rel: ManyToOneRel +) -> type[RelatedManager[_M, _M_QS]]: ... class ManyToManyDescriptor(ReverseManyToOneDescriptor, Generic[_To, _Through]): """ diff --git a/django-stubs/db/models/manager.pyi b/django-stubs/db/models/manager.pyi index 95704c6e8..0e1a397ca 100644 --- a/django-stubs/db/models/manager.pyi +++ b/django-stubs/db/models/manager.pyi @@ -8,13 +8,15 @@ from django.db.models.query import QuerySet, RawQuerySet from typing_extensions import Self _T = TypeVar("_T", bound=Model, covariant=True) +_QS = TypeVar("_QS", bound=QuerySet[Any], covariant=True, default=QuerySet[_T]) -class BaseManager(Generic[_T]): +class BaseManager(Generic[_T, _QS]): creation_counter: int auto_created: bool use_in_migrations: bool name: str model: type[_T] + _queryset_class: type[_QS] _db: str | None def __new__(cls, *args: Any, **kwargs: Any) -> Self: ... def __init__(self) -> None: ... @@ -24,17 +26,25 @@ class BaseManager(Generic[_T]): ) -> tuple[bool, str | None, str | None, Sequence[Any] | None, dict[str, Any] | None]: ... def check(self, **kwargs: Any) -> list[Any]: ... @classmethod - def from_queryset(cls, queryset_class: type[QuerySet[_T]], class_name: str | None = None) -> type[Self]: ... + def from_queryset( + cls, queryset_class: type[QuerySet[_T]], class_name: str | None = None + ) -> type[BaseManager[_T, _QS]]: ... @classmethod def _get_queryset_methods(cls, queryset_class: type) -> dict[str, Any]: ... def contribute_to_class(self, cls: type[Model], name: str) -> None: ... def db_manager(self, using: str | None = None, hints: dict[str, Model] | None = None) -> Self: ... @property def db(self) -> str: ... - def get_queryset(self) -> QuerySet[_T]: ... - def all(self) -> QuerySet[_T]: ... + def get_queryset(self) -> _QS: ... + def all(self) -> _QS: ... -class Manager(BaseManager[_T]): +class Manager(BaseManager[_T, _QS]): + # `from_queryset` is redeclared here because Self cannot have type arguments + # ie `def from_queryset(...) -> type[Self[_T, _QS]]` is not valid (mypy raises an error, but resolves type correctly) + @classmethod + def from_queryset( + cls, queryset_class: type[QuerySet[_T]], class_name: str | None = None + ) -> type[Manager[_T, _QS]]: ... # NOTE: The following methods are in common with QuerySet, but note that the use of QuerySet as a return type # rather than a self-type (_QS), since Manager's QuerySet-like methods return QuerySets and not Managers. def iterator(self, chunk_size: int | None = ...) -> Iterator[_T]: ... @@ -112,10 +122,10 @@ class Manager(BaseManager[_T]): def datetimes( self, field_name: str, kind: str, order: str = ..., tzinfo: datetime.tzinfo | None = ... ) -> QuerySet[_T, datetime.datetime]: ... - def none(self) -> QuerySet[_T]: ... - def filter(self, *args: Any, **kwargs: Any) -> QuerySet[_T]: ... - def exclude(self, *args: Any, **kwargs: Any) -> QuerySet[_T]: ... - def complex_filter(self, filter_obj: Any) -> QuerySet[_T]: ... + def none(self) -> _QS: ... + def filter(self, *args: Any, **kwargs: Any) -> _QS: ... + def exclude(self, *args: Any, **kwargs: Any) -> _QS: ... + def complex_filter(self, filter_obj: Any) -> _QS: ... def count(self) -> int: ... async def acount(self) -> int: ... def union(self, *other_qs: Any, all: bool = ...) -> QuerySet[_T]: ... @@ -123,13 +133,13 @@ class Manager(BaseManager[_T]): def difference(self, *other_qs: Any) -> QuerySet[_T]: ... def select_for_update( self, nowait: bool = ..., skip_locked: bool = ..., of: Sequence[str] = ..., no_key: bool = ... - ) -> QuerySet[_T]: ... - def select_related(self, *fields: Any) -> QuerySet[_T]: ... - def prefetch_related(self, *lookups: Any) -> QuerySet[_T]: ... - def annotate(self, *args: Any, **kwargs: Any) -> QuerySet[_T]: ... - def alias(self, *args: Any, **kwargs: Any) -> QuerySet[_T]: ... - def order_by(self, *field_names: Any) -> QuerySet[_T]: ... - def distinct(self, *field_names: Any) -> QuerySet[_T]: ... + ) -> _QS: ... + def select_related(self, *fields: Any) -> _QS: ... + def prefetch_related(self, *lookups: Any) -> _QS: ... + def annotate(self, *args: Any, **kwargs: Any) -> _QS: ... + def alias(self, *args: Any, **kwargs: Any) -> _QS: ... + def order_by(self, *field_names: Any) -> _QS: ... + def distinct(self, *field_names: Any) -> _QS: ... # extra() return type won't be supported any time soon def extra( self, @@ -141,9 +151,9 @@ class Manager(BaseManager[_T]): select_params: Sequence[Any] | None = ..., ) -> QuerySet[Any]: ... def reverse(self) -> QuerySet[_T]: ... - def defer(self, *fields: Any) -> QuerySet[_T]: ... - def only(self, *fields: Any) -> QuerySet[_T]: ... - def using(self, alias: str | None) -> QuerySet[_T]: ... + def defer(self, *fields: Any) -> _QS: ... + def only(self, *fields: Any) -> _QS: ... + def using(self, alias: str | None) -> _QS: ... class ManagerDescriptor: manager: BaseManager @@ -153,5 +163,9 @@ class ManagerDescriptor: @overload def __get__(self, instance: Model, cls: type[Model] | None = None) -> NoReturn: ... -class EmptyManager(Manager[_T]): - def __init__(self, model: type[_T]) -> None: ... +# We have to define different typevars here otherwise it conflicts with the ones above +_T2 = TypeVar("_T2", bound=Model, covariant=True) +_QS2 = TypeVar("_QS2", bound=QuerySet[Any], covariant=True, default=QuerySet[_T2]) + +class EmptyManager(Manager[_T2, _QS2]): + def __init__(self, model: type[_T2]) -> None: ... diff --git a/django-stubs/db/models/query.pyi b/django-stubs/db/models/query.pyi index f6dbebf60..029f53a62 100644 --- a/django-stubs/db/models/query.pyi +++ b/django-stubs/db/models/query.pyi @@ -56,7 +56,7 @@ class QuerySet(Generic[_Model, _Row], Iterable[_Row], Sized): hints: dict[str, Model] | None = None, ) -> None: ... @classmethod - def as_manager(cls) -> Manager[_Model]: ... + def as_manager(cls) -> Manager[_Model, Self]: ... def __len__(self) -> int: ... def __bool__(self) -> bool: ... def __class_getitem__(cls, item: type[_Model]) -> Self: ... diff --git a/mypy_django_plugin/lib/helpers.py b/mypy_django_plugin/lib/helpers.py index d1f1b0cf8..9d7c3084e 100644 --- a/mypy_django_plugin/lib/helpers.py +++ b/mypy_django_plugin/lib/helpers.py @@ -1,5 +1,5 @@ from collections import OrderedDict -from collections.abc import Iterable, Iterator +from collections.abc import Iterable, Iterator, Sequence from typing import TYPE_CHECKING, Any, Literal, cast from django.db.models.fields import Field @@ -563,5 +563,11 @@ def get_model_from_expression( return None -def fill_manager(manager: TypeInfo, typ: MypyType) -> Instance: - return Instance(manager, [typ] if manager.is_generic() else []) +def fill_instance(typ: TypeInfo, args: Sequence[MypyType]) -> Instance: + """ + The type might not be generic, for ex with user defined managers: + + class CustomManager(models.Manager["MyModel"]): + pass + """ + return Instance(typ, args if typ.is_generic() else []) diff --git a/mypy_django_plugin/main.py b/mypy_django_plugin/main.py index 42f946c0f..eed88e6cb 100644 --- a/mypy_django_plugin/main.py +++ b/mypy_django_plugin/main.py @@ -42,6 +42,7 @@ add_as_manager_to_queryset_class, create_new_manager_class_from_from_queryset_method, reparametrize_any_manager_hook, + reparametrize_any_queryset_hook, resolve_manager_method, ) from mypy_django_plugin.transformers.models import ( @@ -196,12 +197,13 @@ def get_method_hook(self, fullname: str) -> Callable[[MethodContext], MypyType] def get_customize_class_mro_hook(self, fullname: str) -> Callable[[ClassDefContext], None] | None: sym = self.lookup_fully_qualified(fullname) - if ( - sym is not None - and isinstance(sym.node, TypeInfo) - and sym.node.has_base(fullnames.BASE_MANAGER_CLASS_FULLNAME) - ): - return reparametrize_any_manager_hook + if sym is not None and isinstance(sym.node, TypeInfo): + if sym.node.has_base(fullnames.BASE_MANAGER_CLASS_FULLNAME): + return reparametrize_any_manager_hook + elif sym.node.has_base(fullnames.QUERYSET_CLASS_FULLNAME): + return reparametrize_any_queryset_hook + else: + return None else: return None diff --git a/mypy_django_plugin/transformers/managers.py b/mypy_django_plugin/transformers/managers.py index b812889fc..f7ea8b782 100644 --- a/mypy_django_plugin/transformers/managers.py +++ b/mypy_django_plugin/transformers/managers.py @@ -310,6 +310,8 @@ def create_new_manager_class_from_from_queryset_method(ctx: DynamicClassDefConte """ Insert a new manager class node for a: ' = .from_queryset()'. When the assignment expression lives at module level. + + class level cases are resolved in `AddManagers.try_create_manager_from_from_queryset` """ semanal_api = helpers.get_semanal_api(ctx) @@ -494,6 +496,11 @@ def populate_manager_from_queryset(manager_info: TypeInfo, queryset_info: TypeIn def add_as_manager_to_queryset_class(ctx: ClassDefContext) -> None: + """ + Insert a new manager class node for a: ' = .as_manager()'. + + Similar to `create_manager_info_from_from_queryset_call` + """ semanal_api = helpers.get_semanal_api(ctx) def _defer() -> None: @@ -573,49 +580,41 @@ def _defer() -> None: ctx.cls, "as_manager", args=[], - return_type=Instance(new_manager_info, [AnyType(TypeOfAny.from_omitted_generics)]), + return_type=Instance( + new_manager_info, [AnyType(TypeOfAny.from_omitted_generics), AnyType(TypeOfAny.from_omitted_generics)] + ), is_classmethod=True, ) -def reparametrize_any_manager_hook(ctx: ClassDefContext) -> None: +def reparametrize_generic_class(ctx: ClassDefContext, base_class_fullname: str) -> None: """ - Add implicit generics to manager classes that are defined without generic. - - Eg. - - class MyManager(models.Manager): ... - - is interpreted as: - - _T = TypeVar('_T', covariant=True) - class MyManager(models.Manager[_T]): ... + Add implicit generics to classes that are defined without generic. Note that this does not happen if mypy is run with disallow_any_generics = True, as not specifying the generic type is then considered an error. """ - - manager = ctx.api.lookup_fully_qualified_or_none(ctx.cls.fullname) - if manager is None or manager.node is None: + class_info = ctx.api.lookup_fully_qualified_or_none(ctx.cls.fullname) + if class_info is None or class_info.node is None: return - assert isinstance(manager.node, TypeInfo) + assert isinstance(class_info.node, TypeInfo) - if manager.node.type_vars: - # We've already been here + if class_info.node.type_vars: + # We've already been here or the class is already declared with generic types return - parent_manager = next( - (base for base in manager.node.bases if base.type.has_base(fullnames.BASE_MANAGER_CLASS_FULLNAME)), + parent_class = next( + (base for base in class_info.node.bases if base.type.has_base(base_class_fullname)), None, ) - if parent_manager is None or len(parent_manager.args) != 1: + if parent_class is None or len(parent_class.args) < 1: return - model_param = get_proper_type(parent_manager.args[0]) + model_param = get_proper_type(parent_class.args[0]) if not isinstance(model_param, AnyType) or model_param.type_of_any is not TypeOfAny.from_omitted_generics: return - type_vars = tuple(parent_manager.type.defn.type_vars) + type_vars = tuple(parent_class.type.defn.type_vars) # If we end up with placeholders we need to defer so the placeholders are # resolved in a future iteration @@ -625,6 +624,39 @@ class MyManager(models.Manager[_T]): ... else: return - parent_manager.args = type_vars - manager.node.defn.type_vars = list(type_vars) - manager.node.add_type_vars() + parent_class.args = type_vars + class_info.node.defn.type_vars = list(type_vars) + class_info.node.add_type_vars() + + +def reparametrize_any_manager_hook(ctx: ClassDefContext) -> None: + """ + Add implicit generics to manager classes that are defined without generic. + + Eg. + + class MyManager(models.Manager): ... + + is interpreted as: + + _T = TypeVar("_T", bound=Model, covariant=True) + _QS = TypeVar("_QS", bound=QuerySet[Any], covariant=True, default=QuerySet[_T]) + class MyManager(models.Manager[_T, _QS]): ... + """ + reparametrize_generic_class(ctx, fullnames.BASE_MANAGER_CLASS_FULLNAME) + + +def reparametrize_any_queryset_hook(ctx: ClassDefContext) -> None: + """ + Add implicit generics to queryset classes that are defined without generic. + + Eg. + + class MyQuerySet(models.QuerySet): ... + + is interpreted as: + + _T = TypeVar("_T", bound=Model, covariant=True) + class MyQuerySet(models.QuerySet[_T]): ... + """ + reparametrize_generic_class(ctx, fullnames.QUERYSET_CLASS_FULLNAME) diff --git a/mypy_django_plugin/transformers/models.py b/mypy_django_plugin/transformers/models.py index 675a66540..e790bf5e6 100644 --- a/mypy_django_plugin/transformers/models.py +++ b/mypy_django_plugin/transformers/models.py @@ -29,6 +29,7 @@ from mypy.typeanal import TypeAnalyser from mypy.types import ( AnyType, + CallableType, ExtraAttrs, Instance, ProperType, @@ -207,6 +208,62 @@ def get_or_create_queryset_with_any_fallback(self) -> TypeInfo | None: return queryset_info + def build_manager_instance( + self, manager_cls: type["Manager[Any]"], manager_info: TypeInfo, model_instance: MypyType + ) -> Instance: + """Builds an Instance of a Manager, filling in the Model and QuerySet type if possible.""" + try: + queryset_info = self.get_queryset_info_from_manager(manager_cls, manager_info) + queryset_instance = helpers.fill_instance(queryset_info, [model_instance, model_instance]) + return helpers.fill_instance(manager_info, [model_instance, queryset_instance]) + except helpers.IncompleteDefnException: + return helpers.fill_instance(manager_info, [model_instance]) + + def get_queryset_info_from_manager( + self, manager_cls: type["Manager[Any]"], manager_info: TypeInfo | None = None + ) -> TypeInfo: + """ + Extract the QuerySet TypeInfo from a manager class and type info. + This tries to be extra accommodating for loosely typed custom managers. + """ + if manager_info is not None: + # 1. Use the queryset type param if provided + # Ex: + # class MyManager(models.Manager[MyModel, MyQuerySet]): + # ... + for base in manager_info.bases: + if ( + base.type.fullname == fullnames.MANAGER_CLASS_FULLNAME + and len(base.args) == 2 + and isinstance((queryset_type := get_proper_type(base.args[1])), Instance) + ): + return queryset_type.type + + # 2. Use the return type param of `get_queryset` if explicitly defined + # Ex: + # class MyManager(models.Manager[MyModel]): + # def get_queryset(self) -> MyQuerySet: + # ... + if ( + (get_queryset_sym := manager_info.names.get("get_queryset")) + and (get_queryset_type := get_proper_type(get_queryset_sym.type)) is not None + and isinstance(get_queryset_type, CallableType) + and get_queryset_type.ret_type is not None + and (get_queryset_ret_type := get_proper_type(self.api.anal_type(get_queryset_type.ret_type))) + is not None + and isinstance(get_queryset_ret_type, Instance) + ): + return get_queryset_ret_type.type + + # 3. Fallback to the `_queryset_class` of the manager that django always populate on class creation + # Ex: + # class MyManager(models.Manager): + # _queryset_class = MyQuerySet + queryset_klass = manager_cls()._queryset_class + queryset_fullname = helpers.get_class_fullname(klass=queryset_klass) + queryset_info = self.lookup_typeinfo_or_incomplete_defn_error(queryset_fullname) + return queryset_info + def run_with_model_cls(self, model_cls: type[Model]) -> None: raise NotImplementedError(f"Implement this in subclass {self.__class__.__name__}") @@ -373,7 +430,7 @@ def reparametrize_dynamically_created_manager(self, manager_name: str, manager_i assert manager_info is not None # Reparameterize dynamically created manager with model type - manager_type = helpers.fill_manager(manager_info, Instance(self.model_classdef.info, [])) + manager_type = helpers.fill_instance(manager_info, [Instance(self.model_classdef.info, [])]) self.add_new_node_to_model_class(manager_name, manager_type, is_classvar=True) def run_with_model_cls(self, model_cls: type[Model]) -> None: @@ -392,15 +449,19 @@ def run_with_model_cls(self, model_cls: type[Model]) -> None: if manager_info is None: # We couldn't find a manager type, see if we should create one - manager_info = self.create_manager_from_from_queryset(manager_name) + manager_info = self.try_create_manager_from_from_queryset(manager_name) if manager_info is None: incomplete_manager_defs.add(manager_name) continue assert self.model_classdef.info.self_type is not None - manager_type = helpers.fill_manager(manager_info, self.model_classdef.info.self_type) - self.add_new_node_to_model_class(manager_name, manager_type, is_classvar=True) + manager_instance = self.build_manager_instance( + manager_cls=manager.__class__, + manager_info=manager_info, + model_instance=self.model_classdef.info.self_type, + ) + self.add_new_node_to_model_class(manager_name, manager_instance, is_classvar=True) if incomplete_manager_defs: if not self.api.final_iteration: @@ -416,7 +477,7 @@ def run_with_model_cls(self, model_cls: type[Model]) -> None: fallback_manager_info = self.get_or_create_manager_with_any_fallback() if fallback_manager_info is not None: assert self.model_classdef.info.self_type is not None - manager_type = helpers.fill_manager(fallback_manager_info, self.model_classdef.info.self_type) + manager_type = helpers.fill_instance(fallback_manager_info, [self.model_classdef.info.self_type]) self.add_new_node_to_model_class(manager_name, manager_type, is_classvar=True) # Find expression for e.g. `objects = SomeManager()` @@ -455,12 +516,14 @@ def get_dynamic_manager(self, fullname: str, manager: "Manager[Any]") -> TypeInf return self.lookup_typeinfo(generated_manager_name) - def create_manager_from_from_queryset(self, name: str) -> TypeInfo | None: + def try_create_manager_from_from_queryset(self, name: str) -> TypeInfo | None: """ Try to create a manager from a .from_queryset call: class MyModel(models.Model): objects = MyManager.from_queryset(MyQuerySet)() + + module level cases are resolved in `create_new_manager_class_from_from_queryset_method` """ assign_statement = self.get_manager_expression(name) @@ -479,6 +542,10 @@ def run_with_model_cls(self, model_cls: type[Model]) -> None: if "_default_manager" in self.model_classdef.info.names: return None + if model_cls._meta.default_manager is None: + # Abstract django model + return None + default_manager_cls = model_cls._meta.default_manager.__class__ default_manager_fullname = helpers.get_class_fullname(default_manager_cls) @@ -497,7 +564,11 @@ def run_with_model_cls(self, model_cls: type[Model]) -> None: return None default_manager_info = generated_manager_info - default_manager = helpers.fill_manager(default_manager_info, Instance(self.model_classdef.info, [])) + default_manager = self.build_manager_instance( + manager_cls=default_manager_cls, + manager_info=default_manager_info, + model_instance=Instance(self.model_classdef.info, []), + ) self.add_new_node_to_model_class("_default_manager", default_manager, is_classvar=True) @@ -522,6 +593,7 @@ def process_relation(self, relation: ForeignObjectRel) -> None: to_model_cls = self.django_context.get_field_related_model_cls(relation) to_model_info = self.lookup_class_typeinfo_or_incomplete_defn_error(to_model_cls) + to_model_instance = Instance(to_model_info, []) reverse_lookup_declared = attname in self.model_classdef.info.names if isinstance(relation, OneToOneRel): @@ -530,7 +602,7 @@ def process_relation(self, relation: ForeignObjectRel) -> None: attname, Instance( self.reverse_one_to_one_descriptor, - [Instance(self.model_classdef.info, []), Instance(to_model_info, [])], + [Instance(self.model_classdef.info, []), to_model_instance], ), ) return @@ -542,16 +614,20 @@ def process_relation(self, relation: ForeignObjectRel) -> None: through_model_info = self.lookup_typeinfo_or_incomplete_defn_error(through_fullname) self.add_new_node_to_model_class( attname, - Instance( - self.many_to_many_descriptor, [Instance(to_model_info, []), Instance(through_model_info, [])] - ), + Instance(self.many_to_many_descriptor, [to_model_instance, Instance(through_model_info, [])]), is_classvar=True, ) return - elif not reverse_lookup_declared: - # ManyToOneRel + elif not reverse_lookup_declared: # ManyToOneRel + # default_manager can only be None on abstract models, which cannot be used in ManyToOneRel. + assert to_model_cls._meta.default_manager is not None + + queryset_info = self.get_queryset_info_from_manager(to_model_cls._meta.default_manager.__class__) + queryset_instance = Instance(queryset_info, [to_model_instance, to_model_instance]) self.add_new_node_to_model_class( - attname, Instance(self.reverse_many_to_one_descriptor, [Instance(to_model_info, [])]), is_classvar=True + attname, + Instance(self.reverse_many_to_one_descriptor, [to_model_instance, queryset_instance]), + is_classvar=True, ) related_manager_info = self.lookup_typeinfo_or_incomplete_defn_error(fullnames.RELATED_MANAGER_CLASS) @@ -593,7 +669,7 @@ def process_relation(self, relation: ForeignObjectRel) -> None: # Create a reverse manager subclassed from the default manager of the related # model and 'RelatedManager' - related_manager = Instance(related_manager_info, [Instance(to_model_info, [])]) + related_manager = Instance(related_manager_info, [to_model_instance]) # The reverse manager is based on the related model's manager, so it makes most sense to add the new # related manager in that module new_related_manager_info = helpers.add_new_class_for_module( @@ -738,6 +814,9 @@ def run(self) -> None: model_fullname=f"{self.model_classdef.info.module_name}.{through_model_name}", m2m_args=args, ) + if through_model is not None: + self.add_through_table_managers(through_model) + container = self.model_classdef.info.get_containing_type_info(m2m_field_name) if ( through_model is not None @@ -887,21 +966,26 @@ def create_through_table_class( # Add the foreign key's '_id' field: _id or to__id other_pk = self.get_pk_instance(m2m_args.to.model.type) helpers.add_new_sym_for_info(through_model, name=f"{to_name}_id", sym_type=other_pk.copy_modified()) + return through_model + + def add_through_table_managers(self, through_model: TypeInfo) -> None: + """The `self.manager_info` lookup might trigger a deferral pass so this has to be idempotent""" # Add a manager named 'objects' - helpers.add_new_sym_for_info( - through_model, - name="objects", - sym_type=Instance(self.manager_info, [Instance(through_model, [])]), - is_classvar=True, - ) + if through_model.names.get("objects") is None: + helpers.add_new_sym_for_info( + through_model, + name="objects", + sym_type=Instance(self.manager_info, [Instance(through_model, [])]), + is_classvar=True, + ) # Also add manager as '_default_manager' attribute - helpers.add_new_sym_for_info( - through_model, - name="_default_manager", - sym_type=Instance(self.manager_info, [Instance(through_model, [])]), - is_classvar=True, - ) - return through_model + if through_model.names.get("_default_manager") is None: + helpers.add_new_sym_for_info( + through_model, + name="_default_manager", + sym_type=Instance(self.manager_info, [Instance(through_model, [])]), + is_classvar=True, + ) def resolve_many_to_many_arguments(self, call: CallExpr, /, context: Context) -> M2MArguments | None: """ diff --git a/tests/assert_type/db/models/fields/test_related_descriptors.py b/tests/assert_type/db/models/fields/test_related_descriptors.py index 3155da4c4..e975547d2 100644 --- a/tests/assert_type/db/models/fields/test_related_descriptors.py +++ b/tests/assert_type/db/models/fields/test_related_descriptors.py @@ -1,7 +1,11 @@ from typing import ClassVar from django.db import models -from django.db.models.fields.related_descriptors import RelatedManager, ReverseManyToOneDescriptor +from django.db.models.fields.related_descriptors import ( + RelatedManager, + ReverseManyToOneDescriptor, + create_reverse_many_to_one_manager, +) from typing_extensions import assert_type @@ -13,4 +17,11 @@ class MyModel(models.Model): rel = models.ForeignKey[Other, Other](Other, on_delete=models.CASCADE, related_name="explicit_descriptor") -assert_type(Other().explicit_descriptor, RelatedManager[MyModel]) +assert_type(Other().explicit_descriptor, RelatedManager[MyModel, models.QuerySet[MyModel, MyModel]]) + +# Ensure `create_reverse_many_to_one_manager` pass generic params correctly +reverse_many_to_one_manager = create_reverse_many_to_one_manager( + superclass=MyModel._default_manager.__class__, rel=MyModel.rel.field.remote_field +) +assert_type(MyModel._default_manager.__class__, type[models.Manager[MyModel, models.QuerySet[MyModel, MyModel]]]) +assert_type(reverse_many_to_one_manager, type[RelatedManager[MyModel, models.QuerySet[MyModel, MyModel]]]) diff --git a/tests/typecheck/fields/test_related.yml b/tests/typecheck/fields/test_related.yml index 780d0fceb..4d2cb1486 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.fields.related_descriptors.RelatedManager[myapp.models.Book]" + reveal_type(publisher.books) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.Book, django.db.models.query.QuerySet[myapp.models.Book, 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.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]" + reveal_type(publisher.books) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.Book, django.db.models.query.QuerySet[myapp.models.Book, myapp.models.Book]]" + reveal_type(publisher.books2) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.Book, django.db.models.query.QuerySet[myapp.models.Book, myapp.models.Book]]" installed_apps: - myapp files: @@ -160,9 +160,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.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.views) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.View, django.db.models.query.QuerySet[myapp.models.View, myapp.models.View]]" + reveal_type(self.members) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.Member, django.db.models.query.QuerySet[myapp.models.Member, myapp.models.Member]]" + reveal_type(self.sheets) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.Sheet, django.db.models.query.QuerySet[myapp.models.Sheet, 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) @@ -176,7 +176,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.fields.related_descriptors.RelatedManager[myapp.models.View]" + reveal_type(View().app.views) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.View, django.db.models.query.QuerySet[myapp.models.View, myapp.models.View]]" View().app.unknown # E: "App" has no attribute "unknown" [attr-defined] installed_apps: - myapp @@ -195,13 +195,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.fields.related_descriptors.RelatedManager[myapp.models.View]" + reveal_type(self.views) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.View, django.db.models.query.QuerySet[myapp.models.View, 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.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]" + reveal_type(App().views) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.View, django.db.models.query.QuerySet[myapp.models.View, myapp.models.View]]" + reveal_type(App().views2) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.View2, django.db.models.query.QuerySet[myapp.models.View2, myapp.models.View2]]" installed_apps: - myapp files: @@ -219,7 +219,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.fields.related_descriptors.RelatedManager[myapp2.models.View]" + reveal_type(View().app.views) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp2.models.View, django.db.models.query.QuerySet[myapp2.models.View, myapp2.models.View]]" installed_apps: - myapp - myapp2 @@ -408,7 +408,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.fields.related_descriptors.RelatedManager[myapp.models.Book]" + reveal_type(Publisher().book_set) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.Book, django.db.models.query.QuerySet[myapp.models.Book, myapp.models.Book]]" installed_apps: - myapp files: @@ -510,7 +510,7 @@ publisher = Publisher() reveal_type(publisher.books) - reveal_type(publisher.books2) # 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, django.db.models.query.QuerySet[myapp.models.Book, myapp.models.Book]]" out: | main:6: error: "Publisher" has no attribute "books"; maybe "books2"? [attr-defined] main:6: note: Revealed type is "Any" @@ -600,8 +600,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.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]" + reveal_type(Publisher().books) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.Book, django.db.models.query.QuerySet[myapp.models.Book, myapp.models.Book]]" + reveal_type(Publisher().articles) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.Article, django.db.models.query.QuerySet[myapp.models.Article, myapp.models.Article]]" installed_apps: - myapp files: @@ -656,8 +656,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.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]" + reveal_type(user.book_set) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.Book, django.db.models.query.QuerySet[myapp.models.Book, myapp.models.Book]]" + reveal_type(user.article_set) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.Article, django.db.models.query.QuerySet[myapp.models.Article, myapp.models.Article]]" installed_apps: - myapp files: @@ -688,9 +688,9 @@ reveal_type(user.article_set) # N: Revealed type is "myapp.models.Article_RelatedManager" reveal_type(user.book_set.add) # N: Revealed type is "def (*objs: myapp.models.Book | builtins.int, bulk: builtins.bool =)" reveal_type(user.article_set.add) # N: Revealed type is "def (*objs: myapp.models.Article | builtins.int, bulk: builtins.bool =)" - reveal_type(user.book_set.filter) # N: Revealed type is "def (*args: Any, **kwargs: Any) -> myapp.models.LibraryEntityQuerySet" + reveal_type(user.book_set.filter) # N: Revealed type is "def (*args: Any, **kwargs: Any) -> myapp.models.LibraryEntityQuerySet[myapp.models.Book, myapp.models.Book]" reveal_type(user.book_set.get()) # N: Revealed type is "myapp.models.Book" - reveal_type(user.article_set.filter) # N: Revealed type is "def (*args: Any, **kwargs: Any) -> myapp.models.LibraryEntityQuerySet" + reveal_type(user.article_set.filter) # N: Revealed type is "def (*args: Any, **kwargs: Any) -> myapp.models.LibraryEntityQuerySet[myapp.models.Article, myapp.models.Article]" reveal_type(user.article_set.get()) # N: Revealed type is "myapp.models.Article" reveal_type(user.book_set.queryset_method()) # N: Revealed type is "builtins.int" reveal_type(user.article_set.queryset_method()) # N: Revealed type is "builtins.int" @@ -742,7 +742,7 @@ Transaction().test() -- case: foreign_key_relationship_for_models_with_custom_manager_unsolvable +- case: foreign_key_relationship_for_models_with_generated_custom_manager main: | from myapp.models import Transaction installed_apps: @@ -771,7 +771,7 @@ class TransactionLog(models.Model): transaction = models.ForeignKey(Transaction, on_delete=models.CASCADE) out: | - myapp/models:13: note: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.TransactionLog]" + myapp/models:13: note: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.TransactionLog, django.db.models.query.QuerySet[myapp.models.TransactionLog, myapp.models.TransactionLog]]" myapp/models:15: note: Revealed type is "myapp.models.ManagerFromTransactionQuerySet[myapp.models.Transaction]" myapp/models:16: note: Revealed type is "None" @@ -1088,7 +1088,7 @@ - case: test_explicit_reverse_many_to_one_descriptor main: | from myapp.models import Other - reveal_type(Other.explicit_descriptor) # N: Revealed type is "django.db.models.fields.related_descriptors.ReverseManyToOneDescriptor[myapp.models.MyModel]" + reveal_type(Other.explicit_descriptor) # N: Revealed type is "django.db.models.fields.related_descriptors.ReverseManyToOneDescriptor[myapp.models.MyModel, django.db.models.query.QuerySet[myapp.models.MyModel, myapp.models.MyModel]]" reveal_type(Other().explicit_descriptor) # N: Revealed type is "myapp.models.MyModel_RelatedManager" reveal_type(Other().explicit_descriptor.custom_method()) # N: Revealed type is "builtins.int" installed_apps: @@ -1426,8 +1426,8 @@ from other.models import Other reveal_type(MyModel.m2m_1.through.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.MyModel_m2m_1]" reveal_type(Other.auto_through.through.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.MyModel_m2m_1]" - reveal_type(MyModel.m2m_2.through.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.Through]" - reveal_type(Other.custom_through.through.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.Through]" + reveal_type(MyModel.m2m_2.through.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.Through, django.db.models.query.QuerySet[myapp.models.Through, myapp.models.Through]]" + reveal_type(Other.custom_through.through.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.Through, django.db.models.query.QuerySet[myapp.models.Through, myapp.models.Through]]" installed_apps: - other - myapp @@ -1607,3 +1607,82 @@ p: Person reveal_type(p.friends) # N: Revealed type is "django.db.models.fields.related_descriptors.ManyRelatedManager[main.Person, main.Person_friends]" + +# regression test for #1918 +- case: related_manager_with_typed_custom_queryset + main: | + from myapp.models import MyModel + reveal_type(MyModel.objects.all()) # N: Revealed type is "myapp.models.MyQuerySet[myapp.models.MyModel]" + reveal_type(list(MyModel.objects.all())[0]) # N: Revealed type is "myapp.models.MyModel" + for x in MyModel.objects.all(): + reveal_type(x) # N: Revealed type is "myapp.models.MyModel" + reveal_type(x.relatedobject_set.all()) # N: Revealed type is "myapp.models.MyQuerySet[myapp.models.RelatedObject]" + reveal_type(list(x.relatedobject_set.all())[0]) # N: Revealed type is "myapp.models.RelatedObject" + for y in x.relatedobject_set.all(): + reveal_type(y) # N: Revealed type is "myapp.models.RelatedObject" + + installed_apps: + - myapp + files: + - path: myapp/__init__.py + - path: myapp/models.py + content: | + from django.db import models + from django.db.models.manager import BaseManager + from typing import List, Dict + from typing_extensions import Self + from typing import TypeVar, Generic + + T = TypeVar("T", bound=models.Model) + + class MyQuerySet(models.QuerySet[T], Generic[T]): ... + + class MyModel(models.Model): + objects = MyQuerySet.as_manager() + + class RelatedObject(models.Model): + objects = MyQuerySet.as_manager() + parent = models.ForeignKey(MyModel, models.PROTECT) + +# regression test for #1918 +- case: related_manager_subclassing_with_typed_custom_queryset + main: | + from myapp.models import MyModel + reveal_type(MyModel.objects.all()) # N: Revealed type is "myapp.models.BaseQuerySet[myapp.models.MyModel]" + reveal_type(list(MyModel.objects.all())[0]) # N: Revealed type is "myapp.models.MyModel" + for x in MyModel.objects.all(): + reveal_type(x) # N: Revealed type is "myapp.models.MyModel" + reveal_type(x.relatedobject_set.all()) # N: Revealed type is "myapp.models.BaseQuerySet[myapp.models.RelatedObject]" + reveal_type(list(x.relatedobject_set.all())[0]) # N: Revealed type is "myapp.models.RelatedObject" + for y in x.relatedobject_set.all(): + reveal_type(y) # N: Revealed type is "myapp.models.RelatedObject" + + installed_apps: + - myapp + files: + - path: myapp/__init__.py + - path: myapp/models.py + content: | + from django.db import models + from django.db.models.manager import BaseManager + from typing import List, Dict + from typing_extensions import Self + from typing import TypeVar, Generic + + T = TypeVar("T", bound=models.Model) + + class BaseQuerySet(models.QuerySet[T], Generic[T]): ... + + class BaseModel(models.Model): + objects = BaseQuerySet.as_manager() + class Meta: + abstract = True + + class MyModel(BaseModel): + pass + + class RelatedObjectQuerySet(BaseQuerySet["RelatedObject"]): + pass + + class RelatedObject(BaseModel): + parent = models.ForeignKey(MyModel, models.PROTECT) diff --git a/tests/typecheck/managers/querysets/test_as_manager.yml b/tests/typecheck/managers/querysets/test_as_manager.yml index 160513791..735c9d560 100644 --- a/tests/typecheck/managers/querysets/test_as_manager.yml +++ b/tests/typecheck/managers/querysets/test_as_manager.yml @@ -39,7 +39,7 @@ - case: declares_manager_type_like_django main: | from myapp.models import MyModel - reveal_type(MyModel.objects) # N: Revealed type is "myapp.models.ManagerFromMyQuerySet[myapp.models.MyModel]" + reveal_type(MyModel.objects) # N: Revealed type is "myapp.models.ManagerFromMyQuerySet[myapp.models.MyModel, myapp.models.MyQuerySet[myapp.models.MyModel, myapp.models.MyModel]]" installed_apps: - myapp files: @@ -80,9 +80,9 @@ - case: model_gets_generated_manager_as_default_manager main: | from myapp.models import MyModel - reveal_type(MyModel.objects) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[myapp.models.MyModel]" + reveal_type(MyModel.objects) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[myapp.models.MyModel, myapp.models.ModelQuerySet[myapp.models.MyModel, myapp.models.MyModel]]" reveal_type(MyModel.objects.queryset_method()) # N: Revealed type is "builtins.str" - reveal_type(MyModel._default_manager) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[myapp.models.MyModel]" + reveal_type(MyModel._default_manager) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[myapp.models.MyModel, myapp.models.ModelQuerySet[myapp.models.MyModel, myapp.models.MyModel]]" installed_apps: - myapp files: @@ -102,8 +102,8 @@ main: | from myapp.models import MyModel, ManagerFromModelQuerySet reveal_type(ManagerFromModelQuerySet) # N: Revealed type is "builtins.int" - reveal_type(MyModel.objects) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet1[myapp.models.MyModel]" - reveal_type(MyModel._default_manager) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet1[myapp.models.MyModel]" + reveal_type(MyModel.objects) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet1[myapp.models.MyModel, myapp.models.ModelQuerySet[myapp.models.MyModel, myapp.models.MyModel]]" + reveal_type(MyModel._default_manager) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet1[myapp.models.MyModel, myapp.models.ModelQuerySet[myapp.models.MyModel, myapp.models.MyModel]]" installed_apps: - myapp files: @@ -185,8 +185,8 @@ - case: handles_call_outside_of_model_class_definition main: | from myapp.models import MyModel, MyModelManager - reveal_type(MyModelManager) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[Any]" - reveal_type(MyModel.objects) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[myapp.models.MyModel]" + reveal_type(MyModelManager) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[Any, Any]" + reveal_type(MyModel.objects) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[myapp.models.MyModel, myapp.models.ModelQuerySet]" reveal_type(MyModel.objects.all()) # N: Revealed type is "myapp.models.ModelQuerySet" installed_apps: - myapp @@ -206,8 +206,8 @@ - case: handles_name_collision_when_declared_outside_of_model_class_body main: | from myapp.models import MyModel, ManagerFromModelQuerySet - reveal_type(ManagerFromModelQuerySet) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet1[Any]" - reveal_type(MyModel.objects) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet1[myapp.models.MyModel]" + reveal_type(ManagerFromModelQuerySet) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet1[Any, Any]" + reveal_type(MyModel.objects) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet1[myapp.models.MyModel, myapp.models.ModelQuerySet]" reveal_type(MyModel.objects.all()) # N: Revealed type is "myapp.models.ModelQuerySet" installed_apps: - myapp @@ -352,8 +352,8 @@ - case: reuses_generated_type_when_called_identically_for_multiple_managers main: | from myapp.models import MyModel - reveal_type(MyModel.objects_1) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[myapp.models.MyModel]" - reveal_type(MyModel.objects_2) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[myapp.models.MyModel]" + reveal_type(MyModel.objects_1) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[myapp.models.MyModel, myapp.models.ModelQuerySet]" + reveal_type(MyModel.objects_2) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[myapp.models.MyModel, myapp.models.ModelQuerySet]" reveal_type(MyModel.objects_1.all()) # N: Revealed type is "myapp.models.ModelQuerySet" reveal_type(MyModel.objects_2.all()) # N: Revealed type is "myapp.models.ModelQuerySet" installed_apps: @@ -374,7 +374,7 @@ - case: generates_new_manager_class_when_name_colliding_with_explicit_manager main: | from myapp.models import MyModel - reveal_type(MyModel.objects) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet1[myapp.models.MyModel]" + reveal_type(MyModel.objects) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet1[myapp.models.MyModel, myapp.models.ModelQuerySet]" reveal_type(MyModel.objects.custom_method()) # N: Revealed type is "builtins.int" installed_apps: - myapp @@ -397,9 +397,9 @@ - case: handles_type_collision_with_from_queryset main: | from myapp.models import MyModel, FromQuerySet - reveal_type(FromQuerySet) # N: Revealed type is "def [_T <: django.db.models.base.Model] () -> myapp.models.ManagerFromModelQuerySet[_T`1]" - reveal_type(MyModel.from_queryset) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[myapp.models.MyModel]" - reveal_type(MyModel.as_manager) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[myapp.models.MyModel]" + reveal_type(FromQuerySet) # N: Revealed type is "def [_T <: django.db.models.base.Model, _QS <: django.db.models.query.QuerySet[Any, Any] = django.db.models.query.QuerySet[_T`1, _T`1]] () -> myapp.models.ManagerFromModelQuerySet[_T`1, _QS`2 = django.db.models.query.QuerySet[_T`1, _T`1]]" + reveal_type(MyModel.from_queryset) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[myapp.models.MyModel, myapp.models.ModelQuerySet]" + reveal_type(MyModel.as_manager) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[myapp.models.MyModel, myapp.models.ModelQuerySet]" installed_apps: - myapp files: @@ -419,7 +419,7 @@ - case: nested_queryset_class_definition main: | from myapp.models import MyModel - reveal_type(MyModel.objects) # N: Revealed type is "myapp.models.ManagerFromMyQuerySet[myapp.models.MyModel]" + reveal_type(MyModel.objects) # N: Revealed type is "myapp.models.ManagerFromMyQuerySet[myapp.models.MyModel, myapp.models.MyModel.MyQuerySet[myapp.models.MyModel, myapp.models.MyModel]]" installed_apps: - myapp files: @@ -475,3 +475,41 @@ from django.contrib.auth.models import AbstractUser class Account(AbstractUser): pass + +# regression test for #1023 +- case: queryset_as_manager_from_untyped_custom_queryset + main: | + from myapp.models import AbstractNotification, Notification + reveal_type(AbstractNotification.objects) # N: Revealed type is "myapp.models.ManagerFromNotificationQuerySet[myapp.models.AbstractNotification, myapp.models.NotificationQuerySet[myapp.models.AbstractNotification, myapp.models.AbstractNotification]]" + reveal_type(Notification.objects) # N: Revealed type is "myapp.models.ManagerFromNotificationQuerySet[myapp.models.Notification]" + reveal_type(Notification.objects.all()) # N: Revealed type is "myapp.models.NotificationQuerySet[myapp.models.Notification, myapp.models.Notification]" + installed_apps: + - myapp + files: + - path: myapp/__init__.py + - path: myapp/models.py + content: | + from django.db import models + from typing_extensions import Self + + class NotificationQuerySet(models.query.QuerySet): + ''' Notification QuerySet ''' + def unsent(self) -> Self: + return self.filter(emailed=False) + + def sent(self) -> Self: + return self.filter(emailed=True) + + class AbstractNotification(models.Model): + actor_object_id = models.CharField('actor object id', max_length=255) + + objects = NotificationQuerySet.as_manager() + + class Meta: + abstract = True + verbose_name = 'Notification' + verbose_name_plural = 'Notifications' + + class Notification(AbstractNotification): + class Meta(AbstractNotification.Meta): + abstract = False diff --git a/tests/typecheck/managers/querysets/test_basic_methods.yml b/tests/typecheck/managers/querysets/test_basic_methods.yml index 4258810c2..46f8e8b18 100644 --- a/tests/typecheck/managers/querysets/test_basic_methods.yml +++ b/tests/typecheck/managers/querysets/test_basic_methods.yml @@ -44,11 +44,113 @@ class Blog(models.Model): created_at = models.DateTimeField() +- case: custom_typed_queryset_basic_methods_return_type + main: | + from django.utils import timezone + from myapp.models import Blog + + qs = Blog.objects.all() + reveal_type(qs) # N: Revealed type is "myapp.models.MyQuerySet" + reveal_type(qs.get(id=1)) # N: Revealed type is "myapp.models.Blog" + reveal_type(iter(qs)) # N: Revealed type is "typing.Iterator[myapp.models.Blog]" + reveal_type(qs.iterator()) # N: Revealed type is "typing.Iterator[myapp.models.Blog]" + reveal_type(qs.first()) # N: Revealed type is "myapp.models.Blog | None" + reveal_type(qs.earliest()) # N: Revealed type is "myapp.models.Blog" + reveal_type(qs[0]) # N: Revealed type is "myapp.models.Blog" + reveal_type(qs[:9]) # N: Revealed type is "myapp.models.MyQuerySet" + reveal_type(qs.create()) # N: Revealed type is "myapp.models.Blog" + reveal_type(qs.get_or_create()) # N: Revealed type is "tuple[myapp.models.Blog, builtins.bool]" + reveal_type(qs.exists()) # N: Revealed type is "builtins.bool" + reveal_type(qs.none()) # N: Revealed type is "myapp.models.MyQuerySet" + reveal_type(qs.update_or_create()) # N: Revealed type is "tuple[myapp.models.Blog, builtins.bool]" + reveal_type(qs.explain()) # N: Revealed type is "builtins.str" + reveal_type(qs.raw(qs.explain())) # N: Revealed type is "django.db.models.query.RawQuerySet[Any]" + # .dates / .datetimes + reveal_type(Blog.objects.dates("created_at", "day")) # N: Revealed type is "django.db.models.query.QuerySet[myapp.models.Blog, datetime.date]" + reveal_type(Blog.objects.datetimes("created_at", "day")) # N: Revealed type is "django.db.models.query.QuerySet[myapp.models.Blog, datetime.datetime]" + + # AND-ing QuerySets + reveal_type(Blog.objects.all() & Blog.objects.all()) # N: Revealed type is "myapp.models.MyQuerySet" + + # bulk methods + reveal_type(qs.count()) # N: Revealed type is "builtins.int" + reveal_type(qs.update(created_at=timezone.now())) # N: Revealed type is "builtins.int" + reveal_type(qs.in_bulk()) # N: Revealed type is "builtins.dict[Any, myapp.models.Blog]" + reveal_type(qs.bulk_update(list(qs), fields=["created_at"])) # N: Revealed type is "builtins.int" + reveal_type(qs.bulk_create([])) # N: Revealed type is "builtins.list[myapp.models.Blog]" + reveal_type(qs.delete()) # N: Revealed type is "tuple[builtins.int, builtins.dict[builtins.str, builtins.int]]" + installed_apps: + - myapp + files: + - path: myapp/__init__.py + - path: myapp/models.py + content: | + from django.db import models + class MyQuerySet(models.QuerySet["Blog"]): ... + + MyManager = models.Manager.from_queryset(MyQuerySet) + + class Blog(models.Model): + created_at = models.DateTimeField() + + objects = MyManager() + +- case: custom_untyped_queryset_basic_methods_return_type + main: | + from django.utils import timezone + from myapp.models import Blog, MyManager, MyQuerySet + + qs = Blog.objects.all() + reveal_type(qs) # N: Revealed type is "myapp.models.MyQuerySet[myapp.models.Blog, myapp.models.Blog]" + reveal_type(qs.get(id=1)) # N: Revealed type is "myapp.models.Blog" + reveal_type(iter(qs)) # N: Revealed type is "typing.Iterator[myapp.models.Blog]" + reveal_type(qs.iterator()) # N: Revealed type is "typing.Iterator[myapp.models.Blog]" + reveal_type(qs.first()) # N: Revealed type is "myapp.models.Blog | None" + reveal_type(qs.earliest()) # N: Revealed type is "myapp.models.Blog" + reveal_type(qs[0]) # N: Revealed type is "myapp.models.Blog" + reveal_type(qs[:9]) # N: Revealed type is "myapp.models.MyQuerySet[myapp.models.Blog, myapp.models.Blog]" + reveal_type(qs.create()) # N: Revealed type is "myapp.models.Blog" + reveal_type(qs.get_or_create()) # N: Revealed type is "tuple[myapp.models.Blog, builtins.bool]" + reveal_type(qs.exists()) # N: Revealed type is "builtins.bool" + reveal_type(qs.none()) # N: Revealed type is "myapp.models.MyQuerySet[myapp.models.Blog, myapp.models.Blog]" + reveal_type(qs.update_or_create()) # N: Revealed type is "tuple[myapp.models.Blog, builtins.bool]" + reveal_type(qs.explain()) # N: Revealed type is "builtins.str" + reveal_type(qs.raw(qs.explain())) # N: Revealed type is "django.db.models.query.RawQuerySet[Any]" + # .dates / .datetimes + reveal_type(Blog.objects.dates("created_at", "day")) # N: Revealed type is "django.db.models.query.QuerySet[myapp.models.Blog, datetime.date]" + reveal_type(Blog.objects.datetimes("created_at", "day")) # N: Revealed type is "django.db.models.query.QuerySet[myapp.models.Blog, datetime.datetime]" + + # AND-ing QuerySets + reveal_type(Blog.objects.all() & Blog.objects.all()) # N: Revealed type is "myapp.models.MyQuerySet[myapp.models.Blog, myapp.models.Blog]" + + # bulk methods + reveal_type(qs.count()) # N: Revealed type is "builtins.int" + reveal_type(qs.update(created_at=timezone.now())) # N: Revealed type is "builtins.int" + reveal_type(qs.in_bulk()) # N: Revealed type is "builtins.dict[Any, myapp.models.Blog]" + reveal_type(qs.bulk_update(list(qs), fields=["created_at"])) # N: Revealed type is "builtins.int" + reveal_type(qs.bulk_create([])) # N: Revealed type is "builtins.list[myapp.models.Blog]" + reveal_type(qs.delete()) # N: Revealed type is "tuple[builtins.int, builtins.dict[builtins.str, builtins.int]]" + installed_apps: + - myapp + files: + - path: myapp/__init__.py + - path: myapp/models.py + content: | + from django.db import models + class MyQuerySet(models.QuerySet): ... + + MyManager = models.Manager.from_queryset(MyQuerySet) + + class Blog(models.Model): + created_at = models.DateTimeField() + + objects = MyManager() + - case: queryset_missing_method main: | from myapp.models import User - reveal_type(User.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.User]" - User.objects.not_existing_method() # E: "Manager[User]" has no attribute "not_existing_method" [attr-defined] + reveal_type(User.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.User, django.db.models.query.QuerySet[myapp.models.User, myapp.models.User]]" + User.objects.not_existing_method() # E: "Manager[User, QuerySet[User, User]]" has no attribute "not_existing_method" [attr-defined] installed_apps: - myapp files: @@ -63,7 +165,7 @@ main: | from myapp.models import MyModel1, MyModel2 kls: type[MyModel1 | MyModel2] = MyModel1 - reveal_type(kls.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.MyModel1] | django.db.models.manager.Manager[myapp.models.MyModel2]" + reveal_type(kls.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.MyModel1, django.db.models.query.QuerySet[myapp.models.MyModel1, myapp.models.MyModel1]] | django.db.models.manager.Manager[myapp.models.MyModel2, django.db.models.query.QuerySet[myapp.models.MyModel2, myapp.models.MyModel2]]" reveal_type(kls.objects.all()) # N: Revealed type is "django.db.models.query.QuerySet[myapp.models.MyModel1, myapp.models.MyModel1] | django.db.models.query.QuerySet[myapp.models.MyModel2, myapp.models.MyModel2]" reveal_type(kls.objects.get()) # N: Revealed type is "myapp.models.MyModel1 | myapp.models.MyModel2" installed_apps: diff --git a/tests/typecheck/managers/querysets/test_from_queryset.yml b/tests/typecheck/managers/querysets/test_from_queryset.yml index 3d1199d26..8249491a8 100644 --- a/tests/typecheck/managers/querysets/test_from_queryset.yml +++ b/tests/typecheck/managers/querysets/test_from_queryset.yml @@ -73,7 +73,7 @@ - case: from_queryset_with_base_manager main: | from myapp.models import MyModel - reveal_type(MyModel().objects) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[myapp.models.MyModel]" + reveal_type(MyModel().objects) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[myapp.models.MyModel, myapp.models.ModelQuerySet[myapp.models.MyModel, myapp.models.MyModel]]" reveal_type(MyModel().objects.get()) # N: Revealed type is "myapp.models.MyModel" reveal_type(MyModel().objects.queryset_method()) # N: Revealed type is "builtins.str" reveal_type(MyModel.objects.filter(id=1).queryset_method()) # N: Revealed type is "builtins.str" @@ -100,8 +100,7 @@ - case: from_queryset_queryset_imported_from_other_module main: | from myapp.models import MyModel - reveal_type(MyModel.objects) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[myapp.models.MyModel]" - reveal_type(MyModel.objects) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[myapp.models.MyModel]" + reveal_type(MyModel.objects) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[myapp.models.MyModel, myapp.querysets.ModelQuerySet]" reveal_type(MyModel.objects.get()) # N: Revealed type is "myapp.models.MyModel" reveal_type(MyModel.objects.queryset_method()) # N: Revealed type is "myapp.querysets.ModelQuerySet" reveal_type(MyModel.objects.queryset_method_2()) # N: Revealed type is "typing.Iterable[myapp.querysets.Custom]" @@ -222,7 +221,7 @@ - case: from_queryset_generated_manager_imported_from_other_module main: | from myapp.models import MyModel - reveal_type(MyModel.objects) # N: Revealed type is "myapp.querysets.ManagerFromModelQuerySet[myapp.models.MyModel]" + reveal_type(MyModel.objects) # N: Revealed type is "myapp.querysets.ManagerFromModelQuerySet[myapp.models.MyModel, myapp.querysets.ModelQuerySet]" reveal_type(MyModel.objects.get()) # N: Revealed type is "myapp.models.MyModel" reveal_type(MyModel.objects.queryset_method()) # N: Revealed type is "myapp.querysets.ModelQuerySet" reveal_type(MyModel.objects.queryset_method_2()) # N: Revealed type is "typing.Iterable[myapp.querysets.Custom]" @@ -272,7 +271,7 @@ - case: from_queryset_annotates_manager_variable_as_type main: | from myapp.models import NewManager - reveal_type(NewManager) # N: Revealed type is "def [_T <: django.db.models.base.Model] () -> myapp.models.ManagerFromModelQuerySet[_T`1]" + reveal_type(NewManager) # N: Revealed type is "def [_T <: django.db.models.base.Model, _QS <: django.db.models.query.QuerySet[Any, Any] = django.db.models.query.QuerySet[_T`1, _T`1]] () -> myapp.models.ManagerFromModelQuerySet[_T`1, _QS`2 = django.db.models.query.QuerySet[_T`1, _T`1]]" installed_apps: - myapp files: @@ -289,7 +288,7 @@ - case: from_queryset_with_manager main: | from myapp.models import MyModel - reveal_type(MyModel().objects) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[myapp.models.MyModel]" + reveal_type(MyModel().objects) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[myapp.models.MyModel, myapp.models.ModelQuerySet[myapp.models.MyModel, myapp.models.MyModel]]" reveal_type(MyModel().objects.get()) # N: Revealed type is "myapp.models.MyModel" reveal_type(MyModel().objects.queryset_method()) # N: Revealed type is "builtins.str" installed_apps: @@ -312,7 +311,7 @@ main: | from myapp.models import MyModel1, MyModel2 kls: type[MyModel1 | MyModel2] = MyModel1 - reveal_type(kls.objects) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet1[myapp.models.MyModel1] | myapp.models.ManagerFromModelQuerySet2[myapp.models.MyModel2]" + reveal_type(kls.objects) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet1[myapp.models.MyModel1, myapp.models.ModelQuerySet1] | myapp.models.ManagerFromModelQuerySet2[myapp.models.MyModel2, myapp.models.ModelQuerySet2]" reveal_type(kls.objects.all()) # N: Revealed type is "myapp.models.ModelQuerySet1 | myapp.models.ModelQuerySet2" reveal_type(kls.objects.get()) # N: Revealed type is "myapp.models.MyModel1 | myapp.models.MyModel2" reveal_type(kls.objects.queryset_method()) # N: Revealed type is "builtins.int | builtins.str" @@ -343,8 +342,8 @@ - case: from_queryset_returns_intersection_of_manager_and_queryset main: | from myapp.models import MyModel, NewManager - reveal_type(NewManager()) # N: Revealed type is "myapp.models.ModelBaseManagerFromModelQuerySet[Never]" - reveal_type(MyModel.objects) # N: Revealed type is "myapp.models.ModelBaseManagerFromModelQuerySet[myapp.models.MyModel]" + reveal_type(NewManager()) # N: Revealed type is "myapp.models.ModelBaseManagerFromModelQuerySet[Never, django.db.models.query.QuerySet[_T`304, _T`304]]" + reveal_type(MyModel.objects) # N: Revealed type is "myapp.models.ModelBaseManagerFromModelQuerySet[myapp.models.MyModel, myapp.models.ModelQuerySet[myapp.models.MyModel, myapp.models.MyModel]]" reveal_type(MyModel.objects.get()) # N: Revealed type is "myapp.models.MyModel" reveal_type(MyModel.objects.manager_only_method()) # N: Revealed type is "builtins.int" reveal_type(MyModel.objects.manager_and_queryset_method()) # N: Revealed type is "builtins.str" @@ -369,13 +368,13 @@ - case: from_queryset_with_class_name_provided main: | from myapp.models import MyModel, NewManager, OtherModel, OtherManager - reveal_type(NewManager()) # N: Revealed type is "myapp.models.NewManager[Never]" - reveal_type(MyModel.objects) # N: Revealed type is "myapp.models.NewManager[myapp.models.MyModel]" + reveal_type(NewManager()) # N: Revealed type is "myapp.models.NewManager[Never, django.db.models.query.QuerySet[_T`310, _T`310]]" + reveal_type(MyModel.objects) # N: Revealed type is "myapp.models.NewManager[myapp.models.MyModel, myapp.models.ModelQuerySet[myapp.models.MyModel, myapp.models.MyModel]]" reveal_type(MyModel.objects.get()) # N: Revealed type is "myapp.models.MyModel" reveal_type(MyModel.objects.manager_only_method()) # N: Revealed type is "builtins.int" reveal_type(MyModel.objects.manager_and_queryset_method()) # N: Revealed type is "builtins.str" - reveal_type(OtherManager()) # N: Revealed type is "myapp.models.X[Never]" - reveal_type(OtherModel.objects) # N: Revealed type is "myapp.models.X[myapp.models.OtherModel]" + reveal_type(OtherManager()) # N: Revealed type is "myapp.models.X[Never, django.db.models.query.QuerySet[_T`312, _T`312]]" + reveal_type(OtherModel.objects) # N: Revealed type is "myapp.models.X[myapp.models.OtherModel, myapp.models.ModelQuerySet[myapp.models.OtherModel, myapp.models.OtherModel]]" reveal_type(OtherModel.objects.manager_only_method()) # N: Revealed type is "builtins.int" reveal_type(OtherModel.objects.manager_and_queryset_method()) # N: Revealed type is "builtins.str" installed_apps: @@ -403,7 +402,7 @@ - case: from_queryset_with_class_inheritance main: | from myapp.models import MyModel - reveal_type(MyModel().objects) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[myapp.models.MyModel]" + reveal_type(MyModel().objects) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[myapp.models.MyModel, myapp.models.ModelQuerySet[myapp.models.MyModel, myapp.models.MyModel]]" reveal_type(MyModel().objects.get()) # N: Revealed type is "myapp.models.MyModel" reveal_type(MyModel().objects.queryset_method()) # N: Revealed type is "builtins.str" installed_apps: @@ -427,7 +426,7 @@ - case: from_queryset_with_manager_in_another_directory_and_imports main: | from myapp.models import MyModel - reveal_type(MyModel().objects) # N: Revealed type is "myapp.managers.ManagerFromModelQuerySet[myapp.models.MyModel]" + reveal_type(MyModel().objects) # N: Revealed type is "myapp.managers.ManagerFromModelQuerySet[myapp.models.MyModel, myapp.managers.ModelQuerySet[myapp.models.MyModel, myapp.models.MyModel]]" reveal_type(MyModel().objects.get()) # N: Revealed type is "myapp.models.MyModel" reveal_type(MyModel().objects.queryset_method) # N: Revealed type is "def (param: builtins.str | None =) -> builtins.str | None" reveal_type(MyModel().objects.queryset_method('str')) # N: Revealed type is "builtins.str | None" @@ -456,7 +455,7 @@ disable_cache: true main: | from myapp.models import MyModel - reveal_type(MyModel().objects) # N: Revealed type is "myapp.managers.ManagerFromModelQuerySet[myapp.models.MyModel]" + reveal_type(MyModel().objects) # N: Revealed type is "myapp.managers.ManagerFromModelQuerySet[myapp.models.MyModel, myapp.managers.ModelQuerySet[myapp.models.MyModel, myapp.models.MyModel]]" reveal_type(MyModel().objects.get()) # N: Revealed type is "myapp.models.MyModel" reveal_type(MyModel().objects.base_queryset_method) # N: Revealed type is "def (param: builtins.int | builtins.str) -> Never" reveal_type(MyModel().objects.base_queryset_method(2)) # N: Revealed type is "Never" @@ -488,7 +487,7 @@ - case: from_queryset_with_decorated_queryset_methods main: | from myapp.models import MyModel - reveal_type(MyModel().objects) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[myapp.models.MyModel]" + reveal_type(MyModel().objects) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[myapp.models.MyModel, myapp.models.ModelQuerySet[myapp.models.MyModel, myapp.models.MyModel]]" reveal_type(MyModel().objects.queryset_method()) # N: Revealed type is "builtins.str" reveal_type(MyModel.objects.queryset_method_2()) # N: Revealed type is "builtins.int" installed_apps: @@ -516,9 +515,9 @@ - case: from_queryset_model_gets_generated_manager_as_default_manager main: | from myapp.models import MyModel - reveal_type(MyModel.objects) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[myapp.models.MyModel]" + reveal_type(MyModel.objects) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[myapp.models.MyModel, myapp.models.ModelQuerySet[myapp.models.MyModel, myapp.models.MyModel]]" reveal_type(MyModel.objects.queryset_method()) # N: Revealed type is "builtins.str" - reveal_type(MyModel._default_manager) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[myapp.models.MyModel]" + reveal_type(MyModel._default_manager) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[myapp.models.MyModel, myapp.models.ModelQuerySet[myapp.models.MyModel, myapp.models.MyModel]]" # Overloads reveal_type(MyModel.objects.overloaded_method(1)) # N: Revealed type is "builtins.int" @@ -552,7 +551,7 @@ - case: from_queryset_can_resolve_explicit_any_methods main: | from myapp.models import MyModel - reveal_type(MyModel.objects) # N: Revealed type is "myapp.models.MyManagerFromModelQuerySet[myapp.models.MyModel]" + reveal_type(MyModel.objects) # N: Revealed type is "myapp.models.MyManagerFromModelQuerySet[myapp.models.MyModel, myapp.models.ModelQuerySet[myapp.models.MyModel, myapp.models.MyModel]]" reveal_type(MyModel.objects.queryset_method(1)) # N: Revealed type is "Any" reveal_type(MyModel.objects.queryset_method) # N: Revealed type is "def (qarg: Any) -> Any" reveal_type(MyModel.objects.manager_method(2)) # N: Revealed type is "Any" @@ -718,10 +717,10 @@ - case: reuses_type_when_called_twice_identically main: | from myapp.models import MyModel, FirstManager, SecondManager - reveal_type(FirstManager) # N: Revealed type is "def [_T <: django.db.models.base.Model] () -> myapp.models.ManagerFromModelQuerySet[_T`1]" - reveal_type(SecondManager) # N: Revealed type is "def [_T <: django.db.models.base.Model] () -> myapp.models.ManagerFromModelQuerySet[_T`1]" - reveal_type(MyModel.first) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[myapp.models.MyModel]" - reveal_type(MyModel.second) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[myapp.models.MyModel]" + reveal_type(FirstManager) # N: Revealed type is "def [_T <: django.db.models.base.Model, _QS <: django.db.models.query.QuerySet[Any, Any] = django.db.models.query.QuerySet[_T`1, _T`1]] () -> myapp.models.ManagerFromModelQuerySet[_T`1, _QS`2 = django.db.models.query.QuerySet[_T`1, _T`1]]" + reveal_type(SecondManager) # N: Revealed type is "def [_T <: django.db.models.base.Model, _QS <: django.db.models.query.QuerySet[Any, Any] = django.db.models.query.QuerySet[_T`1, _T`1]] () -> myapp.models.ManagerFromModelQuerySet[_T`1, _QS`2 = django.db.models.query.QuerySet[_T`1, _T`1]]" + reveal_type(MyModel.first) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[myapp.models.MyModel, myapp.models.ModelQuerySet]" + reveal_type(MyModel.second) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[myapp.models.MyModel, myapp.models.ModelQuerySet]" installed_apps: - myapp files: @@ -743,8 +742,8 @@ - case: handles_name_collision_with_generated_type main: | from myapp.models import MyModel, ManagerFromModelQuerySet - reveal_type(ManagerFromModelQuerySet()) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[Never]" - reveal_type(MyModel.objects) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[myapp.models.MyModel]" + reveal_type(ManagerFromModelQuerySet()) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[Never, django.db.models.query.QuerySet[_T`304, _T`304]]" + reveal_type(MyModel.objects) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet[myapp.models.MyModel, myapp.models.ModelQuerySet]" installed_apps: - myapp files: @@ -765,8 +764,8 @@ main: | from myapp.models import MyModel, Generated, ManagerFromModelQuerySet reveal_type(ManagerFromModelQuerySet) # N: Revealed type is "builtins.int" - reveal_type(Generated()) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet2[Never]" - reveal_type(MyModel.objects) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet2[myapp.models.MyModel]" + reveal_type(Generated()) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet2[Never, django.db.models.query.QuerySet[_T`304, _T`304]]" + reveal_type(MyModel.objects) # N: Revealed type is "myapp.models.ManagerFromModelQuerySet2[myapp.models.MyModel, myapp.models.ModelQuerySet]" installed_apps: - myapp files: @@ -787,8 +786,8 @@ - case: accepts_explicit_none_as_class_name main: | from myapp.models import PositionalNone, NoneAsKwarg - reveal_type(PositionalNone) # N: Revealed type is "def [_T <: django.db.models.base.Model] () -> myapp.models.ManagerFromModelQuerySet[_T`1]" - reveal_type(NoneAsKwarg) # N: Revealed type is "def [_T <: django.db.models.base.Model] () -> myapp.models.ManagerFromModelQuerySet[_T`1]" + reveal_type(PositionalNone) # N: Revealed type is "def [_T <: django.db.models.base.Model, _QS <: django.db.models.query.QuerySet[Any, Any] = django.db.models.query.QuerySet[_T`1, _T`1]] () -> myapp.models.ManagerFromModelQuerySet[_T`1, _QS`2 = django.db.models.query.QuerySet[_T`1, _T`1]]" + reveal_type(NoneAsKwarg) # N: Revealed type is "def [_T <: django.db.models.base.Model, _QS <: django.db.models.query.QuerySet[Any, Any] = django.db.models.query.QuerySet[_T`1, _T`1]] () -> myapp.models.ManagerFromModelQuerySet[_T`1, _QS`2 = django.db.models.query.QuerySet[_T`1, _T`1]]" installed_apps: - myapp files: @@ -807,7 +806,7 @@ - case: uses_fallback_class_name_when_argument_is_not_string_expression main: | from myapp.models import StrCallable - reveal_type(StrCallable) # N: Revealed type is "def [_T <: django.db.models.base.Model] () -> myapp.models.ManagerFromModelQuerySet[_T`1]" + reveal_type(StrCallable) # N: Revealed type is "def [_T <: django.db.models.base.Model, _QS <: django.db.models.query.QuerySet[Any, Any] = django.db.models.query.QuerySet[_T`1, _T`1]] () -> myapp.models.ManagerFromModelQuerySet[_T`1, _QS`2 = django.db.models.query.QuerySet[_T`1, _T`1]]" installed_apps: - myapp files: @@ -943,10 +942,10 @@ ... reveal_type(Manager[models.Model].from_queryset(NonQSGeneric[int])) out: | - main:12: note: Revealed type is "type[django.db.models.manager.Manager[django.db.models.base.Model]]" - main:12: error: Argument 1 to "from_queryset" of "BaseManager" has incompatible type "UnionType[QS1, QS2]"; expected "type[QuerySet[Model, Model]]" [arg-type] - main:17: note: Revealed type is "type[django.db.models.manager.Manager[django.db.models.base.Model]]" - main:17: error: Argument 1 to "from_queryset" of "BaseManager" has incompatible type "type[NonQSGeneric[int]]"; expected "type[QuerySet[Model, Model]]" [arg-type] + main:12: note: Revealed type is "type[django.db.models.manager.Manager[django.db.models.base.Model, django.db.models.query.QuerySet[django.db.models.base.Model, django.db.models.base.Model]]]" + main:12: error: Argument 1 to "from_queryset" of "Manager" has incompatible type "UnionType[QS1, QS2]"; expected "type[QuerySet[Model, Model]]" [arg-type] + main:17: note: Revealed type is "type[django.db.models.manager.Manager[django.db.models.base.Model, django.db.models.query.QuerySet[django.db.models.base.Model, django.db.models.base.Model]]]" + main:17: error: Argument 1 to "from_queryset" of "Manager" has incompatible type "type[NonQSGeneric[int]]"; expected "type[QuerySet[Model, Model]]" [arg-type] - case: test_reverse_manager_with_foreign_key main: | @@ -969,3 +968,93 @@ field = models.CharField() b = models.ForeignKey(B, on_delete=models.CASCADE) objects = Manager() + +# regression test for #2602 +- case: custom_manager_and_from_queryset_for_untyped_custom_queryset + main: | + from myapp.models import MyModel + reveal_type(MyModel.objects_my_manager.all()) # N: Revealed type is "myapp.models.MyQuerySet[myapp.models.MyModel, myapp.models.MyModel]" + reveal_type(MyModel.objects_my_manager.get()) # N: Revealed type is "myapp.models.MyModel" + reveal_type(MyModel.objects_my_manager.all().get()) # N: Revealed type is "myapp.models.MyModel" + reveal_type(MyModel.objects_from_queryset.all()) # N: Revealed type is "myapp.models.MyQuerySet[myapp.models.MyModel, myapp.models.MyModel]" + reveal_type(MyModel.objects_from_queryset.get()) # N: Revealed type is "myapp.models.MyModel" + reveal_type(MyModel.objects_from_queryset.all().get()) # N: Revealed type is "myapp.models.MyModel" + installed_apps: + - myapp + files: + - path: myapp/__init__.py + - path: myapp/models.py + content: | + from django.db import models + + class MyQuerySet(models.QuerySet): + pass + + class MyManager(models.Manager["MyModel", MyQuerySet["MyModel"]]): + def get_queryset(self) -> MyQuerySet["MyModel"]: + return MyQuerySet(self.model, using=self._db) + + class MyModel(models.Model): + objects_my_manager = MyManager() + objects_from_queryset = models.Manager.from_queryset(MyQuerySet)() + +# regression test for #1067 +- case: inherited_custom_manager_method + main: | + from myapp.models import User + User.objects.create_user("foo@mail.com", "password", is_staff=True) + User.objects.create_user("foo@mail.com", "password", is_staff=12) # E: Argument "is_staff" to "create_user" of "UserProfileManager" has incompatible type "int"; expected "bool" [arg-type] + + installed_apps: + - django.contrib.auth + - myapp + files: + - path: myapp/__init__.py + - path: myapp/django_tenant.py + content: | + from django.contrib.auth.models import AbstractBaseUser + from django.contrib.auth.base_user import BaseUserManager + + class UserProfileManager(BaseUserManager): + def create_user( + self, + email: str | None = None, + password: str | None = None, + is_staff: bool = False, + ) -> None:... + + class PermissionsMixinFacade: ... + + class UserProfile(AbstractBaseUser, PermissionsMixinFacade): + objects = UserProfileManager() + + - path: myapp/models.py + content: | + from .django_tenant import UserProfile + + class User(UserProfile): + """This User model replaces default user model.""" + +# regression test for #1067 +# See https://github.com/typeddjango/django-stubs/issues/1067#issuecomment-1216350505 +- case: custom_queryset_method_available_on_manager + main: | + from myapp.models import Attachment + Attachment.objects.load(1) + Attachment.objects.load("12") # E: Argument 1 to "load" of "AttachmentQS" has incompatible type "str"; expected "int" [arg-type] + installed_apps: + - myapp + files: + - path: myapp/__init__.py + - path: myapp/models.py + content: | + from django.db import models + + class AttachmentQS(models.QuerySet['Attachment']): + def load(self, pk: int) -> 'Attachment': ... + + AttachmentManager = models.Manager.from_queryset(AttachmentQS) + + class Attachment(models.Model): + # ... + objects = AttachmentManager() diff --git a/tests/typecheck/managers/querysets/test_union_type.yml b/tests/typecheck/managers/querysets/test_union_type.yml index 9afda5709..0e9d04810 100644 --- a/tests/typecheck/managers/querysets/test_union_type.yml +++ b/tests/typecheck/managers/querysets/test_union_type.yml @@ -8,8 +8,8 @@ model_cls = type(instance) reveal_type(model_cls) # N: Revealed type is "type[myapp.models.Order] | type[myapp.models.User]" - reveal_type(model_cls.objects) # N: Revealed type is "myapp.models.ManagerFromMyQuerySet[myapp.models.Order] | myapp.models.ManagerFromMyQuerySet[myapp.models.User]" - reveal_type(model_cls.objects.my_method()) # N: Revealed type is "myapp.models.MyQuerySet" + reveal_type(model_cls.objects) # N: Revealed type is "myapp.models.ManagerFromMyQuerySet[myapp.models.Order, myapp.models.MyQuerySet[myapp.models.Order, myapp.models.Order]] | myapp.models.ManagerFromMyQuerySet[myapp.models.User, myapp.models.MyQuerySet[myapp.models.User, myapp.models.User]]" + reveal_type(model_cls.objects.my_method()) # N: Revealed type is "myapp.models.MyQuerySet[myapp.models.Order, myapp.models.Order] | myapp.models.MyQuerySet[myapp.models.User, myapp.models.User]" installed_apps: - myapp files: @@ -18,9 +18,10 @@ content: | from __future__ import annotations from django.db import models + from typing_extensions import Self class MyQuerySet(models.QuerySet): - def my_method(self) -> MyQuerySet: + def my_method(self) -> Self: pass UserManager = models.Manager.from_queryset(MyQuerySet) diff --git a/tests/typecheck/managers/test_managers.yml b/tests/typecheck/managers/test_managers.yml index 56445689b..f97302e0a 100644 --- a/tests/typecheck/managers/test_managers.yml +++ b/tests/typecheck/managers/test_managers.yml @@ -1,7 +1,7 @@ - case: test_every_model_has_objects_queryset_available main: | from myapp.models import User - reveal_type(User.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.User]" + reveal_type(User.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.User, django.db.models.query.QuerySet[myapp.models.User, myapp.models.User]]" reveal_type(User.objects.get()) # N: Revealed type is "myapp.models.User" installed_apps: - myapp @@ -16,8 +16,8 @@ - case: every_model_has_its_own_objects_queryset main: | from myapp.models import Parent, Child - reveal_type(Parent.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.Parent]" - reveal_type(Child.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.Child]" + reveal_type(Parent.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.Parent, django.db.models.query.QuerySet[myapp.models.Parent, myapp.models.Parent]]" + reveal_type(Child.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.Child, django.db.models.query.QuerySet[myapp.models.Child, myapp.models.Child]]" installed_apps: - myapp files: @@ -34,7 +34,7 @@ main: | from myapp.models import Base, MyModel base_instance = Base(MyModel) - reveal_type(base_instance.model_cls._default_manager) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.MyModel]" + reveal_type(base_instance.model_cls._default_manager) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.MyModel, django.db.models.query.QuerySet[myapp.models.MyModel, myapp.models.MyModel]]" installed_apps: - myapp files: @@ -48,18 +48,18 @@ class Base(Generic[_T]): def __init__(self, model_cls: type[_T]) -> None: self.model_cls = model_cls - reveal_type(self.model_cls._default_manager) # N: Revealed type is "django.db.models.manager.Manager[_T`1]" + reveal_type(self.model_cls._default_manager) # N: Revealed type is "django.db.models.manager.Manager[_T`1, django.db.models.query.QuerySet[_T`1, _T`1]]" class MyModel(models.Model): pass class Child(Base[MyModel]): def method(self) -> None: - reveal_type(self.model_cls._default_manager) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.MyModel]" + reveal_type(self.model_cls._default_manager) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.MyModel, django.db.models.query.QuerySet[myapp.models.MyModel, myapp.models.MyModel]]" - case: test_base_manager_called_on_model_cls_as_generic_parameter main: | from myapp.models import Base, MyModel base_instance = Base(MyModel) - reveal_type(base_instance.model_cls._base_manager) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.MyModel]" + reveal_type(base_instance.model_cls._base_manager) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.MyModel, django.db.models.query.QuerySet[myapp.models.MyModel, myapp.models.MyModel]]" installed_apps: - myapp files: @@ -73,17 +73,17 @@ class Base(Generic[_T]): def __init__(self, model_cls: type[_T]) -> None: self.model_cls = model_cls - reveal_type(self.model_cls._base_manager) # N: Revealed type is "django.db.models.manager.Manager[_T`1]" + reveal_type(self.model_cls._base_manager) # N: Revealed type is "django.db.models.manager.Manager[_T`1, django.db.models.query.QuerySet[_T`1, _T`1]]" class MyModel(models.Model): pass class Child(Base[MyModel]): def method(self) -> None: - reveal_type(self.model_cls._base_manager) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.MyModel]" + reveal_type(self.model_cls._base_manager) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.MyModel, django.db.models.query.QuerySet[myapp.models.MyModel, myapp.models.MyModel]]" - case: if_custom_manager_defined_it_is_set_to_default_manager main: | from myapp.models import MyModel - reveal_type(MyModel._default_manager) # N: Revealed type is "myapp.models.CustomManager[myapp.models.MyModel]" + reveal_type(MyModel._default_manager) # N: Revealed type is "myapp.models.CustomManager[myapp.models.MyModel, django.db.models.query.QuerySet[myapp.models.MyModel, myapp.models.MyModel]]" installed_apps: - myapp files: @@ -101,7 +101,7 @@ - case: if_default_manager_name_is_passed_set_default_manager_to_it main: | from myapp.models import MyModel - reveal_type(MyModel._default_manager) # N: Revealed type is "myapp.models.Manager2[myapp.models.MyModel]" + reveal_type(MyModel._default_manager) # N: Revealed type is "myapp.models.Manager2[myapp.models.MyModel, django.db.models.query.QuerySet[myapp.models.MyModel, myapp.models.MyModel]]" installed_apps: - myapp files: @@ -147,8 +147,8 @@ - case: model_imported_from_different_file main: | from myapp.models import Inventory, Band - reveal_type(Inventory.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.main.Inventory]" - reveal_type(Band.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.Band]" + reveal_type(Inventory.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.main.Inventory, django.db.models.query.QuerySet[myapp.models.main.Inventory, myapp.models.main.Inventory]]" + reveal_type(Band.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.Band, django.db.models.query.QuerySet[myapp.models.Band, myapp.models.Band]]" installed_apps: - myapp files: @@ -168,7 +168,7 @@ - case: managers_that_defined_on_other_models_do_not_influence main: | from myapp.models import AbstractPerson, Book - reveal_type(AbstractPerson.abstract_persons) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.AbstractPerson]" + reveal_type(AbstractPerson.abstract_persons) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.AbstractPerson, django.db.models.query.QuerySet[myapp.models.AbstractPerson, myapp.models.AbstractPerson]]" reveal_type(Book.published_objects) # N: Revealed type is "myapp.models.PublishedBookManager" Book.published_objects.create(title='hello') reveal_type(Book.annotated_objects) # N: Revealed type is "myapp.models.AnnotatedBookManager" @@ -232,13 +232,13 @@ - case: managers_inherited_from_abstract_classes_multiple_inheritance_with_generic main: | from myapp.models import AbstractBase1, AbstractBase2, Child - reveal_type(Child.manager1) # N: Revealed type is "myapp.models.CustomManager1[myapp.models.Child]" + reveal_type(Child.manager1) # N: Revealed type is "myapp.models.CustomManager1[myapp.models.Child, django.db.models.query.QuerySet[myapp.models.Child, myapp.models.Child]]" reveal_type(Child.manager1.get()) # N: Revealed type is "myapp.models.Child" - reveal_type(Child.restricted) # N: Revealed type is "myapp.models.CustomManager2[myapp.models.Child]" + reveal_type(Child.restricted) # N: Revealed type is "myapp.models.CustomManager2[myapp.models.Child, django.db.models.query.QuerySet[myapp.models.Child, myapp.models.Child]]" reveal_type(Child.restricted.get()) # N: Revealed type is "myapp.models.Child" - reveal_type(AbstractBase1.manager1) # N: Revealed type is "myapp.models.CustomManager1[myapp.models.AbstractBase1]" + reveal_type(AbstractBase1.manager1) # N: Revealed type is "myapp.models.CustomManager1[myapp.models.AbstractBase1, django.db.models.query.QuerySet[myapp.models.AbstractBase1, myapp.models.AbstractBase1]]" reveal_type(AbstractBase1.manager1.get()) # N: Revealed type is "myapp.models.AbstractBase1" - reveal_type(AbstractBase2.restricted) # N: Revealed type is "myapp.models.CustomManager2[myapp.models.AbstractBase2]" + reveal_type(AbstractBase2.restricted) # N: Revealed type is "myapp.models.CustomManager2[myapp.models.AbstractBase2, django.db.models.query.QuerySet[myapp.models.AbstractBase2, myapp.models.AbstractBase2]]" reveal_type(AbstractBase2.restricted.get()) # N: Revealed type is "myapp.models.AbstractBase2" installed_apps: - myapp @@ -270,10 +270,10 @@ - case: model_has_a_manager_of_the_same_type main: | from myapp.models import UnrelatedModel, MyModel - reveal_type(UnrelatedModel.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.UnrelatedModel]" + reveal_type(UnrelatedModel.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.UnrelatedModel, django.db.models.query.QuerySet[myapp.models.UnrelatedModel, myapp.models.UnrelatedModel]]" reveal_type(UnrelatedModel.objects.first()) # N: Revealed type is "myapp.models.UnrelatedModel | None" - reveal_type(MyModel.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.MyModel]" + reveal_type(MyModel.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.MyModel, django.db.models.query.QuerySet[myapp.models.MyModel, myapp.models.MyModel]]" reveal_type(MyModel.objects.first()) # N: Revealed type is "myapp.models.MyModel | None" installed_apps: - myapp @@ -291,10 +291,10 @@ - case: manager_without_annotation_of_the_model_gets_it_from_outer_one main: | from myapp.models import UnrelatedModel2, MyModel2 - reveal_type(UnrelatedModel2.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.UnrelatedModel2]" + reveal_type(UnrelatedModel2.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.UnrelatedModel2, django.db.models.query.QuerySet[myapp.models.UnrelatedModel2, myapp.models.UnrelatedModel2]]" reveal_type(UnrelatedModel2.objects.first()) # N: Revealed type is "myapp.models.UnrelatedModel2 | None" - reveal_type(MyModel2.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.MyModel2]" + reveal_type(MyModel2.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.MyModel2, django.db.models.query.QuerySet[myapp.models.MyModel2, myapp.models.MyModel2]]" reveal_type(MyModel2.objects.first()) # N: Revealed type is "myapp.models.MyModel2 | None" installed_apps: - myapp @@ -312,10 +312,10 @@ - case: inherited_manager_has_the_proper_type_of_model main: | from myapp.models import ParentOfMyModel3, MyModel3 - reveal_type(ParentOfMyModel3.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.ParentOfMyModel3]" + reveal_type(ParentOfMyModel3.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.ParentOfMyModel3, django.db.models.query.QuerySet[myapp.models.ParentOfMyModel3, myapp.models.ParentOfMyModel3]]" reveal_type(ParentOfMyModel3.objects.first()) # N: Revealed type is "myapp.models.ParentOfMyModel3 | None" - reveal_type(MyModel3.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.MyModel3]" + reveal_type(MyModel3.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.MyModel3, django.db.models.query.QuerySet[myapp.models.MyModel3, myapp.models.MyModel3]]" reveal_type(MyModel3.objects.first()) # N: Revealed type is "myapp.models.MyModel3 | None" installed_apps: - myapp @@ -333,10 +333,10 @@ - case: inheritance_with_explicit_type_on_child_manager main: | from myapp.models import ParentOfMyModel4, MyModel4 - reveal_type(ParentOfMyModel4.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.ParentOfMyModel4]" + reveal_type(ParentOfMyModel4.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.ParentOfMyModel4, django.db.models.query.QuerySet[myapp.models.ParentOfMyModel4, myapp.models.ParentOfMyModel4]]" reveal_type(ParentOfMyModel4.objects.first()) # N: Revealed type is "myapp.models.ParentOfMyModel4 | None" - reveal_type(MyModel4.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.MyModel4]" + reveal_type(MyModel4.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.MyModel4, django.db.models.query.QuerySet[myapp.models.MyModel4, myapp.models.MyModel4]]" reveal_type(MyModel4.objects.first()) # N: Revealed type is "myapp.models.MyModel4 | None" installed_apps: - myapp @@ -382,14 +382,14 @@ - case: custom_manager_returns_proper_model_types main: | from myapp.models import User - reveal_type(User.objects) # N: Revealed type is "myapp.models.MyManager[myapp.models.User]" + reveal_type(User.objects) # N: Revealed type is "myapp.models.MyManager[myapp.models.User, django.db.models.query.QuerySet[myapp.models.User, myapp.models.User]]" reveal_type(User.objects.select_related()) # N: Revealed type is "django.db.models.query.QuerySet[myapp.models.User, myapp.models.User]" reveal_type(User.objects.get()) # N: Revealed type is "myapp.models.User" reveal_type(User.objects.get_instance()) # N: Revealed type is "builtins.int" reveal_type(User.objects.get_instance_untyped('hello')) # N: Revealed type is "Any" from myapp.models import ChildUser - reveal_type(ChildUser.objects) # N: Revealed type is "myapp.models.MyManager[myapp.models.ChildUser]" + reveal_type(ChildUser.objects) # N: Revealed type is "myapp.models.MyManager[myapp.models.ChildUser, django.db.models.query.QuerySet[myapp.models.ChildUser, myapp.models.ChildUser]]" reveal_type(ChildUser.objects.select_related()) # N: Revealed type is "django.db.models.query.QuerySet[myapp.models.ChildUser, myapp.models.ChildUser]" reveal_type(ChildUser.objects.get()) # N: Revealed type is "myapp.models.ChildUser" reveal_type(ChildUser.objects.get_instance()) # N: Revealed type is "builtins.int" @@ -415,7 +415,7 @@ - case: custom_manager_annotate_method_before_type_declaration main: | from myapp.models import ModelA, ModelB, ManagerA - reveal_type(ModelA.objects) # N: Revealed type is "myapp.models.ManagerA[myapp.models.ModelA]" + reveal_type(ModelA.objects) # N: Revealed type is "myapp.models.ManagerA[myapp.models.ModelA, django.db.models.query.QuerySet[myapp.models.ModelA, myapp.models.ModelA]]" reveal_type(ModelA.objects.do_something) # N: Revealed type is "def (other_obj: myapp.models.ModelB) -> builtins.str" installed_apps: - myapp @@ -434,7 +434,7 @@ movie = models.TextField() -- case: override_manager_create1 +- case: override_manager_create_no_type_param main: | from myapp.models import MyModel installed_apps: @@ -458,7 +458,7 @@ objects = MyModelManager() -- case: override_manager_create2 +- case: override_manager_create_with_type_param main: | from myapp.models import MyModel MyModel.objects.create() @@ -478,10 +478,36 @@ class MyModel(models.Model): objects = MyModelManager() +- case: override_manager_create_with_incoherent_type_param + main: | + from myapp.models import MyModel + MyModel.objects.create() + installed_apps: + - myapp + out: | + myapp/models:5: error: Return type "MyModel" of "create" incompatible with return type "MyOtherModel" in supertype "django.db.models.manager.Manager" [override] + myapp/models:6: error: Incompatible return value type (got "MyOtherModel", expected "MyModel") [return-value] + files: + - path: myapp/__init__.py + - path: myapp/models.py + content: | + from typing import Any + from django.db import models + class MyModelManager(models.Manager['MyOtherModel']): + + def create(self, **kwargs: Any) -> 'MyModel': + return super().create(**kwargs) + + class MyModel(models.Model): + objects = MyModelManager() + + class MyOtherModel(models.Model): + pass + - case: regression_manager_scope_foreign main: | from myapp.models import MyModel - reveal_type(MyModel.on_site) # N: Revealed type is "django.contrib.sites.managers.CurrentSiteManager[myapp.models.MyModel]" + reveal_type(MyModel.on_site) # N: Revealed type is "django.contrib.sites.managers.CurrentSiteManager[myapp.models.MyModel, django.db.models.query.QuerySet[myapp.models.MyModel, myapp.models.MyModel]]" installed_apps: - myapp - django.contrib.sites @@ -581,17 +607,17 @@ myapp/models:23: error: Could not resolve manager type for "myapp.models.TwoUnresolvable.objects" [django-manager-missing] myapp/models:24: error: Could not resolve manager type for "myapp.models.TwoUnresolvable.second_objects" [django-manager-missing] myapp/models:27: error: Could not resolve manager type for "myapp.models.AbstractUnresolvable.objects" [django-manager-missing] - myapp/models:36: note: Revealed type is "django.db.models.manager.Manager[myapp.models.User]" - myapp/models:37: note: Revealed type is "django.db.models.manager.Manager[myapp.models.User]" + myapp/models:36: note: Revealed type is "django.db.models.manager.Manager[myapp.models.User, django.db.models.query.QuerySet[myapp.models.User, myapp.models.User]]" + myapp/models:37: note: Revealed type is "django.db.models.manager.Manager[myapp.models.User, django.db.models.query.QuerySet[myapp.models.User, myapp.models.User]]" myapp/models:39: note: Revealed type is "myapp.models.UnknownManager[myapp.models.Booking]" - myapp/models:40: note: Revealed type is "django.db.models.manager.Manager[myapp.models.Booking]" + myapp/models:40: note: Revealed type is "django.db.models.manager.Manager[myapp.models.Booking, django.db.models.query.QuerySet[myapp.models.Booking, myapp.models.Booking]]" myapp/models:42: note: Revealed type is "myapp.models.UnknownManager[myapp.models.TwoUnresolvable]" myapp/models:43: note: Revealed type is "myapp.models.UnknownManager[myapp.models.TwoUnresolvable]" - myapp/models:44: note: Revealed type is "django.db.models.manager.Manager[myapp.models.TwoUnresolvable]" + myapp/models:44: note: Revealed type is "django.db.models.manager.Manager[myapp.models.TwoUnresolvable, django.db.models.query.QuerySet[myapp.models.TwoUnresolvable, myapp.models.TwoUnresolvable]]" myapp/models:46: note: Revealed type is "myapp.models.UnknownManager[myapp.models.InvisibleUnresolvable]" - myapp/models:47: note: Revealed type is "django.db.models.manager.Manager[myapp.models.InvisibleUnresolvable]" - myapp/models:49: note: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.Booking]" - myapp/models:50: note: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.Booking]" + myapp/models:47: note: Revealed type is "django.db.models.manager.Manager[myapp.models.InvisibleUnresolvable, django.db.models.query.QuerySet[myapp.models.InvisibleUnresolvable, myapp.models.InvisibleUnresolvable]]" + myapp/models:49: note: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.Booking, django.db.models.query.QuerySet[myapp.models.Booking, myapp.models.Booking]]" + myapp/models:50: note: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.Booking, django.db.models.query.QuerySet[myapp.models.Booking, myapp.models.Booking]]" myapp/models:53: note: Revealed type is "def () -> myapp.models.UnknownQuerySet[myapp.models.Booking, myapp.models.Booking]" myapp/models:54: note: Revealed type is "Any" myapp/models:55: note: Revealed type is "def (*args: Any, **kwargs: Any) -> myapp.models.UnknownQuerySet[myapp.models.Booking, myapp.models.Booking]" @@ -601,7 +627,7 @@ myapp/models:59: note: Revealed type is "builtins.list[myapp.models.Booking]" myapp/models:60: note: Revealed type is "builtins.list[myapp.models.Booking]" myapp/models:64: note: Revealed type is "def () -> django.db.models.query.QuerySet[myapp.models.Booking, myapp.models.Booking]" - myapp/models:65: error: "RelatedManager[Booking]" has no attribute "custom" [attr-defined] + myapp/models:65: error: "RelatedManager[Booking, QuerySet[Booking, Booking]]" has no attribute "custom" [attr-defined] myapp/models:65: note: Revealed type is "Any" myapp/models:66: note: Revealed type is "def (*args: Any, **kwargs: Any) -> django.db.models.query.QuerySet[myapp.models.Booking, myapp.models.Booking]" myapp/models:67: error: "QuerySet[Booking, Booking]" has no attribute "custom" [attr-defined] @@ -680,7 +706,7 @@ installed_apps: - myapp out: | - main:2: note: Revealed type is "myapp.models.MyModel.MyManager[myapp.models.MyModel]" + main:2: note: Revealed type is "myapp.models.MyModel.MyManager[myapp.models.MyModel, django.db.models.query.QuerySet[myapp.models.MyModel, myapp.models.MyModel]]" main:3: note: Revealed type is "myapp.models.MyModel" files: - path: myapp/__init__.py @@ -726,7 +752,7 @@ reveal_type(MyModel.populated_manager_from_populated_queryset.get()) # N: Revealed type is "myapp.models.MyModel" reveal_type(MyModel.populated_manager_from_populated_queryset.filter()) # N: Revealed type is "myapp.models.PopulatedQuerySet" - reveal_type(MyModel.generic_manager) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.MyModel]" + reveal_type(MyModel.generic_manager) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.MyModel, django.db.models.query.QuerySet[myapp.models.MyModel, myapp.models.MyModel]]" reveal_type(MyModel.generic_manager.get()) # N: Revealed type is "myapp.models.MyModel" reveal_type(MyModel.generic_manager.filter()) # N: Revealed type is "django.db.models.query.QuerySet[myapp.models.MyModel, myapp.models.MyModel]" @@ -734,7 +760,7 @@ reveal_type(MyModel.generic_manager_from_generic_queryset.get()) # N: Revealed type is "myapp.models.MyModel" reveal_type(MyModel.generic_manager_from_generic_queryset.filter()) # N: Revealed type is "django.db.models.query.QuerySet[myapp.models.MyModel, myapp.models.MyModel]" - reveal_type(MyModel.generic_manager_from_populated_queryset) # N: Revealed type is "myapp.models.ManagerFromPopulatedQuerySet[myapp.models.MyModel]" + reveal_type(MyModel.generic_manager_from_populated_queryset) # N: Revealed type is "myapp.models.ManagerFromPopulatedQuerySet[myapp.models.MyModel, myapp.models.PopulatedQuerySet]" reveal_type(MyModel.generic_manager_from_populated_queryset.get()) # N: Revealed type is "myapp.models.MyModel" reveal_type(MyModel.generic_manager_from_populated_queryset.filter()) # N: Revealed type is "myapp.models.PopulatedQuerySet" @@ -762,11 +788,54 @@ generic_manager_from_generic_queryset = GenericManagerFromGenericQuerySet() generic_manager_from_populated_queryset = GenericManagerFromPopulatedQuerySet() +- case: manager_with_custom_queryset + main: | + from myapp.models import MyModel + reveal_type(MyModel.my_untyped_manager) # N: Revealed type is "myapp.models.MyUntypedManager[myapp.models.MyModel, django.db.models.query.QuerySet[myapp.models.MyModel, myapp.models.MyModel]]" + reveal_type(MyModel.my_untyped_manager.all()) # N: Revealed type is "django.db.models.query.QuerySet[myapp.models.MyModel, myapp.models.MyModel]" + reveal_type(MyModel.my_untyped_manager.get()) # N: Revealed type is "myapp.models.MyModel" + + reveal_type(MyModel.my_typed_manager) # N: Revealed type is "myapp.models.MyTypedManager" + reveal_type(MyModel.my_typed_manager.all()) # N: Revealed type is "myapp.models.MyOtherQuerySet" + reveal_type(MyModel.my_typed_manager.get()) # N: Revealed type is "myapp.models.MyModel" + + reveal_type(MyModel.my_typed_manager_confilicting_generic) # N: Revealed type is "myapp.models.MyOtherManagerConflictingGeneric" + reveal_type(MyModel.my_typed_manager_confilicting_generic.all()) # N: Revealed type is "myapp.models.MyQuerySet" + reveal_type(MyModel.my_typed_manager_confilicting_generic.get()) # N: Revealed type is "myapp.models.MyModel" + + installed_apps: + - myapp + files: + - path: myapp/__init__.py + - path: myapp/models.py + content: | + from django.db import models + from typing_extensions import Self + + class MyQuerySet(models.QuerySet["MyModel"]): ... + class MyOtherQuerySet(models.QuerySet["MyModel"]): ... + + class MyUntypedManager(models.Manager): + def get_queryset(self) -> MyQuerySet: # E: Return type "MyQuerySet" of "get_queryset" incompatible with return type "_QS" in supertype "django.db.models.manager.BaseManager" [override] + return MyQuerySet(self.model, using=self._db) + + class MyTypedManager(models.Manager["MyModel", MyOtherQuerySet]): + pass + + class MyOtherManagerConflictingGeneric(models.Manager["MyModel", MyQuerySet]): + def get_queryset(self) -> MyOtherQuerySet: # E: Return type "MyOtherQuerySet" of "get_queryset" incompatible with return type "MyQuerySet" in supertype "django.db.models.manager.BaseManager" [override] + return MyOtherQuerySet(self.model, using=self._db) + + class MyModel(models.Model): + my_untyped_manager = MyUntypedManager() + my_typed_manager = MyTypedManager() + my_typed_manager_confilicting_generic = MyOtherManagerConflictingGeneric() + # Regression test for #2304 - case: test_objects_managers_is_kept_with_specific_import_graph main: | from zerver.models import RealmFilter - reveal_type(RealmFilter.objects) # N: Revealed type is "django.db.models.manager.Manager[zerver.models.linkifiers.RealmFilter]" + reveal_type(RealmFilter.objects) # N: Revealed type is "django.db.models.manager.Manager[zerver.models.linkifiers.RealmFilter, django.db.models.query.QuerySet[zerver.models.linkifiers.RealmFilter, zerver.models.linkifiers.RealmFilter]]" installed_apps: - django.contrib.auth - django.contrib.contenttypes @@ -811,3 +880,74 @@ from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin class UserProfile(AbstractBaseUser, PermissionsMixin): pass + +# Regression test for #1023 +- case: inherited_default_objects_manager + main: | + from myapp.models import Base, Foo, Bar, UUIDPrimaryKeyAbstractModel, FooModel + reveal_type(Base.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.Base, django.db.models.query.QuerySet[myapp.models.Base, myapp.models.Base]]" + reveal_type(FooModel.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.FooModel, django.db.models.query.QuerySet[myapp.models.FooModel, myapp.models.FooModel]]" + reveal_type(Foo.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.Foo, django.db.models.query.QuerySet[myapp.models.Foo, myapp.models.Foo]]" + reveal_type(Bar.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.Bar, django.db.models.query.QuerySet[myapp.models.Bar, myapp.models.Bar]]" + installed_apps: + - myapp + files: + - path: myapp/__init__.py + - path: myapp/models.py + content: | + from django.db import models + import uuid + + class Base(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + + class FooModel(Base): + foo = models.CharField(max_length=100) + bar = models.BooleanField() + + # From https://github.com/typeddjango/django-stubs/issues/1023#issuecomment-1174243283 + class UUIDPrimaryKeyAbstractModel(models.Model): + id = models.UUIDField(primary_key=True, unique=True, default=uuid.uuid4, editable=False) + + class Meta: + abstract = True + + class Foo(UUIDPrimaryKeyAbstractModel): + pass + + class Bar(UUIDPrimaryKeyAbstractModel): + foo = models.ForeignKey(Foo, on_delete=models.CASCADE) + +# regression test for #728 +- case: typed_manager_from_untyped_custom_queryset + main: | + from myapp.models import Transaction + reveal_type(Transaction.objects) # N: Revealed type is "myapp.models.TransactionManager[myapp.models.Transaction, myapp.models.TransactionQuerySet[myapp.models.Transaction, myapp.models.Transaction]]" + reveal_type(Transaction.objects.select_related()) # N: Revealed type is "myapp.models.TransactionQuerySet[myapp.models.Transaction, myapp.models.Transaction]" + reveal_type(Transaction.objects.all().get()) # N: Revealed type is "myapp.models.Transaction" + reveal_type(Transaction.objects.get()) # N: Revealed type is "myapp.models.Transaction" + + Transaction.objects.with_status() + Transaction.objects.select_related().with_status() + + installed_apps: + - myapp + files: + - path: myapp/__init__.py + - path: myapp/models.py + content: | + from django.db import models + from typing_extensions import Self + + class TransactionQuerySet(models.QuerySet): + def with_status(self) -> Self: + return self.filter(status=...) + + class TransactionManager(models.Manager): + def get_queryset(self) -> TransactionQuerySet: # E: Return type "TransactionQuerySet[Any, Any]" of "get_queryset" incompatible with return type "_QS" in supertype "django.db.models.manager.BaseManager" [override] + return TransactionQuerySet(self.model, using=self._db) + def with_status(self) -> TransactionQuerySet: + return self.get_queryset().with_status() + + class Transaction(models.Model): + objects = TransactionManager() diff --git a/tests/typecheck/models/test_contrib_models.yml b/tests/typecheck/models/test_contrib_models.yml index a37c184bd..e866d4ed6 100644 --- a/tests/typecheck/models/test_contrib_models.yml +++ b/tests/typecheck/models/test_contrib_models.yml @@ -165,7 +165,7 @@ from django.contrib.contenttypes.models import ContentType reveal_type(Permission().user_set) # N: Revealed type is "django.contrib.auth.models.User_ManyRelatedManager[django.contrib.auth.models.User_user_permissions]" reveal_type(Group().user_set) # N: Revealed type is "django.contrib.auth.models.User_ManyRelatedManager[django.contrib.auth.models.User_groups]" - reveal_type(ContentType.permission_set) # N: Revealed type is "django.db.models.fields.related_descriptors.ReverseManyToOneDescriptor[django.contrib.auth.models.Permission]" + reveal_type(ContentType.permission_set) # N: Revealed type is "django.db.models.fields.related_descriptors.ReverseManyToOneDescriptor[django.contrib.auth.models.Permission, django.db.models.query.QuerySet[django.contrib.auth.models.Permission, django.contrib.auth.models.Permission]]" reveal_type(ContentType().permission_set) # N: Revealed type is "django.contrib.auth.models.Permission_RelatedManager" custom_settings: | INSTALLED_APPS = ('django.contrib.contenttypes', 'django.contrib.auth') diff --git a/tests/typecheck/models/test_related_fields.yml b/tests/typecheck/models/test_related_fields.yml index 802a5a4ae..8247903d5 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.fields.related_descriptors.ReverseManyToOneDescriptor[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.ReverseManyToOneDescriptor[app1.models.Model2, django.db.models.query.QuerySet[app1.models.Model2, app1.models.Model2]]" + reveal_type(Model1().model_2s) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[app1.models.Model2, django.db.models.query.QuerySet[app1.models.Model2, app1.models.Model2]]" installed_apps: - app1 @@ -127,7 +127,7 @@ - case: test_processes_other_relations_when_one_field_is_broken main: | from myapp.models import MyModel - reveal_type(MyModel().others) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.Other]" + reveal_type(MyModel().others) # N: Revealed type is "django.db.models.fields.related_descriptors.RelatedManager[myapp.models.Other, django.db.models.query.QuerySet[myapp.models.Other, myapp.models.Other]]" installed_apps: - myapp files: diff --git a/tests/typecheck/test_config.yml b/tests/typecheck/test_config.yml index a73a748ce..3ba95d22c 100644 --- a/tests/typecheck/test_config.yml +++ b/tests/typecheck/test_config.yml @@ -4,7 +4,7 @@ mymodel = MyModel(user_id=1) reveal_type(mymodel.id) # N: Revealed type is "builtins.int" reveal_type(mymodel.user) # N: Revealed type is "django.contrib.auth.models.User" - reveal_type(mymodel.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.MyModel]" + reveal_type(mymodel.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.MyModel, django.db.models.query.QuerySet[myapp.models.MyModel, myapp.models.MyModel]]" mypy_config: | [mypy.plugins.django-stubs] django_settings_module = mysettings diff --git a/tests/typecheck/test_forms.yml b/tests/typecheck/test_forms.yml index ecdedbb6f..ccc4ee824 100644 --- a/tests/typecheck/test_forms.yml +++ b/tests/typecheck/test_forms.yml @@ -136,7 +136,7 @@ queryset=Article.objects.none(), ) best_category = ArticleChoiceField( - queryset=Category.objects.none(), # E: Argument "queryset" to "ArticleChoiceField" has incompatible type "QuerySet[Category, Category]"; expected "Manager[Article] | QuerySet[Article, Article] | None" [arg-type] + queryset=Category.objects.none(), # E: Argument "queryset" to "ArticleChoiceField" has incompatible type "QuerySet[Category, Category]"; expected "Manager[Article, QuerySet[Article, Article]] | QuerySet[Article, Article] | None" [arg-type] ) installed_apps: - myapp diff --git a/tests/typecheck/test_shortcuts.yml b/tests/typecheck/test_shortcuts.yml index 41af11016..d1aa1721b 100644 --- a/tests/typecheck/test_shortcuts.yml +++ b/tests/typecheck/test_shortcuts.yml @@ -4,6 +4,7 @@ from myapp.models import MyModel reveal_type(get_object_or_404(MyModel)) # N: Revealed type is "myapp.models.MyModel" + reveal_type(MyModel.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.MyModel, django.db.models.query.QuerySet[myapp.models.MyModel, myapp.models.MyModel]]" reveal_type(get_object_or_404(MyModel.objects)) # N: Revealed type is "myapp.models.MyModel" reveal_type(get_object_or_404(MyModel.objects.get_queryset())) # N: Revealed type is "myapp.models.MyModel" @@ -24,7 +25,7 @@ main: | from django.contrib.auth import get_user_model UserModel = get_user_model() - reveal_type(UserModel.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.MyUser]" + reveal_type(UserModel.objects) # N: Revealed type is "django.db.models.manager.Manager[myapp.models.MyUser, django.db.models.query.QuerySet[myapp.models.MyUser, myapp.models.MyUser]]" custom_settings: | INSTALLED_APPS = ('django.contrib.contenttypes', 'django.contrib.auth', 'myapp') AUTH_USER_MODEL = 'myapp.MyUser'