From 248504c25a12ae22ad85fdd25f1d0bf441df51b6 Mon Sep 17 00:00:00 2001 From: Maxim Kurnikov Date: Sat, 20 Jul 2019 20:28:43 +0300 Subject: [PATCH] various fixes --- django-stubs/contrib/contenttypes/fields.pyi | 24 +-- django-stubs/contrib/sites/managers.pyi | 11 +- django-stubs/db/models/expressions.pyi | 3 +- django-stubs/db/models/fields/__init__.pyi | 30 +++- django-stubs/db/models/fields/related.pyi | 60 +++----- .../db/models/fields/reverse_related.pyi | 48 +----- django-stubs/db/models/lookups.pyi | 140 ++++-------------- django-stubs/db/models/manager.pyi | 1 - django-stubs/db/models/options.pyi | 12 +- django-stubs/db/models/query.pyi | 12 +- django-stubs/db/models/query_utils.pyi | 4 +- django-stubs/db/models/sql/query.pyi | 2 +- django-stubs/db/models/sql/where.pyi | 2 +- mypy_django_plugin/main.py | 6 + mypy_django_plugin/transformers/fields.py | 14 +- mypy_django_plugin/transformers/models.py | 18 +-- mypy_django_plugin/transformers/querysets.py | 23 ++- test-data/typecheck/fields/test_base.yml | 33 ++--- .../managers/querysets/test_basic_methods.yml | 2 +- .../typecheck/managers/test_managers.yml | 45 ++++-- .../typecheck/models/test_meta_options.yml | 11 +- test-data/typecheck/test_config.yml | 2 +- 22 files changed, 204 insertions(+), 299 deletions(-) diff --git a/django-stubs/contrib/contenttypes/fields.pyi b/django-stubs/contrib/contenttypes/fields.pyi index de43919..0eba583 100644 --- a/django-stubs/contrib/contenttypes/fields.pyi +++ b/django-stubs/contrib/contenttypes/fields.pyi @@ -54,29 +54,16 @@ class GenericForeignKey(FieldCacheMixin): class GenericRel(ForeignObjectRel): field: GenericRelation - limit_choices_to: Optional[Union[Dict[str, Any], Callable[[], Any]]] - model: Type[Model] - multiple: bool - on_delete: Callable - parent_link: bool - related_name: str - related_query_name: None - symmetrical: bool def __init__( self, field: GenericRelation, to: Union[Type[Model], str], - related_name: None = ..., + related_name: Optional[str] = ..., related_query_name: Optional[str] = ..., limit_choices_to: Optional[Union[Dict[str, Any], Callable[[], Any]]] = ..., ) -> None: ... class GenericRelation(ForeignObject): - auto_created: bool = ... - many_to_many: bool = ... - many_to_one: bool = ... - one_to_many: bool = ... - one_to_one: bool = ... rel_class: Any = ... mti_inherited: bool = ... object_id_field_name: Any = ... @@ -93,23 +80,16 @@ class GenericRelation(ForeignObject): limit_choices_to: Optional[Union[Dict[str, Any], Callable[[], Any]]] = ..., **kwargs: Any ) -> None: ... - def check(self, **kwargs: Any) -> List[Error]: ... def resolve_related_fields(self) -> List[Tuple[PositiveIntegerField, Field]]: ... def get_path_info(self, filtered_relation: Optional[FilteredRelation] = ...) -> List[PathInfo]: ... def get_reverse_path_info(self, filtered_relation: None = ...) -> List[PathInfo]: ... def value_to_string(self, obj: Model) -> str: ... - model: Any = ... - def set_attributes_from_rel(self) -> None: ... - def get_internal_type(self) -> str: ... def get_content_type(self) -> ContentType: ... def get_extra_restriction( self, where_class: Type[WhereNode], alias: Optional[str], remote_alias: str ) -> WhereNode: ... def bulk_related_objects(self, objs: List[Model], using: str = ...) -> QuerySet: ... -class ReverseGenericManyToOneDescriptor(ReverseManyToOneDescriptor): - field: GenericRelation - rel: GenericRel - def related_manager_cls(self): ... +class ReverseGenericManyToOneDescriptor(ReverseManyToOneDescriptor): ... def create_generic_related_manager(superclass: Any, rel: Any): ... diff --git a/django-stubs/contrib/sites/managers.pyi b/django-stubs/contrib/sites/managers.pyi index 8dc2fed..45ae26d 100644 --- a/django-stubs/contrib/sites/managers.pyi +++ b/django-stubs/contrib/sites/managers.pyi @@ -1,15 +1,6 @@ -from typing import Any, List, Optional - -from django.core.checks.messages import Error -from django.db.models.query import QuerySet +from typing import Optional from django.db import models class CurrentSiteManager(models.Manager): - creation_counter: int - model: None - name: None - use_in_migrations: bool = ... def __init__(self, field_name: Optional[str] = ...) -> None: ... - def check(self, **kwargs: Any) -> List[Error]: ... - def get_queryset(self) -> QuerySet: ... diff --git a/django-stubs/db/models/expressions.pyi b/django-stubs/db/models/expressions.pyi index def0086..6579b88 100644 --- a/django-stubs/db/models/expressions.pyi +++ b/django-stubs/db/models/expressions.pyi @@ -52,7 +52,6 @@ class BaseExpression: is_summary: bool = ... filterable: bool = ... window_compatible: bool = ... - output_field: Field def __init__(self, output_field: Optional[_OutputField] = ...) -> None: ... def get_db_converters(self, connection: Any) -> List[Callable]: ... def get_source_expressions(self) -> List[Any]: ... @@ -74,6 +73,8 @@ class BaseExpression: @property def field(self) -> Field: ... @property + def output_field(self) -> Field: ... + @property def convert_value(self) -> Callable: ... def get_lookup(self, lookup: str) -> Optional[Type[Lookup]]: ... def get_transform(self, name: str) -> Optional[Type[Expression]]: ... diff --git a/django-stubs/db/models/fields/__init__.pyi b/django-stubs/db/models/fields/__init__.pyi index ae2f7e9..985b4bf 100644 --- a/django-stubs/db/models/fields/__init__.pyi +++ b/django-stubs/db/models/fields/__init__.pyi @@ -1,13 +1,27 @@ import decimal import uuid from datetime import date, datetime, time, timedelta -from typing import Any, Callable, Dict, Generic, Iterable, Optional, Tuple, Type, TypeVar, Union, Sequence, List +from typing import ( + Any, + Callable, + Dict, + Generic, + Iterable, + Optional, + Tuple, + Type, + TypeVar, + Union, + Sequence, + List, + overload, +) from django.core import checks from django.db.models import Model from django.core.exceptions import FieldDoesNotExist as FieldDoesNotExist -from django.db.models.expressions import Combinable +from django.db.models.expressions import Combinable, Col from django.db.models.query_utils import RegisterLookupMixin from django.forms import Field as FormField, Widget @@ -20,6 +34,7 @@ _FieldChoices = Iterable[Union[_Choice, _ChoiceNamedGroup]] _ValidatorCallable = Callable[..., None] _ErrorMessagesToOverride = Dict[str, Any] +_T = TypeVar("_T", bound="Field") # __set__ value type _ST = TypeVar("_ST") # __get__ return type @@ -36,7 +51,7 @@ class Field(RegisterLookupMixin, Generic[_ST, _GT]): auto_created: bool primary_key: bool remote_field: Field - max_length: Optional[int] + max_length: int model: Type[Model] name: str verbose_name: str @@ -72,6 +87,11 @@ class Field(RegisterLookupMixin, Generic[_ST, _GT]): error_messages: Optional[_ErrorMessagesToOverride] = ..., ): ... def __set__(self, instance, value: _ST) -> None: ... + # class access + @overload + def __get__(self: _T, instance: None, owner) -> _T: ... + # instance access + @overload def __get__(self, instance, owner) -> _GT: ... def deconstruct(self) -> Any: ... def set_attributes_from_name(self, name: str) -> None: ... @@ -96,6 +116,10 @@ class Field(RegisterLookupMixin, Generic[_ST, _GT]): def check(self, **kwargs: Any) -> List[checks.Error]: ... @property def validators(self) -> List[_ValidatorCallable]: ... + def get_col(self, alias: str, output_field: Optional[Field] = ...) -> Col: ... + @property + def cached_col(self) -> Col: ... + def value_from_object(self, obj: Model) -> _GT: ... class IntegerField(Field[_ST, _GT]): _pyi_private_set_type: Union[float, int, str, Combinable] diff --git a/django-stubs/db/models/fields/related.pyi b/django-stubs/db/models/fields/related.pyi index 63abe1e..a7cd76c 100644 --- a/django-stubs/db/models/fields/related.pyi +++ b/django-stubs/db/models/fields/related.pyi @@ -1,39 +1,25 @@ -from typing import ( - Type, - Union, - TypeVar, - Any, - Generic, - List, - Optional, - Dict, - Callable, - Tuple, - Sequence, - TYPE_CHECKING, - Iterable, -) +from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, TYPE_CHECKING, Tuple, Type, TypeVar, Union from uuid import UUID -from django.db import models -from django.db.models import Field, Model, QuerySet +from django.db.models.expressions import Combinable from django.db.models.fields.mixins import FieldCacheMixin +from django.db.models.query_utils import PathInfo, Q + +from django.db import models +from django.db.models import Field, Model from django.db.models.fields.related_descriptors import ( - ReverseManyToOneDescriptor as ReverseManyToOneDescriptor, - ReverseOneToOneDescriptor as ReverseOneToOneDescriptor, - ForwardManyToOneDescriptor as ForwardManyToOneDescriptor, ForwardOneToOneDescriptor as ForwardOneToOneDescriptor, + ForwardManyToOneDescriptor as ForwardManyToOneDescriptor, ManyToManyDescriptor as ManyToManyDescriptor, + ReverseOneToOneDescriptor as ReverseOneToOneDescriptor, + ReverseManyToOneDescriptor as ReverseManyToOneDescriptor, ) from django.db.models.fields.reverse_related import ( ForeignObjectRel as ForeignObjectRel, - ManyToManyRel as ManyToManyRel, - ManyToOneRel as ManyToOneRel, OneToOneRel as OneToOneRel, + ManyToOneRel as ManyToOneRel, + ManyToManyRel as ManyToManyRel, ) -from django.db.models.query_utils import PathInfo, Q - -from django.db.models.expressions import Combinable if TYPE_CHECKING: from django.db.models.manager import RelatedManager @@ -61,14 +47,11 @@ class RelatedField(FieldCacheMixin, Field[_ST, _GT]): many_to_one: bool = ... @property def related_model(self) -> Union[Type[Model], str]: ... - def check(self, **kwargs: Any) -> List[Any]: ... opts: Any = ... def get_forward_related_filter(self, obj: Model) -> Dict[str, Union[int, UUID]]: ... def get_reverse_related_filter(self, obj: Model) -> Q: ... @property def swappable_setting(self) -> Optional[str]: ... - name: Any = ... - verbose_name: Any = ... def set_attributes_from_rel(self) -> None: ... def do_related_class(self, other: Type[Model], cls: Type[Model]) -> None: ... def get_limit_choices_to(self) -> Dict[str, int]: ... @@ -188,7 +171,14 @@ class ManyToManyField(RelatedField[_ST, _GT]): rel_class: Any = ... description: Any = ... has_null_arg: Any = ... - swappable: Any = ... + swappable: bool = ... + m2m_db_table: str = ... + m2m_column_name: str = ... + m2m_reverse_name: str = ... + m2m_field_name: str = ... + m2m_reverse_field_name: str = ... + m2m_target_field_name: str = ... + m2m_reverse_target_field_name: str = ... def __init__( self, to: Union[Type[_T], str], @@ -223,19 +213,9 @@ class ManyToManyField(RelatedField[_ST, _GT]): validators: Iterable[_ValidatorCallable] = ..., error_messages: Optional[_ErrorMessagesToOverride] = ..., ) -> None: ... - def check(self, **kwargs: Any) -> List[Any]: ... + def __get__(self, instance, owner) -> _GT: ... # type: ignore def get_path_info(self, filtered_relation: None = ...) -> List[PathInfo]: ... def get_reverse_path_info(self, filtered_relation: None = ...) -> List[PathInfo]: ... - m2m_db_table: Any = ... - m2m_column_name: Any = ... - m2m_reverse_name: Any = ... - m2m_field_name: Any = ... - m2m_reverse_field_name: Any = ... - m2m_target_field_name: Any = ... - m2m_reverse_target_field_name: Any = ... def contribute_to_related_class(self, cls: Type[Model], related: RelatedField) -> None: ... - def set_attributes_from_rel(self) -> None: ... - def value_from_object(self, obj: Model) -> List[Model]: ... - def save_form_data(self, instance: Model, data: QuerySet) -> None: ... def create_many_to_many_intermediary_model(field: Type[Field], klass: Type[Model]) -> Type[Model]: ... diff --git a/django-stubs/db/models/fields/reverse_related.pyi b/django-stubs/db/models/fields/reverse_related.pyi index c156bb2..c220bee 100644 --- a/django-stubs/db/models/fields/reverse_related.pyi +++ b/django-stubs/db/models/fields/reverse_related.pyi @@ -10,13 +10,10 @@ from django.db.models.sql.where import WhereNode from .mixins import FieldCacheMixin class ForeignObjectRel(FieldCacheMixin): - hidden: bool many_to_many: bool many_to_one: bool - name: str one_to_many: bool one_to_one: bool - related_model: Type[Model] auto_created: bool = ... concrete: bool = ... editable: bool = ... @@ -43,6 +40,12 @@ class ForeignObjectRel(FieldCacheMixin): on_delete: Optional[Callable] = ..., ) -> None: ... @property + def hidden(self) -> bool: ... + @property + def name(self) -> str: ... + @property + def related_model(self) -> Type[Model]: ... + @property def remote_field(self) -> RelatedField: ... @property def target_field(self) -> AutoField: ... @@ -63,22 +66,6 @@ class ForeignObjectRel(FieldCacheMixin): def get_path_info(self, filtered_relation: Optional[FilteredRelation] = ...) -> List[PathInfo]: ... class ManyToOneRel(ForeignObjectRel): - field: RelatedField - hidden: bool - limit_choices_to: Any - many_to_many: bool - many_to_one: bool - model: Union[Type[Model], str] - multiple: bool - name: str - on_delete: Callable - one_to_many: bool - one_to_one: bool - parent_link: bool - related_model: Type[Model] - related_name: Optional[str] - related_query_name: Optional[str] - symmetrical: bool def __init__( self, field: ForeignKey, @@ -91,25 +78,8 @@ class ManyToOneRel(ForeignObjectRel): on_delete: Callable = ..., ) -> None: ... def get_related_field(self) -> Field: ... - def set_field_name(self) -> None: ... class OneToOneRel(ManyToOneRel): - field_name: Optional[str] - hidden: bool - limit_choices_to: Dict[str, str] - many_to_many: bool - many_to_one: bool - model: Union[Type[Model], str] - name: str - on_delete: Callable - one_to_many: bool - one_to_one: bool - parent_link: bool - related_model: Type[Model] - related_name: Optional[str] - related_query_name: Optional[str] - symmetrical: bool - multiple: bool = ... def __init__( self, field: OneToOneField, @@ -123,14 +93,8 @@ class OneToOneRel(ManyToOneRel): ) -> None: ... class ManyToManyRel(ForeignObjectRel): - field_name: None - multiple: bool - name: str - parent_link: bool - related_model: Type[Model] through: Optional[Union[Type[Model], str]] = ... through_fields: Optional[Tuple[str, str]] = ... - symmetrical: bool = ... db_constraint: bool = ... def __init__( self, diff --git a/django-stubs/db/models/lookups.pyi b/django-stubs/db/models/lookups.pyi index 5d0a6a3..2a7529a 100644 --- a/django-stubs/db/models/lookups.pyi +++ b/django-stubs/db/models/lookups.pyi @@ -1,6 +1,6 @@ from collections import OrderedDict from datetime import datetime -from typing import Any, Dict, List, Optional, Tuple, Type, Union, Iterable +from typing import Any, Dict, Iterable, List, Optional, Tuple, Type, Union, Mapping from django.db.backends.sqlite3.base import DatabaseWrapper from django.db.models.expressions import Combinable, Expression, Func @@ -10,22 +10,21 @@ from django.db.models.sql.query import Query from django.utils.datastructures import OrderedSet from django.utils.safestring import SafeText -from django.db.models import lookups -from django.db.models.fields import TextField, related_lookups, Field +from django.db.models.fields import TextField, related_lookups class Lookup: - lookup_name: Any = ... + lookup_name: str = ... prepare_rhs: bool = ... can_use_none_as_rhs: bool = ... + lhs: Any = ... rhs: Any = ... - bilateral_transforms: Any = ... + bilateral_transforms: List[Type[Transform]] = ... def __init__(self, lhs: Union[Expression, TextField, related_lookups.MultiColSource], rhs: Any) -> None: ... def apply_bilateral_transforms(self, value: Expression) -> Transform: ... def batch_process_rhs( self, compiler: SQLCompiler, connection: DatabaseWrapper, rhs: Optional[OrderedSet] = ... ) -> Tuple[List[str], List[str]]: ... def get_source_expressions(self) -> List[Expression]: ... - lhs: Any = ... def set_source_expressions(self, new_exprs: List[Expression]) -> None: ... def get_prep_lookup(self) -> Any: ... def get_db_prep_lookup(self, value: Union[int, str], connection: DatabaseWrapper) -> Tuple[str, List[SafeText]]: ... @@ -36,20 +35,16 @@ class Lookup: self, compiler: SQLCompiler, connection: DatabaseWrapper ) -> Tuple[str, Union[List[Union[int, str]], Tuple[int, int]]]: ... def rhs_is_direct_value(self) -> bool: ... - def relabeled_clone( - self, relabels: Union[Dict[Optional[str], str], OrderedDict] - ) -> Union[BuiltinLookup, FieldGetDbPrepValueMixin]: ... + def relabeled_clone(self, relabels: Mapping[str, str]) -> Union[BuiltinLookup, FieldGetDbPrepValueMixin]: ... def get_group_by_cols(self) -> List[Expression]: ... - def as_sql(self, compiler: Any, connection: Any) -> None: ... + def as_sql(self, compiler: Any, connection: Any) -> Any: ... def contains_aggregate(self) -> bool: ... def contains_over_clause(self) -> bool: ... @property - def is_summary(self): ... + def is_summary(self) -> bool: ... class Transform(RegisterLookupMixin, Func): bilateral: bool = ... - arity: int = ... - output_field: Field @property def lhs(self) -> Expression: ... def get_bilateral_transforms(self) -> List[Type[Transform]]: ... @@ -61,122 +56,51 @@ class FieldGetDbPrepValueMixin: get_db_prep_lookup_value_is_iterable: bool = ... class FieldGetDbPrepValueIterableMixin(FieldGetDbPrepValueMixin): - get_db_prep_lookup_value_is_iterable: bool = ... def get_prep_lookup(self) -> Iterable[Any]: ... def resolve_expression_parameter( - self, compiler: SQLCompiler, connection: DatabaseWrapper, sql: str, param: Optional[Union[Combinable, int, str]] - ) -> Tuple[str, List[None]]: ... + self, compiler: SQLCompiler, connection: DatabaseWrapper, sql: str, param: Any + ) -> Any: ... -class Exact(FieldGetDbPrepValueMixin, BuiltinLookup): - bilateral_transforms: List[Type[lookups.Transform]] - lookup_name: str = ... - -class IExact(BuiltinLookup): - bilateral_transforms: List[Any] - lookup_name: str = ... - -class GreaterThan(FieldGetDbPrepValueMixin, BuiltinLookup): - bilateral_transforms: List[Any] - lookup_name: str = ... - -class GreaterThanOrEqual(FieldGetDbPrepValueMixin, BuiltinLookup): - bilateral_transforms: List[Any] - lookup_name: str = ... - -class LessThan(FieldGetDbPrepValueMixin, BuiltinLookup): - bilateral_transforms: List[Any] - lookup_name: str = ... - -class LessThanOrEqual(FieldGetDbPrepValueMixin, BuiltinLookup): - bilateral_transforms: List[Type[lookups.Transform]] - lookup_name: str = ... +class Exact(FieldGetDbPrepValueMixin, BuiltinLookup): ... +class IExact(BuiltinLookup): ... +class GreaterThan(FieldGetDbPrepValueMixin, BuiltinLookup): ... +class GreaterThanOrEqual(FieldGetDbPrepValueMixin, BuiltinLookup): ... +class LessThan(FieldGetDbPrepValueMixin, BuiltinLookup): ... +class LessThanOrEqual(FieldGetDbPrepValueMixin, BuiltinLookup): ... class IntegerFieldFloatRounding: rhs: Any = ... - def get_prep_lookup(self) -> Union[Combinable, Query, int]: ... + def get_prep_lookup(self) -> Any: ... class IntegerGreaterThanOrEqual(IntegerFieldFloatRounding, GreaterThanOrEqual): ... class IntegerLessThan(IntegerFieldFloatRounding, LessThan): ... class In(FieldGetDbPrepValueIterableMixin, BuiltinLookup): - bilateral_transforms: List[Type[lookups.Transform]] - lookup_name: str = ... - def get_rhs_op(self, connection: DatabaseWrapper, rhs: str) -> str: ... def split_parameter_list_as_sql(self, compiler: Any, connection: Any): ... class PatternLookup(BuiltinLookup): param_pattern: str = ... - prepare_rhs: bool = ... - def get_rhs_op(self, connection: DatabaseWrapper, rhs: str) -> str: ... -class Contains(PatternLookup): - bilateral_transforms: List[Type[lookups.Transform]] - lookup_name: str = ... - -class IContains(Contains): - bilateral_transforms: List[Any] - lookup_name: str = ... - -class StartsWith(PatternLookup): - bilateral_transforms: List[Any] - lookup_name: str = ... - param_pattern: str = ... - -class IStartsWith(StartsWith): - bilateral_transforms: List[Any] - lookup_name: str = ... - -class EndsWith(PatternLookup): - bilateral_transforms: List[Any] - lookup_name: str = ... - param_pattern: str = ... - -class IEndsWith(EndsWith): - bilateral_transforms: List[Any] - lookup_name: str = ... - -class Range(FieldGetDbPrepValueIterableMixin, BuiltinLookup): - bilateral_transforms: List[Type[lookups.Transform]] - lookup_name: str = ... - -class IsNull(BuiltinLookup): - bilateral_transforms: List[Any] - lookup_name: str = ... - prepare_rhs: bool = ... - -class Regex(BuiltinLookup): - bilateral_transforms: List[Any] - lookup_name: str = ... - prepare_rhs: bool = ... - -class IRegex(Regex): - bilateral_transforms: List[Any] - lookup_name: str = ... +class Contains(PatternLookup): ... +class IContains(Contains): ... +class StartsWith(PatternLookup): ... +class IStartsWith(StartsWith): ... +class EndsWith(PatternLookup): ... +class IEndsWith(EndsWith): ... +class Range(FieldGetDbPrepValueIterableMixin, BuiltinLookup): ... +class IsNull(BuiltinLookup): ... +class Regex(BuiltinLookup): ... +class IRegex(Regex): ... class YearLookup(Lookup): def year_lookup_bounds(self, connection: DatabaseWrapper, year: int) -> List[str]: ... class YearComparisonLookup(YearLookup): - bilateral_transforms: List[Any] def get_rhs_op(self, connection: DatabaseWrapper, rhs: str) -> str: ... def get_bound(self, start: datetime, finish: datetime) -> Any: ... -class YearExact(YearLookup, Exact): - bilateral_transforms: List[Any] - lookup_name: str = ... - -class YearGt(YearComparisonLookup): - bilateral_transforms: List[Any] - lookup_name: str = ... - -class YearGte(YearComparisonLookup): - bilateral_transforms: List[Any] - lookup_name: str = ... - -class YearLt(YearComparisonLookup): - bilateral_transforms: List[Any] - lookup_name: str = ... - -class YearLte(YearComparisonLookup): - bilateral_transforms: List[Any] - lookup_name: str = ... +class YearExact(YearLookup, Exact): ... +class YearGt(YearComparisonLookup): ... +class YearGte(YearComparisonLookup): ... +class YearLt(YearComparisonLookup): ... +class YearLte(YearComparisonLookup): ... diff --git a/django-stubs/db/models/manager.pyi b/django-stubs/db/models/manager.pyi index 933ee57..cffede8 100644 --- a/django-stubs/db/models/manager.pyi +++ b/django-stubs/db/models/manager.pyi @@ -11,7 +11,6 @@ class BaseManager(QuerySet[_T, _T]): use_in_migrations: bool = ... model: Optional[Any] = ... name: Optional[Any] = ... - def __new__(cls: Type[BaseManager], *args: Any, **kwargs: Any) -> BaseManager: ... def __init__(self) -> None: ... def deconstruct(self) -> Tuple[bool, str, None, Tuple, Dict[str, int]]: ... def check(self, **kwargs: Any) -> List[Any]: ... diff --git a/django-stubs/db/models/options.pyi b/django-stubs/db/models/options.pyi index 9fc21b0..8ab108b 100644 --- a/django-stubs/db/models/options.pyi +++ b/django-stubs/db/models/options.pyi @@ -29,7 +29,7 @@ def make_immutable_fields_list( name: str, data: Union[Iterator[Any], List[Union[ArrayField, CIText]], List[Union[Field, FieldCacheMixin]]] ) -> ImmutableList: ... -_M = TypeVar('_M', bound=Model) +_M = TypeVar("_M", bound=Model) class Options(Generic[_M]): base_manager: Manager @@ -81,6 +81,8 @@ class Options(Generic[_M]): related_fkey_lookups: List[Any] = ... apps: Apps = ... default_related_name: None = ... + model: Type[Model] = ... + original_attrs: Dict[str, Any] = ... def __init__(self, meta: Optional[type], app_label: Optional[str] = ...) -> None: ... @property def label(self) -> str: ... @@ -90,8 +92,6 @@ class Options(Generic[_M]): def app_config(self) -> AppConfig: ... @property def installed(self): ... - model: Type[Model] = ... - original_attrs: Dict[str, Union[List[str], Apps, str]] = ... def contribute_to_class(self, cls: Type[Model], name: str) -> None: ... def add_manager(self, manager: Manager) -> None: ... def add_field(self, field: Union[GenericForeignKey, Field], private: bool = ...) -> None: ... @@ -102,8 +102,10 @@ class Options(Generic[_M]): def verbose_name_raw(self) -> Any: ... @property def swapped(self) -> Optional[str]: ... - def many_to_many(self) -> ImmutableList: ... - def fields_map(self) -> Dict[str, ForeignObjectRel]: ... + @property + def many_to_many(self) -> List[ManyToManyField]: ... + @property + def fields_map(self) -> Dict[str, Union[Field, ForeignObjectRel]]: ... def get_field(self, field_name: Union[Callable, str]) -> Field: ... def get_base_chain(self, model: Type[Model]) -> List[Type[Model]]: ... def get_parent_list(self) -> List[Type[Model]]: ... diff --git a/django-stubs/db/models/query.pyi b/django-stubs/db/models/query.pyi index eedfbe8..5387684 100644 --- a/django-stubs/db/models/query.pyi +++ b/django-stubs/db/models/query.pyi @@ -88,16 +88,20 @@ class QuerySet(Generic[_T, _Row], Collection[_Row], Sized): ) -> Tuple[_T, bool]: ... def earliest(self, *fields: Any, field_name: Optional[Any] = ...) -> _Row: ... def latest(self, *fields: Any, field_name: Optional[Any] = ...) -> _Row: ... - def first(self) -> Optional[_Row]: ... - def last(self) -> Optional[_Row]: ... + # technically it's Optional[_Row], but it creates a lot of false-positives (same for last()) + def first(self) -> _Row: ... + def last(self) -> _Row: ... def in_bulk(self, id_list: Iterable[Any] = ..., *, field_name: str = ...) -> Dict[Any, _T]: ... def delete(self) -> Tuple[int, Dict[str, int]]: ... def update(self, **kwargs: Any) -> int: ... - def _update(self, values: Any) -> Optional[Any]: ... def exists(self) -> bool: ... def explain(self, *, format: Optional[Any] = ..., **options: Any) -> str: ... def raw( - self, raw_query: str, params: Any = ..., translations: Optional[Dict[str, str]] = ..., using: None = ... + self, + raw_query: str, + params: Any = ..., + translations: Optional[Dict[str, str]] = ..., + using: Optional[str] = ..., ) -> RawQuerySet: ... # The type of values may be overridden to be more specific in the mypy plugin, depending on the fields param def values(self, *fields: Union[str, Combinable], **expressions: Any) -> QuerySet[_T, Dict[str, Any]]: ... diff --git a/django-stubs/db/models/query_utils.pyi b/django-stubs/db/models/query_utils.pyi index afc30c8..141083c 100644 --- a/django-stubs/db/models/query_utils.pyi +++ b/django-stubs/db/models/query_utils.pyi @@ -20,7 +20,7 @@ class QueryWrapper: contains_aggregate: bool = ... data: Tuple[str, List[Any]] = ... def __init__(self, sql: str, params: List[Any]) -> None: ... - def as_sql(self, compiler: SQLCompiler = ..., connection: Any = ...) -> Tuple[str, List[Any]]: ... + def as_sql(self, compiler: SQLCompiler = ..., connection: Any = ...) -> Any: ... class Q(tree.Node): children: Union[List[Dict[str, str]], List[Tuple[str, Any]], List[Q]] @@ -84,4 +84,4 @@ class FilteredRelation: def __init__(self, relation_name: str, *, condition: Any = ...) -> None: ... def clone(self) -> FilteredRelation: ... def resolve_expression(self, *args: Any, **kwargs: Any) -> None: ... - def as_sql(self, compiler: SQLCompiler, connection: Any) -> Tuple[str, List[Union[int, str]]]: ... + def as_sql(self, compiler: SQLCompiler, connection: Any) -> Any: ... diff --git a/django-stubs/db/models/sql/query.pyi b/django-stubs/db/models/sql/query.pyi index a78e150..26938c9 100644 --- a/django-stubs/db/models/sql/query.pyi +++ b/django-stubs/db/models/sql/query.pyi @@ -98,7 +98,7 @@ class Query: def get_initial_alias(self) -> str: ... def count_active_tables(self) -> int: ... def resolve_expression(self, query: Query, *args: Any, **kwargs: Any) -> Query: ... - def as_sql(self, compiler: SQLCompiler, connection: Any) -> Tuple[str, Tuple]: ... + def as_sql(self, compiler: SQLCompiler, connection: Any) -> Any: ... def resolve_lookup_value(self, value: Any, can_reuse: Optional[Set[str]], allow_joins: bool) -> Any: ... def solve_lookup_type(self, lookup: str) -> Tuple[Sequence[str], Sequence[str], bool]: ... def build_filter( diff --git a/django-stubs/db/models/sql/where.pyi b/django-stubs/db/models/sql/where.pyi index 417956b..dfab304 100644 --- a/django-stubs/db/models/sql/where.pyi +++ b/django-stubs/db/models/sql/where.pyi @@ -18,7 +18,7 @@ class WhereNode(tree.Node): resolved: bool = ... conditional: bool = ... def split_having(self, negated: bool = ...) -> Tuple[Optional[WhereNode], Optional[WhereNode]]: ... - def as_sql(self, compiler: SQLCompiler, connection: Any) -> Tuple[str, List[Union[int, str]]]: ... + def as_sql(self, compiler: SQLCompiler, connection: Any) -> Any: ... def get_group_by_cols(self) -> List[Expression]: ... def relabel_aliases(self, change_map: Union[Dict[Optional[str], str], OrderedDict]) -> None: ... def clone(self) -> WhereNode: ... diff --git a/mypy_django_plugin/main.py b/mypy_django_plugin/main.py index 7213555..9a20873 100644 --- a/mypy_django_plugin/main.py +++ b/mypy_django_plugin/main.py @@ -204,6 +204,12 @@ class NewSemanalDjangoPlugin(Plugin): return partial(settings.get_type_of_settings_attribute, django_context=self.django_context) + # def get_type_analyze_hook(self, fullname: str + # ( ): + # info = self._get_typeinfo_or_none(fullname) + # if info and info.has_base(fullnames.QUERYSET_CLASS_FULLNAME): + # return partial(querysets.set_first_generic_param_as_default_for_second, fullname=fullname) + def plugin(version): return NewSemanalDjangoPlugin diff --git a/mypy_django_plugin/transformers/fields.py b/mypy_django_plugin/transformers/fields.py index d294043..016ef1b 100644 --- a/mypy_django_plugin/transformers/fields.py +++ b/mypy_django_plugin/transformers/fields.py @@ -1,6 +1,6 @@ from typing import Optional, Tuple, cast -from mypy.nodes import MypyFile, TypeInfo +from mypy.nodes import MypyFile, TypeInfo, Var, AssignmentStmt, SymbolTableNode, MDEF from mypy.plugin import FunctionContext from mypy.types import AnyType, CallableType, Instance, Type as MypyType, TypeOfAny @@ -15,9 +15,9 @@ def get_referred_to_model_fullname(ctx: FunctionContext, django_context: DjangoC return to_arg_type.ret_type.type.fullname() outer_model_info = ctx.api.scope.active_class() - if not outer_model_info or not outer_model_info.has_base(fullnames.MODEL_CLASS_FULLNAME): - # not inside models.Model class - return None + # if not outer_model_info or not outer_model_info.has_base(fullnames.MODEL_CLASS_FULLNAME): + # # not inside models.Model class + # return None assert isinstance(outer_model_info, TypeInfo) to_arg_expr = helpers.get_call_argument_by_name(ctx, 'to') @@ -101,6 +101,12 @@ def transform_into_proper_return_type(ctx: FunctionContext, django_context: Djan default_return_type = ctx.default_return_type assert isinstance(default_return_type, Instance) + outer_model_info = ctx.api.scope.active_class() + if not outer_model_info or not outer_model_info.has_base(fullnames.MODEL_CLASS_FULLNAME): + # not inside models.Model class + return ctx.default_return_type + assert isinstance(outer_model_info, TypeInfo) + if helpers.has_any_of_bases(default_return_type.type, fullnames.RELATED_FIELDS_CLASSES): return fill_descriptor_types_for_related_field(ctx, django_context) diff --git a/mypy_django_plugin/transformers/models.py b/mypy_django_plugin/transformers/models.py index 7cc4418..42b8e5c 100644 --- a/mypy_django_plugin/transformers/models.py +++ b/mypy_django_plugin/transformers/models.py @@ -1,6 +1,5 @@ from abc import ABCMeta, abstractmethod -from abc import ABCMeta, abstractmethod -from typing import cast, Type +from typing import Type, cast from django.db.models.base import Model from django.db.models.fields.related import ForeignKey @@ -18,7 +17,7 @@ from mypy_django_plugin.transformers import fields from mypy_django_plugin.transformers.fields import get_field_descriptor_types -class ModelClassInitializer(metaclass=ABCMeta): +class ModelClassInitializer: def __init__(self, ctx: ClassDefContext, django_context: DjangoContext): self.api = cast(NewSemanticAnalyzer, ctx.api) self.model_classdef = ctx.cls @@ -52,7 +51,6 @@ class ModelClassInitializer(metaclass=ABCMeta): return self.run_with_model_cls(model_cls) - @abstractmethod def run_with_model_cls(self, model_cls): pass @@ -70,7 +68,7 @@ class InjectAnyAsBaseForNestedMeta(ModelClassInitializer): to get around incompatible Meta inner classes for different models. """ - def run_with_model_cls(self, model_cls: Type[Model])-> None: + def run(self) -> None: meta_node = helpers.get_nested_meta_node_for_current_class(self.model_classdef.info) if meta_node is None: return None @@ -78,7 +76,7 @@ class InjectAnyAsBaseForNestedMeta(ModelClassInitializer): class AddDefaultPrimaryKey(ModelClassInitializer): - def run_with_model_cls(self, model_cls: Type[Model])-> None: + def run_with_model_cls(self, model_cls: Type[Model]) -> None: auto_field = model_cls._meta.auto_field if auto_field and not self.model_classdef.info.has_readable_member(auto_field.attname): # autogenerated field @@ -91,7 +89,7 @@ class AddDefaultPrimaryKey(ModelClassInitializer): class AddRelatedModelsId(ModelClassInitializer): - def run_with_model_cls(self, model_cls: Type[Model])-> None: + def run_with_model_cls(self, model_cls: Type[Model]) -> None: for field in model_cls._meta.get_fields(): if isinstance(field, ForeignKey): rel_primary_key_field = self.django_context.get_primary_key_field(field.related_model) @@ -103,7 +101,7 @@ class AddRelatedModelsId(ModelClassInitializer): class AddManagers(ModelClassInitializer): - def run_with_model_cls(self, model_cls: Type[Model])-> None: + def run_with_model_cls(self, model_cls: Type[Model]) -> None: for manager_name, manager in model_cls._meta.managers_map.items(): if manager_name not in self.model_classdef.info.names: manager_fullname = helpers.get_class_fullname(manager.__class__) @@ -140,7 +138,7 @@ class AddManagers(ModelClassInitializer): class AddExtraFieldMethods(ModelClassInitializer): - def run_with_model_cls(self, model_cls: Type[Model])-> None: + def run_with_model_cls(self, model_cls: Type[Model]) -> None: # get_FOO_display for choices for field in self.django_context.get_model_fields(model_cls): if field.choices: @@ -172,7 +170,7 @@ class AddExtraFieldMethods(ModelClassInitializer): class AddMetaOptionsAttribute(ModelClassInitializer): - def run_with_model_cls(self, model_cls: Type[Model])-> None: + def run_with_model_cls(self, model_cls: Type[Model]) -> None: if '_meta' not in self.model_classdef.info.names: options_info = self.lookup_typeinfo_or_incomplete_defn_error(fullnames.OPTIONS_CLASS_FULLNAME) self.add_new_node_to_model_class('_meta', diff --git a/mypy_django_plugin/transformers/querysets.py b/mypy_django_plugin/transformers/querysets.py index 7188014..f7dda60 100644 --- a/mypy_django_plugin/transformers/querysets.py +++ b/mypy_django_plugin/transformers/querysets.py @@ -1,9 +1,10 @@ from collections import OrderedDict -from typing import Optional, Tuple, Type, Sequence, List, Union +from typing import Optional, Tuple, Type, Sequence, List, Union, cast from django.core.exceptions import FieldError from django.db.models.base import Model from django.db.models.fields.related import ForeignKey +from mypy.newsemanal.typeanal import TypeAnalyser from mypy.nodes import NameExpr, Expression from mypy.plugin import AnalyzeTypeContext, FunctionContext, MethodContext from mypy.types import AnyType, Instance, Type as MypyType, TypeOfAny @@ -13,24 +14,20 @@ from mypy_django_plugin.lib import fullnames, helpers def set_first_generic_param_as_default_for_second(ctx: AnalyzeTypeContext, fullname: str) -> MypyType: + info = helpers.lookup_fully_qualified_typeinfo(ctx.api.api, fullname) + if info is None: + if not ctx.api.api.final_iteration: + ctx.api.api.defer() + if not ctx.type.args: - try: - return ctx.api.named_type(fullname, [AnyType(TypeOfAny.explicit), - AnyType(TypeOfAny.explicit)]) - except KeyError: - # really should never happen - return AnyType(TypeOfAny.explicit) + return Instance(info, [AnyType(TypeOfAny.explicit), AnyType(TypeOfAny.explicit)]) args = ctx.type.args if len(args) == 1: args = [args[0], args[0]] analyzed_args = [ctx.api.analyze_type(arg) for arg in args] - ctx.api.analyze_type(ctx.type) - try: - return ctx.api.named_type(fullname, analyzed_args) - except KeyError: - return AnyType(TypeOfAny.explicit) + return Instance(info, analyzed_args) def determine_proper_manager_type(ctx: FunctionContext) -> MypyType: @@ -69,7 +66,7 @@ def get_values_list_row_type(ctx: MethodContext, django_context: DjangoContext, if flat: primary_key_field = django_context.get_primary_key_field(model_cls) _, column_type = get_lookup_field_get_type(ctx, django_context, model_cls, - primary_key_field.attname, 'values_list') + primary_key_field.attname, 'values_list') return column_type elif named: column_types = OrderedDict() diff --git a/test-data/typecheck/fields/test_base.yml b/test-data/typecheck/fields/test_base.yml index bb0d9ef..d3782d1 100644 --- a/test-data/typecheck/fields/test_base.yml +++ b/test-data/typecheck/fields/test_base.yml @@ -77,23 +77,6 @@ class User(models.Model): my_pk = models.IntegerField(primary_key=True) -- case: test_primary_key_on_optional_queryset_method - main: | - from myapp.models import User - reveal_type(User.objects.first().id) - out: | - main:2: note: Revealed type is 'Union[builtins.int*, Any]' - main:2: error: Item "None" of "Optional[User]" has no attribute "id" - installed_apps: - - myapp - files: - - path: myapp/__init__.py - - path: myapp/models.py - content: | - from django.db import models - class User(models.Model): - pass - - case: blank_and_null_char_field_allows_none main: | from myapp.models import MyModel @@ -126,4 +109,18 @@ content: | from django.db import models class MyModel(models.Model): - notnulltext=models.CharField(max_length=1, blank=True, null=False) \ No newline at end of file + notnulltext=models.CharField(max_length=1, blank=True, null=False) + +- case: if_field_called_on_class_return_field_itself + main: | + from myapp.models import MyUser + reveal_type(MyUser.name) # N: Revealed type is 'django.db.models.fields.CharField[Union[builtins.str, builtins.int, django.db.models.expressions.Combinable], builtins.str]' + installed_apps: + - myapp + files: + - path: myapp/__init__.py + - path: myapp/models.py + content: | + from django.db import models + class MyUser(models.Model): + name = models.CharField(max_length=100) \ No newline at end of file diff --git a/test-data/typecheck/managers/querysets/test_basic_methods.yml b/test-data/typecheck/managers/querysets/test_basic_methods.yml index 9132c3a..a6741f4 100644 --- a/test-data/typecheck/managers/querysets/test_basic_methods.yml +++ b/test-data/typecheck/managers/querysets/test_basic_methods.yml @@ -7,7 +7,7 @@ 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 'Union[myapp.models.Blog*, None]' + reveal_type(qs.first()) # N: Revealed type is 'myapp.models.Blog*' 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 'django.db.models.query.QuerySet[myapp.models.Blog*, myapp.models.Blog*]' diff --git a/test-data/typecheck/managers/test_managers.yml b/test-data/typecheck/managers/test_managers.yml index eb4914f..1ef2634 100644 --- a/test-data/typecheck/managers/test_managers.yml +++ b/test-data/typecheck/managers/test_managers.yml @@ -212,10 +212,10 @@ 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.first()) # N: Revealed type is 'Union[myapp.models.UnrelatedModel*, None]' + reveal_type(UnrelatedModel.objects.first()) # N: Revealed type is 'myapp.models.UnrelatedModel*' reveal_type(MyModel.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.MyModel]' - reveal_type(MyModel.objects.first()) # N: Revealed type is 'Union[myapp.models.MyModel*, None]' + reveal_type(MyModel.objects.first()) # N: Revealed type is 'myapp.models.MyModel*' installed_apps: - myapp files: @@ -233,10 +233,10 @@ 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.first()) # N: Revealed type is 'Union[myapp.models.UnrelatedModel2*, None]' + reveal_type(UnrelatedModel2.objects.first()) # N: Revealed type is 'myapp.models.UnrelatedModel2*' reveal_type(MyModel2.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.MyModel2]' - reveal_type(MyModel2.objects.first()) # N: Revealed type is 'Union[myapp.models.MyModel2*, None]' + reveal_type(MyModel2.objects.first()) # N: Revealed type is 'myapp.models.MyModel2*' installed_apps: - myapp files: @@ -254,10 +254,10 @@ 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.first()) # N: Revealed type is 'Union[myapp.models.ParentOfMyModel3*, None]' + reveal_type(ParentOfMyModel3.objects.first()) # N: Revealed type is 'myapp.models.ParentOfMyModel3*' reveal_type(MyModel3.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.MyModel3]' - reveal_type(MyModel3.objects.first()) # N: Revealed type is 'Union[myapp.models.MyModel3*, None]' + reveal_type(MyModel3.objects.first()) # N: Revealed type is 'myapp.models.MyModel3*' installed_apps: - myapp files: @@ -275,10 +275,10 @@ 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.first()) # N: Revealed type is 'Union[myapp.models.ParentOfMyModel4*, None]' + reveal_type(ParentOfMyModel4.objects.first()) # N: Revealed type is 'myapp.models.ParentOfMyModel4*' reveal_type(MyModel4.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.MyModel4]' - reveal_type(MyModel4.objects.first()) # N: Revealed type is 'Union[myapp.models.MyModel4*, None]' + reveal_type(MyModel4.objects.first()) # N: Revealed type is 'myapp.models.MyModel4*' installed_apps: - myapp files: @@ -290,4 +290,31 @@ objects = models.Manager() class MyModel4(ParentOfMyModel4): - objects = models.Manager['MyModel4']() \ No newline at end of file + objects = models.Manager['MyModel4']() + +# TODO: make it work someday +#- case: inheritance_of_two_models_with_custom_objects_manager +# main: | +# from myapp.models import MyBaseUser, MyUser +# reveal_type(MyBaseUser.objects) # N: Revealed type is 'myapp.models.MyBaseManager[myapp.models.MyBaseUser]' +# reveal_type(MyBaseUser.objects.get()) # N: Revealed type is 'myapp.models.MyBaseUser' +# +# reveal_type(MyUser.objects) # N: Revealed type is 'myapp.models.MyManager[myapp.models.MyUser]' +# reveal_type(MyUser.objects.get()) # N: Revealed type is 'myapp.models.MyUser' +# installed_apps: +# - myapp +# files: +# - path: myapp/__init__.py +# - path: myapp/models.py +# content: | +# from django.db import models +# +# class MyBaseManager(models.Manager): +# pass +# class MyBaseUser(models.Model): +# objects = MyBaseManager() +# +# class MyManager(models.Manager): +# pass +# class MyUser(MyBaseUser): +# objects = MyManager() \ No newline at end of file diff --git a/test-data/typecheck/models/test_meta_options.yml b/test-data/typecheck/models/test_meta_options.yml index a97f48a..ea7e298 100644 --- a/test-data/typecheck/models/test_meta_options.yml +++ b/test-data/typecheck/models/test_meta_options.yml @@ -15,12 +15,14 @@ - case: get_field_returns_proper_field_type main: | from myapp.models import MyUser + reveal_type(MyUser._meta.get_field('base_name')) # N: Revealed type is 'django.db.models.fields.CharField[Any, Any]' reveal_type(MyUser._meta.get_field('name')) # N: Revealed type is 'django.db.models.fields.CharField[Any, Any]' reveal_type(MyUser._meta.get_field('age')) # N: Revealed type is 'django.db.models.fields.IntegerField[Any, Any]' reveal_type(MyUser._meta.get_field('unknown')) + reveal_type(MyUser._meta.get_field('to_user')) # N: Revealed type is 'django.db.models.fields.related.ForeignKey[Any, Any]' out: | - main:4: note: Revealed type is 'Any' - main:4: error: MyUser has no field named 'unknown' + main:5: note: Revealed type is 'Any' + main:5: error: MyUser has no field named 'unknown' installed_apps: - myapp files: @@ -28,6 +30,9 @@ - path: myapp/models.py content: | from django.db import models - class MyUser(models.Model): + class MyBaseUser(models.Model): + base_name = models.CharField(max_length=100) + class MyUser(MyBaseUser): name = models.CharField(max_length=100) age = models.IntegerField() + to_user = models.ForeignKey('self', on_delete=models.SET_NULL) diff --git a/test-data/typecheck/test_config.yml b/test-data/typecheck/test_config.yml index 1f9df4f..500ab91 100644 --- a/test-data/typecheck/test_config.yml +++ b/test-data/typecheck/test_config.yml @@ -22,7 +22,7 @@ class MyModel(models.Model): user = models.ForeignKey('auth.User', on_delete=models.CASCADE) if TYPE_CHECKING: - reveal_type(MyModel.user) # N: Revealed type is 'django.contrib.auth.models.User*' + reveal_type(MyModel().user) # N: Revealed type is 'django.contrib.auth.models.User*' - case: generate_pyproject_toml_and_settings_file_from_installed_apps_key main: |