From 5a0f00dde1fc7bb243327da1efb606878563f67e Mon Sep 17 00:00:00 2001 From: Maxim Kurnikov Date: Thu, 24 Jan 2019 18:59:41 +0300 Subject: [PATCH] fix problem with TypeVar bound to Model passed as a parameter --- django-stubs/contrib/auth/models.pyi | 15 +------------- django-stubs/db/models/base.pyi | 6 ++++-- django-stubs/db/models/manager.pyi | 4 ++-- django-stubs/db/models/query.pyi | 2 +- mypy_django_plugin/plugins/models.py | 7 ++++++- test-data/typecheck/models.test | 30 +++++++++++++++++++++++++++- 6 files changed, 43 insertions(+), 21 deletions(-) diff --git a/django-stubs/contrib/auth/models.pyi b/django-stubs/contrib/auth/models.pyi index 52f6b75..15e7b31 100644 --- a/django-stubs/contrib/auth/models.pyi +++ b/django-stubs/contrib/auth/models.pyi @@ -21,12 +21,6 @@ class Permission(models.Model): name: str = ... content_type: Any = ... codename: str = ... - objects: Any = ... - class Meta: - verbose_name: Any = ... - verbose_name_plural: Any = ... - unique_together: Any = ... - ordering: Any = ... def natural_key(self) -> Tuple[str, str, str]: ... class GroupManager(models.Manager): @@ -40,10 +34,6 @@ class Group(models.Model): id: None name: str = ... permissions: Any = ... - objects: Any = ... - class Meta: - verbose_name: Any = ... - verbose_name_plural: Any = ... def natural_key(self): ... class UserManager(BaseUserManager): @@ -77,7 +67,6 @@ class AbstractUser(AbstractBaseUser, PermissionsMixin): email: str = ... is_staff: bool = ... date_joined: datetime.datetime = ... - objects: Any = ... EMAIL_FIELD: str = ... USERNAME_FIELD: str = ... def clean(self) -> None: ... @@ -85,9 +74,7 @@ class AbstractUser(AbstractBaseUser, PermissionsMixin): def get_short_name(self) -> str: ... def email_user(self, subject: str, message: str, from_email: str = ..., **kwargs: Any) -> None: ... -class User(AbstractUser): - class Meta(AbstractUser.Meta): - swappable: str = ... +class User(AbstractUser): ... class AnonymousUser: id: Any = ... diff --git a/django-stubs/db/models/base.pyi b/django-stubs/db/models/base.pyi index 626a7f0..d8b4787 100644 --- a/django-stubs/db/models/base.pyi +++ b/django-stubs/db/models/base.pyi @@ -1,12 +1,14 @@ from typing import Any, List, Optional, Set, Tuple, Dict -class ModelBase(type): - pass +from django.db.models.manager import Manager + +class ModelBase(type): ... class Model(metaclass=ModelBase): class DoesNotExist(Exception): pass pk: Any = ... + objects: Manager[Model] def __init__(self, **kwargs) -> None: ... def delete(self, using: Any = ..., keep_parents: bool = ...) -> Tuple[int, Dict[str, int]]: ... def full_clean(self, exclude: Optional[List[str]] = ..., validate_unique: bool = ...) -> None: ... diff --git a/django-stubs/db/models/manager.pyi b/django-stubs/db/models/manager.pyi index f612049..28bdaab 100644 --- a/django-stubs/db/models/manager.pyi +++ b/django-stubs/db/models/manager.pyi @@ -3,7 +3,7 @@ from typing import Any, Dict, List, Optional, Tuple, Type, TypeVar from django.db.models.base import Model from django.db.models.query import QuerySet -_T = TypeVar("_T", bound=Model) +_T = TypeVar("_T", bound=Model, covariant=True) class BaseManager(QuerySet[_T]): creation_counter: int = ... @@ -24,7 +24,7 @@ class BaseManager(QuerySet[_T]): class Manager(BaseManager[_T]): ... class RelatedManager(Manager[_T]): - def add(self, *objs: _T, bulk: bool = ...) -> None: ... + def add(self, *objs: Model, bulk: bool = ...) -> None: ... def clear(self) -> None: ... class ManagerDescriptor: diff --git a/django-stubs/db/models/query.pyi b/django-stubs/db/models/query.pyi index 5f06e00..6ee30aa 100644 --- a/django-stubs/db/models/query.pyi +++ b/django-stubs/db/models/query.pyi @@ -6,7 +6,7 @@ from django.db import models from django.db.models import Manager from django.db.models.sql.query import Query, RawQuery -_T = TypeVar("_T", bound=models.Model) +_T = TypeVar("_T", bound=models.Model, covariant=True) class QuerySet(Iterable[_T], Sized): def __init__( diff --git a/mypy_django_plugin/plugins/models.py b/mypy_django_plugin/plugins/models.py index 547d58e..86e4b0e 100644 --- a/mypy_django_plugin/plugins/models.py +++ b/mypy_django_plugin/plugins/models.py @@ -93,8 +93,13 @@ class InjectAnyAsBaseForNestedMeta(ModelClassInitializer): class AddDefaultObjectsManager(ModelClassInitializer): + def is_default_objects_attr(self, sym: SymbolTableNode) -> bool: + return sym.fullname == helpers.MODEL_CLASS_FULLNAME + '.' + 'objects' + def run(self) -> None: - if 'objects' in self.model_classdef.info.names: + existing_objects_sym = self.model_classdef.info.get('objects') + if (existing_objects_sym is not None + and not self.is_default_objects_attr(existing_objects_sym)): return None if self.is_abstract_model(): diff --git a/test-data/typecheck/models.test b/test-data/typecheck/models.test index 897b11a..c29c497 100644 --- a/test-data/typecheck/models.test +++ b/test-data/typecheck/models.test @@ -8,7 +8,7 @@ reveal_type(User.objects.get()) # E: Revealed type is 'main.User*' [CASE test_leave_as_is_if_objects_is_set_and_fill_typevars_with_outer_class] from django.db import models -class UserManager(models.Manager): +class UserManager(models.Manager[User]): def get_or_404(self) -> User: pass @@ -19,3 +19,31 @@ reveal_type(User.objects) # E: Revealed type is 'main.UserManager' reveal_type(User.objects.get()) # E: Revealed type is 'main.User*' reveal_type(User.objects.get_or_404()) # E: Revealed type is 'main.User' +[CASE test_model_objects_attribute_present_in_case_of_model_cls_passed_as_parameter] +from typing import Type +from django.db import models + +class Base: + def __init__(self, model_cls: Type[models.Model]): + self.model_cls = model_cls +class MyModel(models.Model): + pass +reveal_type(Base(MyModel).model_cls.objects) # E: Revealed type is 'django.db.models.manager.Manager[django.db.models.base.Model]' + +[CASE test_model_objects_attribute_present_in_case_of_model_cls_passed_as_generic_parameter] +from typing import TypeVar, Generic, Type +from django.db import models + +_T = TypeVar('_T', bound=models.Model) +class Base(Generic[_T]): + def __init__(self, model_cls: Type[_T]): + self.model_cls = model_cls + reveal_type(self.model_cls.objects) # E: Revealed type is 'django.db.models.manager.Manager[django.db.models.base.Model]' +class MyModel(models.Model): + pass +base_instance = Base(MyModel) +reveal_type(base_instance.model_cls.objects) # E: Revealed type is 'django.db.models.manager.Manager[main.MyModel]' + +class Child(Base[MyModel]): + def method(self) -> None: + reveal_type(self.model_cls.objects) # E: Revealed type is 'django.db.models.manager.Manager[main.MyModel]' \ No newline at end of file