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:
Maxim Kurnikov
2019-04-12 14:54:00 +03:00
committed by GitHub
parent 13d19017b7
commit aeb435c8b3
24 changed files with 801 additions and 607 deletions

View File

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