mirror of
https://github.com/davidhalter/django-stubs.git
synced 2025-12-06 20:24:31 +08:00
new semanal wip 1
This commit is contained in:
@@ -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'
|
||||
|
||||
@@ -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: ...
|
||||
|
||||
@@ -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: ...
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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 = ...
|
||||
|
||||
@@ -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]: ...
|
||||
|
||||
@@ -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]]: ...
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
0
mypy_django_plugin/lib/tests/__init__.py
Normal file
0
mypy_django_plugin/lib/tests/__init__.py
Normal file
21
mypy_django_plugin/lib/tests/sample_django_project/manage.py
Executable file
21
mypy_django_plugin/lib/tests/sample_django_project/manage.py
Executable 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()
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
@@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class MyappConfig(AppConfig):
|
||||
label = 'myapp22'
|
||||
@@ -0,0 +1,6 @@
|
||||
from django.db import models
|
||||
|
||||
|
||||
# Create your models here.
|
||||
class MyModel(models.Model):
|
||||
pass
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
@@ -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/'
|
||||
@@ -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),
|
||||
]
|
||||
@@ -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()
|
||||
14
mypy_django_plugin/lib/tests/test_get_app_configs.py
Normal file
14
mypy_django_plugin/lib/tests/test_get_app_configs.py
Normal 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())
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
0
mypy_django_plugin_newsemanal/__init__.py
Normal file
0
mypy_django_plugin_newsemanal/__init__.py
Normal file
80
mypy_django_plugin_newsemanal/context.py
Normal file
80
mypy_django_plugin_newsemanal/context.py
Normal 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
|
||||
0
mypy_django_plugin_newsemanal/lib/__init__.py
Normal file
0
mypy_django_plugin_newsemanal/lib/__init__.py
Normal file
35
mypy_django_plugin_newsemanal/lib/fullnames.py
Normal file
35
mypy_django_plugin_newsemanal/lib/fullnames.py
Normal 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
|
||||
}
|
||||
99
mypy_django_plugin_newsemanal/lib/helpers.py
Normal file
99
mypy_django_plugin_newsemanal/lib/helpers.py
Normal 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
|
||||
27
mypy_django_plugin_newsemanal/lib/metadata.py
Normal file
27
mypy_django_plugin_newsemanal/lib/metadata.py
Normal 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', {})
|
||||
0
mypy_django_plugin_newsemanal/lib/tests/__init__.py
Normal file
0
mypy_django_plugin_newsemanal/lib/tests/__init__.py
Normal file
21
mypy_django_plugin_newsemanal/lib/tests/sample_django_project/manage.py
Executable file
21
mypy_django_plugin_newsemanal/lib/tests/sample_django_project/manage.py
Executable 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()
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
@@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class MyappConfig(AppConfig):
|
||||
label = 'myapp22'
|
||||
@@ -0,0 +1,6 @@
|
||||
from django.db import models
|
||||
|
||||
|
||||
# Create your models here.
|
||||
class MyModel(models.Model):
|
||||
pass
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
@@ -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/'
|
||||
@@ -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),
|
||||
]
|
||||
@@ -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()
|
||||
@@ -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())
|
||||
|
||||
173
mypy_django_plugin_newsemanal/main.py
Normal file
173
mypy_django_plugin_newsemanal/main.py
Normal 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
|
||||
264
mypy_django_plugin_newsemanal/transformers/fields.py
Normal file
264
mypy_django_plugin_newsemanal/transformers/fields.py
Normal 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'
|
||||
35
mypy_django_plugin_newsemanal/transformers/init_create.py
Normal file
35
mypy_django_plugin_newsemanal/transformers/init_create.py
Normal 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
|
||||
255
mypy_django_plugin_newsemanal/transformers/models.py
Normal file
255
mypy_django_plugin_newsemanal/transformers/models.py
Normal 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()
|
||||
39
mypy_django_plugin_newsemanal/transformers/querysets.py
Normal file
39
mypy_django_plugin_newsemanal/transformers/querysets.py
Normal 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, [])])
|
||||
18
mypy_django_plugin_newsemanal/transformers/settings.py
Normal file
18
mypy_django_plugin_newsemanal/transformers/settings.py
Normal 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, []))
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
[tool.black]
|
||||
line-length = 120
|
||||
include = 'django-stubs/.*.pyi$'
|
||||
include = 'django-stubs/.*\.pyi$'
|
||||
@@ -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
|
||||
0
pytest_plugin/__init__.py
Normal file
0
pytest_plugin/__init__.py
Normal file
41
pytest_plugin/collect.py
Normal file
41
pytest_plugin/collect.py
Normal 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
42
pytest_plugin/setup.py
Normal 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'
|
||||
]
|
||||
)
|
||||
4
setup.py
4
setup.py
@@ -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',
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
incremental = True
|
||||
strict_optional = True
|
||||
plugins =
|
||||
mypy_django_plugin.main
|
||||
mypy_django_plugin_newsemanal.main
|
||||
129
test-data-newsemanal/typecheck/fields/test_base.yml
Normal file
129
test-data-newsemanal/typecheck/fields/test_base.yml
Normal 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)
|
||||
70
test-data-newsemanal/typecheck/fields/test_nullable.yml
Normal file
70
test-data-newsemanal/typecheck/fields/test_nullable.yml
Normal 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)
|
||||
@@ -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))
|
||||
410
test-data-newsemanal/typecheck/fields/test_related.yml
Normal file
410
test-data-newsemanal/typecheck/fields/test_related.yml
Normal 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')
|
||||
293
test-data-newsemanal/typecheck/managers/test_managers.yml
Normal file
293
test-data-newsemanal/typecheck/managers/test_managers.yml
Normal 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']()
|
||||
460
test-data-newsemanal/typecheck/managers/test_queryset.yml
Normal file
460
test-data-newsemanal/typecheck/managers/test_queryset.yml
Normal 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')
|
||||
97
test-data-newsemanal/typecheck/models/test_create.yml
Normal file
97
test-data-newsemanal/typecheck/models/test_create.yml
Normal 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)
|
||||
70
test-data-newsemanal/typecheck/models/test_inheritance.yml
Normal file
70
test-data-newsemanal/typecheck/models/test_inheritance.yml
Normal 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
|
||||
219
test-data-newsemanal/typecheck/models/test_init.yml
Normal file
219
test-data-newsemanal/typecheck/models/test_init.yml
Normal 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)
|
||||
53
test-data-newsemanal/typecheck/test_config.yml
Normal file
53
test-data-newsemanal/typecheck/test_config.yml
Normal 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)
|
||||
|
||||
57
test-data-newsemanal/typecheck/test_forms.yml
Normal file
57
test-data-newsemanal/typecheck/test_forms.yml
Normal 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
|
||||
42
test-data-newsemanal/typecheck/test_helpers.yml
Normal file
42
test-data-newsemanal/typecheck/test_helpers.yml
Normal file
@@ -0,0 +1,42 @@
|
||||
- case: transaction_atomic_contextmanager
|
||||
main: |
|
||||
from django.db import transaction
|
||||
with transaction.atomic():
|
||||
pass
|
||||
with transaction.atomic(using="mydb"):
|
||||
pass
|
||||
with transaction.atomic(using="mydb", savepoint=False):
|
||||
pass
|
||||
|
||||
- case: transaction_atomic_decorator
|
||||
main: |
|
||||
from django.db import transaction
|
||||
|
||||
@transaction.atomic()
|
||||
def decorated_func(param1: str, param2: int) -> bool:
|
||||
pass
|
||||
# Ensure that the function's type is preserved
|
||||
reveal_type(decorated_func) # N: Revealed type is 'def (param1: builtins.str, param2: builtins.int) -> builtins.bool'
|
||||
|
||||
@transaction.atomic(using="mydb")
|
||||
def decorated_func_using(param1: str, param2: int) -> bool:
|
||||
pass
|
||||
# Ensure that the function's type is preserved
|
||||
reveal_type(decorated_func_using) # N: Revealed type is 'def (param1: builtins.str, param2: builtins.int) -> builtins.bool'
|
||||
|
||||
class ClassWithAtomicMethod:
|
||||
# Bare decorator
|
||||
@transaction.atomic
|
||||
def atomic_method1(self, abc: int) -> str:
|
||||
pass
|
||||
@transaction.atomic(savepoint=True)
|
||||
def atomic_method2(self):
|
||||
pass
|
||||
@transaction.atomic(using="db", savepoint=True)
|
||||
def atomic_method3(self, myparam: str) -> int:
|
||||
pass
|
||||
ClassWithAtomicMethod().atomic_method1("abc") # E: Argument 1 to "atomic_method1" of "ClassWithAtomicMethod" has incompatible type "str"; expected "int"
|
||||
# Ensure that the method's type is preserved
|
||||
reveal_type(ClassWithAtomicMethod().atomic_method1) # N: Revealed type is 'def (abc: builtins.int) -> builtins.str'
|
||||
# Ensure that the method's type is preserved
|
||||
reveal_type(ClassWithAtomicMethod().atomic_method3) # N: Revealed type is 'def (myparam: builtins.str) -> builtins.int'
|
||||
402
test-data-newsemanal/typecheck/test_import_all.yml
Normal file
402
test-data-newsemanal/typecheck/test_import_all.yml
Normal 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
|
||||
33
test-data-newsemanal/typecheck/test_migrations.yml
Normal file
33
test-data-newsemanal/typecheck/test_migrations.yml
Normal 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
|
||||
43
test-data-newsemanal/typecheck/test_settings.yml
Normal file
43
test-data-newsemanal/typecheck/test_settings.yml
Normal 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'
|
||||
39
test-data-newsemanal/typecheck/test_shortcuts.yml
Normal file
39
test-data-newsemanal/typecheck/test_shortcuts.yml
Normal 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
|
||||
@@ -1,37 +0,0 @@
|
||||
[CASE missing_settings_ignored_flag]
|
||||
[env MYPY_DJANGO_CONFIG=${MYPY_CWD}/mypy_django.ini]
|
||||
[disable_cache]
|
||||
from django.conf import settings
|
||||
reveal_type(settings.NO_SUCH_SETTING) # N: Revealed type is 'Any'
|
||||
|
||||
[file mypy_django.ini]
|
||||
[[mypy_django_plugin]
|
||||
ignore_missing_settings = True
|
||||
[/CASE]
|
||||
|
||||
[CASE django_settings_via_config_file]
|
||||
[env MYPY_DJANGO_CONFIG=${MYPY_CWD}/mypy_django.ini]
|
||||
[disable_cache]
|
||||
from django.conf import settings
|
||||
reveal_type(settings.MY_SETTING) # N: Revealed type is 'builtins.int'
|
||||
|
||||
[file mypy_django.ini]
|
||||
[[mypy_django_plugin]
|
||||
django_settings = mysettings
|
||||
|
||||
[file mysettings.py]
|
||||
MY_SETTING: int = 1
|
||||
[/CASE]
|
||||
|
||||
[CASE mypy_django_ini_in_current_directory_is_a_default]
|
||||
[disable_cache]
|
||||
from django.conf import settings
|
||||
reveal_type(settings.MY_SETTING) # N: Revealed type is 'builtins.int'
|
||||
|
||||
[file mypy_django.ini]
|
||||
[[mypy_django_plugin]
|
||||
django_settings = mysettings
|
||||
|
||||
[file mysettings.py]
|
||||
MY_SETTING: int = 1
|
||||
[/CASE]
|
||||
@@ -1,144 +0,0 @@
|
||||
[CASE 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]
|
||||
@@ -1,41 +0,0 @@
|
||||
[CASE no_incompatible_meta_nested_class_false_positive]
|
||||
from django.db import models
|
||||
from django import forms
|
||||
|
||||
class Article(models.Model):
|
||||
pass
|
||||
class Category(models.Model):
|
||||
pass
|
||||
class ArticleForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Article
|
||||
fields = '__all__'
|
||||
class CategoryForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Category
|
||||
fields = '__all__'
|
||||
class CompositeForm(ArticleForm, CategoryForm):
|
||||
pass
|
||||
[/CASE]
|
||||
|
||||
[CASE formview_methods_on_forms_return_proper_types]
|
||||
from typing import Any
|
||||
from django import forms
|
||||
from django.views.generic.edit import FormView
|
||||
|
||||
class MyForm(forms.ModelForm):
|
||||
pass
|
||||
class MyForm2(forms.ModelForm):
|
||||
pass
|
||||
|
||||
class MyView(FormView):
|
||||
form_class = MyForm
|
||||
|
||||
def post(self, request, *args: Any, **kwds: Any):
|
||||
form_class = self.get_form_class()
|
||||
reveal_type(form_class) # N: Revealed type is 'Type[main.MyForm]'
|
||||
reveal_type(self.get_form(None)) # N: Revealed type is 'main.MyForm'
|
||||
reveal_type(self.get_form()) # N: Revealed type is 'main.MyForm'
|
||||
reveal_type(self.get_form(form_class)) # N: Revealed type is 'main.MyForm'
|
||||
reveal_type(self.get_form(MyForm2)) # N: Revealed type is 'main.MyForm2'
|
||||
[/CASE]
|
||||
@@ -1,402 +0,0 @@
|
||||
[CASE import_all_packages_to_trigger_stubs_check]
|
||||
import django.apps
|
||||
import django.apps.config
|
||||
import django.apps.registry
|
||||
import django.conf.global_settings
|
||||
import django.conf.urls
|
||||
import django.conf.urls.i18n
|
||||
import django.conf.urls.static
|
||||
import django.contrib.admin.actions
|
||||
import django.contrib.admin.apps
|
||||
import django.contrib.admin.checks
|
||||
import django.contrib.admin.decorators
|
||||
import django.contrib.admin.filters
|
||||
import django.contrib.admin.forms
|
||||
import django.contrib.admin.helpers
|
||||
import django.contrib.admin.models
|
||||
import django.contrib.admin.options
|
||||
import django.contrib.admin.sites
|
||||
import django.contrib.admin.templatetags
|
||||
import django.contrib.admin.templatetags.admin_list
|
||||
import django.contrib.admin.templatetags.admin_modify
|
||||
import django.contrib.admin.templatetags.admin_static
|
||||
import django.contrib.admin.templatetags.admin_urls
|
||||
import django.contrib.admin.templatetags.base
|
||||
import django.contrib.admin.templatetags.log
|
||||
import django.contrib.admin.utils
|
||||
import django.contrib.admin.views
|
||||
import django.contrib.admin.views.autocomplete
|
||||
import django.contrib.admin.views.decorators
|
||||
import django.contrib.admin.views.main
|
||||
import django.contrib.admin.widgets
|
||||
import django.contrib.admindocs
|
||||
import django.contrib.admindocs.middleware
|
||||
import django.contrib.admindocs.utils
|
||||
import django.contrib.admindocs.views
|
||||
import django.contrib.auth.admin
|
||||
import django.contrib.auth.apps
|
||||
import django.contrib.auth.backends
|
||||
import django.contrib.auth.base_user
|
||||
import django.contrib.auth.checks
|
||||
import django.contrib.auth.context_processors
|
||||
import django.contrib.auth.decorators
|
||||
import django.contrib.auth.forms
|
||||
import django.contrib.auth.handlers
|
||||
import django.contrib.auth.handlers.modwsgi
|
||||
import django.contrib.auth.hashers
|
||||
import django.contrib.auth.management.commands
|
||||
import django.contrib.auth.management.commands.changepassword
|
||||
import django.contrib.auth.management.commands.createsuperuser
|
||||
import django.contrib.auth.middleware
|
||||
import django.contrib.auth.mixins
|
||||
import django.contrib.auth.models
|
||||
import django.contrib.auth.password_validation
|
||||
import django.contrib.auth.signals
|
||||
import django.contrib.auth.tokens
|
||||
import django.contrib.auth.validators
|
||||
import django.contrib.auth.views
|
||||
import django.contrib.contenttypes.admin
|
||||
import django.contrib.contenttypes.apps
|
||||
import django.contrib.contenttypes.checks
|
||||
import django.contrib.contenttypes.fields
|
||||
import django.contrib.contenttypes.forms
|
||||
import django.contrib.contenttypes.management.commands
|
||||
import django.contrib.contenttypes.management.commands.remove_stale_contenttypes
|
||||
import django.contrib.contenttypes.models
|
||||
import django.contrib.contenttypes.views
|
||||
import django.contrib.flatpages.forms
|
||||
import django.contrib.flatpages.middleware
|
||||
import django.contrib.flatpages.models
|
||||
import django.contrib.flatpages.sitemaps
|
||||
import django.contrib.flatpages.templatetags
|
||||
import django.contrib.flatpages.templatetags.flatpages
|
||||
import django.contrib.flatpages.views
|
||||
import django.contrib.humanize.templatetags
|
||||
import django.contrib.humanize.templatetags.humanize
|
||||
import django.contrib.messages.api
|
||||
import django.contrib.messages.context_processors
|
||||
import django.contrib.messages.middleware
|
||||
import django.contrib.messages.storage
|
||||
import django.contrib.messages.storage.base
|
||||
import django.contrib.messages.storage.cookie
|
||||
import django.contrib.messages.storage.fallback
|
||||
import django.contrib.messages.storage.session
|
||||
import django.contrib.messages.utils
|
||||
import django.contrib.messages.views
|
||||
import django.contrib.postgres.fields
|
||||
import django.contrib.postgres.fields.array
|
||||
import django.contrib.postgres.fields.citext
|
||||
import django.contrib.postgres.fields.hstore
|
||||
import django.contrib.postgres.fields.jsonb
|
||||
import django.contrib.postgres.fields.mixins
|
||||
import django.contrib.postgres.fields.ranges
|
||||
import django.contrib.postgres.operations
|
||||
import django.contrib.redirects
|
||||
import django.contrib.redirects.middleware
|
||||
import django.contrib.redirects.models
|
||||
import django.contrib.sessions.backends
|
||||
import django.contrib.sessions.backends.base
|
||||
import django.contrib.sessions.backends.cache
|
||||
import django.contrib.sessions.backends.cached_db
|
||||
import django.contrib.sessions.backends.db
|
||||
import django.contrib.sessions.backends.file
|
||||
import django.contrib.sessions.backends.signed_cookies
|
||||
import django.contrib.sessions.base_session
|
||||
import django.contrib.sessions.management.commands
|
||||
import django.contrib.sessions.management.commands.clearsessions
|
||||
import django.contrib.sessions.middleware
|
||||
import django.contrib.sessions.models
|
||||
import django.contrib.sessions.serializers
|
||||
import django.contrib.sitemaps.management.commands
|
||||
import django.contrib.sitemaps.management.commands.ping_google
|
||||
import django.contrib.sitemaps.views
|
||||
import django.contrib.sites
|
||||
import django.contrib.sites.apps
|
||||
import django.contrib.sites.management
|
||||
import django.contrib.sites.managers
|
||||
import django.contrib.sites.middleware
|
||||
import django.contrib.sites.models
|
||||
import django.contrib.sites.requests
|
||||
import django.contrib.sites.shortcuts
|
||||
import django.contrib.staticfiles.apps
|
||||
import django.contrib.staticfiles.checks
|
||||
import django.contrib.staticfiles.finders
|
||||
import django.contrib.staticfiles.handlers
|
||||
import django.contrib.staticfiles.management.commands
|
||||
import django.contrib.staticfiles.management.commands.collectstatic
|
||||
import django.contrib.staticfiles.management.commands.findstatic
|
||||
import django.contrib.staticfiles.management.commands.runserver
|
||||
import django.contrib.staticfiles.storage
|
||||
import django.contrib.staticfiles.templatetags
|
||||
import django.contrib.staticfiles.templatetags.staticfiles
|
||||
import django.contrib.staticfiles.urls
|
||||
import django.contrib.staticfiles.utils
|
||||
import django.contrib.staticfiles.views
|
||||
import django.contrib.syndication
|
||||
import django.contrib.syndication.views
|
||||
import django.core.cache.backends
|
||||
import django.core.cache.backends.base
|
||||
import django.core.cache.backends.db
|
||||
import django.core.cache.backends.dummy
|
||||
import django.core.cache.backends.filebased
|
||||
import django.core.cache.backends.locmem
|
||||
import django.core.cache.utils
|
||||
import django.core.checks.caches
|
||||
import django.core.checks.database
|
||||
import django.core.checks.messages
|
||||
import django.core.checks.model_checks
|
||||
import django.core.checks.registry
|
||||
import django.core.checks.security
|
||||
import django.core.checks.security.base
|
||||
import django.core.checks.security.csrf
|
||||
import django.core.checks.security.sessions
|
||||
import django.core.checks.templates
|
||||
import django.core.checks.urls
|
||||
import django.core.exceptions
|
||||
import django.core.files
|
||||
import django.core.files.base
|
||||
import django.core.files.images
|
||||
import django.core.files.locks
|
||||
import django.core.files.move
|
||||
import django.core.files.storage
|
||||
import django.core.files.temp
|
||||
import django.core.files.uploadedfile
|
||||
import django.core.files.uploadhandler
|
||||
import django.core.files.utils
|
||||
import django.core.handlers
|
||||
import django.core.handlers.base
|
||||
import django.core.handlers.exception
|
||||
import django.core.handlers.wsgi
|
||||
import django.core.mail
|
||||
import django.core.mail.message
|
||||
import django.core.mail.utils
|
||||
import django.core.management
|
||||
import django.core.management.base
|
||||
import django.core.management.color
|
||||
import django.core.management.sql
|
||||
import django.core.management.templates
|
||||
import django.core.management.utils
|
||||
import django.core.paginator
|
||||
import django.core.serializers
|
||||
import django.core.serializers.base
|
||||
import django.core.serializers.json
|
||||
import django.core.serializers.python
|
||||
import django.core.servers
|
||||
import django.core.servers.basehttp
|
||||
import django.core.signals
|
||||
import django.core.signing
|
||||
import django.core.validators
|
||||
import django.core.wsgi
|
||||
import django.db.backends.base
|
||||
import django.db.backends.base.base
|
||||
import django.db.backends.base.client
|
||||
import django.db.backends.base.creation
|
||||
import django.db.backends.base.features
|
||||
import django.db.backends.base.introspection
|
||||
import django.db.backends.base.operations
|
||||
import django.db.backends.base.schema
|
||||
import django.db.backends.base.validation
|
||||
import django.db.backends.ddl_references
|
||||
import django.db.backends.dummy
|
||||
import django.db.backends.dummy.base
|
||||
import django.db.backends.mysql
|
||||
import django.db.backends.mysql.client
|
||||
import django.db.backends.postgresql
|
||||
import django.db.backends.postgresql.client
|
||||
import django.db.backends.sqlite3
|
||||
import django.db.backends.sqlite3.base
|
||||
import django.db.backends.sqlite3.creation
|
||||
import django.db.backends.sqlite3.features
|
||||
import django.db.backends.sqlite3.introspection
|
||||
import django.db.backends.sqlite3.operations
|
||||
import django.db.backends.sqlite3.schema
|
||||
import django.db.backends.utils
|
||||
import django.db.migrations.autodetector
|
||||
import django.db.migrations.exceptions
|
||||
import django.db.migrations.executor
|
||||
import django.db.migrations.graph
|
||||
import django.db.migrations.loader
|
||||
import django.db.migrations.migration
|
||||
import django.db.migrations.operations
|
||||
import django.db.migrations.operations.base
|
||||
import django.db.migrations.operations.fields
|
||||
import django.db.migrations.operations.models
|
||||
import django.db.migrations.operations.special
|
||||
import django.db.migrations.operations.utils
|
||||
import django.db.migrations.optimizer
|
||||
import django.db.migrations.questioner
|
||||
import django.db.migrations.recorder
|
||||
import django.db.migrations.serializer
|
||||
import django.db.migrations.state
|
||||
import django.db.migrations.topological_sort
|
||||
import django.db.migrations.utils
|
||||
import django.db.migrations.writer
|
||||
import django.db.models.aggregates
|
||||
import django.db.models.base
|
||||
import django.db.models.deletion
|
||||
import django.db.models.expressions
|
||||
import django.db.models.fields
|
||||
import django.db.models.fields.files
|
||||
import django.db.models.fields.mixins
|
||||
import django.db.models.fields.proxy
|
||||
import django.db.models.fields.related
|
||||
import django.db.models.fields.related_descriptors
|
||||
import django.db.models.fields.related_lookups
|
||||
import django.db.models.fields.reverse_related
|
||||
import django.db.models.functions
|
||||
import django.db.models.functions.comparison
|
||||
import django.db.models.functions.datetime
|
||||
import django.db.models.functions.text
|
||||
import django.db.models.functions.window
|
||||
import django.db.models.indexes
|
||||
import django.db.models.lookups
|
||||
import django.db.models.manager
|
||||
import django.db.models.options
|
||||
import django.db.models.query
|
||||
import django.db.models.query_utils
|
||||
import django.db.models.signals
|
||||
import django.db.models.sql
|
||||
import django.db.models.sql.compiler
|
||||
import django.db.models.sql.constants
|
||||
import django.db.models.sql.datastructures
|
||||
import django.db.models.sql.query
|
||||
import django.db.models.sql.subqueries
|
||||
import django.db.models.sql.where
|
||||
import django.db.models.utils
|
||||
import django.db.transaction
|
||||
import django.db.utils
|
||||
import django.dispatch
|
||||
import django.dispatch.dispatcher
|
||||
import django.forms
|
||||
import django.forms.boundfield
|
||||
import django.forms.fields
|
||||
import django.forms.forms
|
||||
import django.forms.formsets
|
||||
import django.forms.models
|
||||
import django.forms.renderers
|
||||
import django.forms.utils
|
||||
import django.forms.widgets
|
||||
import django.http
|
||||
import django.http.cookie
|
||||
import django.http.multipartparser
|
||||
import django.http.request
|
||||
import django.http.response
|
||||
import django.middleware
|
||||
import django.middleware.cache
|
||||
import django.middleware.clickjacking
|
||||
import django.middleware.common
|
||||
import django.middleware.csrf
|
||||
import django.middleware.gzip
|
||||
import django.middleware.http
|
||||
import django.middleware.locale
|
||||
import django.middleware.security
|
||||
import django.shortcuts
|
||||
import django.template.backends
|
||||
import django.template.backends.base
|
||||
import django.template.backends.django
|
||||
import django.template.backends.dummy
|
||||
import django.template.backends.jinja2
|
||||
import django.template.backends.utils
|
||||
import django.template.base
|
||||
import django.template.context
|
||||
import django.template.context_processors
|
||||
import django.template.defaultfilters
|
||||
import django.template.defaulttags
|
||||
import django.template.engine
|
||||
import django.template.exceptions
|
||||
import django.template.library
|
||||
import django.template.loader
|
||||
import django.template.loader_tags
|
||||
import django.template.loaders
|
||||
import django.template.loaders.app_directories
|
||||
import django.template.loaders.base
|
||||
import django.template.loaders.cached
|
||||
import django.template.loaders.filesystem
|
||||
import django.template.loaders.locmem
|
||||
import django.template.response
|
||||
import django.template.smartif
|
||||
import django.template.utils
|
||||
import django.templatetags
|
||||
import django.templatetags.cache
|
||||
import django.templatetags.i18n
|
||||
import django.templatetags.l10n
|
||||
import django.templatetags.static
|
||||
import django.templatetags.tz
|
||||
import django.test
|
||||
import django.test.client
|
||||
import django.test.html
|
||||
import django.test.runner
|
||||
import django.test.selenium
|
||||
import django.test.signals
|
||||
import django.test.testcases
|
||||
import django.test.utils
|
||||
import django.urls
|
||||
import django.urls.base
|
||||
import django.urls.conf
|
||||
import django.urls.converters
|
||||
import django.urls.exceptions
|
||||
import django.urls.resolvers
|
||||
import django.urls.utils
|
||||
import django.utils._os
|
||||
import django.utils.archive
|
||||
import django.utils.autoreload
|
||||
import django.utils.baseconv
|
||||
import django.utils.cache
|
||||
import django.utils.crypto
|
||||
import django.utils.datastructures
|
||||
import django.utils.dateformat
|
||||
import django.utils.dateparse
|
||||
import django.utils.dates
|
||||
import django.utils.datetime_safe
|
||||
import django.utils.deconstruct
|
||||
import django.utils.decorators
|
||||
import django.utils.deprecation
|
||||
import django.utils.duration
|
||||
import django.utils.encoding
|
||||
import django.utils.feedgenerator
|
||||
import django.utils.formats
|
||||
import django.utils.functional
|
||||
import django.utils.html
|
||||
import django.utils.http
|
||||
import django.utils.inspect
|
||||
import django.utils.ipv6
|
||||
import django.utils.itercompat
|
||||
import django.utils.jslex
|
||||
import django.utils.log
|
||||
import django.utils.lorem_ipsum
|
||||
import django.utils.module_loading
|
||||
import django.utils.numberformat
|
||||
import django.utils.regex_helper
|
||||
import django.utils.safestring
|
||||
import django.utils.six
|
||||
import django.utils.termcolors
|
||||
import django.utils.text
|
||||
import django.utils.timesince
|
||||
import django.utils.timezone
|
||||
import django.utils.translation
|
||||
import django.utils.translation.template
|
||||
import django.utils.translation.trans_null
|
||||
import django.utils.translation.trans_real
|
||||
import django.utils.tree
|
||||
import django.utils.version
|
||||
import django.utils.xmlutils
|
||||
import django.views.csrf
|
||||
import django.views.debug
|
||||
import django.views.decorators
|
||||
import django.views.decorators.cache
|
||||
import django.views.decorators.clickjacking
|
||||
import django.views.decorators.csrf
|
||||
import django.views.decorators.debug
|
||||
import django.views.decorators.gzip
|
||||
import django.views.decorators.http
|
||||
import django.views.decorators.vary
|
||||
import django.views.defaults
|
||||
import django.views.generic
|
||||
import django.views.generic.base
|
||||
import django.views.generic.dates
|
||||
import django.views.generic.detail
|
||||
import django.views.generic.edit
|
||||
import django.views.generic.list
|
||||
import django.views.i18n
|
||||
import django.views.static
|
||||
[/CASE]
|
||||
@@ -1,201 +0,0 @@
|
||||
[CASE test_every_model_has_objects_queryset_available]
|
||||
from django.db import models
|
||||
class User(models.Model):
|
||||
pass
|
||||
reveal_type(User.objects) # N: Revealed type is 'django.db.models.manager.Manager[main.User]'
|
||||
reveal_type(User.objects.get()) # N: Revealed type is 'main.User*'
|
||||
|
||||
[CASE every_model_has_its_own_objects_queryset]
|
||||
from django.db import models
|
||||
class Parent(models.Model):
|
||||
pass
|
||||
class Child(Parent):
|
||||
pass
|
||||
reveal_type(Parent.objects) # N: Revealed type is 'django.db.models.manager.Manager[main.Parent]'
|
||||
reveal_type(Child.objects) # N: Revealed type is 'django.db.models.manager.Manager[main.Child]'
|
||||
[out]
|
||||
|
||||
[CASE if_manager_is_defined_on_model_do_not_add_objects]
|
||||
from django.db import models
|
||||
|
||||
class MyModel(models.Model):
|
||||
authors = models.Manager[MyModel]()
|
||||
reveal_type(MyModel.authors) # N: Revealed type is 'django.db.models.manager.Manager[main.MyModel]'
|
||||
MyModel.objects # E: "Type[MyModel]" has no attribute "objects"
|
||||
[out]
|
||||
|
||||
[CASE test_model_objects_attribute_present_in_case_of_model_cls_passed_as_generic_parameter]
|
||||
from typing import TypeVar, Generic, Type
|
||||
from django.db import models
|
||||
|
||||
_T = TypeVar('_T', bound=models.Model)
|
||||
class Base(Generic[_T]):
|
||||
def __init__(self, model_cls: Type[_T]):
|
||||
self.model_cls = model_cls
|
||||
reveal_type(self.model_cls._default_manager) # N: Revealed type is 'django.db.models.manager.Manager[django.db.models.base.Model]'
|
||||
class MyModel(models.Model):
|
||||
pass
|
||||
base_instance = Base(MyModel)
|
||||
reveal_type(base_instance.model_cls._default_manager) # N: Revealed type is 'django.db.models.manager.Manager[main.MyModel]'
|
||||
|
||||
class Child(Base[MyModel]):
|
||||
def method(self) -> None:
|
||||
reveal_type(self.model_cls._default_manager) # N: Revealed type is 'django.db.models.manager.Manager[main.MyModel]'
|
||||
|
||||
[CASE if_custom_manager_defined_it_is_set_to_default_manager]
|
||||
from typing import TypeVar
|
||||
from django.db import models
|
||||
_T = TypeVar('_T', bound=models.Model)
|
||||
class CustomManager(models.Manager[_T]):
|
||||
pass
|
||||
class MyModel(models.Model):
|
||||
manager = CustomManager[MyModel]()
|
||||
reveal_type(MyModel._default_manager) # N: Revealed type is 'main.CustomManager[main.MyModel]'
|
||||
|
||||
[CASE if_default_manager_name_is_passed_set_default_manager_to_it]
|
||||
from typing import TypeVar
|
||||
from django.db import models
|
||||
|
||||
_T = TypeVar('_T', bound=models.Model)
|
||||
class Manager1(models.Manager[_T]):
|
||||
pass
|
||||
class Manager2(models.Manager[_T]):
|
||||
pass
|
||||
class MyModel(models.Model):
|
||||
class Meta:
|
||||
default_manager_name = 'm2'
|
||||
m1: Manager1[MyModel]
|
||||
m2: Manager2[MyModel]
|
||||
reveal_type(MyModel._default_manager) # N: Revealed type is 'main.Manager2[main.MyModel]'
|
||||
|
||||
[CASE test_leave_as_is_if_objects_is_set_and_fill_typevars_with_outer_class]
|
||||
from django.db import models
|
||||
|
||||
class UserManager(models.Manager[MyUser]):
|
||||
def get_or_404(self) -> MyUser:
|
||||
pass
|
||||
|
||||
class MyUser(models.Model):
|
||||
objects = UserManager()
|
||||
|
||||
reveal_type(MyUser.objects) # N: Revealed type is 'main.UserManager[main.MyUser]'
|
||||
reveal_type(MyUser.objects.get()) # N: Revealed type is 'main.MyUser*'
|
||||
reveal_type(MyUser.objects.get_or_404()) # N: Revealed type is 'main.MyUser'
|
||||
|
||||
[CASE model_imported_from_different_file]
|
||||
from django.db import models
|
||||
from models.main import Inventory
|
||||
|
||||
class Band(models.Model):
|
||||
pass
|
||||
reveal_type(Inventory.objects) # N: Revealed type is 'django.db.models.manager.Manager[models.main.Inventory]'
|
||||
reveal_type(Band.objects) # N: Revealed type is 'django.db.models.manager.Manager[main.Band]'
|
||||
[file models/__init__.py]
|
||||
[file models/main.py]
|
||||
from django.db import models
|
||||
class Inventory(models.Model):
|
||||
pass
|
||||
|
||||
[CASE managers_that_defined_on_other_models_do_not_influence]
|
||||
from django.db import models
|
||||
|
||||
class AbstractPerson(models.Model):
|
||||
abstract_persons = models.Manager[AbstractPerson]()
|
||||
class PublishedBookManager(models.Manager[Book]):
|
||||
pass
|
||||
class AnnotatedBookManager(models.Manager[Book]):
|
||||
pass
|
||||
class Book(models.Model):
|
||||
title = models.CharField(max_length=50)
|
||||
published_objects = PublishedBookManager()
|
||||
annotated_objects = AnnotatedBookManager()
|
||||
|
||||
reveal_type(AbstractPerson.abstract_persons) # N: Revealed type is 'django.db.models.manager.Manager[main.AbstractPerson]'
|
||||
reveal_type(Book.published_objects) # N: Revealed type is 'main.PublishedBookManager[main.Book]'
|
||||
Book.published_objects.create(title='hello')
|
||||
reveal_type(Book.annotated_objects) # N: Revealed type is 'main.AnnotatedBookManager[main.Book]'
|
||||
Book.annotated_objects.create(title='hello')
|
||||
[out]
|
||||
|
||||
[CASE managers_inherited_from_abstract_classes_multiple_inheritance]
|
||||
from django.db import models
|
||||
class CustomManager1(models.Manager[AbstractBase1]):
|
||||
pass
|
||||
class AbstractBase1(models.Model):
|
||||
class Meta:
|
||||
abstract = True
|
||||
name = models.CharField(max_length=50)
|
||||
manager1 = CustomManager1()
|
||||
class CustomManager2(models.Manager[AbstractBase2]):
|
||||
pass
|
||||
class AbstractBase2(models.Model):
|
||||
class Meta:
|
||||
abstract = True
|
||||
value = models.CharField(max_length=50)
|
||||
restricted = CustomManager2()
|
||||
|
||||
class Child(AbstractBase1, AbstractBase2):
|
||||
pass
|
||||
[out]
|
||||
|
||||
[CASE managers_from_unrelated_models_dont_interfere]
|
||||
|
||||
from django.db import models
|
||||
|
||||
# Normal scenario where one model has a manager with an annotation of the same type as the model
|
||||
class UnrelatedModel(models.Model):
|
||||
objects = models.Manager[UnrelatedModel]()
|
||||
|
||||
class MyModel(models.Model):
|
||||
pass
|
||||
|
||||
reveal_type(UnrelatedModel.objects) # N: Revealed type is 'django.db.models.manager.Manager[main.UnrelatedModel]'
|
||||
reveal_type(UnrelatedModel.objects.first()) # N: Revealed type is 'Union[main.UnrelatedModel*, None]'
|
||||
|
||||
reveal_type(MyModel.objects) # N: Revealed type is 'django.db.models.manager.Manager[main.MyModel]'
|
||||
reveal_type(MyModel.objects.first()) # N: Revealed type is 'Union[main.MyModel*, None]'
|
||||
|
||||
# Possible to specify objects without explicit annotation of models.Manager()
|
||||
class UnrelatedModel2(models.Model):
|
||||
objects = models.Manager()
|
||||
|
||||
class MyModel2(models.Model):
|
||||
pass
|
||||
|
||||
reveal_type(UnrelatedModel2.objects) # N: Revealed type is 'django.db.models.manager.Manager[main.UnrelatedModel2]'
|
||||
reveal_type(UnrelatedModel2.objects.first()) # N: Revealed type is 'Union[main.UnrelatedModel2*, None]'
|
||||
|
||||
reveal_type(MyModel2.objects) # N: Revealed type is 'django.db.models.manager.Manager[main.MyModel2]'
|
||||
reveal_type(MyModel2.objects.first()) # N: Revealed type is 'Union[main.MyModel2*, None]'
|
||||
|
||||
|
||||
# Inheritance works
|
||||
class ParentOfMyModel3(models.Model):
|
||||
objects = models.Manager()
|
||||
|
||||
class MyModel3(ParentOfMyModel3):
|
||||
pass
|
||||
|
||||
|
||||
reveal_type(ParentOfMyModel3.objects) # N: Revealed type is 'django.db.models.manager.Manager[main.ParentOfMyModel3]'
|
||||
reveal_type(ParentOfMyModel3.objects.first()) # N: Revealed type is 'Union[main.ParentOfMyModel3*, None]'
|
||||
|
||||
reveal_type(MyModel3.objects) # N: Revealed type is 'django.db.models.manager.Manager[main.MyModel3]'
|
||||
reveal_type(MyModel3.objects.first()) # N: Revealed type is 'Union[main.MyModel3*, None]'
|
||||
|
||||
|
||||
# Inheritance works with explicit objects in child
|
||||
class ParentOfMyModel4(models.Model):
|
||||
objects = models.Manager()
|
||||
|
||||
class MyModel4(ParentOfMyModel4):
|
||||
objects = models.Manager[MyModel4]()
|
||||
|
||||
reveal_type(ParentOfMyModel4.objects) # N: Revealed type is 'django.db.models.manager.Manager[main.ParentOfMyModel4]'
|
||||
reveal_type(ParentOfMyModel4.objects.first()) # N: Revealed type is 'Union[main.ParentOfMyModel4*, None]'
|
||||
|
||||
reveal_type(MyModel4.objects) # N: Revealed type is 'django.db.models.manager.Manager[main.MyModel4]'
|
||||
reveal_type(MyModel4.objects.first()) # N: Revealed type is 'Union[main.MyModel4*, None]'
|
||||
|
||||
|
||||
[out]
|
||||
@@ -1,10 +0,0 @@
|
||||
[CASE successmessagemixin_compatible_with_formmixin]
|
||||
from django.views.generic.edit import FormMixin
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
|
||||
class FormFirstView(FormMixin, SuccessMessageMixin):
|
||||
pass
|
||||
|
||||
class SuccessMessageFirstView(FormMixin, SuccessMessageMixin):
|
||||
pass
|
||||
[/CASE]
|
||||
@@ -1,48 +0,0 @@
|
||||
[CASE registry_apps_get_model]
|
||||
from django.apps.registry import Apps
|
||||
from typing import TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from myapp.models import User
|
||||
apps = Apps()
|
||||
model_cls = apps.get_model('myapp', 'User')
|
||||
reveal_type(model_cls) # N: Revealed type is 'Type[myapp.models.User]'
|
||||
reveal_type(model_cls.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.User]'
|
||||
|
||||
[file myapp/__init__.py]
|
||||
[file myapp/models.py]
|
||||
from django.db import models
|
||||
class User(models.Model):
|
||||
pass
|
||||
|
||||
[CASE registry_apps_get_model_passed_as_variables_not_supported]
|
||||
from django.apps.registry import Apps
|
||||
from typing import TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from myapp.models import User
|
||||
app_name = 'myapp'
|
||||
model_name = 'User'
|
||||
apps = Apps()
|
||||
model_cls = apps.get_model(app_name, model_name)
|
||||
reveal_type(model_cls) # N: Revealed type is 'Type[django.db.models.base.Model]'
|
||||
|
||||
[file myapp/__init__.py]
|
||||
[file myapp/models.py]
|
||||
from django.db import models
|
||||
class User(models.Model):
|
||||
pass
|
||||
|
||||
[CASE state_apps_get_model]
|
||||
from django.db.migrations.state import StateApps
|
||||
from typing import TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from myapp.models import User
|
||||
apps = StateApps([], {})
|
||||
model_cls = apps.get_model('myapp', 'User')
|
||||
reveal_type(model_cls) # N: Revealed type is 'Type[myapp.models.User]'
|
||||
reveal_type(model_cls.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.User]'
|
||||
|
||||
[file myapp/__init__.py]
|
||||
[file myapp/models.py]
|
||||
from django.db import models
|
||||
class User(models.Model):
|
||||
pass
|
||||
@@ -1,60 +0,0 @@
|
||||
[CASE test_typechecking_for_model_subclasses]
|
||||
from django.db import models
|
||||
|
||||
class A(models.Model):
|
||||
pass
|
||||
class B(models.Model):
|
||||
b_attr = 1
|
||||
pass
|
||||
class C(A):
|
||||
pass
|
||||
|
||||
def service(a: A) -> int:
|
||||
pass
|
||||
|
||||
b_instance = B()
|
||||
service(b_instance) # E: Argument 1 to "service" has incompatible type "B"; expected "A"
|
||||
|
||||
a_instance = A()
|
||||
c_instance = C()
|
||||
service(a_instance)
|
||||
service(c_instance)
|
||||
[/CASE]
|
||||
|
||||
|
||||
[CASE fail_if_no_such_attribute_on_model]
|
||||
from django.db import models
|
||||
|
||||
class B(models.Model):
|
||||
b_attr = 1
|
||||
pass
|
||||
|
||||
b_instance = B()
|
||||
reveal_type(b_instance.b_attr) # N: Revealed type is 'builtins.int'
|
||||
|
||||
reveal_type(b_instance.non_existent_attribute)
|
||||
b_instance.non_existent_attribute = 2
|
||||
[out]
|
||||
main:10: note: Revealed type is 'Any'
|
||||
main:10: error: "B" has no attribute "non_existent_attribute"
|
||||
main:11: error: "B" has no attribute "non_existent_attribute"
|
||||
[/CASE]
|
||||
|
||||
|
||||
[CASE ignore_missing_attributes_if_setting_is_passed]
|
||||
from django.db import models
|
||||
|
||||
class B(models.Model):
|
||||
pass
|
||||
|
||||
b_instance = B()
|
||||
reveal_type(b_instance.non_existent_attribute) # N: Revealed type is 'Any'
|
||||
b_instance.non_existent_attribute = 2
|
||||
|
||||
[env MYPY_DJANGO_CONFIG=${MYPY_CWD}/mypy_django.ini]
|
||||
|
||||
[file mypy_django.ini]
|
||||
[[mypy_django_plugin]
|
||||
ignore_missing_model_attributes = True
|
||||
|
||||
[/CASE]
|
||||
@@ -1,64 +0,0 @@
|
||||
[CASE default_manager_create_is_typechecked]
|
||||
from django.db import models
|
||||
|
||||
class User(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
age = models.IntegerField()
|
||||
|
||||
User.objects.create(name='Max', age=10)
|
||||
User.objects.create(age=[]) # E: Incompatible type for "age" of "User" (got "List[Any]", expected "Union[float, int, str, Combinable]")
|
||||
[out]
|
||||
|
||||
[CASE model_recognises_parent_attributes]
|
||||
from django.db import models
|
||||
|
||||
class Parent(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
class Child(Parent):
|
||||
lastname = models.CharField(max_length=100)
|
||||
Child.objects.create(name='Maxim', lastname='Maxim2')
|
||||
[out]
|
||||
|
||||
[CASE deep_multiple_inheritance_with_create]
|
||||
from django.db import models
|
||||
|
||||
class Parent1(models.Model):
|
||||
name1 = models.CharField(max_length=50)
|
||||
class Parent2(models.Model):
|
||||
id2 = models.AutoField(primary_key=True)
|
||||
name2 = models.CharField(max_length=50)
|
||||
|
||||
class Child1(Parent1, Parent2):
|
||||
value = models.IntegerField()
|
||||
class Child4(Child1):
|
||||
value4 = models.IntegerField()
|
||||
Child4.objects.create(name1='n1', name2='n2', value=1, value4=4)
|
||||
|
||||
[CASE optional_primary_key_for_create_is_error]
|
||||
from django.db import models
|
||||
class MyModel(models.Model):
|
||||
pass
|
||||
MyModel.objects.create(id=None) # E: Incompatible type for "id" of "MyModel" (got "None", expected "int")
|
||||
|
||||
[CASE optional_related_model_for_create_is_error]
|
||||
from django.db import models
|
||||
class Publisher(models.Model):
|
||||
pass
|
||||
class Book(models.Model):
|
||||
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
|
||||
Book.objects.create(publisher=None) # E: Incompatible type for "publisher" of "Book" (got "None", expected "Union[Publisher, Combinable]")
|
||||
|
||||
[CASE when_default_for_primary_key_is_specified_allow_none_to_be_set]
|
||||
from django.db import models
|
||||
def return_int():
|
||||
return 0
|
||||
class MyModel(models.Model):
|
||||
id = models.IntegerField(primary_key=True, default=return_int)
|
||||
MyModel(id=None)
|
||||
MyModel.objects.create(id=None)
|
||||
|
||||
class MyModel2(models.Model):
|
||||
id = models.IntegerField(primary_key=True, default=None)
|
||||
MyModel2(id=None) # E: Incompatible type for "id" of "MyModel2" (got "None", expected "Union[float, int, str, Combinable]")
|
||||
MyModel2.objects.create(id=None) # E: Incompatible type for "id" of "MyModel2" (got "None", expected "Union[float, int, str, Combinable]")
|
||||
[out]
|
||||
@@ -1,177 +0,0 @@
|
||||
[CASE arguments_to_init_unexpected_attributes]
|
||||
from django.db import models
|
||||
|
||||
class MyUser(models.Model):
|
||||
pass
|
||||
user = MyUser(name=1, age=12)
|
||||
[out]
|
||||
main:5: error: Unexpected attribute "name" for model "MyUser"
|
||||
main:5: error: Unexpected attribute "age" for model "MyUser"
|
||||
|
||||
[CASE arguments_to_init_from_class_incompatible_type]
|
||||
from django.db import models
|
||||
|
||||
class MyUser(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
age = models.IntegerField()
|
||||
user = MyUser(name='hello', age=[])
|
||||
[out]
|
||||
main:6: error: Incompatible type for "age" of "MyUser" (got "List[Any]", expected "Union[float, int, str, Combinable]")
|
||||
|
||||
[CASE arguments_to_init_combined_from_base_classes]
|
||||
from django.db import models
|
||||
|
||||
class BaseUser(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
age = models.IntegerField()
|
||||
class ChildUser(BaseUser):
|
||||
lastname = models.CharField(max_length=100)
|
||||
user = ChildUser(name='Max', age=12, lastname='Lastname')
|
||||
[out]
|
||||
|
||||
[CASE fields_from_abstract_user_propagate_to_init]
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
|
||||
class MyUser(AbstractUser):
|
||||
pass
|
||||
user = MyUser(username='maxim', password='password', first_name='Max', last_name='MaxMax')
|
||||
[out]
|
||||
|
||||
[CASE generic_foreign_key_field_no_typechecking]
|
||||
from django.db import models
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
|
||||
class MyUser(models.Model):
|
||||
content_object = GenericForeignKey()
|
||||
|
||||
user = MyUser(content_object=1)
|
||||
[out]
|
||||
|
||||
[CASE pk_refers_to_primary_key_and_could_be_passed_to_init]
|
||||
from django.db import models
|
||||
|
||||
class MyUser1(models.Model):
|
||||
mypk = models.CharField(primary_key=True)
|
||||
class MyUser2(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
user2 = MyUser1(pk='hello')
|
||||
user3= MyUser2(pk=1)
|
||||
[out]
|
||||
|
||||
[CASE typechecking_of_pk]
|
||||
from django.db import models
|
||||
|
||||
class MyUser1(models.Model):
|
||||
mypk = models.IntegerField(primary_key=True)
|
||||
user = MyUser1(pk=[]) # E: Incompatible type for "pk" of "MyUser1" (got "List[Any]", expected "Union[float, int, str, Combinable]")
|
||||
[out]
|
||||
|
||||
[CASE can_set_foreign_key_by_its_primary_key]
|
||||
from django.db import models
|
||||
|
||||
class Publisher(models.Model):
|
||||
pass
|
||||
class PublisherDatetime(models.Model):
|
||||
dt_pk = models.DateTimeField(primary_key=True)
|
||||
class Book(models.Model):
|
||||
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
|
||||
publisher_dt = models.ForeignKey(PublisherDatetime, on_delete=models.CASCADE)
|
||||
|
||||
Book(publisher_id=1)
|
||||
Book(publisher_id=[]) # E: Incompatible type for "publisher_id" of "Book" (got "List[Any]", expected "Union[Combinable, int, str, None]")
|
||||
Book(publisher_dt_id=11) # E: Incompatible type for "publisher_dt_id" of "Book" (got "int", expected "Union[str, date, Combinable, None]")
|
||||
[out]
|
||||
|
||||
[CASE setting_value_to_an_array_of_ints]
|
||||
from typing import List, Tuple
|
||||
|
||||
from django.db import models
|
||||
from django.contrib.postgres.fields import ArrayField
|
||||
|
||||
class MyModel(models.Model):
|
||||
array = ArrayField(base_field=models.IntegerField())
|
||||
array_val: Tuple[int, ...] = (1,)
|
||||
MyModel(array=array_val)
|
||||
array_val2: List[int] = [1]
|
||||
MyModel(array=array_val2)
|
||||
array_val3: List[str] = ['hello']
|
||||
MyModel(array=array_val3) # E: Incompatible type for "array" of "MyModel" (got "List[str]", expected "Union[Sequence[int], Combinable]")
|
||||
[out]
|
||||
|
||||
[CASE if_no_explicit_primary_key_id_can_be_passed]
|
||||
from django.db import models
|
||||
|
||||
class MyModel(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
MyModel(id=1, name='maxim')
|
||||
[out]
|
||||
|
||||
[CASE arguments_can_be_passed_as_positionals]
|
||||
from django.db import models
|
||||
class MyModel(models.Model):
|
||||
pass
|
||||
MyModel(1)
|
||||
|
||||
class MyModel2(models.Model):
|
||||
name = models.IntegerField()
|
||||
MyModel2(1, 12)
|
||||
MyModel2(1, []) # E: Incompatible type for "name" of "MyModel2" (got "List[Any]", expected "Union[float, int, str, Combinable]")
|
||||
[out]
|
||||
|
||||
[CASE arguments_passed_as_dictionary_unpacking_are_not_supported]
|
||||
from django.db import models
|
||||
class MyModel(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
MyModel(**{'name': 'hello'})
|
||||
[out]
|
||||
|
||||
[CASE pointer_to_parent_model_is_not_supported]
|
||||
from django.db import models
|
||||
class Place(models.Model):
|
||||
pass
|
||||
class Restaurant(Place):
|
||||
pass
|
||||
place = Place()
|
||||
Restaurant(place_ptr=place)
|
||||
Restaurant(place_ptr_id=place.id)
|
||||
[out]
|
||||
|
||||
[CASE charfield_with_integer_choices]
|
||||
from django.db import models
|
||||
class MyModel(models.Model):
|
||||
day = models.CharField(max_length=3, choices=((1, 'Fri'), (2, 'Sat')))
|
||||
MyModel(day=1)
|
||||
[out]
|
||||
|
||||
[CASE if_there_is_no_data_for_base_classes_of_fields_and_ignore_unresolved_attributes_set_to_true_to_not_fail]
|
||||
from decimal import Decimal
|
||||
from django.db import models
|
||||
from fields2 import MoneyField
|
||||
|
||||
class InvoiceRow(models.Model):
|
||||
base_amount = MoneyField(max_digits=10, decimal_places=2)
|
||||
vat_rate = models.DecimalField(max_digits=10, decimal_places=2)
|
||||
InvoiceRow(1, Decimal(0), Decimal(0))
|
||||
InvoiceRow(base_amount=Decimal(0), vat_rate=Decimal(0))
|
||||
InvoiceRow.objects.create(base_amount=Decimal(0), vat_rate=Decimal(0))
|
||||
[out]
|
||||
main:3: error: Cannot find module named 'fields2'
|
||||
main:3: note: See https://mypy.readthedocs.io/en/latest/running_mypy.html#missing-imports
|
||||
|
||||
[CASE optional_primary_key_is_allowed_for_init]
|
||||
from django.db import models
|
||||
class MyModel(models.Model):
|
||||
pass
|
||||
MyModel(id=None)
|
||||
MyModel(None)
|
||||
[out]
|
||||
|
||||
[CASE optional_related_model_is_allowed_for_init]
|
||||
from django.db import models
|
||||
class Publisher(models.Model):
|
||||
pass
|
||||
class Book(models.Model):
|
||||
publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)
|
||||
Book(publisher=None)
|
||||
Book(publisher_id=None)
|
||||
[out]
|
||||
@@ -1,42 +0,0 @@
|
||||
[CASE nullable_field_with_strict_optional_true]
|
||||
from django.db import models
|
||||
class MyModel(models.Model):
|
||||
text_nullable = models.CharField(max_length=100, null=True)
|
||||
text = models.CharField(max_length=100)
|
||||
reveal_type(MyModel().text) # N: Revealed type is 'builtins.str*'
|
||||
reveal_type(MyModel().text_nullable) # N: Revealed type is 'Union[builtins.str, None]'
|
||||
MyModel().text = None # E: Incompatible types in assignment (expression has type "None", variable has type "Union[str, int, Combinable]")
|
||||
MyModel().text_nullable = None
|
||||
[out]
|
||||
|
||||
[CASE nullable_array_field]
|
||||
from django.db import models
|
||||
from django.contrib.postgres.fields import ArrayField
|
||||
|
||||
class MyModel(models.Model):
|
||||
lst = ArrayField(base_field=models.CharField(max_length=100), null=True)
|
||||
reveal_type(MyModel().lst) # N: Revealed type is 'Union[builtins.list[builtins.str], None]'
|
||||
[out]
|
||||
|
||||
[CASE nullable_foreign_key]
|
||||
from django.db import models
|
||||
|
||||
class Publisher(models.Model):
|
||||
pass
|
||||
class Book(models.Model):
|
||||
publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE, null=True)
|
||||
reveal_type(Book().publisher) # N: Revealed type is 'Union[main.Publisher, None]'
|
||||
Book().publisher = 11 # E: Incompatible types in assignment (expression has type "int", variable has type "Union[Publisher, Combinable, None]")
|
||||
[out]
|
||||
|
||||
[CASE nullable_self_foreign_key]
|
||||
from django.db import models
|
||||
class Inventory(models.Model):
|
||||
parent = models.ForeignKey('self', on_delete=models.SET_NULL, null=True)
|
||||
parent = Inventory()
|
||||
core = Inventory(parent_id=parent.id)
|
||||
reveal_type(core.parent_id) # N: Revealed type is 'Union[builtins.int, None]'
|
||||
reveal_type(core.parent) # N: Revealed type is 'Union[main.Inventory, None]'
|
||||
Inventory(parent=None)
|
||||
Inventory(parent_id=None)
|
||||
[out]
|
||||
@@ -1,389 +0,0 @@
|
||||
[CASE test_queryset_second_argument_filled_automatically]
|
||||
from django.db import models
|
||||
|
||||
class Blog(models.Model): pass
|
||||
|
||||
# QuerySet where second type argument is not specified shouldn't raise any errors
|
||||
class BlogQuerySet(models.QuerySet[Blog]):
|
||||
pass
|
||||
|
||||
blog_qs: models.QuerySet[Blog]
|
||||
reveal_type(blog_qs) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog, main.Blog]'
|
||||
[/CASE]
|
||||
|
||||
|
||||
[CASE test_queryset_methods]
|
||||
from django.db import models
|
||||
|
||||
class Blog(models.Model):
|
||||
created_at = models.DateTimeField()
|
||||
|
||||
qs = Blog.objects.all()
|
||||
reveal_type(qs) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, main.Blog*]'
|
||||
reveal_type(qs.get(id=1)) # N: Revealed type is 'main.Blog*'
|
||||
reveal_type(iter(qs)) # N: Revealed type is 'typing.Iterator[main.Blog*]'
|
||||
reveal_type(qs.iterator()) # N: Revealed type is 'typing.Iterator[main.Blog*]'
|
||||
reveal_type(qs.first()) # N: Revealed type is 'Union[main.Blog*, None]'
|
||||
reveal_type(qs.earliest()) # N: Revealed type is 'main.Blog*'
|
||||
reveal_type(qs[0]) # N: Revealed type is 'main.Blog*'
|
||||
reveal_type(qs[:9]) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, main.Blog*]'
|
||||
reveal_type(qs.in_bulk()) # N: Revealed type is 'builtins.dict[Any, main.Blog*]'
|
||||
|
||||
# .dates / .datetimes
|
||||
reveal_type(Blog.objects.dates("created_at", "day")) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, datetime.date]'
|
||||
reveal_type(Blog.objects.datetimes("created_at", "day")) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, datetime.datetime]'
|
||||
[/CASE]
|
||||
|
||||
|
||||
[CASE test_combine_querysets_with_and]
|
||||
from django.db import models
|
||||
|
||||
class Blog(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
created_at = models.DateTimeField()
|
||||
|
||||
# When ANDing QuerySets, the left-side's _Row parameter is used
|
||||
reveal_type(Blog.objects.all() & Blog.objects.values()) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, main.Blog*]'
|
||||
reveal_type(Blog.objects.values() & Blog.objects.values()) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.dict*[builtins.str, Any]]'
|
||||
reveal_type(Blog.objects.values_list('id', 'name') & Blog.objects.values()) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, Tuple[builtins.int, builtins.str]]'
|
||||
reveal_type(Blog.objects.values_list('id', 'name', named=True) & Blog.objects.values()) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, Tuple[builtins.int, builtins.str, fallback=main.Row]]'
|
||||
reveal_type(Blog.objects.values_list('id', flat=True) & Blog.objects.values()) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.int*]'
|
||||
[/CASE]
|
||||
|
||||
|
||||
[CASE test_queryset_values_method]
|
||||
from django.db import models
|
||||
|
||||
class Blog(models.Model): pass
|
||||
|
||||
values_qs = Blog.objects.values()
|
||||
reveal_type(values_qs) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.dict[builtins.str, Any]]'
|
||||
reveal_type(values_qs.all()) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.dict*[builtins.str, Any]]'
|
||||
reveal_type(values_qs.get(id=1)) # N: Revealed type is 'builtins.dict*[builtins.str, Any]'
|
||||
reveal_type(iter(values_qs)) # N: Revealed type is 'typing.Iterator[builtins.dict*[builtins.str, Any]]'
|
||||
reveal_type(values_qs.iterator()) # N: Revealed type is 'typing.Iterator[builtins.dict*[builtins.str, Any]]'
|
||||
reveal_type(values_qs.first()) # N: Revealed type is 'Union[builtins.dict*[builtins.str, Any], None]'
|
||||
reveal_type(values_qs.earliest()) # N: Revealed type is 'builtins.dict*[builtins.str, Any]'
|
||||
reveal_type(values_qs[0]) # N: Revealed type is 'builtins.dict*[builtins.str, Any]'
|
||||
reveal_type(values_qs[:9]) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.dict*[builtins.str, Any]]'
|
||||
reveal_type(values_qs.in_bulk()) # N: Revealed type is 'builtins.dict[Any, main.Blog*]'
|
||||
[/CASE]
|
||||
|
||||
|
||||
[CASE test_queryset_values_list_named_false_flat_false]
|
||||
from django.db import models
|
||||
|
||||
class Blog(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
|
||||
values_list_qs = Blog.objects.values_list('id', 'name')
|
||||
reveal_type(values_list_qs) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, Tuple[builtins.int, builtins.str]]'
|
||||
reveal_type(values_list_qs.all()) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, Tuple[builtins.int, builtins.str]]'
|
||||
reveal_type(values_list_qs.get(id=1)) # N: Revealed type is 'Tuple[builtins.int, builtins.str]'
|
||||
reveal_type(iter(values_list_qs)) # N: Revealed type is 'typing.Iterator[Tuple[builtins.int, builtins.str]]'
|
||||
reveal_type(values_list_qs.iterator()) # N: Revealed type is 'typing.Iterator[Tuple[builtins.int, builtins.str]]'
|
||||
reveal_type(values_list_qs.first()) # N: Revealed type is 'Union[Tuple[builtins.int, builtins.str], None]'
|
||||
reveal_type(values_list_qs.earliest()) # N: Revealed type is 'Tuple[builtins.int, builtins.str]'
|
||||
reveal_type(values_list_qs[0]) # N: Revealed type is 'Tuple[builtins.int, builtins.str]'
|
||||
reveal_type(values_list_qs[:9]) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, Tuple[builtins.int, builtins.str]]'
|
||||
reveal_type(values_list_qs.in_bulk()) # N: Revealed type is 'builtins.dict[Any, main.Blog*]'
|
||||
[/CASE]
|
||||
|
||||
|
||||
[CASE test_queryset_values_list_named_false_flat_true]
|
||||
from django.db import models
|
||||
|
||||
class Blog(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
|
||||
flat_values_list_qs = Blog.objects.values_list('id', flat=True)
|
||||
reveal_type(flat_values_list_qs) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.int]'
|
||||
reveal_type(flat_values_list_qs.all()) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.int*]'
|
||||
reveal_type(flat_values_list_qs.get(id=1)) # N: Revealed type is 'builtins.int*'
|
||||
reveal_type(iter(flat_values_list_qs)) # N: Revealed type is 'typing.Iterator[builtins.int*]'
|
||||
reveal_type(flat_values_list_qs.iterator()) # N: Revealed type is 'typing.Iterator[builtins.int*]'
|
||||
reveal_type(flat_values_list_qs.first()) # N: Revealed type is 'Union[builtins.int*, None]'
|
||||
reveal_type(flat_values_list_qs.earliest()) # N: Revealed type is 'builtins.int*'
|
||||
reveal_type(flat_values_list_qs[0]) # N: Revealed type is 'builtins.int*'
|
||||
reveal_type(flat_values_list_qs[:9]) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, builtins.int*]'
|
||||
reveal_type(flat_values_list_qs.in_bulk()) # N: Revealed type is 'builtins.dict[Any, main.Blog*]'
|
||||
[/CASE]
|
||||
|
||||
|
||||
[CASE test_queryset_values_list_named_true_flat_false]
|
||||
from django.db import models
|
||||
|
||||
class Blog(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
|
||||
named_values_list_qs = Blog.objects.values_list('id', named=True)
|
||||
reveal_type(named_values_list_qs) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, Tuple[builtins.int, fallback=main.Row]]'
|
||||
reveal_type(named_values_list_qs.all()) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, Tuple[builtins.int, fallback=main.Row]]'
|
||||
reveal_type(named_values_list_qs.get(id=1)) # N: Revealed type is 'Tuple[builtins.int, fallback=main.Row]'
|
||||
reveal_type(iter(named_values_list_qs)) # N: Revealed type is 'typing.Iterator[Tuple[builtins.int, fallback=main.Row]]'
|
||||
reveal_type(named_values_list_qs.iterator()) # N: Revealed type is 'typing.Iterator[Tuple[builtins.int, fallback=main.Row]]'
|
||||
reveal_type(named_values_list_qs.first()) # N: Revealed type is 'Union[Tuple[builtins.int, fallback=main.Row], None]'
|
||||
reveal_type(named_values_list_qs.earliest()) # N: Revealed type is 'Tuple[builtins.int, fallback=main.Row]'
|
||||
reveal_type(named_values_list_qs[0]) # N: Revealed type is 'Tuple[builtins.int, fallback=main.Row]'
|
||||
reveal_type(named_values_list_qs[:9]) # N: Revealed type is 'django.db.models.query.QuerySet[main.Blog*, Tuple[builtins.int, fallback=main.Row]]'
|
||||
reveal_type(named_values_list_qs.in_bulk()) # N: Revealed type is 'builtins.dict[Any, main.Blog*]'
|
||||
[/CASE]
|
||||
|
||||
|
||||
[CASE test_queryset_values_list_flat_true_custom_primary_key_get_element]
|
||||
from django.db import models
|
||||
|
||||
class Blog(models.Model):
|
||||
primary_uuid = models.UUIDField(primary_key=True)
|
||||
|
||||
# Blog has a primary key field specified, so no automatic 'id' field is expected to exist
|
||||
reveal_type(Blog.objects.values_list('id', flat=True).get()) # N: Revealed type is 'Any'
|
||||
|
||||
# Access Blog's pk (which is UUID field)
|
||||
reveal_type(Blog.objects.values_list('pk', flat=True).get()) # N: Revealed type is 'uuid.UUID*'
|
||||
[/CASE]
|
||||
|
||||
|
||||
[CASE test_queryset_values_list_flat_true_custom_primary_key_related_field]
|
||||
from django.db import models
|
||||
|
||||
class Blog(models.Model):
|
||||
primary_uuid = models.UUIDField(primary_key=True)
|
||||
|
||||
class Entry(models.Model):
|
||||
blog = models.ForeignKey(Blog, on_delete=models.CASCADE, related_name="entries")
|
||||
|
||||
# Accessing PK of model pointed to by foreign key
|
||||
reveal_type(Entry.objects.values_list('blog', flat=True).get()) # N: Revealed type is 'uuid.UUID*'
|
||||
# Alternative way of accessing PK of model pointed to by foreign key
|
||||
reveal_type(Entry.objects.values_list('blog_id', flat=True).get()) # N: Revealed type is 'uuid.UUID*'
|
||||
# Yet another (more explicit) way of accessing PK of related model
|
||||
reveal_type(Entry.objects.values_list('blog__pk', flat=True).get()) # N: Revealed type is 'uuid.UUID*'
|
||||
|
||||
# Blog has a primary key field specified, so no automatic 'id' field is expected to exist
|
||||
reveal_type(Entry.objects.values_list('blog__id', flat=True).get()) # N: Revealed type is 'Any'
|
||||
[/CASE]
|
||||
|
||||
|
||||
[CASE test_queryset_values_list_error_conditions]
|
||||
from django.db import models
|
||||
|
||||
class Blog(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
|
||||
# Emulate at type-check time the errors that Django reports
|
||||
Blog.objects.values_list('id', flat=True, named=True) # E: 'flat' and 'named' can't be used together.
|
||||
Blog.objects.values_list('id', 'name', flat=True) # E: 'flat' is not valid when values_list is called with more than one field.
|
||||
[/CASE]
|
||||
|
||||
|
||||
[CASE test_queryset_values_list_returns_tuple_of_fields]
|
||||
from django.db import models
|
||||
|
||||
class Blog(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
created_at = models.DateTimeField()
|
||||
|
||||
# values_list where parameter types are all known
|
||||
reveal_type(Blog.objects.values_list('id', 'created_at').get()) # N: Revealed type is 'Tuple[builtins.int, datetime.datetime]'
|
||||
tup = Blog.objects.values_list('id', 'created_at').get()
|
||||
reveal_type(tup[0]) # N: Revealed type is 'builtins.int'
|
||||
reveal_type(tup[1]) # N: Revealed type is 'datetime.datetime'
|
||||
tup[2] # E: Tuple index out of range
|
||||
|
||||
# values_list returning namedtuple
|
||||
reveal_type(Blog.objects.values_list('id', 'created_at', named=True).get()) # N: Revealed type is 'Tuple[builtins.int, datetime.datetime, fallback=main.Row]'
|
||||
[/CASE]
|
||||
|
||||
|
||||
[CASE test_queryset_values_list_invalid_lookups_produce_any]
|
||||
from django.db import models
|
||||
|
||||
class Blog(models.Model): pass
|
||||
class Entry(models.Model):
|
||||
blog = models.ForeignKey(Blog, on_delete=models.CASCADE, related_name="entries")
|
||||
|
||||
# Invalid lookups produce Any type rather than giving errors.
|
||||
reveal_type(Blog.objects.values_list('id', 'invalid_lookup').get()) # N: Revealed type is 'Tuple[builtins.int, Any]'
|
||||
reveal_type(Blog.objects.values_list('entries_id', flat=True).get()) # N: Revealed type is 'Any'
|
||||
reveal_type(Blog.objects.values_list('entries__foo', flat=True).get()) # N: Revealed type is 'Any'
|
||||
reveal_type(Blog.objects.values_list('+', flat=True).get()) # N: Revealed type is 'Any'
|
||||
[/CASE]
|
||||
|
||||
|
||||
[CASE test_queryset_values_list_basic_inheritance]
|
||||
from django.db import models
|
||||
|
||||
class Blog(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
created_at = models.DateTimeField()
|
||||
|
||||
class BlogChild(Blog):
|
||||
child_field = models.CharField(max_length=100)
|
||||
|
||||
# Basic inheritance
|
||||
reveal_type(BlogChild.objects.values_list('id', 'created_at', 'child_field').get()) # N: Revealed type is 'Tuple[builtins.int, datetime.datetime, builtins.str]'
|
||||
[/CASE]
|
||||
|
||||
|
||||
[CASE test_query_values_list_flat_true_plain_foreign_key]
|
||||
from django.db import models
|
||||
|
||||
class Blog(models.Model): pass
|
||||
class Entry(models.Model):
|
||||
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
|
||||
|
||||
# Foreign key
|
||||
reveal_type(Entry.objects.values_list('blog', flat=True).get()) # N: Revealed type is 'builtins.int*'
|
||||
reveal_type(Entry.objects.values_list('blog__id', flat=True).get()) # N: Revealed type is 'builtins.int*'
|
||||
reveal_type(Entry.objects.values_list('blog__pk', flat=True).get()) # N: Revealed type is 'builtins.int*'
|
||||
reveal_type(Entry.objects.values_list('blog_id', flat=True).get()) # N: Revealed type is 'builtins.int*'
|
||||
[/CASE]
|
||||
|
||||
|
||||
[CASE test_query_values_list_flat_true_custom_primary_key]
|
||||
from django.db import models
|
||||
|
||||
class Blog(models.Model):
|
||||
id = models.UUIDField(primary_key=True)
|
||||
|
||||
class Entry(models.Model):
|
||||
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
|
||||
|
||||
# Foreign key
|
||||
reveal_type(Entry.objects.values_list('blog', flat=True).get()) # N: Revealed type is 'uuid.UUID*'
|
||||
reveal_type(Entry.objects.values_list('blog__id', flat=True).get()) # N: Revealed type is 'uuid.UUID*'
|
||||
reveal_type(Entry.objects.values_list('blog__pk', flat=True).get()) # N: Revealed type is 'uuid.UUID*'
|
||||
reveal_type(Entry.objects.values_list('blog_id', flat=True).get()) # N: Revealed type is 'uuid.UUID*'
|
||||
[/CASE]
|
||||
|
||||
|
||||
[CASE test_query_values_list_flat_true_nullable_foreign_key]
|
||||
from django.db import models
|
||||
|
||||
class Blog(models.Model): pass
|
||||
class Entry(models.Model):
|
||||
nullable_blog = models.ForeignKey(Blog, on_delete=models.CASCADE, related_name="+", null=True)
|
||||
|
||||
# Foreign key (nullable=True)
|
||||
reveal_type(Entry.objects.values_list('nullable_blog', flat=True).get()) # N: Revealed type is 'Union[builtins.int, None]'
|
||||
reveal_type(Entry.objects.values_list('nullable_blog_id', flat=True).get()) # N: Revealed type is 'Union[builtins.int, None]'
|
||||
reveal_type(Entry.objects.values_list('nullable_blog__id', flat=True).get()) # N: Revealed type is 'Union[builtins.int, None]'
|
||||
reveal_type(Entry.objects.values_list('nullable_blog__pk', flat=True).get()) # N: Revealed type is 'Union[builtins.int, None]'
|
||||
[/CASE]
|
||||
|
||||
|
||||
[CASE test_query_values_list_flat_true_foreign_key_reverse_relation]
|
||||
from django.db import models
|
||||
|
||||
class Blog(models.Model): pass
|
||||
class Entry(models.Model):
|
||||
blog = models.ForeignKey(Blog, on_delete=models.CASCADE, related_name='entries')
|
||||
blog_with_related_query_name = models.ForeignKey(Blog, on_delete=models.CASCADE, related_query_name="my_related_query_name")
|
||||
title = models.CharField(max_length=100)
|
||||
|
||||
# Reverse relation of ForeignKey
|
||||
reveal_type(Blog.objects.values_list('entries', flat=True).get()) # N: Revealed type is 'builtins.int*'
|
||||
reveal_type(Blog.objects.values_list('entries__id', flat=True).get()) # N: Revealed type is 'builtins.int*'
|
||||
reveal_type(Blog.objects.values_list('entries__title', flat=True).get()) # N: Revealed type is 'builtins.str*'
|
||||
|
||||
# Reverse relation of ForeignKey (with related_query_name set)
|
||||
reveal_type(Blog.objects.values_list('my_related_query_name__id', flat=True).get()) # N: Revealed type is 'builtins.int*'
|
||||
[/CASE]
|
||||
|
||||
|
||||
[CASE test_query_values_list_flat_true_foreign_key_custom_primary_key_reverse_relation]
|
||||
from django.db import models
|
||||
|
||||
class Blog(models.Model): pass
|
||||
|
||||
class Entry(models.Model):
|
||||
id = models.UUIDField(primary_key=True)
|
||||
blog = models.ForeignKey(Blog, on_delete=models.CASCADE, related_name='entries')
|
||||
blog_with_related_query_name = models.ForeignKey(Blog, on_delete=models.CASCADE, related_query_name="my_related_query_name")
|
||||
title = models.CharField(max_length=100)
|
||||
|
||||
# Reverse relation of ForeignKey
|
||||
reveal_type(Blog.objects.values_list('entries', flat=True).get()) # N: Revealed type is 'uuid.UUID*'
|
||||
reveal_type(Blog.objects.values_list('entries__id', flat=True).get()) # N: Revealed type is 'uuid.UUID*'
|
||||
|
||||
# Reverse relation of ForeignKey (with related_query_name set)
|
||||
reveal_type(Blog.objects.values_list('my_related_query_name__id', flat=True).get()) # N: Revealed type is 'uuid.UUID*'
|
||||
[/CASE]
|
||||
|
||||
|
||||
[CASE test_queryset_values_list_and_values_behavior_with_no_fields_specified_and_accessing_unknown_attributes]
|
||||
from django.db import models
|
||||
|
||||
class Blog(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
created_at = models.DateTimeField()
|
||||
|
||||
row_named = Blog.objects.values_list('id', 'created_at', named=True).get()
|
||||
reveal_type(row_named.id) # N: Revealed type is 'builtins.int'
|
||||
reveal_type(row_named.created_at) # N: Revealed type is 'datetime.datetime'
|
||||
row_named.non_existent_field # E: "Row" has no attribute "non_existent_field"
|
||||
|
||||
|
||||
# When no fields are specified, fallback to Any
|
||||
row_named_no_fields = Blog.objects.values_list(named=True).get()
|
||||
reveal_type(row_named_no_fields) # N: Revealed type is 'Tuple[, fallback=django._NamedTupleAnyAttr]'
|
||||
|
||||
# Don't complain about access to any attribute for now
|
||||
reveal_type(row_named_no_fields.non_existent_field) # N: Revealed type is 'Any'
|
||||
row_named_no_fields.non_existent_field = 1
|
||||
|
||||
# It should still behave like a NamedTuple
|
||||
reveal_type(row_named_no_fields._asdict()) # N: Revealed type is 'builtins.dict[builtins.str, Any]'
|
||||
|
||||
|
||||
dict_row = Blog.objects.values('id', 'created_at').get()
|
||||
reveal_type(dict_row["id"]) # N: Revealed type is 'builtins.int'
|
||||
reveal_type(dict_row["created_at"]) # N: Revealed type is 'datetime.datetime'
|
||||
dict_row["non_existent_field"] # E: 'non_existent_field' is not a valid TypedDict key; expected one of ('id', 'created_at')
|
||||
dict_row.pop('created_at')
|
||||
dict_row.pop('non_existent_field') # E: 'non_existent_field' is not a valid TypedDict key; expected one of ('id', 'created_at')
|
||||
|
||||
row_dict_no_fields = Blog.objects.values().get()
|
||||
reveal_type(row_dict_no_fields) # N: Revealed type is 'builtins.dict*[builtins.str, Any]'
|
||||
reveal_type(row_dict_no_fields["non_existent_field"]) # N: Revealed type is 'Any'
|
||||
|
||||
[CASE values_with_annotate_inside_the_expressions]
|
||||
from django.db import models
|
||||
from django.db.models.functions import Lower, Upper
|
||||
|
||||
class Publisher(models.Model):
|
||||
pass
|
||||
|
||||
class Book(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE, related_name='books')
|
||||
|
||||
reveal_type(Publisher().books.values('name', lower_name=Lower('name'), upper_name=Upper('name'))) # N: Revealed type is 'django.db.models.query.QuerySet[main.Book*, TypedDict({'name'?: builtins.str, 'lower_name'?: Any, 'upper_name'?: Any})]'
|
||||
|
||||
|
||||
[CASE values_and_values_list_some_dynamic_fields]
|
||||
from django.db import models
|
||||
|
||||
class Publisher(models.Model):
|
||||
pass
|
||||
|
||||
class Book(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE, related_name='books')
|
||||
|
||||
some_dynamic_field = 'publisher'
|
||||
|
||||
# Correct Tuple field types should be filled in when string literal is used, while Any is used for dynamic fields
|
||||
reveal_type(Publisher().books.values_list('name', some_dynamic_field)) # N: Revealed type is 'django.db.models.query.QuerySet[main.Book*, Tuple[builtins.str, Any]]'
|
||||
|
||||
# Flat with dynamic fields (there is only 1), means of course Any
|
||||
reveal_type(Publisher().books.values_list(some_dynamic_field, flat=True)) # N: Revealed type is 'django.db.models.query.QuerySet[main.Book*, Any]'
|
||||
|
||||
# A NamedTuple with a fallback to Any could be implemented, but for now that's unsupported, so all
|
||||
# fields on the NamedTuple are Any for now
|
||||
reveal_type(Publisher().books.values_list('name', some_dynamic_field, named=True).name) # N: Revealed type is 'Any'
|
||||
|
||||
# A TypedDict with a fallback to Any could be implemented, but for now that's unsupported,
|
||||
# so an ordinary Dict is used for now.
|
||||
reveal_type(Publisher().books.values(some_dynamic_field, 'name')) # N: Revealed type is 'django.db.models.query.QuerySet[main.Book*, builtins.dict[builtins.str, Any]]'
|
||||
@@ -1,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]
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
[CASE test_settings_are_parsed_into_django_conf_settings]
|
||||
[env DJANGO_SETTINGS_MODULE=mysettings]
|
||||
[disable_cache]
|
||||
from django.conf import settings
|
||||
|
||||
# standard settings
|
||||
reveal_type(settings.AUTH_USER_MODEL) # N: Revealed type is 'builtins.str'
|
||||
|
||||
reveal_type(settings.ROOT_DIR) # N: Revealed type is 'builtins.str'
|
||||
reveal_type(settings.APPS_DIR) # N: Revealed type is 'pathlib.Path'
|
||||
reveal_type(settings.OBJ) # N: Revealed type is 'django.utils.functional.LazyObject'
|
||||
reveal_type(settings.NUMBERS) # N: Revealed type is 'builtins.list[builtins.str*]'
|
||||
reveal_type(settings.DICT) # N: Revealed type is 'builtins.dict[Any, Any]'
|
||||
[file base.py]
|
||||
from pathlib import Path
|
||||
ROOT_DIR = '/etc'
|
||||
APPS_DIR = Path(ROOT_DIR)
|
||||
[file mysettings.py]
|
||||
from base import *
|
||||
SECRET_KEY = 112233
|
||||
NUMBERS = ['one', 'two']
|
||||
DICT = {} # type: ignore
|
||||
from django.utils.functional import LazyObject
|
||||
OBJ = LazyObject()
|
||||
[/CASE]
|
||||
|
||||
[CASE test_settings_could_be_defined_in_different_module_and_imported_with_star]
|
||||
[env DJANGO_SETTINGS_MODULE=mysettings]
|
||||
[disable_cache]
|
||||
from django.conf import settings
|
||||
|
||||
reveal_type(settings.ROOT_DIR) # N: Revealed type is 'pathlib.Path'
|
||||
reveal_type(settings.SETUP) # N: Revealed type is 'Union[builtins.int, None]'
|
||||
reveal_type(settings.DATABASES) # N: Revealed type is 'builtins.dict[builtins.str*, builtins.str*]'
|
||||
|
||||
reveal_type(settings.LOCAL_SETTING) # N: Revealed type is 'builtins.int'
|
||||
reveal_type(settings.BASE_SETTING) # N: Revealed type is 'builtins.int'
|
||||
|
||||
[file mysettings.py]
|
||||
from local import *
|
||||
from typing import Optional
|
||||
SETUP: Optional[int] = 3
|
||||
|
||||
[file local.py]
|
||||
from base import *
|
||||
SETUP: int = 3
|
||||
DATABASES = {'default': 'mydb'}
|
||||
|
||||
LOCAL_SETTING = 1
|
||||
|
||||
[file base.py]
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
SETUP: Any = None
|
||||
ROOT_DIR = Path(__file__)
|
||||
|
||||
BASE_SETTING = 1
|
||||
|
||||
[/CASE]
|
||||
|
||||
[CASE global_settings_are_always_loaded]
|
||||
from django.conf import settings
|
||||
|
||||
reveal_type(settings.AUTH_USER_MODEL) # N: Revealed type is 'builtins.str'
|
||||
reveal_type(settings.AUTHENTICATION_BACKENDS) # N: Revealed type is 'typing.Sequence[builtins.str]'
|
||||
[/CASE]
|
||||
|
||||
[CASE test_circular_dependency_in_settings_works_if_settings_have_annotations]
|
||||
[env DJANGO_SETTINGS_MODULE=mysettings]
|
||||
[disable_cache]
|
||||
from django.conf import settings
|
||||
class Class:
|
||||
pass
|
||||
reveal_type(settings.MYSETTING) # N: Revealed type is 'builtins.int'
|
||||
reveal_type(settings.REGISTRY) # N: Revealed type is 'Union[main.Class, None]'
|
||||
reveal_type(settings.LIST) # N: Revealed type is 'builtins.list[builtins.str]'
|
||||
|
||||
[file mysettings.py]
|
||||
from typing import TYPE_CHECKING, Optional, List
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from main import Class
|
||||
|
||||
MYSETTING = 1122
|
||||
REGISTRY: Optional['Class'] = None
|
||||
LIST: List[str] = ['1', '2']
|
||||
[/CASE]
|
||||
|
||||
[CASE fail_if_there_is_no_setting]
|
||||
from django.conf import settings
|
||||
reveal_type(settings.NOT_EXISTING)
|
||||
|
||||
[env DJANGO_SETTINGS_MODULE=mysettings2]
|
||||
[disable_cache]
|
||||
[file mysettings2.py]
|
||||
[out]
|
||||
main:2: note: Revealed type is 'Any'
|
||||
main:2: error: 'Settings' object has no attribute 'NOT_EXISTING'
|
||||
[/CASE]
|
||||
@@ -1,62 +0,0 @@
|
||||
[CASE get_object_or_404_returns_proper_types]
|
||||
from django.shortcuts import get_object_or_404, get_list_or_404
|
||||
from django.db import models
|
||||
|
||||
class MyModel(models.Model):
|
||||
pass
|
||||
reveal_type(get_object_or_404(MyModel)) # N: Revealed type is 'main.MyModel*'
|
||||
reveal_type(get_object_or_404(MyModel.objects)) # N: Revealed type is 'main.MyModel*'
|
||||
reveal_type(get_object_or_404(MyModel.objects.get_queryset())) # N: Revealed type is 'main.MyModel*'
|
||||
|
||||
reveal_type(get_list_or_404(MyModel)) # N: Revealed type is 'builtins.list[main.MyModel*]'
|
||||
reveal_type(get_list_or_404(MyModel.objects)) # N: Revealed type is 'builtins.list[main.MyModel*]'
|
||||
reveal_type(get_list_or_404(MyModel.objects.get_queryset())) # N: Revealed type is 'builtins.list[main.MyModel*]'
|
||||
[/CASE]
|
||||
|
||||
[CASE get_user_model_returns_proper_class]
|
||||
[env DJANGO_SETTINGS_MODULE=mysettings]
|
||||
[disable_cache]
|
||||
from typing import TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from myapp.models import MyUser
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
UserModel = get_user_model()
|
||||
reveal_type(UserModel.objects) # N: Revealed type is 'django.db.models.manager.Manager[myapp.models.MyUser]'
|
||||
|
||||
[file mysettings.py]
|
||||
from basic import *
|
||||
INSTALLED_APPS = ('myapp',)
|
||||
|
||||
[file basic.py]
|
||||
AUTH_USER_MODEL = 'myapp.MyUser'
|
||||
|
||||
[file myapp/__init__.py]
|
||||
[file myapp/models.py]
|
||||
from django.db import models
|
||||
class MyUser(models.Model):
|
||||
pass
|
||||
[/CASE]
|
||||
|
||||
[CASE return_type_model_and_show_error_if_model_not_yet_imported]
|
||||
[env DJANGO_SETTINGS_MODULE=mysettings]
|
||||
[disable_cache]
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
UserModel = get_user_model()
|
||||
reveal_type(UserModel.objects)
|
||||
|
||||
[file mysettings.py]
|
||||
INSTALLED_APPS = ('myapp',)
|
||||
AUTH_USER_MODEL = 'myapp.MyUser'
|
||||
|
||||
[file myapp/__init__.py]
|
||||
[file myapp/models.py]
|
||||
from django.db import models
|
||||
class MyUser(models.Model):
|
||||
pass
|
||||
[out]
|
||||
main:3: error: "myapp.MyUser" model class is not imported so far. Try to import it (under if TYPE_CHECKING) at the beginning of the current file
|
||||
main:4: note: Revealed type is 'Any'
|
||||
main:4: error: "Type[Model]" has no attribute "objects"
|
||||
[/CASE]
|
||||
@@ -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]
|
||||
Reference in New Issue
Block a user