new semanal wip 1

This commit is contained in:
Maxim Kurnikov
2019-07-16 01:22:20 +03:00
parent 9c5a6be9a7
commit b11a9a85f9
96 changed files with 4441 additions and 2370 deletions

View File

@@ -10,12 +10,6 @@ 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
python: 3.7
script: 'python ./scripts/typecheck_tests.py'

View File

@@ -1,6 +1,6 @@
import collections
import threading
from typing import Any, Callable, List, Optional, Tuple, Type, Union, Iterable, DefaultDict
from collections import OrderedDict
from typing import Any, Callable, List, Optional, Tuple, Type, Union, Iterable, DefaultDict, Dict
from django.db.migrations.state import AppConfigStub
from django.db.models.base import Model
@@ -8,16 +8,16 @@ 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: ...

View File

@@ -1,9 +1,12 @@
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
from django.db.models.manager import Manager
from django.core.checks.messages import CheckMessage
from django.db.models.options import Options
class ModelBase(type): ...
_Self = TypeVar("_Self", bound="Model")
@@ -12,7 +15,7 @@ class Model(metaclass=ModelBase):
class DoesNotExist(Exception): ...
class MultipleObjectsReturned(Exception): ...
class Meta: ...
_meta: Any
_meta: Options
_default_manager: Manager[Model]
pk: Any = ...
def __init__(self: _Self, *args, **kwargs) -> None: ...

View File

@@ -30,6 +30,9 @@ 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]
model: Type[Model]

View File

@@ -59,6 +59,7 @@ class RelatedField(FieldCacheMixin, Field[_ST, _GT]):
one_to_one: bool = ...
many_to_many: bool = ...
many_to_one: bool = ...
@property
def related_model(self) -> Union[Type[Model], str]: ...
def check(self, **kwargs: Any) -> List[Any]: ...
opts: Any = ...

View File

@@ -9,9 +9,9 @@ class BaseManager(QuerySet[_T, _T]):
creation_counter: int = ...
auto_created: bool = ...
use_in_migrations: bool = ...
def __new__(cls: Type[BaseManager], *args: Any, **kwargs: Any) -> BaseManager: ...
model: Optional[Any] = ...
name: Optional[Any] = ...
def __new__(cls: Type[BaseManager], *args: Any, **kwargs: Any) -> BaseManager: ...
def __init__(self) -> None: ...
def deconstruct(self) -> Tuple[bool, str, None, Tuple, Dict[str, int]]: ...
def check(self, **kwargs: Any) -> List[Any]: ...

View File

@@ -108,4 +108,4 @@ class Options:
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]]: ...

View File

@@ -1,12 +1,17 @@
import os
from configparser import ConfigParser
from typing import Optional
from typing import Dict, List, Optional
import dataclasses
from dataclasses import dataclass
from pytest_mypy.utils import temp_environ
@dataclass
class Config:
django_settings_module: Optional[str] = None
installed_apps: List[str] = dataclasses.field(default_factory=list)
ignore_missing_settings: bool = False
ignore_missing_model_attributes: bool = False
@@ -29,3 +34,21 @@ class Config:
ignore_missing_model_attributes=bool(ini_config.get('mypy_django_plugin',
'ignore_missing_model_attributes',
fallback=False)))
def extract_app_model_aliases(settings_module: str) -> Dict[str, str]:
with temp_environ():
os.environ['DJANGO_SETTINGS_MODULE'] = settings_module
import django
django.setup()
app_model_mapping: Dict[str, str] = {}
from django.apps import apps
for name, app_config in apps.app_configs.items():
app_label = app_config.label
for model_name, model_class in app_config.models.items():
app_model_mapping[app_label + '.' + model_class.__name__] = model_class.__module__ + '.' + model_class.__name__
return app_model_mapping

View File

@@ -27,3 +27,9 @@ MANAGER_CLASSES = {
BASE_MANAGER_CLASS_FULLNAME,
QUERYSET_CLASS_FULLNAME
}
RELATED_FIELDS_CLASSES = {
FOREIGN_KEY_FULLNAME,
ONETOONE_FIELD_FULLNAME,
MANYTOMANY_FIELD_FULLNAME
}

View File

@@ -1,24 +1,20 @@
import typing
from collections import OrderedDict
from typing import Dict, Optional, cast
from typing import Dict, Iterator, List, Optional, Set, TYPE_CHECKING, Tuple, Union, 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.nodes import (AssignmentStmt, Block, CallExpr, ClassDef, Expression, FakeInfo, GDEF, ImportedName, Lvalue, MDEF,
MemberExpr, 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,
)
from mypy.types import (AnyType, Instance, NoneTyp, TupleType, Type as MypyType, TypeOfAny, TypeVarType, TypedDictType,
UnionType)
from mypy_django_plugin.lib import metadata, fullnames
from mypy_django_plugin.lib import fullnames, metadata
if typing.TYPE_CHECKING:
if TYPE_CHECKING:
from mypy.checker import TypeChecker
def get_models_file(app_name: str, all_modules: typing.Dict[str, MypyFile]) -> Optional[MypyFile]:
def get_models_file(app_name: str, all_modules: Dict[str, MypyFile]) -> Optional[MypyFile]:
models_module = '.'.join([app_name, 'models'])
return all_modules.get(models_module)
@@ -85,7 +81,7 @@ def parse_bool(expr: Expression) -> Optional[bool]:
return None
def reparametrize_instance(instance: Instance, new_args: typing.List[Type]) -> Instance:
def reparametrize_instance(instance: Instance, new_args: List[MypyType]) -> Instance:
return Instance(instance.type, args=new_args,
line=instance.line, column=instance.column)
@@ -94,7 +90,7 @@ 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:
def extract_typevar_value(tp: Instance, typevar_name: str) -> MypyType:
if typevar_name in {'_T', '_T_co'}:
if '_T' in tp.type.type_vars:
return tp.args[tp.type.type_vars.index('_T')]
@@ -104,16 +100,16 @@ def extract_typevar_value(tp: Instance, typevar_name: str) -> Type:
def fill_typevars(tp: Instance, type_to_fill: Instance) -> Instance:
typevar_values: typing.List[Type] = []
typevar_values: List[MypyType] = []
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.
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:
@@ -126,7 +122,7 @@ def get_argument_by_name(ctx: typing.Union[FunctionContext, MethodContext], name
return args[0]
def get_argument_type_by_name(ctx: typing.Union[FunctionContext, MethodContext], name: str) -> Optional[Type]:
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.
@@ -157,14 +153,38 @@ def get_setting_expr(api: 'TypeChecker', setting_name: str) -> Optional[Expressi
return None
module_file = api.modules.get(module)
for name_expr, value_expr in iter_over_assignments(module_file):
for name_expr, value_expr in iter_over_module_level_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]]:
def iter_over_class_level_assignments(klass: ClassDef) -> Iterator[Tuple[str, Expression]]:
for stmt in klass.defs.body:
if not isinstance(stmt, AssignmentStmt):
continue
if len(stmt.lvalues) > 1:
# skip multiple assignments
continue
lvalue = stmt.lvalues[0]
if isinstance(lvalue, NameExpr):
yield lvalue.name, stmt.rvalue
def iter_over_module_level_assignments(module: MypyFile) -> Iterator[Tuple[str, Expression]]:
for stmt in module.defs:
if not isinstance(stmt, AssignmentStmt):
continue
if len(stmt.lvalues) > 1:
# skip multiple assignments
continue
lvalue = stmt.lvalues[0]
if isinstance(lvalue, NameExpr):
yield lvalue.name, stmt.rvalue
def iter_over_assignments_in_class(class_or_module: Union[ClassDef, MypyFile]
) -> Iterator[Tuple[str, Expression]]:
if isinstance(class_or_module, ClassDef):
statements = class_or_module.defs.body
else:
@@ -176,10 +196,12 @@ def iter_over_assignments(class_or_module: typing.Union[ClassDef, MypyFile]
if len(stmt.lvalues) > 1:
# not supported yet
continue
yield stmt.lvalues[0], stmt.rvalue
lvalue = stmt.lvalues[0]
if isinstance(lvalue, NameExpr):
yield lvalue.name, stmt.rvalue
def extract_field_setter_type(tp: Instance) -> Optional[Type]:
def extract_field_setter_type(tp: Instance) -> Optional[MypyType]:
""" Extract __set__ value of a field. """
if tp.type.has_base(fullnames.FIELD_FULLNAME):
return tp.args[0]
@@ -189,7 +211,7 @@ def extract_field_setter_type(tp: Instance) -> Optional[Type]:
return None
def extract_field_getter_type(tp: Type) -> Optional[Type]:
def extract_field_getter_type(tp: MypyType) -> Optional[MypyType]:
""" Extract return type of __get__ of subclass of Field"""
if not isinstance(tp, Instance):
return None
@@ -201,7 +223,7 @@ def extract_field_getter_type(tp: Type) -> Optional[Type]:
return None
def extract_explicit_set_type_of_model_primary_key(model: TypeInfo) -> Optional[Type]:
def extract_explicit_set_type_of_model_primary_key(model: TypeInfo) -> Optional[MypyType]:
"""
If field with primary_key=True is set on the model, extract its __set__ type.
"""
@@ -212,7 +234,7 @@ def extract_explicit_set_type_of_model_primary_key(model: TypeInfo) -> Optional[
return None
def extract_primary_key_type_for_get(model: TypeInfo) -> Optional[Type]:
def extract_primary_key_type_for_get(model: TypeInfo) -> Optional[MypyType]:
for field_name, props in metadata.get_fields_metadata(model).items():
is_primary_key = props.get('primary_key', False)
if is_primary_key:
@@ -220,11 +242,11 @@ def extract_primary_key_type_for_get(model: TypeInfo) -> Optional[Type]:
return None
def make_optional(typ: Type):
def make_optional(typ: MypyType) -> MypyType:
return UnionType.make_union([typ, NoneTyp()])
def make_required(typ: Type) -> Type:
def make_required(typ: MypyType) -> MypyType:
if not isinstance(typ, UnionType):
return typ
items = [item for item in typ.items if not isinstance(item, NoneTyp)]
@@ -232,14 +254,14 @@ def make_required(typ: Type) -> Type:
return UnionType.make_union(items)
def is_optional(typ: Type) -> bool:
def is_optional(typ: MypyType) -> 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:
def has_any_of_bases(info: TypeInfo, bases: Set[str]) -> bool:
for base_fullname in bases:
if info.has_base(base_fullname):
return True
@@ -257,10 +279,10 @@ def get_nested_meta_node_for_current_class(info: TypeInfo) -> Optional[TypeInfo]
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
def get_assignment_stmt_by_name(type_info: TypeInfo, name: str) -> Optional[Expression]:
for assignment_name, call_expr in iter_over_class_level_assignments(type_info.defn):
if assignment_name == name:
return call_expr
return None
@@ -268,13 +290,13 @@ def is_field_nullable(model: TypeInfo, field_name: str) -> bool:
return metadata.get_fields_metadata(model).get(field_name, {}).get('null', False)
def is_foreign_key_like(t: Type) -> bool:
def is_foreign_key_like(t: MypyType) -> bool:
if not isinstance(t, Instance):
return False
return has_any_of_bases(t.type, (fullnames.FOREIGN_KEY_FULLNAME, fullnames.ONETOONE_FIELD_FULLNAME))
return has_any_of_bases(t.type, {fullnames.FOREIGN_KEY_FULLNAME, fullnames.ONETOONE_FIELD_FULLNAME})
def build_class_with_annotated_fields(api: 'TypeChecker', base: Type, fields: 'OrderedDict[str, Type]',
def build_class_with_annotated_fields(api: 'TypeChecker', base: MypyType, fields: 'OrderedDict[str, MypyType]',
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
@@ -309,7 +331,7 @@ def build_class_with_annotated_fields(api: 'TypeChecker', base: Type, fields: 'O
return Instance(info, [])
def make_named_tuple(api: 'TypeChecker', fields: 'OrderedDict[str, Type]', name: str) -> Type:
def make_named_tuple(api: 'TypeChecker', fields: 'OrderedDict[str, MypyType]', name: str) -> MypyType:
if not fields:
# No fields specified, so fallback to a subclass of NamedTuple that allows
# __getattr__ / __setattr__ for any attribute name.
@@ -317,27 +339,27 @@ def make_named_tuple(api: 'TypeChecker', fields: 'OrderedDict[str, Type]', name:
else:
fallback = build_class_with_annotated_fields(
api=api,
base=api.named_generic_type('typing.NamedTuple', []),
base=api.named_generic_type('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:
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 make_tuple(api: 'TypeChecker', fields: typing.List[Type]) -> TupleType:
def make_tuple(api: 'TypeChecker', fields: List[MypyType]) -> 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:
def get_private_descriptor_type(type_info: TypeInfo, private_field_name: str, is_nullable: bool) -> MypyType:
node = type_info.get(private_field_name).node
if isinstance(node, Var):
descriptor_type = node.type
@@ -347,16 +369,33 @@ def get_private_descriptor_type(type_info: TypeInfo, private_field_name: str, is
return AnyType(TypeOfAny.unannotated)
def iter_over_classdefs(module_file: MypyFile) -> typing.Iterator[ClassDef]:
class IncompleteDefnException(Exception):
pass
def iter_over_toplevel_classes(module_file: MypyFile) -> 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 iter_call_assignments_in_class(klass: ClassDef) -> Iterator[Tuple[str, CallExpr]]:
for name, expression in iter_over_assignments_in_class(klass):
if isinstance(expression, CallExpr):
yield name, expression
def iter_over_field_inits_in_class(klass: ClassDef) -> Iterator[Tuple[str, CallExpr]]:
for lvalue, rvalue in iter_over_assignments_in_class(klass):
if isinstance(lvalue, NameExpr) and isinstance(rvalue, CallExpr):
field_name = lvalue.name
if isinstance(rvalue.callee, MemberExpr) and isinstance(rvalue.callee.node, TypeInfo):
if isinstance(rvalue.callee.node, FakeInfo):
raise IncompleteDefnException()
field_info = rvalue.callee.node
if field_info.has_base(fullnames.FIELD_FULLNAME):
yield field_name, rvalue
def get_related_manager_type_from_metadata(model_info: TypeInfo, related_manager_name: str,
@@ -394,3 +433,20 @@ def get_primary_key_field_name(model_info: TypeInfo) -> Optional[str]:
if is_primary_key:
return field_name
return None
def _get_app_models_file(app_name: str, all_modules: Dict[str, MypyFile]) -> Optional[MypyFile]:
models_module = '.'.join([app_name, 'models'])
return all_modules.get(models_module)
def get_model_info(app_name_dot_model_name: str, all_modules: Dict[str, MypyFile]) -> Optional[TypeInfo]:
""" Resolve app_name.ModelName into model fullname """
app_name, model_name = app_name_dot_model_name.split('.')
models_file = _get_app_models_file(app_name, all_modules)
if models_file is None:
return None
sym = models_file.names.get(model_name)
if sym and isinstance(sym.node, TypeInfo):
return sym.node

View File

View File

@@ -0,0 +1,21 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'sample_django_project.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

@@ -0,0 +1,5 @@
from django.apps import AppConfig
class MyappConfig(AppConfig):
label = 'myapp22'

View File

@@ -0,0 +1,6 @@
from django.db import models
# Create your models here.
class MyModel(models.Model):
pass

View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View File

@@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

View File

@@ -0,0 +1,121 @@
"""
Django settings for sample_django_project project.
Generated by 'django-admin startproject' using Django 2.2.3.
For more information on this file, see
https://docs.djangoproject.com/en/2.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.2/ref/settings/
"""
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'e6gj!2x(*odqwmjafrn7#35%)&rnn&^*0x-f&j0prgr--&xf+%'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'mypy_django_plugin.lib.tests.sample_django_project.myapp'
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'sample_django_project.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'sample_django_project.wsgi.application'
# Database
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# Password validation
# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/2.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/
STATIC_URL = '/static/'

View File

@@ -0,0 +1,21 @@
"""sample_django_project URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/2.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
urlpatterns = [
path('admin/', admin.site.urls),
]

View File

@@ -0,0 +1,16 @@
"""
WSGI config for sample_django_project project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'sample_django_project.settings')
application = get_wsgi_application()

View File

@@ -0,0 +1,14 @@
from mypy.options import Options
from mypy_django_plugin.lib.config import extract_app_model_aliases
from mypy_django_plugin.main import DjangoPlugin
def test_parse_django_settings():
app_model_mapping = extract_app_model_aliases('mypy_django_plugin.lib.tests.sample_django_project.root.settings')
assert app_model_mapping['myapp.MyModel'] == 'mypy_django_plugin.lib.tests.sample_django_project.myapp.models.MyModel'
def test_instantiate_plugin_with_config():
plugin = DjangoPlugin(Options())

View File

@@ -2,6 +2,7 @@ import os
from functools import partial
from typing import Callable, Dict, List, Optional, Tuple, cast
import toml
from mypy.nodes import MypyFile, NameExpr, TypeInfo
from mypy.options import Options
from mypy.plugin import (
@@ -10,7 +11,7 @@ from mypy.plugin import (
from mypy.types import AnyType, Instance, Type, TypeOfAny
from mypy_django_plugin.lib import metadata, fullnames, helpers
from mypy_django_plugin.config import Config
from mypy_django_plugin.lib.config import Config, extract_app_model_aliases
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,
@@ -31,7 +32,9 @@ from mypy_django_plugin.transformers.settings import (
)
def transform_model_class(ctx: ClassDefContext, ignore_missing_model_attributes: bool) -> None:
def transform_model_class(ctx: ClassDefContext,
ignore_missing_model_attributes: bool,
app_models_mapping: Optional[Dict[str, str]]) -> None:
try:
sym = ctx.api.lookup_fully_qualified(fullnames.MODEL_CLASS_FULLNAME)
except KeyError:
@@ -41,7 +44,7 @@ def transform_model_class(ctx: ClassDefContext, ignore_missing_model_attributes:
if sym is not None and isinstance(sym.node, TypeInfo):
metadata.get_django_metadata(sym.node)['model_bases'][ctx.cls.fullname] = 1
process_model_class(ctx, ignore_missing_model_attributes)
process_model_class(ctx, ignore_missing_model_attributes, app_models_mapping)
def transform_manager_class(ctx: ClassDefContext) -> None:
@@ -116,7 +119,7 @@ def return_type_for_id_field(ctx: AttributeContext) -> Type:
def transform_form_view(ctx: ClassDefContext) -> None:
form_class_value = helpers.get_assigned_value_for_class(ctx.cls.info, 'form_class')
form_class_value = helpers.get_assignment_stmt_by_name(ctx.cls.info, 'form_class')
if isinstance(form_class_value, NameExpr):
metadata.get_django_metadata(ctx.cls.info)['form_class'] = form_class_value.fullname
@@ -125,6 +128,17 @@ class DjangoPlugin(Plugin):
def __init__(self, options: Options) -> None:
super().__init__(options)
django_plugin_config = None
if os.path.exists('pyproject.toml'):
with open('pyproject.toml', 'r') as f:
pyproject_toml = toml.load(f)
django_plugin_config = pyproject_toml.get('tool', {}).get('django-stubs')
if django_plugin_config and 'django_settings_module' in django_plugin_config:
self.app_models_mapping = extract_app_model_aliases(django_plugin_config['django_settings_module'])
else:
self.app_models_mapping = None
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)
@@ -195,10 +209,10 @@ class DjangoPlugin(Plugin):
def get_function_hook(self, fullname: str
) -> Optional[Callable[[FunctionContext], Type]]:
if fullname == 'django.contrib.auth.get_user_model':
return partial(return_user_model_hook,
settings_modules=self._get_settings_modules_in_order_of_priority())
# if fullname == 'django.contrib.auth.get_user_model':
# return partial(return_user_model_hook,
# settings_modules=self._get_settings_modules_in_order_of_priority())
#
manager_bases = self._get_current_manager_bases()
if fullname in manager_bases:
return determine_proper_manager_type
@@ -208,48 +222,48 @@ class DjangoPlugin(Plugin):
if info.has_base(fullnames.FIELD_FULLNAME):
return fields.process_field_instantiation
if metadata.get_django_metadata(info).get('generated_init'):
return init_create.redefine_and_typecheck_model_init
# if metadata.get_django_metadata(info).get('generated_init'):
# return init_create.redefine_and_typecheck_model_init
def get_method_hook(self, fullname: str
) -> Optional[Callable[[MethodContext], Type]]:
class_name, _, method_name = fullname.rpartition('.')
if method_name == 'get_form_class':
info = self._get_typeinfo_or_none(class_name)
if info and info.has_base(fullnames.FORM_MIXIN_CLASS_FULLNAME):
return 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(fullnames.FORM_MIXIN_CLASS_FULLNAME):
return 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(fullnames.QUERYSET_CLASS_FULLNAME):
return extract_proper_type_for_queryset_values
if method_name == 'values_list':
model_info = self._get_typeinfo_or_none(class_name)
if model_info and model_info.has_base(fullnames.QUERYSET_CLASS_FULLNAME):
return extract_proper_type_queryset_values_list
if fullname in {'django.apps.registry.Apps.get_model',
'django.db.migrations.state.StateApps.get_model'}:
return determine_model_cls_from_string_for_migrations
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 None
# def get_method_hook(self, fullname: str
# ) -> Optional[Callable[[MethodContext], Type]]:
# class_name, _, method_name = fullname.rpartition('.')
#
# if method_name == 'get_form_class':
# info = self._get_typeinfo_or_none(class_name)
# if info and info.has_base(fullnames.FORM_MIXIN_CLASS_FULLNAME):
# return 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(fullnames.FORM_MIXIN_CLASS_FULLNAME):
# return 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(fullnames.QUERYSET_CLASS_FULLNAME):
# return extract_proper_type_for_queryset_values
#
# if method_name == 'values_list':
# model_info = self._get_typeinfo_or_none(class_name)
# if model_info and model_info.has_base(fullnames.QUERYSET_CLASS_FULLNAME):
# return extract_proper_type_queryset_values_list
#
# if fullname in {'django.apps.registry.Apps.get_model',
# 'django.db.migrations.state.StateApps.get_model'}:
# return determine_model_cls_from_string_for_migrations
#
# 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
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)
ignore_missing_model_attributes=self.config.ignore_missing_model_attributes,
app_models_mapping=self.app_models_mapping)
if fullname in self._get_current_manager_bases():
return transform_manager_class
@@ -257,20 +271,20 @@ class DjangoPlugin(Plugin):
# 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(fullnames.FORM_MIXIN_CLASS_FULLNAME):
return transform_form_view
# info = self._get_typeinfo_or_none(fullname)
# if info and info.has_base(fullnames.FORM_MIXIN_CLASS_FULLNAME):
# return transform_form_view
return None
def get_attribute_hook(self, fullname: str
) -> Optional[Callable[[AttributeContext], Type]]:
class_name, _, attr_name = fullname.rpartition('.')
if class_name == fullnames.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(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 in self._get_current_model_bases():
if attr_name == 'id':

View File

@@ -1,13 +1,13 @@
from typing import Optional, cast
from mypy.checker import TypeChecker
from mypy.nodes import ListExpr, NameExpr, StrExpr, TupleExpr, TypeInfo
from mypy.nodes import ListExpr, NameExpr, StrExpr, TupleExpr, TypeInfo, Expression
from mypy.plugin import FunctionContext
from mypy.types import (
AnyType, CallableType, Instance, TupleType, Type, UnionType,
)
from mypy_django_plugin.lib import metadata, fullnames, helpers
from mypy_django_plugin.lib import fullnames, helpers, metadata
def extract_referred_to_type(ctx: FunctionContext) -> Optional[Instance]:
@@ -90,7 +90,7 @@ def fill_descriptor_types_for_related_field(ctx: FunctionContext) -> 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'))
is_nullable = helpers.parse_bool(helpers.get_call_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',
@@ -101,7 +101,7 @@ def set_descriptor_types_for_field(ctx: FunctionContext) -> Instance:
def determine_type_of_array_field(ctx: FunctionContext) -> Type:
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
@@ -118,9 +118,7 @@ def transform_into_proper_return_type(ctx: FunctionContext) -> Type:
if not isinstance(default_return_type, Instance):
return default_return_type
if helpers.has_any_of_bases(default_return_type.type, (fullnames.FOREIGN_KEY_FULLNAME,
fullnames.ONETOONE_FIELD_FULLNAME,
fullnames.MANYTOMANY_FIELD_FULLNAME)):
if helpers.has_any_of_bases(default_return_type.type, fullnames.RELATED_FIELDS_CLASSES):
return fill_descriptor_types_for_related_field(ctx)
if default_return_type.type.has_base(fullnames.ARRAY_FIELD_FULLNAME):
@@ -135,55 +133,99 @@ def process_field_instantiation(ctx: FunctionContext) -> Type:
return transform_into_proper_return_type(ctx)
def _parse_choices_type(ctx: FunctionContext, choices_arg: Expression) -> Optional[str]:
if isinstance(choices_arg, (TupleExpr, ListExpr)):
# iterable of 2 element tuples of two kinds
_, analyzed_choices = ctx.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):
return first_element_type.type.fullname()
def _parse_referenced_model(ctx: FunctionContext, to_arg: Expression) -> Optional[TypeInfo]:
if isinstance(to_arg, NameExpr) and isinstance(to_arg.node, TypeInfo):
# reference to the model class
return to_arg.node
elif isinstance(to_arg, StrExpr):
referenced_model_info = helpers.get_model_info(to_arg.value, ctx.api.modules)
if referenced_model_info is not None:
return referenced_model_info
def parse_field_init_arguments_into_model_metadata(ctx: FunctionContext) -> None:
api = cast(TypeChecker, ctx.api)
outer_model = api.scope.active_class()
outer_model = ctx.api.scope.active_class()
if outer_model is None or not outer_model.has_base(fullnames.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
# Determine name of the current field
for attr_name, stmt in helpers.iter_over_class_level_assignments(outer_model.defn):
if stmt == ctx.context:
field_name = attr_name
break
if field_name is None:
else:
return
fields_metadata = metadata.get_fields_metadata(outer_model)
model_fields_metadata = metadata.get_fields_metadata(outer_model)
# primary key
is_primary_key = False
primary_key_arg = helpers.get_argument_by_name(ctx, 'primary_key')
primary_key_arg = helpers.get_call_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}
model_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()
choices_arg = helpers.get_call_argument_by_name(ctx, 'choices')
if choices_arg:
choices_type_fullname = _parse_choices_type(ctx.api, choices_arg)
if choices_type_fullname:
model_fields_metadata[field_name]['choices_type'] = choices_type_fullname
# nullability
null_arg = helpers.get_argument_by_name(ctx, 'null')
null_arg = helpers.get_call_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
model_fields_metadata[field_name]['null'] = is_nullable
# is_blankable
blank_arg = helpers.get_argument_by_name(ctx, 'blank')
blank_arg = helpers.get_call_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
model_fields_metadata[field_name]['blank'] = is_blankable
# default
default_arg = helpers.get_argument_by_name(ctx, 'default')
default_arg = helpers.get_call_argument_by_name(ctx, 'default')
if default_arg and not helpers.is_none_expr(default_arg):
fields_metadata[field_name]['default_specified'] = True
model_fields_metadata[field_name]['default_specified'] = True
if helpers.has_any_of_bases(ctx.default_return_type.type, fullnames.RELATED_FIELDS_CLASSES):
# to
to_arg = helpers.get_call_argument_by_name(ctx, 'to')
if to_arg:
referenced_model = _parse_referenced_model(ctx, to_arg)
if referenced_model is not None:
model_fields_metadata[field_name]['to'] = referenced_model.fullname()
else:
model_fields_metadata[field_name]['to'] = to_arg.value
# referenced_model = to_arg.value
# raise helpers.IncompleteDefnException()
# model_fields_metadata[field_name]['to'] = referenced_model.fullname()
# if referenced_model is not None:
# model_fields_metadata[field_name]['to'] = referenced_model.fullname()
# else:
# assert isinstance(to_arg, StrExpr)
# model_fields_metadata[field_name]['to'] = to_arg.value
# related_name
related_name_arg = helpers.get_call_argument_by_name(ctx, 'related_name')
if related_name_arg:
if isinstance(related_name_arg, StrExpr):
model_fields_metadata[field_name]['related_name'] = related_name_arg.value
else:
model_fields_metadata[field_name]['related_name'] = outer_model.name().lower() + '_set'

View File

@@ -16,7 +16,7 @@ def extract_proper_type_for_get_form(ctx: MethodContext) -> Type:
if not isinstance(object_type, Instance):
return ctx.default_return_type
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 = metadata.get_django_metadata(object_type.type).get('form_class', None)

View File

@@ -18,17 +18,20 @@ from mypy_django_plugin.lib import metadata, fullnames, helpers
class ModelClassInitializer(metaclass=ABCMeta):
api: SemanticAnalyzerPass2
model_classdef: ClassDef
app_models_mapping: Optional[Dict[str, str]] = None
@classmethod
def from_ctx(cls, ctx: ClassDefContext):
return cls(api=cast(SemanticAnalyzerPass2, ctx.api), model_classdef=ctx.cls)
def from_ctx(cls, ctx: ClassDefContext, app_models_mapping: Optional[Dict[str, str]]):
return cls(api=cast(SemanticAnalyzerPass2, ctx.api),
model_classdef=ctx.cls,
app_models_mapping=app_models_mapping)
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
return helpers.get_assigned_value_for_class(meta_node, name)
return helpers.get_assignment_stmt_by_name(meta_node, name)
def is_abstract_model(self) -> bool:
is_abstract_expr = self.get_meta_attribute('abstract')
@@ -46,29 +49,74 @@ class ModelClassInitializer(metaclass=ABCMeta):
var.is_initialized_in_class = True
self.model_classdef.info.names[name] = SymbolTableNode(MDEF, var, plugin_generated=True)
def model_has_name_defined(self, name: str) -> bool:
return name in self.model_classdef.info.names
@abstractmethod
def run(self) -> None:
raise NotImplementedError()
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 {fullnames.FOREIGN_KEY_FULLNAME,
fullnames.ONETOONE_FIELD_FULLNAME}:
yield lvalue, rvalue
for field_name, field_init in helpers.iter_over_field_inits_in_class(klass):
field_info = field_init.callee.node
assert isinstance(field_info, TypeInfo)
if helpers.has_any_of_bases(field_init.callee.node, {fullnames.FOREIGN_KEY_FULLNAME,
fullnames.ONETOONE_FIELD_FULLNAME}):
yield field_name, field_init
class SetIdAttrsForRelatedFields(ModelClassInitializer):
class AddReferencesToRelatedModels(ModelClassInitializer):
"""
For every
attr1 = models.ForeignKey(to=MyModel)
sets `attr1_id` attribute to the current model.
"""
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'))
for field_name, field_init_expr in helpers.iter_over_field_inits_in_class(self.model_classdef):
ref_id_name = field_name + '_id'
field_info = field_init_expr.callee.node
assert isinstance(field_info, TypeInfo)
if not self.model_has_name_defined(ref_id_name):
if helpers.has_any_of_bases(field_info, {fullnames.FOREIGN_KEY_FULLNAME,
fullnames.ONETOONE_FIELD_FULLNAME}):
self.add_new_node_to_model_class(name=ref_id_name,
typ=self.api.builtin_type('builtins.int'))
# field_init_expr.callee.node
#
# for field_name, field_init_expr in helpers.iter_call_assignments_in_class(self.model_classdef):
# ref_id_name = field_name + '_id'
# if not self.model_has_name_defined(ref_id_name):
# field_class_info = field_init_expr.callee.node
# if not field_class_info:
#
# if not field_init_expr.callee.node:
#
# if isinstance(field_init_expr.callee.node, TypeInfo) \
# and helpers.has_any_of_bases(field_init_expr.callee.node,
# {fullnames.FOREIGN_KEY_FULLNAME,
# fullnames.ONETOONE_FIELD_FULLNAME}):
# self.add_new_node_to_model_class(name=ref_id_name,
# typ=self.api.builtin_type('builtins.int'))
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:
@@ -77,24 +125,24 @@ class InjectAnyAsBaseForNestedMeta(ModelClassInitializer):
class AddDefaultObjectsManager(ModelClassInitializer):
def add_new_manager(self, name: str, manager_type: Optional[Instance]) -> None:
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)
def add_private_default_manager(self, manager_type: Optional[Instance]) -> None:
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]]:
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
for manager_name, call_expr in helpers.iter_call_assignments_in_class(base.defn):
callee_expr = call_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(fullnames.BASE_MANAGER_CLASS_FULLNAME):
@@ -102,12 +150,12 @@ class AddDefaultObjectsManager(ModelClassInitializer):
return managers
def run(self) -> None:
existing_managers = self.get_existing_managers()
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)
self._add_new_manager(name=manager_name, manager_type=manager_type)
if first_manager_type is None:
first_manager_type = manager_type
else:
@@ -117,33 +165,46 @@ class AddDefaultObjectsManager(ModelClassInitializer):
first_manager_type = self.api.named_type_or_none(fullnames.MANAGER_CLASS_FULLNAME,
args=[Instance(self.model_classdef.info, [])])
self.add_new_manager('objects', manager_type=first_manager_type)
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)
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)
self._add_private_default_manager(first_manager_type)
class AddIdAttributeIfPrimaryKeyTrueIsNotSet(ModelClassInitializer):
class AddDefaultPrimaryKey(ModelClassInitializer):
"""
Sets default integer `id` attribute, if:
* model is not abstract (abstract = False)
* there's no field with primary_key=True
"""
def run(self) -> None:
if self.is_abstract_model():
# no need for .id attr
# abstract models cannot be instantiated, and do not need `id` attribute
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')])):
for _, field_init_expr in helpers.iter_over_field_inits_in_class(self.model_classdef):
if ('primary_key' in field_init_expr.arg_names
and self.api.parse_bool(field_init_expr.args[field_init_expr.arg_names.index('primary_key')])):
break
else:
self.add_new_node_to_model_class('id', self.api.builtin_type('builtins.object'))
self.add_new_node_to_model_class('id', self.api.builtin_type('builtins.int'))
def _get_to_expr(field_init_expr) -> Expression:
if 'to' in field_init_expr.arg_names:
return field_init_expr.args[field_init_expr.arg_names.index('to')]
else:
return field_init_expr.args[0]
class AddRelatedManagers(ModelClassInitializer):
def add_related_manager_variable(self, manager_name: str, related_field_type_data: Dict[str, Any]) -> None:
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'))
@@ -153,24 +214,41 @@ class AddRelatedManagers(ModelClassInitializer):
def run(self) -> None:
for module_name, module_file in self.api.modules.items():
for model_defn in helpers.iter_over_classdefs(module_file):
if not model_defn.info:
self.api.defer()
for model_classdef in helpers.iter_over_toplevel_classes(module_file):
for field_name, field_init in helpers.iter_over_field_inits_in_class(model_classdef):
field_info = field_init.callee.node
assert isinstance(field_info, TypeInfo)
for lvalue, field_init in helpers.iter_call_assignments(model_defn):
if is_related_field(field_init, module_file):
try:
referenced_model_fullname = extract_referenced_model_fullname(field_init,
module_file=module_file,
all_modules=self.api.modules)
except helpers.SelfReference:
referenced_model_fullname = model_defn.fullname
if helpers.has_any_of_bases(field_info, fullnames.RELATED_FIELDS_CLASSES):
# try:
to_arg_expr = _get_to_expr(field_init)
if isinstance(to_arg_expr, NameExpr):
referenced_model_fullname = module_file.names[to_arg_expr.name].fullname
else:
assert isinstance(to_arg_expr, StrExpr)
value = to_arg_expr.value
if value == 'self':
# reference to the same model class
referenced_model_fullname = model_classdef.fullname
elif '.' not in value:
# reference to class in the current module
referenced_model_fullname = module_name + '.' + value
else:
referenced_model_fullname = self.app_models_mapping[value]
except helpers.SameFileModel as exc:
referenced_model_fullname = module_name + '.' + exc.model_cls_name
# referenced_model_fullname = extract_referenced_model_fullname(field_init,
# module_file=module_file,
# all_modules=self.api.modules)
# if not referenced_model_fullname:
# raise helpers.IncompleteDefnException('Cannot parse referenced model fullname')
# except helpers.SelfReference:
# referenced_model_fullname = model_classdef.fullname
#
# except helpers.SameFileModel as exc:
# referenced_model_fullname = module_name + '.' + exc.model_cls_name
if self.model_classdef.fullname == referenced_model_fullname:
related_name = model_defn.name.lower() + '_set'
if 'related_name' in field_init.arg_names:
related_name_expr = field_init.args[field_init.arg_names.index('related_name')]
if not isinstance(related_name_expr, StrExpr):
@@ -180,9 +258,10 @@ class AddRelatedManagers(ModelClassInitializer):
if related_name == '+':
# No backwards relation is desired
continue
else:
related_name = model_classdef.name.lower() + '_set'
# Default related_query_name to related_name
related_query_name = related_name
if 'related_query_name' in field_init.arg_names:
related_query_name_expr = field_init.args[field_init.arg_names.index('related_query_name')]
if isinstance(related_query_name_expr, StrExpr):
@@ -191,20 +270,24 @@ class AddRelatedManagers(ModelClassInitializer):
# not string 'related_query_name=' is not yet supported
related_query_name = None
# TODO: Handle defaulting to model name if related_name is not set
# as long as Model is not a Generic, one level depth is fine
if field_init.callee.name in {'ForeignKey', 'ManyToManyField'}:
field_type_data = {
'manager': fullnames.RELATED_MANAGER_CLASS_FULLNAME,
'of': [model_defn.info.fullname()]
}
else:
field_type_data = {
'manager': model_defn.info.fullname(),
'of': []
}
related_query_name = related_name
self.add_related_manager_variable(related_name, related_field_type_data=field_type_data)
# if helpers.has_any_of_bases(field_info, {fullnames.FOREIGN_KEY_FULLNAME,
# fullnames.MANYTOMANY_FIELD_FULLNAME}):
# # as long as Model is not a Generic, one level depth is fine
# field_type_data = {
# 'manager': fullnames.RELATED_MANAGER_CLASS_FULLNAME,
# 'of': [model_classdef.info.fullname()]
# }
# else:
# field_type_data = {
# 'manager': model_classdef.info.fullname(),
# 'of': []
# }
self.add_new_node_to_model_class(related_name, self.api.builtin_type('builtins.object'))
# 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
@@ -239,22 +322,24 @@ def is_related_field(expr: CallExpr, module_file: MypyFile) -> bool:
return False
def extract_referenced_model_fullname(rvalue_expr: CallExpr,
def extract_referenced_model_fullname(field_init_expr: CallExpr,
module_file: MypyFile,
all_modules: Dict[str, MypyFile]) -> Optional[str]:
""" Returns fullname of a Model referenced in "to=" argument of the CallExpr"""
if 'to' in rvalue_expr.arg_names:
to_expr = rvalue_expr.args[rvalue_expr.arg_names.index('to')]
if 'to' in field_init_expr.arg_names:
to_expr = field_init_expr.args[field_init_expr.arg_names.index('to')]
else:
to_expr = rvalue_expr.args[0]
to_expr = field_init_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
@@ -284,16 +369,18 @@ def add_get_set_attr_fallback_to_any(ctx: ClassDefContext):
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,
ignore_unknown_attributes: bool,
app_models_mapping: Optional[Dict[str, str]]) -> None:
initializers = [
InjectAnyAsBaseForNestedMeta,
AddDefaultPrimaryKey,
AddReferencesToRelatedModels,
AddDefaultObjectsManager,
AddIdAttributeIfPrimaryKeyTrueIsNotSet,
SetIdAttrsForRelatedFields,
AddRelatedManagers,
]
for initializer_cls in initializers:
initializer_cls.from_ctx(ctx).run()
initializer_cls.from_ctx(ctx, app_models_mapping).run()
add_dummy_init_method(ctx)

View File

@@ -110,8 +110,8 @@ def extract_proper_type_queryset_values_list(ctx: MethodContext) -> Type:
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'))
flat = helpers.parse_bool(helpers.get_call_argument_by_name(ctx, 'flat'))
named = helpers.parse_bool(helpers.get_call_argument_by_name(ctx, 'named'))
api = cast(TypeChecker, ctx.api)
if named and flat:

View File

@@ -53,7 +53,7 @@ def return_user_model_hook(ctx: FunctionContext, settings_modules: List[str]) ->
setting_module = api.modules[setting_module_name]
model_path = None
for name_expr, rvalue_expr in helpers.iter_over_assignments(setting_module):
for name_expr, rvalue_expr in helpers.iter_over_assignments_in_class(setting_module):
if isinstance(name_expr, NameExpr) and isinstance(rvalue_expr, StrExpr):
if name_expr.name == 'AUTH_USER_MODEL':
model_path = rvalue_expr.value

View File

@@ -0,0 +1,80 @@
import os
from collections import defaultdict
from dataclasses import dataclass
from typing import Any, Dict, List, Optional, TYPE_CHECKING, Tuple, Type
from django.db.models.base import Model
from django.utils.functional import cached_property
from pytest_mypy.utils import temp_environ
if TYPE_CHECKING:
from django.apps.registry import Apps
from django.conf import LazySettings
@dataclass
class DjangoPluginConfig:
ignore_missing_settings: bool = False
ignore_missing_model_attributes: bool = False
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)
models.Manager.__class_getitem__ = classmethod(noop_class_getitem)
from django.conf import settings
from django.apps import apps
apps.get_models.cache_clear()
apps.get_swappable_settings_name.cache_clear()
apps.populate(settings.INSTALLED_APPS)
assert apps.apps_ready
assert settings.configured
return apps, settings
class DjangoContext:
def __init__(self, plugin_toml_config: Optional[Dict[str, Any]]) -> None:
self.config = DjangoPluginConfig()
django_settings_module = None
if plugin_toml_config:
self.config.ignore_missing_settings = plugin_toml_config.get('ignore_missing_settings', False)
self.config.ignore_missing_model_attributes = plugin_toml_config.get('ignore_missing_model_attributes', False)
django_settings_module = plugin_toml_config.get('django_settings_module', None)
self.apps_registry: Optional[Dict[str, str]] = None
self.settings: LazySettings = None
if django_settings_module:
apps, settings = initialize_django(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

View File

@@ -0,0 +1,35 @@
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
}

View File

@@ -0,0 +1,99 @@
from typing import Dict, List, Optional, Set, Union
from mypy.nodes import Expression, MypyFile, NameExpr, SymbolNode, TypeInfo, Var
from mypy.plugin import FunctionContext, MethodContext
from mypy.types import AnyType, Instance, NoneTyp, Type as MypyType, TypeOfAny, UnionType
class IncompleteDefnException(Exception):
pass
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 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:
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 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

View File

@@ -0,0 +1,27 @@
from typing import Any, Dict, List
from mypy.nodes import TypeInfo
def get_django_metadata(model_info: TypeInfo) -> Dict[str, Any]:
return model_info.metadata.setdefault('django', {})
def get_related_field_primary_key_names(base_model: TypeInfo) -> List[str]:
return get_django_metadata(base_model).setdefault('related_field_primary_keys', [])
def get_fields_metadata(model: TypeInfo) -> Dict[str, Any]:
return get_django_metadata(model).setdefault('fields', {})
def get_lookups_metadata(model: TypeInfo) -> Dict[str, Any]:
return get_django_metadata(model).setdefault('lookups', {})
def get_related_managers_metadata(model: TypeInfo) -> Dict[str, Any]:
return get_django_metadata(model).setdefault('related_managers', {})
def get_managers_metadata(model: TypeInfo) -> Dict[str, Any]:
return get_django_metadata(model).setdefault('managers', {})

View File

@@ -0,0 +1,21 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'sample_django_project.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

@@ -0,0 +1,5 @@
from django.apps import AppConfig
class MyappConfig(AppConfig):
label = 'myapp22'

View File

@@ -0,0 +1,6 @@
from django.db import models
# Create your models here.
class MyModel(models.Model):
pass

View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View File

@@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

View File

@@ -0,0 +1,121 @@
"""
Django settings for sample_django_project project.
Generated by 'django-admin startproject' using Django 2.2.3.
For more information on this file, see
https://docs.djangoproject.com/en/2.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.2/ref/settings/
"""
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'e6gj!2x(*odqwmjafrn7#35%)&rnn&^*0x-f&j0prgr--&xf+%'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'mypy_django_plugin.lib.tests.sample_django_project.myapp'
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'sample_django_project.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'sample_django_project.wsgi.application'
# Database
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# Password validation
# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/2.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/
STATIC_URL = '/static/'

View File

@@ -0,0 +1,21 @@
"""sample_django_project URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/2.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
urlpatterns = [
path('admin/', admin.site.urls),
]

View File

@@ -0,0 +1,16 @@
"""
WSGI config for sample_django_project project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'sample_django_project.settings')
application = get_wsgi_application()

View File

@@ -0,0 +1,14 @@
from mypy.options import Options
from mypy_django_plugin.lib.config import extract_app_model_aliases
from mypy_django_plugin.main import DjangoPlugin
def test_parse_django_settings():
app_model_mapping = extract_app_model_aliases('mypy_django_plugin.lib.tests.sample_django_project.root.settings')
assert app_model_mapping['myapp.MyModel'] == 'mypy_django_plugin.lib.tests.sample_django_project.myapp.models.MyModel'
def test_instantiate_plugin_with_config():
plugin = DjangoPlugin(Options())

View File

@@ -0,0 +1,173 @@
import os
from functools import partial
from typing import Callable, Dict, List, Optional, Tuple, Type
import toml
from mypy.nodes import MypyFile, TypeInfo
from mypy.options import Options
from mypy.plugin import ClassDefContext, FunctionContext, Plugin, MethodContext
from mypy.types import Type as MypyType
from mypy_django_plugin_newsemanal.context import DjangoContext
from mypy_django_plugin_newsemanal.lib import fullnames, metadata
from mypy_django_plugin_newsemanal.transformers import fields, settings, querysets, init_create
from mypy_django_plugin_newsemanal.transformers.models import process_model_class
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):
metadata.get_django_metadata(sym.node)['model_bases'][ctx.cls.fullname] = 1
else:
if not ctx.api.final_iteration:
ctx.api.defer()
return
process_model_class(ctx, django_context)
def transform_manager_class(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):
metadata.get_django_metadata(sym.node)['manager_bases'][ctx.cls.fullname] = 1
class NewSemanalDjangoPlugin(Plugin):
def __init__(self, options: Options) -> None:
super().__init__(options)
plugin_toml_config = None
if os.path.exists('pyproject.toml'):
with open('pyproject.toml', 'r') as f:
pyproject_toml = toml.load(f)
plugin_toml_config = pyproject_toml.get('tool', {}).get('django-stubs')
self.django_context = DjangoContext(plugin_toml_config)
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 (metadata.get_django_metadata(model_sym.node)
.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(fullnames.MANAGER_CLASS_FULLNAME)
if model_sym is not None and isinstance(model_sym.node, TypeInfo):
return (metadata.get_django_metadata(model_sym.node)
.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 metadata.get_django_metadata(model_sym.node).setdefault('model_bases',
{fullnames.MODEL_CLASS_FULLNAME: 1})
else:
return {}
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]]:
# for `get_user_model()`
if file.fullname() == 'django.contrib.auth':
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:
for related_object in model_class._meta.related_objects:
related_model_module = related_object.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], MypyType]]:
if fullname == 'django.contrib.auth.get_user_model':
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 querysets.determine_proper_manager_type
info = self._get_typeinfo_or_none(fullname)
if info:
if info.has_base(fullnames.FIELD_FULLNAME):
return partial(fields.process_field_instantiation, django_context=self.django_context)
# if info.has_base(fullnames.MODEL_CLASS_FULLNAME):
# return partial(init_create.redefine_and_typecheck_model_init, django_context=self.django_context)
# def get_method_hook(self, fullname: str
# ) -> Optional[Callable[[MethodContext], Type]]:
# class_name, _, method_name = fullname.rpartition('.')
#
#
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, django_context=self.django_context)
if fullname in self._get_current_manager_bases():
return transform_manager_class
# def get_attribute_hook(self, fullname: str
# ) -> Optional[Callable[[AttributeContext], MypyType]]:
# print(fullname)
# class_name, _, attr_name = fullname.rpartition('.')
# # if class_name == fullnames.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 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:
# attr_sym = model_info.get(attr_name)
# if attr_sym and isinstance(attr_sym.node, TypeInfo) \
# and helpers.has_any_of_bases(attr_sym.node, fullnames.MANAGER_CLASSES):
# return partial(querysets.determite_manager_type, django_context=self.django_context)
#
# # related_managers = metadata.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
# def get_type_analyze_hook(self, fullname: str
# ) -> Optional[Callable[[AnalyzeTypeContext], MypyType]]:
# queryset_bases = self._get_current_queryset_bases()
# if fullname in queryset_bases:
# return partial(querysets.set_first_generic_param_as_default_for_second, fullname=fullname)
def plugin(version):
return NewSemanalDjangoPlugin

View File

@@ -0,0 +1,264 @@
from typing import Optional, Tuple, cast
from mypy.checker import TypeChecker
from mypy.nodes import Expression, ListExpr, NameExpr, StrExpr, TupleExpr, TypeInfo
from mypy.plugin import FunctionContext
from mypy.types import AnyType, CallableType, Instance, TupleType, Type as MypyType, UnionType
from mypy_django_plugin_newsemanal.context import DjangoContext
from mypy_django_plugin_newsemanal.lib import fullnames, helpers, metadata
def extract_referred_to_type(ctx: FunctionContext, django_context: DjangoContext) -> 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!r}',
context=ctx.context)
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
model_string = to_arg_expr.value
if model_string == 'self':
model_fullname = api.tscope.classes[-1].fullname()
elif '.' not in model_string:
model_fullname = api.tscope.classes[-1].module_name + '.' + model_string
else:
if django_context.app_models is not None and model_string in django_context.app_models:
model_fullname = django_context.app_models[model_string]
else:
ctx.api.fail(f'Cannot find referenced model for {model_string!r}', context=ctx.context)
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):
raise helpers.IncompleteDefnException(model_fullname)
return Instance(model_info, [])
referred_to_type = arg_type.ret_type
assert isinstance(referred_to_type, Instance)
if not referred_to_type.type.has_base(fullnames.MODEL_CLASS_FULLNAME):
ctx.api.msg.fail(f'to= parameter value must be a subclass of {fullnames.MODEL_CLASS_FULLNAME!r}',
context=ctx.context)
return None
return referred_to_type
def convert_any_to_type(typ: MypyType, replacement_type: MypyType) -> MypyType:
"""
Converts any encountered Any (in typ itself, or in generic parameters) into referred_to_type
"""
if isinstance(typ, UnionType):
converted_items = []
for item in typ.items:
converted_items.append(convert_any_to_type(item, replacement_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(replacement_type)
else:
args.append(default_arg)
return helpers.reparametrize_instance(typ, args)
if isinstance(typ, AnyType):
return replacement_type
return typ
def get_referred_to_model_fullname(ctx: FunctionContext, django_context: DjangoContext) -> str:
to_arg_type = helpers.get_call_argument_type_by_name(ctx, 'to')
if isinstance(to_arg_type, CallableType):
assert isinstance(to_arg_type.ret_type, Instance)
return to_arg_type.ret_type.type.fullname()
to_arg_expr = helpers.get_call_argument_by_name(ctx, 'to')
if not isinstance(to_arg_expr, StrExpr):
raise helpers.IncompleteDefnException(f'Not a string: {to_arg_expr}')
outer_model_info = ctx.api.tscope.classes[-1]
assert isinstance(outer_model_info, TypeInfo)
model_string = to_arg_expr.value
if model_string == 'self':
return outer_model_info.fullname()
if '.' not in model_string:
# same file class
return outer_model_info.module_name + '.' + model_string
model_cls = django_context.apps_registry.get_model(model_string)
model_fullname = helpers.get_class_fullname(model_cls)
return model_fullname
def fill_descriptor_types_for_related_field(ctx: FunctionContext, django_context: DjangoContext) -> MypyType:
referred_to_fullname = get_referred_to_model_fullname(ctx, django_context)
referred_to_typeinfo = helpers.lookup_fully_qualified_generic(referred_to_fullname, ctx.api.modules)
assert isinstance(referred_to_typeinfo, TypeInfo)
referred_to_type = Instance(referred_to_typeinfo, [])
default_related_field_type = set_descriptor_types_for_field(ctx)
# replace Any with referred_to_type
args = []
for default_arg in default_related_field_type.args:
args.append(convert_any_to_type(default_arg, referred_to_type))
return helpers.reparametrize_instance(default_related_field_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_call_argument_by_name(ctx, 'null'))
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 transform_into_proper_return_type(ctx: FunctionContext, django_context: DjangoContext) -> MypyType:
default_return_type = ctx.default_return_type
assert isinstance(default_return_type, Instance)
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 process_field_instantiation(ctx: FunctionContext, django_context: DjangoContext) -> MypyType:
# Parse __init__ parameters of field into corresponding Model's metadata
# parse_field_init_arguments_into_model_metadata(ctx)
return transform_into_proper_return_type(ctx, django_context)
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_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))
return helpers.reparametrize_instance(default_return_type, args)
# def _parse_choices_type(ctx: FunctionContext, choices_arg: Expression) -> Optional[str]:
# if isinstance(choices_arg, (TupleExpr, ListExpr)):
# # iterable of 2 element tuples of two kinds
# _, analyzed_choices = ctx.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):
# return first_element_type.type.fullname()
# def _parse_referenced_model(ctx: FunctionContext, to_arg: Expression) -> Optional[TypeInfo]:
# if isinstance(to_arg, NameExpr) and isinstance(to_arg.node, TypeInfo):
# # reference to the model class
# return to_arg.node
#
# elif isinstance(to_arg, StrExpr):
# referenced_model_info = helpers.get_model_info(to_arg.value, ctx.api.modules)
# if referenced_model_info is not None:
# return referenced_model_info
# def parse_field_init_arguments_into_model_metadata(ctx: FunctionContext) -> None:
# outer_model = ctx.api.scope.active_class()
# if outer_model is None or not outer_model.has_base(fullnames.MODEL_CLASS_FULLNAME):
# # outside models.Model class, undetermined
# return
#
# # Determine name of the current field
# for attr_name, stmt in helpers.iter_over_class_level_assignments(outer_model.defn):
# if stmt == ctx.context:
# field_name = attr_name
# break
# else:
# return
#
# model_fields_metadata = metadata.get_fields_metadata(outer_model)
#
# # primary key
# is_primary_key = False
# primary_key_arg = helpers.get_call_argument_by_name(ctx, 'primary_key')
# if primary_key_arg:
# is_primary_key = helpers.parse_bool(primary_key_arg)
# model_fields_metadata[field_name] = {'primary_key': is_primary_key}
#
# # choices
# choices_arg = helpers.get_call_argument_by_name(ctx, 'choices')
# if choices_arg:
# choices_type_fullname = _parse_choices_type(ctx.api, choices_arg)
# if choices_type_fullname:
# model_fields_metadata[field_name]['choices_type'] = choices_type_fullname
#
# # nullability
# null_arg = helpers.get_call_argument_by_name(ctx, 'null')
# is_nullable = False
# if null_arg:
# is_nullable = helpers.parse_bool(null_arg)
# model_fields_metadata[field_name]['null'] = is_nullable
#
# # is_blankable
# blank_arg = helpers.get_call_argument_by_name(ctx, 'blank')
# is_blankable = False
# if blank_arg:
# is_blankable = helpers.parse_bool(blank_arg)
# model_fields_metadata[field_name]['blank'] = is_blankable
#
# # default
# default_arg = helpers.get_call_argument_by_name(ctx, 'default')
# if default_arg and not helpers.is_none_expr(default_arg):
# model_fields_metadata[field_name]['default_specified'] = True
#
# if helpers.has_any_of_bases(ctx.default_return_type.type, fullnames.RELATED_FIELDS_CLASSES):
# # to
# to_arg = helpers.get_call_argument_by_name(ctx, 'to')
# if to_arg:
# referenced_model = _parse_referenced_model(ctx, to_arg)
# if referenced_model is not None:
# model_fields_metadata[field_name]['to'] = referenced_model.fullname()
# else:
# model_fields_metadata[field_name]['to'] = to_arg.value
# # referenced_model = to_arg.value
# # raise helpers.IncompleteDefnException()
#
# # model_fields_metadata[field_name]['to'] = referenced_model.fullname()
# # if referenced_model is not None:
# # model_fields_metadata[field_name]['to'] = referenced_model.fullname()
# # else:
# # assert isinstance(to_arg, StrExpr)
# # model_fields_metadata[field_name]['to'] = to_arg.value
#
# # related_name
# related_name_arg = helpers.get_call_argument_by_name(ctx, 'related_name')
# if related_name_arg:
# if isinstance(related_name_arg, StrExpr):
# model_fields_metadata[field_name]['related_name'] = related_name_arg.value
# else:
# model_fields_metadata[field_name]['related_name'] = outer_model.name().lower() + '_set'

View File

@@ -0,0 +1,35 @@
from typing import cast
from mypy.checker import TypeChecker
from mypy.nodes import Argument, Var, ARG_NAMED
from mypy.plugin import FunctionContext
from mypy.types import Type as MypyType, Instance
from mypy_django_plugin_newsemanal.context import DjangoContext
from mypy_django_plugin_newsemanal.lib import helpers
def redefine_and_typecheck_model_init(ctx: FunctionContext, django_context: DjangoContext) -> MypyType:
assert isinstance(ctx.default_return_type, Instance)
api = cast(TypeChecker, ctx.api)
model_info = ctx.default_return_type.type
model_cls = django_context.get_model_class_by_fullname(model_info.fullname())
# expected_types = {}
# for field in model_cls._meta.get_fields():
# field_fullname = helpers.get_class_fullname(field.__class__)
# field_info = api.lookup_typeinfo(field_fullname)
# field_set_type = helpers.get_private_descriptor_type(field_info, '_pyi_private_set_type',
# is_nullable=False)
# field_kwarg = Argument(variable=Var(field.attname, field_set_type),
# type_annotation=field_set_type,
# initializer=None,
# kind=ARG_NAMED)
# expected_types[field.attname] = field_set_type
# for field_name, field in model_cls._meta.fields_map.items():
# print()
# print()
return ctx.default_return_type

View File

@@ -0,0 +1,255 @@
import dataclasses
from abc import ABCMeta, abstractmethod
from typing import Optional, Type, cast
from django.db.models.base import Model
from django.db.models.fields.related import ForeignKey
from mypy.newsemanal.semanal import NewSemanticAnalyzer
from mypy.nodes import ARG_NAMED_OPT, Argument, ClassDef, MDEF, SymbolTableNode, TypeInfo, Var
from mypy.plugin import ClassDefContext
from mypy.plugins import common
from mypy.types import AnyType, Instance, NoneType, Type as MypyType, UnionType
from django.contrib.postgres.fields import ArrayField
from django.db.models.fields import CharField, Field
from mypy_django_plugin_newsemanal.context import DjangoContext
from mypy_django_plugin_newsemanal.lib import helpers
from mypy_django_plugin_newsemanal.transformers import fields
from mypy_django_plugin_newsemanal.transformers.fields import get_field_descriptor_types
@dataclasses.dataclass
class ModelClassInitializer(metaclass=ABCMeta):
api: NewSemanticAnalyzer
model_classdef: ClassDef
django_context: DjangoContext
ctx: ClassDefContext
@classmethod
def from_ctx(cls, ctx: ClassDefContext, django_context: DjangoContext):
return cls(api=cast(NewSemanticAnalyzer, ctx.api),
model_classdef=ctx.cls,
django_context=django_context,
ctx=ctx)
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 lookup_field_typeinfo_or_incomplete_defn_error(self, field: Field) -> TypeInfo:
fullname = helpers.get_class_fullname(field.__class__)
field_info = self.lookup_typeinfo_or_incomplete_defn_error(fullname)
return field_info
def add_new_node_to_model_class(self, name: str, typ: Instance) -> None:
# 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_initialized_in_class = True
var.is_inferred = True
self.model_classdef.info.names[name] = SymbolTableNode(MDEF, var, plugin_generated=True)
# assert self.model_classdef.info == self.api.type
# self.api.add_symbol_table_node(name, SymbolTableNode(MDEF, var, plugin_generated=True))
def convert_any_to_type(self, typ: MypyType, referred_to_type: MypyType) -> MypyType:
if isinstance(typ, UnionType):
converted_items = []
for item in typ.items:
converted_items.append(self.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)
if isinstance(typ, AnyType):
return referred_to_type
return typ
def get_field_set_type(self, field: Field, method: str) -> MypyType:
target_field = field
if isinstance(field, ForeignKey):
target_field = field.target_field
field_fullname = helpers.get_class_fullname(target_field.__class__)
field_info = self.lookup_typeinfo_or_incomplete_defn_error(field_fullname)
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(target_field.base_field, method)
field_set_type = self.convert_any_to_type(field_set_type, argument_field_type)
return field_set_type
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
return nullable
def get_field_kind(self, field: Field, method: str):
if method == '__init__':
# all arguments are optional in __init__
return ARG_NAMED_OPT
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 make_field_kwarg(self, name: str, field: Field, method: str) -> Argument:
field_set_type = self.get_field_set_type(field, method)
kind = self.get_field_kind(field, method)
field_kwarg = Argument(variable=Var(name, field_set_type),
type_annotation=field_set_type,
initializer=None,
kind=kind)
return field_kwarg
def get_field_kwargs(self, model_cls: Type[Model], method: str):
field_kwargs = []
if method == '__init__':
# add primary key `pk`
primary_key_field = self.get_primary_key_field(model_cls)
field_kwarg = self.make_field_kwarg('pk', primary_key_field, method)
field_kwargs.append(field_kwarg)
for field in model_cls._meta.get_fields():
if isinstance(field, Field):
field_kwarg = self.make_field_kwarg(field.attname, field, method)
field_kwargs.append(field_kwarg)
if isinstance(field, ForeignKey):
attname = field.name
related_model_fullname = helpers.get_class_fullname(field.related_model)
model_info = self.lookup_typeinfo_or_incomplete_defn_error(related_model_fullname)
is_nullable = self.get_field_nullability(field, method)
field_set_type = Instance(model_info, [])
if is_nullable:
field_set_type = helpers.make_optional(field_set_type)
kind = self.get_field_kind(field, method)
field_kwarg = Argument(variable=Var(attname, field_set_type),
type_annotation=field_set_type,
initializer=None,
kind=kind)
field_kwargs.append(field_kwarg)
return field_kwargs
@abstractmethod
def run(self) -> None:
raise NotImplementedError()
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:
return None
meta_node.fallback_to_any = True
class AddDefaultPrimaryKey(ModelClassInitializer):
def run(self) -> None:
model_cls = self.django_context.get_model_class_by_fullname(self.model_classdef.fullname)
if model_cls is None:
return
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)
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 AddRelatedModelsId(ModelClassInitializer):
def run(self) -> None:
model_cls = self.django_context.get_model_class_by_fullname(self.model_classdef.fullname)
if model_cls is None:
return
for field in model_cls._meta.get_fields():
if isinstance(field, ForeignKey):
rel_primary_key_field = self.get_primary_key_field(field.related_model)
field_info = self.lookup_field_typeinfo_or_incomplete_defn_error(rel_primary_key_field)
is_nullable = self.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 AddManagers(ModelClassInitializer):
def run(self):
model_cls = self.django_context.get_model_class_by_fullname(self.model_classdef.fullname)
if model_cls is None:
return
for manager_name, manager in model_cls._meta.managers_map.items():
if manager_name not in self.model_classdef.info.names:
manager_fullname = helpers.get_class_fullname(manager.__class__)
manager_info = self.lookup_typeinfo_or_incomplete_defn_error(manager_fullname)
manager = Instance(manager_info, [Instance(self.model_classdef.info, [])])
self.add_new_node_to_model_class(manager_name, manager)
# 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)
class AddInitMethod(ModelClassInitializer):
def run(self):
model_cls = self.django_context.get_model_class_by_fullname(self.model_classdef.info.fullname())
if model_cls is None:
return
field_kwargs = self.get_field_kwargs(model_cls, '__init__')
common.add_method(self.ctx, '__init__', field_kwargs, NoneType())
def process_model_class(ctx: ClassDefContext,
django_context: DjangoContext) -> None:
initializers = [
InjectAnyAsBaseForNestedMeta,
AddDefaultPrimaryKey,
AddRelatedModelsId,
AddManagers,
AddInitMethod
]
for initializer_cls in initializers:
try:
initializer_cls.from_ctx(ctx, django_context).run()
except helpers.IncompleteDefnException:
if not ctx.api.final_iteration:
ctx.api.defer()

View File

@@ -0,0 +1,39 @@
from mypy.plugin import AnalyzeTypeContext, FunctionContext
from mypy.types import AnyType, Instance, Type as MypyType, TypeOfAny
from mypy_django_plugin_newsemanal.lib import fullnames, helpers
def set_first_generic_param_as_default_for_second(ctx: AnalyzeTypeContext, fullname: str) -> MypyType:
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]
ctx.api.analyze_type(ctx.type)
try:
return ctx.api.named_type(fullname, analyzed_args)
except KeyError:
return AnyType(TypeOfAny.explicit)
def determine_proper_manager_type(ctx: FunctionContext) -> MypyType:
ret = ctx.default_return_type
assert isinstance(ret, Instance)
if not ctx.api.tscope.classes:
# not in class
return ret
outer_model_info = ctx.api.tscope.classes[0]
if not outer_model_info.has_base(fullnames.MODEL_CLASS_FULLNAME):
return ret
return helpers.reparametrize_instance(ret, [Instance(outer_model_info, [])])

View File

@@ -0,0 +1,18 @@
from mypy.nodes import TypeInfo
from mypy.plugin import FunctionContext
from mypy.types import Type as MypyType, TypeType, Instance
from mypy_django_plugin_newsemanal.context import DjangoContext
from mypy_django_plugin_newsemanal.lib import helpers
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)
model_info = helpers.lookup_fully_qualified_generic(model_cls_fullname, ctx.api.modules)
assert isinstance(model_info, TypeInfo)
return TypeType(Instance(model_info, []))

View File

@@ -1,3 +1,3 @@
[tool.black]
line-length = 120
include = 'django-stubs/.*.pyi$'
include = 'django-stubs/.*\.pyi$'

View File

@@ -1,7 +1,8 @@
[pytest]
testpaths = ./test-data
testpaths = ./test-data-newsemanal
addopts =
--tb=native
--mypy-ini-file=./test-data/plugins.ini
--mypy-ini-file=./test-data-newsemanal/plugins.ini
-s
-v
--cache-clear

View File

41
pytest_plugin/collect.py Normal file
View File

@@ -0,0 +1,41 @@
# noinspection PyUnresolvedReferences
from pytest_mypy.collect import File, YamlTestFile, pytest_addoption
from pytest_mypy.item import YamlTestItem
class DjangoYamlTestFile(YamlTestFile):
def get_test_class(self):
return NewSemanalDjangoTestItem
def pytest_collect_file(path, parent):
if path.ext in {'.yaml', '.yml'} and path.basename.startswith(('test-', 'test_')):
return DjangoYamlTestFile(path, parent=parent, config=parent.config)
class NewSemanalDjangoTestItem(YamlTestItem):
def custom_init(self):
settings = {
'SECRET_KEY': '"1"',
}
additional_settings = self.parsed_test_data.get('additional_settings')
if additional_settings:
for item in additional_settings:
name, _, val = item.partition('=')
settings[name] = val
installed_apps = self.parsed_test_data.get('installed_apps')
if installed_apps:
installed_apps_as_str = '(' + ','.join([repr(app) for app in installed_apps]) + ',)'
pyproject_toml_file = File(path='pyproject.toml',
content='[tool.django-stubs]\ndjango_settings_module=\'mysettings\'')
self.files.append(pyproject_toml_file)
settings_contents = f'INSTALLED_APPS={installed_apps_as_str}\n'
settings_contents += '\n'.join([f'{key}={val}' for key, val in settings.items()])
mysettings_file = File(path='mysettings.py', content=settings_contents)
self.files.append(mysettings_file)

42
pytest_plugin/setup.py Normal file
View File

@@ -0,0 +1,42 @@
import sys
from setuptools import setup
# with open('README.md', 'r') as f:
# readme = f.read()
dependencies = [
# 'pytest-mypy-plugins',
# 'mypy',
# 'decorator',
# 'capturer'
]
# if sys.version_info[:2] < (3, 7):
# # dataclasses port for 3.6
# dependencies += ['dataclasses']
setup(
name='pytest-django-stubs-newsemanal',
version='0.4.0',
# description='pytest plugin for writing tests for mypy plugins',
# long_description=readme,
# long_description_content_type='text/markdown',
license='MIT',
url="https://github.com/mkurnikov/pytest-mypy-plugins",
author="Maksim Kurnikov",
author_email="maxim.kurnikov@gmail.com",
# packages=['pytest_plugin'],
# the following makes a plugin available to pytest
entry_points={
'pytest11': [
'pytest-django-stubs-newsemanal = pytest_plugin.collect'
]
},
install_requires=dependencies,
classifiers=[
'Development Status :: 3 - Alpha',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7'
]
)

View File

@@ -21,7 +21,7 @@ with open('README.md', 'r') as f:
readme = f.read()
dependencies = [
'mypy>=0.710,<0.720',
'mypy>=0.720,<0.730',
'typing-extensions'
]
if sys.version_info[:2] < (3, 7):
@@ -30,7 +30,7 @@ if sys.version_info[:2] < (3, 7):
setup(
name="django-stubs",
version="0.13.0",
version="1.0.0",
description='Django mypy stubs',
long_description=readme,
long_description_content_type='text/markdown',

View File

@@ -2,4 +2,4 @@
incremental = True
strict_optional = True
plugins =
mypy_django_plugin.main
mypy_django_plugin_newsemanal.main

View File

@@ -0,0 +1,129 @@
- 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: test_primary_key_on_optional_queryset_method
main: |
from myapp.models import User
reveal_type(User.objects.first().id)
out: |
main:2: note: Revealed type is 'Union[builtins.int*, Any]'
main:2: error: Item "None" of "Optional[User]" has no attribute "id"
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class User(models.Model):
pass
- case: blank_and_null_char_field_allows_none
main: |
from myapp.models import MyModel
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)

View File

@@ -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)

View File

@@ -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))

View File

@@ -0,0 +1,410 @@
- 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: test_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 '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)
owner = models.ForeignKey(db_column='model_id', to='db.Unknown', on_delete=models.CASCADE)
- case: test_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: test_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: test_circular_dependency_in_imports_with_foreign_key
main: |
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:7: note: Revealed type is 'Any'
main:7: error: "App" has no attribute "unknown"
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
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)
- 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):
app = 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[myapp.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.profile) # N: Revealed type is 'myapp.models.Profile'
installed_apps:
- myapp
- myapp2
files:
- path: myapp/__init__.py
- path: myapp/models/__init__.py
content: |
from .user import User
- path: myapp/models/app.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
from myapp.models import User
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*[myapp.models.App]'
installed_apps:
- myapp
- myapp2
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
from myapp.models import App
class Member(models.Model):
apps = models.ManyToManyField(to='myapp.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: |
from myapp.models import Book
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 Book(models.Model):
publisher = models.ForeignKey(to='Publisher', on_delete=models.CASCADE)
class Publisher(models.Model):
pass
- 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:16: note: Revealed type is 'Any'
main:16: error: "Publisher" has no attribute "books"; maybe "books2"?
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')

View File

@@ -0,0 +1,293 @@
- 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: if_manager_is_defined_on_model_do_not_add_objects
main: |
from myapp.models import MyModel
reveal_type(MyModel.authors) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.MyModel]'
MyModel.objects # E: "Type[MyModel]" has no attribute "objects"
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class MyModel(models.Model):
authors = models.Manager['MyModel']()
- 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 'Union[myapp.models.UnrelatedModel*, None]'
reveal_type(MyModel.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.MyModel]'
reveal_type(MyModel.objects.first()) # N: Revealed type is 'Union[myapp.models.MyModel*, None]'
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 'Union[myapp.models.UnrelatedModel2*, None]'
reveal_type(MyModel2.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.MyModel2]'
reveal_type(MyModel2.objects.first()) # N: Revealed type is 'Union[myapp.models.MyModel2*, None]'
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 'Union[myapp.models.ParentOfMyModel3*, None]'
reveal_type(MyModel3.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.MyModel3]'
reveal_type(MyModel3.objects.first()) # N: Revealed type is 'Union[myapp.models.MyModel3*, None]'
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 'Union[myapp.models.ParentOfMyModel4*, None]'
reveal_type(MyModel4.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.MyModel4]'
reveal_type(MyModel4.objects.first()) # N: Revealed type is 'Union[myapp.models.MyModel4*, None]'
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']()

View File

@@ -0,0 +1,460 @@
- case: test_queryset_method_annotations
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 'Union[myapp.models.Blog*, None]'
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]'
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: test_combine_querysets_with_and
main: |
from myapp.models import Blog
# 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[myapp.models.Blog*, myapp.models.Blog*]'
reveal_type(Blog.objects.values() & Blog.objects.values()) # N: Revealed type is 'django.db.models.query.QuerySet[myapp.models.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[myapp.models.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[myapp.models.Blog*, Tuple[builtins.int, builtins.str, fallback=myapp.models.Row]]'
reveal_type(Blog.objects.values_list('id', flat=True) & Blog.objects.values()) # N: Revealed type is 'django.db.models.query.QuerySet[myapp.models.Blog*, builtins.int*]'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
created_at = models.DateTimeField()
- case: test_queryset_values_method
main: |
from myapp.models import Blog
values_qs = Blog.objects.values()
reveal_type(values_qs) # N: Revealed type is 'django.db.models.query.QuerySet[myapp.models.Blog*, builtins.dict[builtins.str, Any]]'
reveal_type(values_qs.all()) # N: Revealed type is 'django.db.models.query.QuerySet[myapp.models.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[myapp.models.Blog*, builtins.dict*[builtins.str, Any]]'
reveal_type(values_qs.in_bulk()) # N: Revealed type is 'builtins.dict[Any, 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): pass
- case: test_queryset_values_list_named_false_flat_false
main: |
from myapp.models import Blog
values_list_qs = Blog.objects.values_list('id', 'name')
reveal_type(values_list_qs) # N: Revealed type is 'django.db.models.query.QuerySet[myapp.models.Blog*, Tuple[builtins.int, builtins.str]]'
reveal_type(values_list_qs.all()) # N: Revealed type is 'django.db.models.query.QuerySet[myapp.models.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[myapp.models.Blog*, Tuple[builtins.int, builtins.str]]'
reveal_type(values_list_qs.in_bulk()) # N: Revealed type is 'builtins.dict[Any, 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):
name = models.CharField(max_length=100)
- case: test_queryset_values_list_named_false_flat_true
main: |
from myapp.models import Blog
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[myapp.models.Blog*, builtins.int]'
reveal_type(flat_values_list_qs.all()) # N: Revealed type is 'django.db.models.query.QuerySet[myapp.models.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[myapp.models.Blog*, builtins.int*]'
reveal_type(flat_values_list_qs.in_bulk()) # N: Revealed type is 'builtins.dict[Any, 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):
name = models.CharField(max_length=100)
- case: test_queryset_values_list_named_true_flat_false
main: |
from myapp.models import Blog
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[myapp.models.Blog*, Tuple[builtins.int, fallback=myapp.models.Row]]'
reveal_type(named_values_list_qs.all()) # N: Revealed type is 'django.db.models.query.QuerySet[myapp.models.Blog*, Tuple[builtins.int, fallback=myapp.models.Row]]'
reveal_type(named_values_list_qs.get(id=1)) # N: Revealed type is 'Tuple[builtins.int, fallback=myapp.models.Row]'
reveal_type(iter(named_values_list_qs)) # N: Revealed type is 'typing.Iterator[Tuple[builtins.int, fallback=myapp.models.Row]]'
reveal_type(named_values_list_qs.iterator()) # N: Revealed type is 'typing.Iterator[Tuple[builtins.int, fallback=myapp.models.Row]]'
reveal_type(named_values_list_qs.first()) # N: Revealed type is 'Union[Tuple[builtins.int, fallback=myapp.models.Row], None]'
reveal_type(named_values_list_qs.earliest()) # N: Revealed type is 'Tuple[builtins.int, fallback=myapp.models.Row]'
reveal_type(named_values_list_qs[0]) # N: Revealed type is 'Tuple[builtins.int, fallback=myapp.models.Row]'
reveal_type(named_values_list_qs[:9]) # N: Revealed type is 'django.db.models.query.QuerySet[myapp.models.Blog*, Tuple[builtins.int, fallback=myapp.models.Row]]'
reveal_type(named_values_list_qs.in_bulk()) # N: Revealed type is 'builtins.dict[Any, 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):
name = models.CharField(max_length=100)
- case: test_queryset_values_list_flat_true_custom_primary_key_get_element
main: |
from myapp.models import Blog
# 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*'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class Blog(models.Model):
primary_uuid = models.UUIDField(primary_key=True)
- case: test_queryset_values_list_flat_true_custom_primary_key_related_field
main: |
from myapp.models import Blog, Entry
# 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'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
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")
- case: test_queryset_values_list_error_conditions
main: |
from myapp.models import Blog
# 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.
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
- case: test_queryset_values_list_returns_tuple_of_fields
main: |
from myapp.models import Blog
# 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=myapp.models.Row]'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
created_at = models.DateTimeField()
- case: test_queryset_values_list_invalid_lookups_produce_any
main: |
from myapp.models import Blog
# 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'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
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")
- case: test_queryset_values_list_basic_inheritance
main: |
from myapp.models import BlogChild
# 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]'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
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)
- case: test_query_values_list_flat_true_plain_foreign_key
main: |
from myapp.models import Entry
# 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*'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class Blog(models.Model): pass
class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
- case: test_query_values_list_flat_true_custom_primary_key
main: |
from myapp.models import Entry
# 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*'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
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)
- case: test_query_values_list_flat_true_nullable_foreign_key
main: |
from myapp.models import Entry
# 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]'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
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)
- case: test_query_values_list_flat_true_foreign_key_reverse_relation
main: |
from myapp.models import Blog
# 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*'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
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)
- case: test_query_values_list_flat_true_foreign_key_custom_primary_key_reverse_relation
main: |
from myapp.models import Blog
# 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*'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
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)
- case: test_queryset_values_list_and_values_behavior_with_no_fields_specified_and_accessing_unknown_attributes
main: |
from myapp.models import Blog
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'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
created_at = models.DateTimeField()
- case: values_with_annotate_inside_the_expressions
main: |
from myapp.models import Publisher
reveal_type(Publisher().books.values('name', lower_name=Lower('name'), upper_name=Upper('name'))) # N: Revealed type is 'django.db.models.query.QuerySet[myapp.models.Book*, TypedDict({'name'?: builtins.str, 'lower_name'?: Any, 'upper_name'?: Any})]'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
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')
- case: values_and_values_list_some_dynamic_fields
main: |
from myapp.models import Publisher
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[myapp.models.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[myapp.models.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[myapp.models.Book*, builtins.dict[builtins.str, 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):
name = models.CharField(max_length=100)
publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE, related_name='books')

View File

@@ -0,0 +1,97 @@
- case: default_manager_create_is_typechecked
main: |
from myapp.models import User
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]")
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
main: |
from myapp.models import Publisher, Book
Book.objects.create(id=None) # E: Incompatible type for "id" of "MyModel" (got "None", expected "int")
Book.objects.create(publisher=None) # E: Incompatible type for "id" of "MyModel" (got "None", expected "int")
Book.objects.create(publisher_id=None) # E: Incompatible type for "id" of "MyModel" (got "None", expected "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 Book(models.Model):
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
- 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) # 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]")
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, default=None)

View File

@@ -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: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"
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

View File

@@ -0,0 +1,219 @@
- case: arguments_to_init_unexpected_attributes
main: |
from myapp.models import MyUser
user = MyUser(name=1, age=12)
out: |
main:2: error: Unexpected keyword argument "name" for "MyUser"
main:2: error: Unexpected keyword argument "age" for "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: arguments_to_init_from_class_incompatible_type
main: |
from myapp.models import MyUser
user = MyUser(name='hello', age=[])
out: |
main:2: error: Argument "age" to "MyUser" has incompatible type "List[<nothing>]"; 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: Argument "pk" to "MyUser1" has incompatible type "List[<nothing>]"; 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: Argument "publisher_id" to "Book" has incompatible type "List[<nothing>]"; expected "Union[Combinable, int, str, None]"
Book(publisher_id=1, publisher_dt_id=1) # E: Argument "publisher_dt_id" to "Book" has incompatible type "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: Argument "array" to "MyModel" has incompatible type "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)

View File

@@ -0,0 +1,53 @@
- 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]'
files:
- path: pyproject.toml
content: |
[tool.django-stubs]
django_settings_module = 'mysettings'
- path: mysettings.py
content: |
SECRET_KEY = '1'
INSTALLED_APPS = ('django.contrib.contenttypes', 'django.contrib.auth', 'myapp')
- 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: missing_settings_ignored_flag
main: |
from django.conf import settings
reveal_type(settings.NO_SUCH_SETTING) # N: Revealed type is 'Any'
files:
- path: pyproject.toml
content: |
[tool.django-stubs]
ignore_missing_settings = true
- 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.contenttypes
- 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)

View File

@@ -0,0 +1,57 @@
- case: no_incompatible_meta_nested_class_false_positive
main: |
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

View File

@@ -0,0 +1,42 @@
- case: transaction_atomic_contextmanager
main: |
from django.db import transaction
with transaction.atomic():
pass
with transaction.atomic(using="mydb"):
pass
with transaction.atomic(using="mydb", savepoint=False):
pass
- case: transaction_atomic_decorator
main: |
from django.db import transaction
@transaction.atomic()
def decorated_func(param1: str, param2: int) -> bool:
pass
# Ensure that the function's type is preserved
reveal_type(decorated_func) # N: Revealed type is 'def (param1: builtins.str, param2: builtins.int) -> builtins.bool'
@transaction.atomic(using="mydb")
def decorated_func_using(param1: str, param2: int) -> bool:
pass
# Ensure that the function's type is preserved
reveal_type(decorated_func_using) # N: Revealed type is 'def (param1: builtins.str, param2: builtins.int) -> builtins.bool'
class ClassWithAtomicMethod:
# Bare decorator
@transaction.atomic
def atomic_method1(self, abc: int) -> str:
pass
@transaction.atomic(savepoint=True)
def atomic_method2(self):
pass
@transaction.atomic(using="db", savepoint=True)
def atomic_method3(self, myparam: str) -> int:
pass
ClassWithAtomicMethod().atomic_method1("abc") # E: Argument 1 to "atomic_method1" of "ClassWithAtomicMethod" has incompatible type "str"; expected "int"
# Ensure that the method's type is preserved
reveal_type(ClassWithAtomicMethod().atomic_method1) # N: Revealed type is 'def (abc: builtins.int) -> builtins.str'
# Ensure that the method's type is preserved
reveal_type(ClassWithAtomicMethod().atomic_method3) # N: Revealed type is 'def (myparam: builtins.str) -> builtins.int'

View File

@@ -0,0 +1,402 @@
- case: import_all_modules
main: |
import django.apps
import django.apps.config
import django.apps.registry
import django.conf.global_settings
import django.conf.urls
import django.conf.urls.i18n
import django.conf.urls.static
import django.contrib.admin.actions
import django.contrib.admin.apps
import django.contrib.admin.checks
import django.contrib.admin.decorators
import django.contrib.admin.filters
import django.contrib.admin.forms
import django.contrib.admin.helpers
import django.contrib.admin.models
import django.contrib.admin.options
import django.contrib.admin.sites
import django.contrib.admin.templatetags
import django.contrib.admin.templatetags.admin_list
import django.contrib.admin.templatetags.admin_modify
import django.contrib.admin.templatetags.admin_static
import django.contrib.admin.templatetags.admin_urls
import django.contrib.admin.templatetags.base
import django.contrib.admin.templatetags.log
import django.contrib.admin.utils
import django.contrib.admin.views
import django.contrib.admin.views.autocomplete
import django.contrib.admin.views.decorators
import django.contrib.admin.views.main
import django.contrib.admin.widgets
import django.contrib.admindocs
import django.contrib.admindocs.middleware
import django.contrib.admindocs.utils
import django.contrib.admindocs.views
import django.contrib.auth.admin
import django.contrib.auth.apps
import django.contrib.auth.backends
import django.contrib.auth.base_user
import django.contrib.auth.checks
import django.contrib.auth.context_processors
import django.contrib.auth.decorators
import django.contrib.auth.forms
import django.contrib.auth.handlers
import django.contrib.auth.handlers.modwsgi
import django.contrib.auth.hashers
import django.contrib.auth.management.commands
import django.contrib.auth.management.commands.changepassword
import django.contrib.auth.management.commands.createsuperuser
import django.contrib.auth.middleware
import django.contrib.auth.mixins
import django.contrib.auth.models
import django.contrib.auth.password_validation
import django.contrib.auth.signals
import django.contrib.auth.tokens
import django.contrib.auth.validators
import django.contrib.auth.views
import django.contrib.contenttypes.admin
import django.contrib.contenttypes.apps
import django.contrib.contenttypes.checks
import django.contrib.contenttypes.fields
import django.contrib.contenttypes.forms
import django.contrib.contenttypes.management.commands
import django.contrib.contenttypes.management.commands.remove_stale_contenttypes
import django.contrib.contenttypes.models
import django.contrib.contenttypes.views
import django.contrib.flatpages.forms
import django.contrib.flatpages.middleware
import django.contrib.flatpages.models
import django.contrib.flatpages.sitemaps
import django.contrib.flatpages.templatetags
import django.contrib.flatpages.templatetags.flatpages
import django.contrib.flatpages.views
import django.contrib.humanize.templatetags
import django.contrib.humanize.templatetags.humanize
import django.contrib.messages.api
import django.contrib.messages.context_processors
import django.contrib.messages.middleware
import django.contrib.messages.storage
import django.contrib.messages.storage.base
import django.contrib.messages.storage.cookie
import django.contrib.messages.storage.fallback
import django.contrib.messages.storage.session
import django.contrib.messages.utils
import django.contrib.messages.views
import django.contrib.postgres.fields
import django.contrib.postgres.fields.array
import django.contrib.postgres.fields.citext
import django.contrib.postgres.fields.hstore
import django.contrib.postgres.fields.jsonb
import django.contrib.postgres.fields.mixins
import django.contrib.postgres.fields.ranges
import django.contrib.postgres.operations
import django.contrib.redirects
import django.contrib.redirects.middleware
import django.contrib.redirects.models
import django.contrib.sessions.backends
import django.contrib.sessions.backends.base
import django.contrib.sessions.backends.cache
import django.contrib.sessions.backends.cached_db
import django.contrib.sessions.backends.db
import django.contrib.sessions.backends.file
import django.contrib.sessions.backends.signed_cookies
import django.contrib.sessions.base_session
import django.contrib.sessions.management.commands
import django.contrib.sessions.management.commands.clearsessions
import django.contrib.sessions.middleware
import django.contrib.sessions.models
import django.contrib.sessions.serializers
import django.contrib.sitemaps.management.commands
import django.contrib.sitemaps.management.commands.ping_google
import django.contrib.sitemaps.views
import django.contrib.sites
import django.contrib.sites.apps
import django.contrib.sites.management
import django.contrib.sites.managers
import django.contrib.sites.middleware
import django.contrib.sites.models
import django.contrib.sites.requests
import django.contrib.sites.shortcuts
import django.contrib.staticfiles.apps
import django.contrib.staticfiles.checks
import django.contrib.staticfiles.finders
import django.contrib.staticfiles.handlers
import django.contrib.staticfiles.management.commands
import django.contrib.staticfiles.management.commands.collectstatic
import django.contrib.staticfiles.management.commands.findstatic
import django.contrib.staticfiles.management.commands.runserver
import django.contrib.staticfiles.storage
import django.contrib.staticfiles.templatetags
import django.contrib.staticfiles.templatetags.staticfiles
import django.contrib.staticfiles.urls
import django.contrib.staticfiles.utils
import django.contrib.staticfiles.views
import django.contrib.syndication
import django.contrib.syndication.views
import django.core.cache.backends
import django.core.cache.backends.base
import django.core.cache.backends.db
import django.core.cache.backends.dummy
import django.core.cache.backends.filebased
import django.core.cache.backends.locmem
import django.core.cache.utils
import django.core.checks.caches
import django.core.checks.database
import django.core.checks.messages
import django.core.checks.model_checks
import django.core.checks.registry
import django.core.checks.security
import django.core.checks.security.base
import django.core.checks.security.csrf
import django.core.checks.security.sessions
import django.core.checks.templates
import django.core.checks.urls
import django.core.exceptions
import django.core.files
import django.core.files.base
import django.core.files.images
import django.core.files.locks
import django.core.files.move
import django.core.files.storage
import django.core.files.temp
import django.core.files.uploadedfile
import django.core.files.uploadhandler
import django.core.files.utils
import django.core.handlers
import django.core.handlers.base
import django.core.handlers.exception
import django.core.handlers.wsgi
import django.core.mail
import django.core.mail.message
import django.core.mail.utils
import django.core.management
import django.core.management.base
import django.core.management.color
import django.core.management.sql
import django.core.management.templates
import django.core.management.utils
import django.core.paginator
import django.core.serializers
import django.core.serializers.base
import django.core.serializers.json
import django.core.serializers.python
import django.core.servers
import django.core.servers.basehttp
import django.core.signals
import django.core.signing
import django.core.validators
import django.core.wsgi
import django.db.backends.base
import django.db.backends.base.base
import django.db.backends.base.client
import django.db.backends.base.creation
import django.db.backends.base.features
import django.db.backends.base.introspection
import django.db.backends.base.operations
import django.db.backends.base.schema
import django.db.backends.base.validation
import django.db.backends.ddl_references
import django.db.backends.dummy
import django.db.backends.dummy.base
import django.db.backends.mysql
import django.db.backends.mysql.client
import django.db.backends.postgresql
import django.db.backends.postgresql.client
import django.db.backends.sqlite3
import django.db.backends.sqlite3.base
import django.db.backends.sqlite3.creation
import django.db.backends.sqlite3.features
import django.db.backends.sqlite3.introspection
import django.db.backends.sqlite3.operations
import django.db.backends.sqlite3.schema
import django.db.backends.utils
import django.db.migrations.autodetector
import django.db.migrations.exceptions
import django.db.migrations.executor
import django.db.migrations.graph
import django.db.migrations.loader
import django.db.migrations.migration
import django.db.migrations.operations
import django.db.migrations.operations.base
import django.db.migrations.operations.fields
import django.db.migrations.operations.models
import django.db.migrations.operations.special
import django.db.migrations.operations.utils
import django.db.migrations.optimizer
import django.db.migrations.questioner
import django.db.migrations.recorder
import django.db.migrations.serializer
import django.db.migrations.state
import django.db.migrations.topological_sort
import django.db.migrations.utils
import django.db.migrations.writer
import django.db.models.aggregates
import django.db.models.base
import django.db.models.deletion
import django.db.models.expressions
import django.db.models.fields
import django.db.models.fields.files
import django.db.models.fields.mixins
import django.db.models.fields.proxy
import django.db.models.fields.related
import django.db.models.fields.related_descriptors
import django.db.models.fields.related_lookups
import django.db.models.fields.reverse_related
import django.db.models.functions
import django.db.models.functions.comparison
import django.db.models.functions.datetime
import django.db.models.functions.text
import django.db.models.functions.window
import django.db.models.indexes
import django.db.models.lookups
import django.db.models.manager
import django.db.models.options
import django.db.models.query
import django.db.models.query_utils
import django.db.models.signals
import django.db.models.sql
import django.db.models.sql.compiler
import django.db.models.sql.constants
import django.db.models.sql.datastructures
import django.db.models.sql.query
import django.db.models.sql.subqueries
import django.db.models.sql.where
import django.db.models.utils
import django.db.transaction
import django.db.utils
import django.dispatch
import django.dispatch.dispatcher
import django.forms
import django.forms.boundfield
import django.forms.fields
import django.forms.forms
import django.forms.formsets
import django.forms.models
import django.forms.renderers
import django.forms.utils
import django.forms.widgets
import django.http
import django.http.cookie
import django.http.multipartparser
import django.http.request
import django.http.response
import django.middleware
import django.middleware.cache
import django.middleware.clickjacking
import django.middleware.common
import django.middleware.csrf
import django.middleware.gzip
import django.middleware.http
import django.middleware.locale
import django.middleware.security
import django.shortcuts
import django.template.backends
import django.template.backends.base
import django.template.backends.django
import django.template.backends.dummy
import django.template.backends.jinja2
import django.template.backends.utils
import django.template.base
import django.template.context
import django.template.context_processors
import django.template.defaultfilters
import django.template.defaulttags
import django.template.engine
import django.template.exceptions
import django.template.library
import django.template.loader
import django.template.loader_tags
import django.template.loaders
import django.template.loaders.app_directories
import django.template.loaders.base
import django.template.loaders.cached
import django.template.loaders.filesystem
import django.template.loaders.locmem
import django.template.response
import django.template.smartif
import django.template.utils
import django.templatetags
import django.templatetags.cache
import django.templatetags.i18n
import django.templatetags.l10n
import django.templatetags.static
import django.templatetags.tz
import django.test
import django.test.client
import django.test.html
import django.test.runner
import django.test.selenium
import django.test.signals
import django.test.testcases
import django.test.utils
import django.urls
import django.urls.base
import django.urls.conf
import django.urls.converters
import django.urls.exceptions
import django.urls.resolvers
import django.urls.utils
import django.utils._os
import django.utils.archive
import django.utils.autoreload
import django.utils.baseconv
import django.utils.cache
import django.utils.crypto
import django.utils.datastructures
import django.utils.dateformat
import django.utils.dateparse
import django.utils.dates
import django.utils.datetime_safe
import django.utils.deconstruct
import django.utils.decorators
import django.utils.deprecation
import django.utils.duration
import django.utils.encoding
import django.utils.feedgenerator
import django.utils.formats
import django.utils.functional
import django.utils.html
import django.utils.http
import django.utils.inspect
import django.utils.ipv6
import django.utils.itercompat
import django.utils.jslex
import django.utils.log
import django.utils.lorem_ipsum
import django.utils.module_loading
import django.utils.numberformat
import django.utils.regex_helper
import django.utils.safestring
import django.utils.six
import django.utils.termcolors
import django.utils.text
import django.utils.timesince
import django.utils.timezone
import django.utils.translation
import django.utils.translation.template
import django.utils.translation.trans_null
import django.utils.translation.trans_real
import django.utils.tree
import django.utils.version
import django.utils.xmlutils
import django.views.csrf
import django.views.debug
import django.views.decorators
import django.views.decorators.cache
import django.views.decorators.clickjacking
import django.views.decorators.csrf
import django.views.decorators.debug
import django.views.decorators.gzip
import django.views.decorators.http
import django.views.decorators.vary
import django.views.defaults
import django.views.generic
import django.views.generic.base
import django.views.generic.dates
import django.views.generic.detail
import django.views.generic.edit
import django.views.generic.list
import django.views.i18n
import django.views.static

View File

@@ -0,0 +1,33 @@
- case: registry_apps_get_model
main: |
from django.apps.registry import Apps
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]'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class User(models.Model):
pass
- case: state_apps_get_model
main: |
from django.db.migrations.state import StateApps
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]'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class User(models.Model):
pass

View File

@@ -0,0 +1,43 @@
- case: settings_loaded_from_different_files
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.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]'
files:
- path: pyproject.toml
content: |
[tool.django-stubs]
django_settings_module = 'mysettings'
- path: mysettings.py
content: |
from base import *
SECRET_KEY = 112233
NUMBERS = ['one', 'two']
DICT = {} # type: ignore
from django.utils.functional import LazyObject
OBJ = LazyObject()
- 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]'
- 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'

View File

@@ -0,0 +1,39 @@
- case: get_object_or_404_returns_proper_types
main: |
from django.shortcuts import get_object_or_404, get_list_or_404
from myapp.models import MyModel
reveal_type(get_object_or_404(MyModel)) # N: Revealed type is 'myapp.models.MyModel*'
reveal_type(get_object_or_404(MyModel.objects)) # N: Revealed type is 'myapp.models.MyModel*'
reveal_type(get_object_or_404(MyModel.objects.get_queryset())) # N: Revealed type is 'myapp.models.MyModel*'
reveal_type(get_list_or_404(MyModel)) # N: Revealed type is 'builtins.list[myapp.models.MyModel*]'
reveal_type(get_list_or_404(MyModel.objects)) # N: Revealed type is 'builtins.list[myapp.models.MyModel*]'
reveal_type(get_list_or_404(MyModel.objects.get_queryset())) # N: Revealed type is 'builtins.list[myapp.models.MyModel*]'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class MyModel(models.Model):
pass
- case: get_user_model_returns_proper_class
disable_cache: true
main: |
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]'
additional_settings:
- AUTH_USER_MODEL='myapp.MyUser'
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class MyUser(models.Model):
pass

View File

@@ -1,37 +0,0 @@
[CASE missing_settings_ignored_flag]
[env MYPY_DJANGO_CONFIG=${MYPY_CWD}/mypy_django.ini]
[disable_cache]
from django.conf import settings
reveal_type(settings.NO_SUCH_SETTING) # N: Revealed type is 'Any'
[file mypy_django.ini]
[[mypy_django_plugin]
ignore_missing_settings = True
[/CASE]
[CASE django_settings_via_config_file]
[env MYPY_DJANGO_CONFIG=${MYPY_CWD}/mypy_django.ini]
[disable_cache]
from django.conf import settings
reveal_type(settings.MY_SETTING) # N: Revealed type is 'builtins.int'
[file mypy_django.ini]
[[mypy_django_plugin]
django_settings = mysettings
[file mysettings.py]
MY_SETTING: int = 1
[/CASE]
[CASE mypy_django_ini_in_current_directory_is_a_default]
[disable_cache]
from django.conf import settings
reveal_type(settings.MY_SETTING) # N: Revealed type is 'builtins.int'
[file mypy_django.ini]
[[mypy_django_plugin]
django_settings = mysettings
[file mysettings.py]
MY_SETTING: int = 1
[/CASE]

View File

@@ -1,144 +0,0 @@
[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]
[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]

View File

@@ -1,41 +0,0 @@
[CASE no_incompatible_meta_nested_class_false_positive]
from django.db import models
from django import forms
class Article(models.Model):
pass
class Category(models.Model):
pass
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = '__all__'
class CategoryForm(forms.ModelForm):
class Meta:
model = Category
fields = '__all__'
class CompositeForm(ArticleForm, CategoryForm):
pass
[/CASE]
[CASE formview_methods_on_forms_return_proper_types]
from typing import Any
from django import forms
from django.views.generic.edit import FormView
class MyForm(forms.ModelForm):
pass
class MyForm2(forms.ModelForm):
pass
class MyView(FormView):
form_class = MyForm
def post(self, request, *args: Any, **kwds: Any):
form_class = self.get_form_class()
reveal_type(form_class) # N: Revealed type is 'Type[main.MyForm]'
reveal_type(self.get_form(None)) # N: Revealed type is 'main.MyForm'
reveal_type(self.get_form()) # N: Revealed type is 'main.MyForm'
reveal_type(self.get_form(form_class)) # N: Revealed type is 'main.MyForm'
reveal_type(self.get_form(MyForm2)) # N: Revealed type is 'main.MyForm2'
[/CASE]

View File

@@ -1,402 +0,0 @@
[CASE import_all_packages_to_trigger_stubs_check]
import django.apps
import django.apps.config
import django.apps.registry
import django.conf.global_settings
import django.conf.urls
import django.conf.urls.i18n
import django.conf.urls.static
import django.contrib.admin.actions
import django.contrib.admin.apps
import django.contrib.admin.checks
import django.contrib.admin.decorators
import django.contrib.admin.filters
import django.contrib.admin.forms
import django.contrib.admin.helpers
import django.contrib.admin.models
import django.contrib.admin.options
import django.contrib.admin.sites
import django.contrib.admin.templatetags
import django.contrib.admin.templatetags.admin_list
import django.contrib.admin.templatetags.admin_modify
import django.contrib.admin.templatetags.admin_static
import django.contrib.admin.templatetags.admin_urls
import django.contrib.admin.templatetags.base
import django.contrib.admin.templatetags.log
import django.contrib.admin.utils
import django.contrib.admin.views
import django.contrib.admin.views.autocomplete
import django.contrib.admin.views.decorators
import django.contrib.admin.views.main
import django.contrib.admin.widgets
import django.contrib.admindocs
import django.contrib.admindocs.middleware
import django.contrib.admindocs.utils
import django.contrib.admindocs.views
import django.contrib.auth.admin
import django.contrib.auth.apps
import django.contrib.auth.backends
import django.contrib.auth.base_user
import django.contrib.auth.checks
import django.contrib.auth.context_processors
import django.contrib.auth.decorators
import django.contrib.auth.forms
import django.contrib.auth.handlers
import django.contrib.auth.handlers.modwsgi
import django.contrib.auth.hashers
import django.contrib.auth.management.commands
import django.contrib.auth.management.commands.changepassword
import django.contrib.auth.management.commands.createsuperuser
import django.contrib.auth.middleware
import django.contrib.auth.mixins
import django.contrib.auth.models
import django.contrib.auth.password_validation
import django.contrib.auth.signals
import django.contrib.auth.tokens
import django.contrib.auth.validators
import django.contrib.auth.views
import django.contrib.contenttypes.admin
import django.contrib.contenttypes.apps
import django.contrib.contenttypes.checks
import django.contrib.contenttypes.fields
import django.contrib.contenttypes.forms
import django.contrib.contenttypes.management.commands
import django.contrib.contenttypes.management.commands.remove_stale_contenttypes
import django.contrib.contenttypes.models
import django.contrib.contenttypes.views
import django.contrib.flatpages.forms
import django.contrib.flatpages.middleware
import django.contrib.flatpages.models
import django.contrib.flatpages.sitemaps
import django.contrib.flatpages.templatetags
import django.contrib.flatpages.templatetags.flatpages
import django.contrib.flatpages.views
import django.contrib.humanize.templatetags
import django.contrib.humanize.templatetags.humanize
import django.contrib.messages.api
import django.contrib.messages.context_processors
import django.contrib.messages.middleware
import django.contrib.messages.storage
import django.contrib.messages.storage.base
import django.contrib.messages.storage.cookie
import django.contrib.messages.storage.fallback
import django.contrib.messages.storage.session
import django.contrib.messages.utils
import django.contrib.messages.views
import django.contrib.postgres.fields
import django.contrib.postgres.fields.array
import django.contrib.postgres.fields.citext
import django.contrib.postgres.fields.hstore
import django.contrib.postgres.fields.jsonb
import django.contrib.postgres.fields.mixins
import django.contrib.postgres.fields.ranges
import django.contrib.postgres.operations
import django.contrib.redirects
import django.contrib.redirects.middleware
import django.contrib.redirects.models
import django.contrib.sessions.backends
import django.contrib.sessions.backends.base
import django.contrib.sessions.backends.cache
import django.contrib.sessions.backends.cached_db
import django.contrib.sessions.backends.db
import django.contrib.sessions.backends.file
import django.contrib.sessions.backends.signed_cookies
import django.contrib.sessions.base_session
import django.contrib.sessions.management.commands
import django.contrib.sessions.management.commands.clearsessions
import django.contrib.sessions.middleware
import django.contrib.sessions.models
import django.contrib.sessions.serializers
import django.contrib.sitemaps.management.commands
import django.contrib.sitemaps.management.commands.ping_google
import django.contrib.sitemaps.views
import django.contrib.sites
import django.contrib.sites.apps
import django.contrib.sites.management
import django.contrib.sites.managers
import django.contrib.sites.middleware
import django.contrib.sites.models
import django.contrib.sites.requests
import django.contrib.sites.shortcuts
import django.contrib.staticfiles.apps
import django.contrib.staticfiles.checks
import django.contrib.staticfiles.finders
import django.contrib.staticfiles.handlers
import django.contrib.staticfiles.management.commands
import django.contrib.staticfiles.management.commands.collectstatic
import django.contrib.staticfiles.management.commands.findstatic
import django.contrib.staticfiles.management.commands.runserver
import django.contrib.staticfiles.storage
import django.contrib.staticfiles.templatetags
import django.contrib.staticfiles.templatetags.staticfiles
import django.contrib.staticfiles.urls
import django.contrib.staticfiles.utils
import django.contrib.staticfiles.views
import django.contrib.syndication
import django.contrib.syndication.views
import django.core.cache.backends
import django.core.cache.backends.base
import django.core.cache.backends.db
import django.core.cache.backends.dummy
import django.core.cache.backends.filebased
import django.core.cache.backends.locmem
import django.core.cache.utils
import django.core.checks.caches
import django.core.checks.database
import django.core.checks.messages
import django.core.checks.model_checks
import django.core.checks.registry
import django.core.checks.security
import django.core.checks.security.base
import django.core.checks.security.csrf
import django.core.checks.security.sessions
import django.core.checks.templates
import django.core.checks.urls
import django.core.exceptions
import django.core.files
import django.core.files.base
import django.core.files.images
import django.core.files.locks
import django.core.files.move
import django.core.files.storage
import django.core.files.temp
import django.core.files.uploadedfile
import django.core.files.uploadhandler
import django.core.files.utils
import django.core.handlers
import django.core.handlers.base
import django.core.handlers.exception
import django.core.handlers.wsgi
import django.core.mail
import django.core.mail.message
import django.core.mail.utils
import django.core.management
import django.core.management.base
import django.core.management.color
import django.core.management.sql
import django.core.management.templates
import django.core.management.utils
import django.core.paginator
import django.core.serializers
import django.core.serializers.base
import django.core.serializers.json
import django.core.serializers.python
import django.core.servers
import django.core.servers.basehttp
import django.core.signals
import django.core.signing
import django.core.validators
import django.core.wsgi
import django.db.backends.base
import django.db.backends.base.base
import django.db.backends.base.client
import django.db.backends.base.creation
import django.db.backends.base.features
import django.db.backends.base.introspection
import django.db.backends.base.operations
import django.db.backends.base.schema
import django.db.backends.base.validation
import django.db.backends.ddl_references
import django.db.backends.dummy
import django.db.backends.dummy.base
import django.db.backends.mysql
import django.db.backends.mysql.client
import django.db.backends.postgresql
import django.db.backends.postgresql.client
import django.db.backends.sqlite3
import django.db.backends.sqlite3.base
import django.db.backends.sqlite3.creation
import django.db.backends.sqlite3.features
import django.db.backends.sqlite3.introspection
import django.db.backends.sqlite3.operations
import django.db.backends.sqlite3.schema
import django.db.backends.utils
import django.db.migrations.autodetector
import django.db.migrations.exceptions
import django.db.migrations.executor
import django.db.migrations.graph
import django.db.migrations.loader
import django.db.migrations.migration
import django.db.migrations.operations
import django.db.migrations.operations.base
import django.db.migrations.operations.fields
import django.db.migrations.operations.models
import django.db.migrations.operations.special
import django.db.migrations.operations.utils
import django.db.migrations.optimizer
import django.db.migrations.questioner
import django.db.migrations.recorder
import django.db.migrations.serializer
import django.db.migrations.state
import django.db.migrations.topological_sort
import django.db.migrations.utils
import django.db.migrations.writer
import django.db.models.aggregates
import django.db.models.base
import django.db.models.deletion
import django.db.models.expressions
import django.db.models.fields
import django.db.models.fields.files
import django.db.models.fields.mixins
import django.db.models.fields.proxy
import django.db.models.fields.related
import django.db.models.fields.related_descriptors
import django.db.models.fields.related_lookups
import django.db.models.fields.reverse_related
import django.db.models.functions
import django.db.models.functions.comparison
import django.db.models.functions.datetime
import django.db.models.functions.text
import django.db.models.functions.window
import django.db.models.indexes
import django.db.models.lookups
import django.db.models.manager
import django.db.models.options
import django.db.models.query
import django.db.models.query_utils
import django.db.models.signals
import django.db.models.sql
import django.db.models.sql.compiler
import django.db.models.sql.constants
import django.db.models.sql.datastructures
import django.db.models.sql.query
import django.db.models.sql.subqueries
import django.db.models.sql.where
import django.db.models.utils
import django.db.transaction
import django.db.utils
import django.dispatch
import django.dispatch.dispatcher
import django.forms
import django.forms.boundfield
import django.forms.fields
import django.forms.forms
import django.forms.formsets
import django.forms.models
import django.forms.renderers
import django.forms.utils
import django.forms.widgets
import django.http
import django.http.cookie
import django.http.multipartparser
import django.http.request
import django.http.response
import django.middleware
import django.middleware.cache
import django.middleware.clickjacking
import django.middleware.common
import django.middleware.csrf
import django.middleware.gzip
import django.middleware.http
import django.middleware.locale
import django.middleware.security
import django.shortcuts
import django.template.backends
import django.template.backends.base
import django.template.backends.django
import django.template.backends.dummy
import django.template.backends.jinja2
import django.template.backends.utils
import django.template.base
import django.template.context
import django.template.context_processors
import django.template.defaultfilters
import django.template.defaulttags
import django.template.engine
import django.template.exceptions
import django.template.library
import django.template.loader
import django.template.loader_tags
import django.template.loaders
import django.template.loaders.app_directories
import django.template.loaders.base
import django.template.loaders.cached
import django.template.loaders.filesystem
import django.template.loaders.locmem
import django.template.response
import django.template.smartif
import django.template.utils
import django.templatetags
import django.templatetags.cache
import django.templatetags.i18n
import django.templatetags.l10n
import django.templatetags.static
import django.templatetags.tz
import django.test
import django.test.client
import django.test.html
import django.test.runner
import django.test.selenium
import django.test.signals
import django.test.testcases
import django.test.utils
import django.urls
import django.urls.base
import django.urls.conf
import django.urls.converters
import django.urls.exceptions
import django.urls.resolvers
import django.urls.utils
import django.utils._os
import django.utils.archive
import django.utils.autoreload
import django.utils.baseconv
import django.utils.cache
import django.utils.crypto
import django.utils.datastructures
import django.utils.dateformat
import django.utils.dateparse
import django.utils.dates
import django.utils.datetime_safe
import django.utils.deconstruct
import django.utils.decorators
import django.utils.deprecation
import django.utils.duration
import django.utils.encoding
import django.utils.feedgenerator
import django.utils.formats
import django.utils.functional
import django.utils.html
import django.utils.http
import django.utils.inspect
import django.utils.ipv6
import django.utils.itercompat
import django.utils.jslex
import django.utils.log
import django.utils.lorem_ipsum
import django.utils.module_loading
import django.utils.numberformat
import django.utils.regex_helper
import django.utils.safestring
import django.utils.six
import django.utils.termcolors
import django.utils.text
import django.utils.timesince
import django.utils.timezone
import django.utils.translation
import django.utils.translation.template
import django.utils.translation.trans_null
import django.utils.translation.trans_real
import django.utils.tree
import django.utils.version
import django.utils.xmlutils
import django.views.csrf
import django.views.debug
import django.views.decorators
import django.views.decorators.cache
import django.views.decorators.clickjacking
import django.views.decorators.csrf
import django.views.decorators.debug
import django.views.decorators.gzip
import django.views.decorators.http
import django.views.decorators.vary
import django.views.defaults
import django.views.generic
import django.views.generic.base
import django.views.generic.dates
import django.views.generic.detail
import django.views.generic.edit
import django.views.generic.list
import django.views.i18n
import django.views.static
[/CASE]

View File

@@ -1,201 +0,0 @@
[CASE test_every_model_has_objects_queryset_available]
from django.db import models
class User(models.Model):
pass
reveal_type(User.objects) # N: Revealed type is 'django.db.models.manager.Manager[main.User]'
reveal_type(User.objects.get()) # N: Revealed type is 'main.User*'
[CASE every_model_has_its_own_objects_queryset]
from django.db import models
class Parent(models.Model):
pass
class Child(Parent):
pass
reveal_type(Parent.objects) # N: Revealed type is 'django.db.models.manager.Manager[main.Parent]'
reveal_type(Child.objects) # N: Revealed type is 'django.db.models.manager.Manager[main.Child]'
[out]
[CASE if_manager_is_defined_on_model_do_not_add_objects]
from django.db import models
class MyModel(models.Model):
authors = models.Manager[MyModel]()
reveal_type(MyModel.authors) # N: Revealed type is 'django.db.models.manager.Manager[main.MyModel]'
MyModel.objects # E: "Type[MyModel]" has no attribute "objects"
[out]
[CASE test_model_objects_attribute_present_in_case_of_model_cls_passed_as_generic_parameter]
from typing import TypeVar, Generic, Type
from django.db import models
_T = TypeVar('_T', bound=models.Model)
class Base(Generic[_T]):
def __init__(self, model_cls: Type[_T]):
self.model_cls = model_cls
reveal_type(self.model_cls._default_manager) # N: Revealed type is 'django.db.models.manager.Manager[django.db.models.base.Model]'
class MyModel(models.Model):
pass
base_instance = Base(MyModel)
reveal_type(base_instance.model_cls._default_manager) # N: Revealed type is 'django.db.models.manager.Manager[main.MyModel]'
class Child(Base[MyModel]):
def method(self) -> None:
reveal_type(self.model_cls._default_manager) # N: Revealed type is 'django.db.models.manager.Manager[main.MyModel]'
[CASE if_custom_manager_defined_it_is_set_to_default_manager]
from typing import TypeVar
from django.db import models
_T = TypeVar('_T', bound=models.Model)
class CustomManager(models.Manager[_T]):
pass
class MyModel(models.Model):
manager = CustomManager[MyModel]()
reveal_type(MyModel._default_manager) # N: Revealed type is 'main.CustomManager[main.MyModel]'
[CASE if_default_manager_name_is_passed_set_default_manager_to_it]
from typing import TypeVar
from django.db import models
_T = TypeVar('_T', bound=models.Model)
class Manager1(models.Manager[_T]):
pass
class Manager2(models.Manager[_T]):
pass
class MyModel(models.Model):
class Meta:
default_manager_name = 'm2'
m1: Manager1[MyModel]
m2: Manager2[MyModel]
reveal_type(MyModel._default_manager) # N: Revealed type is 'main.Manager2[main.MyModel]'
[CASE test_leave_as_is_if_objects_is_set_and_fill_typevars_with_outer_class]
from django.db import models
class UserManager(models.Manager[MyUser]):
def get_or_404(self) -> MyUser:
pass
class MyUser(models.Model):
objects = UserManager()
reveal_type(MyUser.objects) # N: Revealed type is 'main.UserManager[main.MyUser]'
reveal_type(MyUser.objects.get()) # N: Revealed type is 'main.MyUser*'
reveal_type(MyUser.objects.get_or_404()) # N: Revealed type is 'main.MyUser'
[CASE model_imported_from_different_file]
from django.db import models
from models.main import Inventory
class Band(models.Model):
pass
reveal_type(Inventory.objects) # N: Revealed type is 'django.db.models.manager.Manager[models.main.Inventory]'
reveal_type(Band.objects) # N: Revealed type is 'django.db.models.manager.Manager[main.Band]'
[file models/__init__.py]
[file models/main.py]
from django.db import models
class Inventory(models.Model):
pass
[CASE managers_that_defined_on_other_models_do_not_influence]
from django.db import models
class AbstractPerson(models.Model):
abstract_persons = models.Manager[AbstractPerson]()
class PublishedBookManager(models.Manager[Book]):
pass
class AnnotatedBookManager(models.Manager[Book]):
pass
class Book(models.Model):
title = models.CharField(max_length=50)
published_objects = PublishedBookManager()
annotated_objects = AnnotatedBookManager()
reveal_type(AbstractPerson.abstract_persons) # N: Revealed type is 'django.db.models.manager.Manager[main.AbstractPerson]'
reveal_type(Book.published_objects) # N: Revealed type is 'main.PublishedBookManager[main.Book]'
Book.published_objects.create(title='hello')
reveal_type(Book.annotated_objects) # N: Revealed type is 'main.AnnotatedBookManager[main.Book]'
Book.annotated_objects.create(title='hello')
[out]
[CASE managers_inherited_from_abstract_classes_multiple_inheritance]
from django.db import models
class CustomManager1(models.Manager[AbstractBase1]):
pass
class AbstractBase1(models.Model):
class Meta:
abstract = True
name = models.CharField(max_length=50)
manager1 = CustomManager1()
class CustomManager2(models.Manager[AbstractBase2]):
pass
class AbstractBase2(models.Model):
class Meta:
abstract = True
value = models.CharField(max_length=50)
restricted = CustomManager2()
class Child(AbstractBase1, AbstractBase2):
pass
[out]
[CASE managers_from_unrelated_models_dont_interfere]
from django.db import models
# Normal scenario where one model has a manager with an annotation of the same type as the model
class UnrelatedModel(models.Model):
objects = models.Manager[UnrelatedModel]()
class MyModel(models.Model):
pass
reveal_type(UnrelatedModel.objects) # N: Revealed type is 'django.db.models.manager.Manager[main.UnrelatedModel]'
reveal_type(UnrelatedModel.objects.first()) # N: Revealed type is 'Union[main.UnrelatedModel*, None]'
reveal_type(MyModel.objects) # N: Revealed type is 'django.db.models.manager.Manager[main.MyModel]'
reveal_type(MyModel.objects.first()) # N: Revealed type is 'Union[main.MyModel*, None]'
# Possible to specify objects without explicit annotation of models.Manager()
class UnrelatedModel2(models.Model):
objects = models.Manager()
class MyModel2(models.Model):
pass
reveal_type(UnrelatedModel2.objects) # N: Revealed type is 'django.db.models.manager.Manager[main.UnrelatedModel2]'
reveal_type(UnrelatedModel2.objects.first()) # N: Revealed type is 'Union[main.UnrelatedModel2*, None]'
reveal_type(MyModel2.objects) # N: Revealed type is 'django.db.models.manager.Manager[main.MyModel2]'
reveal_type(MyModel2.objects.first()) # N: Revealed type is 'Union[main.MyModel2*, None]'
# Inheritance works
class ParentOfMyModel3(models.Model):
objects = models.Manager()
class MyModel3(ParentOfMyModel3):
pass
reveal_type(ParentOfMyModel3.objects) # N: Revealed type is 'django.db.models.manager.Manager[main.ParentOfMyModel3]'
reveal_type(ParentOfMyModel3.objects.first()) # N: Revealed type is 'Union[main.ParentOfMyModel3*, None]'
reveal_type(MyModel3.objects) # N: Revealed type is 'django.db.models.manager.Manager[main.MyModel3]'
reveal_type(MyModel3.objects.first()) # N: Revealed type is 'Union[main.MyModel3*, None]'
# Inheritance works with explicit objects in child
class ParentOfMyModel4(models.Model):
objects = models.Manager()
class MyModel4(ParentOfMyModel4):
objects = models.Manager[MyModel4]()
reveal_type(ParentOfMyModel4.objects) # N: Revealed type is 'django.db.models.manager.Manager[main.ParentOfMyModel4]'
reveal_type(ParentOfMyModel4.objects.first()) # N: Revealed type is 'Union[main.ParentOfMyModel4*, None]'
reveal_type(MyModel4.objects) # N: Revealed type is 'django.db.models.manager.Manager[main.MyModel4]'
reveal_type(MyModel4.objects.first()) # N: Revealed type is 'Union[main.MyModel4*, None]'
[out]

View File

@@ -1,10 +0,0 @@
[CASE successmessagemixin_compatible_with_formmixin]
from django.views.generic.edit import FormMixin
from django.contrib.messages.views import SuccessMessageMixin
class FormFirstView(FormMixin, SuccessMessageMixin):
pass
class SuccessMessageFirstView(FormMixin, SuccessMessageMixin):
pass
[/CASE]

View File

@@ -1,48 +0,0 @@
[CASE registry_apps_get_model]
from django.apps.registry import Apps
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from myapp.models import User
apps = Apps()
model_cls = apps.get_model('myapp', 'User')
reveal_type(model_cls) # N: Revealed type is 'Type[myapp.models.User]'
reveal_type(model_cls.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.User]'
[file myapp/__init__.py]
[file myapp/models.py]
from django.db import models
class User(models.Model):
pass
[CASE registry_apps_get_model_passed_as_variables_not_supported]
from django.apps.registry import Apps
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from myapp.models import User
app_name = 'myapp'
model_name = 'User'
apps = Apps()
model_cls = apps.get_model(app_name, model_name)
reveal_type(model_cls) # N: Revealed type is 'Type[django.db.models.base.Model]'
[file myapp/__init__.py]
[file myapp/models.py]
from django.db import models
class User(models.Model):
pass
[CASE state_apps_get_model]
from django.db.migrations.state import StateApps
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from myapp.models import User
apps = StateApps([], {})
model_cls = apps.get_model('myapp', 'User')
reveal_type(model_cls) # N: Revealed type is 'Type[myapp.models.User]'
reveal_type(model_cls.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.User]'
[file myapp/__init__.py]
[file myapp/models.py]
from django.db import models
class User(models.Model):
pass

View File

@@ -1,60 +0,0 @@
[CASE test_typechecking_for_model_subclasses]
from django.db import models
class A(models.Model):
pass
class B(models.Model):
b_attr = 1
pass
class C(A):
pass
def service(a: A) -> int:
pass
b_instance = B()
service(b_instance) # E: Argument 1 to "service" has incompatible type "B"; expected "A"
a_instance = A()
c_instance = C()
service(a_instance)
service(c_instance)
[/CASE]
[CASE fail_if_no_such_attribute_on_model]
from django.db import models
class B(models.Model):
b_attr = 1
pass
b_instance = B()
reveal_type(b_instance.b_attr) # N: Revealed type is 'builtins.int'
reveal_type(b_instance.non_existent_attribute)
b_instance.non_existent_attribute = 2
[out]
main:10: note: Revealed type is 'Any'
main:10: error: "B" has no attribute "non_existent_attribute"
main:11: error: "B" has no attribute "non_existent_attribute"
[/CASE]
[CASE ignore_missing_attributes_if_setting_is_passed]
from django.db import models
class B(models.Model):
pass
b_instance = B()
reveal_type(b_instance.non_existent_attribute) # N: Revealed type is 'Any'
b_instance.non_existent_attribute = 2
[env MYPY_DJANGO_CONFIG=${MYPY_CWD}/mypy_django.ini]
[file mypy_django.ini]
[[mypy_django_plugin]
ignore_missing_model_attributes = True
[/CASE]

View File

@@ -1,64 +0,0 @@
[CASE default_manager_create_is_typechecked]
from django.db import models
class User(models.Model):
name = models.CharField(max_length=100)
age = models.IntegerField()
User.objects.create(name='Max', age=10)
User.objects.create(age=[]) # E: Incompatible type for "age" of "User" (got "List[Any]", expected "Union[float, int, str, Combinable]")
[out]
[CASE model_recognises_parent_attributes]
from django.db import models
class Parent(models.Model):
name = models.CharField(max_length=100)
class Child(Parent):
lastname = models.CharField(max_length=100)
Child.objects.create(name='Maxim', lastname='Maxim2')
[out]
[CASE deep_multiple_inheritance_with_create]
from django.db import models
class Parent1(models.Model):
name1 = models.CharField(max_length=50)
class Parent2(models.Model):
id2 = models.AutoField(primary_key=True)
name2 = models.CharField(max_length=50)
class Child1(Parent1, Parent2):
value = models.IntegerField()
class Child4(Child1):
value4 = models.IntegerField()
Child4.objects.create(name1='n1', name2='n2', value=1, value4=4)
[CASE optional_primary_key_for_create_is_error]
from django.db import models
class MyModel(models.Model):
pass
MyModel.objects.create(id=None) # E: Incompatible type for "id" of "MyModel" (got "None", expected "int")
[CASE optional_related_model_for_create_is_error]
from django.db import models
class Publisher(models.Model):
pass
class Book(models.Model):
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
Book.objects.create(publisher=None) # E: Incompatible type for "publisher" of "Book" (got "None", expected "Union[Publisher, Combinable]")
[CASE when_default_for_primary_key_is_specified_allow_none_to_be_set]
from django.db import models
def return_int():
return 0
class MyModel(models.Model):
id = models.IntegerField(primary_key=True, default=return_int)
MyModel(id=None)
MyModel.objects.create(id=None)
class MyModel2(models.Model):
id = models.IntegerField(primary_key=True, default=None)
MyModel2(id=None) # E: Incompatible type for "id" of "MyModel2" (got "None", expected "Union[float, int, str, Combinable]")
MyModel2.objects.create(id=None) # E: Incompatible type for "id" of "MyModel2" (got "None", expected "Union[float, int, str, Combinable]")
[out]

View File

@@ -1,177 +0,0 @@
[CASE arguments_to_init_unexpected_attributes]
from django.db import models
class MyUser(models.Model):
pass
user = MyUser(name=1, age=12)
[out]
main:5: error: Unexpected attribute "name" for model "MyUser"
main:5: error: Unexpected attribute "age" for model "MyUser"
[CASE arguments_to_init_from_class_incompatible_type]
from django.db import models
class MyUser(models.Model):
name = models.CharField(max_length=100)
age = models.IntegerField()
user = MyUser(name='hello', age=[])
[out]
main:6: error: Incompatible type for "age" of "MyUser" (got "List[Any]", expected "Union[float, int, str, Combinable]")
[CASE arguments_to_init_combined_from_base_classes]
from django.db import models
class BaseUser(models.Model):
name = models.CharField(max_length=100)
age = models.IntegerField()
class ChildUser(BaseUser):
lastname = models.CharField(max_length=100)
user = ChildUser(name='Max', age=12, lastname='Lastname')
[out]
[CASE fields_from_abstract_user_propagate_to_init]
from django.contrib.auth.models import AbstractUser
class MyUser(AbstractUser):
pass
user = MyUser(username='maxim', password='password', first_name='Max', last_name='MaxMax')
[out]
[CASE generic_foreign_key_field_no_typechecking]
from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey
class MyUser(models.Model):
content_object = GenericForeignKey()
user = MyUser(content_object=1)
[out]
[CASE pk_refers_to_primary_key_and_could_be_passed_to_init]
from django.db import models
class MyUser1(models.Model):
mypk = models.CharField(primary_key=True)
class MyUser2(models.Model):
name = models.CharField(max_length=100)
user2 = MyUser1(pk='hello')
user3= MyUser2(pk=1)
[out]
[CASE typechecking_of_pk]
from django.db import models
class MyUser1(models.Model):
mypk = models.IntegerField(primary_key=True)
user = MyUser1(pk=[]) # E: Incompatible type for "pk" of "MyUser1" (got "List[Any]", expected "Union[float, int, str, Combinable]")
[out]
[CASE can_set_foreign_key_by_its_primary_key]
from django.db import models
class Publisher(models.Model):
pass
class PublisherDatetime(models.Model):
dt_pk = models.DateTimeField(primary_key=True)
class Book(models.Model):
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
publisher_dt = models.ForeignKey(PublisherDatetime, on_delete=models.CASCADE)
Book(publisher_id=1)
Book(publisher_id=[]) # E: Incompatible type for "publisher_id" of "Book" (got "List[Any]", expected "Union[Combinable, int, str, None]")
Book(publisher_dt_id=11) # E: Incompatible type for "publisher_dt_id" of "Book" (got "int", expected "Union[str, date, Combinable, None]")
[out]
[CASE setting_value_to_an_array_of_ints]
from typing import List, Tuple
from django.db import models
from django.contrib.postgres.fields import ArrayField
class MyModel(models.Model):
array = ArrayField(base_field=models.IntegerField())
array_val: Tuple[int, ...] = (1,)
MyModel(array=array_val)
array_val2: List[int] = [1]
MyModel(array=array_val2)
array_val3: List[str] = ['hello']
MyModel(array=array_val3) # E: Incompatible type for "array" of "MyModel" (got "List[str]", expected "Union[Sequence[int], Combinable]")
[out]
[CASE if_no_explicit_primary_key_id_can_be_passed]
from django.db import models
class MyModel(models.Model):
name = models.CharField(max_length=100)
MyModel(id=1, name='maxim')
[out]
[CASE arguments_can_be_passed_as_positionals]
from django.db import models
class MyModel(models.Model):
pass
MyModel(1)
class MyModel2(models.Model):
name = models.IntegerField()
MyModel2(1, 12)
MyModel2(1, []) # E: Incompatible type for "name" of "MyModel2" (got "List[Any]", expected "Union[float, int, str, Combinable]")
[out]
[CASE arguments_passed_as_dictionary_unpacking_are_not_supported]
from django.db import models
class MyModel(models.Model):
name = models.CharField(max_length=100)
MyModel(**{'name': 'hello'})
[out]
[CASE pointer_to_parent_model_is_not_supported]
from django.db import models
class Place(models.Model):
pass
class Restaurant(Place):
pass
place = Place()
Restaurant(place_ptr=place)
Restaurant(place_ptr_id=place.id)
[out]
[CASE charfield_with_integer_choices]
from django.db import models
class MyModel(models.Model):
day = models.CharField(max_length=3, choices=((1, 'Fri'), (2, 'Sat')))
MyModel(day=1)
[out]
[CASE if_there_is_no_data_for_base_classes_of_fields_and_ignore_unresolved_attributes_set_to_true_to_not_fail]
from decimal import Decimal
from django.db import models
from fields2 import MoneyField
class InvoiceRow(models.Model):
base_amount = MoneyField(max_digits=10, decimal_places=2)
vat_rate = models.DecimalField(max_digits=10, decimal_places=2)
InvoiceRow(1, Decimal(0), Decimal(0))
InvoiceRow(base_amount=Decimal(0), vat_rate=Decimal(0))
InvoiceRow.objects.create(base_amount=Decimal(0), vat_rate=Decimal(0))
[out]
main:3: error: Cannot find module named 'fields2'
main:3: note: See https://mypy.readthedocs.io/en/latest/running_mypy.html#missing-imports
[CASE optional_primary_key_is_allowed_for_init]
from django.db import models
class MyModel(models.Model):
pass
MyModel(id=None)
MyModel(None)
[out]
[CASE optional_related_model_is_allowed_for_init]
from django.db import models
class Publisher(models.Model):
pass
class Book(models.Model):
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
Book(publisher=None)
Book(publisher_id=None)
[out]

View File

@@ -1,42 +0,0 @@
[CASE nullable_field_with_strict_optional_true]
from django.db import models
class MyModel(models.Model):
text_nullable = models.CharField(max_length=100, null=True)
text = models.CharField(max_length=100)
reveal_type(MyModel().text) # N: Revealed type is 'builtins.str*'
reveal_type(MyModel().text_nullable) # N: Revealed type is 'Union[builtins.str, None]'
MyModel().text = None # E: Incompatible types in assignment (expression has type "None", variable has type "Union[str, int, Combinable]")
MyModel().text_nullable = None
[out]
[CASE nullable_array_field]
from django.db import models
from django.contrib.postgres.fields import ArrayField
class MyModel(models.Model):
lst = ArrayField(base_field=models.CharField(max_length=100), null=True)
reveal_type(MyModel().lst) # N: Revealed type is 'Union[builtins.list[builtins.str], None]'
[out]
[CASE nullable_foreign_key]
from django.db import models
class Publisher(models.Model):
pass
class Book(models.Model):
publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE, null=True)
reveal_type(Book().publisher) # N: Revealed type is 'Union[main.Publisher, None]'
Book().publisher = 11 # E: Incompatible types in assignment (expression has type "int", variable has type "Union[Publisher, Combinable, None]")
[out]
[CASE nullable_self_foreign_key]
from django.db import models
class Inventory(models.Model):
parent = models.ForeignKey('self', on_delete=models.SET_NULL, null=True)
parent = Inventory()
core = Inventory(parent_id=parent.id)
reveal_type(core.parent_id) # N: Revealed type is 'Union[builtins.int, None]'
reveal_type(core.parent) # N: Revealed type is 'Union[main.Inventory, None]'
Inventory(parent=None)
Inventory(parent_id=None)
[out]

View File

@@ -1,389 +0,0 @@
[CASE test_queryset_second_argument_filled_automatically]
from django.db import models
class Blog(models.Model): pass
# QuerySet where second type argument is not specified shouldn't raise any errors
class BlogQuerySet(models.QuerySet[Blog]):
pass
blog_qs: models.QuerySet[Blog]
reveal_type(blog_qs) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog, main.Blog]'
[/CASE]
[CASE test_queryset_methods]
from django.db import models
class Blog(models.Model):
created_at = models.DateTimeField()
qs = Blog.objects.all()
reveal_type(qs) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, main.Blog*]'
reveal_type(qs.get(id=1)) # N: Revealed type is 'main.Blog*'
reveal_type(iter(qs)) # N: Revealed type is 'typing.Iterator[main.Blog*]'
reveal_type(qs.iterator()) # N: Revealed type is 'typing.Iterator[main.Blog*]'
reveal_type(qs.first()) # N: Revealed type is 'Union[main.Blog*, None]'
reveal_type(qs.earliest()) # N: Revealed type is 'main.Blog*'
reveal_type(qs[0]) # N: Revealed type is 'main.Blog*'
reveal_type(qs[:9]) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, main.Blog*]'
reveal_type(qs.in_bulk()) # N: Revealed type is 'builtins.dict[Any, main.Blog*]'
# .dates / .datetimes
reveal_type(Blog.objects.dates("created_at", "day")) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, datetime.date]'
reveal_type(Blog.objects.datetimes("created_at", "day")) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, datetime.datetime]'
[/CASE]
[CASE test_combine_querysets_with_and]
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
created_at = models.DateTimeField()
# When ANDing QuerySets, the left-side's _Row parameter is used
reveal_type(Blog.objects.all() & Blog.objects.values()) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, main.Blog*]'
reveal_type(Blog.objects.values() & Blog.objects.values()) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.dict*[builtins.str, Any]]'
reveal_type(Blog.objects.values_list('id', 'name') & Blog.objects.values()) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, Tuple[builtins.int, builtins.str]]'
reveal_type(Blog.objects.values_list('id', 'name', named=True) & Blog.objects.values()) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, Tuple[builtins.int, builtins.str, fallback=main.Row]]'
reveal_type(Blog.objects.values_list('id', flat=True) & Blog.objects.values()) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.int*]'
[/CASE]
[CASE test_queryset_values_method]
from django.db import models
class Blog(models.Model): pass
values_qs = Blog.objects.values()
reveal_type(values_qs) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.dict[builtins.str, Any]]'
reveal_type(values_qs.all()) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.dict*[builtins.str, Any]]'
reveal_type(values_qs.get(id=1)) # N: Revealed type is 'builtins.dict*[builtins.str, Any]'
reveal_type(iter(values_qs)) # N: Revealed type is 'typing.Iterator[builtins.dict*[builtins.str, Any]]'
reveal_type(values_qs.iterator()) # N: Revealed type is 'typing.Iterator[builtins.dict*[builtins.str, Any]]'
reveal_type(values_qs.first()) # N: Revealed type is 'Union[builtins.dict*[builtins.str, Any], None]'
reveal_type(values_qs.earliest()) # N: Revealed type is 'builtins.dict*[builtins.str, Any]'
reveal_type(values_qs[0]) # N: Revealed type is 'builtins.dict*[builtins.str, Any]'
reveal_type(values_qs[:9]) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.dict*[builtins.str, Any]]'
reveal_type(values_qs.in_bulk()) # N: Revealed type is 'builtins.dict[Any, main.Blog*]'
[/CASE]
[CASE test_queryset_values_list_named_false_flat_false]
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
values_list_qs = Blog.objects.values_list('id', 'name')
reveal_type(values_list_qs) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, Tuple[builtins.int, builtins.str]]'
reveal_type(values_list_qs.all()) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, Tuple[builtins.int, builtins.str]]'
reveal_type(values_list_qs.get(id=1)) # N: Revealed type is 'Tuple[builtins.int, builtins.str]'
reveal_type(iter(values_list_qs)) # N: Revealed type is 'typing.Iterator[Tuple[builtins.int, builtins.str]]'
reveal_type(values_list_qs.iterator()) # N: Revealed type is 'typing.Iterator[Tuple[builtins.int, builtins.str]]'
reveal_type(values_list_qs.first()) # N: Revealed type is 'Union[Tuple[builtins.int, builtins.str], None]'
reveal_type(values_list_qs.earliest()) # N: Revealed type is 'Tuple[builtins.int, builtins.str]'
reveal_type(values_list_qs[0]) # N: Revealed type is 'Tuple[builtins.int, builtins.str]'
reveal_type(values_list_qs[:9]) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, Tuple[builtins.int, builtins.str]]'
reveal_type(values_list_qs.in_bulk()) # N: Revealed type is 'builtins.dict[Any, main.Blog*]'
[/CASE]
[CASE test_queryset_values_list_named_false_flat_true]
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
flat_values_list_qs = Blog.objects.values_list('id', flat=True)
reveal_type(flat_values_list_qs) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.int]'
reveal_type(flat_values_list_qs.all()) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.int*]'
reveal_type(flat_values_list_qs.get(id=1)) # N: Revealed type is 'builtins.int*'
reveal_type(iter(flat_values_list_qs)) # N: Revealed type is 'typing.Iterator[builtins.int*]'
reveal_type(flat_values_list_qs.iterator()) # N: Revealed type is 'typing.Iterator[builtins.int*]'
reveal_type(flat_values_list_qs.first()) # N: Revealed type is 'Union[builtins.int*, None]'
reveal_type(flat_values_list_qs.earliest()) # N: Revealed type is 'builtins.int*'
reveal_type(flat_values_list_qs[0]) # N: Revealed type is 'builtins.int*'
reveal_type(flat_values_list_qs[:9]) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.int*]'
reveal_type(flat_values_list_qs.in_bulk()) # N: Revealed type is 'builtins.dict[Any, main.Blog*]'
[/CASE]
[CASE test_queryset_values_list_named_true_flat_false]
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
named_values_list_qs = Blog.objects.values_list('id', named=True)
reveal_type(named_values_list_qs) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, Tuple[builtins.int, fallback=main.Row]]'
reveal_type(named_values_list_qs.all()) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, Tuple[builtins.int, fallback=main.Row]]'
reveal_type(named_values_list_qs.get(id=1)) # N: Revealed type is 'Tuple[builtins.int, fallback=main.Row]'
reveal_type(iter(named_values_list_qs)) # N: Revealed type is 'typing.Iterator[Tuple[builtins.int, fallback=main.Row]]'
reveal_type(named_values_list_qs.iterator()) # N: Revealed type is 'typing.Iterator[Tuple[builtins.int, fallback=main.Row]]'
reveal_type(named_values_list_qs.first()) # N: Revealed type is 'Union[Tuple[builtins.int, fallback=main.Row], None]'
reveal_type(named_values_list_qs.earliest()) # N: Revealed type is 'Tuple[builtins.int, fallback=main.Row]'
reveal_type(named_values_list_qs[0]) # N: Revealed type is 'Tuple[builtins.int, fallback=main.Row]'
reveal_type(named_values_list_qs[:9]) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, Tuple[builtins.int, fallback=main.Row]]'
reveal_type(named_values_list_qs.in_bulk()) # N: Revealed type is 'builtins.dict[Any, main.Blog*]'
[/CASE]
[CASE test_queryset_values_list_flat_true_custom_primary_key_get_element]
from django.db import models
class Blog(models.Model):
primary_uuid = models.UUIDField(primary_key=True)
# Blog has a primary key field specified, so no automatic 'id' field is expected to exist
reveal_type(Blog.objects.values_list('id', flat=True).get()) # N: Revealed type is 'Any'
# Access Blog's pk (which is UUID field)
reveal_type(Blog.objects.values_list('pk', flat=True).get()) # N: Revealed type is 'uuid.UUID*'
[/CASE]
[CASE test_queryset_values_list_flat_true_custom_primary_key_related_field]
from django.db import models
class Blog(models.Model):
primary_uuid = models.UUIDField(primary_key=True)
class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE, related_name="entries")
# Accessing PK of model pointed to by foreign key
reveal_type(Entry.objects.values_list('blog', flat=True).get()) # N: Revealed type is 'uuid.UUID*'
# Alternative way of accessing PK of model pointed to by foreign key
reveal_type(Entry.objects.values_list('blog_id', flat=True).get()) # N: Revealed type is 'uuid.UUID*'
# Yet another (more explicit) way of accessing PK of related model
reveal_type(Entry.objects.values_list('blog__pk', flat=True).get()) # N: Revealed type is 'uuid.UUID*'
# Blog has a primary key field specified, so no automatic 'id' field is expected to exist
reveal_type(Entry.objects.values_list('blog__id', flat=True).get()) # N: Revealed type is 'Any'
[/CASE]
[CASE test_queryset_values_list_error_conditions]
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
# Emulate at type-check time the errors that Django reports
Blog.objects.values_list('id', flat=True, named=True) # E: 'flat' and 'named' can't be used together.
Blog.objects.values_list('id', 'name', flat=True) # E: 'flat' is not valid when values_list is called with more than one field.
[/CASE]
[CASE test_queryset_values_list_returns_tuple_of_fields]
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
created_at = models.DateTimeField()
# values_list where parameter types are all known
reveal_type(Blog.objects.values_list('id', 'created_at').get()) # N: Revealed type is 'Tuple[builtins.int, datetime.datetime]'
tup = Blog.objects.values_list('id', 'created_at').get()
reveal_type(tup[0]) # N: Revealed type is 'builtins.int'
reveal_type(tup[1]) # N: Revealed type is 'datetime.datetime'
tup[2] # E: Tuple index out of range
# values_list returning namedtuple
reveal_type(Blog.objects.values_list('id', 'created_at', named=True).get()) # N: Revealed type is 'Tuple[builtins.int, datetime.datetime, fallback=main.Row]'
[/CASE]
[CASE test_queryset_values_list_invalid_lookups_produce_any]
from django.db import models
class Blog(models.Model): pass
class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE, related_name="entries")
# Invalid lookups produce Any type rather than giving errors.
reveal_type(Blog.objects.values_list('id', 'invalid_lookup').get()) # N: Revealed type is 'Tuple[builtins.int, Any]'
reveal_type(Blog.objects.values_list('entries_id', flat=True).get()) # N: Revealed type is 'Any'
reveal_type(Blog.objects.values_list('entries__foo', flat=True).get()) # N: Revealed type is 'Any'
reveal_type(Blog.objects.values_list('+', flat=True).get()) # N: Revealed type is 'Any'
[/CASE]
[CASE test_queryset_values_list_basic_inheritance]
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
created_at = models.DateTimeField()
class BlogChild(Blog):
child_field = models.CharField(max_length=100)
# Basic inheritance
reveal_type(BlogChild.objects.values_list('id', 'created_at', 'child_field').get()) # N: Revealed type is 'Tuple[builtins.int, datetime.datetime, builtins.str]'
[/CASE]
[CASE test_query_values_list_flat_true_plain_foreign_key]
from django.db import models
class Blog(models.Model): pass
class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
# Foreign key
reveal_type(Entry.objects.values_list('blog', flat=True).get()) # N: Revealed type is 'builtins.int*'
reveal_type(Entry.objects.values_list('blog__id', flat=True).get()) # N: Revealed type is 'builtins.int*'
reveal_type(Entry.objects.values_list('blog__pk', flat=True).get()) # N: Revealed type is 'builtins.int*'
reveal_type(Entry.objects.values_list('blog_id', flat=True).get()) # N: Revealed type is 'builtins.int*'
[/CASE]
[CASE test_query_values_list_flat_true_custom_primary_key]
from django.db import models
class Blog(models.Model):
id = models.UUIDField(primary_key=True)
class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
# Foreign key
reveal_type(Entry.objects.values_list('blog', flat=True).get()) # N: Revealed type is 'uuid.UUID*'
reveal_type(Entry.objects.values_list('blog__id', flat=True).get()) # N: Revealed type is 'uuid.UUID*'
reveal_type(Entry.objects.values_list('blog__pk', flat=True).get()) # N: Revealed type is 'uuid.UUID*'
reveal_type(Entry.objects.values_list('blog_id', flat=True).get()) # N: Revealed type is 'uuid.UUID*'
[/CASE]
[CASE test_query_values_list_flat_true_nullable_foreign_key]
from django.db import models
class Blog(models.Model): pass
class Entry(models.Model):
nullable_blog = models.ForeignKey(Blog, on_delete=models.CASCADE, related_name="+", null=True)
# Foreign key (nullable=True)
reveal_type(Entry.objects.values_list('nullable_blog', flat=True).get()) # N: Revealed type is 'Union[builtins.int, None]'
reveal_type(Entry.objects.values_list('nullable_blog_id', flat=True).get()) # N: Revealed type is 'Union[builtins.int, None]'
reveal_type(Entry.objects.values_list('nullable_blog__id', flat=True).get()) # N: Revealed type is 'Union[builtins.int, None]'
reveal_type(Entry.objects.values_list('nullable_blog__pk', flat=True).get()) # N: Revealed type is 'Union[builtins.int, None]'
[/CASE]
[CASE test_query_values_list_flat_true_foreign_key_reverse_relation]
from django.db import models
class Blog(models.Model): pass
class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE, related_name='entries')
blog_with_related_query_name = models.ForeignKey(Blog, on_delete=models.CASCADE, related_query_name="my_related_query_name")
title = models.CharField(max_length=100)
# Reverse relation of ForeignKey
reveal_type(Blog.objects.values_list('entries', flat=True).get()) # N: Revealed type is 'builtins.int*'
reveal_type(Blog.objects.values_list('entries__id', flat=True).get()) # N: Revealed type is 'builtins.int*'
reveal_type(Blog.objects.values_list('entries__title', flat=True).get()) # N: Revealed type is 'builtins.str*'
# Reverse relation of ForeignKey (with related_query_name set)
reveal_type(Blog.objects.values_list('my_related_query_name__id', flat=True).get()) # N: Revealed type is 'builtins.int*'
[/CASE]
[CASE test_query_values_list_flat_true_foreign_key_custom_primary_key_reverse_relation]
from django.db import models
class Blog(models.Model): pass
class Entry(models.Model):
id = models.UUIDField(primary_key=True)
blog = models.ForeignKey(Blog, on_delete=models.CASCADE, related_name='entries')
blog_with_related_query_name = models.ForeignKey(Blog, on_delete=models.CASCADE, related_query_name="my_related_query_name")
title = models.CharField(max_length=100)
# Reverse relation of ForeignKey
reveal_type(Blog.objects.values_list('entries', flat=True).get()) # N: Revealed type is 'uuid.UUID*'
reveal_type(Blog.objects.values_list('entries__id', flat=True).get()) # N: Revealed type is 'uuid.UUID*'
# Reverse relation of ForeignKey (with related_query_name set)
reveal_type(Blog.objects.values_list('my_related_query_name__id', flat=True).get()) # N: Revealed type is 'uuid.UUID*'
[/CASE]
[CASE test_queryset_values_list_and_values_behavior_with_no_fields_specified_and_accessing_unknown_attributes]
from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
created_at = models.DateTimeField()
row_named = Blog.objects.values_list('id', 'created_at', named=True).get()
reveal_type(row_named.id) # N: Revealed type is 'builtins.int'
reveal_type(row_named.created_at) # N: Revealed type is 'datetime.datetime'
row_named.non_existent_field # E: "Row" has no attribute "non_existent_field"
# When no fields are specified, fallback to Any
row_named_no_fields = Blog.objects.values_list(named=True).get()
reveal_type(row_named_no_fields) # N: Revealed type is 'Tuple[, fallback=django._NamedTupleAnyAttr]'
# Don't complain about access to any attribute for now
reveal_type(row_named_no_fields.non_existent_field) # N: Revealed type is 'Any'
row_named_no_fields.non_existent_field = 1
# It should still behave like a NamedTuple
reveal_type(row_named_no_fields._asdict()) # N: Revealed type is 'builtins.dict[builtins.str, Any]'
dict_row = Blog.objects.values('id', 'created_at').get()
reveal_type(dict_row["id"]) # N: Revealed type is 'builtins.int'
reveal_type(dict_row["created_at"]) # N: Revealed type is 'datetime.datetime'
dict_row["non_existent_field"] # E: 'non_existent_field' is not a valid TypedDict key; expected one of ('id', 'created_at')
dict_row.pop('created_at')
dict_row.pop('non_existent_field') # E: 'non_existent_field' is not a valid TypedDict key; expected one of ('id', 'created_at')
row_dict_no_fields = Blog.objects.values().get()
reveal_type(row_dict_no_fields) # N: Revealed type is 'builtins.dict*[builtins.str, Any]'
reveal_type(row_dict_no_fields["non_existent_field"]) # N: Revealed type is 'Any'
[CASE values_with_annotate_inside_the_expressions]
from django.db import models
from django.db.models.functions import Lower, Upper
class Publisher(models.Model):
pass
class Book(models.Model):
name = models.CharField(max_length=100)
publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE, related_name='books')
reveal_type(Publisher().books.values('name', lower_name=Lower('name'), upper_name=Upper('name'))) # N: Revealed type is 'django.db.models.query.QuerySet[main.Book*, TypedDict({'name'?: builtins.str, 'lower_name'?: Any, 'upper_name'?: Any})]'
[CASE values_and_values_list_some_dynamic_fields]
from django.db import models
class Publisher(models.Model):
pass
class Book(models.Model):
name = models.CharField(max_length=100)
publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE, related_name='books')
some_dynamic_field = 'publisher'
# Correct Tuple field types should be filled in when string literal is used, while Any is used for dynamic fields
reveal_type(Publisher().books.values_list('name', some_dynamic_field)) # N: Revealed type is 'django.db.models.query.QuerySet[main.Book*, Tuple[builtins.str, Any]]'
# Flat with dynamic fields (there is only 1), means of course Any
reveal_type(Publisher().books.values_list(some_dynamic_field, flat=True)) # N: Revealed type is 'django.db.models.query.QuerySet[main.Book*, Any]'
# A NamedTuple with a fallback to Any could be implemented, but for now that's unsupported, so all
# fields on the NamedTuple are Any for now
reveal_type(Publisher().books.values_list('name', some_dynamic_field, named=True).name) # N: Revealed type is 'Any'
# A TypedDict with a fallback to Any could be implemented, but for now that's unsupported,
# so an ordinary Dict is used for now.
reveal_type(Publisher().books.values(some_dynamic_field, 'name')) # N: Revealed type is 'django.db.models.query.QuerySet[main.Book*, builtins.dict[builtins.str, Any]]'

View File

@@ -1,319 +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) # N: Revealed type is 'myapp.models.user.User'
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]

View File

@@ -1,99 +0,0 @@
[CASE test_settings_are_parsed_into_django_conf_settings]
[env DJANGO_SETTINGS_MODULE=mysettings]
[disable_cache]
from django.conf import settings
# standard settings
reveal_type(settings.AUTH_USER_MODEL) # N: Revealed type is 'builtins.str'
reveal_type(settings.ROOT_DIR) # N: Revealed type is 'builtins.str'
reveal_type(settings.APPS_DIR) # N: Revealed type is 'pathlib.Path'
reveal_type(settings.OBJ) # N: Revealed type is 'django.utils.functional.LazyObject'
reveal_type(settings.NUMBERS) # N: Revealed type is 'builtins.list[builtins.str*]'
reveal_type(settings.DICT) # N: Revealed type is 'builtins.dict[Any, Any]'
[file base.py]
from pathlib import Path
ROOT_DIR = '/etc'
APPS_DIR = Path(ROOT_DIR)
[file mysettings.py]
from base import *
SECRET_KEY = 112233
NUMBERS = ['one', 'two']
DICT = {} # type: ignore
from django.utils.functional import LazyObject
OBJ = LazyObject()
[/CASE]
[CASE test_settings_could_be_defined_in_different_module_and_imported_with_star]
[env DJANGO_SETTINGS_MODULE=mysettings]
[disable_cache]
from django.conf import settings
reveal_type(settings.ROOT_DIR) # N: Revealed type is 'pathlib.Path'
reveal_type(settings.SETUP) # N: Revealed type is 'Union[builtins.int, None]'
reveal_type(settings.DATABASES) # N: Revealed type is 'builtins.dict[builtins.str*, builtins.str*]'
reveal_type(settings.LOCAL_SETTING) # N: Revealed type is 'builtins.int'
reveal_type(settings.BASE_SETTING) # N: Revealed type is 'builtins.int'
[file mysettings.py]
from local import *
from typing import Optional
SETUP: Optional[int] = 3
[file local.py]
from base import *
SETUP: int = 3
DATABASES = {'default': 'mydb'}
LOCAL_SETTING = 1
[file base.py]
from pathlib import Path
from typing import Any
SETUP: Any = None
ROOT_DIR = Path(__file__)
BASE_SETTING = 1
[/CASE]
[CASE global_settings_are_always_loaded]
from django.conf import settings
reveal_type(settings.AUTH_USER_MODEL) # N: Revealed type is 'builtins.str'
reveal_type(settings.AUTHENTICATION_BACKENDS) # N: Revealed type is 'typing.Sequence[builtins.str]'
[/CASE]
[CASE test_circular_dependency_in_settings_works_if_settings_have_annotations]
[env DJANGO_SETTINGS_MODULE=mysettings]
[disable_cache]
from django.conf import settings
class Class:
pass
reveal_type(settings.MYSETTING) # N: Revealed type is 'builtins.int'
reveal_type(settings.REGISTRY) # N: Revealed type is 'Union[main.Class, None]'
reveal_type(settings.LIST) # N: Revealed type is 'builtins.list[builtins.str]'
[file mysettings.py]
from typing import TYPE_CHECKING, Optional, List
if TYPE_CHECKING:
from main import Class
MYSETTING = 1122
REGISTRY: Optional['Class'] = None
LIST: List[str] = ['1', '2']
[/CASE]
[CASE fail_if_there_is_no_setting]
from django.conf import settings
reveal_type(settings.NOT_EXISTING)
[env DJANGO_SETTINGS_MODULE=mysettings2]
[disable_cache]
[file mysettings2.py]
[out]
main:2: note: Revealed type is 'Any'
main:2: error: 'Settings' object has no attribute 'NOT_EXISTING'
[/CASE]

View File

@@ -1,62 +0,0 @@
[CASE get_object_or_404_returns_proper_types]
from django.shortcuts import get_object_or_404, get_list_or_404
from django.db import models
class MyModel(models.Model):
pass
reveal_type(get_object_or_404(MyModel)) # N: Revealed type is 'main.MyModel*'
reveal_type(get_object_or_404(MyModel.objects)) # N: Revealed type is 'main.MyModel*'
reveal_type(get_object_or_404(MyModel.objects.get_queryset())) # N: Revealed type is 'main.MyModel*'
reveal_type(get_list_or_404(MyModel)) # N: Revealed type is 'builtins.list[main.MyModel*]'
reveal_type(get_list_or_404(MyModel.objects)) # N: Revealed type is 'builtins.list[main.MyModel*]'
reveal_type(get_list_or_404(MyModel.objects.get_queryset())) # N: Revealed type is 'builtins.list[main.MyModel*]'
[/CASE]
[CASE get_user_model_returns_proper_class]
[env DJANGO_SETTINGS_MODULE=mysettings]
[disable_cache]
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from myapp.models import MyUser
from django.contrib.auth import get_user_model
UserModel = get_user_model()
reveal_type(UserModel.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.MyUser]'
[file mysettings.py]
from basic import *
INSTALLED_APPS = ('myapp',)
[file basic.py]
AUTH_USER_MODEL = 'myapp.MyUser'
[file myapp/__init__.py]
[file myapp/models.py]
from django.db import models
class MyUser(models.Model):
pass
[/CASE]
[CASE return_type_model_and_show_error_if_model_not_yet_imported]
[env DJANGO_SETTINGS_MODULE=mysettings]
[disable_cache]
from django.contrib.auth import get_user_model
UserModel = get_user_model()
reveal_type(UserModel.objects)
[file mysettings.py]
INSTALLED_APPS = ('myapp',)
AUTH_USER_MODEL = 'myapp.MyUser'
[file myapp/__init__.py]
[file myapp/models.py]
from django.db import models
class MyUser(models.Model):
pass
[out]
main:3: error: "myapp.MyUser" model class is not imported so far. Try to import it (under if TYPE_CHECKING) at the beginning of the current file
main:4: note: Revealed type is 'Any'
main:4: error: "Type[Model]" has no attribute "objects"
[/CASE]

View File

@@ -1,50 +0,0 @@
[CASE test_transaction_atomic]
from django.db import transaction
with transaction.atomic():
pass
with transaction.atomic(using="mydb"):
pass
with transaction.atomic(using="mydb", savepoint=False):
pass
@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'
[out]