mirror of
https://github.com/davidhalter/django-stubs.git
synced 2025-12-08 13:04:47 +08:00
Disable monkeypatches, add dependencies via new hook (#60)
* code cleanups, disable monkeypatches, move to add_additional_deps * disable incremental mode for tests * add pip-wheel-metadata * move some code from get_base_hook to get_attribute_hook to reduce dependencies * simplify values/values_list tests and code * disable cache for some tests failing with incremental mode * enable incremental mode for tests typechecking * pin mypy version * fix tests * lint * fix internal crashes
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from typing import Dict, Iterator, List, Optional, Tuple, cast
|
||||
from typing import Any, Dict, Iterator, List, Optional, Tuple, cast
|
||||
|
||||
import dataclasses
|
||||
from mypy.nodes import (
|
||||
ARG_STAR, ARG_STAR2, MDEF, Argument, CallExpr, ClassDef, Expression, IndexExpr, Lvalue, MemberExpr, MypyFile,
|
||||
ARG_POS, ARG_STAR, ARG_STAR2, MDEF, Argument, CallExpr, ClassDef, Expression, IndexExpr, MemberExpr, MypyFile,
|
||||
NameExpr, StrExpr, SymbolTableNode, TypeInfo, Var,
|
||||
ARG_POS)
|
||||
)
|
||||
from mypy.plugin import ClassDefContext
|
||||
from mypy.plugins.common import add_method
|
||||
from mypy.semanal import SemanticAnalyzerPass2
|
||||
@@ -37,8 +37,10 @@ class ModelClassInitializer(metaclass=ABCMeta):
|
||||
return self.api.parse_bool(is_abstract_expr)
|
||||
|
||||
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 = typ.type
|
||||
# var.info: type of the object variable is bound to
|
||||
var.info = self.model_classdef.info
|
||||
var._fullname = self.model_classdef.info.fullname() + '.' + name
|
||||
var.is_inferred = True
|
||||
var.is_initialized_in_class = True
|
||||
@@ -49,14 +51,8 @@ class ModelClassInitializer(metaclass=ABCMeta):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def iter_call_assignments(klass: ClassDef) -> Iterator[Tuple[Lvalue, CallExpr]]:
|
||||
for lvalue, rvalue in helpers.iter_over_assignments(klass):
|
||||
if isinstance(rvalue, CallExpr):
|
||||
yield lvalue, rvalue
|
||||
|
||||
|
||||
def iter_over_one_to_n_related_fields(klass: ClassDef) -> Iterator[Tuple[NameExpr, CallExpr]]:
|
||||
for lvalue, rvalue in iter_call_assignments(klass):
|
||||
for lvalue, rvalue in helpers.iter_call_assignments(klass):
|
||||
if (isinstance(lvalue, NameExpr)
|
||||
and isinstance(rvalue.callee, MemberExpr)):
|
||||
if rvalue.callee.fullname in {helpers.FOREIGN_KEY_FULLNAME,
|
||||
@@ -80,15 +76,6 @@ class InjectAnyAsBaseForNestedMeta(ModelClassInitializer):
|
||||
meta_node.fallback_to_any = True
|
||||
|
||||
|
||||
def get_model_argument(manager_info: TypeInfo) -> Optional[Instance]:
|
||||
for base in manager_info.bases:
|
||||
if base.args:
|
||||
model_arg = base.args[0]
|
||||
if isinstance(model_arg, Instance) and model_arg.type.has_base(helpers.MODEL_CLASS_FULLNAME):
|
||||
return model_arg
|
||||
return None
|
||||
|
||||
|
||||
class AddDefaultObjectsManager(ModelClassInitializer):
|
||||
def add_new_manager(self, name: str, manager_type: Optional[Instance]) -> None:
|
||||
if manager_type is None:
|
||||
@@ -103,7 +90,7 @@ class AddDefaultObjectsManager(ModelClassInitializer):
|
||||
def get_existing_managers(self) -> List[Tuple[str, TypeInfo]]:
|
||||
managers = []
|
||||
for base in self.model_classdef.info.mro:
|
||||
for name_expr, member_expr in iter_call_assignments(base.defn):
|
||||
for name_expr, member_expr in helpers.iter_call_assignments(base.defn):
|
||||
manager_name = name_expr.name
|
||||
callee_expr = member_expr.callee
|
||||
if isinstance(callee_expr, IndexExpr):
|
||||
@@ -147,7 +134,7 @@ class AddIdAttributeIfPrimaryKeyTrueIsNotSet(ModelClassInitializer):
|
||||
# no need for .id attr
|
||||
return None
|
||||
|
||||
for _, rvalue in iter_call_assignments(self.model_classdef):
|
||||
for _, rvalue in helpers.iter_call_assignments(self.model_classdef):
|
||||
if ('primary_key' in rvalue.arg_names
|
||||
and self.api.parse_bool(rvalue.args[rvalue.arg_names.index('primary_key')])):
|
||||
break
|
||||
@@ -156,23 +143,31 @@ class AddIdAttributeIfPrimaryKeyTrueIsNotSet(ModelClassInitializer):
|
||||
|
||||
|
||||
class AddRelatedManagers(ModelClassInitializer):
|
||||
def add_related_manager_variable(self, manager_name: str, related_field_type_data: Dict[str, Any]) -> None:
|
||||
# add dummy related manager for use later
|
||||
self.add_new_node_to_model_class(manager_name, self.api.builtin_type('builtins.object'))
|
||||
|
||||
# save name in metadata for use in get_attribute_hook later
|
||||
related_managers_metadata = helpers.get_related_managers_metadata(self.model_classdef.info)
|
||||
related_managers_metadata[manager_name] = related_field_type_data
|
||||
|
||||
def run(self) -> None:
|
||||
for module_name, module_file in self.api.modules.items():
|
||||
for defn in iter_over_classdefs(module_file):
|
||||
for lvalue, rvalue in iter_call_assignments(defn):
|
||||
for model_defn in helpers.iter_over_classdefs(module_file):
|
||||
for lvalue, rvalue in helpers.iter_call_assignments(model_defn):
|
||||
if is_related_field(rvalue, module_file):
|
||||
try:
|
||||
ref_to_fullname = extract_ref_to_fullname(rvalue,
|
||||
module_file=module_file,
|
||||
all_modules=self.api.modules)
|
||||
referenced_model_fullname = extract_ref_to_fullname(rvalue,
|
||||
module_file=module_file,
|
||||
all_modules=self.api.modules)
|
||||
except helpers.SelfReference:
|
||||
ref_to_fullname = defn.fullname
|
||||
referenced_model_fullname = model_defn.fullname
|
||||
|
||||
except helpers.SameFileModel as exc:
|
||||
ref_to_fullname = module_name + '.' + exc.model_cls_name
|
||||
referenced_model_fullname = module_name + '.' + exc.model_cls_name
|
||||
|
||||
if self.model_classdef.fullname == ref_to_fullname:
|
||||
related_name = defn.name.lower() + '_set'
|
||||
if self.model_classdef.fullname == referenced_model_fullname:
|
||||
related_name = model_defn.name.lower() + '_set'
|
||||
if 'related_name' in rvalue.arg_names:
|
||||
related_name_expr = rvalue.args[rvalue.arg_names.index('related_name')]
|
||||
if not isinstance(related_name_expr, StrExpr):
|
||||
@@ -192,10 +187,28 @@ class AddRelatedManagers(ModelClassInitializer):
|
||||
else:
|
||||
# No related_query_name specified, default to related_name
|
||||
related_query_name = related_name
|
||||
typ = get_related_field_type(rvalue, self.api, defn.info)
|
||||
if typ is None:
|
||||
continue
|
||||
self.add_new_node_to_model_class(related_name, typ)
|
||||
|
||||
# field_type_data = get_related_field_type(rvalue, self.api, defn.info)
|
||||
# if typ is None:
|
||||
# continue
|
||||
|
||||
# TODO: recursively serialize types, or just https://github.com/python/mypy/issues/6506
|
||||
# as long as Model is not a Generic, one level depth is fine
|
||||
if rvalue.callee.name in {'ForeignKey', 'ManyToManyField'}:
|
||||
field_type_data = {
|
||||
'manager': helpers.RELATED_MANAGER_CLASS_FULLNAME,
|
||||
'of': [model_defn.info.fullname()]
|
||||
}
|
||||
# return api.named_type_or_none(helpers.RELATED_MANAGER_CLASS_FULLNAME,
|
||||
# args=[Instance(related_model_typ, [])])
|
||||
else:
|
||||
field_type_data = {
|
||||
'manager': model_defn.info.fullname(),
|
||||
'of': []
|
||||
}
|
||||
|
||||
self.add_related_manager_variable(related_name, related_field_type_data=field_type_data)
|
||||
|
||||
if related_query_name is not None:
|
||||
# Only create related_query_name if it is a string literal
|
||||
helpers.get_lookups_metadata(self.model_classdef.info)[related_query_name] = {
|
||||
@@ -203,19 +216,20 @@ class AddRelatedManagers(ModelClassInitializer):
|
||||
}
|
||||
|
||||
|
||||
def iter_over_classdefs(module_file: MypyFile) -> Iterator[ClassDef]:
|
||||
for defn in module_file.defs:
|
||||
if isinstance(defn, ClassDef):
|
||||
yield defn
|
||||
|
||||
|
||||
def get_related_field_type(rvalue: CallExpr, api: SemanticAnalyzerPass2,
|
||||
related_model_typ: TypeInfo) -> Optional[Instance]:
|
||||
def get_related_field_type(rvalue: CallExpr, related_model_typ: TypeInfo) -> Dict[str, Any]:
|
||||
if rvalue.callee.name in {'ForeignKey', 'ManyToManyField'}:
|
||||
return api.named_type_or_none(helpers.RELATED_MANAGER_CLASS_FULLNAME,
|
||||
args=[Instance(related_model_typ, [])])
|
||||
return {
|
||||
'manager': helpers.RELATED_MANAGER_CLASS_FULLNAME,
|
||||
'of': [related_model_typ.fullname()]
|
||||
}
|
||||
# return api.named_type_or_none(helpers.RELATED_MANAGER_CLASS_FULLNAME,
|
||||
# args=[Instance(related_model_typ, [])])
|
||||
else:
|
||||
return Instance(related_model_typ, [])
|
||||
return {
|
||||
'manager': related_model_typ.fullname(),
|
||||
'of': []
|
||||
}
|
||||
# return Instance(related_model_typ, [])
|
||||
|
||||
|
||||
def is_related_field(expr: CallExpr, module_file: MypyFile) -> bool:
|
||||
|
||||
Reference in New Issue
Block a user