Merge pull request #109 from mkurnikov/new-semanal

New semanal support, rewrite to use Django app registry
This commit is contained in:
Maxim Kurnikov
2019-07-25 20:14:03 +03:00
committed by GitHub
102 changed files with 4894 additions and 5338 deletions
+2 -2
View File
@@ -5,7 +5,7 @@ out/
/django
.idea/
.mypy_cache/
django-sources
build/
dist/
pip-wheel-metadata/
pip-wheel-metadata/
.pytest_cache/
+4
View File
@@ -0,0 +1,4 @@
[submodule "django-sources"]
path = django-sources
url = https://github.com/django/django.git
branch = stable/2.2.x
+10 -17
View File
@@ -10,16 +10,18 @@ jobs:
set -e
pytest
- name: Run plugin test suite with python 3.6
python: 3.6
script: |
set -e
pytest
- name: Typecheck Django test suite
- name: Typecheck Django test suite with python 3.7
python: 3.7
script: 'python ./scripts/typecheck_tests.py'
- name: Typecheck Django test suite with python 3.6
python: 3.6
script: 'python ./scripts/typecheck_tests.py'
- name: Mypy for plugin code
python: 3.7
script: 'mypy ./mypy_django_plugin'
- name: Lint with black
python: 3.7
script: 'black --check --line-length=120 django-stubs/'
@@ -36,13 +38,4 @@ before_install: |
# Upgrade pip, setuptools, and wheel
pip install -U pip setuptools wheel
install: |
pip install -r ./dev-requirements.txt
pip install -r ./scripts/typecheck-tests-requirements.txt
#deploy:
# provider: pypi
# user: "mkurnikov"
# password:
# secure: 0E+hkaIdtpEtyL1KZeglunZ5/PKjouFfa8ljakAwoig7VNUL+2sO/bTyg38wRQl0NvzDzEHSMEt1bzg4Tq7b7Zp6nLuewG/w7mGLzqaOlTySiPEfRsg8s6uO2KrTn7g9VhlXH6UtyTXoQdMt6aE8+bt/GmEesanS57NB2mhwmylFgQwlJFu4LfIv/+aGmc4eLeGI2Qhvs9QYf7qvYlLQldgFh8mAckQEEvaBg35sf+puypZgf4nkx1k/dfG9wnFWZU8PJ41LbMw/Wj+k/9NpF8ePwiAr0fvRMErZd8nvoiWjQQjhzgrLVHhXEP5pTHh3zjDuGFMWyKuBhC6WLsG4qOQz/HvxeYvNI+jaTp15BgxtefG/pCNDUl/8GlCde7xVt7xzEcYNJSRaZPY2oofEFSd9qDnr4kqmyCXpNsaHRHvkL61bFjXUcfOsMMYvQCC6N2Jjb7S97RbnDdkOZO/lnFhVANT2rigsaXlSlWyN6f7ApxDNvu6Ehu5yrx6IjlPZJ0sI9vvY3IoS6Fik7w9E6zjNVjbmUn1D4MKFP4v5ppNASOqYcZeLd42j8rjEp0gIc3ccz9aUIT9q8VqSXSdUbqA6SVwvHXIVPxJMXj0bqWBG1iKs0cPBuzRVpRrwkENWCSWElDAewM1qFEnK0LppyoYFbqoQ8F5FG0+re7QttKQ=
# on:
# tags: true
pip install -r ./dev-requirements.txt
+28 -18
View File
@@ -17,6 +17,10 @@ Could be run on earlier versions of Django, but expect some missing imports warn
pip install django-stubs
```
### WARNING: All configuration from pre-1.0.0 versions is dropped, use one below.
### WARNING: 1.0.0 breaks `dmypy`, if you need it, stay on the 0.12.x series.
To make mypy aware of the plugin, you need to add
```
@@ -27,27 +31,33 @@ plugins =
in your `mypy.ini` file.
## Configuration
In order to specify config file, set `MYPY_DJANGO_CONFIG` environment variable with path to the config file. Default is `./mypy_django.ini`
Config file format (.ini):
Plugin requires Django settings module (what you put into `DJANGO_SETTINGS_MODULE` variable) to be specified inside `mypy.ini` file.
```
[mypy_django_plugin]
[mypy]
strict_optional = True
# specify settings module to use for django.conf.settings, this setting
# could also be specified with DJANGO_SETTINGS_MODULE environment variable
# (it also takes priority over config file)
django_settings = mysettings.local
# if True, all unknown settings in django.conf.settings will fallback to Any,
# specify it if your settings are loaded dynamically to avoid false positives
ignore_missing_settings = True
# if True, unknown attributes on Model instances won't produce errors
ignore_missing_model_attributes = True
; this one is new
[mypy.plugins.django-stubs]
django_settings_module = mysettings
```
where `mysettings` is a value of `DJANGO_SETTINGS_MODULE` (with or without quotes)
New implementation uses Django runtime to extract models information, so it will crash, if your installed apps `models.py` is not correct. For this same reason, you cannot use `reveal_type` inside global scope of any Python file that will be executed for `django.setup()`.
In other words, if your `manage.py runserver` crashes, mypy will crash too.
## Notes
Implementation monkey-patches Django to add `__class_getitem__` to the `Manager` class. If you'd use Python3.7 and do that too in your code, you can make things like
```
class MyUserManager(models.Manager['MyUser']):
pass
class MyUser(models.Model):
objects = UserManager()
```
work, which should make a error messages a bit better.
Otherwise, custom type will be created in mypy, named `MyUser__MyUserManager`, which will rewrite base manager as `models.Manager[User]` to make methods like `get_queryset()` and others return properly typed `QuerySet`.
## To get help
+1 -1
View File
@@ -1,5 +1,5 @@
black
pytest-mypy-plugins
pytest-mypy-plugins==1.0.3
flake8
isort==4.3.4
-e .
+1
Submodule django-sources added at 4d6449e125
+8 -7
View File
@@ -1,6 +1,6 @@
import collections
import threading
from typing import Any, Callable, List, Optional, Tuple, Type, Union, Iterable, DefaultDict
from collections import OrderedDict
from typing import Any, Callable, List, Optional, Tuple, Type, Union, Iterable, DefaultDict, Dict
from django.db.migrations.state import AppConfigStub
from django.db.models.base import Model
@@ -8,23 +8,24 @@ from django.db.models.base import Model
from .config import AppConfig
class Apps:
all_models: collections.defaultdict = ...
app_configs: collections.OrderedDict = ...
all_models: "Dict[str, OrderedDict[str, Type[Model]]]" = ...
app_configs: "OrderedDict[str, AppConfig]" = ...
stored_app_configs: List[Any] = ...
apps_ready: bool = ...
ready_event: threading.Event = ...
loading: bool = ...
_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 = ...
def __init__(self, installed_apps: Optional[Union[List[AppConfigStub], List[str], Tuple]] = ...) -> None: ...
def populate(self, installed_apps: Union[List[AppConfigStub], List[str], Tuple] = ...) -> None: ...
def check_apps_ready(self) -> None: ...
def check_models_ready(self) -> None: ...
def get_app_configs(self) -> Iterable[AppConfig]: ...
def get_app_config(self, app_label: str) -> AppConfig: ...
def get_models(self, include_auto_created: bool = ..., include_swapped: bool = ...) -> List[Type[Model]]: ...
def get_model(self, app_label: str, model_name: Optional[str] = ..., require_ready: bool = ...) -> Type[Model]: ...
# it's not possible to support it in plugin properly now
def get_models(self, include_auto_created: bool = ..., include_swapped: bool = ...) -> List[Type[Any]]: ...
def get_model(self, app_label: str, model_name: Optional[str] = ..., require_ready: bool = ...) -> Type[Any]: ...
def register_model(self, app_label: str, model: Type[Model]) -> None: ...
def is_installed(self, app_name: str) -> bool: ...
def get_containing_app_config(self, object_name: str) -> Optional[AppConfig]: ...
+7 -3
View File
@@ -1,12 +1,16 @@
from typing import Any, Optional, Tuple, List, overload
from typing import Any, Optional, Tuple, List, overload, TypeVar
from django.db.models.base import Model
from django.db import models
class BaseUserManager(models.Manager):
_T = TypeVar("_T", bound=Model)
class BaseUserManager(models.Manager[_T]):
@classmethod
def normalize_email(cls, email: Optional[str]) -> str: ...
def make_random_password(self, length: int = ..., allowed_chars: str = ...) -> str: ...
def get_by_natural_key(self, username: Optional[str]) -> AbstractBaseUser: ...
def get_by_natural_key(self, username: Optional[str]) -> _T: ...
class AbstractBaseUser(models.Model):
password: models.CharField = ...
+8 -5
View File
@@ -1,10 +1,11 @@
from typing import Any, Collection, Optional, Set, Tuple, Type, Union
from typing import Any, Collection, Optional, Set, Tuple, Type, TypeVar
from django.contrib.auth.base_user import AbstractBaseUser as AbstractBaseUser, BaseUserManager as BaseUserManager
from django.contrib.auth.validators import UnicodeUsernameValidator
from django.contrib.contenttypes.models import ContentType
from django.db.models.base import Model
from django.db.models.manager import EmptyManager
from django.contrib.auth.validators import UnicodeUsernameValidator
from django.db import models
def update_last_login(sender: Type[AbstractBaseUser], user: AbstractBaseUser, **kwargs: Any) -> None: ...
@@ -27,13 +28,15 @@ class Group(models.Model):
permissions: models.ManyToManyField = models.ManyToManyField(Permission)
def natural_key(self): ...
class UserManager(BaseUserManager):
_T = TypeVar("_T", bound=Model)
class UserManager(BaseUserManager[_T]):
def create_user(
self, username: str, email: Optional[str] = ..., password: Optional[str] = ..., **extra_fields: Any
) -> AbstractUser: ...
) -> _T: ...
def create_superuser(
self, username: str, email: Optional[str], password: Optional[str], **extra_fields: Any
) -> AbstractBaseUser: ...
) -> _T: ...
class PermissionsMixin(models.Model):
is_superuser: models.BooleanField = ...
+9 -26
View File
@@ -7,6 +7,7 @@ from django.db.models.fields.related import ForeignObject
from django.db.models.fields.related_descriptors import ReverseManyToOneDescriptor
from django.db.models.fields.reverse_related import ForeignObjectRel
from django.db.models.expressions import Combinable
from django.db.models.fields import Field, PositiveIntegerField
from django.db.models.fields.mixins import FieldCacheMixin
from django.db.models.query import QuerySet
@@ -14,6 +15,10 @@ from django.db.models.query_utils import FilteredRelation, PathInfo
from django.db.models.sql.where import WhereNode
class GenericForeignKey(FieldCacheMixin):
# django-stubs implementation only fields
_pyi_private_set_type: Union[Any, Combinable]
_pyi_private_get_type: Any
# attributes
auto_created: bool = ...
concrete: bool = ...
editable: bool = ...
@@ -44,36 +49,21 @@ class GenericForeignKey(FieldCacheMixin):
def get_prefetch_queryset(
self, instances: Union[List[Model], QuerySet], queryset: Optional[QuerySet] = ...
) -> Tuple[List[Model], Callable, Callable, bool, str, bool]: ...
def __get__(
self, instance: Optional[Model], cls: Type[Model] = ...
) -> Optional[Union[GenericForeignKey, Model]]: ...
def __set__(self, instance: Model, value: Optional[Model]) -> None: ...
def __get__(self, instance: Optional[Model], cls: Type[Model] = ...) -> Optional[Any]: ...
def __set__(self, instance: Model, value: Optional[Any]) -> None: ...
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 = ...
@@ -90,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): ...
@@ -5,7 +5,7 @@ from django.db.models import Field
from django.db.models.lookups import Transform
from .mixins import CheckFieldDefaultMixin
class JsonAdapter(object):
class JsonAdapter:
encoder: Any = ...
def __init__(self, adapted: Any, dumps: Optional[Any] = ..., encoder: Optional[Any] = ...) -> None: ...
def dumps(self, obj: Any): ...
@@ -22,12 +22,7 @@ class JSONField(CheckFieldDefaultMixin, Field):
encoder: Optional[Type[JSONEncoder]] = ...,
**kwargs: Any
) -> None: ...
def db_type(self, connection: Any): ...
def get_transform(self, name: Any): ...
def get_prep_value(self, value: Any): ...
def validate(self, value: Any, model_instance: Any) -> None: ...
def value_to_string(self, obj: Any): ...
def formfield(self, **kwargs: Any): ...
class KeyTransform(Transform):
operator: str = ...
+1 -10
View File
@@ -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: ...
+3 -3
View File
@@ -1,11 +1,11 @@
from typing import Any, Dict, List, Optional, Tuple, Union
from typing import Any, Dict, List, Tuple, Union
from django.core.management.base import BaseCommand as BaseCommand, CommandError as CommandError
from .base import BaseCommand as BaseCommand, CommandError as CommandError
def find_commands(management_dir: str) -> List[str]: ...
def load_command_class(app_name: str, name: str) -> BaseCommand: ...
def get_commands() -> Dict[str, str]: ...
def call_command(command_name: Union[Tuple[str], BaseCommand, str], *args: Any, **options: Any) -> Optional[str]: ...
def call_command(command_name: Union[Tuple[str], BaseCommand, str], *args: Any, **options: Any) -> str: ...
class ManagementUtility:
argv: List[str] = ...
+11 -16
View File
@@ -1,24 +1,23 @@
from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple, Type, Union, Iterable
from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional, Type, Union
from django.apps.config import AppConfig
from django.db.models.base import Model
from django.db.models.query import QuerySet
from .base import (
Serializer as Serializer,
Deserializer as Deserializer,
SerializerDoesNotExist as SerializerDoesNotExist,
SerializationError as SerializationError,
DeserializationError as DeserializationError,
DeserializedObject,
Deserializer as Deserializer,
M2MDeserializationError as M2MDeserializationError,
SerializationError as SerializationError,
Serializer as Serializer,
SerializerDoesNotExist as SerializerDoesNotExist,
)
BUILTIN_SERIALIZERS: Any
class BadSerializer:
internal_use_only: bool = ...
exception: ModuleNotFoundError = ...
def __init__(self, exception: ImportError) -> None: ...
exception: BaseException = ...
def __init__(self, exception: BaseException) -> None: ...
def __call__(self, *args: Any, **kwargs: Any) -> Any: ...
def register_serializer(format: str, serializer_module: str, serializers: Optional[Dict[str, Any]] = ...) -> None: ...
@@ -27,10 +26,6 @@ def get_serializer(format: str) -> Union[Type[Serializer], BadSerializer]: ...
def get_serializer_formats() -> List[str]: ...
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[bytes, str]]: ...
def deserialize(format: str, stream_or_string: Any, **options: Any) -> Union[Iterator[Any], Deserializer]: ...
def sort_dependencies(
app_list: Union[Iterable[Tuple[AppConfig, None]], Iterable[Tuple[str, Iterable[Type[Model]]]]]
) -> List[Type[Model]]: ...
def serialize(format: str, queryset: Iterable[Model], **options: Any) -> Any: ...
def deserialize(format: str, stream_or_string: Any, **options: Any) -> Iterator[DeserializedObject]: ...
def sort_dependencies(app_list: Iterable[Any]) -> List[Type[Model]]: ...
+17 -14
View File
@@ -1,13 +1,13 @@
from collections import OrderedDict
from datetime import date
from io import BufferedReader, StringIO, TextIOWrapper
from typing import Any, Dict, Iterator, List, Optional, Tuple, Type, Union
from typing import Any, Dict, Iterable, List, Mapping, Optional, Type, Union
from uuid import UUID
from django.core.management.base import OutputWrapper
from django.db.models.base import Model
from django.db.models.fields.related import ForeignKey, ManyToManyField
from django.db.models.query import QuerySet
from django.db.models.fields import Field
class SerializerDoesNotExist(KeyError): ...
class SerializationError(Exception): ...
@@ -43,7 +43,7 @@ class Serializer:
first: bool = ...
def serialize(
self,
queryset: Union[Iterator[Any], List[Model], QuerySet],
queryset: Iterable[Model],
*,
stream: Optional[Any] = ...,
fields: Optional[Any] = ...,
@@ -52,7 +52,7 @@ class Serializer:
progress_output: Optional[Any] = ...,
object_count: int = ...,
**options: Any
) -> Optional[Union[List[OrderedDict], bytes, str]]: ...
) -> Any: ...
def start_serialization(self) -> None: ...
def end_serialization(self) -> None: ...
def start_object(self, obj: Any) -> None: ...
@@ -70,15 +70,18 @@ class Deserializer:
def __next__(self) -> None: ...
class DeserializedObject:
object: Model = ...
m2m_data: Dict[Any, Any] = ...
def __init__(self, obj: Model, m2m_data: Optional[Dict[str, List[int]]] = ...) -> None: ...
object: Any = ...
m2m_data: Dict[str, List[int]] = ...
deferred_fields: Mapping[Field, Any]
def __init__(
self,
obj: Model,
m2m_data: Optional[Dict[str, List[int]]] = ...,
deferred_fields: Optional[Mapping[Field, Any]] = ...,
) -> None: ...
def save(self, save_m2m: bool = ..., using: Optional[str] = ..., **kwargs: Any) -> None: ...
def save_deferred_fields(self, using: Optional[str] = ...) -> None: ...
def build_instance(Model: Type[Model], data: Dict[str, Optional[Union[date, int, str, UUID]]], db: str) -> Model: ...
def deserialize_m2m_values(
field: ManyToManyField, field_value: Union[List[List[str]], List[int]], using: str
) -> List[int]: ...
def deserialize_fk_value(
field: ForeignKey, field_value: Optional[Union[List[str], Tuple[str], int, str]], using: str
) -> Optional[Union[int, str, UUID]]: ...
def deserialize_m2m_values(field: ManyToManyField, field_value: Any, using: str) -> List[Any]: ...
def deserialize_fk_value(field: ForeignKey, field_value: Any, using: str) -> Any: ...
+6 -3
View File
@@ -1,19 +1,22 @@
from typing import Any, Dict, List, Optional, Sequence, Set, Tuple, TypeVar, Union
from typing import Any, Dict, List, Optional, Sequence, Set, Tuple, TypeVar, Union, ClassVar, Type
from django.db.models.manager import Manager
from django.core.checks.messages import CheckMessage
class ModelBase(type): ...
from django.db.models.options import Options
_Self = TypeVar("_Self", bound="Model")
class ModelBase(type): ...
class Model(metaclass=ModelBase):
class DoesNotExist(Exception): ...
class MultipleObjectsReturned(Exception): ...
class Meta: ...
_meta: Any
_default_manager: Manager[Model]
_meta: Options[Any]
objects: Manager[Any]
pk: Any = ...
def __init__(self: _Self, *args, **kwargs) -> None: ...
def delete(self, using: Any = ..., keep_parents: bool = ...) -> Tuple[int, Dict[str, int]]: ...
-1
View File
@@ -52,7 +52,6 @@ class BaseExpression:
is_summary: bool = ...
filterable: bool = ...
window_compatible: bool = ...
output_field: Any
def __init__(self, output_field: Optional[_OutputField] = ...) -> None: ...
def get_db_converters(self, connection: Any) -> List[Callable]: ...
def get_source_expressions(self) -> List[Any]: ...
+50 -4
View File
@@ -1,11 +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
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
@@ -18,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
@@ -30,14 +47,21 @@ class Field(RegisterLookupMixin, Generic[_ST, _GT]):
widget: Widget
help_text: str
db_table: str
attname: str
auto_created: bool
primary_key: bool
remote_field: Field
max_length: Optional[int]
max_length: int
model: Type[Model]
name: str
verbose_name: str
blank: bool = ...
null: bool = ...
editable: bool = ...
choices: Optional[_FieldChoices] = ...
db_column: Optional[str]
column: str
error_messages: _ErrorMessagesToOverride
def __init__(
self,
verbose_name: Optional[Union[str, bytes]] = ...,
@@ -63,7 +87,15 @@ class Field(RegisterLookupMixin, Generic[_ST, _GT]):
error_messages: Optional[_ErrorMessagesToOverride] = ...,
): ...
def __set__(self, instance, value: _ST) -> None: ...
def __get__(self, instance, owner) -> _GT: ...
# class access
@overload
def __get__(self: _T, instance: None, owner) -> _T: ...
# Model instance access
@overload
def __get__(self, instance: Model, owner) -> _GT: ...
# non-Model instances
@overload
def __get__(self: _T, instance, owner) -> _T: ...
def deconstruct(self) -> Any: ...
def set_attributes_from_name(self, name: str) -> None: ...
def db_type(self, connection: Any) -> str: ...
@@ -71,6 +103,7 @@ class Field(RegisterLookupMixin, Generic[_ST, _GT]):
def get_prep_value(self, value: Any) -> Any: ...
def get_internal_type(self) -> str: ...
def formfield(self, **kwargs) -> FormField: ...
def save_form_data(self, instance: Model, data: Any) -> None: ...
def contribute_to_class(self, cls: Type[Model], name: str, private_only: bool = ...) -> None: ...
def to_python(self, value: Any) -> Any: ...
def clean(self, value: Any, model_instance: Optional[Model]) -> Any: ...
@@ -81,7 +114,17 @@ class Field(RegisterLookupMixin, Generic[_ST, _GT]):
limit_choices_to: Optional[Any] = ...,
ordering: Sequence[str] = ...,
) -> Sequence[Union[_Choice, _ChoiceNamedGroup]]: ...
def has_default(self) -> bool: ...
def get_default(self) -> Any: ...
def check(self, **kwargs: Any) -> List[checks.Error]: ...
@property
def validators(self) -> List[_ValidatorCallable]: ...
def validate(self, value: Any, model_instance: Model) -> None: ...
def run_validators(self, value: Any) -> None: ...
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]
@@ -102,6 +145,9 @@ class FloatField(Field[_ST, _GT]):
class DecimalField(Field[_ST, _GT]):
_pyi_private_set_type: Union[str, float, decimal.Decimal, Combinable]
_pyi_private_get_type: decimal.Decimal
# attributes
max_digits: int = ...
decimal_places: int = ...
def __init__(
self,
verbose_name: Optional[Union[str, bytes]] = ...,
+21 -41
View File
@@ -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
@@ -59,15 +45,13 @@ class RelatedField(FieldCacheMixin, Field[_ST, _GT]):
one_to_one: bool = ...
many_to_many: bool = ...
many_to_one: bool = ...
def related_model(self) -> Union[Type[Model], str]: ...
def check(self, **kwargs: Any) -> List[Any]: ...
opts: Any = ...
@property
def related_model(self) -> Type[Model]: ...
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]: ...
@@ -187,7 +171,7 @@ class ManyToManyField(RelatedField[_ST, _GT]):
rel_class: Any = ...
description: Any = ...
has_null_arg: Any = ...
swappable: Any = ...
swappable: bool = ...
def __init__(
self,
to: Union[Type[_T], str],
@@ -222,19 +206,15 @@ 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 m2m_db_table(self) -> str: ...
def m2m_column_name(self) -> str: ...
def m2m_reverse_name(self) -> str: ...
def m2m_reverse_field_name(self) -> str: ...
def m2m_target_field_name(self) -> str: ...
def m2m_reverse_target_field_name(self) -> str: ...
def create_many_to_many_intermediary_model(field: Type[Field], klass: Type[Model]) -> Type[Model]: ...
@@ -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,
+32 -108
View File
@@ -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): ...
+3 -7
View File
@@ -9,9 +9,9 @@ class BaseManager(QuerySet[_T, _T]):
creation_counter: int = ...
auto_created: bool = ...
use_in_migrations: bool = ...
def __new__(cls: Type[BaseManager], *args: Any, **kwargs: Any) -> BaseManager: ...
model: Optional[Any] = ...
name: Optional[Any] = ...
name: str = ...
model: Type[Model] = ...
db: str
def __init__(self) -> None: ...
def deconstruct(self) -> Tuple[bool, str, None, Tuple, Dict[str, int]]: ...
def check(self, **kwargs: Any) -> List[Any]: ...
@@ -35,8 +35,4 @@ class ManagerDescriptor:
def __get__(self, instance: Optional[Model], cls: Type[Model] = ...) -> Manager: ...
class EmptyManager(Manager):
creation_counter: int
name: None
model: Optional[Type[Model]] = ...
def __init__(self, model: Type[Model]) -> None: ...
def get_queryset(self) -> QuerySet: ...
+23 -15
View File
@@ -1,5 +1,5 @@
import collections
from typing import Any, Callable, Dict, Iterator, List, Optional, Set, Tuple, Type, Union
from typing import Any, Callable, Dict, Iterator, List, Optional, Set, Tuple, Type, Union, TypeVar, Generic, Sequence
from django.apps.config import AppConfig
from django.apps.registry import Apps
@@ -29,14 +29,14 @@ def make_immutable_fields_list(
name: str, data: Union[Iterator[Any], List[Union[ArrayField, CIText]], List[Union[Field, FieldCacheMixin]]]
) -> ImmutableList: ...
class Options:
_M = TypeVar("_M", bound=Model)
class Options(Generic[_M]):
base_manager: Manager
concrete_fields: ImmutableList
default_manager: Manager
fields: ImmutableList
local_concrete_fields: ImmutableList
managers: ImmutableList
managers_map: Dict[str, Manager]
related_objects: ImmutableList
FORWARD_PROPERTIES: Any = ...
REVERSE_PROPERTIES: Any = ...
@@ -45,22 +45,22 @@ class Options:
local_many_to_many: List[ManyToManyField] = ...
private_fields: List[Any] = ...
local_managers: List[Manager] = ...
base_manager_name: None = ...
default_manager_name: None = ...
base_manager_name: Optional[str] = ...
default_manager_name: Optional[str] = ...
model_name: Optional[str] = ...
verbose_name: Optional[str] = ...
verbose_name_plural: Optional[str] = ...
db_table: str = ...
ordering: List[str] = ...
ordering: Optional[List[str]] = ...
indexes: List[Any] = ...
unique_together: Union[List[Any], Tuple] = ...
index_together: Union[List[Any], Tuple] = ...
select_on_save: bool = ...
default_permissions: Tuple[str, str, str, str] = ...
default_permissions: Sequence[str] = ...
permissions: List[Any] = ...
object_name: Optional[str] = ...
app_label: str = ...
get_latest_by: None = ...
get_latest_by: Optional[Sequence[str]] = ...
order_with_respect_to: None = ...
db_tablespace: str = ...
required_db_features: List[Any] = ...
@@ -78,7 +78,9 @@ class Options:
auto_created: bool = ...
related_fkey_lookups: List[Any] = ...
apps: Apps = ...
default_related_name: None = ...
default_related_name: Optional[str] = ...
model: Type[Model] = ...
original_attrs: Dict[str, Any] = ...
def __init__(self, meta: Optional[type], app_label: Optional[str] = ...) -> None: ...
@property
def label(self) -> str: ...
@@ -88,8 +90,6 @@ class Options:
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: ...
@@ -100,12 +100,20 @@ class Options:
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]]: ...
@property
def managers(self) -> List[Manager]: ...
@property
def managers_map(self) -> Dict[str, Manager]: ...
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]]: ...
def get_ancestor_link(self, ancestor: Type[Model]) -> Optional[OneToOneField]: ...
def get_path_to_parent(self, parent: Type[Model]) -> List[PathInfo]: ...
def get_path_from_parent(self, parent: Type[Model]) -> List[PathInfo]: ...
def get_fields(self, include_parents: bool = ..., include_hidden: bool = ...) -> ImmutableList: ...
def get_fields(
self, include_parents: bool = ..., include_hidden: bool = ...
) -> List[Union[Field, ForeignObjectRel]]: ...
+13 -7
View File
@@ -78,7 +78,7 @@ class QuerySet(Generic[_T, _Row], Collection[_Row], Sized):
def iterator(self, chunk_size: int = ...) -> Iterator[_Row]: ...
def aggregate(self, *args: Any, **kwargs: Any) -> Dict[str, Any]: ...
def get(self, *args: Any, **kwargs: Any) -> _Row: ...
def create(self, **kwargs: Any) -> _T: ...
def create(self, *args: Any, **kwargs: Any) -> _T: ...
def bulk_create(
self, objs: Iterable[Model], batch_size: Optional[int] = ..., ignore_conflicts: bool = ...
) -> List[_T]: ...
@@ -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]]: ...
@@ -121,9 +125,11 @@ class QuerySet(Generic[_T, _Row], Collection[_Row], Sized):
def select_for_update(self, nowait: bool = ..., skip_locked: bool = ..., of: Tuple = ...) -> QuerySet[_T, _Row]: ...
def select_related(self, *fields: Any) -> QuerySet[_T, _Row]: ...
def prefetch_related(self, *lookups: Any) -> QuerySet[_T, _Row]: ...
def annotate(self, *args: Any, **kwargs: Any) -> QuerySet[_T, _Row]: ...
# TODO: return type
def annotate(self, *args: Any, **kwargs: Any) -> QuerySet[Any, Any]: ...
def order_by(self, *field_names: Any) -> QuerySet[_T, _Row]: ...
def distinct(self, *field_names: Any) -> QuerySet[_T, _Row]: ...
# extra() return type won't be supported any time soon
def extra(
self,
select: Optional[Dict[str, Any]] = ...,
@@ -132,7 +138,7 @@ class QuerySet(Generic[_T, _Row], Collection[_Row], Sized):
tables: Optional[List[str]] = ...,
order_by: Optional[Sequence[str]] = ...,
select_params: Optional[Sequence[Any]] = ...,
) -> QuerySet[_T, _Row]: ...
) -> QuerySet[Any, Any]: ...
def reverse(self) -> QuerySet[_T, _Row]: ...
def defer(self, *fields: Any) -> QuerySet[_T, _Row]: ...
def only(self, *fields: Any) -> QuerySet[_T, _Row]: ...
+2 -2
View File
@@ -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: ...
+10 -6
View File
@@ -2,8 +2,8 @@ import collections
from collections import OrderedDict, namedtuple
from typing import Any, Callable, Dict, Iterator, List, Optional, Sequence, Set, Tuple, Type, Union
from django.db.models.lookups import Lookup
from django.db.models.query_utils import PathInfo
from django.db.models.lookups import Lookup, Transform
from django.db.models.query_utils import PathInfo, RegisterLookupMixin
from django.db.models.sql.compiler import SQLCompiler
from django.db.models.sql.datastructures import BaseTable
from django.db.models.sql.where import WhereNode
@@ -69,6 +69,8 @@ class Query:
explain_query: bool = ...
explain_format: Optional[str] = ...
explain_options: Dict[str, int] = ...
high_mark: Optional[int] = ...
low_mark: int = ...
def __init__(self, model: Optional[Type[Model]], where: Type[WhereNode] = ...) -> None: ...
@property
def extra(self) -> OrderedDict: ...
@@ -98,8 +100,9 @@ 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(
self,
filter_expr: Union[Dict[str, str], Tuple[str, Tuple[int, int]]],
@@ -140,8 +143,6 @@ class Query:
) -> Tuple[WhereNode, Tuple]: ...
def set_empty(self) -> None: ...
def is_empty(self) -> bool: ...
high_mark: Optional[int] = ...
low_mark: int = ...
def set_limits(self, low: Optional[int] = ..., high: Optional[int] = ...) -> None: ...
def clear_limits(self) -> None: ...
def has_limit_one(self) -> bool: ...
@@ -177,7 +178,10 @@ class Query:
def set_values(self, fields: Union[List[str], Tuple]) -> None: ...
def trim_start(self, names_with_path: List[Tuple[str, List[PathInfo]]]) -> Tuple[str, bool]: ...
def is_nullable(self, field: Field) -> bool: ...
def build_lookup(self, lookups: Sequence[str], lhs: Query, rhs: Optional[Query]) -> Lookup: ...
def build_lookup(
self, lookups: Sequence[str], lhs: Union[RegisterLookupMixin, Query], rhs: Optional[Query]
) -> Lookup: ...
def try_transform(self, lhs: Union[RegisterLookupMixin, Query], name: str) -> Transform: ...
class JoinPromoter:
connector: str = ...
+2 -150
View File
@@ -1,104 +1,25 @@
import collections
import uuid
from typing import Any, Dict, Iterable, List, Optional, Set, Tuple, Type, Union
from typing import Any, Dict, Iterable, List, Optional, Tuple, Type, Union
from django.db.models.base import Model
from django.db.models.expressions import Case
from django.db.models.query import QuerySet
from django.db.models.sql.datastructures import BaseTable
from django.db.models.sql.query import Query
from django.db.models.sql.where import WhereNode
from django.db.models.fields import Field
class DeleteQuery(Query):
alias_refcount: Dict[str, int]
annotation_select_mask: None
base_table: str
combinator: None
combinator_all: bool
combined_queries: Tuple
default_cols: bool
default_ordering: bool
deferred_loading: Tuple[frozenset, bool]
distinct: bool
distinct_fields: Tuple
explain_format: None
explain_options: Dict[Any, Any]
explain_query: bool
external_aliases: Set[Any]
extra_order_by: Tuple
extra_select_mask: None
extra_tables: Tuple
filter_is_sticky: bool
group_by: None
high_mark: None
low_mark: int
max_depth: int
model: Type[Model]
order_by: Tuple
select: Tuple
select_for_update: bool
select_for_update_nowait: bool
select_for_update_of: Tuple
select_for_update_skip_locked: bool
select_related: bool
standard_ordering: bool
subq_aliases: frozenset
subquery: bool
table_map: Dict[str, List[str]]
used_aliases: Set[str]
values_select: Tuple
where_class: Type[WhereNode]
compiler: str = ...
where: WhereNode = ...
def do_query(self, table: str, where: WhereNode, using: str) -> int: ...
def delete_batch(self, pk_list: Union[List[int], List[str]], using: str) -> int: ...
def delete_qs(self, query: QuerySet, using: str) -> int: ...
class UpdateQuery(Query):
alias_refcount: Dict[str, int]
annotation_select_mask: Optional[Set[Any]]
base_table: str
combinator: None
combinator_all: bool
combined_queries: Tuple
default_cols: bool
default_ordering: bool
deferred_loading: Tuple[frozenset, bool]
distinct: bool
distinct_fields: Tuple
explain_format: None
explain_options: Dict[Any, Any]
explain_query: bool
external_aliases: Set[Any]
extra_order_by: Tuple
extra_select_mask: Optional[Set[Any]]
extra_tables: Tuple
filter_is_sticky: bool
group_by: Optional[bool]
high_mark: None
low_mark: int
max_depth: int
model: Type[Model]
order_by: Tuple
select: Tuple
select_for_update: bool
select_for_update_nowait: bool
select_for_update_of: Tuple
select_for_update_skip_locked: bool
select_related: bool
standard_ordering: bool
subq_aliases: frozenset
subquery: bool
table_map: Dict[str, List[str]]
used_aliases: Set[str]
values: List[Tuple[Field, Optional[Type[Model]], Union[Case, uuid.UUID]]]
values_select: Tuple
where_class: Type[WhereNode]
compiler: str = ...
def __init__(self, *args: Any, **kwargs: Any) -> None: ...
def clone(self) -> UpdateQuery: ...
where: WhereNode = ...
def update_batch(self, pk_list: List[int], values: Dict[str, Optional[int]], using: str) -> None: ...
def add_update_values(self, values: Dict[str, Any]) -> None: ...
@@ -107,87 +28,18 @@ class UpdateQuery(Query):
def get_related_updates(self) -> List[UpdateQuery]: ...
class InsertQuery(Query):
alias_refcount: Dict[str, int]
annotation_select_mask: None
combinator: None
combinator_all: bool
combined_queries: Tuple
default_cols: bool
default_ordering: bool
deferred_loading: Tuple[frozenset, bool]
distinct: bool
distinct_fields: Tuple
explain_format: None
explain_options: Dict[Any, Any]
explain_query: bool
external_aliases: Set[Any]
extra_order_by: Tuple
extra_select_mask: None
extra_tables: Tuple
filter_is_sticky: bool
group_by: None
high_mark: None
low_mark: int
max_depth: int
model: Type[Model]
order_by: Tuple
select: Tuple
select_for_update: bool
select_for_update_nowait: bool
select_for_update_of: Tuple
select_for_update_skip_locked: bool
select_related: bool
standard_ordering: bool
subquery: bool
table_map: Dict[str, List[str]]
used_aliases: Set[Any]
values_select: Tuple
where: WhereNode
where_class: Type[WhereNode]
compiler: str = ...
fields: Iterable[Field] = ...
objs: List[Model] = ...
def __init__(self, *args: Any, **kwargs: Any) -> None: ...
raw: bool = ...
def __init__(self, *args: Any, **kwargs: Any) -> None: ...
def insert_values(self, fields: Iterable[Field], objs: List[Model], raw: bool = ...) -> None: ...
class AggregateQuery(Query):
alias_refcount: Dict[Any, Any]
annotation_select_mask: None
combinator: None
combinator_all: bool
combined_queries: Tuple
default_cols: bool
default_ordering: bool
deferred_loading: Tuple[frozenset, bool]
distinct: bool
distinct_fields: Tuple
explain_format: None
explain_options: Dict[Any, Any]
explain_query: bool
external_aliases: Set[Any]
extra_order_by: Tuple
extra_select_mask: None
extra_tables: Tuple
filter_is_sticky: bool
group_by: None
high_mark: None
low_mark: int
max_depth: int
model: Type[Model]
order_by: Tuple
select: Tuple
select_for_update: bool
select_for_update_nowait: bool
select_for_update_of: Tuple
select_for_update_skip_locked: bool
select_related: bool
standard_ordering: bool
sub_params: Tuple
table_map: Dict[Any, Any]
used_aliases: Set[Any]
values_select: Tuple
where: WhereNode
where_class: Type[WhereNode]
compiler: str = ...
def add_subquery(self, query: Query, using: str) -> None: ...
+1 -1
View File
@@ -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: ...
+1 -3
View File
@@ -100,8 +100,7 @@ class IntegerField(Field):
label_suffix: Optional[Any] = ...,
) -> None: ...
class FloatField(IntegerField):
def validate(self, value: Optional[float]) -> None: ...
class FloatField(IntegerField): ...
class DecimalField(IntegerField):
decimal_places: Optional[int]
@@ -125,7 +124,6 @@ class DecimalField(IntegerField):
disabled: bool = ...,
label_suffix: Optional[Any] = ...,
) -> None: ...
def validate(self, value: Optional[Decimal]) -> None: ...
class BaseTemporalField(Field):
input_formats: Any = ...
+3
View File
@@ -17,8 +17,10 @@ from typing import (
)
from django.contrib.sessions.backends.base import SessionBase
from django.db.models.base import Model
from django.utils.datastructures import CaseInsensitiveMapping, ImmutableList, MultiValueDict
from django.contrib.auth.base_user import AbstractBaseUser
from django.core.files import uploadedfile, uploadhandler
from django.urls import ResolverMatch
@@ -49,6 +51,7 @@ class HttpRequest(BytesIO):
resolver_match: ResolverMatch = ...
content_type: Optional[str] = ...
content_params: Optional[Dict[str, str]] = ...
user: AbstractBaseUser
session: SessionBase
encoding: Optional[str] = ...
upload_handlers: UploadHandlerList = ...
+1 -1
View File
@@ -22,7 +22,7 @@ def rotate_token(request: HttpRequest) -> None: ...
class CsrfViewMiddleware(MiddlewareMixin):
def process_request(self, request: HttpRequest) -> None: ...
def process_view(
self, request: HttpRequest, callback: Callable, callback_args: Tuple, callback_kwargs: Dict[str, Any]
self, request: HttpRequest, callback: Optional[Callable], callback_args: Tuple, callback_kwargs: Dict[str, Any]
) -> Optional[HttpResponseForbidden]: ...
def process_response(self, request: HttpRequest, response: HttpResponseBase) -> HttpResponseBase: ...
+1 -5
View File
@@ -14,6 +14,7 @@ from django.http import HttpResponse
class ContentNotRenderedError(Exception): ...
class SimpleTemplateResponse(HttpResponse):
content: Any = ...
closed: bool
cookies: SimpleCookie
status_code: int
@@ -35,15 +36,10 @@ class SimpleTemplateResponse(HttpResponse):
@property
def rendered_content(self) -> str: ...
def add_post_render_callback(self, callback: Callable) -> None: ...
content: Any = ...
def render(self) -> SimpleTemplateResponse: ...
@property
def is_rendered(self) -> bool: ...
def __iter__(self) -> Any: ...
@property
def content(self): ...
@content.setter
def content(self, value: Any) -> None: ...
class TemplateResponse(SimpleTemplateResponse):
client: Client
+3 -3
View File
@@ -39,10 +39,10 @@ ungettext = ngettext
def pgettext(context: str, message: str) -> str: ...
def npgettext(context: str, singular: str, plural: str, number: int) -> str: ...
gettext_lazy: Any
gettext_lazy: Callable[[str], str]
ugettext_lazy: Any
pgettext_lazy: Any
ugettext_lazy: Callable[[str], str]
pgettext_lazy: Callable[[str], str]
def ngettext_lazy(singular: Any, plural: Any, number: Optional[Any] = ...): ...
-3
View File
@@ -1,4 +1 @@
[mypy]
[mypy-mypy_django_plugin.monkeypatch.*]
ignore_errors = True
-31
View File
@@ -1,31 +0,0 @@
from configparser import ConfigParser
from typing import Optional
from dataclasses import dataclass
@dataclass
class Config:
django_settings_module: Optional[str] = None
ignore_missing_settings: bool = False
ignore_missing_model_attributes: bool = False
@classmethod
def from_config_file(cls, fpath: str) -> 'Config':
ini_config = ConfigParser()
ini_config.read(fpath)
if not ini_config.has_section('mypy_django_plugin'):
raise ValueError('Invalid config file: no [mypy_django_plugin] section')
django_settings = ini_config.get('mypy_django_plugin', 'django_settings',
fallback=None)
if django_settings:
django_settings = django_settings.strip()
return Config(django_settings_module=django_settings,
ignore_missing_settings=bool(ini_config.get('mypy_django_plugin',
'ignore_missing_settings',
fallback=False)),
ignore_missing_model_attributes=bool(ini_config.get('mypy_django_plugin',
'ignore_missing_model_attributes',
fallback=False)))
+252
View File
@@ -0,0 +1,252 @@
import os
from collections import defaultdict
from contextlib import contextmanager
from typing import TYPE_CHECKING, Dict, Iterator, List, Optional, Tuple, Type
from django.contrib.postgres.fields import ArrayField
from django.core.exceptions import FieldError
from django.db.models.base import Model
from django.db.models.fields import AutoField, CharField, Field
from django.db.models.fields.related import ForeignKey, RelatedField
from django.db.models.fields.reverse_related import ForeignObjectRel
from django.db.models.sql.query import Query
from django.utils.functional import cached_property
from mypy.checker import TypeChecker
from mypy.types import AnyType, Instance
from mypy.types import Type as MypyType
from mypy.types import TypeOfAny
from mypy_django_plugin.lib import helpers
if TYPE_CHECKING:
from django.apps.registry import Apps # noqa: F401
from django.conf import LazySettings # noqa: F401
@contextmanager
def temp_environ():
"""Allow the ability to set os.environ temporarily"""
environ = dict(os.environ)
try:
yield
finally:
os.environ.clear()
os.environ.update(environ)
def initialize_django(settings_module: str) -> Tuple['Apps', 'LazySettings']:
with temp_environ():
os.environ['DJANGO_SETTINGS_MODULE'] = settings_module
def noop_class_getitem(cls, key):
return cls
from django.db import models
models.QuerySet.__class_getitem__ = classmethod(noop_class_getitem) # type: ignore
models.Manager.__class_getitem__ = classmethod(noop_class_getitem) # type: ignore
from django.conf import settings
from django.apps import apps
apps.get_models.cache_clear() # type: ignore
apps.get_swappable_settings_name.cache_clear() # type: ignore
if not settings.configured:
settings._setup()
apps.populate(settings.INSTALLED_APPS)
assert apps.apps_ready
assert settings.configured
return apps, settings
class DjangoFieldsContext:
def __init__(self, django_context: 'DjangoContext') -> None:
self.django_context = django_context
def get_attname(self, field: Field) -> str:
attname = field.attname
return attname
def get_field_nullability(self, field: Field, method: Optional[str]) -> bool:
nullable = field.null
if not nullable and isinstance(field, CharField) and field.blank:
return True
if method == '__init__':
if field.primary_key or isinstance(field, ForeignKey):
return True
if method == 'create':
if isinstance(field, AutoField):
return True
if field.has_default():
return True
return nullable
def get_field_set_type(self, api: TypeChecker, field: Field, *, method: str) -> MypyType:
""" Get a type of __set__ for this specific Django field. """
target_field = field
if isinstance(field, ForeignKey):
target_field = field.target_field
field_info = helpers.lookup_class_typeinfo(api, target_field.__class__)
if field_info is None:
return AnyType(TypeOfAny.from_error)
field_set_type = helpers.get_private_descriptor_type(field_info, '_pyi_private_set_type',
is_nullable=self.get_field_nullability(field, method))
if isinstance(target_field, ArrayField):
argument_field_type = self.get_field_set_type(api, target_field.base_field, method=method)
field_set_type = helpers.convert_any_to_type(field_set_type, argument_field_type)
return field_set_type
def get_field_get_type(self, api: TypeChecker, field: Field, *, method: str) -> MypyType:
""" Get a type of __get__ for this specific Django field. """
field_info = helpers.lookup_class_typeinfo(api, field.__class__)
if field_info is None:
return AnyType(TypeOfAny.unannotated)
is_nullable = self.get_field_nullability(field, method)
if isinstance(field, RelatedField):
if method == 'values':
primary_key_field = self.django_context.get_primary_key_field(field.related_model)
return self.get_field_get_type(api, primary_key_field, method=method)
model_info = helpers.lookup_class_typeinfo(api, field.related_model)
if model_info is None:
return AnyType(TypeOfAny.unannotated)
return Instance(model_info, [])
else:
return helpers.get_private_descriptor_type(field_info, '_pyi_private_get_type',
is_nullable=is_nullable)
class DjangoLookupsContext:
def __init__(self, django_context: 'DjangoContext'):
self.django_context = django_context
def resolve_lookup(self, model_cls: Type[Model], lookup: str) -> Field:
query = Query(model_cls)
lookup_parts, field_parts, is_expression = query.solve_lookup_type(lookup)
if lookup_parts:
raise FieldError('Lookups not supported yet')
currently_observed_model = model_cls
current_field = None
for field_part in field_parts:
if field_part == 'pk':
return self.django_context.get_primary_key_field(currently_observed_model)
current_field = currently_observed_model._meta.get_field(field_part)
if isinstance(current_field, ForeignObjectRel):
currently_observed_model = current_field.related_model
current_field = self.django_context.get_primary_key_field(currently_observed_model)
else:
if isinstance(current_field, RelatedField):
currently_observed_model = current_field.related_model
# if it is None, solve_lookup_type() will fail earlier
assert current_field is not None
return current_field
class DjangoContext:
def __init__(self, django_settings_module: str) -> None:
self.fields_context = DjangoFieldsContext(self)
self.lookups_context = DjangoLookupsContext(self)
self.django_settings_module = django_settings_module
apps, settings = initialize_django(self.django_settings_module)
self.apps_registry = apps
self.settings = settings
@cached_property
def model_modules(self) -> Dict[str, List[Type[Model]]]:
""" All modules that contain Django models. """
if self.apps_registry is None:
return {}
modules: Dict[str, List[Type[Model]]] = defaultdict(list)
for model_cls in self.apps_registry.get_models():
modules[model_cls.__module__].append(model_cls)
return modules
def get_model_class_by_fullname(self, fullname: str) -> Optional[Type[Model]]:
# Returns None if Model is abstract
module, _, model_cls_name = fullname.rpartition('.')
for model_cls in self.model_modules.get(module, []):
if model_cls.__name__ == model_cls_name:
return model_cls
return None
def get_model_fields(self, model_cls: Type[Model]) -> Iterator[Field]:
for field in model_cls._meta.get_fields():
if isinstance(field, Field):
yield field
def get_model_relations(self, model_cls: Type[Model]) -> Iterator[ForeignObjectRel]:
for field in model_cls._meta.get_fields():
if isinstance(field, ForeignObjectRel):
yield field
def get_primary_key_field(self, model_cls: Type[Model]) -> Field:
for field in model_cls._meta.get_fields():
if isinstance(field, Field):
if field.primary_key:
return field
raise ValueError('No primary key defined')
def get_expected_types(self, api: TypeChecker, model_cls: Type[Model], *, method: str) -> Dict[str, MypyType]:
from django.contrib.contenttypes.fields import GenericForeignKey
expected_types = {}
# add pk
primary_key_field = self.get_primary_key_field(model_cls)
field_set_type = self.fields_context.get_field_set_type(api, primary_key_field, method=method)
expected_types['pk'] = field_set_type
for field in model_cls._meta.get_fields():
if isinstance(field, Field):
field_name = field.attname
field_set_type = self.fields_context.get_field_set_type(api, field, method=method)
expected_types[field_name] = field_set_type
if isinstance(field, ForeignKey):
field_name = field.name
foreign_key_info = helpers.lookup_class_typeinfo(api, field.__class__)
if foreign_key_info is None:
# maybe there's no type annotation for the field
expected_types[field_name] = AnyType(TypeOfAny.unannotated)
continue
related_model = field.related_model
if related_model._meta.proxy_for_model:
related_model = field.related_model._meta.proxy_for_model
related_model_info = helpers.lookup_class_typeinfo(api, related_model)
if related_model_info is None:
expected_types[field_name] = AnyType(TypeOfAny.unannotated)
continue
is_nullable = self.fields_context.get_field_nullability(field, method)
foreign_key_set_type = helpers.get_private_descriptor_type(foreign_key_info,
'_pyi_private_set_type',
is_nullable=is_nullable)
model_set_type = helpers.convert_any_to_type(foreign_key_set_type,
Instance(related_model_info, []))
expected_types[field_name] = model_set_type
elif isinstance(field, GenericForeignKey):
# it's generic, so cannot set specific model
field_name = field.name
gfk_info = helpers.lookup_class_typeinfo(api, field.__class__)
gfk_set_type = helpers.get_private_descriptor_type(gfk_info, '_pyi_private_set_type',
is_nullable=True)
expected_types[field_name] = gfk_set_type
return expected_types
-443
View File
@@ -1,443 +0,0 @@
import typing
from collections import OrderedDict
from typing import Dict, Optional, cast
from mypy.mro import calculate_mro
from mypy.nodes import (
GDEF, MDEF, AssignmentStmt, Block, CallExpr, ClassDef, Expression, ImportedName, Lvalue, MypyFile, NameExpr,
SymbolNode, SymbolTable, SymbolTableNode, TypeInfo, Var,
)
from mypy.plugin import CheckerPluginInterface, FunctionContext, MethodContext
from mypy.types import (
AnyType, Instance, NoneTyp, TupleType, Type, TypedDictType, TypeOfAny, TypeVarType, UnionType,
)
if typing.TYPE_CHECKING:
from mypy.checker import TypeChecker
MODEL_CLASS_FULLNAME = 'django.db.models.base.Model'
FIELD_FULLNAME = 'django.db.models.fields.Field'
CHAR_FIELD_FULLNAME = 'django.db.models.fields.CharField'
ARRAY_FIELD_FULLNAME = 'django.contrib.postgres.fields.array.ArrayField'
AUTO_FIELD_FULLNAME = 'django.db.models.fields.AutoField'
GENERIC_FOREIGN_KEY_FULLNAME = 'django.contrib.contenttypes.fields.GenericForeignKey'
FOREIGN_KEY_FULLNAME = 'django.db.models.fields.related.ForeignKey'
ONETOONE_FIELD_FULLNAME = 'django.db.models.fields.related.OneToOneField'
MANYTOMANY_FIELD_FULLNAME = 'django.db.models.fields.related.ManyToManyField'
DUMMY_SETTINGS_BASE_CLASS = 'django.conf._DjangoConfLazyObject'
QUERYSET_CLASS_FULLNAME = 'django.db.models.query.QuerySet'
BASE_MANAGER_CLASS_FULLNAME = 'django.db.models.manager.BaseManager'
MANAGER_CLASS_FULLNAME = 'django.db.models.manager.Manager'
RELATED_MANAGER_CLASS_FULLNAME = 'django.db.models.manager.RelatedManager'
BASEFORM_CLASS_FULLNAME = 'django.forms.forms.BaseForm'
FORM_CLASS_FULLNAME = 'django.forms.forms.Form'
MODELFORM_CLASS_FULLNAME = 'django.forms.models.ModelForm'
FORM_MIXIN_CLASS_FULLNAME = 'django.views.generic.edit.FormMixin'
MANAGER_CLASSES = {
MANAGER_CLASS_FULLNAME,
RELATED_MANAGER_CLASS_FULLNAME,
BASE_MANAGER_CLASS_FULLNAME,
QUERYSET_CLASS_FULLNAME
}
def get_models_file(app_name: str, all_modules: typing.Dict[str, MypyFile]) -> Optional[MypyFile]:
models_module = '.'.join([app_name, 'models'])
return all_modules.get(models_module)
def get_model_fullname(app_name: str, model_name: str,
all_modules: Dict[str, MypyFile]) -> Optional[str]:
models_file = get_models_file(app_name, all_modules)
if models_file is None:
# not imported so far, not supported
return None
sym = models_file.names.get(model_name)
if not sym:
return None
if isinstance(sym.node, TypeInfo):
return sym.node.fullname()
elif isinstance(sym.node, ImportedName):
return sym.node.target_fullname
else:
return None
class SameFileModel(Exception):
def __init__(self, model_cls_name: str):
self.model_cls_name = model_cls_name
class SelfReference(ValueError):
pass
def get_model_fullname_from_string(model_string: str,
all_modules: Dict[str, MypyFile]) -> Optional[str]:
if model_string == 'self':
raise SelfReference()
if '.' not in model_string:
raise SameFileModel(model_string)
app_name, model_name = model_string.split('.')
return get_model_fullname(app_name, model_name, all_modules)
def lookup_fully_qualified_generic(name: str, all_modules: Dict[str, MypyFile]) -> Optional[SymbolNode]:
if '.' not in name:
return None
module, cls_name = name.rsplit('.', 1)
module_file = all_modules.get(module)
if module_file is None:
return None
sym = module_file.names.get(cls_name)
if sym is None:
return None
return sym.node
def parse_bool(expr: Expression) -> Optional[bool]:
if isinstance(expr, NameExpr):
if expr.fullname == 'builtins.True':
return True
if expr.fullname == 'builtins.False':
return False
return None
def reparametrize_instance(instance: Instance, new_args: typing.List[Type]) -> Instance:
return Instance(instance.type, args=new_args,
line=instance.line, column=instance.column)
def fill_typevars_with_any(instance: Instance) -> Instance:
return reparametrize_instance(instance, [AnyType(TypeOfAny.unannotated)])
def extract_typevar_value(tp: Instance, typevar_name: str) -> Type:
if typevar_name in {'_T', '_T_co'}:
if '_T' in tp.type.type_vars:
return tp.args[tp.type.type_vars.index('_T')]
if '_T_co' in tp.type.type_vars:
return tp.args[tp.type.type_vars.index('_T_co')]
return tp.args[tp.type.type_vars.index(typevar_name)]
def fill_typevars(tp: Instance, type_to_fill: Instance) -> Instance:
typevar_values: typing.List[Type] = []
for typevar_arg in type_to_fill.args:
if isinstance(typevar_arg, TypeVarType):
typevar_values.append(extract_typevar_value(tp, typevar_arg.name))
return Instance(type_to_fill.type, typevar_values)
def get_argument_by_name(ctx: typing.Union[FunctionContext, MethodContext], name: str) -> Optional[Expression]:
"""Return the expression for the specific argument.
This helper should only be used with non-star arguments.
"""
if name not in ctx.callee_arg_names:
return None
idx = ctx.callee_arg_names.index(name)
args = ctx.args[idx]
if len(args) != 1:
# Either an error or no value passed.
return None
return args[0]
def get_argument_type_by_name(ctx: typing.Union[FunctionContext, MethodContext], name: str) -> Optional[Type]:
"""Return the type for the specific argument.
This helper should only be used with non-star arguments.
"""
if name not in ctx.callee_arg_names:
return None
idx = ctx.callee_arg_names.index(name)
arg_types = ctx.arg_types[idx]
if len(arg_types) != 1:
# Either an error or no value passed.
return None
return arg_types[0]
def get_setting_expr(api: 'TypeChecker', setting_name: str) -> Optional[Expression]:
try:
settings_sym = api.modules['django.conf'].names['settings']
except KeyError:
return None
settings_type: TypeInfo = settings_sym.type.type
auth_user_model_sym = settings_type.get(setting_name)
if not auth_user_model_sym:
return None
module, _, name = auth_user_model_sym.fullname.rpartition('.')
if module not in api.modules:
return None
module_file = api.modules.get(module)
for name_expr, value_expr in iter_over_assignments(module_file):
if isinstance(name_expr, NameExpr) and name_expr.name == setting_name:
return value_expr
return None
def iter_over_assignments(class_or_module: typing.Union[ClassDef, MypyFile]
) -> typing.Iterator[typing.Tuple[Lvalue, Expression]]:
if isinstance(class_or_module, ClassDef):
statements = class_or_module.defs.body
else:
statements = class_or_module.defs
for stmt in statements:
if not isinstance(stmt, AssignmentStmt):
continue
if len(stmt.lvalues) > 1:
# not supported yet
continue
yield stmt.lvalues[0], stmt.rvalue
def extract_field_setter_type(tp: Instance) -> Optional[Type]:
""" Extract __set__ value of a field. """
if tp.type.has_base(FIELD_FULLNAME):
return tp.args[0]
# GenericForeignKey
if tp.type.has_base(GENERIC_FOREIGN_KEY_FULLNAME):
return AnyType(TypeOfAny.special_form)
return None
def extract_field_getter_type(tp: Type) -> Optional[Type]:
""" Extract return type of __get__ of subclass of Field"""
if not isinstance(tp, Instance):
return None
if tp.type.has_base(FIELD_FULLNAME):
return tp.args[1]
# GenericForeignKey
if tp.type.has_base(GENERIC_FOREIGN_KEY_FULLNAME):
return AnyType(TypeOfAny.special_form)
return None
def get_django_metadata(model_info: TypeInfo) -> Dict[str, typing.Any]:
return model_info.metadata.setdefault('django', {})
def get_related_field_primary_key_names(base_model: TypeInfo) -> typing.List[str]:
return get_django_metadata(base_model).setdefault('related_field_primary_keys', [])
def get_fields_metadata(model: TypeInfo) -> Dict[str, typing.Any]:
return get_django_metadata(model).setdefault('fields', {})
def get_lookups_metadata(model: TypeInfo) -> Dict[str, typing.Any]:
return get_django_metadata(model).setdefault('lookups', {})
def get_related_managers_metadata(model: TypeInfo) -> Dict[str, typing.Any]:
return get_django_metadata(model).setdefault('related_managers', {})
def extract_explicit_set_type_of_model_primary_key(model: TypeInfo) -> Optional[Type]:
"""
If field with primary_key=True is set on the model, extract its __set__ type.
"""
for field_name, props in get_fields_metadata(model).items():
is_primary_key = props.get('primary_key', False)
if is_primary_key:
return extract_field_setter_type(model.names[field_name].type)
return None
def extract_primary_key_type_for_get(model: TypeInfo) -> Optional[Type]:
for field_name, props in get_fields_metadata(model).items():
is_primary_key = props.get('primary_key', False)
if is_primary_key:
return extract_field_getter_type(model.names[field_name].type)
return None
def make_optional(typ: Type):
return UnionType.make_union([typ, NoneTyp()])
def make_required(typ: Type) -> Type:
if not isinstance(typ, UnionType):
return typ
items = [item for item in typ.items if not isinstance(item, NoneTyp)]
# will reduce to Instance, if only one item
return UnionType.make_union(items)
def is_optional(typ: Type) -> bool:
if not isinstance(typ, UnionType):
return False
return any([isinstance(item, NoneTyp) for item in typ.items])
def has_any_of_bases(info: TypeInfo, bases: typing.Sequence[str]) -> bool:
for base_fullname in bases:
if info.has_base(base_fullname):
return True
return False
def is_none_expr(expr: Expression) -> bool:
return isinstance(expr, NameExpr) and expr.fullname == 'builtins.None'
def get_nested_meta_node_for_current_class(info: TypeInfo) -> Optional[TypeInfo]:
metaclass_sym = info.names.get('Meta')
if metaclass_sym is not None and isinstance(metaclass_sym.node, TypeInfo):
return metaclass_sym.node
return None
def get_assigned_value_for_class(type_info: TypeInfo, name: str) -> Optional[Expression]:
for lvalue, rvalue in iter_over_assignments(type_info.defn):
if isinstance(lvalue, NameExpr) and lvalue.name == name:
return rvalue
return None
def is_field_nullable(model: TypeInfo, field_name: str) -> bool:
return get_fields_metadata(model).get(field_name, {}).get('null', False)
def is_foreign_key_like(t: Type) -> bool:
if not isinstance(t, Instance):
return False
return has_any_of_bases(t.type, (FOREIGN_KEY_FULLNAME, ONETOONE_FIELD_FULLNAME))
def build_class_with_annotated_fields(api: 'TypeChecker', base: Type, fields: 'OrderedDict[str, Type]',
name: str) -> Instance:
"""Build an Instance with `name` that contains the specified `fields` as attributes and extends `base`."""
# Credit: This code is largely copied/modified from TypeChecker.intersect_instance_callable and
# NamedTupleAnalyzer.build_namedtuple_typeinfo
from mypy.checker import gen_unique_name
cur_module = cast(MypyFile, api.scope.stack[0])
gen_name = gen_unique_name(name, cur_module.names)
cdef = ClassDef(name, Block([]))
cdef.fullname = cur_module.fullname() + '.' + gen_name
info = TypeInfo(SymbolTable(), cdef, cur_module.fullname())
cdef.info = info
info.bases = [base]
def add_field(var: Var, is_initialized_in_class: bool = False,
is_property: bool = False) -> None:
var.info = info
var.is_initialized_in_class = is_initialized_in_class
var.is_property = is_property
var._fullname = '%s.%s' % (info.fullname(), var.name())
info.names[var.name()] = SymbolTableNode(MDEF, var)
vars = [Var(item, typ) for item, typ in fields.items()]
for var in vars:
add_field(var, is_property=True)
calculate_mro(info)
info.calculate_metaclass_type()
cur_module.names[gen_name] = SymbolTableNode(GDEF, info, plugin_generated=True)
return Instance(info, [])
def make_named_tuple(api: 'TypeChecker', fields: 'OrderedDict[str, Type]', name: str) -> Type:
if not fields:
# No fields specified, so fallback to a subclass of NamedTuple that allows
# __getattr__ / __setattr__ for any attribute name.
fallback = api.named_generic_type('django._NamedTupleAnyAttr', [])
else:
fallback = build_class_with_annotated_fields(
api=api,
base=api.named_generic_type('typing.NamedTuple', []),
fields=fields,
name=name
)
return TupleType(list(fields.values()), fallback=fallback)
def make_typeddict(api: CheckerPluginInterface, fields: 'OrderedDict[str, Type]',
required_keys: typing.Set[str]) -> TypedDictType:
object_type = api.named_generic_type('mypy_extensions._TypedDict', [])
typed_dict_type = TypedDictType(fields, required_keys=required_keys, fallback=object_type)
return typed_dict_type
def make_tuple(api: 'TypeChecker', fields: typing.List[Type]) -> TupleType:
implicit_any = AnyType(TypeOfAny.special_form)
fallback = api.named_generic_type('builtins.tuple', [implicit_any])
return TupleType(fields, fallback=fallback)
def get_private_descriptor_type(type_info: TypeInfo, private_field_name: str, is_nullable: bool) -> Type:
node = type_info.get(private_field_name).node
if isinstance(node, Var):
descriptor_type = node.type
if is_nullable:
descriptor_type = make_optional(descriptor_type)
return descriptor_type
return AnyType(TypeOfAny.unannotated)
def iter_over_classdefs(module_file: MypyFile) -> typing.Iterator[ClassDef]:
for defn in module_file.defs:
if isinstance(defn, ClassDef):
yield defn
def iter_call_assignments(klass: ClassDef) -> typing.Iterator[typing.Tuple[Lvalue, CallExpr]]:
for lvalue, rvalue in iter_over_assignments(klass):
if isinstance(rvalue, CallExpr):
yield lvalue, rvalue
def get_related_manager_type_from_metadata(model_info: TypeInfo, related_manager_name: str,
api: CheckerPluginInterface) -> Optional[Instance]:
related_manager_metadata = get_related_managers_metadata(model_info)
if not related_manager_metadata:
return None
if related_manager_name not in related_manager_metadata:
return None
manager_class_name = related_manager_metadata[related_manager_name]['manager']
of = related_manager_metadata[related_manager_name]['of']
of_types = []
for of_type_name in of:
if of_type_name == 'any':
of_types.append(AnyType(TypeOfAny.implementation_artifact))
else:
try:
of_type = api.named_generic_type(of_type_name, [])
except AssertionError:
# Internal error: attempted lookup of unknown name
of_type = AnyType(TypeOfAny.implementation_artifact)
of_types.append(of_type)
return api.named_generic_type(manager_class_name, of_types)
def get_primary_key_field_name(model_info: TypeInfo) -> Optional[str]:
for base in model_info.mro:
fields = get_fields_metadata(base)
for field_name, field_props in fields.items():
is_primary_key = field_props.get('primary_key', False)
if is_primary_key:
return field_name
return None
View File
+39
View File
@@ -0,0 +1,39 @@
MODEL_CLASS_FULLNAME = 'django.db.models.base.Model'
FIELD_FULLNAME = 'django.db.models.fields.Field'
CHAR_FIELD_FULLNAME = 'django.db.models.fields.CharField'
ARRAY_FIELD_FULLNAME = 'django.contrib.postgres.fields.array.ArrayField'
AUTO_FIELD_FULLNAME = 'django.db.models.fields.AutoField'
GENERIC_FOREIGN_KEY_FULLNAME = 'django.contrib.contenttypes.fields.GenericForeignKey'
FOREIGN_KEY_FULLNAME = 'django.db.models.fields.related.ForeignKey'
ONETOONE_FIELD_FULLNAME = 'django.db.models.fields.related.OneToOneField'
MANYTOMANY_FIELD_FULLNAME = 'django.db.models.fields.related.ManyToManyField'
DUMMY_SETTINGS_BASE_CLASS = 'django.conf._DjangoConfLazyObject'
QUERYSET_CLASS_FULLNAME = 'django.db.models.query.QuerySet'
BASE_MANAGER_CLASS_FULLNAME = 'django.db.models.manager.BaseManager'
MANAGER_CLASS_FULLNAME = 'django.db.models.manager.Manager'
RELATED_MANAGER_CLASS_FULLNAME = 'django.db.models.manager.RelatedManager'
BASEFORM_CLASS_FULLNAME = 'django.forms.forms.BaseForm'
FORM_CLASS_FULLNAME = 'django.forms.forms.Form'
MODELFORM_CLASS_FULLNAME = 'django.forms.models.ModelForm'
FORM_MIXIN_CLASS_FULLNAME = 'django.views.generic.edit.FormMixin'
MANAGER_CLASSES = {
MANAGER_CLASS_FULLNAME,
RELATED_MANAGER_CLASS_FULLNAME,
BASE_MANAGER_CLASS_FULLNAME,
# QUERYSET_CLASS_FULLNAME
}
RELATED_FIELDS_CLASSES = {
FOREIGN_KEY_FULLNAME,
ONETOONE_FIELD_FULLNAME,
MANYTOMANY_FIELD_FULLNAME
}
MIGRATION_CLASS_FULLNAME = 'django.db.migrations.migration.Migration'
OPTIONS_CLASS_FULLNAME = 'django.db.models.options.Options'
HTTPREQUEST_CLASS_FULLNAME = 'django.http.request.HttpRequest'
+253
View File
@@ -0,0 +1,253 @@
from collections import OrderedDict
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Union, cast
from mypy import checker
from mypy.checker import TypeChecker
from mypy.mro import calculate_mro
from mypy.nodes import (
GDEF, MDEF, Block, ClassDef, Expression, MemberExpr, MypyFile, NameExpr, StrExpr, SymbolNode, SymbolTable,
SymbolTableNode, TypeInfo, Var,
)
from mypy.plugin import (
AttributeContext, CheckerPluginInterface, FunctionContext, MethodContext,
)
from mypy.types import AnyType, Instance, NoneTyp, TupleType
from mypy.types import Type as MypyType
from mypy.types import TypedDictType, TypeOfAny, UnionType
if TYPE_CHECKING:
from mypy_django_plugin.django.context import DjangoContext
def get_django_metadata(model_info: TypeInfo) -> Dict[str, Any]:
return model_info.metadata.setdefault('django', {})
class IncompleteDefnException(Exception):
pass
def lookup_fully_qualified_sym(fullname: str, all_modules: Dict[str, MypyFile]) -> Optional[SymbolTableNode]:
if '.' not in fullname:
return None
module, cls_name = fullname.rsplit('.', 1)
module_file = all_modules.get(module)
if module_file is None:
return None
sym = module_file.names.get(cls_name)
if sym is None:
return None
return sym
def lookup_fully_qualified_generic(name: str, all_modules: Dict[str, MypyFile]) -> Optional[SymbolNode]:
sym = lookup_fully_qualified_sym(name, all_modules)
if sym is None:
return None
return sym.node
def lookup_fully_qualified_typeinfo(api: TypeChecker, fullname: str) -> Optional[TypeInfo]:
node = lookup_fully_qualified_generic(fullname, api.modules)
if not isinstance(node, TypeInfo):
return None
return node
def lookup_class_typeinfo(api: TypeChecker, klass: type) -> Optional[TypeInfo]:
fullname = get_class_fullname(klass)
field_info = lookup_fully_qualified_typeinfo(api, fullname)
return field_info
def reparametrize_instance(instance: Instance, new_args: List[MypyType]) -> Instance:
return Instance(instance.type, args=new_args,
line=instance.line, column=instance.column)
def get_class_fullname(klass: type) -> str:
return klass.__module__ + '.' + klass.__qualname__
def get_call_argument_by_name(ctx: Union[FunctionContext, MethodContext], name: str) -> Optional[Expression]:
"""
Return the expression for the specific argument.
This helper should only be used with non-star arguments.
"""
if name not in ctx.callee_arg_names:
return None
idx = ctx.callee_arg_names.index(name)
args = ctx.args[idx]
if len(args) != 1:
# Either an error or no value passed.
return None
return args[0]
def get_call_argument_type_by_name(ctx: Union[FunctionContext, MethodContext], name: str) -> Optional[MypyType]:
"""Return the type for the specific argument.
This helper should only be used with non-star arguments.
"""
if name not in ctx.callee_arg_names:
return None
idx = ctx.callee_arg_names.index(name)
arg_types = ctx.arg_types[idx]
if len(arg_types) != 1:
# Either an error or no value passed.
return None
return arg_types[0]
def make_optional(typ: MypyType) -> MypyType:
return UnionType.make_union([typ, NoneTyp()])
def parse_bool(expr: Expression) -> Optional[bool]:
if isinstance(expr, NameExpr):
if expr.fullname == 'builtins.True':
return True
if expr.fullname == 'builtins.False':
return False
return None
def has_any_of_bases(info: TypeInfo, bases: Set[str]) -> bool:
for base_fullname in bases:
if info.has_base(base_fullname):
return True
return False
def get_private_descriptor_type(type_info: TypeInfo, private_field_name: str, is_nullable: bool) -> MypyType:
""" Return declared type of type_info's private_field_name (used for private Field attributes)"""
sym = type_info.get(private_field_name)
if sym is None:
return AnyType(TypeOfAny.unannotated)
node = sym.node
if isinstance(node, Var):
descriptor_type = node.type
if descriptor_type is None:
return AnyType(TypeOfAny.unannotated)
if is_nullable:
descriptor_type = make_optional(descriptor_type)
return descriptor_type
return AnyType(TypeOfAny.unannotated)
def get_nested_meta_node_for_current_class(info: TypeInfo) -> Optional[TypeInfo]:
metaclass_sym = info.names.get('Meta')
if metaclass_sym is not None and isinstance(metaclass_sym.node, TypeInfo):
return metaclass_sym.node
return None
def add_new_class_for_module(module: MypyFile, name: str, bases: List[Instance],
fields: 'OrderedDict[str, MypyType]') -> TypeInfo:
new_class_unique_name = checker.gen_unique_name(name, module.names)
# make new class expression
classdef = ClassDef(new_class_unique_name, Block([]))
classdef.fullname = module.fullname() + '.' + new_class_unique_name
# make new TypeInfo
new_typeinfo = TypeInfo(SymbolTable(), classdef, module.fullname())
new_typeinfo.bases = bases
calculate_mro(new_typeinfo)
new_typeinfo.calculate_metaclass_type()
def add_field_to_new_typeinfo(var: Var, is_initialized_in_class: bool = False,
is_property: bool = False) -> None:
var.info = new_typeinfo
var.is_initialized_in_class = is_initialized_in_class
var.is_property = is_property
var._fullname = new_typeinfo.fullname() + '.' + var.name()
new_typeinfo.names[var.name()] = SymbolTableNode(MDEF, var)
# add fields
var_items = [Var(item, typ) for item, typ in fields.items()]
for var_item in var_items:
add_field_to_new_typeinfo(var_item, is_property=True)
classdef.info = new_typeinfo
module.names[new_class_unique_name] = SymbolTableNode(GDEF, new_typeinfo, plugin_generated=True)
return new_typeinfo
def get_current_module(api: TypeChecker) -> MypyFile:
current_module = None
for item in reversed(api.scope.stack):
if isinstance(item, MypyFile):
current_module = item
break
assert current_module is not None
return current_module
def make_oneoff_named_tuple(api: TypeChecker, name: str, fields: 'OrderedDict[str, MypyType]') -> TupleType:
current_module = get_current_module(api)
namedtuple_info = add_new_class_for_module(current_module, name,
bases=[api.named_generic_type('typing.NamedTuple', [])],
fields=fields)
return TupleType(list(fields.values()), fallback=Instance(namedtuple_info, []))
def make_tuple(api: 'TypeChecker', fields: List[MypyType]) -> TupleType:
# fallback for tuples is any builtins.tuple instance
fallback = api.named_generic_type('builtins.tuple',
[AnyType(TypeOfAny.special_form)])
return TupleType(fields, fallback=fallback)
def convert_any_to_type(typ: MypyType, referred_to_type: MypyType) -> MypyType:
if isinstance(typ, UnionType):
converted_items = []
for item in typ.items:
converted_items.append(convert_any_to_type(item, referred_to_type))
return UnionType.make_union(converted_items,
line=typ.line, column=typ.column)
if isinstance(typ, Instance):
args = []
for default_arg in typ.args:
if isinstance(default_arg, AnyType):
args.append(referred_to_type)
else:
args.append(default_arg)
return reparametrize_instance(typ, args)
if isinstance(typ, AnyType):
return referred_to_type
return typ
def make_typeddict(api: CheckerPluginInterface, fields: 'OrderedDict[str, MypyType]',
required_keys: Set[str]) -> TypedDictType:
object_type = api.named_generic_type('mypy_extensions._TypedDict', [])
typed_dict_type = TypedDictType(fields, required_keys=required_keys, fallback=object_type)
return typed_dict_type
def resolve_string_attribute_value(attr_expr: Expression, ctx: Union[FunctionContext, MethodContext],
django_context: 'DjangoContext') -> Optional[str]:
if isinstance(attr_expr, StrExpr):
return attr_expr.value
# support extracting from settings, in general case it's unresolvable yet
if isinstance(attr_expr, MemberExpr):
member_name = attr_expr.name
if isinstance(attr_expr.expr, NameExpr) and attr_expr.expr.fullname == 'django.conf.settings':
if hasattr(django_context.settings, member_name):
return getattr(django_context.settings, member_name)
ctx.api.fail(f'Expression of type {type(attr_expr).__name__!r} is not supported', ctx.context)
return None
def get_typechecker_api(ctx: Union[AttributeContext, MethodContext, FunctionContext]) -> TypeChecker:
if not isinstance(ctx.api, TypeChecker):
raise ValueError('Not a TypeChecker')
return cast(TypeChecker, ctx.api)
-159
View File
@@ -1,159 +0,0 @@
from typing import List, Union
import dataclasses
from mypy.nodes import TypeInfo
from mypy.plugin import CheckerPluginInterface
from mypy.types import Instance, Type
from mypy_django_plugin import helpers
@dataclasses.dataclass
class RelatedModelNode:
typ: Instance
is_nullable: bool
@dataclasses.dataclass
class FieldNode:
typ: Type
LookupNode = Union[RelatedModelNode, FieldNode]
class LookupException(Exception):
pass
def resolve_lookup(api: CheckerPluginInterface, model_type_info: TypeInfo, lookup: str) -> List[LookupNode]:
"""Resolve a lookup str to a list of LookupNodes.
Each node represents a part of the lookup (separated by "__"), in order.
Each node is the Model or Field that was resolved.
Raises LookupException if there were any issues resolving the lookup.
"""
lookup_parts = lookup.split("__")
nodes = []
while lookup_parts:
lookup_part = lookup_parts.pop(0)
if not nodes:
current_node = None
else:
current_node = nodes[-1]
if current_node is None:
new_node = resolve_model_lookup(api, model_type_info, lookup_part)
elif isinstance(current_node, RelatedModelNode):
new_node = resolve_model_lookup(api, current_node.typ.type, lookup_part)
elif isinstance(current_node, FieldNode):
raise LookupException(f"Field lookups not yet supported for lookup {lookup}")
else:
raise LookupException(f"Unsupported node type: {type(current_node)}")
nodes.append(new_node)
return nodes
def resolve_model_pk_lookup(api: CheckerPluginInterface, model_type_info: TypeInfo) -> LookupNode:
# Primary keys are special-cased
primary_key_type = helpers.extract_primary_key_type_for_get(model_type_info)
if primary_key_type:
return FieldNode(primary_key_type)
else:
# No PK, use the get type for AutoField as PK type.
autofield_info = api.lookup_typeinfo('django.db.models.fields.AutoField')
pk_type = helpers.get_private_descriptor_type(autofield_info, '_pyi_private_get_type',
is_nullable=False)
return FieldNode(pk_type)
def resolve_model_lookup(api: CheckerPluginInterface, model_type_info: TypeInfo,
lookup: str) -> LookupNode:
"""Resolve a lookup on the given model."""
if lookup == 'pk':
return resolve_model_pk_lookup(api, model_type_info)
field_name = get_actual_field_name_for_lookup_field(lookup, model_type_info)
field_node = model_type_info.get(field_name)
if not field_node:
raise LookupException(
f'When resolving lookup "{lookup}", field "{field_name}" was not found in model {model_type_info.name()}')
if field_name.endswith('_id'):
field_name_without_id = field_name.rstrip('_id')
foreign_key_field = model_type_info.get(field_name_without_id)
if foreign_key_field is not None and helpers.is_foreign_key_like(foreign_key_field.type):
# Hack: If field ends with '_id' and there is a model field without the '_id' suffix, then use that field.
field_node = foreign_key_field
field_name = field_name_without_id
field_node_type = field_node.type
if field_node_type is None or not isinstance(field_node_type, Instance):
raise LookupException(
f'When resolving lookup "{lookup}", could not determine type for {model_type_info.name()}.{field_name}')
if field_node_type.type.fullname() == 'builtins.object':
# could be related manager
related_manager_type = helpers.get_related_manager_type_from_metadata(model_type_info, field_name, api)
if related_manager_type:
model_arg = related_manager_type.args[0]
if not isinstance(model_arg, Instance):
raise LookupException(
f'When resolving lookup "{lookup}", could not determine type '
f'for {model_type_info.name()}.{field_name}')
return RelatedModelNode(typ=model_arg, is_nullable=False)
if helpers.is_foreign_key_like(field_node_type):
field_type = helpers.extract_field_getter_type(field_node_type)
is_nullable = helpers.is_optional(field_type)
if is_nullable:
# type is always non-optional
field_type = helpers.make_required(field_type)
if isinstance(field_type, Instance):
return RelatedModelNode(typ=field_type, is_nullable=is_nullable)
else:
raise LookupException(f"Not an instance for field {field_type} lookup {lookup}")
field_type = helpers.extract_field_getter_type(field_node_type)
if field_type:
return FieldNode(typ=field_type)
# Not a Field
if field_name == 'id':
# If no 'id' field was found, use an int
return FieldNode(api.named_generic_type('builtins.int', []))
raise LookupException(
f'When resolving lookup {lookup!r}, could not determine type for {model_type_info.name()}.{field_name}')
def get_actual_field_name_for_lookup_field(lookup: str, model_type_info: TypeInfo) -> str:
"""Attempt to find out the real field name if this lookup is a related_query_name (for reverse relations).
If it's not, return the original lookup.
"""
lookups_metadata = helpers.get_lookups_metadata(model_type_info)
lookup_metadata = lookups_metadata.get(lookup)
if lookup_metadata is None:
# If not found on current model, look in all bases for their lookup metadata
for base in model_type_info.mro:
lookups_metadata = helpers.get_lookups_metadata(base)
lookup_metadata = lookups_metadata.get(lookup)
if lookup_metadata:
break
if not lookup_metadata:
lookup_metadata = {}
related_name = lookup_metadata.get('related_query_name_target', None)
if related_name:
# If the lookup is a related lookup, then look at the field specified by related_name.
# This is to support if related_query_name is set and differs from.
field_name = related_name
else:
field_name = lookup
return field_name
+152 -204
View File
@@ -1,299 +1,247 @@
import os
import configparser
from functools import partial
from typing import Callable, Dict, List, Optional, Tuple, cast
from typing import Callable, Dict, List, Optional, Tuple
from mypy.nodes import MypyFile, NameExpr, TypeInfo
from django.db.models.fields.related import RelatedField
from mypy.errors import Errors
from mypy.nodes import MypyFile, TypeInfo
from mypy.options import Options
from mypy.plugin import (
AnalyzeTypeContext, AttributeContext, ClassDefContext, FunctionContext, MethodContext, Plugin,
)
from mypy.types import AnyType, Instance, Type, TypeOfAny
from mypy.types import Type as MypyType
from mypy_django_plugin import helpers
from mypy_django_plugin.config import Config
from mypy_django_plugin.transformers import fields, init_create
from mypy_django_plugin.transformers.forms import (
extract_proper_type_for_get_form, extract_proper_type_for_get_form_class, make_meta_nested_class_inherit_from_any,
)
from mypy_django_plugin.transformers.migrations import (
determine_model_cls_from_string_for_migrations,
from mypy_django_plugin.django.context import DjangoContext
from mypy_django_plugin.lib import fullnames, helpers
from mypy_django_plugin.transformers import (
fields, forms, init_create, meta, querysets, request, settings,
)
from mypy_django_plugin.transformers.models import process_model_class
from mypy_django_plugin.transformers.queryset import (
extract_proper_type_for_queryset_values, extract_proper_type_queryset_values_list,
set_first_generic_param_as_default_for_second,
)
from mypy_django_plugin.transformers.related import (
determine_type_of_related_manager, extract_and_return_primary_key_of_bound_related_field_parameter,
)
from mypy_django_plugin.transformers.settings import (
get_type_of_setting, return_user_model_hook,
)
def transform_model_class(ctx: ClassDefContext, ignore_missing_model_attributes: bool) -> None:
try:
sym = ctx.api.lookup_fully_qualified(helpers.MODEL_CLASS_FULLNAME)
except KeyError:
# models.Model is not loaded, skip metadata model write
pass
def transform_model_class(ctx: ClassDefContext,
django_context: DjangoContext) -> None:
sym = ctx.api.lookup_fully_qualified_or_none(fullnames.MODEL_CLASS_FULLNAME)
if sym is not None and isinstance(sym.node, TypeInfo):
helpers.get_django_metadata(sym.node)['model_bases'][ctx.cls.fullname] = 1
else:
if sym is not None and isinstance(sym.node, TypeInfo):
helpers.get_django_metadata(sym.node)['model_bases'][ctx.cls.fullname] = 1
if not ctx.api.final_iteration:
ctx.api.defer()
return
process_model_class(ctx, ignore_missing_model_attributes)
process_model_class(ctx, django_context)
def transform_manager_class(ctx: ClassDefContext) -> None:
sym = ctx.api.lookup_fully_qualified_or_none(helpers.MANAGER_CLASS_FULLNAME)
def transform_form_class(ctx: ClassDefContext) -> None:
sym = ctx.api.lookup_fully_qualified_or_none(fullnames.BASEFORM_CLASS_FULLNAME)
if sym is not None and isinstance(sym.node, TypeInfo):
helpers.get_django_metadata(sym.node)['baseform_bases'][ctx.cls.fullname] = 1
forms.make_meta_nested_class_inherit_from_any(ctx)
def add_new_manager_base(ctx: ClassDefContext) -> None:
sym = ctx.api.lookup_fully_qualified_or_none(fullnames.MANAGER_CLASS_FULLNAME)
if sym is not None and isinstance(sym.node, TypeInfo):
helpers.get_django_metadata(sym.node)['manager_bases'][ctx.cls.fullname] = 1
def transform_form_class(ctx: ClassDefContext) -> None:
sym = ctx.api.lookup_fully_qualified_or_none(helpers.BASEFORM_CLASS_FULLNAME)
if sym is not None and isinstance(sym.node, TypeInfo):
helpers.get_django_metadata(sym.node)['baseform_bases'][ctx.cls.fullname] = 1
def extract_django_settings_module(config_file_path: Optional[str]) -> str:
errors = Errors()
if config_file_path is None:
errors.report(0, None, "'django_settings_module' is not set: no mypy config file specified")
errors.raise_error()
make_meta_nested_class_inherit_from_any(ctx)
parser = configparser.ConfigParser()
parser.read(config_file_path) # type: ignore
if not parser.has_section('mypy.plugins.django-stubs'):
errors.report(0, None, "'django_settings_module' is not set: no section [mypy.plugins.django-stubs]",
file=config_file_path)
errors.raise_error()
if not parser.has_option('mypy.plugins.django-stubs', 'django_settings_module'):
errors.report(0, None, "'django_settings_module' is not set: setting is not provided",
file=config_file_path)
errors.raise_error()
django_settings_module = parser.get('mypy.plugins.django-stubs', 'django_settings_module').strip('\'"')
return django_settings_module
def determine_proper_manager_type(ctx: FunctionContext) -> Type:
from mypy.checker import TypeChecker
api = cast(TypeChecker, ctx.api)
ret = ctx.default_return_type
if not api.tscope.classes:
# not in class
return ret
outer_model_info = api.tscope.classes[0]
if not outer_model_info.has_base(helpers.MODEL_CLASS_FULLNAME):
return ret
if not isinstance(ret, Instance):
return ret
has_manager_base = False
for i, base in enumerate(ret.type.bases):
if base.type.fullname() in {helpers.MANAGER_CLASS_FULLNAME,
helpers.RELATED_MANAGER_CLASS_FULLNAME,
helpers.BASE_MANAGER_CLASS_FULLNAME}:
has_manager_base = True
break
if has_manager_base:
# Fill in the manager's type argument from the outer model
new_type_args = [Instance(outer_model_info, [])]
return helpers.reparametrize_instance(ret, new_type_args)
else:
return ret
def return_type_for_id_field(ctx: AttributeContext) -> Type:
if not isinstance(ctx.type, Instance):
return AnyType(TypeOfAny.from_error)
model_info = ctx.type.type # type: TypeInfo
primary_key_field_name = helpers.get_primary_key_field_name(model_info)
if not primary_key_field_name:
# no field with primary_key=True, just return id as int
return ctx.api.named_generic_type('builtins.int', [])
if primary_key_field_name != 'id':
# there's field with primary_key=True, but it's name is not 'id', fail
ctx.api.fail("Default primary key 'id' is not defined", ctx.context)
return AnyType(TypeOfAny.from_error)
primary_key_sym = model_info.get(primary_key_field_name)
if primary_key_sym and isinstance(primary_key_sym.type, Instance):
pass
# try to parse field type out of primary key field
field_type = helpers.extract_field_getter_type(primary_key_sym.type)
if field_type:
return field_type
return primary_key_sym.type
def transform_form_view(ctx: ClassDefContext) -> None:
form_class_value = helpers.get_assigned_value_for_class(ctx.cls.info, 'form_class')
if isinstance(form_class_value, NameExpr):
helpers.get_django_metadata(ctx.cls.info)['form_class'] = form_class_value.fullname
class DjangoPlugin(Plugin):
class NewSemanalDjangoPlugin(Plugin):
def __init__(self, options: Options) -> None:
super().__init__(options)
django_settings_module = extract_django_settings_module(options.config_file)
self.django_context = DjangoContext(django_settings_module)
config_fpath = os.environ.get('MYPY_DJANGO_CONFIG', 'mypy_django.ini')
if config_fpath and os.path.exists(config_fpath):
self.config = Config.from_config_file(config_fpath)
self.django_settings_module = self.config.django_settings_module
else:
self.config = Config()
self.django_settings_module = None
if 'DJANGO_SETTINGS_MODULE' in os.environ:
self.django_settings_module = os.environ['DJANGO_SETTINGS_MODULE']
def _get_current_model_bases(self) -> Dict[str, int]:
model_sym = self.lookup_fully_qualified(helpers.MODEL_CLASS_FULLNAME)
def _get_current_queryset_bases(self) -> Dict[str, int]:
model_sym = self.lookup_fully_qualified(fullnames.QUERYSET_CLASS_FULLNAME)
if model_sym is not None and isinstance(model_sym.node, TypeInfo):
return (helpers.get_django_metadata(model_sym.node)
.setdefault('model_bases', {helpers.MODEL_CLASS_FULLNAME: 1}))
.setdefault('queryset_bases', {fullnames.QUERYSET_CLASS_FULLNAME: 1}))
else:
return {}
def _get_current_manager_bases(self) -> Dict[str, int]:
model_sym = self.lookup_fully_qualified(helpers.MANAGER_CLASS_FULLNAME)
model_sym = self.lookup_fully_qualified(fullnames.MANAGER_CLASS_FULLNAME)
if model_sym is not None and isinstance(model_sym.node, TypeInfo):
return (helpers.get_django_metadata(model_sym.node)
.setdefault('manager_bases', {helpers.MANAGER_CLASS_FULLNAME: 1}))
.setdefault('manager_bases', {fullnames.MANAGER_CLASS_FULLNAME: 1}))
else:
return {}
def _get_current_model_bases(self) -> Dict[str, int]:
model_sym = self.lookup_fully_qualified(fullnames.MODEL_CLASS_FULLNAME)
if model_sym is not None and isinstance(model_sym.node, TypeInfo):
return helpers.get_django_metadata(model_sym.node).setdefault('model_bases',
{fullnames.MODEL_CLASS_FULLNAME: 1})
else:
return {}
def _get_current_form_bases(self) -> Dict[str, int]:
model_sym = self.lookup_fully_qualified(helpers.BASEFORM_CLASS_FULLNAME)
model_sym = self.lookup_fully_qualified(fullnames.BASEFORM_CLASS_FULLNAME)
if model_sym is not None and isinstance(model_sym.node, TypeInfo):
return (helpers.get_django_metadata(model_sym.node)
.setdefault('baseform_bases', {helpers.BASEFORM_CLASS_FULLNAME: 1,
helpers.FORM_CLASS_FULLNAME: 1,
helpers.MODELFORM_CLASS_FULLNAME: 1}))
.setdefault('baseform_bases', {fullnames.BASEFORM_CLASS_FULLNAME: 1,
fullnames.FORM_CLASS_FULLNAME: 1,
fullnames.MODELFORM_CLASS_FULLNAME: 1}))
else:
return {}
def _get_current_queryset_bases(self) -> Dict[str, int]:
model_sym = self.lookup_fully_qualified(helpers.QUERYSET_CLASS_FULLNAME)
if model_sym is not None and isinstance(model_sym.node, TypeInfo):
return (helpers.get_django_metadata(model_sym.node)
.setdefault('queryset_bases', {helpers.QUERYSET_CLASS_FULLNAME: 1}))
else:
return {}
def _get_settings_modules_in_order_of_priority(self) -> List[str]:
settings_modules = []
if self.django_settings_module:
settings_modules.append(self.django_settings_module)
settings_modules.append('django.conf.global_settings')
return settings_modules
def _get_typeinfo_or_none(self, class_name: str) -> Optional[TypeInfo]:
sym = self.lookup_fully_qualified(class_name)
if sym is not None and isinstance(sym.node, TypeInfo):
return sym.node
return None
def _new_dependency(self, module: str) -> Tuple[int, str, int]:
return 10, module, -1
def get_additional_deps(self, file: MypyFile) -> List[Tuple[int, str, int]]:
if file.fullname() == 'django.conf' and self.django_settings_module:
return [(10, self.django_settings_module, -1)]
# for settings
if file.fullname() == 'django.conf' and self.django_context.django_settings_module:
return [self._new_dependency(self.django_context.django_settings_module)]
if file.fullname() == 'django.db.models.query':
return [(10, 'mypy_extensions', -1)]
# for values / values_list
if file.fullname() == 'django.db.models':
return [self._new_dependency('mypy_extensions'), self._new_dependency('typing')]
return []
# for `get_user_model()`
if self.django_context.settings:
if (file.fullname() == 'django.contrib.auth'
or file.fullname() in {'django.http', 'django.http.request'}):
auth_user_model_name = self.django_context.settings.AUTH_USER_MODEL
try:
auth_user_module = self.django_context.apps_registry.get_model(auth_user_model_name).__module__
except LookupError:
# get_user_model() model app is not installed
return []
return [self._new_dependency(auth_user_module)]
# ensure that all mentioned to='someapp.SomeModel' are loaded with corresponding related Fields
defined_model_classes = self.django_context.model_modules.get(file.fullname())
if not defined_model_classes:
return []
deps = set()
for model_class in defined_model_classes:
# forward relations
for field in self.django_context.get_model_fields(model_class):
if isinstance(field, RelatedField):
related_model_module = field.related_model.__module__
if related_model_module != file.fullname():
deps.add(self._new_dependency(related_model_module))
# reverse relations
for relation in model_class._meta.related_objects:
related_model_module = relation.related_model.__module__
if related_model_module != file.fullname():
deps.add(self._new_dependency(related_model_module))
return list(deps)
def get_function_hook(self, fullname: str
) -> Optional[Callable[[FunctionContext], Type]]:
) -> Optional[Callable[[FunctionContext], MypyType]]:
if fullname == 'django.contrib.auth.get_user_model':
return partial(return_user_model_hook,
settings_modules=self._get_settings_modules_in_order_of_priority())
return partial(settings.get_user_model_hook, django_context=self.django_context)
manager_bases = self._get_current_manager_bases()
if fullname in manager_bases:
return determine_proper_manager_type
return querysets.determine_proper_manager_type
info = self._get_typeinfo_or_none(fullname)
if info:
if info.has_base(helpers.FIELD_FULLNAME):
return fields.adjust_return_type_of_field_instantiation
if info.has_base(fullnames.FIELD_FULLNAME):
return partial(fields.transform_into_proper_return_type, django_context=self.django_context)
if helpers.get_django_metadata(info).get('generated_init'):
return init_create.redefine_and_typecheck_model_init
if info.has_base(fullnames.MODEL_CLASS_FULLNAME):
return partial(init_create.redefine_and_typecheck_model_init, django_context=self.django_context)
return None
def get_method_hook(self, fullname: str
) -> Optional[Callable[[MethodContext], Type]]:
class_name, _, method_name = fullname.rpartition('.')
) -> Optional[Callable[[MethodContext], MypyType]]:
class_fullname, _, method_name = fullname.rpartition('.')
if method_name == 'get_form_class':
info = self._get_typeinfo_or_none(class_name)
if info and info.has_base(helpers.FORM_MIXIN_CLASS_FULLNAME):
return extract_proper_type_for_get_form_class
info = self._get_typeinfo_or_none(class_fullname)
if info and info.has_base(fullnames.FORM_MIXIN_CLASS_FULLNAME):
return forms.extract_proper_type_for_get_form_class
if method_name == 'get_form':
info = self._get_typeinfo_or_none(class_name)
if info and info.has_base(helpers.FORM_MIXIN_CLASS_FULLNAME):
return extract_proper_type_for_get_form
info = self._get_typeinfo_or_none(class_fullname)
if info and info.has_base(fullnames.FORM_MIXIN_CLASS_FULLNAME):
return forms.extract_proper_type_for_get_form
if method_name == 'values':
model_info = self._get_typeinfo_or_none(class_name)
if model_info and model_info.has_base(helpers.QUERYSET_CLASS_FULLNAME):
return extract_proper_type_for_queryset_values
info = self._get_typeinfo_or_none(class_fullname)
if info and info.has_base(fullnames.QUERYSET_CLASS_FULLNAME):
return partial(querysets.extract_proper_type_queryset_values, django_context=self.django_context)
if method_name == 'values_list':
model_info = self._get_typeinfo_or_none(class_name)
if model_info and model_info.has_base(helpers.QUERYSET_CLASS_FULLNAME):
return extract_proper_type_queryset_values_list
info = self._get_typeinfo_or_none(class_fullname)
if info and info.has_base(fullnames.QUERYSET_CLASS_FULLNAME):
return partial(querysets.extract_proper_type_queryset_values_list, django_context=self.django_context)
if fullname in {'django.apps.registry.Apps.get_model',
'django.db.migrations.state.StateApps.get_model'}:
return determine_model_cls_from_string_for_migrations
if method_name == 'get_field':
info = self._get_typeinfo_or_none(class_fullname)
if info and info.has_base(fullnames.OPTIONS_CLASS_FULLNAME):
return partial(meta.return_proper_field_type_from_get_field, django_context=self.django_context)
manager_classes = self._get_current_manager_bases()
class_fullname, _, method_name = fullname.rpartition('.')
if class_fullname in manager_classes and method_name == 'create':
return init_create.redefine_and_typecheck_model_create
return partial(init_create.redefine_and_typecheck_model_create, django_context=self.django_context)
return None
def get_base_class_hook(self, fullname: str
) -> Optional[Callable[[ClassDefContext], None]]:
if fullname in self._get_current_model_bases():
return partial(transform_model_class,
ignore_missing_model_attributes=self.config.ignore_missing_model_attributes)
return partial(transform_model_class, django_context=self.django_context)
if fullname in self._get_current_manager_bases():
return transform_manager_class
return add_new_manager_base
if fullname in self._get_current_form_bases():
return transform_form_class
info = self._get_typeinfo_or_none(fullname)
if info and info.has_base(helpers.FORM_MIXIN_CLASS_FULLNAME):
return transform_form_view
return None
def get_attribute_hook(self, fullname: str
) -> Optional[Callable[[AttributeContext], Type]]:
) -> Optional[Callable[[AttributeContext], MypyType]]:
class_name, _, attr_name = fullname.rpartition('.')
if class_name == helpers.DUMMY_SETTINGS_BASE_CLASS:
return partial(get_type_of_setting,
setting_name=attr_name,
settings_modules=self._get_settings_modules_in_order_of_priority(),
ignore_missing_settings=self.config.ignore_missing_settings)
if class_name == fullnames.DUMMY_SETTINGS_BASE_CLASS:
return partial(settings.get_type_of_settings_attribute,
django_context=self.django_context)
if class_name in self._get_current_model_bases():
if attr_name == 'id':
return return_type_for_id_field
model_info = self._get_typeinfo_or_none(class_name)
if model_info:
related_managers = helpers.get_related_managers_metadata(model_info)
if attr_name in related_managers:
return partial(determine_type_of_related_manager,
related_manager_name=attr_name)
if attr_name.endswith('_id'):
return extract_and_return_primary_key_of_bound_related_field_parameter
info = self._get_typeinfo_or_none(class_name)
if info and info.has_base(fullnames.HTTPREQUEST_CLASS_FULLNAME) and attr_name == 'user':
return partial(request.set_auth_user_model_as_type_for_request_user, django_context=self.django_context)
return None
def get_type_analyze_hook(self, fullname: str
) -> Optional[Callable[[AnalyzeTypeContext], Type]]:
queryset_bases = self._get_current_queryset_bases()
if fullname in queryset_bases:
return partial(set_first_generic_param_as_default_for_second, fullname)
) -> Optional[Callable[[AnalyzeTypeContext], MypyType]]:
info = self._get_typeinfo_or_none(fullname)
if (info
and info.has_base(fullnames.QUERYSET_CLASS_FULLNAME)
and not info.has_base(fullnames.BASE_MANAGER_CLASS_FULLNAME)):
return partial(querysets.set_first_generic_param_as_default_for_second, fullname=fullname)
return None
def plugin(version):
return DjangoPlugin
return NewSemanalDjangoPlugin
+85 -146
View File
@@ -1,188 +1,127 @@
from typing import Optional, cast
from typing import Optional, Tuple, cast
from mypy.checker import TypeChecker
from mypy.nodes import ListExpr, NameExpr, StrExpr, TupleExpr, TypeInfo
from django.db.models.fields import Field
from django.db.models.fields.related import RelatedField
from mypy.nodes import AssignmentStmt, NameExpr, TypeInfo
from mypy.plugin import FunctionContext
from mypy.types import (
AnyType, CallableType, Instance, TupleType, Type, UnionType,
)
from mypy.types import AnyType, Instance
from mypy.types import Type as MypyType
from mypy.types import TypeOfAny
from mypy_django_plugin import helpers
from mypy_django_plugin.django.context import DjangoContext
from mypy_django_plugin.lib import fullnames, helpers
def extract_referred_to_type(ctx: FunctionContext) -> Optional[Instance]:
api = cast(TypeChecker, ctx.api)
if 'to' not in ctx.callee_arg_names:
api.msg.fail(f'to= parameter must be set for {ctx.context.callee.fullname}',
context=ctx.context)
def _get_current_field_from_assignment(ctx: FunctionContext, django_context: DjangoContext) -> Optional[Field]:
outer_model_info = helpers.get_typechecker_api(ctx).scope.active_class()
if (outer_model_info is None
or not outer_model_info.has_base(fullnames.MODEL_CLASS_FULLNAME)):
return None
arg_type = ctx.arg_types[ctx.callee_arg_names.index('to')][0]
if not isinstance(arg_type, CallableType):
to_arg_expr = ctx.args[ctx.callee_arg_names.index('to')][0]
if not isinstance(to_arg_expr, StrExpr):
# not string, not supported
return None
try:
model_fullname = helpers.get_model_fullname_from_string(to_arg_expr.value,
all_modules=api.modules)
except helpers.SelfReference:
model_fullname = api.tscope.classes[-1].fullname()
except helpers.SameFileModel as exc:
model_fullname = api.tscope.classes[-1].module_name + '.' + exc.model_cls_name
if model_fullname is None:
return None
model_info = helpers.lookup_fully_qualified_generic(model_fullname,
all_modules=api.modules)
if model_info is None or not isinstance(model_info, TypeInfo):
return None
return Instance(model_info, [])
referred_to_type = arg_type.ret_type
if not isinstance(referred_to_type, Instance):
return None
if not referred_to_type.type.has_base(helpers.MODEL_CLASS_FULLNAME):
ctx.api.msg.fail(f'to= parameter value must be '
f'a subclass of {helpers.MODEL_CLASS_FULLNAME}',
context=ctx.context)
field_name = None
for stmt in outer_model_info.defn.defs.body:
if isinstance(stmt, AssignmentStmt):
if stmt.rvalue == ctx.context:
if not isinstance(stmt.lvalues[0], NameExpr):
return None
field_name = stmt.lvalues[0].name
break
if field_name is None:
return None
return referred_to_type
model_cls = django_context.get_model_class_by_fullname(outer_model_info.fullname())
if model_cls is None:
return None
current_field = model_cls._meta.get_field(field_name)
return current_field
def convert_any_to_type(typ: Type, referred_to_type: Type) -> Type:
if isinstance(typ, UnionType):
converted_items = []
for item in typ.items:
converted_items.append(convert_any_to_type(item, referred_to_type))
return UnionType.make_union(converted_items,
line=typ.line, column=typ.column)
if isinstance(typ, Instance):
args = []
for default_arg in typ.args:
if isinstance(default_arg, AnyType):
args.append(referred_to_type)
else:
args.append(default_arg)
return helpers.reparametrize_instance(typ, args)
def fill_descriptor_types_for_related_field(ctx: FunctionContext, django_context: DjangoContext) -> MypyType:
current_field = _get_current_field_from_assignment(ctx, django_context)
if current_field is None:
return AnyType(TypeOfAny.from_error)
if isinstance(typ, AnyType):
return referred_to_type
assert isinstance(current_field, RelatedField)
return typ
related_model = related_model_to_set = current_field.related_model
if related_model_to_set._meta.proxy_for_model:
related_model_to_set = related_model._meta.proxy_for_model
typechecker_api = helpers.get_typechecker_api(ctx)
def fill_descriptor_types_for_related_field(ctx: FunctionContext) -> Type:
default_return_type = set_descriptor_types_for_field(ctx)
referred_to_type = extract_referred_to_type(ctx)
if referred_to_type is None:
return default_return_type
related_model_info = helpers.lookup_class_typeinfo(typechecker_api, related_model)
if related_model_info is None:
# maybe no type stub
related_model_type = AnyType(TypeOfAny.unannotated)
else:
related_model_type = Instance(related_model_info, []) # type: ignore
related_model_to_set_info = helpers.lookup_class_typeinfo(typechecker_api, related_model_to_set)
if related_model_to_set_info is None:
# maybe no type stub
related_model_to_set_type = AnyType(TypeOfAny.unannotated)
else:
related_model_to_set_type = Instance(related_model_to_set_info, []) # type: ignore
default_related_field_type = set_descriptor_types_for_field(ctx)
# replace Any with referred_to_type
args = []
for default_arg in default_return_type.args:
args.append(convert_any_to_type(default_arg, referred_to_type))
args = [
helpers.convert_any_to_type(default_related_field_type.args[0], related_model_to_set_type),
helpers.convert_any_to_type(default_related_field_type.args[1], related_model_type),
]
return helpers.reparametrize_instance(default_related_field_type, new_args=args)
return helpers.reparametrize_instance(ctx.default_return_type, new_args=args)
def get_field_descriptor_types(field_info: TypeInfo, is_nullable: bool) -> Tuple[MypyType, MypyType]:
set_type = helpers.get_private_descriptor_type(field_info, '_pyi_private_set_type',
is_nullable=is_nullable)
get_type = helpers.get_private_descriptor_type(field_info, '_pyi_private_get_type',
is_nullable=is_nullable)
return set_type, get_type
def set_descriptor_types_for_field(ctx: FunctionContext) -> Instance:
default_return_type = cast(Instance, ctx.default_return_type)
is_nullable = helpers.parse_bool(helpers.get_argument_by_name(ctx, 'null'))
set_type = helpers.get_private_descriptor_type(default_return_type.type, '_pyi_private_set_type',
is_nullable=is_nullable)
get_type = helpers.get_private_descriptor_type(default_return_type.type, '_pyi_private_get_type',
is_nullable=is_nullable)
is_nullable = False
null_expr = helpers.get_call_argument_by_name(ctx, 'null')
if null_expr is not None:
is_nullable = helpers.parse_bool(null_expr) or False
set_type, get_type = get_field_descriptor_types(default_return_type.type, is_nullable)
return helpers.reparametrize_instance(default_return_type, [set_type, get_type])
def determine_type_of_array_field(ctx: FunctionContext) -> Type:
def determine_type_of_array_field(ctx: FunctionContext, django_context: DjangoContext) -> MypyType:
default_return_type = set_descriptor_types_for_field(ctx)
base_field_arg_type = helpers.get_argument_type_by_name(ctx, 'base_field')
base_field_arg_type = helpers.get_call_argument_type_by_name(ctx, 'base_field')
if not base_field_arg_type or not isinstance(base_field_arg_type, Instance):
return default_return_type
base_type = base_field_arg_type.args[1] # extract __get__ type
args = []
for default_arg in default_return_type.args:
args.append(convert_any_to_type(default_arg, base_type))
args.append(helpers.convert_any_to_type(default_arg, base_type))
return helpers.reparametrize_instance(default_return_type, args)
def transform_into_proper_return_type(ctx: FunctionContext) -> Type:
def transform_into_proper_return_type(ctx: FunctionContext, django_context: DjangoContext) -> MypyType:
default_return_type = ctx.default_return_type
if not isinstance(default_return_type, Instance):
return default_return_type
assert isinstance(default_return_type, Instance)
if helpers.has_any_of_bases(default_return_type.type, (helpers.FOREIGN_KEY_FULLNAME,
helpers.ONETOONE_FIELD_FULLNAME,
helpers.MANYTOMANY_FIELD_FULLNAME)):
return fill_descriptor_types_for_related_field(ctx)
outer_model_info = helpers.get_typechecker_api(ctx).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 default_return_type.type.has_base(helpers.ARRAY_FIELD_FULLNAME):
return determine_type_of_array_field(ctx)
if helpers.has_any_of_bases(default_return_type.type, fullnames.RELATED_FIELDS_CLASSES):
return fill_descriptor_types_for_related_field(ctx, django_context)
if default_return_type.type.has_base(fullnames.ARRAY_FIELD_FULLNAME):
return determine_type_of_array_field(ctx, django_context)
return set_descriptor_types_for_field(ctx)
def adjust_return_type_of_field_instantiation(ctx: FunctionContext) -> Type:
record_field_properties_into_outer_model_class(ctx)
return transform_into_proper_return_type(ctx)
def record_field_properties_into_outer_model_class(ctx: FunctionContext) -> None:
api = cast(TypeChecker, ctx.api)
outer_model = api.scope.active_class()
if outer_model is None or not outer_model.has_base(helpers.MODEL_CLASS_FULLNAME):
# outside models.Model class, undetermined
return
field_name = None
for name_expr, stmt in helpers.iter_over_assignments(outer_model.defn):
if stmt == ctx.context and isinstance(name_expr, NameExpr):
field_name = name_expr.name
break
if field_name is None:
return
fields_metadata = helpers.get_fields_metadata(outer_model)
# primary key
is_primary_key = False
primary_key_arg = helpers.get_argument_by_name(ctx, 'primary_key')
if primary_key_arg:
is_primary_key = helpers.parse_bool(primary_key_arg)
fields_metadata[field_name] = {'primary_key': is_primary_key}
# choices
choices_arg = helpers.get_argument_by_name(ctx, 'choices')
if choices_arg and isinstance(choices_arg, (TupleExpr, ListExpr)):
# iterable of 2 element tuples of two kinds
_, analyzed_choices = api.analyze_iterable_item_type(choices_arg)
if isinstance(analyzed_choices, TupleType):
first_element_type = analyzed_choices.items[0]
if isinstance(first_element_type, Instance):
fields_metadata[field_name]['choices'] = first_element_type.type.fullname()
# nullability
null_arg = helpers.get_argument_by_name(ctx, 'null')
is_nullable = False
if null_arg:
is_nullable = helpers.parse_bool(null_arg)
fields_metadata[field_name]['null'] = is_nullable
# is_blankable
blank_arg = helpers.get_argument_by_name(ctx, 'blank')
is_blankable = False
if blank_arg:
is_blankable = helpers.parse_bool(blank_arg)
fields_metadata[field_name]['blank'] = is_blankable
# default
default_arg = helpers.get_argument_by_name(ctx, 'default')
if default_arg and not helpers.is_none_expr(default_arg):
fields_metadata[field_name]['default_specified'] = True
+28 -22
View File
@@ -1,29 +1,36 @@
from mypy.plugin import ClassDefContext, MethodContext
from mypy.types import CallableType, Instance, NoneTyp, Type, TypeType
from typing import Optional
from mypy_django_plugin import helpers
from mypy.plugin import ClassDefContext, MethodContext
from mypy.types import CallableType, Instance, NoneTyp
from mypy.types import Type as MypyType
from mypy.types import TypeType
from mypy_django_plugin.lib import helpers
def make_meta_nested_class_inherit_from_any(ctx: ClassDefContext) -> None:
meta_node = helpers.get_nested_meta_node_for_current_class(ctx.cls.info)
if meta_node is None:
return None
meta_node.fallback_to_any = True
if not ctx.api.final_iteration:
ctx.api.defer()
else:
meta_node.fallback_to_any = True
def extract_proper_type_for_get_form(ctx: MethodContext) -> Type:
def get_specified_form_class(object_type: Instance) -> Optional[TypeType]:
form_class_sym = object_type.type.get('form_class')
if form_class_sym and isinstance(form_class_sym.type, CallableType):
return TypeType(form_class_sym.type.ret_type)
return None
def extract_proper_type_for_get_form(ctx: MethodContext) -> MypyType:
object_type = ctx.type
if not isinstance(object_type, Instance):
return ctx.default_return_type
assert isinstance(object_type, Instance)
form_class_type = helpers.get_argument_type_by_name(ctx, 'form_class')
form_class_type = helpers.get_call_argument_type_by_name(ctx, 'form_class')
if form_class_type is None or isinstance(form_class_type, NoneTyp):
# extract from specified form_class in metadata
form_class_fullname = helpers.get_django_metadata(object_type.type).get('form_class', None)
if not form_class_fullname:
return ctx.default_return_type
return ctx.api.named_generic_type(form_class_fullname, [])
form_class_type = get_specified_form_class(object_type)
if isinstance(form_class_type, TypeType) and isinstance(form_class_type.item, Instance):
return form_class_type.item
@@ -34,13 +41,12 @@ def extract_proper_type_for_get_form(ctx: MethodContext) -> Type:
return ctx.default_return_type
def extract_proper_type_for_get_form_class(ctx: MethodContext) -> Type:
def extract_proper_type_for_get_form_class(ctx: MethodContext) -> MypyType:
object_type = ctx.type
if not isinstance(object_type, Instance):
assert isinstance(object_type, Instance)
form_class_type = get_specified_form_class(object_type)
if form_class_type is None:
return ctx.default_return_type
form_class_fullname = helpers.get_django_metadata(object_type.type).get('form_class', None)
if not form_class_fullname:
return ctx.default_return_type
return TypeType(ctx.api.named_generic_type(form_class_fullname, []))
return form_class_type
+55 -174
View File
@@ -1,194 +1,75 @@
from typing import Dict, Optional, Set, cast
from typing import List, Tuple, Type, Union
from mypy.checker import TypeChecker
from mypy.nodes import TypeInfo, Var
from django.db.models.base import Model
from mypy.plugin import FunctionContext, MethodContext
from mypy.types import AnyType, Instance, Type, TypeOfAny
from mypy.types import Instance
from mypy.types import Type as MypyType
from mypy_django_plugin import helpers
from mypy_django_plugin.django.context import DjangoContext
from mypy_django_plugin.lib import helpers
def extract_base_pointer_args(model: TypeInfo) -> Set[str]:
pointer_args: Set[str] = set()
for base in model.bases:
if base.type.has_base(helpers.MODEL_CLASS_FULLNAME):
parent_name = base.type.name().lower()
pointer_args.add(f'{parent_name}_ptr')
pointer_args.add(f'{parent_name}_ptr_id')
return pointer_args
def redefine_and_typecheck_model_init(ctx: FunctionContext) -> Type:
assert isinstance(ctx.default_return_type, Instance)
api = cast(TypeChecker, ctx.api)
model: TypeInfo = ctx.default_return_type.type
expected_types = extract_expected_types(ctx, model, is_init=True)
# order is preserved, can be used for positionals
positional_names = list(expected_types.keys())
positional_names.remove('pk')
visited_positionals = set()
# check positionals
for i, (_, actual_pos_type) in enumerate(zip(ctx.arg_names[0], ctx.arg_types[0])):
actual_pos_name = positional_names[i]
api.check_subtype(actual_pos_type, expected_types[actual_pos_name],
ctx.context,
'Incompatible type for "{}" of "{}"'.format(actual_pos_name,
model.name()),
'got', 'expected')
visited_positionals.add(actual_pos_name)
# extract name of base models for _ptr
base_pointer_args = extract_base_pointer_args(model)
# check kwargs
for i, (actual_name, actual_type) in enumerate(zip(ctx.arg_names[1], ctx.arg_types[1])):
if actual_name in base_pointer_args:
# parent_ptr args are not supported
continue
if actual_name in visited_positionals:
continue
def get_actual_types(ctx: Union[MethodContext, FunctionContext],
expected_keys: List[str]) -> List[Tuple[str, MypyType]]:
actual_types = []
# positionals
for pos, (actual_name, actual_type) in enumerate(zip(ctx.arg_names[0], ctx.arg_types[0])):
if actual_name is None:
# unpacked dict as kwargs is not supported
continue
if ctx.callee_arg_names[0] == 'kwargs':
# unpacked dict as kwargs is not supported
continue
actual_name = expected_keys[pos]
actual_types.append((actual_name, actual_type))
# kwargs
if len(ctx.callee_arg_names) > 1:
for actual_name, actual_type in zip(ctx.arg_names[1], ctx.arg_types[1]):
if actual_name is None:
# unpacked dict as kwargs is not supported
continue
actual_types.append((actual_name, actual_type))
return actual_types
def typecheck_model_method(ctx: Union[FunctionContext, MethodContext], django_context: DjangoContext,
model_cls: Type[Model], method: str) -> MypyType:
typechecker_api = helpers.get_typechecker_api(ctx)
expected_types = django_context.get_expected_types(typechecker_api, model_cls, method=method)
expected_keys = [key for key in expected_types.keys() if key != 'pk']
for actual_name, actual_type in get_actual_types(ctx, expected_keys):
if actual_name not in expected_types:
ctx.api.fail('Unexpected attribute "{}" for model "{}"'.format(actual_name,
model.name()),
model_cls.__name__),
ctx.context)
continue
api.check_subtype(actual_type, expected_types[actual_name],
ctx.context,
'Incompatible type for "{}" of "{}"'.format(actual_name,
model.name()),
'got', 'expected')
typechecker_api.check_subtype(actual_type, expected_types[actual_name],
ctx.context,
'Incompatible type for "{}" of "{}"'.format(actual_name,
model_cls.__name__),
'got', 'expected')
return ctx.default_return_type
def redefine_and_typecheck_model_create(ctx: MethodContext) -> Type:
api = cast(TypeChecker, ctx.api)
if isinstance(ctx.type, Instance) and len(ctx.type.args) > 0:
model_generic_arg = ctx.type.args[0]
else:
model_generic_arg = ctx.default_return_type
def redefine_and_typecheck_model_init(ctx: FunctionContext, django_context: DjangoContext) -> MypyType:
assert isinstance(ctx.default_return_type, Instance)
if isinstance(model_generic_arg, AnyType):
model_fullname = ctx.default_return_type.type.fullname()
model_cls = django_context.get_model_class_by_fullname(model_fullname)
if model_cls is None:
return ctx.default_return_type
model: TypeInfo = model_generic_arg.type
# extract name of base models for _ptr
base_pointer_args = extract_base_pointer_args(model)
expected_types = extract_expected_types(ctx, model)
for actual_name, actual_type in zip(ctx.arg_names[0], ctx.arg_types[0]):
if actual_name in base_pointer_args:
# parent_ptr args are not supported
continue
if actual_name is None:
# unpacked dict as kwargs is not supported
continue
if actual_name not in expected_types:
api.fail('Unexpected attribute "{}" for model "{}"'.format(actual_name,
model.name()),
ctx.context)
continue
api.check_subtype(actual_type, expected_types[actual_name],
ctx.context,
'Incompatible type for "{}" of "{}"'.format(actual_name,
model.name()),
'got', 'expected')
return ctx.default_return_type
return typecheck_model_method(ctx, django_context, model_cls, '__init__')
def extract_choices_type(model: TypeInfo, field_name: str) -> Optional[str]:
field_metadata = helpers.get_fields_metadata(model).get(field_name, {})
if 'choices' in field_metadata:
return field_metadata['choices']
return None
def redefine_and_typecheck_model_create(ctx: MethodContext, django_context: DjangoContext) -> MypyType:
if not isinstance(ctx.default_return_type, Instance):
# only work with ctx.default_return_type = model Instance
return ctx.default_return_type
model_fullname = ctx.default_return_type.type.fullname()
model_cls = django_context.get_model_class_by_fullname(model_fullname)
if model_cls is None:
return ctx.default_return_type
def extract_expected_types(ctx: FunctionContext, model: TypeInfo,
is_init: bool = False) -> Dict[str, Type]:
api = cast(TypeChecker, ctx.api)
expected_types: Dict[str, Type] = {}
primary_key_type = helpers.extract_explicit_set_type_of_model_primary_key(model)
if not primary_key_type:
# no explicit primary key, set pk to Any and add id
primary_key_type = AnyType(TypeOfAny.special_form)
if is_init:
expected_types['id'] = helpers.make_optional(ctx.api.named_generic_type('builtins.int', []))
else:
expected_types['id'] = ctx.api.named_generic_type('builtins.int', [])
expected_types['pk'] = primary_key_type
for base in model.mro:
# extract all fields for all models in MRO
for name, sym in base.names.items():
# do not redefine special attrs
if name in {'_meta', 'pk'}:
continue
if isinstance(sym.node, Var):
typ = sym.node.type
if typ is None or isinstance(typ, AnyType):
# types are not ready, fallback to Any
expected_types[name] = AnyType(TypeOfAny.from_unimported_type)
expected_types[name + '_id'] = AnyType(TypeOfAny.from_unimported_type)
elif isinstance(typ, Instance):
field_type = helpers.extract_field_setter_type(typ)
if field_type is None:
continue
if helpers.has_any_of_bases(typ.type, (helpers.FOREIGN_KEY_FULLNAME,
helpers.ONETOONE_FIELD_FULLNAME)):
related_primary_key_type = AnyType(TypeOfAny.implementation_artifact)
# in case it's optional, we need Instance type
referred_to_model = typ.args[1]
is_nullable = helpers.is_optional(referred_to_model)
if is_nullable:
referred_to_model = helpers.make_required(typ.args[1])
if isinstance(referred_to_model, Instance) and referred_to_model.type.has_base(
helpers.MODEL_CLASS_FULLNAME):
pk_type = helpers.extract_explicit_set_type_of_model_primary_key(referred_to_model.type)
if not pk_type:
# extract set type of AutoField
autofield_info = api.lookup_typeinfo('django.db.models.fields.AutoField')
pk_type = helpers.get_private_descriptor_type(autofield_info, '_pyi_private_set_type',
is_nullable=is_nullable)
related_primary_key_type = pk_type
if is_init:
related_primary_key_type = helpers.make_optional(related_primary_key_type)
expected_types[name + '_id'] = related_primary_key_type
field_metadata = helpers.get_fields_metadata(model).get(name, {})
if field_type:
# related fields could be None in __init__ (but should be specified before save())
if helpers.has_any_of_bases(typ.type, (helpers.FOREIGN_KEY_FULLNAME,
helpers.ONETOONE_FIELD_FULLNAME)) and is_init:
field_type = helpers.make_optional(field_type)
# if primary_key=True and default specified
elif field_metadata.get('primary_key', False) and field_metadata.get('default_specified',
False):
field_type = helpers.make_optional(field_type)
# if CharField(blank=True,...) and not nullable, then field can be None in __init__
elif (
helpers.has_any_of_bases(typ.type, (helpers.CHAR_FIELD_FULLNAME,)) and is_init and
field_metadata.get('blank', False) and not field_metadata.get('null', False)
):
field_type = helpers.make_optional(field_type)
expected_types[name] = field_type
return expected_types
return typecheck_model_method(ctx, django_context, model_cls, 'create')
+46
View File
@@ -0,0 +1,46 @@
from django.core.exceptions import FieldDoesNotExist
from mypy.plugin import MethodContext
from mypy.types import AnyType, Instance
from mypy.types import Type as MypyType
from mypy.types import TypeOfAny
from mypy_django_plugin.django.context import DjangoContext
from mypy_django_plugin.lib import fullnames, helpers
def _get_field_instance(ctx: MethodContext, field_fullname: str) -> MypyType:
field_info = helpers.lookup_fully_qualified_typeinfo(helpers.get_typechecker_api(ctx),
field_fullname)
if field_info is None:
return AnyType(TypeOfAny.unannotated)
return Instance(field_info, [AnyType(TypeOfAny.explicit), AnyType(TypeOfAny.explicit)])
def return_proper_field_type_from_get_field(ctx: MethodContext, django_context: DjangoContext) -> MypyType:
# Options instance
assert isinstance(ctx.type, Instance)
model_type = ctx.type.args[0]
if not isinstance(model_type, Instance):
return _get_field_instance(ctx, fullnames.FIELD_FULLNAME)
model_cls = django_context.get_model_class_by_fullname(model_type.type.fullname())
if model_cls is None:
return _get_field_instance(ctx, fullnames.FIELD_FULLNAME)
field_name_expr = helpers.get_call_argument_by_name(ctx, 'field_name')
if field_name_expr is None:
return _get_field_instance(ctx, fullnames.FIELD_FULLNAME)
field_name = helpers.resolve_string_attribute_value(field_name_expr, ctx, django_context)
if field_name is None:
return _get_field_instance(ctx, fullnames.FIELD_FULLNAME)
try:
field = model_cls._meta.get_field(field_name)
except FieldDoesNotExist as exc:
ctx.api.fail(exc.args[0], ctx.context)
return AnyType(TypeOfAny.from_error)
field_fullname = helpers.get_class_fullname(field.__class__)
return _get_field_instance(ctx, field_fullname)
@@ -1,44 +0,0 @@
from typing import Optional, cast
from mypy.checker import TypeChecker
from mypy.nodes import Expression, StrExpr, TypeInfo
from mypy.plugin import MethodContext
from mypy.types import Instance, Type, TypeType
from mypy_django_plugin import helpers
def get_string_value_from_expr(expr: Expression) -> Optional[str]:
if isinstance(expr, StrExpr):
return expr.value
# TODO: somehow figure out other cases
return None
def determine_model_cls_from_string_for_migrations(ctx: MethodContext) -> Type:
app_label_expr = ctx.args[ctx.callee_arg_names.index('app_label')][0]
app_label = get_string_value_from_expr(app_label_expr)
if app_label is None:
return ctx.default_return_type
if 'model_name' not in ctx.callee_arg_names:
return ctx.default_return_type
model_name_expr_tuple = ctx.args[ctx.callee_arg_names.index('model_name')]
if not model_name_expr_tuple:
return ctx.default_return_type
model_name = get_string_value_from_expr(model_name_expr_tuple[0])
if model_name is None:
return ctx.default_return_type
api = cast(TypeChecker, ctx.api)
model_fullname = helpers.get_model_fullname(app_label, model_name, all_modules=api.modules)
if model_fullname is None:
return ctx.default_return_type
model_info = helpers.lookup_fully_qualified_generic(model_fullname,
all_modules=api.modules)
if model_info is None or not isinstance(model_info, TypeInfo):
return ctx.default_return_type
return TypeType(Instance(model_info, []))
+177 -251
View File
@@ -1,74 +1,81 @@
from abc import ABCMeta, abstractmethod
from typing import Any, Dict, Iterator, List, Optional, Tuple, cast
from collections import OrderedDict
from typing import Type, cast
import dataclasses
from django.db.models.base import Model
from django.db.models.fields import DateField, DateTimeField
from django.db.models.fields.related import ForeignKey
from django.db.models.fields.reverse_related import (
ManyToManyRel, ManyToOneRel, OneToOneRel,
)
from mypy.newsemanal.semanal import NewSemanticAnalyzer
from mypy.nodes import (
ARG_POS, ARG_STAR, ARG_STAR2, MDEF, Argument, CallExpr, ClassDef, Expression, IndexExpr, MemberExpr, MypyFile,
NameExpr, StrExpr, SymbolTableNode, TypeInfo, Var,
ARG_STAR2, MDEF, Argument, SymbolTableNode, TypeInfo, Var,
)
from mypy.plugin import ClassDefContext
from mypy.plugins.common import add_method
from mypy.semanal import SemanticAnalyzerPass2
from mypy.types import AnyType, Instance, NoneTyp, TypeOfAny
from mypy.plugins import common
from mypy.types import AnyType, Instance, TypeOfAny
from mypy_django_plugin import helpers
from mypy_django_plugin.django.context import DjangoContext
from mypy_django_plugin.lib import fullnames, helpers
from mypy_django_plugin.transformers import fields
from mypy_django_plugin.transformers.fields import get_field_descriptor_types
@dataclasses.dataclass
class ModelClassInitializer(metaclass=ABCMeta):
api: SemanticAnalyzerPass2
model_classdef: ClassDef
class ModelClassInitializer:
def __init__(self, ctx: ClassDefContext, django_context: DjangoContext):
self.api = cast(NewSemanticAnalyzer, ctx.api)
self.model_classdef = ctx.cls
self.django_context = django_context
self.ctx = ctx
@classmethod
def from_ctx(cls, ctx: ClassDefContext):
return cls(api=cast(SemanticAnalyzerPass2, ctx.api), model_classdef=ctx.cls)
def lookup_typeinfo_or_incomplete_defn_error(self, fullname: str) -> TypeInfo:
sym = self.api.lookup_fully_qualified_or_none(fullname)
if sym is None or not isinstance(sym.node, TypeInfo):
raise helpers.IncompleteDefnException(f'No {fullname!r} found')
return sym.node
def get_meta_attribute(self, name: str) -> Optional[Expression]:
meta_node = helpers.get_nested_meta_node_for_current_class(self.model_classdef.info)
if meta_node is None:
return None
def lookup_class_typeinfo_or_incomplete_defn_error(self, klass: type) -> TypeInfo:
fullname = helpers.get_class_fullname(klass)
field_info = self.lookup_typeinfo_or_incomplete_defn_error(fullname)
return field_info
return helpers.get_assigned_value_for_class(meta_node, name)
def is_abstract_model(self) -> bool:
is_abstract_expr = self.get_meta_attribute('abstract')
if is_abstract_expr is None:
return False
return self.api.parse_bool(is_abstract_expr)
def add_new_node_to_model_class(self, name: str, typ: Instance) -> None:
def create_new_var(self, name: str, typ: Instance) -> Var:
# type=: type of the variable itself
var = Var(name=name, type=typ)
# var.info: type of the object variable is bound to
var.info = self.model_classdef.info
var._fullname = self.model_classdef.info.fullname() + '.' + name
var.is_inferred = True
var.is_initialized_in_class = True
var.is_inferred = True
return var
def add_new_node_to_model_class(self, name: str, typ: Instance) -> None:
var = self.create_new_var(name, typ)
self.model_classdef.info.names[name] = SymbolTableNode(MDEF, var, plugin_generated=True)
@abstractmethod
def run(self) -> None:
raise NotImplementedError()
model_cls = self.django_context.get_model_class_by_fullname(self.model_classdef.fullname)
if model_cls is None:
return
self.run_with_model_cls(model_cls)
def iter_over_one_to_n_related_fields(klass: ClassDef) -> Iterator[Tuple[NameExpr, CallExpr]]:
for lvalue, rvalue in helpers.iter_call_assignments(klass):
if (isinstance(lvalue, NameExpr)
and isinstance(rvalue.callee, MemberExpr)):
if rvalue.callee.fullname in {helpers.FOREIGN_KEY_FULLNAME,
helpers.ONETOONE_FIELD_FULLNAME}:
yield lvalue, rvalue
class SetIdAttrsForRelatedFields(ModelClassInitializer):
def run(self) -> None:
for lvalue, rvalue in iter_over_one_to_n_related_fields(self.model_classdef):
node_name = lvalue.name + '_id'
self.add_new_node_to_model_class(name=node_name,
typ=self.api.builtin_type('builtins.int'))
def run_with_model_cls(self, model_cls):
pass
class InjectAnyAsBaseForNestedMeta(ModelClassInitializer):
"""
Replaces
class MyModel(models.Model):
class Meta:
pass
with
class MyModel(models.Model):
class Meta(Any):
pass
to get around incompatible Meta inner classes for different models.
"""
def run(self) -> None:
meta_node = helpers.get_nested_meta_node_for_current_class(self.model_classdef.info)
if meta_node is None:
@@ -76,228 +83,147 @@ class InjectAnyAsBaseForNestedMeta(ModelClassInitializer):
meta_node.fallback_to_any = True
class AddDefaultObjectsManager(ModelClassInitializer):
def add_new_manager(self, name: str, manager_type: Optional[Instance]) -> None:
if manager_type is None:
return None
self.add_new_node_to_model_class(name, manager_type)
class AddDefaultPrimaryKey(ModelClassInitializer):
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
auto_field_fullname = helpers.get_class_fullname(auto_field.__class__)
auto_field_info = self.lookup_typeinfo_or_incomplete_defn_error(auto_field_fullname)
def add_private_default_manager(self, manager_type: Optional[Instance]) -> None:
if manager_type is None:
return None
self.add_new_node_to_model_class('_default_manager', manager_type)
def get_existing_managers(self) -> List[Tuple[str, TypeInfo]]:
managers = []
for base in self.model_classdef.info.mro:
for name_expr, member_expr in helpers.iter_call_assignments(base.defn):
manager_name = name_expr.name
callee_expr = member_expr.callee
if isinstance(callee_expr, IndexExpr):
callee_expr = callee_expr.analyzed.expr
if isinstance(callee_expr, (MemberExpr, NameExpr)) \
and isinstance(callee_expr.node, TypeInfo) \
and callee_expr.node.has_base(helpers.BASE_MANAGER_CLASS_FULLNAME):
managers.append((manager_name, callee_expr.node))
return managers
def run(self) -> None:
existing_managers = self.get_existing_managers()
if existing_managers:
first_manager_type = None
for manager_name, manager_type_info in existing_managers:
manager_type = Instance(manager_type_info, args=[Instance(self.model_classdef.info, [])])
self.add_new_manager(name=manager_name, manager_type=manager_type)
if first_manager_type is None:
first_manager_type = manager_type
else:
if self.is_abstract_model():
# abstract models do not need 'objects' queryset
return None
first_manager_type = self.api.named_type_or_none(helpers.MANAGER_CLASS_FULLNAME,
args=[Instance(self.model_classdef.info, [])])
self.add_new_manager('objects', manager_type=first_manager_type)
if self.is_abstract_model():
return None
default_manager_name_expr = self.get_meta_attribute('default_manager_name')
if isinstance(default_manager_name_expr, StrExpr):
self.add_private_default_manager(self.model_classdef.info.get(default_manager_name_expr.value).type)
else:
self.add_private_default_manager(first_manager_type)
set_type, get_type = fields.get_field_descriptor_types(auto_field_info, is_nullable=False)
self.add_new_node_to_model_class(auto_field.attname, Instance(auto_field_info,
[set_type, get_type]))
class AddIdAttributeIfPrimaryKeyTrueIsNotSet(ModelClassInitializer):
def run(self) -> None:
if self.is_abstract_model():
# no need for .id attr
return None
for _, rvalue in helpers.iter_call_assignments(self.model_classdef):
if ('primary_key' in rvalue.arg_names
and self.api.parse_bool(rvalue.args[rvalue.arg_names.index('primary_key')])):
break
else:
self.add_new_node_to_model_class('id', self.api.builtin_type('builtins.object'))
class AddRelatedModelsId(ModelClassInitializer):
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)
field_info = self.lookup_class_typeinfo_or_incomplete_defn_error(rel_primary_key_field.__class__)
is_nullable = self.django_context.fields_context.get_field_nullability(field, None)
set_type, get_type = get_field_descriptor_types(field_info, is_nullable)
self.add_new_node_to_model_class(field.attname,
Instance(field_info, [set_type, get_type]))
class AddRelatedManagers(ModelClassInitializer):
def add_related_manager_variable(self, manager_name: str, related_field_type_data: Dict[str, Any]) -> None:
# add dummy related manager for use later
self.add_new_node_to_model_class(manager_name, self.api.builtin_type('builtins.object'))
class AddManagers(ModelClassInitializer):
def _is_manager_any(self, typ: Instance) -> bool:
return typ.type.fullname() == fullnames.MANAGER_CLASS_FULLNAME and type(typ.args[0]) == AnyType
# save name in metadata for use in get_attribute_hook later
related_managers_metadata = helpers.get_related_managers_metadata(self.model_classdef.info)
related_managers_metadata[manager_name] = related_field_type_data
def run_with_model_cls(self, model_cls: Type[Model]) -> None:
for manager_name, manager in model_cls._meta.managers_map.items():
manager_fullname = helpers.get_class_fullname(manager.__class__)
manager_info = self.lookup_typeinfo_or_incomplete_defn_error(manager_fullname)
def run(self) -> None:
for module_name, module_file in self.api.modules.items():
for model_defn in helpers.iter_over_classdefs(module_file):
for lvalue, rvalue in helpers.iter_call_assignments(model_defn):
if is_related_field(rvalue, module_file):
try:
referenced_model_fullname = extract_ref_to_fullname(rvalue,
module_file=module_file,
all_modules=self.api.modules)
except helpers.SelfReference:
referenced_model_fullname = model_defn.fullname
if manager_name not in self.model_classdef.info.names:
manager_type = Instance(manager_info, [Instance(self.model_classdef.info, [])])
self.add_new_node_to_model_class(manager_name, manager_type)
else:
# create new MODELNAME_MANAGERCLASSNAME class that represents manager parametrized with current model
has_manager_any_base = any(self._is_manager_any(base) for base in manager_info.bases)
if has_manager_any_base:
custom_model_manager_name = manager.model.__name__ + '_' + manager.__class__.__name__
bases = []
for original_base in manager_info.bases:
if self._is_manager_any(original_base):
if original_base.type is None:
if not self.api.final_iteration:
self.api.defer()
original_base = helpers.reparametrize_instance(original_base,
[Instance(self.model_classdef.info, [])])
bases.append(original_base)
current_module = self.api.modules[self.model_classdef.info.module_name]
custom_manager_info = helpers.add_new_class_for_module(current_module,
custom_model_manager_name,
bases=bases,
fields=OrderedDict())
custom_manager_type = Instance(custom_manager_info, [Instance(self.model_classdef.info, [])])
self.add_new_node_to_model_class(manager_name, custom_manager_type)
except helpers.SameFileModel as exc:
referenced_model_fullname = module_name + '.' + exc.model_cls_name
# add _default_manager
if '_default_manager' not in self.model_classdef.info.names:
default_manager_fullname = helpers.get_class_fullname(model_cls._meta.default_manager.__class__)
default_manager_info = self.lookup_typeinfo_or_incomplete_defn_error(default_manager_fullname)
default_manager = Instance(default_manager_info, [Instance(self.model_classdef.info, [])])
self.add_new_node_to_model_class('_default_manager', default_manager)
if self.model_classdef.fullname == referenced_model_fullname:
related_name = model_defn.name.lower() + '_set'
if 'related_name' in rvalue.arg_names:
related_name_expr = rvalue.args[rvalue.arg_names.index('related_name')]
if not isinstance(related_name_expr, StrExpr):
continue
related_name = related_name_expr.value
if related_name == '+':
# No backwards relation is desired
continue
# add related managers
for relation in self.django_context.get_model_relations(model_cls):
attname = relation.get_accessor_name()
if attname is None:
# no reverse accessor
continue
if 'related_query_name' in rvalue.arg_names:
related_query_name_expr = rvalue.args[rvalue.arg_names.index('related_query_name')]
if not isinstance(related_query_name_expr, StrExpr):
related_query_name = None
else:
related_query_name = related_query_name_expr.value
# TODO: Handle defaulting to model name if related_name is not set
else:
# No related_query_name specified, default to related_name
related_query_name = related_name
related_model_info = self.lookup_class_typeinfo_or_incomplete_defn_error(relation.related_model)
# field_type_data = get_related_field_type(rvalue, self.api, defn.info)
# if typ is None:
# continue
if isinstance(relation, OneToOneRel):
self.add_new_node_to_model_class(attname, Instance(related_model_info, []))
continue
# TODO: recursively serialize types, or just https://github.com/python/mypy/issues/6506
# as long as Model is not a Generic, one level depth is fine
if rvalue.callee.name in {'ForeignKey', 'ManyToManyField'}:
field_type_data = {
'manager': helpers.RELATED_MANAGER_CLASS_FULLNAME,
'of': [model_defn.info.fullname()]
}
# return api.named_type_or_none(helpers.RELATED_MANAGER_CLASS_FULLNAME,
# args=[Instance(related_model_typ, [])])
else:
field_type_data = {
'manager': model_defn.info.fullname(),
'of': []
}
self.add_related_manager_variable(related_name, related_field_type_data=field_type_data)
if related_query_name is not None:
# Only create related_query_name if it is a string literal
helpers.get_lookups_metadata(self.model_classdef.info)[related_query_name] = {
'related_query_name_target': related_name
}
if isinstance(relation, (ManyToOneRel, ManyToManyRel)):
manager_info = self.lookup_typeinfo_or_incomplete_defn_error(fullnames.RELATED_MANAGER_CLASS_FULLNAME)
self.add_new_node_to_model_class(attname,
Instance(manager_info, [Instance(related_model_info, [])]))
continue
def get_related_field_type(rvalue: CallExpr, related_model_typ: TypeInfo) -> Dict[str, Any]:
if rvalue.callee.name in {'ForeignKey', 'ManyToManyField'}:
return {
'manager': helpers.RELATED_MANAGER_CLASS_FULLNAME,
'of': [related_model_typ.fullname()]
}
# return api.named_type_or_none(helpers.RELATED_MANAGER_CLASS_FULLNAME,
# args=[Instance(related_model_typ, [])])
else:
return {
'manager': related_model_typ.fullname(),
'of': []
}
# return Instance(related_model_typ, [])
class AddExtraFieldMethods(ModelClassInitializer):
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:
info = self.lookup_typeinfo_or_incomplete_defn_error('builtins.str')
return_type = Instance(info, [])
common.add_method(self.ctx,
name='get_{}_display'.format(field.attname),
args=[],
return_type=return_type)
# get_next_by, get_previous_by for Date, DateTime
for field in self.django_context.get_model_fields(model_cls):
if isinstance(field, (DateField, DateTimeField)) and not field.null:
return_type = Instance(self.model_classdef.info, [])
common.add_method(self.ctx,
name='get_next_by_{}'.format(field.attname),
args=[Argument(Var('kwargs', AnyType(TypeOfAny.explicit)),
AnyType(TypeOfAny.explicit),
initializer=None,
kind=ARG_STAR2)],
return_type=return_type)
common.add_method(self.ctx,
name='get_previous_by_{}'.format(field.attname),
args=[Argument(Var('kwargs', AnyType(TypeOfAny.explicit)),
AnyType(TypeOfAny.explicit),
initializer=None,
kind=ARG_STAR2)],
return_type=return_type)
def is_related_field(expr: CallExpr, module_file: MypyFile) -> bool:
if isinstance(expr.callee, MemberExpr) and isinstance(expr.callee.expr, NameExpr):
module = module_file.names.get(expr.callee.expr.name)
if module \
and module.fullname == 'django.db.models' \
and expr.callee.name in {'ForeignKey',
'OneToOneField',
'ManyToManyField'}:
return True
return False
class AddMetaOptionsAttribute(ModelClassInitializer):
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',
Instance(options_info, [
Instance(self.model_classdef.info, [])
]))
def extract_ref_to_fullname(rvalue_expr: CallExpr,
module_file: MypyFile, all_modules: Dict[str, MypyFile]) -> Optional[str]:
if 'to' in rvalue_expr.arg_names:
to_expr = rvalue_expr.args[rvalue_expr.arg_names.index('to')]
else:
to_expr = rvalue_expr.args[0]
if isinstance(to_expr, NameExpr):
return module_file.names[to_expr.name].fullname
elif isinstance(to_expr, StrExpr):
typ_fullname = helpers.get_model_fullname_from_string(to_expr.value, all_modules)
if typ_fullname is None:
return None
return typ_fullname
return None
def add_dummy_init_method(ctx: ClassDefContext) -> None:
any = AnyType(TypeOfAny.special_form)
pos_arg = Argument(variable=Var('args', any),
type_annotation=any, initializer=None, kind=ARG_STAR)
kw_arg = Argument(variable=Var('kwargs', any),
type_annotation=any, initializer=None, kind=ARG_STAR2)
add_method(ctx, '__init__', [pos_arg, kw_arg], NoneTyp())
# mark as model class
ctx.cls.info.metadata.setdefault('django', {})['generated_init'] = True
def add_get_set_attr_fallback_to_any(ctx: ClassDefContext):
any = AnyType(TypeOfAny.special_form)
name_arg = Argument(variable=Var('name', any),
type_annotation=any, initializer=None, kind=ARG_POS)
add_method(ctx, '__getattr__', [name_arg], any)
value_arg = Argument(variable=Var('value', any),
type_annotation=any, initializer=None, kind=ARG_POS)
add_method(ctx, '__setattr__', [name_arg, value_arg], any)
def process_model_class(ctx: ClassDefContext, ignore_unknown_attributes: bool) -> None:
def process_model_class(ctx: ClassDefContext,
django_context: DjangoContext) -> None:
initializers = [
InjectAnyAsBaseForNestedMeta,
AddDefaultObjectsManager,
AddIdAttributeIfPrimaryKeyTrueIsNotSet,
SetIdAttrsForRelatedFields,
AddRelatedManagers,
AddDefaultPrimaryKey,
AddRelatedModelsId,
AddManagers,
AddExtraFieldMethods,
AddMetaOptionsAttribute
]
for initializer_cls in initializers:
initializer_cls.from_ctx(ctx).run()
add_dummy_init_method(ctx)
if ignore_unknown_attributes:
add_get_set_attr_fallback_to_any(ctx)
try:
initializer_cls(ctx, django_context).run()
except helpers.IncompleteDefnException:
if not ctx.api.final_iteration:
ctx.api.defer()
-209
View File
@@ -1,209 +0,0 @@
from collections import OrderedDict
from typing import List, Optional, cast
from mypy.checker import TypeChecker
from mypy.nodes import StrExpr, TypeInfo
from mypy.plugin import (
AnalyzeTypeContext, CheckerPluginInterface, MethodContext,
)
from mypy.types import AnyType, Instance, Type, TypeOfAny
from mypy_django_plugin import helpers
from mypy_django_plugin.lookups import (
LookupException, RelatedModelNode, resolve_lookup,
)
def get_queryset_model_arg(ret_type: Instance) -> Type:
if ret_type.args:
return ret_type.args[0]
else:
return AnyType(TypeOfAny.implementation_artifact)
def extract_proper_type_for_queryset_values(ctx: MethodContext) -> Type:
object_type = ctx.type
if not isinstance(object_type, Instance):
return ctx.default_return_type
fields_arg_expr = ctx.args[ctx.callee_arg_names.index('fields')]
if len(fields_arg_expr) == 0:
# values_list/values with no args is not yet supported, so default to Any types for field types
# It should in the future include all model fields, "extra" fields and "annotated" fields
return ctx.default_return_type
model_arg = get_queryset_model_arg(ctx.default_return_type)
if isinstance(model_arg, Instance):
model_type_info = model_arg.type
else:
model_type_info = None
column_types: OrderedDict[str, Type] = OrderedDict()
# parse *fields
for field_expr in fields_arg_expr:
if isinstance(field_expr, StrExpr):
field_name = field_expr.value
# Default to any type
column_types[field_name] = AnyType(TypeOfAny.implementation_artifact)
if model_type_info:
resolved_lookup_type = resolve_values_lookup(ctx.api, model_type_info, field_name)
if resolved_lookup_type is not None:
column_types[field_name] = resolved_lookup_type
else:
return ctx.default_return_type
# parse **expressions
expression_arg_names = ctx.arg_names[ctx.callee_arg_names.index('expressions')]
for expression_name in expression_arg_names:
# Arbitrary additional annotation expressions are supported, but they all have type Any for now
column_types[expression_name] = AnyType(TypeOfAny.implementation_artifact)
row_arg = helpers.make_typeddict(ctx.api, fields=column_types,
required_keys=set())
return helpers.reparametrize_instance(ctx.default_return_type, [model_arg, row_arg])
def extract_proper_type_queryset_values_list(ctx: MethodContext) -> Type:
object_type = ctx.type
if not isinstance(object_type, Instance):
return ctx.default_return_type
ret = ctx.default_return_type
model_arg = get_queryset_model_arg(ctx.default_return_type)
# model_arg: Union[AnyType, Type] = ret.args[0] if len(ret.args) > 0 else any_type
column_names: List[Optional[str]] = []
column_types: OrderedDict[str, Type] = OrderedDict()
fields_arg_expr = ctx.args[ctx.callee_arg_names.index('fields')]
fields_param_is_specified = True
if len(fields_arg_expr) == 0:
# values_list/values with no args is not yet supported, so default to Any types for field types
# It should in the future include all model fields, "extra" fields and "annotated" fields
fields_param_is_specified = False
if isinstance(model_arg, Instance):
model_type_info = model_arg.type
else:
model_type_info = None
any_type = AnyType(TypeOfAny.implementation_artifact)
# Figure out each field name passed to fields
only_strings_as_fields_expressions = True
for field_expr in fields_arg_expr:
if isinstance(field_expr, StrExpr):
field_name = field_expr.value
column_names.append(field_name)
# Default to any type
column_types[field_name] = any_type
if model_type_info:
resolved_lookup_type = resolve_values_lookup(ctx.api, model_type_info, field_name)
if resolved_lookup_type is not None:
column_types[field_name] = resolved_lookup_type
else:
# Dynamic field names are partially supported for values_list, but not values
column_names.append(None)
only_strings_as_fields_expressions = False
flat = helpers.parse_bool(helpers.get_argument_by_name(ctx, 'flat'))
named = helpers.parse_bool(helpers.get_argument_by_name(ctx, 'named'))
api = cast(TypeChecker, ctx.api)
if named and flat:
api.fail("'flat' and 'named' can't be used together.", ctx.context)
return ret
elif named:
# named=True, flat=False -> List[NamedTuple]
if fields_param_is_specified and only_strings_as_fields_expressions:
row_arg = helpers.make_named_tuple(api, fields=column_types, name="Row")
else:
# fallback to catch-all NamedTuple
row_arg = helpers.make_named_tuple(api, fields=OrderedDict(), name="Row")
elif flat:
# named=False, flat=True -> List of elements
if len(ctx.args[0]) > 1:
api.fail("'flat' is not valid when values_list is called with more than one field.",
ctx.context)
return ctx.default_return_type
if fields_param_is_specified and only_strings_as_fields_expressions:
# Grab first element
row_arg = column_types[column_names[0]]
else:
row_arg = any_type
else:
# named=False, flat=False -> List[Tuple]
if fields_param_is_specified:
args = [
# Fallback to Any if the column name is unknown (e.g. dynamic)
column_types.get(column_name, any_type) if column_name is not None else any_type
for column_name in column_names
]
else:
args = [any_type]
row_arg = helpers.make_tuple(api, fields=args)
new_type_args = [model_arg, row_arg]
return helpers.reparametrize_instance(ret, new_type_args)
def resolve_values_lookup(api: CheckerPluginInterface, model_type_info: TypeInfo, lookup: str) -> Optional[Type]:
"""Resolves a values/values_list lookup if possible, to a Type."""
try:
nodes = resolve_lookup(api, model_type_info, lookup)
except LookupException:
nodes = []
if not nodes:
return None
make_optional = False
for node in nodes:
if isinstance(node, RelatedModelNode) and node.is_nullable:
# All lookups following a relation which is nullable should be optional
make_optional = True
node = nodes[-1]
node_type = node.typ
if isinstance(node, RelatedModelNode):
# Related models used in values/values_list get resolved to the primary key of the related model.
# So, we lookup the pk of that model.
pk_lookup_nodes = resolve_lookup(api, node_type.type, "pk")
if not pk_lookup_nodes:
return None
node_type = pk_lookup_nodes[0].typ
if make_optional:
return helpers.make_optional(node_type)
else:
return node_type
def set_first_generic_param_as_default_for_second(fullname: str, ctx: AnalyzeTypeContext) -> Type:
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)
args = ctx.type.args
if len(args) == 1:
args = [args[0], args[0]]
analyzed_args = [ctx.api.analyze_type(arg) for arg in args]
try:
return ctx.api.named_type(fullname, analyzed_args)
except KeyError:
# really should never happen
return AnyType(TypeOfAny.explicit)
@@ -0,0 +1,191 @@
from collections import OrderedDict
from typing import List, Optional, Sequence, Type, Union, cast
from django.core.exceptions import FieldError
from django.db.models.base import Model
from mypy.newsemanal.typeanal import TypeAnalyser
from mypy.nodes import Expression, NameExpr, TypeInfo
from mypy.plugin import AnalyzeTypeContext, FunctionContext, MethodContext
from mypy.types import AnyType, Instance
from mypy.types import Type as MypyType
from mypy.types import TypeOfAny
from mypy_django_plugin.django.context import DjangoContext
from mypy_django_plugin.lib import fullnames, helpers
def determine_proper_manager_type(ctx: FunctionContext) -> MypyType:
default_return_type = ctx.default_return_type
assert isinstance(default_return_type, Instance)
outer_model_info = helpers.get_typechecker_api(ctx).scope.active_class()
if (outer_model_info is None
or not outer_model_info.has_base(fullnames.MODEL_CLASS_FULLNAME)):
return default_return_type
return helpers.reparametrize_instance(default_return_type, [Instance(outer_model_info, [])])
def get_field_type_from_lookup(ctx: MethodContext, django_context: DjangoContext, model_cls: Type[Model],
*, method: str, lookup: str) -> Optional[MypyType]:
try:
lookup_field = django_context.lookups_context.resolve_lookup(model_cls, lookup)
except FieldError as exc:
ctx.api.fail(exc.args[0], ctx.context)
return None
field_get_type = django_context.fields_context.get_field_get_type(helpers.get_typechecker_api(ctx),
lookup_field, method=method)
return field_get_type
def get_values_list_row_type(ctx: MethodContext, django_context: DjangoContext, model_cls: Type[Model],
flat: bool, named: bool) -> MypyType:
field_lookups = resolve_field_lookups(ctx.args[0], ctx, django_context)
if field_lookups is None:
return AnyType(TypeOfAny.from_error)
typechecker_api = helpers.get_typechecker_api(ctx)
if len(field_lookups) == 0:
if flat:
primary_key_field = django_context.get_primary_key_field(model_cls)
lookup_type = get_field_type_from_lookup(ctx, django_context, model_cls,
lookup=primary_key_field.attname, method='values_list')
assert lookup_type is not None
return lookup_type
elif named:
column_types: 'OrderedDict[str, MypyType]' = OrderedDict()
for field in django_context.get_model_fields(model_cls):
column_type = django_context.fields_context.get_field_get_type(typechecker_api, field,
method='values_list')
column_types[field.attname] = column_type
return helpers.make_oneoff_named_tuple(typechecker_api, 'Row', column_types)
else:
# flat=False, named=False, all fields
field_lookups = []
for field in django_context.get_model_fields(model_cls):
field_lookups.append(field.attname)
if len(field_lookups) > 1 and flat:
typechecker_api.fail("'flat' is not valid when 'values_list' is called with more than one field", ctx.context)
return AnyType(TypeOfAny.from_error)
column_types = OrderedDict()
for field_lookup in field_lookups:
lookup_field_type = get_field_type_from_lookup(ctx, django_context, model_cls,
lookup=field_lookup, method='values_list')
if lookup_field_type is None:
return AnyType(TypeOfAny.from_error)
column_types[field_lookup] = lookup_field_type
if flat:
assert len(column_types) == 1
row_type = next(iter(column_types.values()))
elif named:
row_type = helpers.make_oneoff_named_tuple(typechecker_api, 'Row', column_types)
else:
row_type = helpers.make_tuple(typechecker_api, list(column_types.values()))
return row_type
def extract_proper_type_queryset_values_list(ctx: MethodContext, django_context: DjangoContext) -> MypyType:
# called on the Instance, returns QuerySet of something
assert isinstance(ctx.type, Instance)
assert isinstance(ctx.default_return_type, Instance)
# bail if queryset of Any or other non-instances
if not isinstance(ctx.type.args[0], Instance):
return AnyType(TypeOfAny.from_omitted_generics)
model_type = ctx.type.args[0]
model_cls = django_context.get_model_class_by_fullname(model_type.type.fullname())
if model_cls is None:
return ctx.default_return_type
flat_expr = helpers.get_call_argument_by_name(ctx, 'flat')
if flat_expr is not None and isinstance(flat_expr, NameExpr):
flat = helpers.parse_bool(flat_expr)
else:
flat = False
named_expr = helpers.get_call_argument_by_name(ctx, 'named')
if named_expr is not None and isinstance(named_expr, NameExpr):
named = helpers.parse_bool(named_expr)
else:
named = False
if flat and named:
ctx.api.fail("'flat' and 'named' can't be used together", ctx.context)
return helpers.reparametrize_instance(ctx.default_return_type, [model_type, AnyType(TypeOfAny.from_error)])
# account for possible None
flat = flat or False
named = named or False
row_type = get_values_list_row_type(ctx, django_context, model_cls,
flat=flat, named=named)
return helpers.reparametrize_instance(ctx.default_return_type, [model_type, row_type])
def resolve_field_lookups(lookup_exprs: Sequence[Expression], ctx: Union[FunctionContext, MethodContext],
django_context: DjangoContext) -> Optional[List[str]]:
field_lookups = []
for field_lookup_expr in lookup_exprs:
field_lookup = helpers.resolve_string_attribute_value(field_lookup_expr, ctx, django_context)
if field_lookup is None:
return None
field_lookups.append(field_lookup)
return field_lookups
def extract_proper_type_queryset_values(ctx: MethodContext, django_context: DjangoContext) -> MypyType:
# called on QuerySet, return QuerySet of something
assert isinstance(ctx.type, Instance)
assert isinstance(ctx.default_return_type, Instance)
# if queryset of non-instance type
if not isinstance(ctx.type.args[0], Instance):
return AnyType(TypeOfAny.from_omitted_generics)
model_type = ctx.type.args[0]
model_cls = django_context.get_model_class_by_fullname(model_type.type.fullname())
if model_cls is None:
return ctx.default_return_type
field_lookups = resolve_field_lookups(ctx.args[0], ctx, django_context)
if field_lookups is None:
return AnyType(TypeOfAny.from_error)
if len(field_lookups) == 0:
for field in django_context.get_model_fields(model_cls):
field_lookups.append(field.attname)
column_types: 'OrderedDict[str, MypyType]' = OrderedDict()
for field_lookup in field_lookups:
field_lookup_type = get_field_type_from_lookup(ctx, django_context, model_cls,
lookup=field_lookup, method='values')
if field_lookup_type is None:
return helpers.reparametrize_instance(ctx.default_return_type, [model_type, AnyType(TypeOfAny.from_error)])
column_types[field_lookup] = field_lookup_type
row_type = helpers.make_typeddict(ctx.api, column_types, set(column_types.keys()))
return helpers.reparametrize_instance(ctx.default_return_type, [model_type, row_type])
def set_first_generic_param_as_default_for_second(ctx: AnalyzeTypeContext, fullname: str) -> MypyType:
type_analyser_api = cast(TypeAnalyser, ctx.api)
info = helpers.lookup_fully_qualified_typeinfo(type_analyser_api.api, fullname) # type: ignore
assert isinstance(info, TypeInfo)
if not ctx.type.args:
return Instance(info, [AnyType(TypeOfAny.explicit), AnyType(TypeOfAny.explicit)])
args = ctx.type.args
if len(args) == 1:
args = [args[0], args[0]]
analyzed_args = [type_analyser_api.analyze_type(arg) for arg in args]
return Instance(info, analyzed_args)
@@ -1,59 +0,0 @@
from typing import Optional, Union
from mypy.checkmember import AttributeContext
from mypy.nodes import TypeInfo
from mypy.types import AnyType, Instance, Type, TypeOfAny, UnionType
from mypy_django_plugin import helpers
def _extract_referred_to_type_info(typ: Union[UnionType, Instance]) -> Optional[TypeInfo]:
if isinstance(typ, Instance):
return typ.type
else:
# should be Union[TYPE, None]
typ = helpers.make_required(typ)
if isinstance(typ, Instance):
return typ.type
return None
def extract_and_return_primary_key_of_bound_related_field_parameter(ctx: AttributeContext) -> Type:
if not isinstance(ctx.default_attr_type, Instance) or not (ctx.default_attr_type.type.fullname() == 'builtins.int'):
return ctx.default_attr_type
if not isinstance(ctx.type, Instance) or not ctx.type.type.has_base(helpers.MODEL_CLASS_FULLNAME):
return ctx.default_attr_type
field_name = ctx.context.name.split('_')[0]
sym = ctx.type.type.get(field_name)
if sym and isinstance(sym.type, Instance) and len(sym.type.args) > 0:
referred_to = sym.type.args[1]
if isinstance(referred_to, AnyType):
return AnyType(TypeOfAny.implementation_artifact)
model_type = _extract_referred_to_type_info(referred_to)
if model_type is None:
return AnyType(TypeOfAny.implementation_artifact)
primary_key_type = helpers.extract_primary_key_type_for_get(model_type)
if primary_key_type:
return primary_key_type
is_nullable = helpers.is_field_nullable(ctx.type.type, field_name)
if is_nullable:
return helpers.make_optional(ctx.default_attr_type)
return ctx.default_attr_type
def determine_type_of_related_manager(ctx: AttributeContext, related_manager_name: str) -> Type:
if not isinstance(ctx.type, Instance):
return ctx.default_attr_type
related_manager_type = helpers.get_related_manager_type_from_metadata(ctx.type.type,
related_manager_name, ctx.api)
if not related_manager_type:
return ctx.default_attr_type
return related_manager_type
@@ -0,0 +1,16 @@
from mypy.plugin import AttributeContext
from mypy.types import Instance
from mypy.types import Type as MypyType
from mypy_django_plugin.django.context import DjangoContext
from mypy_django_plugin.lib import helpers
def set_auth_user_model_as_type_for_request_user(ctx: AttributeContext, django_context: DjangoContext) -> MypyType:
auth_user_model = django_context.settings.AUTH_USER_MODEL
model_cls = django_context.apps_registry.get_model(auth_user_model)
model_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), model_cls)
if model_info is None:
return ctx.default_attr_type
return Instance(model_info, [])
+44 -75
View File
@@ -1,81 +1,50 @@
from typing import TYPE_CHECKING, List, Optional, cast
from mypy.nodes import MemberExpr
from mypy.plugin import AttributeContext, FunctionContext
from mypy.types import AnyType, Instance
from mypy.types import Type as MypyType
from mypy.types import TypeOfAny, TypeType
from mypy.checkexpr import FunctionContext
from mypy.checkmember import AttributeContext
from mypy.nodes import NameExpr, StrExpr, SymbolTableNode, TypeInfo
from mypy.types import AnyType, Instance, Type, TypeOfAny, TypeType
from mypy_django_plugin import helpers
if TYPE_CHECKING:
from mypy.checker import TypeChecker
from mypy_django_plugin.django.context import DjangoContext
from mypy_django_plugin.lib import helpers
def get_setting_sym(name: str, api: 'TypeChecker', settings_modules: List[str]) -> Optional[SymbolTableNode]:
for settings_mod_name in settings_modules:
if settings_mod_name not in api.modules:
continue
def get_user_model_hook(ctx: FunctionContext, django_context: DjangoContext) -> MypyType:
auth_user_model = django_context.settings.AUTH_USER_MODEL
model_cls = django_context.apps_registry.get_model(auth_user_model)
model_cls_fullname = helpers.get_class_fullname(model_cls)
file = api.modules[settings_mod_name]
sym = file.names.get(name)
if sym is not None:
return sym
model_info = helpers.lookup_fully_qualified_typeinfo(helpers.get_typechecker_api(ctx),
model_cls_fullname)
if model_info is None:
return AnyType(TypeOfAny.unannotated)
return None
def get_type_of_setting(ctx: AttributeContext, setting_name: str,
settings_modules: List[str], ignore_missing_settings: bool) -> Type:
setting_sym = get_setting_sym(setting_name, ctx.api, settings_modules)
if setting_sym:
if setting_sym.type is None:
# TODO: defer till setting_sym.type is not None
return AnyType(TypeOfAny.implementation_artifact)
return setting_sym.type
if not ignore_missing_settings:
ctx.api.fail(f"'Settings' object has no attribute {setting_name!r}", ctx.context)
return ctx.default_attr_type
def return_user_model_hook(ctx: FunctionContext, settings_modules: List[str]) -> Type:
from mypy.checker import TypeChecker
api = cast(TypeChecker, ctx.api)
setting_sym = get_setting_sym('AUTH_USER_MODEL', api, settings_modules)
if setting_sym is None:
return ctx.default_return_type
setting_module_name, _, _ = setting_sym.fullname.rpartition('.')
setting_module = api.modules[setting_module_name]
model_path = None
for name_expr, rvalue_expr in helpers.iter_over_assignments(setting_module):
if isinstance(name_expr, NameExpr) and isinstance(rvalue_expr, StrExpr):
if name_expr.name == 'AUTH_USER_MODEL':
model_path = rvalue_expr.value
break
if not model_path:
return ctx.default_return_type
app_label, _, model_class_name = model_path.rpartition('.')
if app_label is None:
return ctx.default_return_type
model_fullname = helpers.get_model_fullname(app_label, model_class_name,
all_modules=api.modules)
if model_fullname is None:
api.fail(f'"{app_label}.{model_class_name}" model class is not imported so far. Try to import it '
f'(under if TYPE_CHECKING) at the beginning of the current file',
context=ctx.context)
return ctx.default_return_type
model_info = helpers.lookup_fully_qualified_generic(model_fullname,
all_modules=api.modules)
if model_info is None or not isinstance(model_info, TypeInfo):
return ctx.default_return_type
return TypeType(Instance(model_info, []))
def get_type_of_settings_attribute(ctx: AttributeContext, django_context: DjangoContext) -> MypyType:
assert isinstance(ctx.context, MemberExpr)
setting_name = ctx.context.name
if not hasattr(django_context.settings, setting_name):
ctx.api.fail(f"'Settings' object has no attribute {setting_name!r}", ctx.context)
return ctx.default_attr_type
typechecker_api = helpers.get_typechecker_api(ctx)
# first look for the setting in the project settings file, then global settings
settings_module = typechecker_api.modules.get(django_context.django_settings_module)
global_settings_module = typechecker_api.modules.get('django.conf.global_settings')
for module in [settings_module, global_settings_module]:
if module is not None:
sym = module.names.get(setting_name)
if sym is not None and sym.type is not None:
return sym.type
# if by any reason it isn't present there, get type from django settings
value = getattr(django_context.settings, setting_name)
value_fullname = helpers.get_class_fullname(value.__class__)
value_info = helpers.lookup_fully_qualified_typeinfo(typechecker_api, value_fullname)
if value_info is None:
return ctx.default_attr_type
return Instance(value_info, [])
+1 -1
View File
@@ -1,3 +1,3 @@
[tool.black]
line-length = 120
include = 'django-stubs/.*.pyi$'
include = 'django-stubs/.*\.pyi$'
+3 -1
View File
@@ -2,6 +2,8 @@
testpaths = ./test-data
addopts =
--tb=native
--mypy-ini-file=./test-data/plugins.ini
-s
-v
--cache-clear
--mypy-ini-file=./test-data/plugins.ini
--mypy-extension-hook=scripts.tests_extension_hook.django_plugin_hook
View File
+229
View File
@@ -0,0 +1,229 @@
SECRET_KEY = '1'
SITE_ID = 1
INSTALLED_APPS = [
'django.contrib.contenttypes',
'django.contrib.auth',
'django.contrib.sites',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.admin.apps.SimpleAdminConfig',
'django.contrib.staticfiles',
]
test_modules = [
'absolute_url_overrides',
'admin_autodiscover',
'admin_changelist',
'admin_checks',
'admin_custom_urls',
'admin_default_site',
'admin_docs',
'admin_filters',
'admin_inlines',
'admin_ordering',
'admin_registration',
'admin_scripts',
'admin_utils',
'admin_views',
'admin_widgets',
'aggregation',
'aggregation_regress',
'annotations',
'app_loading',
'apps',
'auth_tests',
'backends',
'base',
'bash_completion',
'basic',
'builtin_server',
'bulk_create',
'cache',
'check_framework',
'choices',
'conditional_processing',
'constraints',
'contenttypes_tests',
'context_processors',
'csrf_tests',
'custom_columns',
'custom_lookups',
'custom_managers',
'custom_methods',
'custom_migration_operations',
'custom_pk',
'datatypes',
'dates',
'datetimes',
'db_functions',
'db_typecasts',
'db_utils',
'dbshell',
'decorators',
'defer',
'defer_regress',
'delete',
'delete_regress',
'deprecation',
'dispatch',
'distinct_on_fields',
'empty',
'expressions',
'expressions_case',
'expressions_window',
'extra_regress',
'field_deconstruction',
'field_defaults',
'field_subclassing',
'file_storage',
'file_uploads',
'files',
'filtered_relation',
'fixtures',
'fixtures_model_package',
'fixtures_regress',
'flatpages_tests',
'force_insert_update',
'foreign_object',
'forms_tests',
'from_db_value',
'generic_inline_admin',
'generic_relations',
'generic_relations_regress',
'generic_views',
'get_earliest_or_latest',
'get_object_or_404',
'get_or_create',
'gis_tests',
'handlers',
'httpwrappers',
'humanize_tests',
'i18n',
'import_error_package',
'indexes',
'inline_formsets',
'inspectdb',
'introspection',
'invalid_models_tests',
'known_related_objects',
'logging_tests',
'lookup',
'm2m_and_m2o',
'm2m_intermediary',
'm2m_multiple',
'm2m_recursive',
'm2m_regress',
'm2m_signals',
'm2m_through',
'm2m_through_regress',
'm2o_recursive',
'mail',
'managers_regress',
'many_to_many',
'many_to_one',
'many_to_one_null',
'max_lengths',
'messages_tests',
'middleware',
'middleware_exceptions',
'migrate_signals',
'migration_test_data_persistence',
'migrations',
'migrations2',
'model_fields',
'model_forms',
'model_formsets',
'model_formsets_regress',
'model_indexes',
'model_inheritance',
'model_inheritance_regress',
'model_meta',
'model_options',
'model_package',
'model_regress',
'modeladmin',
'multiple_database',
'mutually_referential',
'nested_foreign_keys',
'no_models',
'null_fk',
'null_fk_ordering',
'null_queries',
'one_to_one',
'or_lookups',
'order_with_respect_to',
'ordering',
'pagination',
'postgres_tests',
'prefetch_related',
'project_template',
'properties',
'proxy_model_inheritance',
'proxy_models',
'queries',
'queryset_pickle',
'raw_query',
'redirects_tests',
'requests',
'reserved_names',
'resolve_url',
'responses',
'reverse_lookup',
'save_delete_hooks',
'schema',
'select_for_update',
'select_related',
'select_related_onetoone',
'select_related_regress',
'serializers',
'servers',
'sessions_tests',
'settings_tests',
'shell',
'shortcuts',
'signals',
'signed_cookies_tests',
'signing',
'sitemaps_tests',
'sites_framework',
'sites_tests',
'staticfiles_tests',
'str',
'string_lookup',
'swappable_models',
'syndication_tests',
'template_backends',
'template_loader',
'template_tests',
'test_client',
'test_client_regress',
'test_exceptions',
'test_runner',
'test_runner_apps',
'test_utils',
'timezones',
'transaction_hooks',
'transactions',
'unmanaged_models',
'update',
'update_only_fields',
'urlpatterns',
'urlpatterns_reverse',
'user_commands',
'utils_tests',
'validation',
'validators',
'version',
'view_tests',
'wsgi',
]
invalid_apps = {
'import_error_package',
}
for app in invalid_apps:
test_modules.remove(app)
INSTALLED_APPS += test_modules
+337
View File
@@ -0,0 +1,337 @@
# Some errors occur for the test suite itself, and cannot be addressed via django-stubs. They should be ignored
# using this constant.
import re
IGNORED_MODULES = {'schema', 'gis_tests', 'admin_widgets', 'admin_filters', 'migrations',
'sitemaps_tests', 'staticfiles_tests', 'modeladmin', 'model_forms',
'generic_views', 'forms_tests', 'flatpages_tests', 'admin_utils',
'admin_ordering', 'admin_changelist', 'admin_views', 'mail', 'redirects_tests',
'invalid_models_tests', 'i18n', 'migrate_signals', 'model_formsets',
'template_tests', 'template_backends', 'test_runner', 'admin_scripts',
'sites_tests', 'inline_formsets', 'foreign_object', 'cache', 'test_client', 'test_client_regress'}
MOCK_OBJECTS = ['MockRequest', 'MockCompiler', 'modelz', 'call_count', 'call_args_list',
'call_args', 'MockUser', 'Xtemplate', 'DummyRequest', 'DummyUser', 'MinimalUser']
EXTERNAL_MODULES = ['psycopg2', 'PIL', 'selenium', 'oracle', 'mysql', 'sqlite3', 'sqlparse', 'tblib', 'numpy',
'bcrypt', 'argon2', 'xml.dom']
IGNORED_ERRORS = {
'__new_common__': [
*MOCK_OBJECTS,
*EXTERNAL_MODULES,
'SupportsFloat',
'Need type annotation for',
'has no attribute "getvalue"',
'Cannot assign to a method',
'Cannot infer type of lambda',
'already defined (possibly by an import)',
'already defined on line',
'Cannot assign to a type',
'"HttpResponse" has no attribute',
'"HttpResponseBase" has no attribute',
# '"HttpRequest" has no attribute',
'"object" has no attribute',
'defined in the current module',
re.compile(r'"Callable\[(\[(Any(, )?)*((, )?VarArg\(Any\))?((, )?KwArg\(Any\))?\]|\.\.\.), Any\]" '
r'has no attribute'),
'has no attribute "deconstruct"',
# private members
re.compile(r'has no attribute ("|\')_[a-zA-Z_]+("|\')'),
"'Settings' object has no attribute",
'**Dict',
re.compile(r"Expression of type '.*' is not supported"),
'has incompatible type "object"',
'undefined in superclass',
'Argument after ** must be a mapping, not "object"',
'note:',
re.compile(r'Item "None" of "[a-zA-Z_ ,\[\]]+" has no attribute'),
'"Optional[List[_Record]]"',
'"Callable[..., None]" has no attribute',
'does not return a value',
'has no attribute "alternatives"',
'gets multiple values for keyword argument',
'"Handler" has no attribute',
'Module has no attribute',
"No installed app with label 'missing'",
'namedtuple',
'Lookups not supported yet',
'Argument 1 to "loads" has incompatible type',
# TODO: see test in managers/test_managers.yml
"Cannot determine type of",
'cache_clear',
'cache_info',
'Incompatible types in assignment (expression has type "None", variable has type Module)',
"Module 'django.contrib.messages.storage.fallback' has no attribute 'CookieStorage'",
# TODO: not supported yet
'GenericRelation',
'RelatedObjectDoesNotExist',
# Rel's attributes are not accessible from `get_field()`
re.compile(r'"Field\[Any, Any\]" has no attribute '
r'"(through|field_name|field|get_related_field|related_model|related_name'
r'|get_accessor_name|empty_strings_allowed|many_to_many)"'),
# TODO: multitable inheritance
'ptr',
'Incompatible types in assignment (expression has type "Callable[',
'SimpleLazyObject'
],
'apps': [
'Incompatible types in assignment (expression has type "str", target has type "type")',
],
'auth_tests': [
'"PasswordValidator" has no attribute "min_length"',
'AbstractBaseUser',
'Argument "password_validators" to "password_changed" has incompatible type "Tuple[Validator]"; '
+ 'expected "Optional[Sequence[PasswordValidator]]"',
'Unsupported right operand type for in ("object")',
'mock_getpass',
'Unsupported left operand type for + ("Sequence[str]")',
'AuthenticationFormWithInactiveUsersOkay',
'Incompatible types in assignment (expression has type "Dict[str, Any]", variable has type "QueryDict")',
],
'basic': [
'Unexpected keyword argument "unknown_kwarg" for "refresh_from_db" of "Model"',
'Unexpected attribute "foo" for model "Article"',
'has no attribute "touched"'
],
'backends': [
'"DatabaseError" has no attribute "pgcode"'
],
'check_framework': [
'base class "Model" defined the type as "Callable',
],
'constraints': [
'Argument "condition" to "UniqueConstraint" has incompatible type "str"; expected "Optional[Q]"'
],
'contenttypes_tests': [
# 'Item "Model" of "Union[GenericForeignKey, Model, None]" has no attribute'
'base class "BaseOrderWithRespectToTests" defined the type as "None"'
],
'custom_lookups': [
'in base class "SQLFuncMixin"'
],
'custom_pk': [
'"Employee" has no attribute "id"'
],
'custom_managers': [
'Unsupported dynamic base class',
'"Book" has no attribute "favorite_avg"',
'Incompatible types in assignment (expression has type "CharField'
],
'csrf_tests': [
'Incompatible types in assignment (expression has type "property", ' +
'base class "HttpRequest" defined the type as "QueryDict")'
],
'dates': [
'Too few arguments for "dates" of "QuerySet"',
],
'defer': [
'Too many arguments for "refresh_from_db" of "Model"'
],
'dispatch': [
'Item "str" of "Union[ValueError, str]" has no attribute "args"'
],
'deprecation': [
'"Manager" has no attribute "old"',
'"Manager" has no attribute "new"'
],
'db_functions': [
'"FloatModel" has no attribute',
],
'decorators': [
'"Type[object]" has no attribute "method"'
],
'expressions_case': [
'Item "Field[Any, Any]" of "Union[Field[Any, Any], ForeignObjectRel]" has no attribute "is_relation"'
],
'expressions_window': [
'has incompatible type "str"'
],
'file_uploads': [
'"Iterable[Any]" has no attribute',
'"IO[Any]" has no attribute'
],
'file_storage': [
'Incompatible types in assignment (expression has type "Callable"'
],
'files': [
'Incompatible types in assignment (expression has type "IOBase", variable has type "File")',
],
'fixtures': [
'Incompatible types in assignment (expression has type "int", target has type "Iterable[str]")',
'Incompatible types in assignment (expression has type "SpyManager[Spy]"'
],
'from_db_value': [
'"Cash" has no attribute'
],
'get_object_or_404': [
'Argument 1 to "get_object_or_404" has incompatible type "str"; '
+ 'expected "Union[Type[<nothing>], QuerySet[<nothing>, <nothing>]]"',
'Argument 1 to "get_list_or_404" has incompatible type "List[Type[Article]]"; '
+ 'expected "Union[Type[<nothing>], QuerySet[<nothing>, <nothing>]]"',
'CustomClass'
],
'generic_relations_regress': [
'"Link" has no attribute'
],
'httpwrappers': [
'Argument 2 to "appendlist" of "QueryDict"',
'Incompatible types in assignment (expression has type "int", target has type "Union[str, List[str]]")',
'Argument 1 to "fromkeys" of "QueryDict" has incompatible type "int"'
],
'humanize_tests': [
'Argument 1 to "append" of "list" has incompatible type "None"; expected "str"'
],
'lookup': [
'Unexpected keyword argument "headline__startswith" for "in_bulk" of "QuerySet"',
'is called with more than one field'
],
'messages_tests': [
'List item 0 has incompatible type "Dict[str, Message]"; expected "Message"'
],
'middleware': [
'"HttpRequest" has no attribute'
],
'managers_regress': [
'"Type[AbstractBase3]" has no attribute "objects"'
],
'many_to_one': [
'Incompatible type for "parent" of "Child" (got "None", expected "Union[Parent, Combinable]")',
'Incompatible type for "parent" of "Child" (got "Child", expected "Union[Parent, Combinable]")'
],
'middleware_exceptions': [
'Argument 1 to "append" of "list" has incompatible type "Tuple[Any, Any]"; expected "str"'
],
'model_fields': [
'Item "Field[Any, Any]" of "Union[Field[Any, Any], ForeignObjectRel]" has no attribute',
'has no attribute "field"',
'Incompatible types in assignment (expression has type "Type[Person',
'base class "IntegerFieldTests"',
'ImageFieldTestMixin',
'Incompatible types in assignment (expression has type "FloatModel", variable has type',
],
'model_indexes': [
'Argument "condition" to "Index" has incompatible type "str"; expected "Optional[Q]"'
],
'model_inheritance': [
'base class "AbstractBase" defined',
'base class "AbstractModel" defined',
'Definition of "name" in base class "ConcreteParent"',
' Definition of "name" in base class "AbstractParent"',
'referent_references'
],
'model_meta': [
'List item 0 has incompatible type "str"; expected "Union[Field[Any, Any], ForeignObjectRel]"'
],
'model_regress': [
'Incompatible type for "department" of "Worker"',
'"PickledModel" has no attribute',
'"Department" has no attribute "evaluate"',
],
'multiple_database': [
'Unexpected attribute "extra_arg" for model "Book"'
],
'order_with_respect_to': [
'BaseOrderWithRespectToTests',
'"Dimension" has no attribute "set_component_order"',
],
'one_to_one': [
'expression has type "None", variable has type "UndergroundBar"'
],
'postgres_tests': [
'DummyArrayField',
'DummyJSONField',
'Incompatible types in assignment (expression has type "Type[Field[Any, Any]]',
'Argument "encoder" to "JSONField" has incompatible type "DjangoJSONEncoder";',
re.compile(r'Incompatible types in assignment \(expression has type "Type\[.+?\]", '
r'base class "(UnaccentTest|TrigramTest)" defined the type as "Type\[CharFieldModel\]"\)'),
'("None" and "SearchQuery")',
# TODO:
'django.contrib.postgres.forms',
'django.contrib.postgres.aggregates',
],
'properties': [
re.compile('Unexpected attribute "(full_name|full_name_2)" for model "Person"')
],
'prefetch_related': [
'"Person" has no attribute "houses_lst"',
'"Book" has no attribute "first_authors"',
'"Book" has no attribute "the_authors"',
'Incompatible types in assignment (expression has type "List[Room]", variable has type "QuerySet[Room, Room]")',
'"Room" has no attribute "main_room_of_attr"',
'"Room" has no attribute "house_attr"'
],
'proxy_models': [
'Incompatible types in assignment',
'in base class "User"'
],
'queries': [
'Incompatible types in assignment (expression has type "None", variable has type "str")',
'Invalid index type "Optional[str]" for "Dict[str, int]"; expected type "str"',
'Unsupported operand types for & ("QuerySet[Author, Author]" and "QuerySet[Tag, Tag]")',
'Unsupported operand types for | ("QuerySet[Author, Author]" and "QuerySet[Tag, Tag]")',
'ObjectA',
'ObjectB',
'ObjectC',
"'flat' and 'named' can't be used together",
],
'requests': [
'Incompatible types in assignment (expression has type "Dict[str, str]", variable has type "QueryDict")'
],
'responses': [
'Argument 1 to "TextIOWrapper" has incompatible type "HttpResponse"; expected "IO[bytes]"'
],
'settings_tests': [
'Argument 1 to "Settings" has incompatible type "Optional[str]"; expected "str"'
],
'signals': [
'Argument 1 to "append" of "list" has incompatible type "Tuple[Any, Any, Optional[Any], Any]";'
],
'syndication_tests': [
'List or tuple expected as variable arguments'
],
'sessions_tests': [
'base class "SessionTestsMixin" defined the type as "None")',
'Incompatible types in assignment (expression has type "None", variable has type "int")',
'"AbstractBaseSession" has no attribute'
],
'select_related_onetoone': [
'Incompatible types in assignment (expression has type "Parent2", variable has type "Parent1")',
'"Parent1" has no attribute'
],
'servers': [
re.compile('Argument [0-9] to "WSGIRequestHandler"'),
'"HTTPResponse" has no attribute',
'"type" has no attribute',
'"WSGIRequest" has no attribute "makefile"'
],
'serializers': [
'"SerializersTestBase" defined the type as "None"',
'"Model" has no attribute "data"',
'"Iterable[Any]" has no attribute "content"',
],
'transactions': [
'Incompatible types in assignment (expression has type "Thread", variable has type "Callable[[], Any]")'
],
'urlpatterns': [
'"object" not callable'
],
'urlpatterns_reverse': [
'List or tuple expected as variable arguments',
'No overload variant of "zip" matches argument types "Any", "object"',
'Argument 1 to "get_callable" has incompatible type "int"'
],
'utils_tests': [
'Too few arguments for "__init__"',
'Argument 1 to "activate" has incompatible type "None"; expected "Union[tzinfo, str]"',
'Incompatible types in assignment (expression has type "None", base class "object" defined the type as',
'Class',
'has no attribute "cp"',
'Argument "name" to "cached_property" has incompatible type "int"; expected "Optional[str]"',
'has no attribute "sort"',
'Unsupported target for indexed assignment',
'defined the type as "None"',
'Argument 1 to "Path" has incompatible type "Optional[str]"'
],
'view_tests': [
"Module 'django.views.debug' has no attribute 'Path'"
]
}
+4 -1
View File
@@ -1,6 +1,6 @@
[mypy]
strict_optional = True
ignore_missing_imports = False
ignore_missing_imports = True
check_untyped_defs = True
warn_no_return = False
show_traceback = True
@@ -9,3 +9,6 @@ incremental = True
plugins =
mypy_django_plugin.main
[mypy.plugins.django-stubs]
django_settings_module = 'django_tests_settings'
+30
View File
@@ -0,0 +1,30 @@
from pytest_mypy.collect import File
from pytest_mypy.item import YamlTestItem
def django_plugin_hook(test_item: YamlTestItem) -> None:
custom_settings = test_item.parsed_test_data.get('custom_settings', '')
installed_apps = test_item.parsed_test_data.get('installed_apps', None)
if installed_apps and custom_settings:
raise ValueError('"installed_apps" and "custom_settings" are not compatible, please use one or the other')
if installed_apps is not None:
# custom_settings is empty, add INSTALLED_APPS
installed_apps += ['django.contrib.contenttypes']
installed_apps_as_str = '(' + ','.join([repr(app) for app in installed_apps]) + ',)'
custom_settings += 'INSTALLED_APPS = ' + installed_apps_as_str
if 'SECRET_KEY' not in custom_settings:
custom_settings = 'SECRET_KEY = "1"\n' + custom_settings
django_settings_section = "\n[mypy.plugins.django-stubs]\n" \
"django_settings_module = mysettings"
if not test_item.additional_mypy_config:
test_item.additional_mypy_config = django_settings_section
else:
if '[mypy.plugins.django-stubs]' not in test_item.additional_mypy_config:
test_item.additional_mypy_config += django_settings_section
mysettings_file = File(path='mysettings.py', content=custom_settings)
test_item.files.append(mysettings_file)
-1
View File
@@ -1 +0,0 @@
gitpython
+31 -832
View File
@@ -1,809 +1,23 @@
import itertools
import os
import re
import shutil
import subprocess
import sys
from contextlib import contextmanager
from pathlib import Path
from typing import Pattern
from git import Repo
from mypy import build
from mypy.main import process_options
from scripts.enabled_test_modules import IGNORED_ERRORS, IGNORED_MODULES
PROJECT_DIRECTORY = Path(__file__).parent.parent
# Django branch to typecheck against
DJANGO_BRANCH = 'stable/2.2.x'
# Specific commit in the Django repository to check against
DJANGO_COMMIT_SHA = '395cf7c37514b642c4bcf30e01fc1a2c4f82b2fe'
# Some errors occur for the test suite itself, and cannot be addressed via django-stubs. They should be ignored
# using this constant.
MOCK_OBJECTS = ['MockRequest', 'MockCompiler', 'modelz', 'call_count', 'call_args_list', 'call_args', 'MockUser']
IGNORED_ERRORS = {
'__common__': [
*MOCK_OBJECTS,
'LazySettings',
'NullTranslations',
'Need type annotation for',
'Invalid value for a to= parameter',
'already defined (possibly by an import)',
'already defined on line',
'gets multiple values for keyword argument',
'Cannot assign to a type',
re.compile(r'Cannot assign to class variable "[a-z_]+" via instance'),
# forms <-> models plugin support
# '"Model" has no attribute',
re.compile(r'Cannot determine type of \'(objects|stuff)\''),
# settings
re.compile(r'Module has no attribute "[A-Z_]+"'),
# attributes assigned to test functions
re.compile(
r'"Callable\[(\[(Any(, )?)*((, )?VarArg\(Any\))?((, )?KwArg\(Any\))?\]|\.\.\.), Any\]" has no attribute'),
# assign empty tuple
re.compile(r'Incompatible types in assignment \(expression has type "Tuple\[\]", '
r'variable has type "Tuple\[[A-Za-z, ]+\]"'),
# assign method to a method
'Cannot assign to a method',
'Cannot infer type of lambda',
re.compile(r'Incompatible types in assignment \(expression has type "Callable\[\[(Any(, )?)+\], Any\]", '
r'variable has type "Callable\['),
# cookies private attribute
'full_clean" of "Model" does not return a value',
# private members
re.compile(r'has no attribute ("|\')_[a-zA-Z_]+("|\')'),
'Invalid base class',
'ValuesIterable',
'Value of type "Optional[Dict[str, Any]]" is not indexable',
'Argument 1 to "len" has incompatible type "Optional[List[_Record]]"; expected "Sized"',
'Argument 1 to "loads" has incompatible type "Union[bytes, str, None]"; '
+ 'expected "Union[str, bytes, bytearray]"',
'Incompatible types in assignment (expression has type "None", variable has type Module)',
'note:',
'\'Settings\' object has no attribute',
re.compile(r'"Type\[Model\]" has no attribute "[a-zA-Z_]+"'),
re.compile(r'Item "None" of "[a-zA-Z_ ,\[\]]+" has no attribute'),
'Xtemplate',
re.compile(r'has no attribute "get_[a-z_]+_display"'),
re.compile(r'has no attribute "get_next_by_[a-z_]+"'),
re.compile(r'has no attribute "get_previous_by_[a-z_]+"'),
re.compile(r'has no attribute "set_[a-z_]+_order"'),
'psycopg2',
'PIL',
'has no attribute "getvalue"',
'MySQLdb',
'sqlparse',
'selenium',
'oracle',
'mysql',
'sqlite3',
'LogEntry',
'"HttpResponse" has no attribute',
'"HttpResponseBase" has no attribute',
'"object" has no attribute',
'"HttpRequest" has no attribute',
'xml.dom',
'numpy',
'tblib',
'bcrypt',
'argon2',
# TODO: values().annotate()
'TypedDict',
'namedtuple',
'has no attribute "deconstruct"',
'**Dict',
'undefined in superclass',
'SimpleLazyObject'
],
'admin_scripts': [
'Incompatible types in assignment (expression has type "Callable['
],
'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]")',
'"Article" has no attribute "non_field"'
],
'admin_views': [
'Argument 1 to "FileWrapper" has incompatible type "StringIO"; expected "IO[bytes]"',
'Incompatible types in assignment',
'"object" not callable',
'"Type[SubscriberAdmin]" has no attribute "overridden"'
],
'admin_ordering': [
'"Band" has no attribute "field"'
],
'aggregation': [
'Incompatible type for "contact" of "Book" (got "Optional[Author]", expected "Union[Author, Combinable]")',
'Incompatible type for "publisher" of "Book" (got "Optional[Publisher]", '
+ 'expected "Union[Publisher, Combinable]")',
'has no attribute'
],
'aggregation_regress': [
'has no attribute'
],
'annotations': [
'Incompatible type for "store" of "Employee" (got "Optional[Store]", expected "Union[Store, Combinable]")',
'"Book" has no attribute',
'"Employee" has no attribute',
'Item "Book" of',
'Item "Ticket" of'
],
'apps': [
'Incompatible types in assignment (expression has type "str", target has type "type")',
'"Callable[[bool, bool], List[Type[Model]]]" has no attribute "cache_clear"'
],
'auth_tests': [
'"PasswordValidator" has no attribute "min_length"',
'"validate_password" does not return a value',
'"password_changed" does not return a value',
re.compile(r'"validate" of "([A-Za-z]+)" does not return a value'),
'Module has no attribute "SessionStore"',
'AbstractBaseUser',
'Argument "user" to "password_changed" has incompatible type "object"',
'Argument "password_validators" to "password_changed" has incompatible type "Tuple[Validator]"; '
+ 'expected "Optional[Sequence[PasswordValidator]]"',
'Value of type "Optional[List[_Record]]" is not indexable',
'"Model" has no attribute',
'Incompatible type for "id" of "User"',
re.compile(r'Module has no attribute "(update|revert)_proxy_model_permissions"'),
'Unsupported right operand type for in ("object")',
'mock_getpass',
'Unsupported left operand type for + ("Sequence[str]")',
'AuthenticationFormWithInactiveUsersOkay',
'has no attribute "alternatives"',
'Incompatible types in assignment (expression has type "Dict[str, Any]", variable has type "QueryDict")'
],
'basic': [
'Unexpected keyword argument "unknown_kwarg" for "refresh_from_db" of "Model"',
'Unexpected attribute "foo" for model "Article"',
'has no attribute'
],
'backends': [
'"DatabaseError" has no attribute "pgcode"'
],
'check_framework': [
'base class "Model" defined the type as "Callable',
'Cannot determine type of \'check\''
],
'constraints': [
'Argument "condition" to "UniqueConstraint" has incompatible type "str"; expected "Optional[Q]"'
],
'contenttypes_tests': [
'Item "Model" of "Union[GenericForeignKey, Model, None]" has no attribute'
],
'custom_lookups': [
'in base class "SQLFuncMixin"'
],
'custom_pk': [
'"Employee" has no attribute "id"'
],
'custom_managers': [
'"Type[Book]" has no attribute "objects"'
],
'csrf_tests': [
'Incompatible types in assignment (expression has type "property", ' +
'base class "HttpRequest" defined the type as "QueryDict")'
],
'dates': [
'Too few arguments for "dates" of "QuerySet"',
],
'defer': [
'Too many arguments for "refresh_from_db" of "Model"'
],
'dispatch': [
'Argument 1 to "connect" of "Signal" has incompatible type "object"; expected "Callable[..., Any]"',
'Item "str" of "Union[ValueError, str]" has no attribute "args"'
],
'deprecation': [
'"Manager" has no attribute "old"',
'"Manager" has no attribute "new"'
],
'db_functions': [
'for **',
'expected "float"',
'Incompatible types in assignment (expression has type "Optional[FloatModel]", variable has type "FloatModel")',
re.compile(r'Item .* has no attribute'),
'Module has no attribute',
'Module \'datetime\' has no attribute',
'"DTModel" has no attribute',
'"Author" has no attribute',
'"FloatModel" has no attribute',
'"Article" has no attribute'
],
'decorators': [
'DummyRequest',
'DummyUser',
'"Type[object]" has no attribute "method"'
],
'defer_regress': [
'"Base" has no attribute "derived"'
],
'delete': [
'"RChild" has no attribute',
'"Child" has no attribute "parent_ptr"'
],
'expressions': [
'Item "Experiment" of',
'Item "Time" of',
'"Experiment" has no attribute',
'"Time" has no attribute',
],
'expressions_case': [
'"CaseTestModel" has no attribute "selected"'
],
'expressions_window': [
'has incompatible type "str"'
],
'extra_regress': [
'"User" has no attribute "alpha"'
],
'file_uploads': [
'"Iterable[Any]" has no attribute',
'"IO[Any]" has no attribute'
],
'file_storage': [
'Incompatible types in assignment (expression has type "Callable[[], Any]"'
],
'files': [
'"file_move_safe" does not return a value',
'Incompatible types in assignment (expression has type "IOBase", variable has type "File")',
'Module has no attribute "open"'
],
'fixtures': [
'Incompatible types in assignment (expression has type "int", target has type "Iterable[str]")'
],
'flatpages_tests': [
'"Site" has no attribute "add"',
],
'forms_tests': [
'List item 0 has incompatible type "Jinja2"; expected "DjangoTemplates"',
'Not enough arguments for format string',
'Argument after ** must be a mapping, not "object"',
'expression has type "None", base class "TestFormParent"',
'variable has type "SongForm"',
'"full_clean" of "BaseForm" does not return a value',
'No overload variant of "zip" matches argument types "Tuple[str, str, str]", "object"',
'Incompatible types in assignment (expression has type "GetDateShowHiddenInitial", '
+ 'variable has type "GetDate")',
re.compile(r'Incompatible types in assignment \(expression has type "[a-zA-Z]+Field", '
r'base class "BaseForm" defined the type as "Dict\[str, Any\]"\)'),
'List or tuple expected as variable arguments',
'Argument 1 to "__init__" of "MultiWidget" has incompatible type "List[object]"; '
+ 'expected "Sequence[Union[Widget, Type[Widget]]]"',
'Argument 1 to "issubclass" has incompatible type "ModelFormMetaclass"; expected "type"',
'Incompatible types in assignment (expression has type "List[str]", target has type "str")',
'Incompatible types in assignment (expression has type "TestForm", variable has type "Person")',
'Incompatible types in assignment (expression has type "Type[Textarea]", '
+ 'base class "Field" defined the type as "Widget")',
'Incompatible types in assignment (expression has type "SimpleUploadedFile", variable has type "BinaryIO")',
'has no attribute',
'Name \'forms.Field\' is not defined'
],
'foreign_object': [
'"Person" has no attribute',
'"SlugPage" has no attribute'
],
'from_db_value': [
'"Cash" has no attribute'
],
'get_object_or_404': [
'Argument 1 to "get_object_or_404" has incompatible type "str"; '
+ 'expected "Union[Type[<nothing>], QuerySet[<nothing>, <nothing>]]"',
'Argument 1 to "get_list_or_404" has incompatible type "List[Type[Article]]"; '
+ 'expected "Union[Type[<nothing>], QuerySet[<nothing>, <nothing>]]"',
'CustomClass'
],
'generic_relations': [
'has no attribute "id"',
'has no attribute "pk"'
],
'generic_relations_regress': [
'"HasLinkThing" has no attribute',
'"Link" has no attribute'
],
'httpwrappers': [
'Argument 2 to "appendlist" of "QueryDict"',
'Incompatible types in assignment (expression has type "int", target has type "Union[str, List[str]]")',
'Argument 1 to "fromkeys" of "QueryDict" has incompatible type "int"'
],
'humanize_tests': [
'Argument 1 to "append" of "list" has incompatible type "None"; expected "str"'
],
'inline_formsets': [
'has no attribute "form"'
],
'i18n': [
'Incompatible types in assignment (expression has type "I18nForm", variable has type "SelectDateForm")',
'has incompatible type "object"',
'Value of type "Optional[List[_Record]]" is not indexable',
'"Command" has no attribute'
],
'logging_tests': [
'"Handler" has no attribute "stream"'
],
'lookup': [
'Unexpected keyword argument "headline__startswith" for "in_bulk" of "QuerySet"',
'\'flat\' is not valid when values_list is called with more than one field.'
],
'm2o_recursive': [
'Incompatible type for "id" of "Category" (got "None", expected "int")'
],
'many_to_one': [
'Incompatible type for "parent" of "Child" (got "None", expected "Union[Parent, Combinable]")',
'Incompatible type for "parent" of "Child" (got "Child", expected "Union[Parent, Combinable]")'
],
'managers_regress': [
'"Type[AbstractBase3]" has no attribute "objects"'
],
'middleware_exceptions': [
'Argument 1 to "append" of "list" has incompatible type "Tuple[Any, Any]"; expected "str"'
],
'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',
re.compile(
r'Incompatible types in assignment \(expression has type "Type\[.+?\]", base class "IntegerFieldTests"'
r' defined the type as "Type\[IntegerModel\]"\)'),
re.compile(r'Incompatible types in assignment \(expression has type "Type\[.+?\]", base class'
r' "ImageFieldTestMixin" defined the type as "Type\[PersonWithHeightAndWidth\]"\)'),
'Incompatible import of "Person"',
'Incompatible types in assignment (expression has type "FloatModel", variable has type '
'"Union[float, int, str, Combinable]")',
'"UUIDGrandchild" has no attribute "uuidchild_ptr_id"',
'"Person" has no attribute',
'"Foo" has no attribute',
],
'model_formsets': [
'has no attribute'
],
'model_indexes': [
'Argument "condition" to "Index" has incompatible type "str"; expected "Optional[Q]"'
],
'model_inheritance_regress': [
'"Restaurant" has no attribute',
'"ArticleWithAuthor" has no attribute',
'"Person" has no attribute',
'"MessyBachelorParty" has no attribute',
'"Place" has no attribute',
'"ItalianRestaurant" has no attribute'
],
'model_meta': [
'"Field[Any, Any]" has no attribute "many_to_many"'
],
'model_regress': [
re.compile(r'Incompatible type for "[a-z]+" of "Worker" \(got "int", expected'),
'"PickledModel" has no attribute',
'"Department" has no attribute'
],
'modeladmin': [
'BandAdmin',
'base class "ModelAdmin" defined the type a',
'base class "InlineModelAdmin" defined the type a',
'List item 0 has incompatible type',
'Incompatible types in assignment (expression has type "None", base class "AdminBase" '
+ 'defined the type as "List[str]")'
],
'migrate_signals': [
'Value of type "None" is not indexable',
'Argument 1 to "set" has incompatible type "None"; expected "Iterable[<nothing>]"'
],
'migrations': [
'FakeMigration',
'FakeLoader',
'Dict entry 0 has incompatible type "Any": "Set[Tuple[Any, ...]]"; expected "Any": "str"',
'Argument 1 to "RunPython" has incompatible type "str"; expected "Callable[..., Any]"',
'Argument 1 to "append" of "list" has incompatible type "AddIndex"; expected "CreateModel"',
'Argument 2 to "register_serializer" of "MigrationWriter" has incompatible type '
+ '"Type[TestModel1]"; expected "Type[BaseSerializer]"',
'Argument 1 to "append" of "list" has incompatible type "AddConstraint"; expected "CreateModel"'
],
'multiple_database': [
'Too many arguments for "create" of "QuerySet"',
'"User" has no attribute "userprofile"',
'Item "GenericForeignKey" of',
'Item "Model" of'
],
'known_related_objects': [
'"Pool" has no attribute'
],
'one_to_one': [
'"Place" has no attribute',
'"Type[Place]" has no attribute',
'"ManualPrimaryKey" has no attribute'
],
'postgres_tests': [
'DummyArrayField',
'DummyJSONField',
'Cannot assign multiple types to name',
'Incompatible types in assignment (expression has type "Type[Field[Any, Any]]',
'Argument "encoder" to "JSONField" has incompatible type "DjangoJSONEncoder"; '
+ 'expected "Optional[Type[JSONEncoder]]"',
'Incompatible type for "field" of "IntegerArrayModel" (got "None", '
+ 'expected "Union[Sequence[int], Combinable]")',
re.compile(r'Incompatible types in assignment \(expression has type "Type\[.+?\]", '
r'base class "(UnaccentTest|TrigramTest)" defined the type as "Type\[CharFieldModel\]"\)'),
'"Type[PostgreSQLModel]" has no attribute "objects"',
'("None" and "SearchQuery")',
# TODO:
'django.contrib.postgres.forms',
'django.contrib.postgres.aggregates',
],
'properties': [
re.compile('Unexpected attribute "(full_name|full_name_2)" for model "Person"')
],
'prefetch_related': [
'Incompatible types in assignment (expression has type "List[Room]", variable has type "QuerySet[Room, Room]")',
'"Person" has no attribute',
'"Author" has no attribute',
'"Book" has no attribute',
'Item "Room" of',
'"AuthorWithAge" has no attribute',
'has no attribute "read_by"'
],
'proxy_model_inheritance': [
'Incompatible import of "ProxyModel"'
],
'queries': [
'Incompatible types in assignment (expression has type "None", variable has type "str")',
'Invalid index type "Optional[str]" for "Dict[str, int]"; expected type "str"',
'No overload variant of "values_list" of "QuerySet" matches argument types "str", "bool", "bool"',
'Unsupported operand types for & ("QuerySet[Author, Author]" and "QuerySet[Tag, Tag]")',
'Unsupported operand types for | ("QuerySet[Author, Author]" and "QuerySet[Tag, Tag]")',
'Incompatible types in assignment (expression has type "ObjectB", variable has type "ObjectA")',
'Incompatible types in assignment (expression has type "ObjectC", variable has type "ObjectA")',
'Incompatible type for "objectb" of "ObjectC" (got "ObjectA", expected'
' "Union[ObjectB, Combinable, None, None]")',
'"Note" has no attribute',
'"Ranking" has no attribute',
'"BaseUser" has no attribute',
'"Item" has no attribute',
"'flat' and 'named' can't be used together.",
],
'queryset_pickle': [
'RelatedObjectDoesNotExist',
'"Type[Event]" has no attribute "happening"'
],
'requests': [
'Incompatible types in assignment (expression has type "Dict[str, str]", variable has type "QueryDict")'
],
'responses': [
'Argument 1 to "TextIOWrapper" has incompatible type "HttpResponse"; expected "IO[bytes]"'
],
'schema': [
'Incompatible type for "info" of "Note" (got "None", expected "Union[str, Combinable]")',
'Incompatible type for "detail_info" of "NoteRename" (got "None", expected "Union[str, Combinable]")',
'Incompatible type for "year" of "UniqueTest" (got "None", expected "Union[float, int, str, Combinable]")'
],
'settings_tests': [
'Argument 1 to "Settings" has incompatible type "Optional[str]"; expected "str"'
],
'signals': [
'Argument 1 to "append" of "list" has incompatible type "Tuple[Any, Any, Optional[Any], Any]"; '
+ 'expected "Tuple[Any, Any, Any]"'
],
'sites_tests': [
'Item "RequestSite" of "Union[Site, RequestSite]" has no attribute "id"'
],
'syndication_tests': [
'List or tuple expected as variable arguments'
],
'sessions_tests': [
'base class "SessionTestsMixin" defined the type as "None")',
'Incompatible types in assignment (expression has type "None", variable has type "int")',
'"AbstractBaseSession" has no attribute'
],
'select_related': [
'"Species" has no attribute',
'Item "object" of "Union[object, Any]" has no attribute "first"'
],
'select_related_onetoone': [
'Incompatible types in assignment (expression has type "Parent2", variable has type "Parent1")',
'has no attribute'
],
'select_for_update': [
'has incompatible type "object"'
],
'servers': [
re.compile('Argument [0-9] to "WSGIRequestHandler"'),
'"HTTPResponse" has no attribute',
'"type" has no attribute',
'"WSGIRequest" has no attribute "makefile"'
],
'template_tests': [
re.compile(r'Argument 1 to "[a-zA-Z_]+" has incompatible type "int"; expected "str"'),
'TestObject',
'variable has type "Callable[[Any], Any]',
'Value of type variable "AnyStr" of "urljoin" cannot be "Optional[str]"',
'has no attribute "template_debug"',
"Module 'django.template.base' has no attribute 'TemplateSyntaxError'"
],
'template_backends': [
'Incompatible import of "Jinja2" (imported name has type "Type[Jinja2]", local name has type "object")',
'TemplateStringsTests',
"Cannot find module named 'DoesNotExist'"
],
'test_client': [
'Incompatible types in assignment (expression has type "HttpResponse", '
+ 'variable has type "StreamingHttpResponse")'
],
'test_client_regress': [
'Incompatible types in assignment (expression has type "Dict[<nothing>, <nothing>]", '
+ 'variable has type "SessionBase")',
'Unsupported left operand type for + ("None")',
],
'test_runner': [
'Argument "result" to "run" of "TestCase" has incompatible type "RemoteTestResult"; '
+ 'expected "Optional[TestResult]"',
'has no attribute "id"',
'MockTestRunner'
],
'transactions': [
'Incompatible types in assignment (expression has type "Thread", variable has type "Callable[[], Any]")'
],
'urlpatterns': [
'"object" not callable'
],
'user_commands': [
'Incompatible types in assignment (expression has type "Callable[[Any, KwArg(Any)], Any]", variable has type',
'Cannot find module named \'user_commands.management.commands\''
],
'utils_tests': [
'a_package_name_that_does_not_exist',
'Too few arguments for "__init__"',
'Argument 1 to "activate" has incompatible type "None"; expected "Union[tzinfo, str]"',
'Module has no attribute "content"',
'Argument 1 to "int_to_base36" has incompatible type "object"; expected "int"',
'Argument 1 to "base36_to_int" has incompatible type "object"; expected "str"',
'Incompatible types in assignment (expression has type "None", base class "object" defined the type as',
'Invalid type "Class"',
'has no attribute "cp"',
'Argument "name" to "cached_property" has incompatible type "int"; expected "Optional[str]"',
'foo',
'"ImmutableList[int]" has no attribute "sort"',
'has no attribute "cache_clear"',
'has no attribute "cache_info"',
'Argument 1 to "Path" has incompatible type "Optional[str]"; expected "Union[str, _PathLike[str]]"',
'defined the type as "None"'
],
'view_tests': [
'"EmailMessage" has no attribute "alternatives"',
'"Handler" has no attribute "include_html"',
"Module 'django.views.debug' has no attribute 'Path'"
]
}
# Test folders to typecheck
TESTS_DIRS = [
'absolute_url_overrides',
'admin_autodiscover',
'admin_changelist',
'admin_checks',
'admin_custom_urls',
'admin_default_site',
'admin_docs',
# TODO: 'admin_filters',
'admin_inlines',
'admin_ordering',
'admin_registration',
'admin_scripts',
'admin_utils',
'admin_views',
'admin_widgets',
'aggregation',
'aggregation_regress',
'annotations',
'app_loading',
'apps',
'auth_tests',
'backends',
'base',
'bash_completion',
'basic',
'builtin_server',
'bulk_create',
# TODO: 'cache',
'check_framework',
'choices',
'conditional_processing',
'constraints',
'contenttypes_tests',
'context_processors',
'csrf_tests',
'custom_columns',
'custom_lookups',
'custom_managers',
'custom_methods',
'custom_migration_operations',
'custom_pk',
'datatypes',
'dates',
'datetimes',
'db_functions',
'db_typecasts',
'db_utils',
'dbshell',
'decorators',
'defer',
'defer_regress',
'delete',
'delete_regress',
'deprecation',
'dispatch',
'distinct_on_fields',
'empty',
'expressions',
'expressions_case',
'expressions_window',
'extra_regress',
'field_deconstruction',
'field_defaults',
'field_subclassing',
'file_storage',
'file_uploads',
'files',
'filtered_relation',
'fixtures',
'fixtures_model_package',
'fixtures_regress',
'flatpages_tests',
'force_insert_update',
'foreign_object',
'forms_tests',
'from_db_value',
'generic_inline_admin',
'generic_relations',
'generic_relations_regress',
# TODO: 'generic_views',
'get_earliest_or_latest',
'get_object_or_404',
'get_or_create',
# TODO: 'gis_tests',
'handlers',
'httpwrappers',
'humanize_tests',
'i18n',
'import_error_package',
'indexes',
'inline_formsets',
'inspectdb',
'introspection',
# not practical
# 'invalid_models_tests',
'known_related_objects',
'logging_tests',
'lookup',
'm2m_and_m2o',
'm2m_intermediary',
'm2m_multiple',
'm2m_recursive',
'm2m_regress',
'm2m_signals',
'm2m_through',
'm2m_through_regress',
'm2o_recursive',
# TODO: 'mail',
'managers_regress',
'many_to_many',
'many_to_one',
'many_to_one_null',
'max_lengths',
# TODO: 'messages_tests',
'middleware',
'middleware_exceptions',
'migrate_signals',
'migration_test_data_persistence',
'migrations',
'migrations2',
'model_fields',
# TODO: 'model_forms',
'model_formsets',
'model_formsets_regress',
'model_indexes',
# TODO: 'model_inheritance',
'model_inheritance_regress',
'model_meta',
'model_options',
'model_package',
'model_regress',
'modeladmin',
'multiple_database',
'mutually_referential',
'nested_foreign_keys',
'no_models',
'null_fk',
'null_fk_ordering',
'null_queries',
'one_to_one',
'or_lookups',
'order_with_respect_to',
'ordering',
'pagination',
'postgres_tests',
'prefetch_related',
'project_template',
'properties',
'proxy_model_inheritance',
# TODO: 'proxy_models',
'queries',
'queryset_pickle',
'raw_query',
'redirects_tests',
'requests',
'reserved_names',
'resolve_url',
'responses',
'reverse_lookup',
'save_delete_hooks',
'schema',
'select_for_update',
'select_related',
'select_related_onetoone',
'select_related_regress',
# TODO: 'serializers',
'servers',
'sessions_tests',
'settings_tests',
'shell',
'shortcuts',
'signals',
'signed_cookies_tests',
'signing',
# TODO: 'sitemaps_tests',
'sites_framework',
'sites_tests',
# TODO: 'staticfiles_tests',
'str',
'string_lookup',
'swappable_models',
'syndication_tests',
'template_backends',
'template_loader',
'template_tests',
'test_client',
'test_client_regress',
'test_exceptions',
'test_runner',
'test_runner_apps',
'test_utils',
'timezones',
'transaction_hooks',
'transactions',
'unmanaged_models',
'update',
'update_only_fields',
'urlpatterns',
# not annotatable without annotation in test
# 'urlpatterns_reverse',
'user_commands',
'utils_tests',
'validation',
'validators',
'version',
'view_tests',
'wsgi',
]
@contextmanager
def cd(path):
"""Context manager to temporarily change working directories"""
if not path:
return
prev_cwd = Path.cwd().as_posix()
if isinstance(path, Path):
path = path.as_posix()
os.chdir(str(path))
try:
yield
finally:
os.chdir(prev_cwd)
def is_ignored(line: str, test_folder_name: str) -> bool:
for pattern in itertools.chain(IGNORED_ERRORS['__common__'],
if 'runtests' in line:
return True
if test_folder_name in IGNORED_MODULES:
return True
for pattern in itertools.chain(IGNORED_ERRORS['__new_common__'],
IGNORED_ERRORS.get(test_folder_name, [])):
if isinstance(pattern, Pattern):
if pattern.search(line):
@@ -828,24 +42,6 @@ def replace_with_clickable_location(error: str, abs_test_folder: Path) -> str:
return error.replace(raw_path, clickable_location)
def check_with_mypy(abs_path: Path, config_file_path: Path) -> int:
error_happened = False
with cd(abs_path):
sources, options = process_options(['--cache-dir', str(config_file_path.parent / '.mypy_cache'),
'--config-file', str(config_file_path),
str(abs_path)])
res = build.build(sources, options)
for error_line in res.errors:
if not is_ignored(error_line, abs_path.name):
error_happened = True
print(replace_with_clickable_location(error_line, abs_test_folder=abs_path))
return int(error_happened)
def get_absolute_path_for_test(test_dirname: str):
return (PROJECT_DIRECTORY / tests_root / test_dirname).absolute()
if __name__ == '__main__':
mypy_config_file = (PROJECT_DIRECTORY / 'scripts' / 'mypy.ini').absolute()
repo_directory = PROJECT_DIRECTORY / 'django-sources'
@@ -853,28 +49,31 @@ if __name__ == '__main__':
tests_root = repo_directory / 'tests'
global_rc = 0
# clone Django repository, if it does not exist
if not repo_directory.exists():
repo = Repo.clone_from('https://github.com/django/django.git', repo_directory)
else:
repo = Repo(repo_directory)
repo.remotes['origin'].pull(DJANGO_BRANCH)
repo.git.checkout(DJANGO_COMMIT_SHA)
if len(sys.argv) > 1:
tests_to_run = sys.argv[1:]
else:
tests_to_run = TESTS_DIRS
# copy django settings to the tests_root directory
shutil.copy(PROJECT_DIRECTORY / 'scripts' / 'django_tests_settings.py', tests_root)
try:
for dirname in tests_to_run:
abs_path = get_absolute_path_for_test(dirname)
print(f'Checking {abs_path}')
mypy_options = ['--cache-dir', str(mypy_config_file.parent / '.mypy_cache'),
'--config-file', str(mypy_config_file)]
mypy_options += [str(tests_root)]
rc = check_with_mypy(abs_path, mypy_config_file)
if rc != 0:
global_rc = 1
import distutils.spawn
mypy_executable = distutils.spawn.find_executable('mypy')
completed = subprocess.run([mypy_executable, *mypy_options], env={'PYTHONPATH': str(tests_root)},
stdout=subprocess.PIPE, cwd=str(tests_root))
sorted_lines = sorted(completed.stdout.decode().splitlines())
for line in sorted_lines:
try:
module_name = line.split('/')[0]
except IndexError:
module_name = 'unknown'
if not is_ignored(line, module_name):
if line.startswith(module_name):
print(replace_with_clickable_location(line, abs_test_folder=tests_root))
else:
print(line)
sys.exit(global_rc)
+11 -11
View File
@@ -1,11 +1,11 @@
import os
import sys
from distutils.core import setup
from typing import List
from setuptools import find_packages
def find_stub_files(name):
def find_stub_files(name: str) -> List[str]:
result = []
for root, dirs, files in os.walk(name):
for file in files:
@@ -21,27 +21,27 @@ with open('README.md', 'r') as f:
readme = f.read()
dependencies = [
'mypy>=0.710,<0.720',
'typing-extensions'
'mypy>=0.720,<0.730',
'typing-extensions',
'django',
# depends on psycopg2 because of Postgres' ArrayField support
'psycopg2'
]
if sys.version_info[:2] < (3, 7):
# dataclasses port for 3.6
dependencies += ['dataclasses']
setup(
name="django-stubs",
version="0.13.0",
description='Django mypy stubs',
version="1.0.0",
description='Mypy stubs for Django',
long_description=readme,
long_description_content_type='text/markdown',
license='MIT',
url="https://github.com/mkurnikov/django-stubs",
url="https://github.com/typeddjango/django-stubs",
author="Maksim Kurnikov",
author_email="maxim.kurnikov@gmail.com",
py_modules=[],
python_requires='>=3.6',
install_requires=dependencies,
packages=['django-stubs', *find_packages()],
packages=['django-stubs', *find_packages(exclude=['scripts'])],
package_data={'django-stubs': find_stub_files('django-stubs')},
classifiers=[
'Development Status :: 3 - Alpha',
-37
View File
@@ -1,37 +0,0 @@
[CASE missing_settings_ignored_flag]
[env MYPY_DJANGO_CONFIG=${MYPY_CWD}/mypy_django.ini]
[disable_cache]
from django.conf import settings
reveal_type(settings.NO_SUCH_SETTING) # N: Revealed type is 'Any'
[file mypy_django.ini]
[[mypy_django_plugin]
ignore_missing_settings = True
[/CASE]
[CASE django_settings_via_config_file]
[env MYPY_DJANGO_CONFIG=${MYPY_CWD}/mypy_django.ini]
[disable_cache]
from django.conf import settings
reveal_type(settings.MY_SETTING) # N: Revealed type is 'builtins.int'
[file mypy_django.ini]
[[mypy_django_plugin]
django_settings = mysettings
[file mysettings.py]
MY_SETTING: int = 1
[/CASE]
[CASE mypy_django_ini_in_current_directory_is_a_default]
[disable_cache]
from django.conf import settings
reveal_type(settings.MY_SETTING) # N: Revealed type is 'builtins.int'
[file mypy_django.ini]
[[mypy_django_plugin]
django_settings = mysettings
[file mysettings.py]
MY_SETTING: int = 1
[/CASE]
-144
View File
@@ -1,144 +0,0 @@
[CASE array_field_descriptor_access]
from django.db import models
from django.contrib.postgres.fields import ArrayField
class User(models.Model):
array = ArrayField(base_field=models.Field())
user = User()
reveal_type(user.array) # N: Revealed type is 'builtins.list*[Any]'
[/CASE]
[CASE array_field_base_field_parsed_into_generic_typevar]
from django.db import models
from django.contrib.postgres.fields import ArrayField
class User(models.Model):
members = ArrayField(base_field=models.IntegerField())
members_as_text = ArrayField(base_field=models.CharField(max_length=255))
user = User()
reveal_type(user.members) # N: Revealed type is 'builtins.list*[builtins.int]'
reveal_type(user.members_as_text) # N: Revealed type is 'builtins.list*[builtins.str]'
[/CASE]
[CASE test_model_fields_classes_present_as_primitives]
from django.db import models
class User(models.Model):
id = models.AutoField(primary_key=True)
small_int = models.SmallIntegerField()
name = models.CharField(max_length=255)
slug = models.SlugField(max_length=255)
text = models.TextField()
user = User()
reveal_type(user.id) # N: Revealed type is 'builtins.int'
reveal_type(user.small_int) # N: Revealed type is 'builtins.int*'
reveal_type(user.name) # N: Revealed type is 'builtins.str*'
reveal_type(user.slug) # N: Revealed type is 'builtins.str*'
reveal_type(user.text) # N: Revealed type is 'builtins.str*'
[/CASE]
[CASE test_model_field_classes_from_existing_locations]
from django.db import models
from django.contrib.postgres import fields as pg_fields
from decimal import Decimal
class Booking(models.Model):
id = models.AutoField(primary_key=True)
time_range = pg_fields.DateTimeRangeField(null=False)
some_decimal = models.DecimalField(max_digits=10, decimal_places=5)
booking = Booking()
reveal_type(booking.id) # N: Revealed type is 'builtins.int'
reveal_type(booking.time_range) # N: Revealed type is 'Any'
reveal_type(booking.some_decimal) # N: Revealed type is 'decimal.Decimal*'
[/CASE]
[CASE test_add_id_field_if_no_primary_key_defined]
from django.db import models
class User(models.Model):
pass
reveal_type(User().id) # N: Revealed type is 'builtins.int'
[/CASE]
[CASE test_do_not_add_id_if_field_with_primary_key_True_defined]
from django.db import models
class User(models.Model):
my_pk = models.IntegerField(primary_key=True)
reveal_type(User().my_pk) # N: Revealed type is 'builtins.int*'
reveal_type(User().id)
[out]
main:7: note: Revealed type is 'Any'
main:7: error: "User" has no attribute "id"
[/CASE]
[CASE test_meta_nested_class_allows_subclassing_in_multiple_inheritance]
from typing import Any
from django.db import models
class Mixin1(models.Model):
class Meta:
abstract = True
class Mixin2(models.Model):
class Meta:
abstract = True
class User(Mixin1, Mixin2):
pass
[/CASE]
[CASE test_inheritance_from_abstract_model_does_not_fail_if_field_with_id_exists]
from django.db import models
class Abstract(models.Model):
class Meta:
abstract = True
class User(Abstract):
id = models.AutoField(primary_key=True)
[/CASE]
[CASE test_primary_key_on_optional_queryset_method]
from django.db import models
class User(models.Model):
pass
reveal_type(User.objects.first().id)
[out]
main:4: note: Revealed type is 'Any'
main:4: error: Item "None" of "Optional[User]" has no attribute "id"
[/CASE]
[CASE standard_it_from_parent_model_could_be_overridden_with_non_integer_field_in_child_model]
from django.db import models
import uuid
class ParentModel(models.Model):
pass
class MyModel(ParentModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
reveal_type(MyModel().id) # N: Revealed type is 'uuid.UUID'
[/CASE]
[CASE blank_and_null_char_field_allows_none]
from django.db import models
class MyModel(models.Model):
nulltext=models.CharField(max_length=1, blank=True, null=True)
MyModel(nulltext="")
MyModel(nulltext=None)
MyModel().nulltext=None
reveal_type(MyModel().nulltext) # N: Revealed type is 'Union[builtins.str, None]'
[/CASE]
[CASE blank_and_not_null_charfield_does_not_allow_none]
from django.db import models
class MyModel(models.Model):
notnulltext=models.CharField(max_length=1, blank=True, null=False)
MyModel(notnulltext=None) # Should allow None in constructor
MyModel(notnulltext="")
MyModel().notnulltext = None # E: Incompatible types in assignment (expression has type "None", variable has type "Union[str, int, Combinable]")
reveal_type(MyModel().notnulltext) # N: Revealed type is 'builtins.str*'
[/CASE]
+134
View File
@@ -0,0 +1,134 @@
- case: test_model_fields_classes_present_as_primitives
main: |
from myapp.models import User
user = User(small_int=1, name='user', slug='user', text='user')
reveal_type(user.id) # N: Revealed type is 'builtins.int*'
reveal_type(user.small_int) # N: Revealed type is 'builtins.int*'
reveal_type(user.name) # N: Revealed type is 'builtins.str*'
reveal_type(user.slug) # N: Revealed type is 'builtins.str*'
reveal_type(user.text) # N: Revealed type is 'builtins.str*'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class User(models.Model):
id = models.AutoField(primary_key=True)
small_int = models.SmallIntegerField()
name = models.CharField(max_length=255)
slug = models.SlugField(max_length=255)
text = models.TextField()
- case: test_model_field_classes_from_existing_locations
main: |
from myapp.models import Booking
booking = Booking()
reveal_type(booking.id) # N: Revealed type is 'builtins.int*'
reveal_type(booking.time_range) # N: Revealed type is 'Any'
reveal_type(booking.some_decimal) # N: Revealed type is 'decimal.Decimal*'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
from django.contrib.postgres import fields as pg_fields
from decimal import Decimal
class Booking(models.Model):
id = models.AutoField(primary_key=True)
time_range = pg_fields.DateTimeRangeField(null=False)
some_decimal = models.DecimalField(max_digits=10, decimal_places=5)
- case: test_add_id_field_if_no_primary_key_defined
disable_cache: true
main: |
from myapp.models import User
reveal_type(User().id) # N: Revealed type is 'builtins.int*'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class User(models.Model):
pass
- case: test_do_not_add_id_if_field_with_primary_key_True_defined
disable_cache: true
main: |
from myapp.models import User
reveal_type(User().my_pk) # N: Revealed type is 'builtins.int*'
reveal_type(User().id)
out: |
main:3: note: Revealed type is 'Any'
main:3: error: "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):
my_pk = models.IntegerField(primary_key=True)
- case: blank_and_null_char_field_allows_none
main: |
from myapp.models import MyModel
MyModel(nulltext="")
MyModel(nulltext=None)
MyModel().nulltext=None
reveal_type(MyModel().nulltext) # N: Revealed type is 'Union[builtins.str, None]'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class MyModel(models.Model):
nulltext=models.CharField(max_length=1, blank=True, null=True)
- case: blank_and_not_null_charfield_does_not_allow_none
main: |
from myapp.models import MyModel
MyModel(notnulltext=None) # Should allow None in constructor
MyModel(notnulltext="")
MyModel().notnulltext = None # E: Incompatible types in assignment (expression has type "None", variable has type "Union[str, int, Combinable]")
reveal_type(MyModel().notnulltext) # N: Revealed type is 'builtins.str*'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class MyModel(models.Model):
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)
- case: fields_on_non_model_classes_resolve_to_field_type
main: |
from django.db import models
class MyClass:
myfield: models.IntegerField[int, int]
reveal_type(MyClass.myfield) # N: Revealed type is 'django.db.models.fields.IntegerField[builtins.int, builtins.int]'
reveal_type(MyClass().myfield) # N: Revealed type is 'django.db.models.fields.IntegerField[builtins.int, builtins.int]'
@@ -0,0 +1,21 @@
- case: generic_foreign_key_could_point_to_any_model_and_is_always_optional
main: |
from myapp.models import Tag, User
myuser = User()
Tag(content_object=None)
Tag(content_object=myuser)
Tag.objects.create(content_object=None)
Tag.objects.create(content_object=myuser)
reveal_type(Tag().content_object) # N: Revealed type is 'Union[Any, None]'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
from django.contrib.contenttypes import fields
class User(models.Model):
pass
class Tag(models.Model):
content_object = fields.GenericForeignKey()
@@ -0,0 +1,70 @@
- case: nullable_field_with_strict_optional_true
main: |
from myapp.models import MyModel
reveal_type(MyModel().text) # N: Revealed type is 'builtins.str*'
reveal_type(MyModel().text_nullable) # N: Revealed type is 'Union[builtins.str, None]'
MyModel().text = None # E: Incompatible types in assignment (expression has type "None", variable has type "Union[str, int, Combinable]")
MyModel().text_nullable = None
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class MyModel(models.Model):
text_nullable = models.CharField(max_length=100, null=True)
text = models.CharField(max_length=100)
- case: nullable_array_field
main: |
from myapp.models import MyModel
reveal_type(MyModel().lst) # N: Revealed type is 'Union[builtins.list[builtins.str], None]'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
from django.contrib.postgres.fields import ArrayField
class MyModel(models.Model):
lst = ArrayField(base_field=models.CharField(max_length=100), null=True)
- case: nullable_foreign_key
main: |
from myapp.models import Publisher, Book
reveal_type(Book().publisher) # N: Revealed type is 'Union[myapp.models.Publisher, None]'
Book().publisher = 11 # E: Incompatible types in assignment (expression has type "int", variable has type "Union[Publisher, Combinable, None]")
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class Publisher(models.Model):
pass
class Book(models.Model):
publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE, null=True)
- case: nullable_self_foreign_key
main: |
from myapp.models import Inventory
parent = Inventory()
core = Inventory(parent_id=parent.id)
reveal_type(core.parent_id) # N: Revealed type is 'Union[builtins.int, None]'
reveal_type(core.parent) # N: Revealed type is 'Union[myapp.models.Inventory, None]'
Inventory(parent=None)
Inventory(parent_id=None)
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class Inventory(models.Model):
parent = models.ForeignKey('self', on_delete=models.SET_NULL, null=True)
@@ -0,0 +1,35 @@
- case: array_field_descriptor_access
main: |
from myapp.models import User
user = User(array=[])
reveal_type(user.array) # N: Revealed type is 'builtins.list*[Any]'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
from django.contrib.postgres.fields import ArrayField
class User(models.Model):
array = ArrayField(base_field=models.Field())
- case: array_field_base_field_parsed_into_generic_typevar
main: |
from myapp.models import User
user = User()
reveal_type(user.members) # N: Revealed type is 'builtins.list*[builtins.int]'
reveal_type(user.members_as_text) # N: Revealed type is 'builtins.list*[builtins.str]'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
from django.contrib.postgres.fields import ArrayField
class User(models.Model):
members = ArrayField(base_field=models.IntegerField())
members_as_text = ArrayField(base_field=models.CharField(max_length=255))
+522
View File
@@ -0,0 +1,522 @@
- case: test_foreign_key_field_with_related_name
main: |
from myapp.models import Book, Publisher
book = Book()
reveal_type(book.publisher) # N: Revealed type is 'myapp.models.Publisher*'
publisher = Publisher()
reveal_type(publisher.books) # N: Revealed type is 'django.db.models.manager.RelatedManager[myapp.models.Book]'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class Publisher(models.Model):
pass
class Book(models.Model):
publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE,
related_name='books')
- case: foreign_key_field_creates_attribute_with_underscore_id
main: |
from myapp.models import Book
book = Book()
reveal_type(book.publisher_id) # N: Revealed type is 'builtins.int*'
reveal_type(book.owner_id) # N: Revealed type is 'builtins.int*'
installed_apps:
- django.contrib.auth
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class Publisher(models.Model):
pass
class Book(models.Model):
publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE)
owner = models.ForeignKey(db_column='model_id', to='auth.User', on_delete=models.CASCADE)
- case: foreign_key_field_different_order_of_params
main: |
from myapp.models import Book, Publisher
book = Book()
reveal_type(book.publisher) # N: Revealed type is 'myapp.models.Publisher*'
reveal_type(book.publisher2) # N: Revealed type is 'myapp.models.Publisher*'
publisher = Publisher()
reveal_type(publisher.books) # N: Revealed type is 'django.db.models.manager.RelatedManager[myapp.models.Book]'
reveal_type(publisher.books2) # N: Revealed type is 'django.db.models.manager.RelatedManager[myapp.models.Book]'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class Publisher(models.Model):
pass
class Book(models.Model):
publisher = models.ForeignKey(on_delete=models.CASCADE, to=Publisher,
related_name='books')
publisher2 = models.ForeignKey(to=Publisher, related_name='books2', on_delete=models.CASCADE)
- case: to_parameter_as_string_with_application_name__model_imported
main: |
from myapp2.models import Book
book = Book()
reveal_type(book.publisher) # N: Revealed type is 'myapp.models.Publisher*'
installed_apps:
- myapp
- myapp2
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class Publisher(models.Model):
pass
- path: myapp2/__init__.py
- path: myapp2/models.py
content: |
from django.db import models
class Book(models.Model):
publisher = models.ForeignKey(to='myapp.Publisher', on_delete=models.CASCADE)
- case: one_to_one_field_no_related_name
main: |
from myapp.models import User, Profile
reveal_type(User().profile) # N: Revealed type is 'myapp.models.Profile'
reveal_type(Profile().user) # N: Revealed type is 'myapp.models.User*'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class User(models.Model):
pass
class Profile(models.Model):
user = models.OneToOneField(to=User, on_delete=models)
- case: test_circular_dependency_in_imports_with_foreign_key
main: |
from myapp import models
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class App(models.Model):
def method(self) -> None:
reveal_type(self.views) # N: Revealed type is 'django.db.models.manager.RelatedManager[myapp.models.View]'
reveal_type(self.members) # N: Revealed type is 'django.db.models.manager.RelatedManager[myapp.models.Member]'
reveal_type(self.sheets) # N: Revealed type is 'django.db.models.manager.RelatedManager[myapp.models.Sheet]'
reveal_type(self.profile) # N: Revealed type is 'myapp.models.Profile'
class View(models.Model):
app = models.ForeignKey(to=App, related_name='views', on_delete=models.CASCADE)
class Member(models.Model):
app = models.ForeignKey(related_name='members', on_delete=models.CASCADE, to=App)
class Sheet(models.Model):
app = models.ForeignKey(App, related_name='sheets', on_delete=models.CASCADE)
class Profile(models.Model):
app = models.OneToOneField(App, related_name='profile', on_delete=models.CASCADE)
- case: test_circular_dependency_in_imports_with_string_based
main: |
from myapp.models import View
reveal_type(View().app.views) # N: Revealed type is 'django.db.models.manager.RelatedManager[myapp.models.View]'
reveal_type(View().app.unknown)
out: |
main:3: note: Revealed type is 'Any'
main:3: error: "App" has no attribute "unknown"
installed_apps:
- myapp
- myapp2
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
from myapp2.models import App
class View(models.Model):
app = models.ForeignKey(to=App, related_name='views', on_delete=models.CASCADE)
- path: myapp2/__init__.py
- path: myapp2/models.py
content: |
from django.db import models
class App(models.Model):
def method(self) -> None:
reveal_type(self.views) # N: Revealed type is 'django.db.models.manager.RelatedManager[myapp.models.View]'
- case: models_related_managers_work_with_direct_model_inheritance_and_with_inheritance_from_other_model
main: |
from myapp.models import App
reveal_type(App().views) # N: Revealed type is 'django.db.models.manager.RelatedManager[myapp.models.View]'
reveal_type(App().views2) # N: Revealed type is 'django.db.models.manager.RelatedManager[myapp.models.View2]'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class App(models.Model):
pass
class View(models.Model):
app = models.ForeignKey(to=App, on_delete=models.CASCADE, related_name='views')
class View2(View):
app2 = models.ForeignKey(to=App, on_delete=models.CASCADE, related_name='views2')
- case: models_imported_inside_init_file_foreign_key
main: |
from myapp2.models import View
reveal_type(View().app.views) # N: Revealed type is 'django.db.models.manager.RelatedManager[myapp2.models.View]'
installed_apps:
- myapp
- myapp2
files:
- path: myapp/__init__.py
- path: myapp/models/__init__.py
content: |
from .app import App
- path: myapp/models/app.py
content: |
from django.db import models
class App(models.Model):
pass
- path: myapp2/__init__.py
- path: myapp2/models.py
content: |
from django.db import models
from myapp.models import App
class View(models.Model):
app = models.ForeignKey(to='myapp.App', related_name='views', on_delete=models.CASCADE)
- case: models_imported_inside_init_file_one_to_one_field
main: |
from myapp2.models import Profile
reveal_type(Profile().user) # N: Revealed type is 'myapp.models.user.User*'
reveal_type(Profile().user.profile) # N: Revealed type is 'myapp2.models.Profile'
installed_apps:
- myapp
- myapp2
files:
- path: myapp/__init__.py
- path: myapp/models/__init__.py
content: |
from .user import User
- path: myapp/models/user.py
content: |
from django.db import models
class User(models.Model):
pass
- path: myapp2/__init__.py
- path: myapp2/models.py
content: |
from django.db import models
from myapp.models import User
class Profile(models.Model):
user = models.OneToOneField(to='myapp.User', related_name='profile', on_delete=models.CASCADE)
- case: models_triple_circular_reference
main: |
from myapp.models import App
reveal_type(App().owner) # N: Revealed type is 'myapp.models.user.User*'
reveal_type(App().owner.profile) # N: Revealed type is 'myapp.models.profile.Profile'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models/__init__.py
content: |
from .user import User
from .profile import Profile
from .app import App
- path: myapp/models/user.py
content: |
from django.db import models
class User(models.Model):
pass
- path: myapp/models/profile.py
content: |
from django.db import models
class Profile(models.Model):
user = models.OneToOneField(to='myapp.User', related_name='profile', on_delete=models.CASCADE)
- path: myapp/models/app.py
content: |
from django.db import models
class App(models.Model):
owner = models.ForeignKey(to='myapp.User', on_delete=models.CASCADE, related_name='apps')
- case: many_to_many_field_converts_to_queryset_of_model_type
main: |
from myapp.models import App, Member
reveal_type(Member().apps) # N: Revealed type is 'django.db.models.manager.RelatedManager*[myapp.models.App]'
reveal_type(App().members) # N: Revealed type is 'django.db.models.manager.RelatedManager[myapp.models.Member]'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class App(models.Model):
pass
class Member(models.Model):
apps = models.ManyToManyField(to=App, related_name='members')
- case: many_to_many_works_with_string_if_imported
main: |
from myapp.models import Member
reveal_type(Member().apps) # N: Revealed type is 'django.db.models.manager.RelatedManager*[myapp2.models.App]'
installed_apps:
- myapp
- myapp2
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class Member(models.Model):
apps = models.ManyToManyField(to='myapp2.App', related_name='members')
- path: myapp2/__init__.py
- path: myapp2/models.py
content: |
from django.db import models
class App(models.Model):
pass
- case: foreign_key_with_self
main: |
from myapp.models import User
reveal_type(User().parent) # N: Revealed type is 'myapp.models.User*'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class User(models.Model):
parent = models.ForeignKey('self', on_delete=models.CASCADE)
- case: many_to_many_with_self
main: |
from myapp.models import User
reveal_type(User().friends) # N: Revealed type is 'django.db.models.manager.RelatedManager*[myapp.models.User]'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class User(models.Model):
friends = models.ManyToManyField('self')
- case: recursively_checking_for_base_model_in_to_parameter
main: |
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class BaseModel(models.Model):
pass
class ParkingSpot(BaseModel):
pass
class Booking(BaseModel):
parking_spot = models.ForeignKey(to=ParkingSpot, null=True, on_delete=models.SET_NULL)
- case: if_no_related_name_is_passed_create_default_related_managers
main: |
from myapp.models import Publisher
reveal_type(Publisher().book_set) # N: Revealed type is 'django.db.models.manager.RelatedManager[myapp.models.Book]'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class Publisher(models.Model):
pass
class Book(models.Model):
publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE)
- case: underscore_id_attribute_has_set_type_of_primary_key_if_explicit
main: |
import datetime
from myapp.models import Book, Book2
reveal_type(Book().publisher_id) # N: Revealed type is 'builtins.str*'
Book(publisher_id=1)
Book(publisher_id='hello')
Book(publisher_id=datetime.datetime.now()) # E: Incompatible type for "publisher_id" of "Book" (got "datetime", expected "Union[str, int, Combinable, None]")
Book.objects.create(publisher_id=1)
Book.objects.create(publisher_id='hello')
reveal_type(Book2().publisher_id) # N: Revealed type is 'builtins.int*'
Book2(publisher_id=1)
Book2(publisher_id=[]) # E: Incompatible type for "publisher_id" of "Book2" (got "List[Any]", expected "Union[float, int, str, Combinable, None]")
Book2.objects.create(publisher_id=1)
Book2.objects.create(publisher_id=[]) # E: Incompatible type for "publisher_id" of "Book2" (got "List[Any]", expected "Union[float, int, str, Combinable]")
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
import datetime
class Publisher(models.Model):
mypk = models.CharField(max_length=100, primary_key=True)
class Book(models.Model):
publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE)
class Publisher2(models.Model):
mypk = models.IntegerField(primary_key=True)
class Book2(models.Model):
publisher = models.ForeignKey(to=Publisher2, on_delete=models.CASCADE)
- case: if_model_is_defined_as_name_of_the_class_look_for_it_in_the_same_file
main: |
from myapp.models import Book
reveal_type(Book().publisher) # N: Revealed type is 'myapp.models.Publisher*'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class Publisher(models.Model):
pass
class Book(models.Model):
publisher = models.ForeignKey(to='Publisher', on_delete=models.CASCADE)
- case: test_foreign_key_field_without_backwards_relation
main: |
from myapp.models import Book, Publisher
book = Book()
reveal_type(book.publisher) # N: Revealed type is 'myapp.models.Publisher*'
publisher = Publisher()
reveal_type(publisher.books)
reveal_type(publisher.books2) # N: Revealed type is 'django.db.models.manager.RelatedManager[myapp.models.Book]'
out: |
main:6: error: "Publisher" has no attribute "books"; maybe "books2"?
main:6: note: Revealed type is 'Any'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class Publisher(models.Model):
pass
class Book(models.Model):
publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE,
related_name='+')
publisher2 = models.ForeignKey(to=Publisher, on_delete=models.CASCADE,
related_name='books2')
- case: to_parameter_could_be_resolved_if_passed_from_settings
main: |
from myapp.models import Book
book = Book()
reveal_type(book.publisher) # N: Revealed type is 'myapp.models.Publisher*'
custom_settings: |
INSTALLED_APPS = ('django.contrib.contenttypes', 'myapp')
BOOK_RELATED_MODEL = 'myapp.Publisher'
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.conf import settings
from django.db import models
class Publisher(models.Model):
pass
class Book(models.Model):
publisher = models.ForeignKey(to=settings.BOOK_RELATED_MODEL, on_delete=models.CASCADE,
related_name='books')
- case: foreign_key_with_custom_app_name
main: |
from myapp.models import MyMain
reveal_type(MyMain().user) # N: Revealed type is 'myapp2.models.MyUser*'
installed_apps:
- myapp
- myapp2.apps.MyApp2Config
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class MyMain(models.Model):
user = models.ForeignKey('myapp2__user.MyUser', on_delete=models.CASCADE)
- path: myapp2/__init__.py
- path: myapp2/models.py
content: |
from django.db import models
class MyUser(models.Model):
pass
- path: myapp2/apps.py
content: |
from django.apps.config import AppConfig
class MyApp2Config(AppConfig):
name = 'myapp2'
label = 'myapp2__user'
- case: related_field_to_extracted_from_function
main: |
from myapp.models import Profile
reveal_type(Profile().user) # N: Revealed type is 'myapp.models.User*'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class User(models.Model):
pass
def get_user_model_name():
return 'myapp.User'
class Profile(models.Model):
user = models.ForeignKey(to=get_user_model_name(), on_delete=models.CASCADE)
- case: related_manager_name_defined_by_pattern
main: |
from myapp.models import Publisher
reveal_type(Publisher().books) # N: Revealed type is 'django.db.models.manager.RelatedManager[myapp.models.Book]'
reveal_type(Publisher().articles) # N: Revealed type is 'django.db.models.manager.RelatedManager[myapp.models.Article]'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class Publisher(models.Model):
pass
class Entry(models.Model):
class Meta:
abstract = True
publisher = models.ForeignKey(to=Publisher, related_name='%(class)ss', on_delete=models.CASCADE)
class Book(Entry):
pass
class Article(Entry):
pass
-41
View File
@@ -1,41 +0,0 @@
[CASE no_incompatible_meta_nested_class_false_positive]
from django.db import models
from django import forms
class Article(models.Model):
pass
class Category(models.Model):
pass
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = '__all__'
class CategoryForm(forms.ModelForm):
class Meta:
model = Category
fields = '__all__'
class CompositeForm(ArticleForm, CategoryForm):
pass
[/CASE]
[CASE formview_methods_on_forms_return_proper_types]
from typing import Any
from django import forms
from django.views.generic.edit import FormView
class MyForm(forms.ModelForm):
pass
class MyForm2(forms.ModelForm):
pass
class MyView(FormView):
form_class = MyForm
def post(self, request, *args: Any, **kwds: Any):
form_class = self.get_form_class()
reveal_type(form_class) # N: Revealed type is 'Type[main.MyForm]'
reveal_type(self.get_form(None)) # N: Revealed type is 'main.MyForm'
reveal_type(self.get_form()) # N: Revealed type is 'main.MyForm'
reveal_type(self.get_form(form_class)) # N: Revealed type is 'main.MyForm'
reveal_type(self.get_form(MyForm2)) # N: Revealed type is 'main.MyForm2'
[/CASE]
-402
View File
@@ -1,402 +0,0 @@
[CASE import_all_packages_to_trigger_stubs_check]
import django.apps
import django.apps.config
import django.apps.registry
import django.conf.global_settings
import django.conf.urls
import django.conf.urls.i18n
import django.conf.urls.static
import django.contrib.admin.actions
import django.contrib.admin.apps
import django.contrib.admin.checks
import django.contrib.admin.decorators
import django.contrib.admin.filters
import django.contrib.admin.forms
import django.contrib.admin.helpers
import django.contrib.admin.models
import django.contrib.admin.options
import django.contrib.admin.sites
import django.contrib.admin.templatetags
import django.contrib.admin.templatetags.admin_list
import django.contrib.admin.templatetags.admin_modify
import django.contrib.admin.templatetags.admin_static
import django.contrib.admin.templatetags.admin_urls
import django.contrib.admin.templatetags.base
import django.contrib.admin.templatetags.log
import django.contrib.admin.utils
import django.contrib.admin.views
import django.contrib.admin.views.autocomplete
import django.contrib.admin.views.decorators
import django.contrib.admin.views.main
import django.contrib.admin.widgets
import django.contrib.admindocs
import django.contrib.admindocs.middleware
import django.contrib.admindocs.utils
import django.contrib.admindocs.views
import django.contrib.auth.admin
import django.contrib.auth.apps
import django.contrib.auth.backends
import django.contrib.auth.base_user
import django.contrib.auth.checks
import django.contrib.auth.context_processors
import django.contrib.auth.decorators
import django.contrib.auth.forms
import django.contrib.auth.handlers
import django.contrib.auth.handlers.modwsgi
import django.contrib.auth.hashers
import django.contrib.auth.management.commands
import django.contrib.auth.management.commands.changepassword
import django.contrib.auth.management.commands.createsuperuser
import django.contrib.auth.middleware
import django.contrib.auth.mixins
import django.contrib.auth.models
import django.contrib.auth.password_validation
import django.contrib.auth.signals
import django.contrib.auth.tokens
import django.contrib.auth.validators
import django.contrib.auth.views
import django.contrib.contenttypes.admin
import django.contrib.contenttypes.apps
import django.contrib.contenttypes.checks
import django.contrib.contenttypes.fields
import django.contrib.contenttypes.forms
import django.contrib.contenttypes.management.commands
import django.contrib.contenttypes.management.commands.remove_stale_contenttypes
import django.contrib.contenttypes.models
import django.contrib.contenttypes.views
import django.contrib.flatpages.forms
import django.contrib.flatpages.middleware
import django.contrib.flatpages.models
import django.contrib.flatpages.sitemaps
import django.contrib.flatpages.templatetags
import django.contrib.flatpages.templatetags.flatpages
import django.contrib.flatpages.views
import django.contrib.humanize.templatetags
import django.contrib.humanize.templatetags.humanize
import django.contrib.messages.api
import django.contrib.messages.context_processors
import django.contrib.messages.middleware
import django.contrib.messages.storage
import django.contrib.messages.storage.base
import django.contrib.messages.storage.cookie
import django.contrib.messages.storage.fallback
import django.contrib.messages.storage.session
import django.contrib.messages.utils
import django.contrib.messages.views
import django.contrib.postgres.fields
import django.contrib.postgres.fields.array
import django.contrib.postgres.fields.citext
import django.contrib.postgres.fields.hstore
import django.contrib.postgres.fields.jsonb
import django.contrib.postgres.fields.mixins
import django.contrib.postgres.fields.ranges
import django.contrib.postgres.operations
import django.contrib.redirects
import django.contrib.redirects.middleware
import django.contrib.redirects.models
import django.contrib.sessions.backends
import django.contrib.sessions.backends.base
import django.contrib.sessions.backends.cache
import django.contrib.sessions.backends.cached_db
import django.contrib.sessions.backends.db
import django.contrib.sessions.backends.file
import django.contrib.sessions.backends.signed_cookies
import django.contrib.sessions.base_session
import django.contrib.sessions.management.commands
import django.contrib.sessions.management.commands.clearsessions
import django.contrib.sessions.middleware
import django.contrib.sessions.models
import django.contrib.sessions.serializers
import django.contrib.sitemaps.management.commands
import django.contrib.sitemaps.management.commands.ping_google
import django.contrib.sitemaps.views
import django.contrib.sites
import django.contrib.sites.apps
import django.contrib.sites.management
import django.contrib.sites.managers
import django.contrib.sites.middleware
import django.contrib.sites.models
import django.contrib.sites.requests
import django.contrib.sites.shortcuts
import django.contrib.staticfiles.apps
import django.contrib.staticfiles.checks
import django.contrib.staticfiles.finders
import django.contrib.staticfiles.handlers
import django.contrib.staticfiles.management.commands
import django.contrib.staticfiles.management.commands.collectstatic
import django.contrib.staticfiles.management.commands.findstatic
import django.contrib.staticfiles.management.commands.runserver
import django.contrib.staticfiles.storage
import django.contrib.staticfiles.templatetags
import django.contrib.staticfiles.templatetags.staticfiles
import django.contrib.staticfiles.urls
import django.contrib.staticfiles.utils
import django.contrib.staticfiles.views
import django.contrib.syndication
import django.contrib.syndication.views
import django.core.cache.backends
import django.core.cache.backends.base
import django.core.cache.backends.db
import django.core.cache.backends.dummy
import django.core.cache.backends.filebased
import django.core.cache.backends.locmem
import django.core.cache.utils
import django.core.checks.caches
import django.core.checks.database
import django.core.checks.messages
import django.core.checks.model_checks
import django.core.checks.registry
import django.core.checks.security
import django.core.checks.security.base
import django.core.checks.security.csrf
import django.core.checks.security.sessions
import django.core.checks.templates
import django.core.checks.urls
import django.core.exceptions
import django.core.files
import django.core.files.base
import django.core.files.images
import django.core.files.locks
import django.core.files.move
import django.core.files.storage
import django.core.files.temp
import django.core.files.uploadedfile
import django.core.files.uploadhandler
import django.core.files.utils
import django.core.handlers
import django.core.handlers.base
import django.core.handlers.exception
import django.core.handlers.wsgi
import django.core.mail
import django.core.mail.message
import django.core.mail.utils
import django.core.management
import django.core.management.base
import django.core.management.color
import django.core.management.sql
import django.core.management.templates
import django.core.management.utils
import django.core.paginator
import django.core.serializers
import django.core.serializers.base
import django.core.serializers.json
import django.core.serializers.python
import django.core.servers
import django.core.servers.basehttp
import django.core.signals
import django.core.signing
import django.core.validators
import django.core.wsgi
import django.db.backends.base
import django.db.backends.base.base
import django.db.backends.base.client
import django.db.backends.base.creation
import django.db.backends.base.features
import django.db.backends.base.introspection
import django.db.backends.base.operations
import django.db.backends.base.schema
import django.db.backends.base.validation
import django.db.backends.ddl_references
import django.db.backends.dummy
import django.db.backends.dummy.base
import django.db.backends.mysql
import django.db.backends.mysql.client
import django.db.backends.postgresql
import django.db.backends.postgresql.client
import django.db.backends.sqlite3
import django.db.backends.sqlite3.base
import django.db.backends.sqlite3.creation
import django.db.backends.sqlite3.features
import django.db.backends.sqlite3.introspection
import django.db.backends.sqlite3.operations
import django.db.backends.sqlite3.schema
import django.db.backends.utils
import django.db.migrations.autodetector
import django.db.migrations.exceptions
import django.db.migrations.executor
import django.db.migrations.graph
import django.db.migrations.loader
import django.db.migrations.migration
import django.db.migrations.operations
import django.db.migrations.operations.base
import django.db.migrations.operations.fields
import django.db.migrations.operations.models
import django.db.migrations.operations.special
import django.db.migrations.operations.utils
import django.db.migrations.optimizer
import django.db.migrations.questioner
import django.db.migrations.recorder
import django.db.migrations.serializer
import django.db.migrations.state
import django.db.migrations.topological_sort
import django.db.migrations.utils
import django.db.migrations.writer
import django.db.models.aggregates
import django.db.models.base
import django.db.models.deletion
import django.db.models.expressions
import django.db.models.fields
import django.db.models.fields.files
import django.db.models.fields.mixins
import django.db.models.fields.proxy
import django.db.models.fields.related
import django.db.models.fields.related_descriptors
import django.db.models.fields.related_lookups
import django.db.models.fields.reverse_related
import django.db.models.functions
import django.db.models.functions.comparison
import django.db.models.functions.datetime
import django.db.models.functions.text
import django.db.models.functions.window
import django.db.models.indexes
import django.db.models.lookups
import django.db.models.manager
import django.db.models.options
import django.db.models.query
import django.db.models.query_utils
import django.db.models.signals
import django.db.models.sql
import django.db.models.sql.compiler
import django.db.models.sql.constants
import django.db.models.sql.datastructures
import django.db.models.sql.query
import django.db.models.sql.subqueries
import django.db.models.sql.where
import django.db.models.utils
import django.db.transaction
import django.db.utils
import django.dispatch
import django.dispatch.dispatcher
import django.forms
import django.forms.boundfield
import django.forms.fields
import django.forms.forms
import django.forms.formsets
import django.forms.models
import django.forms.renderers
import django.forms.utils
import django.forms.widgets
import django.http
import django.http.cookie
import django.http.multipartparser
import django.http.request
import django.http.response
import django.middleware
import django.middleware.cache
import django.middleware.clickjacking
import django.middleware.common
import django.middleware.csrf
import django.middleware.gzip
import django.middleware.http
import django.middleware.locale
import django.middleware.security
import django.shortcuts
import django.template.backends
import django.template.backends.base
import django.template.backends.django
import django.template.backends.dummy
import django.template.backends.jinja2
import django.template.backends.utils
import django.template.base
import django.template.context
import django.template.context_processors
import django.template.defaultfilters
import django.template.defaulttags
import django.template.engine
import django.template.exceptions
import django.template.library
import django.template.loader
import django.template.loader_tags
import django.template.loaders
import django.template.loaders.app_directories
import django.template.loaders.base
import django.template.loaders.cached
import django.template.loaders.filesystem
import django.template.loaders.locmem
import django.template.response
import django.template.smartif
import django.template.utils
import django.templatetags
import django.templatetags.cache
import django.templatetags.i18n
import django.templatetags.l10n
import django.templatetags.static
import django.templatetags.tz
import django.test
import django.test.client
import django.test.html
import django.test.runner
import django.test.selenium
import django.test.signals
import django.test.testcases
import django.test.utils
import django.urls
import django.urls.base
import django.urls.conf
import django.urls.converters
import django.urls.exceptions
import django.urls.resolvers
import django.urls.utils
import django.utils._os
import django.utils.archive
import django.utils.autoreload
import django.utils.baseconv
import django.utils.cache
import django.utils.crypto
import django.utils.datastructures
import django.utils.dateformat
import django.utils.dateparse
import django.utils.dates
import django.utils.datetime_safe
import django.utils.deconstruct
import django.utils.decorators
import django.utils.deprecation
import django.utils.duration
import django.utils.encoding
import django.utils.feedgenerator
import django.utils.formats
import django.utils.functional
import django.utils.html
import django.utils.http
import django.utils.inspect
import django.utils.ipv6
import django.utils.itercompat
import django.utils.jslex
import django.utils.log
import django.utils.lorem_ipsum
import django.utils.module_loading
import django.utils.numberformat
import django.utils.regex_helper
import django.utils.safestring
import django.utils.six
import django.utils.termcolors
import django.utils.text
import django.utils.timesince
import django.utils.timezone
import django.utils.translation
import django.utils.translation.template
import django.utils.translation.trans_null
import django.utils.translation.trans_real
import django.utils.tree
import django.utils.version
import django.utils.xmlutils
import django.views.csrf
import django.views.debug
import django.views.decorators
import django.views.decorators.cache
import django.views.decorators.clickjacking
import django.views.decorators.csrf
import django.views.decorators.debug
import django.views.decorators.gzip
import django.views.decorators.http
import django.views.decorators.vary
import django.views.defaults
import django.views.generic
import django.views.generic.base
import django.views.generic.dates
import django.views.generic.detail
import django.views.generic.edit
import django.views.generic.list
import django.views.i18n
import django.views.static
[/CASE]
-201
View File
@@ -1,201 +0,0 @@
[CASE test_every_model_has_objects_queryset_available]
from django.db import models
class User(models.Model):
pass
reveal_type(User.objects) # N: Revealed type is 'django.db.models.manager.Manager[main.User]'
reveal_type(User.objects.get()) # N: Revealed type is 'main.User*'
[CASE every_model_has_its_own_objects_queryset]
from django.db import models
class Parent(models.Model):
pass
class Child(Parent):
pass
reveal_type(Parent.objects) # N: Revealed type is 'django.db.models.manager.Manager[main.Parent]'
reveal_type(Child.objects) # N: Revealed type is 'django.db.models.manager.Manager[main.Child]'
[out]
[CASE if_manager_is_defined_on_model_do_not_add_objects]
from django.db import models
class MyModel(models.Model):
authors = models.Manager[MyModel]()
reveal_type(MyModel.authors) # N: Revealed type is 'django.db.models.manager.Manager[main.MyModel]'
MyModel.objects # E: "Type[MyModel]" has no attribute "objects"
[out]
[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._default_manager) # N: 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._default_manager) # N: Revealed type is 'django.db.models.manager.Manager[main.MyModel]'
class Child(Base[MyModel]):
def method(self) -> None:
reveal_type(self.model_cls._default_manager) # N: Revealed type is 'django.db.models.manager.Manager[main.MyModel]'
[CASE if_custom_manager_defined_it_is_set_to_default_manager]
from typing import TypeVar
from django.db import models
_T = TypeVar('_T', bound=models.Model)
class CustomManager(models.Manager[_T]):
pass
class MyModel(models.Model):
manager = CustomManager[MyModel]()
reveal_type(MyModel._default_manager) # N: Revealed type is 'main.CustomManager[main.MyModel]'
[CASE if_default_manager_name_is_passed_set_default_manager_to_it]
from typing import TypeVar
from django.db import models
_T = TypeVar('_T', bound=models.Model)
class Manager1(models.Manager[_T]):
pass
class Manager2(models.Manager[_T]):
pass
class MyModel(models.Model):
class Meta:
default_manager_name = 'm2'
m1: Manager1[MyModel]
m2: Manager2[MyModel]
reveal_type(MyModel._default_manager) # N: Revealed type is 'main.Manager2[main.MyModel]'
[CASE test_leave_as_is_if_objects_is_set_and_fill_typevars_with_outer_class]
from django.db import models
class UserManager(models.Manager[MyUser]):
def get_or_404(self) -> MyUser:
pass
class MyUser(models.Model):
objects = UserManager()
reveal_type(MyUser.objects) # N: Revealed type is 'main.UserManager[main.MyUser]'
reveal_type(MyUser.objects.get()) # N: Revealed type is 'main.MyUser*'
reveal_type(MyUser.objects.get_or_404()) # N: Revealed type is 'main.MyUser'
[CASE model_imported_from_different_file]
from django.db import models
from models.main import Inventory
class Band(models.Model):
pass
reveal_type(Inventory.objects) # N: Revealed type is 'django.db.models.manager.Manager[models.main.Inventory]'
reveal_type(Band.objects) # N: Revealed type is 'django.db.models.manager.Manager[main.Band]'
[file models/__init__.py]
[file models/main.py]
from django.db import models
class Inventory(models.Model):
pass
[CASE managers_that_defined_on_other_models_do_not_influence]
from django.db import models
class AbstractPerson(models.Model):
abstract_persons = models.Manager[AbstractPerson]()
class PublishedBookManager(models.Manager[Book]):
pass
class AnnotatedBookManager(models.Manager[Book]):
pass
class Book(models.Model):
title = models.CharField(max_length=50)
published_objects = PublishedBookManager()
annotated_objects = AnnotatedBookManager()
reveal_type(AbstractPerson.abstract_persons) # N: Revealed type is 'django.db.models.manager.Manager[main.AbstractPerson]'
reveal_type(Book.published_objects) # N: Revealed type is 'main.PublishedBookManager[main.Book]'
Book.published_objects.create(title='hello')
reveal_type(Book.annotated_objects) # N: Revealed type is 'main.AnnotatedBookManager[main.Book]'
Book.annotated_objects.create(title='hello')
[out]
[CASE managers_inherited_from_abstract_classes_multiple_inheritance]
from django.db import models
class CustomManager1(models.Manager[AbstractBase1]):
pass
class AbstractBase1(models.Model):
class Meta:
abstract = True
name = models.CharField(max_length=50)
manager1 = CustomManager1()
class CustomManager2(models.Manager[AbstractBase2]):
pass
class AbstractBase2(models.Model):
class Meta:
abstract = True
value = models.CharField(max_length=50)
restricted = CustomManager2()
class Child(AbstractBase1, AbstractBase2):
pass
[out]
[CASE managers_from_unrelated_models_dont_interfere]
from django.db import models
# Normal scenario where one model has a manager with an annotation of the same type as the model
class UnrelatedModel(models.Model):
objects = models.Manager[UnrelatedModel]()
class MyModel(models.Model):
pass
reveal_type(UnrelatedModel.objects) # N: Revealed type is 'django.db.models.manager.Manager[main.UnrelatedModel]'
reveal_type(UnrelatedModel.objects.first()) # N: Revealed type is 'Union[main.UnrelatedModel*, None]'
reveal_type(MyModel.objects) # N: Revealed type is 'django.db.models.manager.Manager[main.MyModel]'
reveal_type(MyModel.objects.first()) # N: Revealed type is 'Union[main.MyModel*, None]'
# Possible to specify objects without explicit annotation of models.Manager()
class UnrelatedModel2(models.Model):
objects = models.Manager()
class MyModel2(models.Model):
pass
reveal_type(UnrelatedModel2.objects) # N: Revealed type is 'django.db.models.manager.Manager[main.UnrelatedModel2]'
reveal_type(UnrelatedModel2.objects.first()) # N: Revealed type is 'Union[main.UnrelatedModel2*, None]'
reveal_type(MyModel2.objects) # N: Revealed type is 'django.db.models.manager.Manager[main.MyModel2]'
reveal_type(MyModel2.objects.first()) # N: Revealed type is 'Union[main.MyModel2*, None]'
# Inheritance works
class ParentOfMyModel3(models.Model):
objects = models.Manager()
class MyModel3(ParentOfMyModel3):
pass
reveal_type(ParentOfMyModel3.objects) # N: Revealed type is 'django.db.models.manager.Manager[main.ParentOfMyModel3]'
reveal_type(ParentOfMyModel3.objects.first()) # N: Revealed type is 'Union[main.ParentOfMyModel3*, None]'
reveal_type(MyModel3.objects) # N: Revealed type is 'django.db.models.manager.Manager[main.MyModel3]'
reveal_type(MyModel3.objects.first()) # N: Revealed type is 'Union[main.MyModel3*, None]'
# Inheritance works with explicit objects in child
class ParentOfMyModel4(models.Model):
objects = models.Manager()
class MyModel4(ParentOfMyModel4):
objects = models.Manager[MyModel4]()
reveal_type(ParentOfMyModel4.objects) # N: Revealed type is 'django.db.models.manager.Manager[main.ParentOfMyModel4]'
reveal_type(ParentOfMyModel4.objects.first()) # N: Revealed type is 'Union[main.ParentOfMyModel4*, None]'
reveal_type(MyModel4.objects) # N: Revealed type is 'django.db.models.manager.Manager[main.MyModel4]'
reveal_type(MyModel4.objects.first()) # N: Revealed type is 'Union[main.MyModel4*, None]'
[out]
@@ -0,0 +1,38 @@
- case: queryset_basic_methods_return_type
main: |
from myapp.models import Blog
qs = Blog.objects.all()
reveal_type(qs) # N: Revealed type is 'django.db.models.query.QuerySet[myapp.models.Blog*, myapp.models.Blog*]'
reveal_type(qs.get(id=1)) # N: Revealed type is 'myapp.models.Blog*'
reveal_type(iter(qs)) # N: Revealed type is 'typing.Iterator[myapp.models.Blog*]'
reveal_type(qs.iterator()) # N: Revealed type is 'typing.Iterator[myapp.models.Blog*]'
reveal_type(qs.first()) # N: Revealed type is 'myapp.models.Blog*'
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*]'
reveal_type(qs.in_bulk()) # N: Revealed type is 'builtins.dict[Any, myapp.models.Blog*]'
# .dates / .datetimes
reveal_type(Blog.objects.dates("created_at", "day")) # N: Revealed type is 'django.db.models.query.QuerySet[myapp.models.Blog*, datetime.date]'
reveal_type(Blog.objects.datetimes("created_at", "day")) # N: Revealed type is 'django.db.models.query.QuerySet[myapp.models.Blog*, datetime.datetime]'
# AND-ing QuerySets
reveal_type(Blog.objects.all() & Blog.objects.all()) # N: Revealed type is 'django.db.models.query.QuerySet[myapp.models.Blog*, myapp.models.Blog*]'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class Blog(models.Model):
created_at = models.DateTimeField()
- case: queryset_could_be_specified_with_one_type
main: |
from typing import Optional
from django.db import models
queryset: models.QuerySet[models.Model] = models.QuerySet()
reveal_type(queryset) # N: Revealed type is 'django.db.models.query.QuerySet[django.db.models.base.Model, django.db.models.base.Model]'
@@ -0,0 +1,109 @@
- case: queryset_values_method_returns_typeddict
main: |
from myapp.models import Blog
values = Blog.objects.values('num_posts', 'text').get()
reveal_type(values) # N: Revealed type is 'TypedDict({'num_posts': builtins.int, 'text': builtins.str})'
reveal_type(values["num_posts"]) # N: Revealed type is 'builtins.int'
reveal_type(values["text"]) # N: Revealed type is 'builtins.str'
values_pk = Blog.objects.values('pk').get()
reveal_type(values_pk) # N: Revealed type is 'TypedDict({'pk': builtins.int})'
reveal_type(values_pk["pk"]) # N: Revealed type is 'builtins.int'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class Blog(models.Model):
num_posts = models.IntegerField()
text = models.CharField(max_length=100)
- case: queryset_values_all_values
main: |
from myapp.models import Blog
all_values_dict = Blog.objects.values().get()
reveal_type(all_values_dict) # N: Revealed type is 'TypedDict({'id': builtins.int, 'num_posts': builtins.int, 'text': builtins.str})'
reveal_type(all_values_dict["id"]) # N: Revealed type is 'builtins.int'
reveal_type(all_values_dict["num_posts"]) # N: Revealed type is 'builtins.int'
reveal_type(all_values_dict["text"]) # N: Revealed type is 'builtins.str'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class Publisher(models.Model):
pass
class Blog(models.Model):
num_posts = models.IntegerField()
text = models.CharField(max_length=100)
- case: queryset_foreign_key_object_always_a_primary_key
main: |
from myapp.models import Blog
values1 = Blog.objects.values('publisher').get()
reveal_type(values1) # N: Revealed type is 'TypedDict({'publisher': builtins.int})'
reveal_type(values1['publisher']) # N: Revealed type is 'builtins.int'
values2 = Blog.objects.values('publisher_id').get()
reveal_type(values2) # N: Revealed type is 'TypedDict({'publisher_id': builtins.int})'
reveal_type(values2["publisher_id"]) # N: Revealed type is 'builtins.int'
# all values return _id version
all_values = Blog.objects.values().get()
reveal_type(all_values) # N: Revealed type is 'TypedDict({'id': builtins.int, 'publisher_id': builtins.int})'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class Publisher(models.Model):
pass
class Blog(models.Model):
publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE)
- case: values_with_related_model_fields
main: |
from myapp.models import Entry
values = Entry.objects.values('blog__num_articles', 'blog__publisher__name').get()
reveal_type(values) # N: Revealed type is 'TypedDict({'blog__num_articles': builtins.int, 'blog__publisher__name': builtins.str})'
pk_values = Entry.objects.values('blog__pk', 'blog__publisher__pk').get()
reveal_type(pk_values) # N: Revealed type is 'TypedDict({'blog__pk': builtins.int, 'blog__publisher__pk': builtins.int})'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class Publisher(models.Model):
name = models.CharField(max_length=100)
class Blog(models.Model):
num_articles = models.IntegerField()
publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE)
class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
- case: select_all_related_model_values_for_every_current_value
main: |
from myapp.models import Publisher
related_model_values = Publisher.objects.values('id', 'blog__name').get()
reveal_type(related_model_values) # N: Revealed type is 'TypedDict({'id': builtins.int, 'blog__name': builtins.str})'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class Publisher(models.Model):
pass
class Blog(models.Model):
name = models.CharField(max_length=100)
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
@@ -0,0 +1,190 @@
- case: values_list_simple_field_returns_queryset_of_tuples
main: |
from myapp.models import MyUser
reveal_type(MyUser.objects.values_list('name').get()) # N: Revealed type is 'Tuple[builtins.str]'
reveal_type(MyUser.objects.values_list('id', 'name').get()) # N: Revealed type is 'Tuple[builtins.int, builtins.str]'
values_tuple = MyUser.objects.values_list('name', 'age').get()
reveal_type(values_tuple[0]) # N: Revealed type is 'builtins.str'
reveal_type(values_tuple[1]) # N: Revealed type is 'builtins.int'
# no fields specified return all fields
all_values_tuple = MyUser.objects.values_list().get()
reveal_type(all_values_tuple) # N: Revealed type is 'Tuple[builtins.int, builtins.str, builtins.int]'
# pk as field
pk_values = MyUser.objects.values_list('pk').get()
reveal_type(pk_values) # N: # N: Revealed type is 'Tuple[builtins.int]'
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)
age = models.IntegerField()
- case: values_list_related_model_fields
main: |
from myapp.models import Post, Blog
values_tuple = Post.objects.values_list('blog', 'blog__num_posts', 'blog__publisher', 'blog__publisher__name').get()
reveal_type(values_tuple[0]) # N: Revealed type is 'myapp.models.Blog'
reveal_type(values_tuple[1]) # N: Revealed type is 'builtins.int'
reveal_type(values_tuple[2]) # N: Revealed type is 'myapp.models.Publisher'
reveal_type(values_tuple[3]) # N: Revealed type is 'builtins.str'
reverse_fields_list = Blog.objects.values_list('post__text').get()
reveal_type(reverse_fields_list) # N: Revealed type is 'Tuple[builtins.str]'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class Publisher(models.Model):
name = models.CharField(max_length=100)
class Blog(models.Model):
num_posts = models.IntegerField()
publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE)
class Post(models.Model):
text = models.CharField(max_length=100)
blog = models.ForeignKey(to=Blog, on_delete=models.CASCADE)
- case: values_list_flat_true
main: |
from myapp.models import MyUser, MyUser2
reveal_type(MyUser.objects.values_list('name', flat=True).get()) # N: Revealed type is 'builtins.str*'
reveal_type(MyUser.objects.values_list('name', 'age', flat=True).get())
# flat=True without specified fields returns primary key values
reveal_type(MyUser.objects.values_list(flat=True)[0]) # N: Revealed type is 'builtins.int*'
reveal_type(MyUser2.objects.values_list(flat=True)[0]) # N: Revealed type is 'builtins.str*'
out: |
main:3: error: 'flat' is not valid when 'values_list' is called with more than one field
main:3: note: Revealed type is 'Any'
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)
age = models.IntegerField()
class MyUser2(models.Model):
name = models.CharField(max_length=100, primary_key=True)
- case: values_list_named_true
main: |
from myapp.models import MyUser
values_named_tuple = MyUser.objects.values_list('name', 'age', named=True).get()
reveal_type(values_named_tuple) # N: Revealed type is 'Tuple[builtins.str, builtins.int, fallback=main.Row]'
reveal_type(values_named_tuple.name) # N: Revealed type is 'builtins.str'
reveal_type(values_named_tuple.age) # N: Revealed type is 'builtins.int'
# no fields specified, returns all fields namedtuple
all_values_named_tuple = MyUser.objects.values_list(named=True).get()
reveal_type(all_values_named_tuple.id) # N: Revealed type is 'builtins.int'
reveal_type(all_values_named_tuple.name) # N: Revealed type is 'builtins.str'
reveal_type(all_values_named_tuple.age) # N: Revealed type is 'builtins.int'
reveal_type(all_values_named_tuple.is_admin) # N: Revealed type is 'builtins.bool'
# pk as field
pk_values = MyUser.objects.values_list('pk', named=True).get()
reveal_type(pk_values) # N: Revealed type is 'Tuple[builtins.int, fallback=main.Row2]'
reveal_type(pk_values.pk) # N: # N: Revealed type is 'builtins.int'
# values_list(named=True) inside function
def func() -> None:
from myapp.models import MyUser
reveal_type(MyUser.objects.values_list('name', named=True).get()) # N: Revealed type is 'Tuple[builtins.str, fallback=main.Row3]'
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)
age = models.IntegerField()
is_admin = models.BooleanField()
- case: values_list_flat_true_named_true_error
main: |
from myapp.models import MyUser
reveal_type(MyUser.objects.values_list('name', flat=True, named=True).get())
out: |
main:2: error: 'flat' and 'named' can't be used together
main:2: note: Revealed type is 'Any'
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)
- case: invalid_lookups
main: |
from myapp.models import Blog
reveal_type(Blog.objects.values_list('unknown').get())
reveal_type(Blog.objects.values_list('unknown', flat=True).get())
reveal_type(Blog.objects.values_list('unknown', named=True).get())
reveal_type(Blog.objects.values_list('publisher__unknown').get())
out: |
main:2: error: Cannot resolve keyword 'unknown' into field. Choices are: id, publisher, publisher_id
main:2: note: Revealed type is 'Any'
main:3: error: Cannot resolve keyword 'unknown' into field. Choices are: id, publisher, publisher_id
main:3: note: Revealed type is 'Any'
main:4: error: Cannot resolve keyword 'unknown' into field. Choices are: id, publisher, publisher_id
main:4: note: Revealed type is 'Any'
main:5: error: Lookups not supported yet
main:5: note: Revealed type is 'Any'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class Publisher(models.Model):
pass
class Blog(models.Model):
publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE)
- case: named_true_with_related_model_fields
main: |
from myapp.models import Entry, Blog
values = Entry.objects.values_list('blog__num_articles', 'blog__publisher__name', named=True).get()
reveal_type(values.blog__num_articles) # N: Revealed type is 'builtins.int'
reveal_type(values.blog__publisher__name) # N: Revealed type is 'builtins.str'
pk_values = Entry.objects.values_list('blog__pk', 'blog__publisher__pk', named=True).get()
reveal_type(pk_values.blog__pk) # N: Revealed type is 'builtins.int'
reveal_type(pk_values.blog__publisher__pk) # N: Revealed type is 'builtins.int'
# reverse relation
reverse_values = Blog.objects.values_list('entry__text', named=True).get()
reveal_type(reverse_values.entry__text) # N: Revealed type is 'builtins.str'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class Publisher(models.Model):
name = models.CharField(max_length=100)
class Blog(models.Model):
num_articles = models.IntegerField()
publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE)
class Entry(models.Model):
text = models.CharField(max_length=100)
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
@@ -0,0 +1,322 @@
- case: test_every_model_has_objects_queryset_available
main: |
from myapp.models import User
reveal_type(User.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.User]'
reveal_type(User.objects.get()) # N: Revealed type is 'myapp.models.User*'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class User(models.Model):
pass
- case: every_model_has_its_own_objects_queryset
main: |
from myapp.models import Parent, Child
reveal_type(Parent.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.Parent]'
reveal_type(Child.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.Child]'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class Parent(models.Model):
pass
class Child(Parent):
pass
- case: test_model_objects_attribute_present_in_case_of_model_cls_passed_as_generic_parameter
main: |
from myapp.models import Base, MyModel
base_instance = Base(MyModel)
reveal_type(base_instance.model_cls._default_manager) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.MyModel]'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
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._default_manager) # N: Revealed type is 'django.db.models.manager.Manager[django.db.models.base.Model]'
class MyModel(models.Model):
pass
class Child(Base[MyModel]):
def method(self) -> None:
reveal_type(self.model_cls._default_manager) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.MyModel]'
- case: if_custom_manager_defined_it_is_set_to_default_manager
main: |
from myapp.models import MyModel
reveal_type(MyModel._default_manager) # N: Revealed type is 'myapp.models.CustomManager[myapp.models.MyModel]'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from typing import TypeVar
from django.db import models
_T = TypeVar('_T', bound=models.Model)
class CustomManager(models.Manager[_T]):
pass
class MyModel(models.Model):
manager = CustomManager['MyModel']()
- case: if_default_manager_name_is_passed_set_default_manager_to_it
main: |
from myapp.models import MyModel
reveal_type(MyModel._default_manager) # N: Revealed type is 'myapp.models.Manager2[myapp.models.MyModel]'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from typing import TypeVar
from django.db import models
_T = TypeVar('_T', bound=models.Model)
class Manager1(models.Manager[_T]):
pass
class Manager2(models.Manager[_T]):
pass
class MyModel(models.Model):
class Meta:
default_manager_name = 'm2'
m1 = Manager1['MyModel']()
m2 = Manager2['MyModel']()
- case: test_leave_as_is_if_objects_is_set_and_fill_typevars_with_outer_class
main: |
from myapp.models import MyUser
reveal_type(MyUser.objects) # N: Revealed type is 'myapp.models.UserManager[myapp.models.MyUser]'
reveal_type(MyUser.objects.get()) # N: Revealed type is 'myapp.models.MyUser*'
reveal_type(MyUser.objects.get_or_404()) # 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 UserManager(models.Manager['MyUser']):
def get_or_404(self) -> 'MyUser':
pass
class MyUser(models.Model):
objects = UserManager()
- case: model_imported_from_different_file
main: |
from myapp.models import Inventory, Band
reveal_type(Inventory.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.main.Inventory]'
reveal_type(Band.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.Band]'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models/__init__.py
content: |
from django.db import models
from .main import Inventory
class Band(models.Model):
pass
- path: myapp/models/main.py
content: |
from django.db import models
class Inventory(models.Model):
pass
- case: managers_that_defined_on_other_models_do_not_influence
main: |
from myapp.models import AbstractPerson, Book
reveal_type(AbstractPerson.abstract_persons) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.AbstractPerson]'
reveal_type(Book.published_objects) # N: Revealed type is 'myapp.models.PublishedBookManager[myapp.models.Book]'
Book.published_objects.create(title='hello')
reveal_type(Book.annotated_objects) # N: Revealed type is 'myapp.models.AnnotatedBookManager[myapp.models.Book]'
Book.annotated_objects.create(title='hello')
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class AbstractPerson(models.Model):
abstract_persons = models.Manager['AbstractPerson']()
class PublishedBookManager(models.Manager['Book']):
pass
class AnnotatedBookManager(models.Manager['Book']):
pass
class Book(models.Model):
title = models.CharField(max_length=50)
published_objects = PublishedBookManager()
annotated_objects = AnnotatedBookManager()
- case: managers_inherited_from_abstract_classes_multiple_inheritance
main: |
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class CustomManager1(models.Manager['AbstractBase1']):
pass
class AbstractBase1(models.Model):
class Meta:
abstract = True
name = models.CharField(max_length=50)
manager1 = CustomManager1()
class CustomManager2(models.Manager['AbstractBase2']):
pass
class AbstractBase2(models.Model):
class Meta:
abstract = True
value = models.CharField(max_length=50)
restricted = CustomManager2()
class Child(AbstractBase1, AbstractBase2):
pass
- case: model_has_a_manager_of_the_same_type
main: |
from myapp.models import UnrelatedModel, MyModel
reveal_type(UnrelatedModel.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.UnrelatedModel]'
reveal_type(UnrelatedModel.objects.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 'myapp.models.MyModel*'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class UnrelatedModel(models.Model):
objects = models.Manager['UnrelatedModel']()
class MyModel(models.Model):
pass
- case: manager_without_annotation_of_the_model_gets_it_from_outer_one
main: |
from myapp.models import UnrelatedModel2, MyModel2
reveal_type(UnrelatedModel2.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.UnrelatedModel2]'
reveal_type(UnrelatedModel2.objects.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 'myapp.models.MyModel2*'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class UnrelatedModel2(models.Model):
objects = models.Manager()
class MyModel2(models.Model):
pass
- case: inherited_manager_has_the_proper_type_of_model
main: |
from myapp.models import ParentOfMyModel3, MyModel3
reveal_type(ParentOfMyModel3.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.ParentOfMyModel3]'
reveal_type(ParentOfMyModel3.objects.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 'myapp.models.MyModel3*'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class ParentOfMyModel3(models.Model):
objects = models.Manager()
class MyModel3(ParentOfMyModel3):
pass
- case: inheritance_with_explicit_type_on_child_manager
main: |
from myapp.models import ParentOfMyModel4, MyModel4
reveal_type(ParentOfMyModel4.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.ParentOfMyModel4]'
reveal_type(ParentOfMyModel4.objects.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 'myapp.models.MyModel4*'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class ParentOfMyModel4(models.Model):
objects = models.Manager()
class MyModel4(ParentOfMyModel4):
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()
- case: custom_manager_returns_proper_model_types
main: |
from myapp.models import User
reveal_type(User.objects.get()) # N: Revealed type is 'myapp.models.User*'
reveal_type(User.objects.select_related()) # N: Revealed type is 'django.db.models.query.QuerySet[myapp.models.User*, myapp.models.User*]'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class MyManager(models.Manager):
pass
class User(models.Model):
objects = MyManager()
-10
View File
@@ -1,10 +0,0 @@
[CASE successmessagemixin_compatible_with_formmixin]
from django.views.generic.edit import FormMixin
from django.contrib.messages.views import SuccessMessageMixin
class FormFirstView(FormMixin, SuccessMessageMixin):
pass
class SuccessMessageFirstView(FormMixin, SuccessMessageMixin):
pass
[/CASE]
-48
View File
@@ -1,48 +0,0 @@
[CASE registry_apps_get_model]
from django.apps.registry import Apps
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from myapp.models import User
apps = Apps()
model_cls = apps.get_model('myapp', 'User')
reveal_type(model_cls) # N: Revealed type is 'Type[myapp.models.User]'
reveal_type(model_cls.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.User]'
[file myapp/__init__.py]
[file myapp/models.py]
from django.db import models
class User(models.Model):
pass
[CASE registry_apps_get_model_passed_as_variables_not_supported]
from django.apps.registry import Apps
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from myapp.models import User
app_name = 'myapp'
model_name = 'User'
apps = Apps()
model_cls = apps.get_model(app_name, model_name)
reveal_type(model_cls) # N: Revealed type is 'Type[django.db.models.base.Model]'
[file myapp/__init__.py]
[file myapp/models.py]
from django.db import models
class User(models.Model):
pass
[CASE state_apps_get_model]
from django.db.migrations.state import StateApps
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from myapp.models import User
apps = StateApps([], {})
model_cls = apps.get_model('myapp', 'User')
reveal_type(model_cls) # N: Revealed type is 'Type[myapp.models.User]'
reveal_type(model_cls.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.User]'
[file myapp/__init__.py]
[file myapp/models.py]
from django.db import models
class User(models.Model):
pass
-60
View File
@@ -1,60 +0,0 @@
[CASE test_typechecking_for_model_subclasses]
from django.db import models
class A(models.Model):
pass
class B(models.Model):
b_attr = 1
pass
class C(A):
pass
def service(a: A) -> int:
pass
b_instance = B()
service(b_instance) # E: Argument 1 to "service" has incompatible type "B"; expected "A"
a_instance = A()
c_instance = C()
service(a_instance)
service(c_instance)
[/CASE]
[CASE fail_if_no_such_attribute_on_model]
from django.db import models
class B(models.Model):
b_attr = 1
pass
b_instance = B()
reveal_type(b_instance.b_attr) # N: Revealed type is 'builtins.int'
reveal_type(b_instance.non_existent_attribute)
b_instance.non_existent_attribute = 2
[out]
main:10: note: Revealed type is 'Any'
main:10: error: "B" has no attribute "non_existent_attribute"
main:11: error: "B" has no attribute "non_existent_attribute"
[/CASE]
[CASE ignore_missing_attributes_if_setting_is_passed]
from django.db import models
class B(models.Model):
pass
b_instance = B()
reveal_type(b_instance.non_existent_attribute) # N: Revealed type is 'Any'
b_instance.non_existent_attribute = 2
[env MYPY_DJANGO_CONFIG=${MYPY_CWD}/mypy_django.ini]
[file mypy_django.ini]
[[mypy_django_plugin]
ignore_missing_model_attributes = True
[/CASE]
-64
View File
@@ -1,64 +0,0 @@
[CASE default_manager_create_is_typechecked]
from django.db import models
class User(models.Model):
name = models.CharField(max_length=100)
age = models.IntegerField()
User.objects.create(name='Max', age=10)
User.objects.create(age=[]) # E: Incompatible type for "age" of "User" (got "List[Any]", expected "Union[float, int, str, Combinable]")
[out]
[CASE model_recognises_parent_attributes]
from django.db import models
class Parent(models.Model):
name = models.CharField(max_length=100)
class Child(Parent):
lastname = models.CharField(max_length=100)
Child.objects.create(name='Maxim', lastname='Maxim2')
[out]
[CASE deep_multiple_inheritance_with_create]
from django.db import models
class Parent1(models.Model):
name1 = models.CharField(max_length=50)
class Parent2(models.Model):
id2 = models.AutoField(primary_key=True)
name2 = models.CharField(max_length=50)
class Child1(Parent1, Parent2):
value = models.IntegerField()
class Child4(Child1):
value4 = models.IntegerField()
Child4.objects.create(name1='n1', name2='n2', value=1, value4=4)
[CASE optional_primary_key_for_create_is_error]
from django.db import models
class MyModel(models.Model):
pass
MyModel.objects.create(id=None) # E: Incompatible type for "id" of "MyModel" (got "None", expected "int")
[CASE optional_related_model_for_create_is_error]
from django.db import models
class Publisher(models.Model):
pass
class Book(models.Model):
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
Book.objects.create(publisher=None) # E: Incompatible type for "publisher" of "Book" (got "None", expected "Union[Publisher, Combinable]")
[CASE when_default_for_primary_key_is_specified_allow_none_to_be_set]
from django.db import models
def return_int():
return 0
class MyModel(models.Model):
id = models.IntegerField(primary_key=True, default=return_int)
MyModel(id=None)
MyModel.objects.create(id=None)
class MyModel2(models.Model):
id = models.IntegerField(primary_key=True, default=None)
MyModel2(id=None) # E: Incompatible type for "id" of "MyModel2" (got "None", expected "Union[float, int, str, Combinable]")
MyModel2.objects.create(id=None) # E: Incompatible type for "id" of "MyModel2" (got "None", expected "Union[float, int, str, Combinable]")
[out]
-177
View File
@@ -1,177 +0,0 @@
[CASE arguments_to_init_unexpected_attributes]
from django.db import models
class MyUser(models.Model):
pass
user = MyUser(name=1, age=12)
[out]
main:5: error: Unexpected attribute "name" for model "MyUser"
main:5: error: Unexpected attribute "age" for model "MyUser"
[CASE arguments_to_init_from_class_incompatible_type]
from django.db import models
class MyUser(models.Model):
name = models.CharField(max_length=100)
age = models.IntegerField()
user = MyUser(name='hello', age=[])
[out]
main:6: error: Incompatible type for "age" of "MyUser" (got "List[Any]", expected "Union[float, int, str, Combinable]")
[CASE arguments_to_init_combined_from_base_classes]
from django.db import models
class BaseUser(models.Model):
name = models.CharField(max_length=100)
age = models.IntegerField()
class ChildUser(BaseUser):
lastname = models.CharField(max_length=100)
user = ChildUser(name='Max', age=12, lastname='Lastname')
[out]
[CASE fields_from_abstract_user_propagate_to_init]
from django.contrib.auth.models import AbstractUser
class MyUser(AbstractUser):
pass
user = MyUser(username='maxim', password='password', first_name='Max', last_name='MaxMax')
[out]
[CASE generic_foreign_key_field_no_typechecking]
from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey
class MyUser(models.Model):
content_object = GenericForeignKey()
user = MyUser(content_object=1)
[out]
[CASE pk_refers_to_primary_key_and_could_be_passed_to_init]
from django.db import models
class MyUser1(models.Model):
mypk = models.CharField(primary_key=True)
class MyUser2(models.Model):
name = models.CharField(max_length=100)
user2 = MyUser1(pk='hello')
user3= MyUser2(pk=1)
[out]
[CASE typechecking_of_pk]
from django.db import models
class MyUser1(models.Model):
mypk = models.IntegerField(primary_key=True)
user = MyUser1(pk=[]) # E: Incompatible type for "pk" of "MyUser1" (got "List[Any]", expected "Union[float, int, str, Combinable]")
[out]
[CASE can_set_foreign_key_by_its_primary_key]
from django.db import models
class Publisher(models.Model):
pass
class PublisherDatetime(models.Model):
dt_pk = models.DateTimeField(primary_key=True)
class Book(models.Model):
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
publisher_dt = models.ForeignKey(PublisherDatetime, on_delete=models.CASCADE)
Book(publisher_id=1)
Book(publisher_id=[]) # E: Incompatible type for "publisher_id" of "Book" (got "List[Any]", expected "Union[Combinable, int, str, None]")
Book(publisher_dt_id=11) # E: Incompatible type for "publisher_dt_id" of "Book" (got "int", expected "Union[str, date, Combinable, None]")
[out]
[CASE setting_value_to_an_array_of_ints]
from typing import List, Tuple
from django.db import models
from django.contrib.postgres.fields import ArrayField
class MyModel(models.Model):
array = ArrayField(base_field=models.IntegerField())
array_val: Tuple[int, ...] = (1,)
MyModel(array=array_val)
array_val2: List[int] = [1]
MyModel(array=array_val2)
array_val3: List[str] = ['hello']
MyModel(array=array_val3) # E: Incompatible type for "array" of "MyModel" (got "List[str]", expected "Union[Sequence[int], Combinable]")
[out]
[CASE if_no_explicit_primary_key_id_can_be_passed]
from django.db import models
class MyModel(models.Model):
name = models.CharField(max_length=100)
MyModel(id=1, name='maxim')
[out]
[CASE arguments_can_be_passed_as_positionals]
from django.db import models
class MyModel(models.Model):
pass
MyModel(1)
class MyModel2(models.Model):
name = models.IntegerField()
MyModel2(1, 12)
MyModel2(1, []) # E: Incompatible type for "name" of "MyModel2" (got "List[Any]", expected "Union[float, int, str, Combinable]")
[out]
[CASE arguments_passed_as_dictionary_unpacking_are_not_supported]
from django.db import models
class MyModel(models.Model):
name = models.CharField(max_length=100)
MyModel(**{'name': 'hello'})
[out]
[CASE pointer_to_parent_model_is_not_supported]
from django.db import models
class Place(models.Model):
pass
class Restaurant(Place):
pass
place = Place()
Restaurant(place_ptr=place)
Restaurant(place_ptr_id=place.id)
[out]
[CASE charfield_with_integer_choices]
from django.db import models
class MyModel(models.Model):
day = models.CharField(max_length=3, choices=((1, 'Fri'), (2, 'Sat')))
MyModel(day=1)
[out]
[CASE if_there_is_no_data_for_base_classes_of_fields_and_ignore_unresolved_attributes_set_to_true_to_not_fail]
from decimal import Decimal
from django.db import models
from fields2 import MoneyField
class InvoiceRow(models.Model):
base_amount = MoneyField(max_digits=10, decimal_places=2)
vat_rate = models.DecimalField(max_digits=10, decimal_places=2)
InvoiceRow(1, Decimal(0), Decimal(0))
InvoiceRow(base_amount=Decimal(0), vat_rate=Decimal(0))
InvoiceRow.objects.create(base_amount=Decimal(0), vat_rate=Decimal(0))
[out]
main:3: error: Cannot find module named 'fields2'
main:3: note: See https://mypy.readthedocs.io/en/latest/running_mypy.html#missing-imports
[CASE optional_primary_key_is_allowed_for_init]
from django.db import models
class MyModel(models.Model):
pass
MyModel(id=None)
MyModel(None)
[out]
[CASE optional_related_model_is_allowed_for_init]
from django.db import models
class Publisher(models.Model):
pass
class Book(models.Model):
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
Book(publisher=None)
Book(publisher_id=None)
[out]
+113
View File
@@ -0,0 +1,113 @@
- case: default_manager_create_is_typechecked
main: |
from myapp.models import User
User.objects.create(pk=1, name='Max', age=10)
User.objects.create(age=[]) # E: Incompatible type for "age" of "User" (got "List[Any]", expected "Union[float, int, str, Combinable]")
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class User(models.Model):
name = models.CharField(max_length=100)
age = models.IntegerField()
- case: model_recognises_parent_attributes
main: |
from myapp.models import Child
Child.objects.create(name='Maxim', lastname='Maxim2')
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class Parent(models.Model):
name = models.CharField(max_length=100)
class Child(Parent):
lastname = models.CharField(max_length=100)
- case: deep_multiple_inheritance_with_create
main: |
from myapp.models import Child4
Child4.objects.create(name1='n1', name2='n2', value=1, value4=4)
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class Parent1(models.Model):
name1 = models.CharField(max_length=50)
class Parent2(models.Model):
id2 = models.AutoField(primary_key=True)
name2 = models.CharField(max_length=50)
class Child1(Parent1, Parent2):
value = models.IntegerField()
class Child4(Child1):
value4 = models.IntegerField()
- case: optional_id_fields_for_create_is_error_if_not_autofield
main: |
from myapp.models import Publisher, Book
Book.objects.create(id=None) # E: Incompatible type for "id" of "Book" (got "None", expected "Union[float, int, str, Combinable]")
Book.objects.create(publisher=None) # E: Incompatible type for "publisher" of "Book" (got "None", expected "Union[Publisher, Combinable]")
Book.objects.create(publisher_id=None) # E: Incompatible type for "publisher_id" of "Book" (got "None", expected "Union[Combinable, int, str]")
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class Publisher(models.Model):
pass
class Book(models.Model):
id = models.IntegerField(primary_key=True)
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
- case: none_for_primary_key_is_allowed_if_field_is_autogenerated
main: |
from myapp.models import Book
Book.objects.create(id=None)
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class Book(models.Model):
pass
- case: when_default_for_primary_key_is_specified_allow_none_to_be_set
main: |
from myapp.models import MyModel
MyModel(id=None)
MyModel.objects.create(id=None)
from myapp.models import MyModel2
MyModel2(id=None)
MyModel2.objects.create(id=None) # E: Incompatible type for "id" of "MyModel2" (got "None", expected "Union[float, int, str, Combinable]")
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
def return_int():
return 0
class MyModel(models.Model):
id = models.IntegerField(primary_key=True, default=return_int)
class MyModel2(models.Model):
id = models.IntegerField(primary_key=True)
@@ -0,0 +1,56 @@
- case: if_field_has_choices_set_model_has_get_FIELDNAME_display_method
main: |
from myapp.models import MyUser
user = MyUser(name='user', gender='M')
user.get_name_display() # E: "MyUser" has no attribute "get_name_display"; maybe "get_gender_display"?
reveal_type(user.get_gender_display()) # N: Revealed type is 'builtins.str'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
GENDER_CHOICES = (
('M', 'Male'),
('F', 'Female'),
)
class MyUser(models.Model):
name = models.CharField(max_length=100)
gender = models.CharField(max_length=100, choices=GENDER_CHOICES)
- case: date_datetime_fields_have_get_next_by_get_previous_by
main: |
from myapp.models import MyUser
reveal_type(MyUser().get_next_by_date()) # N: Revealed type is 'myapp.models.MyUser'
reveal_type(MyUser().get_next_by_datetime()) # N: Revealed type is 'myapp.models.MyUser'
reveal_type(MyUser().get_previous_by_date()) # N: Revealed type is 'myapp.models.MyUser'
reveal_type(MyUser().get_previous_by_datetime()) # N: Revealed type is 'myapp.models.MyUser'
# accept arbitrary kwargs
MyUser().get_next_by_date(arg1=1, arg2=2)
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class MyUser(models.Model):
date = models.DateField()
datetime = models.DateTimeField()
- case: get_next_by_get_previous_by_absent_if_null_true
main: |
from myapp.models import MyUser
MyUser().get_next_by_date() # E: "MyUser" has no attribute "get_next_by_date"
MyUser().get_previous_by_date() # E: "MyUser" has no attribute "get_previous_by_date"
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class MyUser(models.Model):
date = models.DateField(null=True)
@@ -0,0 +1,70 @@
- case: test_meta_nested_class_allows_subclassing_in_multiple_inheritance
main: |
from typing import Any
from django.db import models
class Mixin1(models.Model):
class Meta:
abstract = True
class Mixin2(models.Model):
class Meta:
abstract = True
class User(Mixin1, Mixin2):
pass
- case: test_inheritance_from_abstract_model_does_not_fail_if_field_with_id_exists
main: |
from django.db import models
class Abstract(models.Model):
class Meta:
abstract = True
class User(Abstract):
id = models.AutoField(primary_key=True)
- case: test_typechecking_for_model_subclasses
main: |
from myapp.models import A, B, C
def service(a: A) -> int:
pass
b_instance = B()
service(b_instance) # E: Argument 1 to "service" has incompatible type "B"; expected "A"
a_instance = A()
c_instance = C()
service(a_instance)
service(c_instance)
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class A(models.Model):
pass
class B(models.Model):
b_attr = 1
pass
class C(A):
pass
- case: fail_if_no_such_attribute_on_model
main: |
from myapp.models import B
b_instance = B()
reveal_type(b_instance.b_attr) # N: Revealed type is 'builtins.int'
reveal_type(b_instance.non_existent_attribute)
b_instance.non_existent_attribute = 2
out: |
main:5: note: Revealed type is 'Any'
main:5: error: "B" has no attribute "non_existent_attribute"
main:6: error: "B" has no attribute "non_existent_attribute"
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class B(models.Model):
b_attr = 1
pass
+235
View File
@@ -0,0 +1,235 @@
- case: arguments_to_init_unexpected_attributes
main: |
from myapp.models import MyUser
user = MyUser(name=1, age=12)
out: |
main:2: error: Unexpected attribute "name" for model "MyUser"
main:2: error: Unexpected attribute "age" for model "MyUser"
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class MyUser(models.Model):
pass
- case: plain_function_which_returns_model
main: |
from myapp.models import MyUser
def func(i: int) -> MyUser:
pass
func("hello") # E: Argument 1 to "func" has incompatible type "str"; expected "int"
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class MyUser(models.Model):
pass
- case: arguments_to_init_from_class_incompatible_type
main: |
from myapp.models import MyUser
user = MyUser(name='hello', age=[])
out: |
main:2: error: Incompatible type for "age" of "MyUser" (got "List[Any]", expected "Union[float, int, str, Combinable]")
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)
age = models.IntegerField()
- case: arguments_to_init_combined_from_base_classes
main: |
from myapp.models import BaseUser, ChildUser
user = ChildUser(name='Max', age=12, lastname='Lastname')
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class BaseUser(models.Model):
name = models.CharField(max_length=100)
age = models.IntegerField()
class ChildUser(BaseUser):
lastname = models.CharField(max_length=100)
- case: fields_from_abstract_user_propagate_to_init
main: |
from myapp.models import MyUser
user = MyUser(name='Maxim')
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class AbstractUser(models.Model):
class Meta:
abstract = True
name = models.CharField(max_length=100)
class MyUser(AbstractUser):
pass
- case: pk_refers_to_primary_key_and_could_be_passed_to_init
main: |
from myapp.models import MyUser1, MyUser2
user2 = MyUser1(pk='hello')
user3 = MyUser2(pk=1, name='maxim')
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class MyUser1(models.Model):
mypk = models.CharField(primary_key=True)
class MyUser2(models.Model):
name = models.CharField(max_length=100)
- case: typechecking_of_pk
main: |
from myapp.models import MyUser1
user = MyUser1(pk=[]) # E: Incompatible type for "pk" of "MyUser1" (got "List[Any]", expected "Union[float, int, str, Combinable, None]")
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class MyUser1(models.Model):
mypk = models.IntegerField(primary_key=True)
- case: set_foreign_key_by_its_primary_key
main: |
from datetime import datetime
now = datetime.now()
from myapp.models import Publisher, PublisherDatetime, Book
Book(publisher_id=1, publisher_dt_id=now)
Book(publisher_id=[], publisher_dt_id=now) # E: Incompatible type for "publisher_id" of "Book" (got "List[Any]", expected "Union[Combinable, int, str, None]")
Book(publisher_id=1, publisher_dt_id=1) # E: Incompatible type for "publisher_dt_id" of "Book" (got "int", expected "Union[str, date, Combinable, None]")
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class Publisher(models.Model):
pass
class PublisherDatetime(models.Model):
dt_pk = models.DateTimeField(primary_key=True)
class Book(models.Model):
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
publisher_dt = models.ForeignKey(PublisherDatetime, on_delete=models.CASCADE)
- case: setting_value_to_an_array_of_ints
main: |
from typing import List, Tuple
from myapp.models import MyModel
array_val: Tuple[int, ...] = (1,)
MyModel(array=array_val)
array_val2: List[int] = [1]
MyModel(array=array_val2)
class NotAValid:
pass
array_val3: List[NotAValid] = [NotAValid()]
MyModel(array=array_val3) # E: Incompatible type for "array" of "MyModel" (got "List[NotAValid]", expected "Union[Sequence[Union[float, int, str, Combinable]], Combinable]")
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from typing import List, Tuple
from django.db import models
from django.contrib.postgres.fields import ArrayField
class MyModel(models.Model):
array = ArrayField(base_field=models.IntegerField())
- case: if_no_explicit_primary_key_id_can_be_passed
main: |
from myapp.models import MyModel
MyModel(id=1, name='maxim')
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class MyModel(models.Model):
name = models.CharField(max_length=100)
- case: arguments_can_be_passed_as_positionals
main: |
from myapp.models import MyModel, MyModel2
MyModel(1)
MyModel2(1, 12)
MyModel2(1, []) # E: Incompatible type for "name" of "MyModel2" (got "List[Any]", expected "Union[float, int, str, Combinable]")
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class MyModel(models.Model):
pass
class MyModel2(models.Model):
name = models.IntegerField()
- case: charfield_with_integer_choices
main: |
from myapp.models import MyModel
MyModel(day=1)
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class MyModel(models.Model):
day = models.CharField(max_length=3, choices=((1, 'Fri'), (2, 'Sat')))
- case: optional_id_fields_allowed_in_init
main: |
from myapp.models import Book, Publisher
Book(id=None)
Book(publisher=None)
Book(publisher_id=None)
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class Publisher(models.Model):
name = models.CharField(primary_key=True, max_length=100)
class Book(models.Model):
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
@@ -0,0 +1,38 @@
- case: meta_attribute_has_a_type_of_current_model
main: |
from myapp.models import MyUser
reveal_type(MyUser._meta) # N: Revealed type is 'django.db.models.options.Options[myapp.models.MyUser]'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class MyUser(models.Model):
pass
- 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:5: note: Revealed type is 'Any'
main:5: error: MyUser has no field named 'unknown'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
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)
@@ -0,0 +1,21 @@
- case: foreign_key_to_proxy_model_accepts_first_non_proxy_model
main: |
from myapp.models import Blog, Publisher, PublisherProxy
Blog(publisher=Publisher())
Blog.objects.create(publisher=Publisher())
Blog().publisher = Publisher()
reveal_type(Blog().publisher) # N: Revealed type is 'myapp.models.PublisherProxy*'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class Publisher(models.Model):
pass
class PublisherProxy(Publisher):
class Meta:
proxy = True
class Blog(models.Model):
publisher = models.ForeignKey(to=PublisherProxy, on_delete=models.CASCADE)
-42
View File
@@ -1,42 +0,0 @@
[CASE nullable_field_with_strict_optional_true]
from django.db import models
class MyModel(models.Model):
text_nullable = models.CharField(max_length=100, null=True)
text = models.CharField(max_length=100)
reveal_type(MyModel().text) # N: Revealed type is 'builtins.str*'
reveal_type(MyModel().text_nullable) # N: Revealed type is 'Union[builtins.str, None]'
MyModel().text = None # E: Incompatible types in assignment (expression has type "None", variable has type "Union[str, int, Combinable]")
MyModel().text_nullable = None
[out]
[CASE nullable_array_field]
from django.db import models
from django.contrib.postgres.fields import ArrayField
class MyModel(models.Model):
lst = ArrayField(base_field=models.CharField(max_length=100), null=True)
reveal_type(MyModel().lst) # N: Revealed type is 'Union[builtins.list[builtins.str], None]'
[out]
[CASE nullable_foreign_key]
from django.db import models
class Publisher(models.Model):
pass
class Book(models.Model):
publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE, null=True)
reveal_type(Book().publisher) # N: Revealed type is 'Union[main.Publisher, None]'
Book().publisher = 11 # E: Incompatible types in assignment (expression has type "int", variable has type "Union[Publisher, Combinable, None]")
[out]
[CASE nullable_self_foreign_key]
from django.db import models
class Inventory(models.Model):
parent = models.ForeignKey('self', on_delete=models.SET_NULL, null=True)
parent = Inventory()
core = Inventory(parent_id=parent.id)
reveal_type(core.parent_id) # N: Revealed type is 'Union[builtins.int, None]'
reveal_type(core.parent) # N: Revealed type is 'Union[main.Inventory, None]'
Inventory(parent=None)
Inventory(parent_id=None)
[out]
-389
View File
@@ -1,389 +0,0 @@
[CASE test_queryset_second_argument_filled_automatically]
from django.db import models
class Blog(models.Model): pass
# QuerySet where second type argument is not specified shouldn't raise any errors
class BlogQuerySet(models.QuerySet[Blog]):
pass
blog_qs: models.QuerySet[Blog]
reveal_type(blog_qs) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog, main.Blog]'
[/CASE]
[CASE test_queryset_methods]
from django.db import models
class Blog(models.Model):
created_at = models.DateTimeField()
qs = Blog.objects.all()
reveal_type(qs) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, main.Blog*]'
reveal_type(qs.get(id=1)) # N: Revealed type is 'main.Blog*'
reveal_type(iter(qs)) # N: Revealed type is 'typing.Iterator[main.Blog*]'
reveal_type(qs.iterator()) # N: Revealed type is 'typing.Iterator[main.Blog*]'
reveal_type(qs.first()) # N: Revealed type is 'Union[main.Blog*, None]'
reveal_type(qs.earliest()) # N: Revealed type is 'main.Blog*'
reveal_type(qs[0]) # N: Revealed type is 'main.Blog*'
reveal_type(qs[:9]) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, main.Blog*]'
reveal_type(qs.in_bulk()) # N: Revealed type is 'builtins.dict[Any, main.Blog*]'
# .dates / .datetimes
reveal_type(Blog.objects.dates("created_at", "day")) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, datetime.date]'
reveal_type(Blog.objects.datetimes("created_at", "day")) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, datetime.datetime]'
[/CASE]
[CASE test_combine_querysets_with_and]
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
created_at = models.DateTimeField()
# When ANDing QuerySets, the left-side's _Row parameter is used
reveal_type(Blog.objects.all() & Blog.objects.values()) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, main.Blog*]'
reveal_type(Blog.objects.values() & Blog.objects.values()) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.dict*[builtins.str, Any]]'
reveal_type(Blog.objects.values_list('id', 'name') & Blog.objects.values()) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, Tuple[builtins.int, builtins.str]]'
reveal_type(Blog.objects.values_list('id', 'name', named=True) & Blog.objects.values()) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, Tuple[builtins.int, builtins.str, fallback=main.Row]]'
reveal_type(Blog.objects.values_list('id', flat=True) & Blog.objects.values()) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.int*]'
[/CASE]
[CASE test_queryset_values_method]
from django.db import models
class Blog(models.Model): pass
values_qs = Blog.objects.values()
reveal_type(values_qs) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.dict[builtins.str, Any]]'
reveal_type(values_qs.all()) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.dict*[builtins.str, Any]]'
reveal_type(values_qs.get(id=1)) # N: Revealed type is 'builtins.dict*[builtins.str, Any]'
reveal_type(iter(values_qs)) # N: Revealed type is 'typing.Iterator[builtins.dict*[builtins.str, Any]]'
reveal_type(values_qs.iterator()) # N: Revealed type is 'typing.Iterator[builtins.dict*[builtins.str, Any]]'
reveal_type(values_qs.first()) # N: Revealed type is 'Union[builtins.dict*[builtins.str, Any], None]'
reveal_type(values_qs.earliest()) # N: Revealed type is 'builtins.dict*[builtins.str, Any]'
reveal_type(values_qs[0]) # N: Revealed type is 'builtins.dict*[builtins.str, Any]'
reveal_type(values_qs[:9]) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.dict*[builtins.str, Any]]'
reveal_type(values_qs.in_bulk()) # N: Revealed type is 'builtins.dict[Any, main.Blog*]'
[/CASE]
[CASE test_queryset_values_list_named_false_flat_false]
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
values_list_qs = Blog.objects.values_list('id', 'name')
reveal_type(values_list_qs) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, Tuple[builtins.int, builtins.str]]'
reveal_type(values_list_qs.all()) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, Tuple[builtins.int, builtins.str]]'
reveal_type(values_list_qs.get(id=1)) # N: Revealed type is 'Tuple[builtins.int, builtins.str]'
reveal_type(iter(values_list_qs)) # N: Revealed type is 'typing.Iterator[Tuple[builtins.int, builtins.str]]'
reveal_type(values_list_qs.iterator()) # N: Revealed type is 'typing.Iterator[Tuple[builtins.int, builtins.str]]'
reveal_type(values_list_qs.first()) # N: Revealed type is 'Union[Tuple[builtins.int, builtins.str], None]'
reveal_type(values_list_qs.earliest()) # N: Revealed type is 'Tuple[builtins.int, builtins.str]'
reveal_type(values_list_qs[0]) # N: Revealed type is 'Tuple[builtins.int, builtins.str]'
reveal_type(values_list_qs[:9]) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, Tuple[builtins.int, builtins.str]]'
reveal_type(values_list_qs.in_bulk()) # N: Revealed type is 'builtins.dict[Any, main.Blog*]'
[/CASE]
[CASE test_queryset_values_list_named_false_flat_true]
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
flat_values_list_qs = Blog.objects.values_list('id', flat=True)
reveal_type(flat_values_list_qs) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.int]'
reveal_type(flat_values_list_qs.all()) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.int*]'
reveal_type(flat_values_list_qs.get(id=1)) # N: Revealed type is 'builtins.int*'
reveal_type(iter(flat_values_list_qs)) # N: Revealed type is 'typing.Iterator[builtins.int*]'
reveal_type(flat_values_list_qs.iterator()) # N: Revealed type is 'typing.Iterator[builtins.int*]'
reveal_type(flat_values_list_qs.first()) # N: Revealed type is 'Union[builtins.int*, None]'
reveal_type(flat_values_list_qs.earliest()) # N: Revealed type is 'builtins.int*'
reveal_type(flat_values_list_qs[0]) # N: Revealed type is 'builtins.int*'
reveal_type(flat_values_list_qs[:9]) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.int*]'
reveal_type(flat_values_list_qs.in_bulk()) # N: Revealed type is 'builtins.dict[Any, main.Blog*]'
[/CASE]
[CASE test_queryset_values_list_named_true_flat_false]
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
named_values_list_qs = Blog.objects.values_list('id', named=True)
reveal_type(named_values_list_qs) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, Tuple[builtins.int, fallback=main.Row]]'
reveal_type(named_values_list_qs.all()) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, Tuple[builtins.int, fallback=main.Row]]'
reveal_type(named_values_list_qs.get(id=1)) # N: Revealed type is 'Tuple[builtins.int, fallback=main.Row]'
reveal_type(iter(named_values_list_qs)) # N: Revealed type is 'typing.Iterator[Tuple[builtins.int, fallback=main.Row]]'
reveal_type(named_values_list_qs.iterator()) # N: Revealed type is 'typing.Iterator[Tuple[builtins.int, fallback=main.Row]]'
reveal_type(named_values_list_qs.first()) # N: Revealed type is 'Union[Tuple[builtins.int, fallback=main.Row], None]'
reveal_type(named_values_list_qs.earliest()) # N: Revealed type is 'Tuple[builtins.int, fallback=main.Row]'
reveal_type(named_values_list_qs[0]) # N: Revealed type is 'Tuple[builtins.int, fallback=main.Row]'
reveal_type(named_values_list_qs[:9]) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, Tuple[builtins.int, fallback=main.Row]]'
reveal_type(named_values_list_qs.in_bulk()) # N: Revealed type is 'builtins.dict[Any, main.Blog*]'
[/CASE]
[CASE test_queryset_values_list_flat_true_custom_primary_key_get_element]
from django.db import models
class Blog(models.Model):
primary_uuid = models.UUIDField(primary_key=True)
# Blog has a primary key field specified, so no automatic 'id' field is expected to exist
reveal_type(Blog.objects.values_list('id', flat=True).get()) # N: Revealed type is 'Any'
# Access Blog's pk (which is UUID field)
reveal_type(Blog.objects.values_list('pk', flat=True).get()) # N: Revealed type is 'uuid.UUID*'
[/CASE]
[CASE test_queryset_values_list_flat_true_custom_primary_key_related_field]
from django.db import models
class Blog(models.Model):
primary_uuid = models.UUIDField(primary_key=True)
class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE, related_name="entries")
# Accessing PK of model pointed to by foreign key
reveal_type(Entry.objects.values_list('blog', flat=True).get()) # N: Revealed type is 'uuid.UUID*'
# Alternative way of accessing PK of model pointed to by foreign key
reveal_type(Entry.objects.values_list('blog_id', flat=True).get()) # N: Revealed type is 'uuid.UUID*'
# Yet another (more explicit) way of accessing PK of related model
reveal_type(Entry.objects.values_list('blog__pk', flat=True).get()) # N: Revealed type is 'uuid.UUID*'
# Blog has a primary key field specified, so no automatic 'id' field is expected to exist
reveal_type(Entry.objects.values_list('blog__id', flat=True).get()) # N: Revealed type is 'Any'
[/CASE]
[CASE test_queryset_values_list_error_conditions]
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
# Emulate at type-check time the errors that Django reports
Blog.objects.values_list('id', flat=True, named=True) # E: 'flat' and 'named' can't be used together.
Blog.objects.values_list('id', 'name', flat=True) # E: 'flat' is not valid when values_list is called with more than one field.
[/CASE]
[CASE test_queryset_values_list_returns_tuple_of_fields]
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
created_at = models.DateTimeField()
# values_list where parameter types are all known
reveal_type(Blog.objects.values_list('id', 'created_at').get()) # N: Revealed type is 'Tuple[builtins.int, datetime.datetime]'
tup = Blog.objects.values_list('id', 'created_at').get()
reveal_type(tup[0]) # N: Revealed type is 'builtins.int'
reveal_type(tup[1]) # N: Revealed type is 'datetime.datetime'
tup[2] # E: Tuple index out of range
# values_list returning namedtuple
reveal_type(Blog.objects.values_list('id', 'created_at', named=True).get()) # N: Revealed type is 'Tuple[builtins.int, datetime.datetime, fallback=main.Row]'
[/CASE]
[CASE test_queryset_values_list_invalid_lookups_produce_any]
from django.db import models
class Blog(models.Model): pass
class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE, related_name="entries")
# Invalid lookups produce Any type rather than giving errors.
reveal_type(Blog.objects.values_list('id', 'invalid_lookup').get()) # N: Revealed type is 'Tuple[builtins.int, Any]'
reveal_type(Blog.objects.values_list('entries_id', flat=True).get()) # N: Revealed type is 'Any'
reveal_type(Blog.objects.values_list('entries__foo', flat=True).get()) # N: Revealed type is 'Any'
reveal_type(Blog.objects.values_list('+', flat=True).get()) # N: Revealed type is 'Any'
[/CASE]
[CASE test_queryset_values_list_basic_inheritance]
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
created_at = models.DateTimeField()
class BlogChild(Blog):
child_field = models.CharField(max_length=100)
# Basic inheritance
reveal_type(BlogChild.objects.values_list('id', 'created_at', 'child_field').get()) # N: Revealed type is 'Tuple[builtins.int, datetime.datetime, builtins.str]'
[/CASE]
[CASE test_query_values_list_flat_true_plain_foreign_key]
from django.db import models
class Blog(models.Model): pass
class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
# Foreign key
reveal_type(Entry.objects.values_list('blog', flat=True).get()) # N: Revealed type is 'builtins.int*'
reveal_type(Entry.objects.values_list('blog__id', flat=True).get()) # N: Revealed type is 'builtins.int*'
reveal_type(Entry.objects.values_list('blog__pk', flat=True).get()) # N: Revealed type is 'builtins.int*'
reveal_type(Entry.objects.values_list('blog_id', flat=True).get()) # N: Revealed type is 'builtins.int*'
[/CASE]
[CASE test_query_values_list_flat_true_custom_primary_key]
from django.db import models
class Blog(models.Model):
id = models.UUIDField(primary_key=True)
class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
# Foreign key
reveal_type(Entry.objects.values_list('blog', flat=True).get()) # N: Revealed type is 'uuid.UUID*'
reveal_type(Entry.objects.values_list('blog__id', flat=True).get()) # N: Revealed type is 'uuid.UUID*'
reveal_type(Entry.objects.values_list('blog__pk', flat=True).get()) # N: Revealed type is 'uuid.UUID*'
reveal_type(Entry.objects.values_list('blog_id', flat=True).get()) # N: Revealed type is 'uuid.UUID*'
[/CASE]
[CASE test_query_values_list_flat_true_nullable_foreign_key]
from django.db import models
class Blog(models.Model): pass
class Entry(models.Model):
nullable_blog = models.ForeignKey(Blog, on_delete=models.CASCADE, related_name="+", null=True)
# Foreign key (nullable=True)
reveal_type(Entry.objects.values_list('nullable_blog', flat=True).get()) # N: Revealed type is 'Union[builtins.int, None]'
reveal_type(Entry.objects.values_list('nullable_blog_id', flat=True).get()) # N: Revealed type is 'Union[builtins.int, None]'
reveal_type(Entry.objects.values_list('nullable_blog__id', flat=True).get()) # N: Revealed type is 'Union[builtins.int, None]'
reveal_type(Entry.objects.values_list('nullable_blog__pk', flat=True).get()) # N: Revealed type is 'Union[builtins.int, None]'
[/CASE]
[CASE test_query_values_list_flat_true_foreign_key_reverse_relation]
from django.db import models
class Blog(models.Model): pass
class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE, related_name='entries')
blog_with_related_query_name = models.ForeignKey(Blog, on_delete=models.CASCADE, related_query_name="my_related_query_name")
title = models.CharField(max_length=100)
# Reverse relation of ForeignKey
reveal_type(Blog.objects.values_list('entries', flat=True).get()) # N: Revealed type is 'builtins.int*'
reveal_type(Blog.objects.values_list('entries__id', flat=True).get()) # N: Revealed type is 'builtins.int*'
reveal_type(Blog.objects.values_list('entries__title', flat=True).get()) # N: Revealed type is 'builtins.str*'
# Reverse relation of ForeignKey (with related_query_name set)
reveal_type(Blog.objects.values_list('my_related_query_name__id', flat=True).get()) # N: Revealed type is 'builtins.int*'
[/CASE]
[CASE test_query_values_list_flat_true_foreign_key_custom_primary_key_reverse_relation]
from django.db import models
class Blog(models.Model): pass
class Entry(models.Model):
id = models.UUIDField(primary_key=True)
blog = models.ForeignKey(Blog, on_delete=models.CASCADE, related_name='entries')
blog_with_related_query_name = models.ForeignKey(Blog, on_delete=models.CASCADE, related_query_name="my_related_query_name")
title = models.CharField(max_length=100)
# Reverse relation of ForeignKey
reveal_type(Blog.objects.values_list('entries', flat=True).get()) # N: Revealed type is 'uuid.UUID*'
reveal_type(Blog.objects.values_list('entries__id', flat=True).get()) # N: Revealed type is 'uuid.UUID*'
# Reverse relation of ForeignKey (with related_query_name set)
reveal_type(Blog.objects.values_list('my_related_query_name__id', flat=True).get()) # N: Revealed type is 'uuid.UUID*'
[/CASE]
[CASE test_queryset_values_list_and_values_behavior_with_no_fields_specified_and_accessing_unknown_attributes]
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
created_at = models.DateTimeField()
row_named = Blog.objects.values_list('id', 'created_at', named=True).get()
reveal_type(row_named.id) # N: Revealed type is 'builtins.int'
reveal_type(row_named.created_at) # N: Revealed type is 'datetime.datetime'
row_named.non_existent_field # E: "Row" has no attribute "non_existent_field"
# When no fields are specified, fallback to Any
row_named_no_fields = Blog.objects.values_list(named=True).get()
reveal_type(row_named_no_fields) # N: Revealed type is 'Tuple[, fallback=django._NamedTupleAnyAttr]'
# Don't complain about access to any attribute for now
reveal_type(row_named_no_fields.non_existent_field) # N: Revealed type is 'Any'
row_named_no_fields.non_existent_field = 1
# It should still behave like a NamedTuple
reveal_type(row_named_no_fields._asdict()) # N: Revealed type is 'builtins.dict[builtins.str, Any]'
dict_row = Blog.objects.values('id', 'created_at').get()
reveal_type(dict_row["id"]) # N: Revealed type is 'builtins.int'
reveal_type(dict_row["created_at"]) # N: Revealed type is 'datetime.datetime'
dict_row["non_existent_field"] # E: 'non_existent_field' is not a valid TypedDict key; expected one of ('id', 'created_at')
dict_row.pop('created_at')
dict_row.pop('non_existent_field') # E: 'non_existent_field' is not a valid TypedDict key; expected one of ('id', 'created_at')
row_dict_no_fields = Blog.objects.values().get()
reveal_type(row_dict_no_fields) # N: Revealed type is 'builtins.dict*[builtins.str, Any]'
reveal_type(row_dict_no_fields["non_existent_field"]) # N: Revealed type is 'Any'
[CASE values_with_annotate_inside_the_expressions]
from django.db import models
from django.db.models.functions import Lower, Upper
class Publisher(models.Model):
pass
class Book(models.Model):
name = models.CharField(max_length=100)
publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE, related_name='books')
reveal_type(Publisher().books.values('name', lower_name=Lower('name'), upper_name=Upper('name'))) # N: Revealed type is 'django.db.models.query.QuerySet[main.Book*, TypedDict({'name'?: builtins.str, 'lower_name'?: Any, 'upper_name'?: Any})]'
[CASE values_and_values_list_some_dynamic_fields]
from django.db import models
class Publisher(models.Model):
pass
class Book(models.Model):
name = models.CharField(max_length=100)
publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE, related_name='books')
some_dynamic_field = 'publisher'
# Correct Tuple field types should be filled in when string literal is used, while Any is used for dynamic fields
reveal_type(Publisher().books.values_list('name', some_dynamic_field)) # N: Revealed type is 'django.db.models.query.QuerySet[main.Book*, Tuple[builtins.str, Any]]'
# Flat with dynamic fields (there is only 1), means of course Any
reveal_type(Publisher().books.values_list(some_dynamic_field, flat=True)) # N: Revealed type is 'django.db.models.query.QuerySet[main.Book*, Any]'
# A NamedTuple with a fallback to Any could be implemented, but for now that's unsupported, so all
# fields on the NamedTuple are Any for now
reveal_type(Publisher().books.values_list('name', some_dynamic_field, named=True).name) # N: Revealed type is 'Any'
# A TypedDict with a fallback to Any could be implemented, but for now that's unsupported,
# so an ordinary Dict is used for now.
reveal_type(Publisher().books.values(some_dynamic_field, 'name')) # N: Revealed type is 'django.db.models.query.QuerySet[main.Book*, builtins.dict[builtins.str, Any]]'
-318
View File
@@ -1,318 +0,0 @@
[CASE test_foreign_key_field_with_related_name]
from django.db import models
class Publisher(models.Model):
pass
class Book(models.Model):
publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE,
related_name='books')
book = Book()
reveal_type(book.publisher) # N: Revealed type is 'main.Publisher*'
publisher = Publisher()
reveal_type(publisher.books) # N: Revealed type is 'django.db.models.manager.RelatedManager[main.Book]'
[CASE test_foreign_key_field_creates_attribute_with_underscore_id]
from django.db import models
class Publisher(models.Model):
pass
class Book(models.Model):
publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE)
owner = models.ForeignKey(db_column='model_id', to='db.Unknown', on_delete=models.CASCADE)
book = Book()
reveal_type(book.publisher_id) # N: Revealed type is 'builtins.int'
reveal_type(book.owner_id) # N: Revealed type is 'Any'
[CASE test_foreign_key_field_different_order_of_params]
from django.db import models
class Publisher(models.Model):
pass
class Book(models.Model):
publisher = models.ForeignKey(on_delete=models.CASCADE, to=Publisher,
related_name='books')
publisher2 = models.ForeignKey(to=Publisher, related_name='books2', on_delete=models.CASCADE)
book = Book()
reveal_type(book.publisher) # N: Revealed type is 'main.Publisher*'
reveal_type(book.publisher2) # N: Revealed type is 'main.Publisher*'
publisher = Publisher()
reveal_type(publisher.books) # N: Revealed type is 'django.db.models.manager.RelatedManager[main.Book]'
reveal_type(publisher.books2) # N: Revealed type is 'django.db.models.manager.RelatedManager[main.Book]'
[CASE test_to_parameter_as_string_with_application_name__model_imported]
from django.db import models
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from myapp.models import Publisher
class Book(models.Model):
publisher = models.ForeignKey(to='myapp.Publisher', on_delete=models.CASCADE)
book = Book()
reveal_type(book.publisher) # N: Revealed type is 'myapp.models.Publisher*'
[file myapp/__init__.py]
[file myapp/models.py]
from django.db import models
class Publisher(models.Model):
pass
[CASE test_to_parameter_as_string_with_application_name_fallbacks_to_any_if_model_not_present_in_dependency_graph]
from django.db import models
class Book(models.Model):
publisher = models.ForeignKey(to='myapp.Publisher', on_delete=models.CASCADE)
book = Book()
reveal_type(book.publisher) # N: Revealed type is 'Any'
reveal_type(book.publisher_id) # N: Revealed type is 'Any'
Book(publisher_id=1)
Book.objects.create(publisher_id=1)
[file myapp/__init__.py]
[file myapp/models.py]
from django.db import models
class Publisher(models.Model):
pass
[CASE test_circular_dependency_in_imports_with_foreign_key]
from django.db import models
class App(models.Model):
def method(self) -> None:
reveal_type(self.views) # N: Revealed type is 'django.db.models.manager.RelatedManager[main.View]'
reveal_type(self.members) # N: Revealed type is 'django.db.models.manager.RelatedManager[main.Member]'
reveal_type(self.sheets) # N: Revealed type is 'django.db.models.manager.RelatedManager[main.Sheet]'
reveal_type(self.profile) # N: Revealed type is 'main.Profile'
class View(models.Model):
app = models.ForeignKey(to=App, related_name='views', on_delete=models.CASCADE)
class Member(models.Model):
app = models.ForeignKey(related_name='members', on_delete=models.CASCADE, to=App)
class Sheet(models.Model):
app = models.ForeignKey(App, related_name='sheets', on_delete=models.CASCADE)
class Profile(models.Model):
app = models.OneToOneField(App, related_name='profile', on_delete=models.CASCADE)
[CASE test_circular_dependency_in_imports_with_string_based]
from django.db import models
from myapp.models import App
class View(models.Model):
app = models.ForeignKey(to=App, related_name='views', on_delete=models.CASCADE)
reveal_type(View().app.views) # N: Revealed type is 'django.db.models.manager.RelatedManager[main.View]'
reveal_type(View().app.unknown)
[out]
main:7: note: Revealed type is 'Any'
main:7: error: "App" has no attribute "unknown"
[file myapp/__init__.py]
[file myapp/models.py]
from django.db import models
class App(models.Model):
def method(self) -> None:
reveal_type(self.views) # N: Revealed type is 'django.db.models.manager.RelatedManager[main.View]'
[CASE models_related_managers_work_with_direct_model_inheritance_and_with_inheritance_from_other_model]
from django.db.models import Model
from django.db import models
class App(Model):
pass
class View(Model):
app = models.ForeignKey(to=App, on_delete=models.CASCADE, related_name='views')
class View2(View):
app = models.ForeignKey(to=App, on_delete=models.CASCADE, related_name='views2')
reveal_type(App().views) # N: Revealed type is 'django.db.models.manager.RelatedManager[main.View]'
reveal_type(App().views2) # N: Revealed type is 'django.db.models.manager.RelatedManager[main.View2]'
[out]
[CASE models_imported_inside_init_file_foreign_key]
[disable_cache]
from django.db import models
from myapp.models import App
class View(models.Model):
app = models.ForeignKey(to='myapp.App', related_name='views', on_delete=models.CASCADE)
reveal_type(View().app.views) # N: Revealed type is 'django.db.models.manager.RelatedManager[main.View]'
[file myapp/__init__.py]
[file myapp/models/__init__.py]
from .app import App
[file myapp/models/app.py]
from django.db import models
class App(models.Model):
pass
[/CASE]
[CASE models_imported_inside_init_file_one_to_one_field]
[disable_cache]
from django.db import models
from myapp.models import User
class Profile(models.Model):
user = models.OneToOneField(to='myapp.User', related_name='profile', on_delete=models.CASCADE)
reveal_type(Profile().user.profile) # N: Revealed type is 'main.Profile'
[file myapp/__init__.py]
[file myapp/models/__init__.py]
from .user import User
[file myapp/models/user.py]
from django.db import models
class User(models.Model):
pass
[CASE models_triple_circular_reference]
from myapp.models import App
reveal_type(App().owner.profile) # N: Revealed type is 'myapp.models.profile.Profile'
[file myapp/__init__.py]
[file myapp/models/__init__.py]
from .user import User
from .profile import Profile
from .app import App
[file myapp/models/user.py]
from django.db import models
class User(models.Model):
pass
[file myapp/models/profile.py]
from django.db import models
from myapp.models import User
class Profile(models.Model):
user = models.OneToOneField(to='myapp.User', related_name='profile', on_delete=models.CASCADE)
[file myapp/models/app.py]
from django.db import models
class App(models.Model):
owner = models.ForeignKey(to='myapp.User', on_delete=models.CASCADE, related_name='apps')
[disable_cache]
[/CASE]
[CASE many_to_many_field_converts_to_queryset_of_model_type]
from django.db import models
class App(models.Model):
pass
class Member(models.Model):
apps = models.ManyToManyField(to=App, related_name='members')
reveal_type(Member().apps) # N: Revealed type is 'django.db.models.manager.RelatedManager*[main.App]'
reveal_type(App().members) # N: Revealed type is 'django.db.models.manager.RelatedManager[main.Member]'
[out]
[CASE many_to_many_works_with_string_if_imported]
from django.db import models
from myapp.models import App
class Member(models.Model):
apps = models.ManyToManyField(to='myapp.App', related_name='members')
reveal_type(Member().apps) # N: Revealed type is 'django.db.models.manager.RelatedManager*[myapp.models.App]'
[file myapp/__init__.py]
[file myapp/models.py]
from django.db import models
class App(models.Model):
pass
[out]
[CASE foreign_key_with_self]
from django.db import models
class User(models.Model):
parent = models.ForeignKey('self', on_delete=models.CASCADE)
reveal_type(User().parent) # N: Revealed type is 'main.User*'
[out]
[CASE many_to_many_with_self]
from django.db import models
class User(models.Model):
friends = models.ManyToManyField('self')
reveal_type(User().friends) # N: Revealed type is 'django.db.models.manager.RelatedManager*[main.User]'
[out]
[CASE recursively_checking_for_base_model_in_to_parameter]
from django.db import models
class BaseModel(models.Model):
pass
class ParkingSpot(BaseModel):
pass
class Booking(BaseModel):
parking_spot = models.ForeignKey(to=ParkingSpot, null=True, on_delete=models.SET_NULL)
[out]
[CASE if_no_related_name_is_passed_create_default_related_managers]
from django.db import models
class Publisher(models.Model):
pass
class Book(models.Model):
publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE)
reveal_type(Publisher().book_set) # N: Revealed type is 'django.db.models.manager.RelatedManager[main.Book]'
[CASE underscore_id_attribute_has_set_type_of_primary_key_if_explicit]
from django.db import models
import datetime
class Publisher(models.Model):
mypk = models.CharField(max_length=100, primary_key=True)
class Book(models.Model):
publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE)
reveal_type(Book().publisher_id) # N: Revealed type is 'builtins.str'
Book(publisher_id=1)
Book(publisher_id='hello')
Book(publisher_id=datetime.datetime.now()) # E: Incompatible type for "publisher_id" of "Book" (got "datetime", expected "Union[str, int, Combinable, None]")
Book.objects.create(publisher_id=1)
Book.objects.create(publisher_id='hello')
class Publisher2(models.Model):
mypk = models.IntegerField(primary_key=True)
class Book2(models.Model):
publisher = models.ForeignKey(to=Publisher2, on_delete=models.CASCADE)
reveal_type(Book2().publisher_id) # N: Revealed type is 'builtins.int'
Book2(publisher_id=1)
Book2(publisher_id=[]) # E: Incompatible type for "publisher_id" of "Book2" (got "List[Any]", expected "Union[float, int, str, Combinable, None]")
Book2.objects.create(publisher_id=1)
Book2.objects.create(publisher_id=[]) # E: Incompatible type for "publisher_id" of "Book2" (got "List[Any]", expected "Union[float, int, str, Combinable]")
[out]
[CASE if_model_is_defined_as_name_of_the_class_look_for_it_in_the_same_file]
from django.db import models
class Book(models.Model):
publisher = models.ForeignKey(to='Publisher', on_delete=models.CASCADE)
class Publisher(models.Model):
pass
reveal_type(Book().publisher) # N: Revealed type is 'main.Publisher*'
[out]
[CASE test_foreign_key_field_without_backwards_relation]
from django.db import models
class Publisher(models.Model):
pass
class Book(models.Model):
publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE,
related_name='+')
publisher2 = models.ForeignKey(to=Publisher, on_delete=models.CASCADE,
related_name='books2')
book = Book()
reveal_type(book.publisher) # N: Revealed type is 'main.Publisher*'
publisher = Publisher()
reveal_type(publisher.books)
reveal_type(publisher.books2) # N: Revealed type is 'django.db.models.manager.RelatedManager[main.Book]'
[out]
main:16: note: Revealed type is 'Any'
main:16: error: "Publisher" has no attribute "books"; maybe "books2"?
[/CASE]
-99
View File
@@ -1,99 +0,0 @@
[CASE test_settings_are_parsed_into_django_conf_settings]
[env DJANGO_SETTINGS_MODULE=mysettings]
[disable_cache]
from django.conf import settings
# standard settings
reveal_type(settings.AUTH_USER_MODEL) # N: Revealed type is 'builtins.str'
reveal_type(settings.ROOT_DIR) # N: Revealed type is 'builtins.str'
reveal_type(settings.APPS_DIR) # N: Revealed type is 'pathlib.Path'
reveal_type(settings.OBJ) # N: Revealed type is 'django.utils.functional.LazyObject'
reveal_type(settings.NUMBERS) # N: Revealed type is 'builtins.list[builtins.str*]'
reveal_type(settings.DICT) # N: Revealed type is 'builtins.dict[Any, Any]'
[file base.py]
from pathlib import Path
ROOT_DIR = '/etc'
APPS_DIR = Path(ROOT_DIR)
[file mysettings.py]
from base import *
SECRET_KEY = 112233
NUMBERS = ['one', 'two']
DICT = {} # type: ignore
from django.utils.functional import LazyObject
OBJ = LazyObject()
[/CASE]
[CASE test_settings_could_be_defined_in_different_module_and_imported_with_star]
[env DJANGO_SETTINGS_MODULE=mysettings]
[disable_cache]
from django.conf import settings
reveal_type(settings.ROOT_DIR) # N: Revealed type is 'pathlib.Path'
reveal_type(settings.SETUP) # N: Revealed type is 'Union[builtins.int, None]'
reveal_type(settings.DATABASES) # N: Revealed type is 'builtins.dict[builtins.str*, builtins.str*]'
reveal_type(settings.LOCAL_SETTING) # N: Revealed type is 'builtins.int'
reveal_type(settings.BASE_SETTING) # N: Revealed type is 'builtins.int'
[file mysettings.py]
from local import *
from typing import Optional
SETUP: Optional[int] = 3
[file local.py]
from base import *
SETUP: int = 3
DATABASES = {'default': 'mydb'}
LOCAL_SETTING = 1
[file base.py]
from pathlib import Path
from typing import Any
SETUP: Any = None
ROOT_DIR = Path(__file__)
BASE_SETTING = 1
[/CASE]
[CASE global_settings_are_always_loaded]
from django.conf import settings
reveal_type(settings.AUTH_USER_MODEL) # N: Revealed type is 'builtins.str'
reveal_type(settings.AUTHENTICATION_BACKENDS) # N: Revealed type is 'typing.Sequence[builtins.str]'
[/CASE]
[CASE test_circular_dependency_in_settings_works_if_settings_have_annotations]
[env DJANGO_SETTINGS_MODULE=mysettings]
[disable_cache]
from django.conf import settings
class Class:
pass
reveal_type(settings.MYSETTING) # N: Revealed type is 'builtins.int'
reveal_type(settings.REGISTRY) # N: Revealed type is 'Union[main.Class, None]'
reveal_type(settings.LIST) # N: Revealed type is 'builtins.list[builtins.str]'
[file mysettings.py]
from typing import TYPE_CHECKING, Optional, List
if TYPE_CHECKING:
from main import Class
MYSETTING = 1122
REGISTRY: Optional['Class'] = None
LIST: List[str] = ['1', '2']
[/CASE]
[CASE fail_if_there_is_no_setting]
from django.conf import settings
reveal_type(settings.NOT_EXISTING)
[env DJANGO_SETTINGS_MODULE=mysettings2]
[disable_cache]
[file mysettings2.py]
[out]
main:2: note: Revealed type is 'Any'
main:2: error: 'Settings' object has no attribute 'NOT_EXISTING'
[/CASE]
-62
View File
@@ -1,62 +0,0 @@
[CASE get_object_or_404_returns_proper_types]
from django.shortcuts import get_object_or_404, get_list_or_404
from django.db import models
class MyModel(models.Model):
pass
reveal_type(get_object_or_404(MyModel)) # N: Revealed type is 'main.MyModel*'
reveal_type(get_object_or_404(MyModel.objects)) # N: Revealed type is 'main.MyModel*'
reveal_type(get_object_or_404(MyModel.objects.get_queryset())) # N: Revealed type is 'main.MyModel*'
reveal_type(get_list_or_404(MyModel)) # N: Revealed type is 'builtins.list[main.MyModel*]'
reveal_type(get_list_or_404(MyModel.objects)) # N: Revealed type is 'builtins.list[main.MyModel*]'
reveal_type(get_list_or_404(MyModel.objects.get_queryset())) # N: Revealed type is 'builtins.list[main.MyModel*]'
[/CASE]
[CASE get_user_model_returns_proper_class]
[env DJANGO_SETTINGS_MODULE=mysettings]
[disable_cache]
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from myapp.models import MyUser
from django.contrib.auth import get_user_model
UserModel = get_user_model()
reveal_type(UserModel.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.MyUser]'
[file mysettings.py]
from basic import *
INSTALLED_APPS = ('myapp',)
[file basic.py]
AUTH_USER_MODEL = 'myapp.MyUser'
[file myapp/__init__.py]
[file myapp/models.py]
from django.db import models
class MyUser(models.Model):
pass
[/CASE]
[CASE return_type_model_and_show_error_if_model_not_yet_imported]
[env DJANGO_SETTINGS_MODULE=mysettings]
[disable_cache]
from django.contrib.auth import get_user_model
UserModel = get_user_model()
reveal_type(UserModel.objects)
[file mysettings.py]
INSTALLED_APPS = ('myapp',)
AUTH_USER_MODEL = 'myapp.MyUser'
[file myapp/__init__.py]
[file myapp/models.py]
from django.db import models
class MyUser(models.Model):
pass
[out]
main:3: error: "myapp.MyUser" model class is not imported so far. Try to import it (under if TYPE_CHECKING) at the beginning of the current file
main:4: note: Revealed type is 'Any'
main:4: error: "Type[Model]" has no attribute "objects"
[/CASE]
+40
View File
@@ -0,0 +1,40 @@
- case: pyproject_toml_config
main: |
from myapp.models import MyModel
mymodel = MyModel(user_id=1)
reveal_type(mymodel.id) # N: Revealed type is 'builtins.int*'
reveal_type(mymodel.user) # N: Revealed type is 'django.contrib.auth.models.User*'
reveal_type(mymodel.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.MyModel]'
mypy_config: |
[mypy.plugins.django-stubs]
django_settings_module = mysettings
custom_settings: |
SECRET_KEY = '1'
INSTALLED_APPS = ('django.contrib.contenttypes', 'django.contrib.auth', 'myapp')
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from typing import TYPE_CHECKING
from django.db import models
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*'
- case: generate_pyproject_toml_and_settings_file_from_installed_apps_key
main: |
from myapp.models import MyModel
mymodel = MyModel(user_id=1)
reveal_type(mymodel.id) # N: Revealed type is 'builtins.int*'
installed_apps:
- django.contrib.auth
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class MyModel(models.Model):
user = models.ForeignKey('auth.User', on_delete=models.CASCADE)
+58
View File
@@ -0,0 +1,58 @@
- case: no_incompatible_meta_nested_class_false_positive
main: |
from django import forms
from myapp.models import Article, Category
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = '__all__'
class CategoryForm(forms.ModelForm):
class Meta:
model = Category
fields = '__all__'
class CompositeForm(ArticleForm, CategoryForm):
pass
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
from django import forms
class Article(models.Model):
pass
class Category(models.Model):
pass
- case: formview_methods_on_forms_return_proper_types
main: |
from typing import Any
from django import forms
from django.views.generic.edit import FormView
class MyForm(forms.ModelForm):
pass
class MyForm2(forms.ModelForm):
pass
class MyView(FormView):
form_class = MyForm
def post(self, request, *args: Any, **kwds: Any):
form_class = self.get_form_class()
reveal_type(form_class) # N: Revealed type is 'Type[main.MyForm]'
reveal_type(self.get_form(None)) # N: Revealed type is 'main.MyForm'
reveal_type(self.get_form()) # N: Revealed type is 'main.MyForm'
reveal_type(self.get_form(form_class)) # N: Revealed type is 'main.MyForm'
reveal_type(self.get_form(MyForm2)) # N: Revealed type is 'main.MyForm2'
- case: successmessagemixin_compatible_with_formmixin
main: |
from django.views.generic.edit import FormMixin
from django.contrib.messages.views import SuccessMessageMixin
class FormFirstView(FormMixin, SuccessMessageMixin):
pass
class SuccessMessageFirstView(FormMixin, SuccessMessageMixin):
pass
+42
View File
@@ -0,0 +1,42 @@
- case: transaction_atomic_contextmanager
main: |
from django.db import transaction
with transaction.atomic():
pass
with transaction.atomic(using="mydb"):
pass
with transaction.atomic(using="mydb", savepoint=False):
pass
- case: transaction_atomic_decorator
main: |
from django.db import transaction
@transaction.atomic()
def decorated_func(param1: str, param2: int) -> bool:
pass
# Ensure that the function's type is preserved
reveal_type(decorated_func) # N: Revealed type is 'def (param1: builtins.str, param2: builtins.int) -> builtins.bool'
@transaction.atomic(using="mydb")
def decorated_func_using(param1: str, param2: int) -> bool:
pass
# Ensure that the function's type is preserved
reveal_type(decorated_func_using) # N: Revealed type is 'def (param1: builtins.str, param2: builtins.int) -> builtins.bool'
class ClassWithAtomicMethod:
# Bare decorator
@transaction.atomic
def atomic_method1(self, abc: int) -> str:
pass
@transaction.atomic(savepoint=True)
def atomic_method2(self):
pass
@transaction.atomic(using="db", savepoint=True)
def atomic_method3(self, myparam: str) -> int:
pass
ClassWithAtomicMethod().atomic_method1("abc") # E: Argument 1 to "atomic_method1" of "ClassWithAtomicMethod" has incompatible type "str"; expected "int"
# Ensure that the method's type is preserved
reveal_type(ClassWithAtomicMethod().atomic_method1) # N: Revealed type is 'def (abc: builtins.int) -> builtins.str'
# Ensure that the method's type is preserved
reveal_type(ClassWithAtomicMethod().atomic_method3) # N: Revealed type is 'def (myparam: builtins.str) -> builtins.int'
+402
View File
@@ -0,0 +1,402 @@
- case: import_all_modules
main: |
import django.apps
import django.apps.config
import django.apps.registry
import django.conf.global_settings
import django.conf.urls
import django.conf.urls.i18n
import django.conf.urls.static
import django.contrib.admin.actions
import django.contrib.admin.apps
import django.contrib.admin.checks
import django.contrib.admin.decorators
import django.contrib.admin.filters
import django.contrib.admin.forms
import django.contrib.admin.helpers
import django.contrib.admin.models
import django.contrib.admin.options
import django.contrib.admin.sites
import django.contrib.admin.templatetags
import django.contrib.admin.templatetags.admin_list
import django.contrib.admin.templatetags.admin_modify
import django.contrib.admin.templatetags.admin_static
import django.contrib.admin.templatetags.admin_urls
import django.contrib.admin.templatetags.base
import django.contrib.admin.templatetags.log
import django.contrib.admin.utils
import django.contrib.admin.views
import django.contrib.admin.views.autocomplete
import django.contrib.admin.views.decorators
import django.contrib.admin.views.main
import django.contrib.admin.widgets
import django.contrib.admindocs
import django.contrib.admindocs.middleware
import django.contrib.admindocs.utils
import django.contrib.admindocs.views
import django.contrib.auth.admin
import django.contrib.auth.apps
import django.contrib.auth.backends
import django.contrib.auth.base_user
import django.contrib.auth.checks
import django.contrib.auth.context_processors
import django.contrib.auth.decorators
import django.contrib.auth.forms
import django.contrib.auth.handlers
import django.contrib.auth.handlers.modwsgi
import django.contrib.auth.hashers
import django.contrib.auth.management.commands
import django.contrib.auth.management.commands.changepassword
import django.contrib.auth.management.commands.createsuperuser
import django.contrib.auth.middleware
import django.contrib.auth.mixins
import django.contrib.auth.models
import django.contrib.auth.password_validation
import django.contrib.auth.signals
import django.contrib.auth.tokens
import django.contrib.auth.validators
import django.contrib.auth.views
import django.contrib.contenttypes.admin
import django.contrib.contenttypes.apps
import django.contrib.contenttypes.checks
import django.contrib.contenttypes.fields
import django.contrib.contenttypes.forms
import django.contrib.contenttypes.management.commands
import django.contrib.contenttypes.management.commands.remove_stale_contenttypes
import django.contrib.contenttypes.models
import django.contrib.contenttypes.views
import django.contrib.flatpages.forms
import django.contrib.flatpages.middleware
import django.contrib.flatpages.models
import django.contrib.flatpages.sitemaps
import django.contrib.flatpages.templatetags
import django.contrib.flatpages.templatetags.flatpages
import django.contrib.flatpages.views
import django.contrib.humanize.templatetags
import django.contrib.humanize.templatetags.humanize
import django.contrib.messages.api
import django.contrib.messages.context_processors
import django.contrib.messages.middleware
import django.contrib.messages.storage
import django.contrib.messages.storage.base
import django.contrib.messages.storage.cookie
import django.contrib.messages.storage.fallback
import django.contrib.messages.storage.session
import django.contrib.messages.utils
import django.contrib.messages.views
import django.contrib.postgres.fields
import django.contrib.postgres.fields.array
import django.contrib.postgres.fields.citext
import django.contrib.postgres.fields.hstore
import django.contrib.postgres.fields.jsonb
import django.contrib.postgres.fields.mixins
import django.contrib.postgres.fields.ranges
import django.contrib.postgres.operations
import django.contrib.redirects
import django.contrib.redirects.middleware
import django.contrib.redirects.models
import django.contrib.sessions.backends
import django.contrib.sessions.backends.base
import django.contrib.sessions.backends.cache
import django.contrib.sessions.backends.cached_db
import django.contrib.sessions.backends.db
import django.contrib.sessions.backends.file
import django.contrib.sessions.backends.signed_cookies
import django.contrib.sessions.base_session
import django.contrib.sessions.management.commands
import django.contrib.sessions.management.commands.clearsessions
import django.contrib.sessions.middleware
import django.contrib.sessions.models
import django.contrib.sessions.serializers
import django.contrib.sitemaps.management.commands
import django.contrib.sitemaps.management.commands.ping_google
import django.contrib.sitemaps.views
import django.contrib.sites
import django.contrib.sites.apps
import django.contrib.sites.management
import django.contrib.sites.managers
import django.contrib.sites.middleware
import django.contrib.sites.models
import django.contrib.sites.requests
import django.contrib.sites.shortcuts
import django.contrib.staticfiles.apps
import django.contrib.staticfiles.checks
import django.contrib.staticfiles.finders
import django.contrib.staticfiles.handlers
import django.contrib.staticfiles.management.commands
import django.contrib.staticfiles.management.commands.collectstatic
import django.contrib.staticfiles.management.commands.findstatic
import django.contrib.staticfiles.management.commands.runserver
import django.contrib.staticfiles.storage
import django.contrib.staticfiles.templatetags
import django.contrib.staticfiles.templatetags.staticfiles
import django.contrib.staticfiles.urls
import django.contrib.staticfiles.utils
import django.contrib.staticfiles.views
import django.contrib.syndication
import django.contrib.syndication.views
import django.core.cache.backends
import django.core.cache.backends.base
import django.core.cache.backends.db
import django.core.cache.backends.dummy
import django.core.cache.backends.filebased
import django.core.cache.backends.locmem
import django.core.cache.utils
import django.core.checks.caches
import django.core.checks.database
import django.core.checks.messages
import django.core.checks.model_checks
import django.core.checks.registry
import django.core.checks.security
import django.core.checks.security.base
import django.core.checks.security.csrf
import django.core.checks.security.sessions
import django.core.checks.templates
import django.core.checks.urls
import django.core.exceptions
import django.core.files
import django.core.files.base
import django.core.files.images
import django.core.files.locks
import django.core.files.move
import django.core.files.storage
import django.core.files.temp
import django.core.files.uploadedfile
import django.core.files.uploadhandler
import django.core.files.utils
import django.core.handlers
import django.core.handlers.base
import django.core.handlers.exception
import django.core.handlers.wsgi
import django.core.mail
import django.core.mail.message
import django.core.mail.utils
import django.core.management
import django.core.management.base
import django.core.management.color
import django.core.management.sql
import django.core.management.templates
import django.core.management.utils
import django.core.paginator
import django.core.serializers
import django.core.serializers.base
import django.core.serializers.json
import django.core.serializers.python
import django.core.servers
import django.core.servers.basehttp
import django.core.signals
import django.core.signing
import django.core.validators
import django.core.wsgi
import django.db.backends.base
import django.db.backends.base.base
import django.db.backends.base.client
import django.db.backends.base.creation
import django.db.backends.base.features
import django.db.backends.base.introspection
import django.db.backends.base.operations
import django.db.backends.base.schema
import django.db.backends.base.validation
import django.db.backends.ddl_references
import django.db.backends.dummy
import django.db.backends.dummy.base
import django.db.backends.mysql
import django.db.backends.mysql.client
import django.db.backends.postgresql
import django.db.backends.postgresql.client
import django.db.backends.sqlite3
import django.db.backends.sqlite3.base
import django.db.backends.sqlite3.creation
import django.db.backends.sqlite3.features
import django.db.backends.sqlite3.introspection
import django.db.backends.sqlite3.operations
import django.db.backends.sqlite3.schema
import django.db.backends.utils
import django.db.migrations.autodetector
import django.db.migrations.exceptions
import django.db.migrations.executor
import django.db.migrations.graph
import django.db.migrations.loader
import django.db.migrations.migration
import django.db.migrations.operations
import django.db.migrations.operations.base
import django.db.migrations.operations.fields
import django.db.migrations.operations.models
import django.db.migrations.operations.special
import django.db.migrations.operations.utils
import django.db.migrations.optimizer
import django.db.migrations.questioner
import django.db.migrations.recorder
import django.db.migrations.serializer
import django.db.migrations.state
import django.db.migrations.topological_sort
import django.db.migrations.utils
import django.db.migrations.writer
import django.db.models.aggregates
import django.db.models.base
import django.db.models.deletion
import django.db.models.expressions
import django.db.models.fields
import django.db.models.fields.files
import django.db.models.fields.mixins
import django.db.models.fields.proxy
import django.db.models.fields.related
import django.db.models.fields.related_descriptors
import django.db.models.fields.related_lookups
import django.db.models.fields.reverse_related
import django.db.models.functions
import django.db.models.functions.comparison
import django.db.models.functions.datetime
import django.db.models.functions.text
import django.db.models.functions.window
import django.db.models.indexes
import django.db.models.lookups
import django.db.models.manager
import django.db.models.options
import django.db.models.query
import django.db.models.query_utils
import django.db.models.signals
import django.db.models.sql
import django.db.models.sql.compiler
import django.db.models.sql.constants
import django.db.models.sql.datastructures
import django.db.models.sql.query
import django.db.models.sql.subqueries
import django.db.models.sql.where
import django.db.models.utils
import django.db.transaction
import django.db.utils
import django.dispatch
import django.dispatch.dispatcher
import django.forms
import django.forms.boundfield
import django.forms.fields
import django.forms.forms
import django.forms.formsets
import django.forms.models
import django.forms.renderers
import django.forms.utils
import django.forms.widgets
import django.http
import django.http.cookie
import django.http.multipartparser
import django.http.request
import django.http.response
import django.middleware
import django.middleware.cache
import django.middleware.clickjacking
import django.middleware.common
import django.middleware.csrf
import django.middleware.gzip
import django.middleware.http
import django.middleware.locale
import django.middleware.security
import django.shortcuts
import django.template.backends
import django.template.backends.base
import django.template.backends.django
import django.template.backends.dummy
import django.template.backends.jinja2
import django.template.backends.utils
import django.template.base
import django.template.context
import django.template.context_processors
import django.template.defaultfilters
import django.template.defaulttags
import django.template.engine
import django.template.exceptions
import django.template.library
import django.template.loader
import django.template.loader_tags
import django.template.loaders
import django.template.loaders.app_directories
import django.template.loaders.base
import django.template.loaders.cached
import django.template.loaders.filesystem
import django.template.loaders.locmem
import django.template.response
import django.template.smartif
import django.template.utils
import django.templatetags
import django.templatetags.cache
import django.templatetags.i18n
import django.templatetags.l10n
import django.templatetags.static
import django.templatetags.tz
import django.test
import django.test.client
import django.test.html
import django.test.runner
import django.test.selenium
import django.test.signals
import django.test.testcases
import django.test.utils
import django.urls
import django.urls.base
import django.urls.conf
import django.urls.converters
import django.urls.exceptions
import django.urls.resolvers
import django.urls.utils
import django.utils._os
import django.utils.archive
import django.utils.autoreload
import django.utils.baseconv
import django.utils.cache
import django.utils.crypto
import django.utils.datastructures
import django.utils.dateformat
import django.utils.dateparse
import django.utils.dates
import django.utils.datetime_safe
import django.utils.deconstruct
import django.utils.decorators
import django.utils.deprecation
import django.utils.duration
import django.utils.encoding
import django.utils.feedgenerator
import django.utils.formats
import django.utils.functional
import django.utils.html
import django.utils.http
import django.utils.inspect
import django.utils.ipv6
import django.utils.itercompat
import django.utils.jslex
import django.utils.log
import django.utils.lorem_ipsum
import django.utils.module_loading
import django.utils.numberformat
import django.utils.regex_helper
import django.utils.safestring
import django.utils.six
import django.utils.termcolors
import django.utils.text
import django.utils.timesince
import django.utils.timezone
import django.utils.translation
import django.utils.translation.template
import django.utils.translation.trans_null
import django.utils.translation.trans_real
import django.utils.tree
import django.utils.version
import django.utils.xmlutils
import django.views.csrf
import django.views.debug
import django.views.decorators
import django.views.decorators.cache
import django.views.decorators.clickjacking
import django.views.decorators.csrf
import django.views.decorators.debug
import django.views.decorators.gzip
import django.views.decorators.http
import django.views.decorators.vary
import django.views.defaults
import django.views.generic
import django.views.generic.base
import django.views.generic.dates
import django.views.generic.detail
import django.views.generic.edit
import django.views.generic.list
import django.views.i18n
import django.views.static
+17
View File
@@ -0,0 +1,17 @@
- case: request_object_has_user_of_type_auth_user_model
disable_cache: true
main: |
from django.http.request import HttpRequest
reveal_type(HttpRequest().user) # N: Revealed type is 'myapp.models.MyUser'
# check that other fields work ok
reveal_type(HttpRequest().method) # N: Revealed type is 'Union[builtins.str, None]'
custom_settings: |
INSTALLED_APPS = ('django.contrib.contenttypes', 'myapp')
AUTH_USER_MODEL='myapp.MyUser'
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class MyUser(models.Model):
pass
+50
View File
@@ -0,0 +1,50 @@
- case: settings_loaded_from_different_files
disable_cache: true
main: |
from django.conf import settings
# standard settings
reveal_type(settings.AUTH_USER_MODEL) # N: Revealed type is 'builtins.str'
reveal_type(settings.ROOT_DIR) # N: Revealed type is 'builtins.str'
reveal_type(settings.APPS_DIR) # N: Revealed type is 'pathlib.Path'
reveal_type(settings.NUMBERS) # N: Revealed type is 'builtins.list[builtins.str*]'
reveal_type(settings.DICT) # N: Revealed type is 'builtins.dict[Any, Any]'
custom_settings: |
from base import *
SECRET_KEY = 112233
NUMBERS = ['one', 'two']
DICT = {} # type: ignore
files:
- path: base.py
content: |
from pathlib import Path
ROOT_DIR = '/etc'
APPS_DIR = Path(ROOT_DIR)
- case: global_settings_are_always_loaded
main: |
from django.conf import settings
reveal_type(settings.AUTH_USER_MODEL) # N: Revealed type is 'builtins.str'
reveal_type(settings.AUTHENTICATION_BACKENDS) # N: Revealed type is 'typing.Sequence[builtins.str]'
installed_apps: []
- case: fail_if_there_is_no_setting
main: |
from django.conf import settings
reveal_type(settings.NOT_EXISTING)
out: |
main:2: note: Revealed type is 'Any'
main:2: error: 'Settings' object has no attribute 'NOT_EXISTING'
- case: override_default_setting_with_different_type_in_the_different_module
custom_settings: |
from settings.basic_settings import *
main: |
from django.conf import settings
reveal_type(settings.MEDIA_ROOT) # N: Revealed type is 'pathlib.Path'
reveal_type(settings.MEDIA_ROOT / 'part') # N: Revealed type is 'pathlib.Path*'
files:
- path: settings/__init__.py
- path: settings/basic_settings.py
content: |
from pathlib import Path
MEDIA_ROOT = Path()

Some files were not shown because too many files have changed in this diff Show More