mirror of
https://github.com/davidhalter/django-stubs.git
synced 2025-12-07 12:44:29 +08:00
add support for forms, values, values_list
This commit is contained in:
@@ -23,8 +23,9 @@ class Apps:
|
|||||||
def check_models_ready(self) -> None: ...
|
def check_models_ready(self) -> None: ...
|
||||||
def get_app_configs(self) -> Iterable[AppConfig]: ...
|
def get_app_configs(self) -> Iterable[AppConfig]: ...
|
||||||
def get_app_config(self, app_label: str) -> AppConfig: ...
|
def get_app_config(self, app_label: str) -> AppConfig: ...
|
||||||
def get_models(self, include_auto_created: bool = ..., include_swapped: bool = ...) -> List[Type[Model]]: ...
|
# it's not possible to support it in plugin properly now
|
||||||
def get_model(self, app_label: str, model_name: Optional[str] = ..., require_ready: bool = ...) -> Type[Model]: ...
|
def get_models(self, include_auto_created: bool = ..., include_swapped: bool = ...) -> List[Type[Any]]: ...
|
||||||
|
def get_model(self, app_label: str, model_name: Optional[str] = ..., require_ready: bool = ...) -> Type[Any]: ...
|
||||||
def register_model(self, app_label: str, model: Type[Model]) -> None: ...
|
def register_model(self, app_label: str, model: Type[Model]) -> None: ...
|
||||||
def is_installed(self, app_name: str) -> bool: ...
|
def is_installed(self, app_name: str) -> bool: ...
|
||||||
def get_containing_app_config(self, object_name: str) -> Optional[AppConfig]: ...
|
def get_containing_app_config(self, object_name: str) -> Optional[AppConfig]: ...
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ class Query:
|
|||||||
def resolve_expression(self, query: Query, *args: Any, **kwargs: Any) -> Query: ...
|
def resolve_expression(self, query: Query, *args: Any, **kwargs: Any) -> Query: ...
|
||||||
def as_sql(self, compiler: SQLCompiler, connection: Any) -> Tuple[str, Tuple]: ...
|
def as_sql(self, compiler: SQLCompiler, connection: Any) -> Tuple[str, Tuple]: ...
|
||||||
def resolve_lookup_value(self, value: Any, can_reuse: Optional[Set[str]], allow_joins: bool) -> Any: ...
|
def resolve_lookup_value(self, value: Any, can_reuse: Optional[Set[str]], allow_joins: bool) -> Any: ...
|
||||||
|
def solve_lookup_type(self, lookup: str) -> Tuple[Sequence[str], Sequence[str], bool]: ...
|
||||||
def build_filter(
|
def build_filter(
|
||||||
self,
|
self,
|
||||||
filter_expr: Union[Dict[str, str], Tuple[str, Tuple[int, int]]],
|
filter_expr: Union[Dict[str, str], Tuple[str, Tuple[int, int]]],
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import os
|
import os
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any, Dict, Iterator, List, Optional, TYPE_CHECKING, Tuple, Type
|
from typing import Any, Dict, Iterator, List, Optional, TYPE_CHECKING, Tuple, Type, Sequence
|
||||||
|
|
||||||
|
from django.core.exceptions import FieldError
|
||||||
from django.db.models.base import Model
|
from django.db.models.base import Model
|
||||||
from django.db.models.fields.related import ForeignKey
|
from django.db.models.fields.related import ForeignKey, RelatedField
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from mypy.checker import TypeChecker
|
from mypy.checker import TypeChecker
|
||||||
from mypy.types import Instance, Type as MypyType
|
from mypy.types import Instance, Type as MypyType
|
||||||
@@ -13,6 +14,8 @@ from pytest_mypy.utils import temp_environ
|
|||||||
from django.contrib.postgres.fields import ArrayField
|
from django.contrib.postgres.fields import ArrayField
|
||||||
from django.db.models.fields import CharField, Field
|
from django.db.models.fields import CharField, Field
|
||||||
from django.db.models.fields.reverse_related import ForeignObjectRel
|
from django.db.models.fields.reverse_related import ForeignObjectRel
|
||||||
|
|
||||||
|
from django.db.models.sql.query import Query
|
||||||
from mypy_django_plugin_newsemanal.lib import helpers
|
from mypy_django_plugin_newsemanal.lib import helpers
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -53,6 +56,9 @@ def initialize_django(settings_module: str) -> Tuple['Apps', 'LazySettings']:
|
|||||||
|
|
||||||
|
|
||||||
class DjangoFieldsContext:
|
class DjangoFieldsContext:
|
||||||
|
def __init__(self, django_context: 'DjangoContext') -> None:
|
||||||
|
self.django_context = django_context
|
||||||
|
|
||||||
def get_attname(self, field: Field) -> str:
|
def get_attname(self, field: Field) -> str:
|
||||||
attname = field.attname
|
attname = field.attname
|
||||||
return attname
|
return attname
|
||||||
@@ -81,11 +87,43 @@ class DjangoFieldsContext:
|
|||||||
field_set_type = helpers.convert_any_to_type(field_set_type, argument_field_type)
|
field_set_type = helpers.convert_any_to_type(field_set_type, argument_field_type)
|
||||||
return field_set_type
|
return field_set_type
|
||||||
|
|
||||||
|
def get_field_get_type(self, api: TypeChecker, field: Field, method: str) -> MypyType:
|
||||||
|
field_info = helpers.lookup_class_typeinfo(api, field.__class__)
|
||||||
|
is_nullable = self.get_field_nullability(field, method)
|
||||||
|
if isinstance(field, RelatedField):
|
||||||
|
if method == 'values':
|
||||||
|
primary_key_field = self.django_context.get_primary_key_field(field.related_model)
|
||||||
|
return self.get_field_get_type(api, primary_key_field, method)
|
||||||
|
|
||||||
|
model_info = helpers.lookup_class_typeinfo(api, field.related_model)
|
||||||
|
return Instance(model_info, [])
|
||||||
|
else:
|
||||||
|
return helpers.get_private_descriptor_type(field_info, '_pyi_private_get_type',
|
||||||
|
is_nullable=is_nullable)
|
||||||
|
|
||||||
|
|
||||||
|
class DjangoLookupsContext:
|
||||||
|
def resolve_lookup(self, model_cls: Type[Model], lookup: str) -> Any:
|
||||||
|
query = Query(model_cls)
|
||||||
|
lookup_parts, field_parts, is_expression = query.solve_lookup_type(lookup)
|
||||||
|
if lookup_parts:
|
||||||
|
raise FieldError('Lookups not supported yet')
|
||||||
|
|
||||||
|
currently_observed_model = model_cls
|
||||||
|
current_field = None
|
||||||
|
for field_name in field_parts:
|
||||||
|
current_field = currently_observed_model._meta.get_field(field_name)
|
||||||
|
if isinstance(current_field, RelatedField):
|
||||||
|
currently_observed_model = current_field.related_model
|
||||||
|
|
||||||
|
return current_field
|
||||||
|
|
||||||
|
|
||||||
class DjangoContext:
|
class DjangoContext:
|
||||||
def __init__(self, plugin_toml_config: Optional[Dict[str, Any]]) -> None:
|
def __init__(self, plugin_toml_config: Optional[Dict[str, Any]]) -> None:
|
||||||
self.config = DjangoPluginConfig()
|
self.config = DjangoPluginConfig()
|
||||||
self.fields_context = DjangoFieldsContext()
|
self.fields_context = DjangoFieldsContext(self)
|
||||||
|
self.lookups_context = DjangoLookupsContext()
|
||||||
|
|
||||||
self.django_settings_module = None
|
self.django_settings_module = None
|
||||||
if plugin_toml_config:
|
if plugin_toml_config:
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
from typing import Dict, List, Optional, Set, Union
|
from collections import OrderedDict
|
||||||
|
from typing import Dict, List, Optional, Set, Union, Any
|
||||||
|
|
||||||
|
from mypy import checker
|
||||||
from mypy.checker import TypeChecker
|
from mypy.checker import TypeChecker
|
||||||
from mypy.nodes import Expression, MypyFile, NameExpr, SymbolNode, TypeInfo, Var, SymbolTableNode
|
from mypy.mro import calculate_mro
|
||||||
from mypy.plugin import FunctionContext, MethodContext
|
from mypy.nodes import Block, ClassDef, Expression, GDEF, MDEF, MypyFile, NameExpr, SymbolNode, SymbolTable, SymbolTableNode, \
|
||||||
from mypy.types import AnyType, Instance, NoneTyp, Type as MypyType, TypeOfAny, UnionType
|
TypeInfo, Var
|
||||||
|
from mypy.plugin import CheckerPluginInterface, FunctionContext, MethodContext
|
||||||
|
from mypy.types import AnyType, Instance, NoneTyp, TupleType, Type as MypyType, TypeOfAny, TypedDictType, UnionType
|
||||||
|
|
||||||
|
|
||||||
|
def get_django_metadata(model_info: TypeInfo) -> Dict[str, Any]:
|
||||||
|
return model_info.metadata.setdefault('django', {})
|
||||||
|
|
||||||
|
|
||||||
class IncompleteDefnException(Exception):
|
class IncompleteDefnException(Exception):
|
||||||
@@ -120,6 +128,53 @@ def get_nested_meta_node_for_current_class(info: TypeInfo) -> Optional[TypeInfo]
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def add_new_class_for_current_module(api: TypeChecker, name: str, bases: List[Instance],
|
||||||
|
fields: 'OrderedDict[str, MypyType]') -> TypeInfo:
|
||||||
|
current_module = api.scope.stack[0]
|
||||||
|
new_class_unique_name = checker.gen_unique_name(name, current_module.names)
|
||||||
|
|
||||||
|
# make new class expression
|
||||||
|
classdef = ClassDef(new_class_unique_name, Block([]))
|
||||||
|
classdef.fullname = current_module.fullname() + '.' + new_class_unique_name
|
||||||
|
|
||||||
|
# make new TypeInfo
|
||||||
|
new_typeinfo = TypeInfo(SymbolTable(), classdef, current_module.fullname())
|
||||||
|
new_typeinfo.bases = bases
|
||||||
|
calculate_mro(new_typeinfo)
|
||||||
|
new_typeinfo.calculate_metaclass_type()
|
||||||
|
|
||||||
|
def add_field_to_new_typeinfo(var: Var, is_initialized_in_class: bool = False,
|
||||||
|
is_property: bool = False) -> None:
|
||||||
|
var.info = new_typeinfo
|
||||||
|
var.is_initialized_in_class = is_initialized_in_class
|
||||||
|
var.is_property = is_property
|
||||||
|
var._fullname = new_typeinfo.fullname() + '.' + var.name()
|
||||||
|
new_typeinfo.names[var.name()] = SymbolTableNode(MDEF, var)
|
||||||
|
|
||||||
|
# add fields
|
||||||
|
var_items = [Var(item, typ) for item, typ in fields.items()]
|
||||||
|
for var_item in var_items:
|
||||||
|
add_field_to_new_typeinfo(var_item, is_property=True)
|
||||||
|
|
||||||
|
classdef.info = new_typeinfo
|
||||||
|
current_module.names[new_class_unique_name] = SymbolTableNode(GDEF, new_typeinfo, plugin_generated=True)
|
||||||
|
return new_typeinfo
|
||||||
|
|
||||||
|
|
||||||
|
def make_oneoff_named_tuple(api: TypeChecker, name: str, fields: 'OrderedDict[str, MypyType]') -> TupleType:
|
||||||
|
namedtuple_info = add_new_class_for_current_module(api, name,
|
||||||
|
bases=[api.named_generic_type('typing.NamedTuple', [])],
|
||||||
|
fields=fields)
|
||||||
|
return TupleType(list(fields.values()), fallback=Instance(namedtuple_info, []))
|
||||||
|
|
||||||
|
|
||||||
|
def make_tuple(api: 'TypeChecker', fields: List[MypyType]) -> TupleType:
|
||||||
|
# fallback for tuples is any builtins.tuple instance
|
||||||
|
fallback = api.named_generic_type('builtins.tuple',
|
||||||
|
[AnyType(TypeOfAny.special_form)])
|
||||||
|
return TupleType(fields, fallback=fallback)
|
||||||
|
|
||||||
|
|
||||||
def convert_any_to_type(typ: MypyType, referred_to_type: MypyType) -> MypyType:
|
def convert_any_to_type(typ: MypyType, referred_to_type: MypyType) -> MypyType:
|
||||||
if isinstance(typ, UnionType):
|
if isinstance(typ, UnionType):
|
||||||
converted_items = []
|
converted_items = []
|
||||||
@@ -140,3 +195,10 @@ def convert_any_to_type(typ: MypyType, referred_to_type: MypyType) -> MypyType:
|
|||||||
return referred_to_type
|
return referred_to_type
|
||||||
|
|
||||||
return typ
|
return typ
|
||||||
|
|
||||||
|
|
||||||
|
def make_typeddict(api: CheckerPluginInterface, fields: 'OrderedDict[str, MypyType]',
|
||||||
|
required_keys: Set[str]) -> TypedDictType:
|
||||||
|
object_type = api.named_generic_type('mypy_extensions._TypedDict', [])
|
||||||
|
typed_dict_type = TypedDictType(fields, required_keys=required_keys, fallback=object_type)
|
||||||
|
return typed_dict_type
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
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', {})
|
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
import os
|
import os
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from typing import Callable, Dict, List, Optional, Tuple, Type
|
from typing import Callable, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
import toml
|
import toml
|
||||||
|
from django.db.models.fields.related import RelatedField
|
||||||
from mypy.nodes import MypyFile, TypeInfo
|
from mypy.nodes import MypyFile, TypeInfo
|
||||||
from mypy.options import Options
|
from mypy.options import Options
|
||||||
from mypy.plugin import ClassDefContext, FunctionContext, Plugin, MethodContext, AttributeContext
|
from mypy.plugin import AttributeContext, ClassDefContext, FunctionContext, MethodContext, Plugin
|
||||||
from mypy.types import Type as MypyType
|
from mypy.types import Type as MypyType
|
||||||
|
|
||||||
from django.db.models.fields.related import RelatedField
|
|
||||||
from mypy_django_plugin_newsemanal.django.context import DjangoContext
|
from mypy_django_plugin_newsemanal.django.context import DjangoContext
|
||||||
from mypy_django_plugin_newsemanal.lib import fullnames, metadata
|
from mypy_django_plugin_newsemanal.lib import fullnames, helpers
|
||||||
from mypy_django_plugin_newsemanal.transformers import fields, settings, querysets, init_create
|
from mypy_django_plugin_newsemanal.transformers import fields, forms, init_create, querysets, settings
|
||||||
from mypy_django_plugin_newsemanal.transformers.models import process_model_class
|
from mypy_django_plugin_newsemanal.transformers.models import process_model_class
|
||||||
|
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ def transform_model_class(ctx: ClassDefContext,
|
|||||||
sym = ctx.api.lookup_fully_qualified_or_none(fullnames.MODEL_CLASS_FULLNAME)
|
sym = ctx.api.lookup_fully_qualified_or_none(fullnames.MODEL_CLASS_FULLNAME)
|
||||||
|
|
||||||
if sym is not None and isinstance(sym.node, TypeInfo):
|
if sym is not None and isinstance(sym.node, TypeInfo):
|
||||||
metadata.get_django_metadata(sym.node)['model_bases'][ctx.cls.fullname] = 1
|
helpers.get_django_metadata(sym.node)['model_bases'][ctx.cls.fullname] = 1
|
||||||
else:
|
else:
|
||||||
if not ctx.api.final_iteration:
|
if not ctx.api.final_iteration:
|
||||||
ctx.api.defer()
|
ctx.api.defer()
|
||||||
@@ -29,10 +29,18 @@ def transform_model_class(ctx: ClassDefContext,
|
|||||||
process_model_class(ctx, django_context)
|
process_model_class(ctx, django_context)
|
||||||
|
|
||||||
|
|
||||||
|
def transform_form_class(ctx: ClassDefContext) -> None:
|
||||||
|
sym = ctx.api.lookup_fully_qualified_or_none(fullnames.BASEFORM_CLASS_FULLNAME)
|
||||||
|
if sym is not None and isinstance(sym.node, TypeInfo):
|
||||||
|
helpers.get_django_metadata(sym.node)['baseform_bases'][ctx.cls.fullname] = 1
|
||||||
|
|
||||||
|
forms.make_meta_nested_class_inherit_from_any(ctx)
|
||||||
|
|
||||||
|
|
||||||
def add_new_manager_base(ctx: ClassDefContext) -> None:
|
def add_new_manager_base(ctx: ClassDefContext) -> None:
|
||||||
sym = ctx.api.lookup_fully_qualified_or_none(fullnames.MANAGER_CLASS_FULLNAME)
|
sym = ctx.api.lookup_fully_qualified_or_none(fullnames.MANAGER_CLASS_FULLNAME)
|
||||||
if sym is not None and isinstance(sym.node, TypeInfo):
|
if sym is not None and isinstance(sym.node, TypeInfo):
|
||||||
metadata.get_django_metadata(sym.node)['manager_bases'][ctx.cls.fullname] = 1
|
helpers.get_django_metadata(sym.node)['manager_bases'][ctx.cls.fullname] = 1
|
||||||
|
|
||||||
|
|
||||||
class NewSemanalDjangoPlugin(Plugin):
|
class NewSemanalDjangoPlugin(Plugin):
|
||||||
@@ -50,7 +58,7 @@ class NewSemanalDjangoPlugin(Plugin):
|
|||||||
def _get_current_queryset_bases(self) -> Dict[str, int]:
|
def _get_current_queryset_bases(self) -> Dict[str, int]:
|
||||||
model_sym = self.lookup_fully_qualified(fullnames.QUERYSET_CLASS_FULLNAME)
|
model_sym = self.lookup_fully_qualified(fullnames.QUERYSET_CLASS_FULLNAME)
|
||||||
if model_sym is not None and isinstance(model_sym.node, TypeInfo):
|
if model_sym is not None and isinstance(model_sym.node, TypeInfo):
|
||||||
return (metadata.get_django_metadata(model_sym.node)
|
return (helpers.get_django_metadata(model_sym.node)
|
||||||
.setdefault('queryset_bases', {fullnames.QUERYSET_CLASS_FULLNAME: 1}))
|
.setdefault('queryset_bases', {fullnames.QUERYSET_CLASS_FULLNAME: 1}))
|
||||||
else:
|
else:
|
||||||
return {}
|
return {}
|
||||||
@@ -58,7 +66,7 @@ class NewSemanalDjangoPlugin(Plugin):
|
|||||||
def _get_current_manager_bases(self) -> Dict[str, int]:
|
def _get_current_manager_bases(self) -> Dict[str, int]:
|
||||||
model_sym = self.lookup_fully_qualified(fullnames.MANAGER_CLASS_FULLNAME)
|
model_sym = self.lookup_fully_qualified(fullnames.MANAGER_CLASS_FULLNAME)
|
||||||
if model_sym is not None and isinstance(model_sym.node, TypeInfo):
|
if model_sym is not None and isinstance(model_sym.node, TypeInfo):
|
||||||
return (metadata.get_django_metadata(model_sym.node)
|
return (helpers.get_django_metadata(model_sym.node)
|
||||||
.setdefault('manager_bases', {fullnames.MANAGER_CLASS_FULLNAME: 1}))
|
.setdefault('manager_bases', {fullnames.MANAGER_CLASS_FULLNAME: 1}))
|
||||||
else:
|
else:
|
||||||
return {}
|
return {}
|
||||||
@@ -66,8 +74,18 @@ class NewSemanalDjangoPlugin(Plugin):
|
|||||||
def _get_current_model_bases(self) -> Dict[str, int]:
|
def _get_current_model_bases(self) -> Dict[str, int]:
|
||||||
model_sym = self.lookup_fully_qualified(fullnames.MODEL_CLASS_FULLNAME)
|
model_sym = self.lookup_fully_qualified(fullnames.MODEL_CLASS_FULLNAME)
|
||||||
if model_sym is not None and isinstance(model_sym.node, TypeInfo):
|
if model_sym is not None and isinstance(model_sym.node, TypeInfo):
|
||||||
return metadata.get_django_metadata(model_sym.node).setdefault('model_bases',
|
return helpers.get_django_metadata(model_sym.node).setdefault('model_bases',
|
||||||
{fullnames.MODEL_CLASS_FULLNAME: 1})
|
{fullnames.MODEL_CLASS_FULLNAME: 1})
|
||||||
|
else:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def _get_current_form_bases(self) -> Dict[str, int]:
|
||||||
|
model_sym = self.lookup_fully_qualified(fullnames.BASEFORM_CLASS_FULLNAME)
|
||||||
|
if model_sym is not None and isinstance(model_sym.node, TypeInfo):
|
||||||
|
return (helpers.get_django_metadata(model_sym.node)
|
||||||
|
.setdefault('baseform_bases', {fullnames.BASEFORM_CLASS_FULLNAME: 1,
|
||||||
|
fullnames.FORM_CLASS_FULLNAME: 1,
|
||||||
|
fullnames.MODELFORM_CLASS_FULLNAME: 1}))
|
||||||
else:
|
else:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
@@ -85,15 +103,20 @@ class NewSemanalDjangoPlugin(Plugin):
|
|||||||
if file.fullname() == 'django.conf' and self.django_context.django_settings_module:
|
if file.fullname() == 'django.conf' and self.django_context.django_settings_module:
|
||||||
return [self._new_dependency(self.django_context.django_settings_module)]
|
return [self._new_dependency(self.django_context.django_settings_module)]
|
||||||
|
|
||||||
|
# for values / values_list
|
||||||
|
if file.fullname() == 'django.db.models':
|
||||||
|
return [self._new_dependency('mypy_extensions'), self._new_dependency('typing')]
|
||||||
|
|
||||||
# for `get_user_model()`
|
# for `get_user_model()`
|
||||||
if file.fullname() == 'django.contrib.auth':
|
if self.django_context.settings:
|
||||||
auth_user_model_name = self.django_context.settings.AUTH_USER_MODEL
|
if file.fullname() == 'django.contrib.auth':
|
||||||
try:
|
auth_user_model_name = self.django_context.settings.AUTH_USER_MODEL
|
||||||
auth_user_module = self.django_context.apps_registry.get_model(auth_user_model_name).__module__
|
try:
|
||||||
except LookupError:
|
auth_user_module = self.django_context.apps_registry.get_model(auth_user_model_name).__module__
|
||||||
# get_user_model() model app is not installed
|
except LookupError:
|
||||||
return []
|
# get_user_model() model app is not installed
|
||||||
return [self._new_dependency(auth_user_module)]
|
return []
|
||||||
|
return [self._new_dependency(auth_user_module)]
|
||||||
|
|
||||||
# ensure that all mentioned to='someapp.SomeModel' are loaded with corresponding related Fields
|
# ensure that all mentioned to='someapp.SomeModel' are loaded with corresponding related Fields
|
||||||
defined_model_classes = self.django_context.model_modules.get(file.fullname())
|
defined_model_classes = self.django_context.model_modules.get(file.fullname())
|
||||||
@@ -132,9 +155,29 @@ class NewSemanalDjangoPlugin(Plugin):
|
|||||||
return partial(init_create.redefine_and_typecheck_model_init, django_context=self.django_context)
|
return partial(init_create.redefine_and_typecheck_model_init, django_context=self.django_context)
|
||||||
|
|
||||||
def get_method_hook(self, fullname: str
|
def get_method_hook(self, fullname: str
|
||||||
) -> Optional[Callable[[MethodContext], Type]]:
|
) -> Optional[Callable[[MethodContext], MypyType]]:
|
||||||
manager_classes = self._get_current_manager_bases()
|
|
||||||
class_fullname, _, method_name = fullname.rpartition('.')
|
class_fullname, _, method_name = fullname.rpartition('.')
|
||||||
|
if method_name == 'get_form_class':
|
||||||
|
info = self._get_typeinfo_or_none(class_fullname)
|
||||||
|
if info and info.has_base(fullnames.FORM_MIXIN_CLASS_FULLNAME):
|
||||||
|
return forms.extract_proper_type_for_get_form_class
|
||||||
|
|
||||||
|
if method_name == 'get_form':
|
||||||
|
info = self._get_typeinfo_or_none(class_fullname)
|
||||||
|
if info and info.has_base(fullnames.FORM_MIXIN_CLASS_FULLNAME):
|
||||||
|
return forms.extract_proper_type_for_get_form
|
||||||
|
|
||||||
|
if method_name == 'values':
|
||||||
|
model_info = self._get_typeinfo_or_none(class_fullname)
|
||||||
|
if model_info and model_info.has_base(fullnames.QUERYSET_CLASS_FULLNAME):
|
||||||
|
return partial(querysets.extract_proper_type_queryset_values, django_context=self.django_context)
|
||||||
|
|
||||||
|
if method_name == 'values_list':
|
||||||
|
model_info = self._get_typeinfo_or_none(class_fullname)
|
||||||
|
if model_info and model_info.has_base(fullnames.QUERYSET_CLASS_FULLNAME):
|
||||||
|
return partial(querysets.extract_proper_type_queryset_values_list, django_context=self.django_context)
|
||||||
|
|
||||||
|
manager_classes = self._get_current_manager_bases()
|
||||||
if class_fullname in manager_classes and method_name == 'create':
|
if class_fullname in manager_classes and method_name == 'create':
|
||||||
return partial(init_create.redefine_and_typecheck_model_create, django_context=self.django_context)
|
return partial(init_create.redefine_and_typecheck_model_create, django_context=self.django_context)
|
||||||
|
|
||||||
@@ -146,6 +189,9 @@ class NewSemanalDjangoPlugin(Plugin):
|
|||||||
if fullname in self._get_current_manager_bases():
|
if fullname in self._get_current_manager_bases():
|
||||||
return add_new_manager_base
|
return add_new_manager_base
|
||||||
|
|
||||||
|
if fullname in self._get_current_form_bases():
|
||||||
|
return transform_form_class
|
||||||
|
|
||||||
def get_attribute_hook(self, fullname: str
|
def get_attribute_hook(self, fullname: str
|
||||||
) -> Optional[Callable[[AttributeContext], MypyType]]:
|
) -> Optional[Callable[[AttributeContext], MypyType]]:
|
||||||
class_name, _, attr_name = fullname.rpartition('.')
|
class_name, _, attr_name = fullname.rpartition('.')
|
||||||
@@ -153,12 +199,6 @@ class NewSemanalDjangoPlugin(Plugin):
|
|||||||
return partial(settings.get_type_of_settings_attribute,
|
return partial(settings.get_type_of_settings_attribute,
|
||||||
django_context=self.django_context)
|
django_context=self.django_context)
|
||||||
|
|
||||||
# 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):
|
def plugin(version):
|
||||||
return NewSemanalDjangoPlugin
|
return NewSemanalDjangoPlugin
|
||||||
|
|||||||
50
mypy_django_plugin_newsemanal/transformers/forms.py
Normal file
50
mypy_django_plugin_newsemanal/transformers/forms.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from mypy.plugin import ClassDefContext, MethodContext
|
||||||
|
from mypy.types import CallableType, Instance, NoneTyp, Type as MypyType, TypeType
|
||||||
|
|
||||||
|
from mypy_django_plugin.lib import helpers
|
||||||
|
|
||||||
|
|
||||||
|
def make_meta_nested_class_inherit_from_any(ctx: ClassDefContext) -> None:
|
||||||
|
meta_node = helpers.get_nested_meta_node_for_current_class(ctx.cls.info)
|
||||||
|
if meta_node is None:
|
||||||
|
if not ctx.api.final_iteration:
|
||||||
|
ctx.api.defer()
|
||||||
|
else:
|
||||||
|
meta_node.fallback_to_any = True
|
||||||
|
|
||||||
|
|
||||||
|
def get_specified_form_class(object_type: Instance) -> Optional[TypeType]:
|
||||||
|
form_class_sym = object_type.type.get('form_class')
|
||||||
|
if form_class_sym and isinstance(form_class_sym.type, CallableType):
|
||||||
|
return TypeType(form_class_sym.type.ret_type)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def extract_proper_type_for_get_form(ctx: MethodContext) -> MypyType:
|
||||||
|
object_type = ctx.type
|
||||||
|
assert isinstance(object_type, Instance)
|
||||||
|
|
||||||
|
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):
|
||||||
|
form_class_type = get_specified_form_class(object_type)
|
||||||
|
|
||||||
|
if isinstance(form_class_type, TypeType) and isinstance(form_class_type.item, Instance):
|
||||||
|
return form_class_type.item
|
||||||
|
|
||||||
|
if isinstance(form_class_type, CallableType) and isinstance(form_class_type.ret_type, Instance):
|
||||||
|
return form_class_type.ret_type
|
||||||
|
|
||||||
|
return ctx.default_return_type
|
||||||
|
|
||||||
|
|
||||||
|
def extract_proper_type_for_get_form_class(ctx: MethodContext) -> MypyType:
|
||||||
|
object_type = ctx.type
|
||||||
|
assert isinstance(object_type, Instance)
|
||||||
|
|
||||||
|
form_class_type = get_specified_form_class(object_type)
|
||||||
|
if form_class_type is None:
|
||||||
|
return ctx.default_return_type
|
||||||
|
|
||||||
|
return form_class_type
|
||||||
@@ -3,15 +3,14 @@ from abc import ABCMeta, abstractmethod
|
|||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
from django.db.models.fields.related import ForeignKey
|
from django.db.models.fields.related import ForeignKey
|
||||||
|
from django.db.models.fields.reverse_related import ManyToManyRel, ManyToOneRel, OneToOneRel
|
||||||
from mypy.newsemanal.semanal import NewSemanticAnalyzer
|
from mypy.newsemanal.semanal import NewSemanticAnalyzer
|
||||||
from mypy.nodes import ClassDef, MDEF, SymbolTableNode, TypeInfo, Var
|
from mypy.nodes import ClassDef, MDEF, SymbolTableNode, TypeInfo, Var
|
||||||
from mypy.plugin import ClassDefContext
|
from mypy.plugin import ClassDefContext
|
||||||
from mypy.types import Instance
|
from mypy.types import Instance
|
||||||
|
|
||||||
from django.db.models.fields import Field
|
|
||||||
from django.db.models.fields.reverse_related import ManyToOneRel, OneToOneRel, ManyToManyRel
|
|
||||||
from mypy_django_plugin_newsemanal.django.context import DjangoContext
|
from mypy_django_plugin_newsemanal.django.context import DjangoContext
|
||||||
from mypy_django_plugin_newsemanal.lib import helpers, fullnames
|
from mypy_django_plugin_newsemanal.lib import fullnames, helpers
|
||||||
from mypy_django_plugin_newsemanal.transformers import fields
|
from mypy_django_plugin_newsemanal.transformers import fields
|
||||||
from mypy_django_plugin_newsemanal.transformers.fields import get_field_descriptor_types
|
from mypy_django_plugin_newsemanal.transformers.fields import get_field_descriptor_types
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
from mypy.plugin import AnalyzeTypeContext, FunctionContext
|
from collections import OrderedDict
|
||||||
|
from typing import Optional, Tuple, Type
|
||||||
|
|
||||||
|
from django.core.exceptions import FieldError
|
||||||
|
from django.db.models.base import Model
|
||||||
|
from django.db.models.fields.related import ForeignKey
|
||||||
|
from mypy.nodes import NameExpr
|
||||||
|
from mypy.plugin import AnalyzeTypeContext, FunctionContext, MethodContext
|
||||||
from mypy.types import AnyType, Instance, Type as MypyType, TypeOfAny
|
from mypy.types import AnyType, Instance, Type as MypyType, TypeOfAny
|
||||||
|
|
||||||
|
from mypy_django_plugin_newsemanal.django.context import DjangoContext
|
||||||
from mypy_django_plugin_newsemanal.lib import fullnames, helpers
|
from mypy_django_plugin_newsemanal.lib import fullnames, helpers
|
||||||
|
|
||||||
|
|
||||||
@@ -37,3 +45,122 @@ def determine_proper_manager_type(ctx: FunctionContext) -> MypyType:
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
return helpers.reparametrize_instance(ret, [Instance(outer_model_info, [])])
|
return helpers.reparametrize_instance(ret, [Instance(outer_model_info, [])])
|
||||||
|
|
||||||
|
|
||||||
|
def get_lookup_field_get_type(ctx: MethodContext, django_context: DjangoContext, model_cls: Type[Model],
|
||||||
|
lookup: str, method: str) -> Optional[Tuple[str, MypyType]]:
|
||||||
|
try:
|
||||||
|
lookup_field = django_context.lookups_context.resolve_lookup(model_cls, lookup)
|
||||||
|
except FieldError as exc:
|
||||||
|
ctx.api.fail(exc.args[0], ctx.context)
|
||||||
|
return None
|
||||||
|
|
||||||
|
field_get_type = django_context.fields_context.get_field_get_type(ctx.api, lookup_field, method)
|
||||||
|
return lookup_field.attname, field_get_type
|
||||||
|
|
||||||
|
|
||||||
|
def get_values_list_row_type(ctx: MethodContext, django_context: DjangoContext, model_cls: Type[Model],
|
||||||
|
flat: bool, named: bool) -> MypyType:
|
||||||
|
field_lookups = [expr.value for expr in ctx.args[0]]
|
||||||
|
if len(field_lookups) == 0:
|
||||||
|
if flat:
|
||||||
|
primary_key_field = django_context.get_primary_key_field(model_cls)
|
||||||
|
_, field_get_type = get_lookup_field_get_type(ctx, django_context, model_cls,
|
||||||
|
primary_key_field.attname, 'values_list')
|
||||||
|
return field_get_type
|
||||||
|
elif named:
|
||||||
|
column_types = OrderedDict()
|
||||||
|
for field in django_context.get_model_fields(model_cls):
|
||||||
|
field_get_type = django_context.fields_context.get_field_get_type(ctx.api, field, 'values_list')
|
||||||
|
column_types[field.attname] = field_get_type
|
||||||
|
return helpers.make_oneoff_named_tuple(ctx.api, 'Row', column_types)
|
||||||
|
else:
|
||||||
|
# flat=False, named=False, all fields
|
||||||
|
field_lookups = []
|
||||||
|
for field in model_cls._meta.get_fields():
|
||||||
|
field_lookups.append(field.attname)
|
||||||
|
|
||||||
|
if len(field_lookups) > 1 and flat:
|
||||||
|
ctx.api.fail("'flat' is not valid when 'values_list' is called with more than one field", ctx.context)
|
||||||
|
return AnyType(TypeOfAny.from_error)
|
||||||
|
|
||||||
|
column_types = OrderedDict()
|
||||||
|
for field_lookup in field_lookups:
|
||||||
|
result = get_lookup_field_get_type(ctx, django_context, model_cls, field_lookup, 'values_list')
|
||||||
|
if result is None:
|
||||||
|
return AnyType(TypeOfAny.from_error)
|
||||||
|
field_name, field_get_type = result
|
||||||
|
column_types[field_name] = field_get_type
|
||||||
|
|
||||||
|
if flat:
|
||||||
|
assert len(column_types) == 1
|
||||||
|
row_type = next(iter(column_types.values()))
|
||||||
|
elif named:
|
||||||
|
row_type = helpers.make_oneoff_named_tuple(ctx.api, 'Row', column_types)
|
||||||
|
else:
|
||||||
|
row_type = helpers.make_tuple(ctx.api, list(column_types.values()))
|
||||||
|
|
||||||
|
return row_type
|
||||||
|
|
||||||
|
|
||||||
|
def extract_proper_type_queryset_values_list(ctx: MethodContext, django_context: DjangoContext) -> MypyType:
|
||||||
|
assert isinstance(ctx.type, Instance)
|
||||||
|
assert isinstance(ctx.type.args[0], Instance)
|
||||||
|
|
||||||
|
model_type = ctx.type.args[0]
|
||||||
|
model_cls = django_context.get_model_class_by_fullname(model_type.type.fullname())
|
||||||
|
if model_cls is None:
|
||||||
|
return ctx.default_return_type
|
||||||
|
|
||||||
|
flat_expr = helpers.get_call_argument_by_name(ctx, 'flat')
|
||||||
|
if flat_expr is not None and isinstance(flat_expr, NameExpr):
|
||||||
|
flat = helpers.parse_bool(flat_expr)
|
||||||
|
else:
|
||||||
|
flat = False
|
||||||
|
|
||||||
|
named_expr = helpers.get_call_argument_by_name(ctx, 'named')
|
||||||
|
if named_expr is not None and isinstance(named_expr, NameExpr):
|
||||||
|
named = helpers.parse_bool(named_expr)
|
||||||
|
else:
|
||||||
|
named = False
|
||||||
|
|
||||||
|
if flat and named:
|
||||||
|
ctx.api.fail("'flat' and 'named' can't be used together", ctx.context)
|
||||||
|
return helpers.reparametrize_instance(ctx.default_return_type, [model_type, AnyType(TypeOfAny.from_error)])
|
||||||
|
|
||||||
|
row_type = get_values_list_row_type(ctx, django_context, model_cls,
|
||||||
|
flat=flat, named=named)
|
||||||
|
return helpers.reparametrize_instance(ctx.default_return_type, [model_type, row_type])
|
||||||
|
|
||||||
|
|
||||||
|
def extract_proper_type_queryset_values(ctx: MethodContext, django_context: DjangoContext) -> MypyType:
|
||||||
|
assert isinstance(ctx.type, Instance)
|
||||||
|
assert isinstance(ctx.type.args[0], Instance)
|
||||||
|
|
||||||
|
model_type = ctx.type.args[0]
|
||||||
|
model_cls = django_context.get_model_class_by_fullname(model_type.type.fullname())
|
||||||
|
if model_cls is None:
|
||||||
|
return ctx.default_return_type
|
||||||
|
|
||||||
|
field_lookups = [expr.value for expr in ctx.args[0]]
|
||||||
|
if len(field_lookups) == 0:
|
||||||
|
for field in model_cls._meta.get_fields():
|
||||||
|
field_lookups.append(field.attname)
|
||||||
|
|
||||||
|
column_types = OrderedDict()
|
||||||
|
for field_lookup in field_lookups:
|
||||||
|
try:
|
||||||
|
lookup_field = django_context.lookups_context.resolve_lookup(model_cls, field_lookup)
|
||||||
|
except FieldError as exc:
|
||||||
|
ctx.api.fail(exc.args[0], ctx.context)
|
||||||
|
return helpers.reparametrize_instance(ctx.default_return_type, [model_type, AnyType(TypeOfAny.from_error)])
|
||||||
|
|
||||||
|
field_get_type = django_context.fields_context.get_field_get_type(ctx.api, lookup_field, 'values')
|
||||||
|
field_name = lookup_field.attname
|
||||||
|
if isinstance(lookup_field, ForeignKey) and field_lookup == lookup_field.name:
|
||||||
|
field_name = lookup_field.name
|
||||||
|
|
||||||
|
column_types[field_name] = field_get_type
|
||||||
|
|
||||||
|
row_type = helpers.make_typeddict(ctx.api, column_types, set(column_types.keys()))
|
||||||
|
return helpers.reparametrize_instance(ctx.default_return_type, [model_type, row_type])
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
- case: queryset_basic_methods_return_type
|
||||||
|
main: |
|
||||||
|
from myapp.models import Blog
|
||||||
|
|
||||||
|
qs = Blog.objects.all()
|
||||||
|
reveal_type(qs) # N: Revealed type is 'django.db.models.query.QuerySet[myapp.models.Blog*, myapp.models.Blog*]'
|
||||||
|
reveal_type(qs.get(id=1)) # N: Revealed type is 'myapp.models.Blog*'
|
||||||
|
reveal_type(iter(qs)) # N: Revealed type is 'typing.Iterator[myapp.models.Blog*]'
|
||||||
|
reveal_type(qs.iterator()) # N: Revealed type is 'typing.Iterator[myapp.models.Blog*]'
|
||||||
|
reveal_type(qs.first()) # N: Revealed type is '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]'
|
||||||
|
|
||||||
|
# AND-ing QuerySets
|
||||||
|
reveal_type(Blog.objects.all() & Blog.objects.all()) # N: Revealed type is 'django.db.models.query.QuerySet[myapp.models.Blog*, myapp.models.Blog*]'
|
||||||
|
installed_apps:
|
||||||
|
- myapp
|
||||||
|
files:
|
||||||
|
- path: myapp/__init__.py
|
||||||
|
- path: myapp/models.py
|
||||||
|
content: |
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
class Blog(models.Model):
|
||||||
|
created_at = models.DateTimeField()
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
- case: queryset_values_method_returns_typeddict
|
||||||
|
main: |
|
||||||
|
from myapp.models import Blog
|
||||||
|
values = Blog.objects.values('num_posts', 'text').get()
|
||||||
|
reveal_type(values) # N: Revealed type is 'TypedDict({'num_posts': builtins.int, 'text': builtins.str})'
|
||||||
|
reveal_type(values["num_posts"]) # N: Revealed type is 'builtins.int'
|
||||||
|
reveal_type(values["text"]) # N: Revealed type is 'builtins.str'
|
||||||
|
installed_apps:
|
||||||
|
- myapp
|
||||||
|
files:
|
||||||
|
- path: myapp/__init__.py
|
||||||
|
- path: myapp/models.py
|
||||||
|
content: |
|
||||||
|
from django.db import models
|
||||||
|
class Blog(models.Model):
|
||||||
|
num_posts = models.IntegerField()
|
||||||
|
text = models.CharField(max_length=100)
|
||||||
|
|
||||||
|
- case: queryset_values_all_values
|
||||||
|
main: |
|
||||||
|
from myapp.models import Blog
|
||||||
|
all_values_dict = Blog.objects.values().get()
|
||||||
|
reveal_type(all_values_dict) # N: Revealed type is 'TypedDict({'id': builtins.int, 'num_posts': builtins.int, 'text': builtins.str})'
|
||||||
|
reveal_type(all_values_dict["id"]) # N: Revealed type is 'builtins.int'
|
||||||
|
reveal_type(all_values_dict["num_posts"]) # N: Revealed type is 'builtins.int'
|
||||||
|
reveal_type(all_values_dict["text"]) # N: Revealed type is 'builtins.str'
|
||||||
|
installed_apps:
|
||||||
|
- myapp
|
||||||
|
files:
|
||||||
|
- path: myapp/__init__.py
|
||||||
|
- path: myapp/models.py
|
||||||
|
content: |
|
||||||
|
from django.db import models
|
||||||
|
class Blog(models.Model):
|
||||||
|
num_posts = models.IntegerField()
|
||||||
|
text = models.CharField(max_length=100)
|
||||||
|
|
||||||
|
- case: queryset_foreign_key_object_always_a_primary_key
|
||||||
|
main: |
|
||||||
|
from myapp.models import Blog
|
||||||
|
values1 = Blog.objects.values('publisher').get()
|
||||||
|
reveal_type(values1) # N: Revealed type is 'TypedDict({'publisher': builtins.int})'
|
||||||
|
reveal_type(values1['publisher']) # N: Revealed type is 'builtins.int'
|
||||||
|
|
||||||
|
values2 = Blog.objects.values('publisher_id').get()
|
||||||
|
reveal_type(values2) # N: Revealed type is 'TypedDict({'publisher_id': builtins.int})'
|
||||||
|
reveal_type(values2["publisher_id"]) # N: Revealed type is 'builtins.int'
|
||||||
|
|
||||||
|
# all values return _id version
|
||||||
|
all_values = Blog.objects.values().get()
|
||||||
|
reveal_type(all_values) # N: Revealed type is 'TypedDict({'id': builtins.int, 'publisher_id': builtins.int})'
|
||||||
|
installed_apps:
|
||||||
|
- myapp
|
||||||
|
files:
|
||||||
|
- path: myapp/__init__.py
|
||||||
|
- path: myapp/models.py
|
||||||
|
content: |
|
||||||
|
from django.db import models
|
||||||
|
class Publisher(models.Model):
|
||||||
|
pass
|
||||||
|
class Blog(models.Model):
|
||||||
|
publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE)
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
- case: values_list_simple_field
|
||||||
|
main: |
|
||||||
|
from myapp.models import MyUser
|
||||||
|
reveal_type(MyUser.objects.values_list('name').get()) # N: Revealed type is 'Tuple[builtins.str]'
|
||||||
|
reveal_type(MyUser.objects.values_list('id', 'name').get()) # N: Revealed type is 'Tuple[builtins.int, builtins.str]'
|
||||||
|
|
||||||
|
values_tuple = MyUser.objects.values_list('name', 'age').get()
|
||||||
|
reveal_type(values_tuple[0]) # N: Revealed type is 'builtins.str'
|
||||||
|
reveal_type(values_tuple[1]) # N: Revealed type is 'builtins.int'
|
||||||
|
|
||||||
|
# no fields specified return all fields
|
||||||
|
all_values_tuple = MyUser.objects.values_list().get()
|
||||||
|
reveal_type(all_values_tuple) # N: Revealed type is 'Tuple[builtins.int, builtins.str, builtins.int]'
|
||||||
|
installed_apps:
|
||||||
|
- myapp
|
||||||
|
files:
|
||||||
|
- path: myapp/__init__.py
|
||||||
|
- path: myapp/models.py
|
||||||
|
content: |
|
||||||
|
from django.db import models
|
||||||
|
class MyUser(models.Model):
|
||||||
|
name = models.CharField(max_length=100)
|
||||||
|
age = models.IntegerField()
|
||||||
|
|
||||||
|
- case: values_list_related_model_fields
|
||||||
|
main: |
|
||||||
|
from myapp.models import Post
|
||||||
|
values_tuple = Post.objects.values_list('blog', 'blog__num_posts', 'blog__publisher', 'blog__publisher__name').get()
|
||||||
|
reveal_type(values_tuple[0]) # N: Revealed type is 'myapp.models.Blog'
|
||||||
|
reveal_type(values_tuple[1]) # N: Revealed type is 'builtins.int'
|
||||||
|
reveal_type(values_tuple[2]) # N: Revealed type is 'myapp.models.Publisher'
|
||||||
|
reveal_type(values_tuple[3]) # N: Revealed type is 'builtins.str'
|
||||||
|
installed_apps:
|
||||||
|
- myapp
|
||||||
|
files:
|
||||||
|
- path: myapp/__init__.py
|
||||||
|
- path: myapp/models.py
|
||||||
|
content: |
|
||||||
|
from django.db import models
|
||||||
|
class Publisher(models.Model):
|
||||||
|
name = models.CharField(max_length=100)
|
||||||
|
class Blog(models.Model):
|
||||||
|
num_posts = models.IntegerField()
|
||||||
|
publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE)
|
||||||
|
class Post(models.Model):
|
||||||
|
blog = models.ForeignKey(to=Blog, on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
- case: values_list_flat_true
|
||||||
|
main: |
|
||||||
|
from myapp.models import MyUser, MyUser2
|
||||||
|
reveal_type(MyUser.objects.values_list('name', flat=True).get()) # N: Revealed type is 'builtins.str*'
|
||||||
|
reveal_type(MyUser.objects.values_list('name', 'age', flat=True).get())
|
||||||
|
|
||||||
|
# flat=True without specified fields returns primary key values
|
||||||
|
reveal_type(MyUser.objects.values_list(flat=True)[0]) # N: Revealed type is 'builtins.int*'
|
||||||
|
reveal_type(MyUser2.objects.values_list(flat=True)[0]) # N: Revealed type is 'builtins.str*'
|
||||||
|
out: |
|
||||||
|
main:3: error: 'flat' is not valid when 'values_list' is called with more than one field
|
||||||
|
main:3: note: Revealed type is 'Any'
|
||||||
|
installed_apps:
|
||||||
|
- myapp
|
||||||
|
files:
|
||||||
|
- path: myapp/__init__.py
|
||||||
|
- path: myapp/models.py
|
||||||
|
content: |
|
||||||
|
from django.db import models
|
||||||
|
class MyUser(models.Model):
|
||||||
|
name = models.CharField(max_length=100)
|
||||||
|
age = models.IntegerField()
|
||||||
|
class MyUser2(models.Model):
|
||||||
|
name = models.CharField(max_length=100, primary_key=True)
|
||||||
|
|
||||||
|
- case: values_list_named_true
|
||||||
|
main: |
|
||||||
|
from myapp.models import MyUser
|
||||||
|
values_named_tuple = MyUser.objects.values_list('name', 'age', named=True).get()
|
||||||
|
reveal_type(values_named_tuple) # N: Revealed type is 'Tuple[builtins.str, builtins.int, fallback=main.Row]'
|
||||||
|
reveal_type(values_named_tuple.name) # N: Revealed type is 'builtins.str'
|
||||||
|
reveal_type(values_named_tuple.age) # N: Revealed type is 'builtins.int'
|
||||||
|
|
||||||
|
# no fields specified, returns all fields namedtuple
|
||||||
|
all_values_named_tuple = MyUser.objects.values_list(named=True).get()
|
||||||
|
reveal_type(all_values_named_tuple.id) # N: Revealed type is 'builtins.int'
|
||||||
|
reveal_type(all_values_named_tuple.name) # N: Revealed type is 'builtins.str'
|
||||||
|
reveal_type(all_values_named_tuple.age) # N: Revealed type is 'builtins.int'
|
||||||
|
reveal_type(all_values_named_tuple.is_admin) # N: Revealed type is 'builtins.bool'
|
||||||
|
installed_apps:
|
||||||
|
- myapp
|
||||||
|
files:
|
||||||
|
- path: myapp/__init__.py
|
||||||
|
- path: myapp/models.py
|
||||||
|
content: |
|
||||||
|
from django.db import models
|
||||||
|
class MyUser(models.Model):
|
||||||
|
name = models.CharField(max_length=100)
|
||||||
|
age = models.IntegerField()
|
||||||
|
is_admin = models.BooleanField()
|
||||||
|
|
||||||
|
- case: values_list_flat_true_named_true_error
|
||||||
|
main: |
|
||||||
|
from myapp.models import MyUser
|
||||||
|
reveal_type(MyUser.objects.values_list('name', flat=True, named=True).get())
|
||||||
|
out: |
|
||||||
|
main:2: error: 'flat' and 'named' can't be used together
|
||||||
|
main:2: note: Revealed type is 'Any'
|
||||||
|
installed_apps:
|
||||||
|
- myapp
|
||||||
|
files:
|
||||||
|
- path: myapp/__init__.py
|
||||||
|
- path: myapp/models.py
|
||||||
|
content: |
|
||||||
|
from django.db import models
|
||||||
|
class MyUser(models.Model):
|
||||||
|
name = models.CharField(max_length=100)
|
||||||
|
|
||||||
|
- case: invalid_lookups
|
||||||
|
main: |
|
||||||
|
from myapp.models import Blog
|
||||||
|
reveal_type(Blog.objects.values_list('unknown').get())
|
||||||
|
reveal_type(Blog.objects.values_list('unknown', flat=True).get())
|
||||||
|
reveal_type(Blog.objects.values_list('unknown', named=True).get())
|
||||||
|
reveal_type(Blog.objects.values_list('publisher__unknown').get())
|
||||||
|
out: |
|
||||||
|
main:2: error: Cannot resolve keyword 'unknown' into field. Choices are: id, publisher, publisher_id
|
||||||
|
main:2: note: Revealed type is 'Any'
|
||||||
|
main:3: error: Cannot resolve keyword 'unknown' into field. Choices are: id, publisher, publisher_id
|
||||||
|
main:3: note: Revealed type is 'Any'
|
||||||
|
main:4: error: Cannot resolve keyword 'unknown' into field. Choices are: id, publisher, publisher_id
|
||||||
|
main:4: note: Revealed type is 'Any'
|
||||||
|
main:5: error: Lookups not supported yet
|
||||||
|
main:5: note: Revealed type is 'Any'
|
||||||
|
installed_apps:
|
||||||
|
- myapp
|
||||||
|
files:
|
||||||
|
- path: myapp/__init__.py
|
||||||
|
- path: myapp/models.py
|
||||||
|
content: |
|
||||||
|
from django.db import models
|
||||||
|
class Publisher(models.Model):
|
||||||
|
pass
|
||||||
|
class Blog(models.Model):
|
||||||
|
publisher = models.ForeignKey(to=Publisher, on_delete=models.CASCADE)
|
||||||
@@ -1,460 +0,0 @@
|
|||||||
- 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')
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
- case: no_incompatible_meta_nested_class_false_positive
|
- case: no_incompatible_meta_nested_class_false_positive
|
||||||
main: |
|
main: |
|
||||||
|
from django import forms
|
||||||
from myapp.models import Article, Category
|
from myapp.models import Article, Category
|
||||||
class ArticleForm(forms.ModelForm):
|
class ArticleForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
- 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
|
|
||||||
Reference in New Issue
Block a user