From 191496ed7239de9bb40ef9617de2687404bb5484 Mon Sep 17 00:00:00 2001 From: Maxim Kurnikov Date: Thu, 7 Feb 2019 00:08:05 +0300 Subject: [PATCH] enable some test folders, bunch of fixes --- django-stubs/apps/config.pyi | 4 +- django-stubs/apps/registry.pyi | 2 +- django-stubs/contrib/admin/options.pyi | 44 +++++-------- .../contrib/admin/templatetags/admin_list.pyi | 2 +- django-stubs/contrib/admin/utils.pyi | 30 ++++----- django-stubs/contrib/contenttypes/fields.pyi | 6 +- .../contrib/postgres/fields/array.pyi | 4 +- django-stubs/core/serializers/__init__.pyi | 2 +- django-stubs/db/migrations/graph.pyi | 4 +- .../db/migrations/operations/models.pyi | 22 +++---- .../db/migrations/operations/special.pyi | 25 +++---- django-stubs/db/migrations/state.pyi | 4 +- django-stubs/db/migrations/writer.pyi | 4 +- django-stubs/db/models/__init__.pyi | 8 ++- django-stubs/db/models/deletion.pyi | 1 + django-stubs/db/models/fields/__init__.pyi | 56 ++++++---------- django-stubs/db/models/fields/files.pyi | 47 +++++++------ django-stubs/db/models/fields/related.pyi | 4 +- .../db/models/fields/reverse_related.pyi | 6 +- django-stubs/dispatch/dispatcher.pyi | 11 +--- django-stubs/forms/models.pyi | 4 +- django-stubs/forms/utils.pyi | 6 +- django-stubs/utils/deconstruct.pyi | 7 +- mypy_django_plugin/plugins/fields.py | 12 +++- scripts/typecheck_tests.py | 66 +++++++++++++++++-- 25 files changed, 208 insertions(+), 173 deletions(-) diff --git a/django-stubs/apps/config.pyi b/django-stubs/apps/config.pyi index fe3ec40..0eadad0 100644 --- a/django-stubs/apps/config.pyi +++ b/django-stubs/apps/config.pyi @@ -1,4 +1,4 @@ -from typing import Any, Iterator, Type, Optional +from typing import Any, Iterator, Type, Optional, Dict from django.db.models.base import Model @@ -12,7 +12,7 @@ class AppConfig: verbose_name: str = ... path: str = ... models_module: None = ... - models: None = ... + models: Optional[Dict[str, Type[Model]]] = ... def __init__(self, app_name: str, app_module: Optional[Any]) -> None: ... @classmethod def create(cls, entry: str) -> AppConfig: ... diff --git a/django-stubs/apps/registry.pyi b/django-stubs/apps/registry.pyi index b8ccef3..836164a 100644 --- a/django-stubs/apps/registry.pyi +++ b/django-stubs/apps/registry.pyi @@ -12,7 +12,7 @@ class Apps: stored_app_configs: List[Any] = ... apps_ready: bool = ... loading: bool = ... - _pending_operations: DefaultDict[str, List] + _pending_operations: DefaultDict[Tuple[str, str], List] def __init__(self, installed_apps: Optional[Union[List[AppConfigStub], List[str], Tuple]] = ...) -> None: ... models_ready: bool = ... ready: bool = ... diff --git a/django-stubs/contrib/admin/options.pyi b/django-stubs/contrib/admin/options.pyi index 4b34ade..1f070b3 100644 --- a/django-stubs/contrib/admin/options.pyi +++ b/django-stubs/contrib/admin/options.pyi @@ -1,6 +1,7 @@ from collections import OrderedDict -from typing import Any, Callable, Dict, List, Optional, Sequence, Set, Tuple, Type, Union +from typing import Any, Callable, Dict, List, Optional, Sequence, Set, Tuple, Type, Union, Iterator +from django.contrib.admin.filters import ListFilter from django.contrib.admin.models import LogEntry from django.contrib.admin.sites import AdminSite from django.contrib.admin.views.main import ChangeList @@ -68,16 +69,11 @@ class BaseModelAdmin: def get_autocomplete_fields(self, request: WSGIRequest) -> Tuple: ... def get_view_on_site_url(self, obj: Optional[Model] = ...) -> Optional[str]: ... def get_empty_value_display(self) -> SafeText: ... - def get_exclude(self, request: WSGIRequest, obj: Optional[Model] = ...) -> None: ... - def get_fields( - self, request: WSGIRequest, obj: Optional[Model] = ... - ) -> Union[List[Union[Callable, str]], Tuple[str, str]]: ... + def get_exclude(self, request: WSGIRequest, obj: Optional[Model] = ...) -> Any: ... + def get_fields(self, request: WSGIRequest, obj: Optional[Model] = ...) -> Sequence[Union[Callable, str]]: ... def get_fieldsets( self, request: WSGIRequest, obj: Optional[Model] = ... - ) -> Union[ - List[Tuple[None, Dict[str, List[Union[Callable, str]]]]], - Tuple[Tuple[Optional[str], Dict[str, Tuple[Union[Tuple[str, str], str]]]]], - ]: ... + ) -> List[Tuple[Optional[str], Dict[str, Any]]]: ... def get_ordering(self, request: WSGIRequest) -> Union[List[str], Tuple]: ... def get_readonly_fields(self, request: WSGIRequest, obj: Optional[Model] = ...) -> Union[List[str], Tuple]: ... def get_prepopulated_fields(self, request: WSGIRequest, obj: Optional[Model] = ...) -> Dict[str, Tuple[str]]: ... @@ -93,21 +89,21 @@ class BaseModelAdmin: class ModelAdmin(BaseModelAdmin): formfield_overrides: Any - list_display: Sequence[str] = ... - list_display_links: Sequence[str] = ... - list_filter: Sequence[str] = ... - list_select_related: Sequence[str] = ... + list_display: Sequence[Union[str, Callable]] = ... + list_display_links: Sequence[Union[str, Callable]] = ... + list_filter: Sequence[Union[str, Type[ListFilter], Tuple[str, Type[ListFilter]]]] = ... + list_select_related: Union[bool, Sequence[str]] = ... list_per_page: int = ... list_max_show_all: int = ... list_editable: Sequence[str] = ... - search_fields: Any = ... - date_hierarchy: Any = ... + search_fields: Sequence[str] = ... + date_hierarchy: Optional[Any] = ... save_as: bool = ... save_as_continue: bool = ... save_on_top: bool = ... paginator: Any = ... preserve_filters: bool = ... - inlines: Any = ... + inlines: Sequence[Type[InlineModelAdmin]] = ... add_form_template: Any = ... change_form_template: Any = ... change_list_template: Any = ... @@ -138,7 +134,7 @@ class ModelAdmin(BaseModelAdmin): def get_object(self, request: WSGIRequest, object_id: str, from_field: None = ...) -> Optional[Model]: ... def get_changelist_form(self, request: Any, **kwargs: Any): ... def get_changelist_formset(self, request: Any, **kwargs: Any): ... - def get_formsets_with_inlines(self, request: WSGIRequest, obj: Optional[Model] = ...) -> None: ... + def get_formsets_with_inlines(self, request: WSGIRequest, obj: Optional[Model] = ...) -> Iterator[Any]: ... def get_paginator( self, request: WSGIRequest, @@ -147,18 +143,8 @@ class ModelAdmin(BaseModelAdmin): orphans: int = ..., allow_empty_first_page: bool = ..., ) -> Paginator: ... - def log_addition( - self, - request: WSGIRequest, - object: Model, - message: Union[Dict[str, Dict[Any, Any]], List[Dict[str, Dict[str, str]]]], - ) -> LogEntry: ... - def log_change( - self, - request: WSGIRequest, - object: Model, - message: Union[Dict[str, Dict[str, List[str]]], List[Dict[str, Dict[str, Union[List[str], str]]]]], - ) -> LogEntry: ... + def log_addition(self, request: WSGIRequest, object: Model, message: Any) -> LogEntry: ... + def log_change(self, request: WSGIRequest, object: Model, message: Any) -> LogEntry: ... def log_deletion(self, request: WSGIRequest, object: Model, object_repr: str) -> LogEntry: ... def action_checkbox(self, obj: Model) -> SafeText: ... def get_actions(self, request: WSGIRequest) -> OrderedDict: ... diff --git a/django-stubs/contrib/admin/templatetags/admin_list.pyi b/django-stubs/contrib/admin/templatetags/admin_list.pyi index 2b7f2bb..9d8ed88 100644 --- a/django-stubs/contrib/admin/templatetags/admin_list.pyi +++ b/django-stubs/contrib/admin/templatetags/admin_list.pyi @@ -32,7 +32,7 @@ def result_list( str, Union[List[Dict[str, Optional[Union[int, str]]]], List[ResultList], List[BoundField], ChangeList, int] ]: ... def result_list_tag(parser: Parser, token: Token) -> InclusionAdminNode: ... -def date_hierarchy(cl: ChangeList) -> Optional[Dict[str, Union[Dict[str, str], List[Dict[str, str]], bool]]]: ... +def date_hierarchy(cl: ChangeList) -> Optional[Dict[str, Any]]: ... def date_hierarchy_tag(parser: Parser, token: Token) -> InclusionAdminNode: ... def search_form(cl: ChangeList) -> Dict[str, Union[bool, ChangeList, str]]: ... def search_form_tag(parser: Parser, token: Token) -> InclusionAdminNode: ... diff --git a/django-stubs/contrib/admin/utils.pyi b/django-stubs/contrib/admin/utils.pyi index 6d2078d..7fd9db9 100644 --- a/django-stubs/contrib/admin/utils.pyi +++ b/django-stubs/contrib/admin/utils.pyi @@ -1,5 +1,6 @@ +import collections from datetime import datetime -from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Type, Union +from typing import Any, Callable, Dict, List, Optional, Sequence, Set, Tuple, Type, Union from uuid import UUID from django.contrib.admin.options import BaseModelAdmin @@ -8,28 +9,23 @@ from django.contrib.auth.forms import AdminPasswordChangeForm from django.core.handlers.wsgi import WSGIRequest from django.db.models.base import Model from django.db.models.deletion import Collector -from django.db.models.fields import Field from django.db.models.fields.mixins import FieldCacheMixin -from django.db.models.fields.reverse_related import ForeignObjectRel, ManyToOneRel, OneToOneRel +from django.db.models.fields.reverse_related import ManyToOneRel from django.db.models.options import Options from django.db.models.query import QuerySet +from django.forms.forms import BaseForm from django.utils.safestring import SafeText +from django.db.models.fields import Field, reverse_related + class FieldIsAForeignKeyColumnName(Exception): ... def lookup_needs_distinct(opts: Options, lookup_path: str) -> bool: ... def prepare_lookup_value(key: str, value: Union[datetime, str]) -> Union[bool, datetime, str]: ... -def quote(s: Union[int, str, UUID]) -> Union[int, str, UUID]: ... +def quote(s: Union[int, str, UUID]) -> str: ... def unquote(s: str) -> str: ... -def flatten( - fields: Union[List[Union[Callable, str]], List[Union[List[str], str]], List[Union[Tuple[str, str], str]], Tuple] -) -> List[Union[Callable, str]]: ... -def flatten_fieldsets( - fieldsets: Union[ - List[Tuple[Optional[str], Dict[str, Tuple[str]]]], - Tuple[Tuple[Optional[str], Dict[str, Tuple[Union[Tuple[str, str], str]]]]], - ] -) -> List[Union[Callable, str]]: ... +def flatten(fields: Any) -> List[Union[Callable, str]]: ... +def flatten_fieldsets(fieldsets: Any) -> List[Union[Callable, str]]: ... def get_deleted_objects( objs: QuerySet, request: WSGIRequest, admin_site: AdminSite ) -> Tuple[List[Any], Dict[Any, Any], Set[Any], List[Any]]: ... @@ -47,7 +43,7 @@ class NestedObjects(Collector): def add_edge(self, source: Optional[Model], target: Model) -> None: ... def collect( self, - objs: Union[List[Model], QuerySet], + objs: Union[Sequence[Model], QuerySet], source: Optional[Type[Model]] = ..., source_attr: Optional[str] = ..., **kwargs: Any @@ -62,7 +58,11 @@ def lookup_field( name: Union[Callable, str], obj: Model, model_admin: BaseModelAdmin = ... ) -> Tuple[Optional[Field], Callable, Callable]: ... def label_for_field( - name: Union[Callable, str], model: Type[Model], model_admin: Optional[BaseModelAdmin] = ..., return_attr: bool = ... + name: Union[Callable, str], + model: Type[Model], + model_admin: Optional[BaseModelAdmin] = ..., + return_attr: bool = ..., + form: Optional[BaseForm] = ..., ) -> Union[Tuple[Optional[str], Union[Callable, Type[str]]], str]: ... def help_text_for_field(name: str, model: Type[Model]) -> str: ... def display_for_field( diff --git a/django-stubs/contrib/contenttypes/fields.pyi b/django-stubs/contrib/contenttypes/fields.pyi index c5e7582..1717d6a 100644 --- a/django-stubs/contrib/contenttypes/fields.pyi +++ b/django-stubs/contrib/contenttypes/fields.pyi @@ -51,7 +51,7 @@ class GenericForeignKey(FieldCacheMixin): class GenericRel(ForeignObjectRel): field: GenericRelation - limit_choices_to: Dict[Any, Any] + limit_choices_to: Optional[Union[Dict[str, Any], Callable[[], Any]]] model: Type[Model] multiple: bool on_delete: Callable @@ -65,7 +65,7 @@ class GenericRel(ForeignObjectRel): to: Union[Type[Model], str], related_name: None = ..., related_query_name: Optional[str] = ..., - limit_choices_to: None = ..., + limit_choices_to: Optional[Union[Dict[str, Any], Callable[[], Any]]] = ..., ) -> None: ... class GenericRelation(ForeignObject): @@ -87,7 +87,7 @@ class GenericRelation(ForeignObject): content_type_field: str = ..., for_concrete_model: bool = ..., related_query_name: Optional[str] = ..., - limit_choices_to: None = ..., + limit_choices_to: Optional[Union[Dict[str, Any], Callable[[], Any]]] = ..., **kwargs: Any ) -> None: ... def check(self, **kwargs: Any) -> List[Error]: ... diff --git a/django-stubs/contrib/postgres/fields/array.pyi b/django-stubs/contrib/postgres/fields/array.pyi index 6c2e8c8..c7f7510 100644 --- a/django-stubs/contrib/postgres/fields/array.pyi +++ b/django-stubs/contrib/postgres/fields/array.pyi @@ -1,4 +1,4 @@ -from typing import Any, Dict, List, Optional, Tuple, Union, TypeVar, Generic, Sequence +from typing import Any, Generic, List, Optional, Sequence, TypeVar from django.db.models.fields import Field from .mixins import CheckFieldDefaultMixin @@ -12,7 +12,7 @@ class ArrayField(CheckFieldDefaultMixin, Field, Generic[_T]): size: Any = ... default_validators: Any = ... from_db_value: Any = ... - def __init__(self, base_field: _T, size: None = ..., **kwargs: Any) -> None: ... + def __init__(self, base_field: _T, size: Optional[int] = ..., **kwargs: Any) -> None: ... def check(self, **kwargs: Any) -> List[Any]: ... @property def description(self): ... diff --git a/django-stubs/core/serializers/__init__.pyi b/django-stubs/core/serializers/__init__.pyi index bf7c90c..c0040e3 100644 --- a/django-stubs/core/serializers/__init__.pyi +++ b/django-stubs/core/serializers/__init__.pyi @@ -30,7 +30,7 @@ def get_public_serializer_formats() -> List[str]: ... def get_deserializer(format: str) -> Union[Callable, Type[Deserializer]]: ... def serialize( format: str, queryset: Union[Iterator[Any], List[Model], QuerySet], **options: Any -) -> Optional[Union[List[OrderedDict], bytes, str]]: ... +) -> Optional[Union[bytes, str]]: ... def deserialize(format: str, stream_or_string: Any, **options: Any) -> Union[Iterator[Any], Deserializer]: ... def sort_dependencies( app_list: Union[List[Tuple[AppConfig, None]], List[Tuple[str, List[Type[Model]]]]] diff --git a/django-stubs/db/migrations/graph.pyi b/django-stubs/db/migrations/graph.pyi index 4e41989..852c6d2 100644 --- a/django-stubs/db/migrations/graph.pyi +++ b/django-stubs/db/migrations/graph.pyi @@ -24,7 +24,7 @@ class DummyNode(Node): parents: Set[Any] origin: Any = ... error_message: Any = ... - def __init__(self, key: Tuple[str, str], origin: Migration, error_message: str) -> None: ... + def __init__(self, key: Tuple[str, str], origin: Union[Migration, str], error_message: str) -> None: ... __class__: Any = ... def promote(self) -> None: ... def raise_error(self) -> None: ... @@ -35,7 +35,7 @@ class MigrationGraph: cached: bool = ... def __init__(self) -> None: ... def add_node(self, key: Tuple[str, str], migration: Optional[Migration]) -> None: ... - def add_dummy_node(self, key: Tuple[str, str], origin: Migration, error_message: str) -> None: ... + def add_dummy_node(self, key: Tuple[str, str], origin: Union[Migration, str], error_message: str) -> None: ... def add_dependency( self, migration: Optional[Union[Migration, str]], diff --git a/django-stubs/db/migrations/operations/models.pyi b/django-stubs/db/migrations/operations/models.pyi index 9d116c5..b255821 100644 --- a/django-stubs/db/migrations/operations/models.pyi +++ b/django-stubs/db/migrations/operations/models.pyi @@ -1,4 +1,4 @@ -from typing import Any, Dict, List, Optional, Set, Tuple, Type, Union +from typing import Any, Collection, Dict, List, Optional, Sequence, Tuple, Union from django.db.migrations.operations.base import Operation from django.db.models.indexes import Index @@ -13,17 +13,17 @@ class ModelOperation(Operation): class CreateModel(ModelOperation): serialization_expand_args: Any = ... - fields: Any = ... + fields: Sequence[Tuple[str, Field]] = ... options: Any = ... - bases: Any = ... - managers: Any = ... + bases: Optional[Sequence[Union[type, str]]] = ... + managers: Optional[Sequence[Tuple[str, Manager]]] = ... def __init__( self, name: str, - fields: List[Tuple[str, Field]], + fields: Sequence[Tuple[str, Field]], options: Optional[Dict[str, Any]] = ..., - bases: Optional[Union[Tuple[Type[Any], ...], Tuple[str, ...]]] = ..., - managers: Optional[List[Tuple[str, Manager]]] = ..., + bases: Optional[Sequence[Union[type, str]]] = ..., + managers: Optional[Sequence[Tuple[str, Manager]]] = ..., ) -> None: ... def model_to_key(self, model: str) -> List[str]: ... @@ -45,13 +45,13 @@ class FieldRelatedOptionOperation(ModelOptionOperation): ... class AlterUniqueTogether(FieldRelatedOptionOperation): option_name: str = ... - unique_together: Any = ... - def __init__(self, name: str, unique_together: Set[Tuple[str, ...]]) -> None: ... + unique_together: Collection[Sequence[str]] = ... + def __init__(self, name: str, unique_together: Collection[Sequence[str]]) -> None: ... class AlterIndexTogether(FieldRelatedOptionOperation): option_name: str = ... - index_together: Set[Tuple[str, ...]] = ... - def __init__(self, name: str, index_together: Set[Tuple[str, ...]]) -> None: ... + index_together: Collection[Sequence[str]] = ... + def __init__(self, name: str, index_together: Collection[Sequence[str]]) -> None: ... class AlterOrderWithRespectTo(FieldRelatedOptionOperation): order_with_respect_to: str = ... diff --git a/django-stubs/db/migrations/operations/special.pyi b/django-stubs/db/migrations/operations/special.pyi index d05cea9..59c65a5 100644 --- a/django-stubs/db/migrations/operations/special.pyi +++ b/django-stubs/db/migrations/operations/special.pyi @@ -1,16 +1,17 @@ -from typing import Any, Callable, List, Optional +from typing import Any, Callable, Optional, Sequence, Dict from django.db.backends.base.schema import BaseDatabaseSchemaEditor -from django.db.migrations.operations.models import CreateModel -from django.db.migrations.state import ProjectState, StateApps +from django.db.migrations.state import StateApps from .base import Operation class SeparateDatabaseAndState(Operation): serialization_expand_args: Any = ... - database_operations: Any = ... - state_operations: Any = ... - def __init__(self, database_operations: List[Any] = ..., state_operations: List[CreateModel] = ...) -> None: ... + database_operations: Sequence[Operation] = ... + state_operations: Sequence[Operation] = ... + def __init__( + self, database_operations: Sequence[Operation] = ..., state_operations: Sequence[Operation] = ... + ) -> None: ... class RunSQL(Operation): noop: str = ... @@ -30,17 +31,17 @@ class RunSQL(Operation): class RunPython(Operation): reduces_to_sql: bool = ... - atomic: Any = ... - code: Any = ... - reverse_code: Any = ... - hints: Any = ... - elidable: Any = ... + atomic: bool = ... + code: Callable = ... + reverse_code: Optional[Callable] = ... + hints: Optional[Dict[str, Any]] = ... + elidable: bool = ... def __init__( self, code: Callable, reverse_code: Optional[Callable] = ..., atomic: Optional[bool] = ..., - hints: None = ..., + hints: Optional[Dict[str, Any]] = ..., elidable: bool = ..., ) -> None: ... @staticmethod diff --git a/django-stubs/db/migrations/state.pyi b/django-stubs/db/migrations/state.pyi index 0b84403..f77a4d9 100644 --- a/django-stubs/db/migrations/state.pyi +++ b/django-stubs/db/migrations/state.pyi @@ -1,4 +1,4 @@ -from typing import Any, Dict, Iterator, List, Optional, Tuple, Type, DefaultDict +from typing import Any, Dict, Iterator, List, Optional, Tuple, Type, DefaultDict, Union, Sequence from django.apps.registry import Apps from django.db.models.base import Model @@ -31,7 +31,7 @@ class ModelState: name: str, fields: List[Tuple[str, Field]], options: Optional[Dict[str, Any]] = ..., - bases: Optional[Tuple[Type[Model]]] = ..., + bases: Optional[Sequence[Union[Type[Model], str]]] = ..., managers: Optional[List[Tuple[str, Manager]]] = ..., ) -> None: ... def clone(self) -> ModelState: ... diff --git a/django-stubs/db/migrations/writer.pyi b/django-stubs/db/migrations/writer.pyi index 66a30ee..cf0392c 100644 --- a/django-stubs/db/migrations/writer.pyi +++ b/django-stubs/db/migrations/writer.pyi @@ -1,4 +1,4 @@ -from typing import Any, Optional, Set, Tuple, Type, List +from typing import Any, Optional, Set, Tuple, Type, List, Union from django.db.migrations.migration import Migration from django.db.migrations.operations.base import Operation @@ -24,7 +24,7 @@ class OperationWriter: class MigrationWriter: migration: Migration = ... needs_manual_porting: bool = ... - def __init__(self, migration: Migration) -> None: ... + def __init__(self, migration: Union[type, Migration]) -> None: ... def as_string(self) -> str: ... @property def basedir(self) -> str: ... diff --git a/django-stubs/db/models/__init__.pyi b/django-stubs/db/models/__init__.pyi index 8f4d224..38b27f4 100644 --- a/django-stubs/db/models/__init__.pyi +++ b/django-stubs/db/models/__init__.pyi @@ -39,7 +39,6 @@ from .fields import ( BinaryField as BinaryField, DurationField as DurationField, BigAutoField as BigAutoField, - FileField as FileField, CommaSeparatedIntegerField as CommaSeparatedIntegerField, ) @@ -53,7 +52,12 @@ from .fields.related import ( OneToOneRel as OneToOneRel, ForeignObjectRel as ForeignObjectRel, ) -from .fields.files import ImageField as ImageField, FileField as FileField +from .fields.files import ( + ImageField as ImageField, + FileField as FileField, + FieldFile as FieldFile, + FileDescriptor as FileDescriptor, +) from .fields.proxy import OrderWrt as OrderWrt from .deletion import ( diff --git a/django-stubs/db/models/deletion.pyi b/django-stubs/db/models/deletion.pyi index bc1d241..fd3eddc 100644 --- a/django-stubs/db/models/deletion.pyi +++ b/django-stubs/db/models/deletion.pyi @@ -10,3 +10,4 @@ def PROTECT(collector, field, sub_objs, using): ... def SET(value: Any) -> Callable: ... class ProtectedError(IntegrityError): ... +class Collector: ... diff --git a/django-stubs/db/models/fields/__init__.pyi b/django-stubs/db/models/fields/__init__.pyi index 0e942b1..cf70a1f 100644 --- a/django-stubs/db/models/fields/__init__.pyi +++ b/django-stubs/db/models/fields/__init__.pyi @@ -1,7 +1,8 @@ +import uuid +from datetime import date, time, datetime, timedelta from typing import Any, Optional, Tuple, Iterable, Callable, Dict, Union, Type import decimal -from django.core.files.storage import Storage from django.db.models import Model from django.db.models.query_utils import RegisterLookupMixin @@ -10,7 +11,7 @@ from django.core.exceptions import FieldDoesNotExist as FieldDoesNotExist from django.forms import Widget, Field as FormField from .mixins import NOT_PROVIDED as NOT_PROVIDED -_Choice = Tuple[Any, str] +_Choice = Tuple[Any, Any] _ChoiceNamedGroup = Union[Tuple[str, Iterable[_Choice]], Tuple[str, Any]] _FieldChoices = Iterable[Union[_Choice, _ChoiceNamedGroup]] @@ -58,6 +59,7 @@ class Field(RegisterLookupMixin): def get_internal_type(self) -> str: ... def formfield(self, **kwargs) -> FormField: ... def contribute_to_class(self, cls: Type[Model], name: str, private_only: bool = ...) -> None: ... + def to_python(self, value: Any) -> Any: ... class IntegerField(Field): def __set__(self, instance, value: Union[int, F]) -> None: ... @@ -70,7 +72,9 @@ class PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField): ... class PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField): ... class SmallIntegerField(IntegerField): ... class BigIntegerField(IntegerField): ... -class FloatField(Field): ... + +class FloatField(Field): + def __get__(self, instance, owner) -> float: ... class DecimalField(Field): def __init__( @@ -170,35 +174,8 @@ class NullBooleanField(Field): def __set__(self, instance, value: Optional[bool]) -> None: ... def __get__(self, instance, owner) -> Optional[bool]: ... -class FileField(Field): - def __init__( - self, - verbose_name: Optional[Union[str, bytes]] = ..., - name: Optional[str] = ..., - upload_to: str = ..., - storage: Optional[Storage] = ..., - primary_key: bool = ..., - max_length: Optional[int] = ..., - unique: bool = ..., - blank: bool = ..., - null: bool = ..., - db_index: bool = ..., - default: Any = ..., - editable: bool = ..., - auto_created: bool = ..., - serialize: bool = ..., - unique_for_date: Optional[str] = ..., - unique_for_month: Optional[str] = ..., - unique_for_year: Optional[str] = ..., - choices: Optional[_FieldChoices] = ..., - help_text: str = ..., - db_column: Optional[str] = ..., - db_tablespace: Optional[str] = ..., - validators: Iterable[_ValidatorCallable] = ..., - error_messages: Optional[_ErrorMessagesToOverride] = ..., - ): ... - -class IPAddressField(Field): ... +class IPAddressField(Field): + def __get__(self, instance, owner) -> str: ... class GenericIPAddressField(Field): default_error_messages: Any = ... @@ -226,6 +203,7 @@ class GenericIPAddressField(Field): validators: Iterable[_ValidatorCallable] = ..., error_messages: Optional[_ErrorMessagesToOverride] = ..., ) -> None: ... + def __get__(self, instance, owner) -> str: ... class DateTimeCheckMixin: ... @@ -253,6 +231,7 @@ class DateField(DateTimeCheckMixin, Field): validators: Iterable[_ValidatorCallable] = ..., error_messages: Optional[_ErrorMessagesToOverride] = ..., ): ... + def __get__(self, instance, owner) -> date: ... class TimeField(DateTimeCheckMixin, Field): def __init__( @@ -277,9 +256,13 @@ class TimeField(DateTimeCheckMixin, Field): validators: Iterable[_ValidatorCallable] = ..., error_messages: Optional[_ErrorMessagesToOverride] = ..., ): ... + def __get__(self, instance, owner) -> time: ... -class DateTimeField(DateField): ... -class UUIDField(Field): ... +class DateTimeField(DateField): + def __get__(self, instance, owner) -> datetime: ... + +class UUIDField(Field): + def __get__(self, instance, owner) -> uuid.UUID: ... class FilePathField(Field): path: str = ... @@ -315,6 +298,9 @@ class FilePathField(Field): ): ... class BinaryField(Field): ... -class DurationField(Field): ... + +class DurationField(Field): + def __get__(self, instance, owner) -> timedelta: ... + class BigAutoField(AutoField): ... class CommaSeparatedIntegerField(CharField): ... diff --git a/django-stubs/db/models/fields/files.pyi b/django-stubs/db/models/fields/files.pyi index 9d031a5..945be31 100644 --- a/django-stubs/db/models/fields/files.pyi +++ b/django-stubs/db/models/fields/files.pyi @@ -1,4 +1,4 @@ -from typing import Any, Callable, List, Optional, Type, Union, Tuple +from typing import Any, Callable, List, Optional, Type, Union, Tuple, Iterable from django.core.checks.messages import Error from django.core.files.base import File @@ -6,7 +6,7 @@ from django.core.files.images import ImageFile from django.core.files.storage import FileSystemStorage, Storage from django.db.models.base import Model -from django.db.models.fields import Field +from django.db.models.fields import Field, _FieldChoices, _ValidatorCallable, _ErrorMessagesToOverride from django.forms import fields as form_fields BLANK_CHOICE_DASH: List[Tuple[str, str]] = ... @@ -31,31 +31,39 @@ class FieldFile(File): class FileDescriptor: field: FileField = ... def __init__(self, field: FileField) -> None: ... - def __get__(self, instance: Optional[Model], cls: Type[Model] = ...) -> Union[FieldFile, FileDescriptor]: ... def __set__(self, instance: Model, value: Optional[Any]) -> None: ... + def __get__(self, instance: Optional[Model], cls: Type[Model] = ...) -> Union[FieldFile, FileDescriptor]: ... class FileField(Field): - attr_class: Any = ... - descriptor_class: Any = ... - description: Any = ... storage: Any = ... - upload_to: Any = ... + upload_to: Union[str, Callable] = ... def __init__( self, - verbose_name: Optional[str] = ..., + verbose_name: Optional[Union[str, bytes]] = ..., name: Optional[str] = ..., - upload_to: Union[Callable, str] = ..., + upload_to: Union[str, Callable] = ..., storage: Optional[Storage] = ..., - **kwargs: Any - ) -> None: ... - def check(self, **kwargs: Any) -> List[Error]: ... - def deconstruct(self) -> Any: ... - def get_internal_type(self) -> str: ... - def get_prep_value(self, value: Union[FieldFile, str]) -> str: ... - def pre_save(self, model_instance: Model, add: bool) -> FieldFile: ... + primary_key: bool = ..., + max_length: Optional[int] = ..., + unique: bool = ..., + blank: bool = ..., + null: bool = ..., + db_index: bool = ..., + default: Any = ..., + editable: bool = ..., + auto_created: bool = ..., + serialize: bool = ..., + unique_for_date: Optional[str] = ..., + unique_for_month: Optional[str] = ..., + unique_for_year: Optional[str] = ..., + choices: Optional[_FieldChoices] = ..., + help_text: str = ..., + db_column: Optional[str] = ..., + db_tablespace: Optional[str] = ..., + validators: Iterable[_ValidatorCallable] = ..., + error_messages: Optional[_ErrorMessagesToOverride] = ..., + ): ... def generate_filename(self, instance: Optional[Model], filename: str) -> str: ... - def save_form_data(self, instance: Model, data: Optional[Union[bool, File, str]]) -> None: ... - def formfield(self, **kwargs: Any) -> form_fields.FileField: ... class ImageFileDescriptor(FileDescriptor): field: ImageField @@ -74,7 +82,4 @@ class ImageField(FileField): height_field: Optional[str] = ..., **kwargs: Any ) -> None: ... - def check(self, **kwargs: Any) -> List[Any]: ... - def deconstruct(self) -> Any: ... def update_dimension_fields(self, instance: Model, force: bool = ..., *args: Any, **kwargs: Any) -> None: ... - def formfield(self, **kwargs: Any) -> form_fields.ImageField: ... diff --git a/django-stubs/db/models/fields/related.pyi b/django-stubs/db/models/fields/related.pyi index 5d7ffe3..b1e998f 100644 --- a/django-stubs/db/models/fields/related.pyi +++ b/django-stubs/db/models/fields/related.pyi @@ -81,7 +81,7 @@ class ForeignObject(RelatedField): rel: None = ..., related_name: Optional[str] = ..., related_query_name: None = ..., - limit_choices_to: None = ..., + limit_choices_to: Optional[Union[Dict[str, Any], Callable[[], Any]]] = ..., parent_link: bool = ..., swappable: bool = ..., verbose_name: Optional[str] = ..., @@ -126,7 +126,7 @@ class ManyToManyField(RelatedField, Generic[_T]): to: Union[Type[_T], str], related_name: Optional[str] = ..., related_query_name: Optional[str] = ..., - limit_choices_to: Optional[Dict[str, Any]] = ..., + limit_choices_to: Optional[Union[Dict[str, Any], Callable[[], Any]]] = ..., symmetrical: Optional[bool] = ..., through: Optional[Union[str, Type[Model]]] = ..., through_fields: Optional[Tuple[str, str]] = ..., diff --git a/django-stubs/db/models/fields/reverse_related.pyi b/django-stubs/db/models/fields/reverse_related.pyi index 3f6f414..c156bb2 100644 --- a/django-stubs/db/models/fields/reverse_related.pyi +++ b/django-stubs/db/models/fields/reverse_related.pyi @@ -26,7 +26,7 @@ class ForeignObjectRel(FieldCacheMixin): model: Union[Type[Model], str] = ... related_name: Optional[str] = ... related_query_name: Optional[str] = ... - limit_choices_to: Union[Callable, Dict[str, Any]] = ... + limit_choices_to: Optional[Union[Dict[str, Any], Callable[[], Any]]] = ... parent_link: bool = ... on_delete: Callable = ... symmetrical: bool = ... @@ -38,7 +38,7 @@ class ForeignObjectRel(FieldCacheMixin): to: Union[Type[Model], str], related_name: Optional[str] = ..., related_query_name: Optional[str] = ..., - limit_choices_to: Any = ..., + limit_choices_to: Optional[Union[Dict[str, Any], Callable[[], Any]]] = ..., parent_link: bool = ..., on_delete: Optional[Callable] = ..., ) -> None: ... @@ -86,7 +86,7 @@ class ManyToOneRel(ForeignObjectRel): field_name: Optional[str], related_name: Optional[str] = ..., related_query_name: Optional[str] = ..., - limit_choices_to: Optional[Union[Callable, Dict[str, Union[int, str]], Q]] = ..., + limit_choices_to: Optional[Union[Dict[str, Any], Callable[[], Any]]] = ..., parent_link: bool = ..., on_delete: Callable = ..., ) -> None: ... diff --git a/django-stubs/dispatch/dispatcher.pyi b/django-stubs/dispatch/dispatcher.pyi index 2767a1a..5058459 100644 --- a/django-stubs/dispatch/dispatcher.pyi +++ b/django-stubs/dispatch/dispatcher.pyi @@ -14,17 +14,10 @@ class Signal: sender_receivers_cache: Any = ... def __init__(self, providing_args: List[str] = ..., use_caching: bool = ...) -> None: ... def connect( - self, - receiver: Callable, - sender: Optional[Union[Type[Model], AppConfig, str]] = ..., - weak: bool = ..., - dispatch_uid: Optional[str] = ..., + self, receiver: Callable, sender: Optional[object] = ..., weak: bool = ..., dispatch_uid: Optional[str] = ... ) -> None: ... def disconnect( - self, - receiver: Optional[Callable] = ..., - sender: Optional[Union[Type[Model], AppConfig, str]] = ..., - dispatch_uid: Optional[str] = ..., + self, receiver: Optional[Callable] = ..., sender: Optional[object] = ..., dispatch_uid: Optional[str] = ... ) -> bool: ... def has_listeners(self, sender: Any = ...) -> bool: ... def send(self, sender: Any, **named: Any) -> List[Tuple[Callable, Optional[str]]]: ... diff --git a/django-stubs/forms/models.pyi b/django-stubs/forms/models.pyi index d9558fa..572d40b 100644 --- a/django-stubs/forms/models.pyi +++ b/django-stubs/forms/models.pyi @@ -235,7 +235,7 @@ class ModelChoiceField(ChoiceField): iterator: Any = ... empty_label: Optional[str] = ... queryset: Any = ... - limit_choices_to: None = ... + limit_choices_to: Optional[Union[Dict[str, Any], Callable[[], Any]]] = ... to_field_name: None = ... def __init__( self, @@ -248,7 +248,7 @@ class ModelChoiceField(ChoiceField): initial: Optional[Any] = ..., help_text: str = ..., to_field_name: Optional[Any] = ..., - limit_choices_to: Optional[Any] = ..., + limit_choices_to: Optional[Union[Dict[str, Any], Callable[[], Any]]] = ..., **kwargs: Any ) -> None: ... def get_limit_choices_to(self) -> Optional[Union[Dict[str, datetime], Q, MagicMock]]: ... diff --git a/django-stubs/forms/utils.pyi b/django-stubs/forms/utils.pyi index 60ac4c7..31ea3cc 100644 --- a/django-stubs/forms/utils.pyi +++ b/django-stubs/forms/utils.pyi @@ -1,6 +1,6 @@ from collections import UserList from datetime import datetime -from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union +from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union, Sequence from django.core.exceptions import ValidationError from django.utils.safestring import SafeText @@ -18,7 +18,9 @@ class ErrorDict(dict): class ErrorList(UserList): data: List[Union[ValidationError, str]] error_class: str = ... - def __init__(self, initlist: Optional[ErrorList] = ..., error_class: Optional[str] = ...) -> None: ... + def __init__( + self, initlist: Optional[Union[ErrorList, Sequence[str]]] = ..., error_class: Optional[str] = ... + ) -> None: ... def as_data(self) -> List[ValidationError]: ... def get_json_data(self, escape_html: bool = ...) -> List[Dict[str, str]]: ... def as_json(self, escape_html: bool = ...) -> str: ... diff --git a/django-stubs/utils/deconstruct.pyi b/django-stubs/utils/deconstruct.pyi index be80428..99f4b4a 100644 --- a/django-stubs/utils/deconstruct.pyi +++ b/django-stubs/utils/deconstruct.pyi @@ -1,6 +1,3 @@ -from typing import Any, Optional, Type, Union +from typing import Any, Optional -from django.contrib.postgres.validators import KeysValidator -from django.core.validators import RegexValidator - -def deconstructible(*args: Any, path: Optional[Any] = ...) -> Type[Union[KeysValidator, RegexValidator]]: ... +def deconstructible(*args: Any, path: Optional[Any] = ...) -> Any: ... diff --git a/mypy_django_plugin/plugins/fields.py b/mypy_django_plugin/plugins/fields.py index 563653a..539b0d3 100644 --- a/mypy_django_plugin/plugins/fields.py +++ b/mypy_django_plugin/plugins/fields.py @@ -1,5 +1,5 @@ from mypy.plugin import FunctionContext -from mypy.types import Type +from mypy.types import Type, Instance def determine_type_of_array_field(ctx: FunctionContext) -> Type: @@ -7,5 +7,13 @@ def determine_type_of_array_field(ctx: FunctionContext) -> Type: return ctx.default_return_type base_field_arg_type = ctx.arg_types[ctx.callee_arg_names.index('base_field')][0] + if not isinstance(base_field_arg_type, Instance): + return ctx.default_return_type + + get_method = base_field_arg_type.type.get_method('__get__') + if not get_method: + # not a method + return ctx.default_return_type + return ctx.api.named_generic_type(ctx.context.callee.fullname, - args=[base_field_arg_type.type.names['__get__'].type.ret_type]) + args=[get_method.type.ret_type]) diff --git a/scripts/typecheck_tests.py b/scripts/typecheck_tests.py index b95bfa0..cff936b 100644 --- a/scripts/typecheck_tests.py +++ b/scripts/typecheck_tests.py @@ -47,7 +47,8 @@ IGNORED_ERRORS = { # cookies private attribute 'full_clean" of "Model" does not return a value', # private members - re.compile(r'has no attribute "|\'_[a-z][a-z_]+"|\'') + re.compile(r'has no attribute "|\'_[a-z][a-z_]+"|\''), + 'Invalid base class' ], 'admin_changelist': [ 'Incompatible types in assignment (expression has type "FilteredChildAdmin", variable has type "ChildAdmin")' @@ -60,6 +61,19 @@ IGNORED_ERRORS = { 'variable has type "AdminRadioSelect")', 'Incompatible types in assignment (expression has type "Widget", variable has type "AutocompleteSelect")' ], + 'admin_utils': [ + re.compile(r'Argument [0-9] to "lookup_field" has incompatible type'), + 'MockModelAdmin', + 'Incompatible types in assignment (expression has type "str", variable has type "Callable[..., Any]")', + 'Dict entry 0 has incompatible type "str": "Tuple[str, str, List[str]]"; expected "str": ' + + '"Tuple[str, str, Tuple[str, str]]"', + 'Incompatible types in assignment (expression has type "bytes", variable has type "str")' + ], + 'admin_views': [ + 'Argument 1 to "FileWrapper" has incompatible type "StringIO"; expected "IO[bytes]"', + 'Incompatible types in assignment', + '"object" not callable' + ], 'aggregation': [ 'Incompatible types in assignment (expression has type "QuerySet[Any]", variable has type "List[Any]")', '"as_sql" undefined in superclass' @@ -108,6 +122,9 @@ IGNORED_ERRORS = { 'defer': [ 'Too many arguments for "refresh_from_db" of "Model"' ], + 'dispatch': [ + 'Argument 1 to "connect" of "Signal" has incompatible type "object"; expected "Callable[..., Any]"' + ], 'db_typecasts': [ '"object" has no attribute "__iter__"; maybe "__str__" or "__dir__"? (not iterable)' ], @@ -131,6 +148,10 @@ IGNORED_ERRORS = { 'httpwrappers': [ 'Argument 2 to "appendlist" of "QueryDict" has incompatible type "List[str]"; expected "str"' ], + 'invalid_models_tests': [ + 'Argument "max_length" to "CharField" has incompatible type "str"; expected "Optional[int]"', + 'Argument "choices" to "CharField" has incompatible type "str"' + ], 'model_inheritance_regress': [ 'Incompatible types in assignment (expression has type "List[Supplier]", variable has type "QuerySet[Supplier]")' ], @@ -138,12 +159,37 @@ IGNORED_ERRORS = { '"object" has no attribute "items"', '"Field" has no attribute "many_to_many"' ], + 'model_fields': [ + 'Incompatible types in assignment (expression has type "Type[Person]", variable has type', + 'Unexpected keyword argument "name" for "Person"', + 'Cannot assign multiple types to name "PersonTwoImages" without an explicit "Type[...]" annotation', + ], + 'modeladmin': [ + 'BandAdmin', + ], 'migrate_signals': [ 'Value of type "None" is not indexable', ], + 'migrations': [ + 'FakeMigration', + 'Incompatible types in assignment (expression has type "TextField", base class "Model" ' + + 'defined the type as "Manager[Model]")', + 'Incompatible types in assignment (expression has type "DeleteModel", variable has type "RemoveField")', + 'Argument "bases" to "CreateModel" has incompatible type "Tuple[Type[Mixin], Type[Mixin]]"; ' + + 'expected "Optional[Sequence[Union[Type[Model], str]]]"', + 'Argument 1 to "RunPython" has incompatible type "str"; expected "Callable[..., Any]"', + 'FakeLoader', + ], 'queryset_pickle': [ '"None" has no attribute "somefield"' ], + 'postgres_tests': [ + 'Cannot assign multiple types to name', + 'Incompatible types in assignment (expression has type "Type[Field]', + 'DummyArrayField', + 'DummyJSONField', + 'Argument "encoder" to "JSONField" has incompatible type "DjangoJSONEncoder"; expected "Optional[Type[JSONEncoder]]"' + ], 'requests': [ 'Incompatible types in assignment (expression has type "Dict[str, str]", variable has type "QueryDict")' ], @@ -242,8 +288,8 @@ TESTS_DIRS = [ 'admin_ordering', 'admin_registration', 'admin_scripts', - # TODO: 'admin_utils', - # TODO: 'admin_views', + 'admin_utils', + 'admin_views', 'admin_widgets', 'aggregation', 'aggregation_regress', @@ -282,7 +328,7 @@ TESTS_DIRS = [ 'delete', 'delete_regress', # TODO: 'deprecation', - # TODO: 'dispatch', + 'dispatch', 'distinct_on_fields', 'empty', 'expressions', @@ -321,7 +367,10 @@ TESTS_DIRS = [ 'inline_formsets', 'inspectdb', 'introspection', - # TODO: 'invalid_models_tests', + + # not practical + # 'invalid_models_tests', + 'known_related_objects', # TODO: 'logging_tests', 'lookup', @@ -345,8 +394,10 @@ TESTS_DIRS = [ # TODO: 'middleware_exceptions', 'migrate_signals', 'migration_test_data_persistence', + # wait for redefinitions # TODO: 'migrations', 'migrations2', + # waits for allow redefinitions # TODO: 'model_fields', # TODO: 'model_forms', 'model_formsets', @@ -358,7 +409,8 @@ TESTS_DIRS = [ 'model_options', 'model_package', 'model_regress', - # TODO: 'modeladmin', + # not practical + # 'modeladmin', # TODO: 'multiple_database', 'mutually_referential', 'nested_foreign_keys', @@ -372,7 +424,7 @@ TESTS_DIRS = [ 'ordering', 'prefetch_related', 'pagination', - # TODO: 'postgres_tests', + 'postgres_tests', 'project_template', 'properties', 'proxy_model_inheritance',