fix mypy errors

This commit is contained in:
Maxim Kurnikov
2019-07-25 18:52:51 +03:00
parent 6466c57c69
commit 4c21855641
12 changed files with 168 additions and 118 deletions

View File

@@ -2,7 +2,7 @@ from typing import Optional, Tuple, cast
from django.db.models.fields import Field
from django.db.models.fields.related import RelatedField
from mypy.nodes import AssignmentStmt, TypeInfo
from mypy.nodes import AssignmentStmt, TypeInfo, NameExpr
from mypy.plugin import FunctionContext
from mypy.types import AnyType, Instance
from mypy.types import Type as MypyType
@@ -13,15 +13,16 @@ from mypy_django_plugin.lib import fullnames, helpers
def _get_current_field_from_assignment(ctx: FunctionContext, django_context: DjangoContext) -> Optional[Field]:
outer_model_info = ctx.api.scope.active_class()
assert isinstance(outer_model_info, TypeInfo)
if not outer_model_info.has_base(fullnames.MODEL_CLASS_FULLNAME):
outer_model_info = helpers.get_typechecker_api(ctx).scope.active_class()
if (outer_model_info is None
or not outer_model_info.has_base(fullnames.MODEL_CLASS_FULLNAME)):
return None
field_name = None
for stmt in outer_model_info.defn.defs.body:
if isinstance(stmt, AssignmentStmt):
if stmt.rvalue == ctx.context:
assert isinstance(stmt.lvalues[0], NameExpr)
field_name = stmt.lvalues[0].name
break
if field_name is None:
@@ -46,8 +47,13 @@ def fill_descriptor_types_for_related_field(ctx: FunctionContext, django_context
if related_model_to_set._meta.proxy_for_model:
related_model_to_set = related_model._meta.proxy_for_model
related_model_info = helpers.lookup_class_typeinfo(ctx.api, related_model)
related_model_to_set_info = helpers.lookup_class_typeinfo(ctx.api, related_model_to_set)
typechecker_api = helpers.get_typechecker_api(ctx)
related_model_info = helpers.lookup_class_typeinfo(typechecker_api, related_model)
assert isinstance(related_model_info, TypeInfo)
related_model_to_set_info = helpers.lookup_class_typeinfo(typechecker_api, related_model_to_set)
assert isinstance(related_model_to_set_info, TypeInfo)
default_related_field_type = set_descriptor_types_for_field(ctx)
# replace Any with referred_to_type
@@ -68,7 +74,12 @@ def get_field_descriptor_types(field_info: TypeInfo, is_nullable: bool) -> Tuple
def set_descriptor_types_for_field(ctx: FunctionContext) -> Instance:
default_return_type = cast(Instance, ctx.default_return_type)
is_nullable = helpers.parse_bool(helpers.get_call_argument_by_name(ctx, 'null'))
is_nullable = False
null_expr = helpers.get_call_argument_by_name(ctx, 'null')
if null_expr is not None:
is_nullable = helpers.parse_bool(null_expr) or False
set_type, get_type = get_field_descriptor_types(default_return_type.type, is_nullable)
return helpers.reparametrize_instance(default_return_type, [set_type, get_type])
@@ -92,7 +103,7 @@ def transform_into_proper_return_type(ctx: FunctionContext, django_context: Djan
default_return_type = ctx.default_return_type
assert isinstance(default_return_type, Instance)
outer_model_info = ctx.api.scope.active_class()
outer_model_info = helpers.get_typechecker_api(ctx).scope.active_class()
if not outer_model_info or not outer_model_info.has_base(fullnames.MODEL_CLASS_FULLNAME):
# not inside models.Model class
return ctx.default_return_type

View File

@@ -2,10 +2,10 @@ from typing import List, Tuple, Type, Union
from django.db.models.base import Model
from mypy.plugin import FunctionContext, MethodContext
from mypy.types import Instance
from mypy.types import Type as MypyType
from mypy.types import Instance, Type as MypyType
from mypy_django_plugin.django.context import DjangoContext
from mypy_django_plugin.lib import helpers
def get_actual_types(ctx: Union[MethodContext, FunctionContext],
@@ -31,7 +31,8 @@ def get_actual_types(ctx: Union[MethodContext, FunctionContext],
def typecheck_model_method(ctx: Union[FunctionContext, MethodContext], django_context: DjangoContext,
model_cls: Type[Model], method: str) -> MypyType:
expected_types = django_context.get_expected_types(ctx.api, model_cls, method)
typechecker_api = helpers.get_typechecker_api(ctx)
expected_types = django_context.get_expected_types(typechecker_api, model_cls, method=method)
expected_keys = [key for key in expected_types.keys() if key != 'pk']
for actual_name, actual_type in get_actual_types(ctx, expected_keys):
@@ -40,11 +41,11 @@ def typecheck_model_method(ctx: Union[FunctionContext, MethodContext], django_co
model_cls.__name__),
ctx.context)
continue
ctx.api.check_subtype(actual_type, expected_types[actual_name],
ctx.context,
'Incompatible type for "{}" of "{}"'.format(actual_name,
model_cls.__name__),
'got', 'expected')
typechecker_api.check_subtype(actual_type, expected_types[actual_name],
ctx.context,
'Incompatible type for "{}" of "{}"'.format(actual_name,
model_cls.__name__),
'got', 'expected')
return ctx.default_return_type

View File

@@ -1,4 +1,5 @@
from django.core.exceptions import FieldDoesNotExist
from mypy.nodes import TypeInfo
from mypy.plugin import MethodContext
from mypy.types import AnyType, Instance
from mypy.types import Type as MypyType
@@ -9,11 +10,16 @@ from mypy_django_plugin.lib import fullnames, helpers
def _get_field_instance(ctx: MethodContext, field_fullname: str) -> MypyType:
field_info = helpers.lookup_fully_qualified_typeinfo(ctx.api, field_fullname)
field_info = helpers.lookup_fully_qualified_typeinfo(helpers.get_typechecker_api(ctx),
field_fullname)
assert isinstance(field_info, TypeInfo)
return Instance(field_info, [AnyType(TypeOfAny.explicit), AnyType(TypeOfAny.explicit)])
def return_proper_field_type_from_get_field(ctx: MethodContext, django_context: DjangoContext) -> MypyType:
# Options instance
assert isinstance(ctx.type, Instance)
model_type = ctx.type.args[0]
if not isinstance(model_type, Instance):
return _get_field_instance(ctx, fullnames.FIELD_FULLNAME)

View File

@@ -118,8 +118,8 @@ class AddManagers(ModelClassInitializer):
manager_info = self.lookup_typeinfo_or_incomplete_defn_error(manager_fullname)
if manager_name not in self.model_classdef.info.names:
manager = Instance(manager_info, [Instance(self.model_classdef.info, [])])
self.add_new_node_to_model_class(manager_name, manager)
manager_type = Instance(manager_info, [Instance(self.model_classdef.info, [])])
self.add_new_node_to_model_class(manager_name, manager_type)
else:
# create new MODELNAME_MANAGERCLASSNAME class that represents manager parametrized with current model
has_manager_any_base = any(self._is_manager_any(base) for base in manager_info.bases)

View File

@@ -1,59 +1,39 @@
from collections import OrderedDict
from typing import List, Optional, Sequence, Tuple, Type, Union
from typing import List, Optional, Sequence, Type, Union
from django.core.exceptions import FieldError
from django.db.models.base import Model
from mypy.nodes import Expression, NameExpr
from mypy.plugin import AnalyzeTypeContext, FunctionContext, MethodContext
from mypy.types import AnyType, Instance
from mypy.types import Type as MypyType
from mypy.types import TypeOfAny
from mypy.plugin import FunctionContext, MethodContext
from mypy.types import AnyType, Instance, Type as MypyType, TypeOfAny
from mypy_django_plugin.django.context import DjangoContext
from mypy_django_plugin.lib import fullnames, helpers
def set_first_generic_param_as_default_for_second(ctx: AnalyzeTypeContext, fullname: str) -> MypyType:
info = helpers.lookup_fully_qualified_typeinfo(ctx.api.api, fullname)
if info is None:
if not ctx.api.api.final_iteration:
ctx.api.api.defer()
if not ctx.type.args:
return Instance(info, [AnyType(TypeOfAny.explicit), 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]
return Instance(info, analyzed_args)
def determine_proper_manager_type(ctx: FunctionContext) -> MypyType:
ret = ctx.default_return_type
assert isinstance(ret, Instance)
default_return_type = ctx.default_return_type
assert isinstance(default_return_type, Instance)
if not ctx.api.tscope.classes:
# not in class
return ret
outer_model_info = ctx.api.tscope.classes[0]
if not outer_model_info.has_base(fullnames.MODEL_CLASS_FULLNAME):
return ret
outer_model_info = helpers.get_typechecker_api(ctx).scope.active_class()
if (outer_model_info is None
or not outer_model_info.has_base(fullnames.MODEL_CLASS_FULLNAME)):
return default_return_type
return helpers.reparametrize_instance(ret, [Instance(outer_model_info, [])])
return helpers.reparametrize_instance(default_return_type, [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]]:
def get_field_type_from_lookup(ctx: MethodContext, django_context: DjangoContext, model_cls: Type[Model],
*, method: str, lookup: str) -> Optional[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_get_type
field_get_type = django_context.fields_context.get_field_get_type(helpers.get_typechecker_api(ctx),
lookup_field, method=method)
return field_get_type
def get_values_list_row_type(ctx: MethodContext, django_context: DjangoContext, model_cls: Type[Model],
@@ -62,18 +42,21 @@ def get_values_list_row_type(ctx: MethodContext, django_context: DjangoContext,
if field_lookups is None:
return AnyType(TypeOfAny.from_error)
typechecker_api = helpers.get_typechecker_api(ctx)
if len(field_lookups) == 0:
if flat:
primary_key_field = django_context.get_primary_key_field(model_cls)
_, column_type = get_lookup_field_get_type(ctx, django_context, model_cls,
primary_key_field.attname, 'values_list')
return column_type
lookup_type = get_field_type_from_lookup(ctx, django_context, model_cls,
lookup=primary_key_field.attname, method='values_list')
assert lookup_type is not None
return lookup_type
elif named:
column_types = OrderedDict()
column_types: 'OrderedDict[str, MypyType]' = OrderedDict()
for field in django_context.get_model_fields(model_cls):
column_type = django_context.fields_context.get_field_get_type(ctx.api, field, 'values_list')
column_type = django_context.fields_context.get_field_get_type(typechecker_api, field,
method='values_list')
column_types[field.attname] = column_type
return helpers.make_oneoff_named_tuple(ctx.api, 'Row', column_types)
return helpers.make_oneoff_named_tuple(typechecker_api, 'Row', column_types)
else:
# flat=False, named=False, all fields
field_lookups = []
@@ -81,32 +64,32 @@ def get_values_list_row_type(ctx: MethodContext, django_context: DjangoContext,
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)
typechecker_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:
lookup_field_type = get_field_type_from_lookup(ctx, django_context, model_cls,
lookup=field_lookup, method='values_list')
if lookup_field_type is None:
return AnyType(TypeOfAny.from_error)
column_name, column_type = result
column_types[column_name] = column_type
column_types[field_lookup] = lookup_field_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)
row_type = helpers.make_oneoff_named_tuple(typechecker_api, 'Row', column_types)
else:
row_type = helpers.make_tuple(ctx.api, list(column_types.values()))
row_type = helpers.make_tuple(typechecker_api, list(column_types.values()))
return row_type
def extract_proper_type_queryset_values_list(ctx: MethodContext, django_context: DjangoContext) -> MypyType:
# called on the Instance
# called on the Instance, returns QuerySet of something
assert isinstance(ctx.type, Instance)
assert isinstance(ctx.default_return_type, Instance)
# bail if queryset of Any or other non-instances
if not isinstance(ctx.type.args[0], Instance):
@@ -133,6 +116,10 @@ def extract_proper_type_queryset_values_list(ctx: MethodContext, django_context:
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)])
# account for possible None
flat = flat or False
named = named or False
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])
@@ -150,8 +137,10 @@ def resolve_field_lookups(lookup_exprs: Sequence[Expression], ctx: Union[Functio
def extract_proper_type_queryset_values(ctx: MethodContext, django_context: DjangoContext) -> MypyType:
# queryset method
# called on QuerySet, return QuerySet of something
assert isinstance(ctx.type, Instance)
assert isinstance(ctx.default_return_type, Instance)
# if queryset of non-instance type
if not isinstance(ctx.type.args[0], Instance):
return AnyType(TypeOfAny.from_omitted_generics)
@@ -169,14 +158,14 @@ def extract_proper_type_queryset_values(ctx: MethodContext, django_context: Djan
for field in django_context.get_model_fields(model_cls):
field_lookups.append(field.attname)
column_types = OrderedDict()
column_types: 'OrderedDict[str, MypyType]' = OrderedDict()
for field_lookup in field_lookups:
result = get_lookup_field_get_type(ctx, django_context, model_cls, field_lookup, 'values')
if result is None:
field_lookup_type = get_field_type_from_lookup(ctx, django_context, model_cls,
lookup=field_lookup, method='values')
if field_lookup_type is None:
return helpers.reparametrize_instance(ctx.default_return_type, [model_type, AnyType(TypeOfAny.from_error)])
column_name, column_type = result
column_types[column_name] = column_type
column_types[field_lookup] = field_lookup_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])

View File

@@ -9,7 +9,7 @@ from mypy_django_plugin.lib import helpers
def set_auth_user_model_as_type_for_request_user(ctx: AttributeContext, django_context: DjangoContext) -> MypyType:
auth_user_model = django_context.settings.AUTH_USER_MODEL
model_cls = django_context.apps_registry.get_model(auth_user_model)
model_info = helpers.lookup_class_typeinfo(ctx.api, model_cls)
model_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), model_cls)
if model_info is None:
return ctx.default_attr_type

View File

@@ -1,3 +1,6 @@
from typing import cast
from mypy.checker import TypeChecker
from mypy.nodes import MemberExpr, TypeInfo
from mypy.plugin import AttributeContext, FunctionContext
from mypy.types import Instance
@@ -13,7 +16,8 @@ def get_user_model_hook(ctx: FunctionContext, django_context: DjangoContext) ->
model_cls = django_context.apps_registry.get_model(auth_user_model)
model_cls_fullname = helpers.get_class_fullname(model_cls)
model_info = helpers.lookup_fully_qualified_generic(model_cls_fullname, ctx.api.modules)
model_info = helpers.lookup_fully_qualified_generic(model_cls_fullname,
helpers.get_typechecker_api(ctx).modules)
assert isinstance(model_info, TypeInfo)
return TypeType(Instance(model_info, []))
@@ -26,9 +30,11 @@ def get_type_of_settings_attribute(ctx: AttributeContext, django_context: Django
ctx.api.fail(f"'Settings' object has no attribute {setting_name!r}", ctx.context)
return ctx.default_attr_type
typechecker_api = helpers.get_typechecker_api(ctx)
# first look for the setting in the project settings file, then global settings
settings_module = ctx.api.modules.get(django_context.django_settings_module)
global_settings_module = ctx.api.modules.get('django.conf.global_settings')
settings_module = typechecker_api.modules.get(django_context.django_settings_module)
global_settings_module = typechecker_api.modules.get('django.conf.global_settings')
for module in [settings_module, global_settings_module]:
if module is not None:
sym = module.names.get(setting_name)
@@ -39,7 +45,7 @@ def get_type_of_settings_attribute(ctx: AttributeContext, django_context: Django
value = getattr(django_context.settings, setting_name)
value_fullname = helpers.get_class_fullname(value.__class__)
value_info = helpers.lookup_fully_qualified_typeinfo(ctx.api, value_fullname)
value_info = helpers.lookup_fully_qualified_typeinfo(typechecker_api, value_fullname)
if value_info is None:
return ctx.default_attr_type