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

@@ -149,7 +149,7 @@ def record_field_properties_into_outer_model_class(ctx: FunctionContext) -> None
if field_name is None:
return
fields_metadata = outer_model.metadata.setdefault('django', {}).setdefault('fields', {})
fields_metadata = helpers.get_fields_metadata(outer_model)
# primary key
is_primary_key = False

View File

@@ -1,4 +1,5 @@
from mypy.plugin import ClassDefContext
from mypy.plugin import ClassDefContext, MethodContext
from mypy.types import CallableType, Instance, NoneTyp, Type, TypeType
from mypy_django_plugin import helpers
@@ -8,3 +9,38 @@ def make_meta_nested_class_inherit_from_any(ctx: ClassDefContext) -> None:
if meta_node is None:
return None
meta_node.fallback_to_any = True
def extract_proper_type_for_get_form(ctx: MethodContext) -> Type:
object_type = ctx.type
if not isinstance(object_type, Instance):
return ctx.default_return_type
form_class_type = helpers.get_argument_type_by_name(ctx, 'form_class')
if form_class_type is None or isinstance(form_class_type, NoneTyp):
# extract from specified form_class in metadata
form_class_fullname = helpers.get_django_metadata(object_type.type).get('form_class', None)
if not form_class_fullname:
return ctx.default_return_type
return ctx.api.named_generic_type(form_class_fullname, [])
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) -> Type:
object_type = ctx.type
if not isinstance(object_type, Instance):
return ctx.default_return_type
form_class_fullname = helpers.get_django_metadata(object_type.type).get('form_class', None)
if not form_class_fullname:
return ctx.default_return_type
return TypeType(ctx.api.named_generic_type(form_class_fullname, []))

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:

View File

@@ -1,46 +1,99 @@
from collections import OrderedDict
from typing import Union, List, cast, Optional
from typing import List, Optional, cast
from mypy.checker import TypeChecker
from mypy.nodes import StrExpr, TypeInfo
from mypy.plugin import MethodContext, CheckerPluginInterface
from mypy.types import Type, Instance, AnyType, TypeOfAny
from mypy.plugin import (
AnalyzeTypeContext, CheckerPluginInterface, MethodContext,
)
from mypy.types import AnyType, Instance, Type, TypeOfAny
from mypy_django_plugin import helpers
from mypy_django_plugin.lookups import resolve_lookup, RelatedModelNode, LookupException
from mypy_django_plugin.lookups import (
LookupException, RelatedModelNode, resolve_lookup,
)
def extract_proper_type_for_values_and_values_list(method_name: str, ctx: MethodContext) -> Type:
api = cast(TypeChecker, ctx.api)
def get_queryset_model_arg(ret_type: Instance) -> Type:
if ret_type.args:
return ret_type.args[0]
else:
return AnyType(TypeOfAny.implementation_artifact)
def extract_proper_type_for_queryset_values(ctx: MethodContext) -> Type:
object_type = ctx.type
if not isinstance(object_type, Instance):
return ctx.default_return_type
fields_arg_expr = ctx.args[ctx.callee_arg_names.index('fields')]
if len(fields_arg_expr) == 0:
# values_list/values with no args is not yet supported, so default to Any types for field types
# It should in the future include all model fields, "extra" fields and "annotated" fields
return ctx.default_return_type
model_arg = get_queryset_model_arg(ctx.default_return_type)
if isinstance(model_arg, Instance):
model_type_info = model_arg.type
else:
model_type_info = None
column_types: OrderedDict[str, Type] = OrderedDict()
# parse *fields
for field_expr in fields_arg_expr:
if isinstance(field_expr, StrExpr):
field_name = field_expr.value
# Default to any type
column_types[field_name] = AnyType(TypeOfAny.implementation_artifact)
if model_type_info:
resolved_lookup_type = resolve_values_lookup(ctx.api, model_type_info, field_name)
if resolved_lookup_type is not None:
column_types[field_name] = resolved_lookup_type
else:
return ctx.default_return_type
# parse **expressions
expression_arg_names = ctx.arg_names[ctx.callee_arg_names.index('expressions')]
for expression_name in expression_arg_names:
# Arbitrary additional annotation expressions are supported, but they all have type Any for now
column_types[expression_name] = AnyType(TypeOfAny.implementation_artifact)
row_arg = helpers.make_typeddict(ctx.api, fields=column_types,
required_keys=set())
return helpers.reparametrize_instance(ctx.default_return_type, [model_arg, row_arg])
def extract_proper_type_queryset_values_list(ctx: MethodContext) -> Type:
object_type = ctx.type
if not isinstance(object_type, Instance):
return ctx.default_return_type
ret = ctx.default_return_type
any_type = AnyType(TypeOfAny.implementation_artifact)
fields_arg_expr = ctx.args[ctx.callee_arg_names.index('fields')]
model_arg: Union[AnyType, Type] = ret.args[0] if len(ret.args) > 0 else any_type
model_arg = get_queryset_model_arg(ctx.default_return_type)
# model_arg: Union[AnyType, Type] = ret.args[0] if len(ret.args) > 0 else any_type
column_names: List[Optional[str]] = []
column_types: OrderedDict[str, Type] = OrderedDict()
fill_column_types = True
fields_arg_expr = ctx.args[ctx.callee_arg_names.index('fields')]
fields_param_is_specified = True
if len(fields_arg_expr) == 0:
# values_list/values with no args is not yet supported, so default to Any types for field types
# It should in the future include all model fields, "extra" fields and "annotated" fields
fill_column_types = False
fields_param_is_specified = False
if isinstance(model_arg, Instance):
model_type_info = model_arg.type
else:
model_type_info = None
any_type = AnyType(TypeOfAny.implementation_artifact)
# Figure out each field name passed to fields
has_dynamic_column_names = False
only_strings_as_fields_expressions = True
for field_expr in fields_arg_expr:
if isinstance(field_expr, StrExpr):
field_name = field_expr.value
@@ -55,52 +108,48 @@ def extract_proper_type_for_values_and_values_list(method_name: str, ctx: Method
else:
# Dynamic field names are partially supported for values_list, but not values
column_names.append(None)
has_dynamic_column_names = True
only_strings_as_fields_expressions = False
if method_name == 'values_list':
flat = helpers.parse_bool(helpers.get_argument_by_name(ctx, 'flat'))
named = helpers.parse_bool(helpers.get_argument_by_name(ctx, 'named'))
flat = helpers.parse_bool(helpers.get_argument_by_name(ctx, 'flat'))
named = helpers.parse_bool(helpers.get_argument_by_name(ctx, 'named'))
if named and flat:
api.fail("'flat' and 'named' can't be used together.", ctx.context)
return ret
elif named:
if fill_column_types and not has_dynamic_column_names:
row_arg = helpers.make_named_tuple(api, fields=column_types, name="Row")
else:
row_arg = helpers.make_named_tuple(api, fields=OrderedDict(), name="Row")
elif flat:
if len(ctx.args[0]) > 1:
api.fail("'flat' is not valid when values_list is called with more than one field.", ctx.context)
return ret
if fill_column_types and not has_dynamic_column_names:
# Grab first element
row_arg = column_types[column_names[0]]
else:
row_arg = any_type
api = cast(TypeChecker, ctx.api)
if named and flat:
api.fail("'flat' and 'named' can't be used together.", ctx.context)
return ret
elif named:
# named=True, flat=False -> List[NamedTuple]
if fields_param_is_specified and only_strings_as_fields_expressions:
row_arg = helpers.make_named_tuple(api, fields=column_types, name="Row")
else:
if fill_column_types:
args = [
# Fallback to Any if the column name is unknown (e.g. dynamic)
column_types.get(column_name, any_type) if column_name is not None else any_type
for column_name in column_names
]
else:
args = [any_type]
row_arg = helpers.make_tuple(api, fields=args)
elif method_name == 'values':
expression_arg_names = ctx.arg_names[ctx.callee_arg_names.index('expressions')]
for expression_name in expression_arg_names:
# Arbitrary additional annotation expressions are supported, but they all have type Any for now
column_names.append(expression_name)
column_types[expression_name] = any_type
# fallback to catch-all NamedTuple
row_arg = helpers.make_named_tuple(api, fields=OrderedDict(), name="Row")
if fill_column_types and not has_dynamic_column_names:
row_arg = helpers.make_typeddict(api, fields=column_types, required_keys=set())
else:
elif flat:
# named=False, flat=True -> List of elements
if len(ctx.args[0]) > 1:
api.fail("'flat' is not valid when values_list is called with more than one field.",
ctx.context)
return ctx.default_return_type
if fields_param_is_specified and only_strings_as_fields_expressions:
# Grab first element
row_arg = column_types[column_names[0]]
else:
row_arg = any_type
else:
raise Exception(f"extract_proper_type_for_values_list doesn't support method {method_name}")
# named=False, flat=False -> List[Tuple]
if fields_param_is_specified:
args = [
# Fallback to Any if the column name is unknown (e.g. dynamic)
column_types.get(column_name, any_type) if column_name is not None else any_type
for column_name in column_names
]
else:
args = [any_type]
row_arg = helpers.make_tuple(api, fields=args)
new_type_args = [model_arg, row_arg]
return helpers.reparametrize_instance(ret, new_type_args)
@@ -137,3 +186,24 @@ def resolve_values_lookup(api: CheckerPluginInterface, model_type_info: TypeInfo
return helpers.make_optional(node_type)
else:
return node_type
def set_first_generic_param_as_default_for_second(fullname: str, ctx: AnalyzeTypeContext) -> Type:
if not ctx.type.args:
try:
return ctx.api.named_type(fullname, [AnyType(TypeOfAny.explicit),
AnyType(TypeOfAny.explicit)])
except KeyError:
# really should never happen
return AnyType(TypeOfAny.explicit)
args = ctx.type.args
if len(args) == 1:
args = [args[0], args[0]]
analyzed_args = [ctx.api.analyze_type(arg) for arg in args]
try:
return ctx.api.named_type(fullname, analyzed_args)
except KeyError:
# really should never happen
return AnyType(TypeOfAny.explicit)

View File

@@ -0,0 +1,59 @@
from typing import Optional, Union
from mypy.checkmember import AttributeContext
from mypy.nodes import TypeInfo
from mypy.types import AnyType, Instance, Type, TypeOfAny, UnionType
from mypy_django_plugin import helpers
def _extract_referred_to_type_info(typ: Union[UnionType, Instance]) -> Optional[TypeInfo]:
if isinstance(typ, Instance):
return typ.type
else:
# should be Union[TYPE, None]
typ = helpers.make_required(typ)
if isinstance(typ, Instance):
return typ.type
return None
def extract_and_return_primary_key_of_bound_related_field_parameter(ctx: AttributeContext) -> Type:
if not isinstance(ctx.default_attr_type, Instance) or not (ctx.default_attr_type.type.fullname() == 'builtins.int'):
return ctx.default_attr_type
if not isinstance(ctx.type, Instance) or not ctx.type.type.has_base(helpers.MODEL_CLASS_FULLNAME):
return ctx.default_attr_type
field_name = ctx.context.name.split('_')[0]
sym = ctx.type.type.get(field_name)
if sym and isinstance(sym.type, Instance) and len(sym.type.args) > 0:
referred_to = sym.type.args[1]
if isinstance(referred_to, AnyType):
return AnyType(TypeOfAny.implementation_artifact)
model_type = _extract_referred_to_type_info(referred_to)
if model_type is None:
return AnyType(TypeOfAny.implementation_artifact)
primary_key_type = helpers.extract_primary_key_type_for_get(model_type)
if primary_key_type:
return primary_key_type
is_nullable = helpers.is_field_nullable(ctx.type.type, field_name)
if is_nullable:
return helpers.make_optional(ctx.default_attr_type)
return ctx.default_attr_type
def determine_type_of_related_manager(ctx: AttributeContext, related_manager_name: str) -> Type:
if not isinstance(ctx.type, Instance):
return ctx.default_attr_type
related_manager_type = helpers.get_related_manager_type_from_metadata(ctx.type.type,
related_manager_name, ctx.api)
if not related_manager_type:
return ctx.default_attr_type
return related_manager_type

View File

@@ -1,97 +1,78 @@
from typing import Iterable, List, Optional, cast
from typing import TYPE_CHECKING, List, Optional, cast
from mypy.nodes import (
ClassDef, Context, ImportAll, MypyFile, SymbolNode, SymbolTableNode, TypeInfo, Var,
)
from mypy.plugin import ClassDefContext
from mypy.semanal import SemanticAnalyzerPass2
from mypy.types import AnyType, Instance, NoneTyp, Type, TypeOfAny, UnionType
from mypy.util import correct_relative_import
from mypy.checkexpr import FunctionContext
from mypy.checkmember import AttributeContext
from mypy.nodes import NameExpr, StrExpr, SymbolTableNode, TypeInfo
from mypy.types import AnyType, Instance, Type, TypeOfAny, TypeType
from mypy_django_plugin import helpers
if TYPE_CHECKING:
from mypy.checker import TypeChecker
def get_error_context(node: SymbolNode) -> Context:
context = Context()
context.set_line(node)
return context
def get_setting_sym(name: str, api: 'TypeChecker', settings_modules: List[str]) -> Optional[SymbolTableNode]:
for settings_mod_name in settings_modules:
file = api.modules[settings_mod_name]
sym = file.names.get(name)
if sym is not None:
return sym
return None
def filter_out_nones(typ: UnionType) -> List[Type]:
return [item for item in typ.items if not isinstance(item, NoneTyp)]
def get_type_of_setting(ctx: AttributeContext, setting_name: str,
settings_modules: List[str], ignore_missing_settings: bool) -> Type:
setting_sym = get_setting_sym(setting_name, ctx.api, settings_modules)
if setting_sym:
if setting_sym.type is None:
# TODO: defer till setting_sym.type is not None
return AnyType(TypeOfAny.implementation_artifact)
return setting_sym.type
if not ignore_missing_settings:
ctx.api.fail(f"'Settings' object has no attribute {setting_name!r}", ctx.context)
return ctx.default_attr_type
def make_sym_copy_of_setting(sym: SymbolTableNode) -> Optional[SymbolTableNode]:
if isinstance(sym.type, Instance):
copied = sym.copy()
copied.node.info = sym.type.type
return copied
elif isinstance(sym.type, UnionType):
instances = filter_out_nones(sym.type)
if len(instances) > 1:
# plain unions not supported yet
return None
typ = instances[0]
if isinstance(typ, Instance):
copied = sym.copy()
copied.node.info = typ.type
return copied
return None
else:
return None
def return_user_model_hook(ctx: FunctionContext, settings_modules: List[str]) -> Type:
from mypy.checker import TypeChecker
api = cast(TypeChecker, ctx.api)
def get_settings_metadata(lazy_settings_info: TypeInfo):
return lazy_settings_info.metadata.setdefault('django', {}).setdefault('settings', {})
setting_sym = get_setting_sym('AUTH_USER_MODEL', api, settings_modules)
if setting_sym is None:
return ctx.default_return_type
setting_module_name, _, _ = setting_sym.fullname.rpartition('.')
setting_module = api.modules[setting_module_name]
def load_settings_from_names(settings_classdef: ClassDef,
modules: Iterable[MypyFile],
api: SemanticAnalyzerPass2) -> None:
settings_metadata = get_settings_metadata(settings_classdef.info)
model_path = None
for name_expr, rvalue_expr in helpers.iter_over_assignments(setting_module):
if isinstance(name_expr, NameExpr) and isinstance(rvalue_expr, StrExpr):
if name_expr.name == 'AUTH_USER_MODEL':
model_path = rvalue_expr.value
break
for module in modules:
for name, sym in module.names.items():
if name.isupper() and isinstance(sym.node, Var):
if sym.type is not None:
copied = make_sym_copy_of_setting(sym)
if copied is None:
continue
settings_classdef.info.names[name] = copied
else:
var = Var(name, AnyType(TypeOfAny.unannotated))
var.info = api.named_type('__builtins__.object').type # outer class type
settings_classdef.info.names[name] = SymbolTableNode(sym.kind, var, plugin_generated=True)
if not model_path:
return ctx.default_return_type
settings_metadata[name] = module.fullname()
app_label, _, model_class_name = model_path.rpartition('.')
if app_label is None:
return ctx.default_return_type
model_fullname = helpers.get_model_fullname(app_label, model_class_name,
all_modules=api.modules)
if model_fullname is None:
api.fail(f'"{app_label}.{model_class_name}" model class is not imported so far. Try to import it '
f'(under if TYPE_CHECKING) at the beginning of the current file',
context=ctx.context)
return ctx.default_return_type
def get_import_star_modules(api: SemanticAnalyzerPass2, module: MypyFile) -> List[str]:
import_star_modules = []
for module_import in module.imports:
# relative import * are not resolved by mypy
if isinstance(module_import, ImportAll) and module_import.relative:
absolute_import_path, correct = correct_relative_import(module.fullname(), module_import.relative,
module_import.id, is_cur_package_init_file=False)
if not correct:
return []
for path in [absolute_import_path] + get_import_star_modules(api,
module=api.modules.get(absolute_import_path)):
if path not in import_star_modules:
import_star_modules.append(path)
return import_star_modules
class AddSettingValuesToDjangoConfObject:
def __init__(self, settings_modules: List[str], ignore_missing_settings: bool):
self.settings_modules = settings_modules
self.ignore_missing_settings = ignore_missing_settings
def __call__(self, ctx: ClassDefContext) -> None:
api = cast(SemanticAnalyzerPass2, ctx.api)
for module_name in self.settings_modules:
module = api.modules[module_name]
star_deps = [api.modules[star_dep]
for star_dep in reversed(get_import_star_modules(api, module))]
load_settings_from_names(ctx.cls, modules=star_deps + [module], api=api)
if self.ignore_missing_settings:
ctx.cls.info.fallback_to_any = True
model_info = helpers.lookup_fully_qualified_generic(model_fullname,
all_modules=api.modules)
if model_info is None or not isinstance(model_info, TypeInfo):
return ctx.default_return_type
return TypeType(Instance(model_info, []))