mirror of
https://github.com/davidhalter/django-stubs.git
synced 2026-09-07 15:29:15 +08:00
Merge pull request #109 from mkurnikov/new-semanal
New semanal support, rewrite to use Django app registry
This commit is contained in:
+2
-2
@@ -5,7 +5,7 @@ out/
|
||||
/django
|
||||
.idea/
|
||||
.mypy_cache/
|
||||
django-sources
|
||||
build/
|
||||
dist/
|
||||
pip-wheel-metadata/
|
||||
pip-wheel-metadata/
|
||||
.pytest_cache/
|
||||
@@ -0,0 +1,4 @@
|
||||
[submodule "django-sources"]
|
||||
path = django-sources
|
||||
url = https://github.com/django/django.git
|
||||
branch = stable/2.2.x
|
||||
+10
-17
@@ -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
|
||||
@@ -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,5 +1,5 @@
|
||||
black
|
||||
pytest-mypy-plugins
|
||||
pytest-mypy-plugins==1.0.3
|
||||
flake8
|
||||
isort==4.3.4
|
||||
-e .
|
||||
|
||||
Submodule
+1
Submodule django-sources added at 4d6449e125
@@ -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]: ...
|
||||
|
||||
@@ -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 = ...
|
||||
|
||||
@@ -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 = ...
|
||||
|
||||
@@ -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,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: ...
|
||||
|
||||
@@ -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] = ...
|
||||
|
||||
@@ -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]]: ...
|
||||
|
||||
@@ -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: ...
|
||||
|
||||
@@ -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]]: ...
|
||||
|
||||
@@ -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]: ...
|
||||
|
||||
@@ -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]] = ...,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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): ...
|
||||
|
||||
@@ -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: ...
|
||||
|
||||
@@ -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]]: ...
|
||||
|
||||
@@ -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]: ...
|
||||
|
||||
@@ -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: ...
|
||||
|
||||
@@ -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 = ...
|
||||
|
||||
@@ -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: ...
|
||||
|
||||
@@ -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: ...
|
||||
|
||||
@@ -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 = ...
|
||||
|
||||
@@ -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 = ...
|
||||
|
||||
@@ -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: ...
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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] = ...): ...
|
||||
|
||||
|
||||
@@ -1,4 +1 @@
|
||||
[mypy]
|
||||
|
||||
[mypy-mypy_django_plugin.monkeypatch.*]
|
||||
ignore_errors = True
|
||||
@@ -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)))
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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'
|
||||
@@ -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)
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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, []))
|
||||
@@ -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()
|
||||
|
||||
@@ -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, [])
|
||||
@@ -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
@@ -1,3 +1,3 @@
|
||||
[tool.black]
|
||||
line-length = 120
|
||||
include = 'django-stubs/.*.pyi$'
|
||||
include = 'django-stubs/.*\.pyi$'
|
||||
+3
-1
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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'
|
||||
@@ -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 +0,0 @@
|
||||
gitpython
|
||||
+31
-832
@@ -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)
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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]
|
||||
@@ -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]
|
||||
@@ -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))
|
||||
@@ -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
|
||||
@@ -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]
|
||||
@@ -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]
|
||||
@@ -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()
|
||||
@@ -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]
|
||||
@@ -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
|
||||
@@ -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]
|
||||
@@ -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]
|
||||
@@ -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]
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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]
|
||||
@@ -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]]'
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
@@ -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]
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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'
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
Reference in New Issue
Block a user