mirror of
https://github.com/davidhalter/django-stubs.git
synced 2025-12-08 04:54:48 +08:00
cleanups, fallback to Any in some corner cases
This commit is contained in:
@@ -32,4 +32,6 @@ RELATED_FIELDS_CLASSES = {
|
|||||||
FOREIGN_KEY_FULLNAME,
|
FOREIGN_KEY_FULLNAME,
|
||||||
ONETOONE_FIELD_FULLNAME,
|
ONETOONE_FIELD_FULLNAME,
|
||||||
MANYTOMANY_FIELD_FULLNAME
|
MANYTOMANY_FIELD_FULLNAME
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MIGRATION_CLASS_FULLNAME = 'django.db.migrations.migration.Migration'
|
||||||
|
|||||||
@@ -1,82 +1,13 @@
|
|||||||
from typing import Optional, Tuple, cast
|
from typing import Optional, Tuple, cast
|
||||||
|
|
||||||
from mypy.checker import TypeChecker
|
from mypy.nodes import TypeInfo
|
||||||
from mypy.nodes import StrExpr, TypeInfo, Expression
|
|
||||||
from mypy.plugin import FunctionContext
|
from mypy.plugin import FunctionContext
|
||||||
from mypy.types import AnyType, CallableType, Instance, Type as MypyType, UnionType, TypeOfAny
|
from mypy.types import AnyType, CallableType, Instance, Type as MypyType, TypeOfAny
|
||||||
|
|
||||||
from mypy_django_plugin.django.context import DjangoContext
|
from mypy_django_plugin.django.context import DjangoContext
|
||||||
from mypy_django_plugin.lib import fullnames, helpers
|
from mypy_django_plugin.lib import fullnames, helpers
|
||||||
|
|
||||||
|
|
||||||
def extract_referred_to_type(ctx: FunctionContext, django_context: DjangoContext) -> Optional[Instance]:
|
|
||||||
api = cast(TypeChecker, ctx.api)
|
|
||||||
if 'to' not in ctx.callee_arg_names:
|
|
||||||
api.msg.fail(f'to= parameter must be set for {ctx.context.callee.fullname!r}',
|
|
||||||
context=ctx.context)
|
|
||||||
return None
|
|
||||||
|
|
||||||
arg_type = ctx.arg_types[ctx.callee_arg_names.index('to')][0]
|
|
||||||
if not isinstance(arg_type, CallableType):
|
|
||||||
to_arg_expr = ctx.args[ctx.callee_arg_names.index('to')][0]
|
|
||||||
if not isinstance(to_arg_expr, StrExpr):
|
|
||||||
# not string, not supported
|
|
||||||
return None
|
|
||||||
|
|
||||||
model_string = to_arg_expr.value
|
|
||||||
if model_string == 'self':
|
|
||||||
model_fullname = api.tscope.classes[-1].fullname()
|
|
||||||
elif '.' not in model_string:
|
|
||||||
model_fullname = api.tscope.classes[-1].module_name + '.' + model_string
|
|
||||||
else:
|
|
||||||
if django_context.app_models is not None and model_string in django_context.app_models:
|
|
||||||
model_fullname = django_context.app_models[model_string]
|
|
||||||
else:
|
|
||||||
ctx.api.fail(f'Cannot find referenced model for {model_string!r}', context=ctx.context)
|
|
||||||
return None
|
|
||||||
|
|
||||||
model_info = helpers.lookup_fully_qualified_generic(model_fullname, all_modules=api.modules)
|
|
||||||
if model_info is None or not isinstance(model_info, TypeInfo):
|
|
||||||
raise helpers.IncompleteDefnException(model_fullname)
|
|
||||||
|
|
||||||
return Instance(model_info, [])
|
|
||||||
|
|
||||||
referred_to_type = arg_type.ret_type
|
|
||||||
assert isinstance(referred_to_type, Instance)
|
|
||||||
|
|
||||||
if not referred_to_type.type.has_base(fullnames.MODEL_CLASS_FULLNAME):
|
|
||||||
ctx.api.msg.fail(f'to= parameter value must be a subclass of {fullnames.MODEL_CLASS_FULLNAME!r}',
|
|
||||||
context=ctx.context)
|
|
||||||
return None
|
|
||||||
|
|
||||||
return referred_to_type
|
|
||||||
|
|
||||||
|
|
||||||
def convert_any_to_type(typ: MypyType, replacement_type: MypyType) -> MypyType:
|
|
||||||
"""
|
|
||||||
Converts any encountered Any (in typ itself, or in generic parameters) into referred_to_type
|
|
||||||
"""
|
|
||||||
if isinstance(typ, UnionType):
|
|
||||||
converted_items = []
|
|
||||||
for item in typ.items:
|
|
||||||
converted_items.append(convert_any_to_type(item, replacement_type))
|
|
||||||
return UnionType.make_union(converted_items,
|
|
||||||
line=typ.line, column=typ.column)
|
|
||||||
if isinstance(typ, Instance):
|
|
||||||
args = []
|
|
||||||
for default_arg in typ.args:
|
|
||||||
if isinstance(default_arg, AnyType):
|
|
||||||
args.append(replacement_type)
|
|
||||||
else:
|
|
||||||
args.append(default_arg)
|
|
||||||
return helpers.reparametrize_instance(typ, args)
|
|
||||||
|
|
||||||
if isinstance(typ, AnyType):
|
|
||||||
return replacement_type
|
|
||||||
|
|
||||||
return typ
|
|
||||||
|
|
||||||
|
|
||||||
def get_referred_to_model_fullname(ctx: FunctionContext, django_context: DjangoContext) -> Optional[str]:
|
def get_referred_to_model_fullname(ctx: FunctionContext, django_context: DjangoContext) -> Optional[str]:
|
||||||
to_arg_type = helpers.get_call_argument_type_by_name(ctx, 'to')
|
to_arg_type = helpers.get_call_argument_type_by_name(ctx, 'to')
|
||||||
if isinstance(to_arg_type, CallableType):
|
if isinstance(to_arg_type, CallableType):
|
||||||
@@ -97,6 +28,10 @@ def get_referred_to_model_fullname(ctx: FunctionContext, django_context: DjangoC
|
|||||||
return outer_model_info.fullname()
|
return outer_model_info.fullname()
|
||||||
if '.' not in model_string:
|
if '.' not in model_string:
|
||||||
# same file class
|
# same file class
|
||||||
|
current_module = ctx.api.tree
|
||||||
|
if model_string not in current_module.names:
|
||||||
|
ctx.api.fail(f'No model {model_string!r} defined in the current module', ctx.context)
|
||||||
|
return None
|
||||||
return outer_model_info.module_name + '.' + model_string
|
return outer_model_info.module_name + '.' + model_string
|
||||||
|
|
||||||
app_label, model_name = model_string.split('.')
|
app_label, model_name = model_string.split('.')
|
||||||
@@ -121,19 +56,15 @@ def fill_descriptor_types_for_related_field(ctx: FunctionContext, django_context
|
|||||||
return AnyType(TypeOfAny.from_error)
|
return AnyType(TypeOfAny.from_error)
|
||||||
|
|
||||||
referred_to_typeinfo = helpers.lookup_fully_qualified_generic(referred_to_fullname, ctx.api.modules)
|
referred_to_typeinfo = helpers.lookup_fully_qualified_generic(referred_to_fullname, ctx.api.modules)
|
||||||
if referred_to_typeinfo is None:
|
|
||||||
ctx.api.fail(f'Cannot resolve {referred_to_fullname!r}. Please, report it to package developers.',
|
|
||||||
ctx.context)
|
|
||||||
return AnyType(TypeOfAny.from_error)
|
|
||||||
|
|
||||||
assert isinstance(referred_to_typeinfo, TypeInfo), f'Cannot resolve {referred_to_fullname!r}'
|
assert isinstance(referred_to_typeinfo, TypeInfo), f'Cannot resolve {referred_to_fullname!r}'
|
||||||
|
|
||||||
referred_to_type = Instance(referred_to_typeinfo, [])
|
referred_to_type = Instance(referred_to_typeinfo, [])
|
||||||
|
|
||||||
default_related_field_type = set_descriptor_types_for_field(ctx)
|
default_related_field_type = set_descriptor_types_for_field(ctx)
|
||||||
# replace Any with referred_to_type
|
# replace Any with referred_to_type
|
||||||
args = []
|
args = []
|
||||||
for default_arg in default_related_field_type.args:
|
for default_arg in default_related_field_type.args:
|
||||||
args.append(convert_any_to_type(default_arg, referred_to_type))
|
args.append(helpers.convert_any_to_type(default_arg, referred_to_type))
|
||||||
|
|
||||||
return helpers.reparametrize_instance(default_related_field_type, new_args=args)
|
return helpers.reparametrize_instance(default_related_field_type, new_args=args)
|
||||||
|
|
||||||
@@ -157,6 +88,12 @@ def transform_into_proper_return_type(ctx: FunctionContext, django_context: Djan
|
|||||||
default_return_type = ctx.default_return_type
|
default_return_type = ctx.default_return_type
|
||||||
assert isinstance(default_return_type, Instance)
|
assert isinstance(default_return_type, Instance)
|
||||||
|
|
||||||
|
# bail out if we're inside migration, not supported yet
|
||||||
|
active_class = ctx.api.scope.active_class()
|
||||||
|
if active_class is not None:
|
||||||
|
if active_class.has_base(fullnames.MIGRATION_CLASS_FULLNAME):
|
||||||
|
return ctx.default_return_type
|
||||||
|
|
||||||
if helpers.has_any_of_bases(default_return_type.type, fullnames.RELATED_FIELDS_CLASSES):
|
if helpers.has_any_of_bases(default_return_type.type, fullnames.RELATED_FIELDS_CLASSES):
|
||||||
return fill_descriptor_types_for_related_field(ctx, django_context)
|
return fill_descriptor_types_for_related_field(ctx, django_context)
|
||||||
|
|
||||||
@@ -176,104 +113,6 @@ def determine_type_of_array_field(ctx: FunctionContext, django_context: DjangoCo
|
|||||||
base_type = base_field_arg_type.args[1] # extract __get__ type
|
base_type = base_field_arg_type.args[1] # extract __get__ type
|
||||||
args = []
|
args = []
|
||||||
for default_arg in default_return_type.args:
|
for default_arg in default_return_type.args:
|
||||||
args.append(convert_any_to_type(default_arg, base_type))
|
args.append(helpers.convert_any_to_type(default_arg, base_type))
|
||||||
|
|
||||||
return helpers.reparametrize_instance(default_return_type, args)
|
return helpers.reparametrize_instance(default_return_type, args)
|
||||||
|
|
||||||
|
|
||||||
# def _parse_choices_type(ctx: FunctionContext, choices_arg: Expression) -> Optional[str]:
|
|
||||||
# if isinstance(choices_arg, (TupleExpr, ListExpr)):
|
|
||||||
# # iterable of 2 element tuples of two kinds
|
|
||||||
# _, analyzed_choices = ctx.api.analyze_iterable_item_type(choices_arg)
|
|
||||||
# if isinstance(analyzed_choices, TupleType):
|
|
||||||
# first_element_type = analyzed_choices.items[0]
|
|
||||||
# if isinstance(first_element_type, Instance):
|
|
||||||
# return first_element_type.type.fullname()
|
|
||||||
|
|
||||||
|
|
||||||
# def _parse_referenced_model(ctx: FunctionContext, to_arg: Expression) -> Optional[TypeInfo]:
|
|
||||||
# if isinstance(to_arg, NameExpr) and isinstance(to_arg.node, TypeInfo):
|
|
||||||
# # reference to the model class
|
|
||||||
# return to_arg.node
|
|
||||||
#
|
|
||||||
# elif isinstance(to_arg, StrExpr):
|
|
||||||
# referenced_model_info = helpers.get_model_info(to_arg.value, ctx.api.modules)
|
|
||||||
# if referenced_model_info is not None:
|
|
||||||
# return referenced_model_info
|
|
||||||
|
|
||||||
|
|
||||||
# def parse_field_init_arguments_into_model_metadata(ctx: FunctionContext) -> None:
|
|
||||||
# outer_model = ctx.api.scope.active_class()
|
|
||||||
# if outer_model is None or not outer_model.has_base(fullnames.MODEL_CLASS_FULLNAME):
|
|
||||||
# # outside models.Model class, undetermined
|
|
||||||
# return
|
|
||||||
#
|
|
||||||
# # Determine name of the current field
|
|
||||||
# for attr_name, stmt in helpers.iter_over_class_level_assignments(outer_model.defn):
|
|
||||||
# if stmt == ctx.context:
|
|
||||||
# field_name = attr_name
|
|
||||||
# break
|
|
||||||
# else:
|
|
||||||
# return
|
|
||||||
#
|
|
||||||
# model_fields_metadata = metadata.get_fields_metadata(outer_model)
|
|
||||||
#
|
|
||||||
# # primary key
|
|
||||||
# is_primary_key = False
|
|
||||||
# primary_key_arg = helpers.get_call_argument_by_name(ctx, 'primary_key')
|
|
||||||
# if primary_key_arg:
|
|
||||||
# is_primary_key = helpers.parse_bool(primary_key_arg)
|
|
||||||
# model_fields_metadata[field_name] = {'primary_key': is_primary_key}
|
|
||||||
#
|
|
||||||
# # choices
|
|
||||||
# choices_arg = helpers.get_call_argument_by_name(ctx, 'choices')
|
|
||||||
# if choices_arg:
|
|
||||||
# choices_type_fullname = _parse_choices_type(ctx.api, choices_arg)
|
|
||||||
# if choices_type_fullname:
|
|
||||||
# model_fields_metadata[field_name]['choices_type'] = choices_type_fullname
|
|
||||||
#
|
|
||||||
# # nullability
|
|
||||||
# null_arg = helpers.get_call_argument_by_name(ctx, 'null')
|
|
||||||
# is_nullable = False
|
|
||||||
# if null_arg:
|
|
||||||
# is_nullable = helpers.parse_bool(null_arg)
|
|
||||||
# model_fields_metadata[field_name]['null'] = is_nullable
|
|
||||||
#
|
|
||||||
# # is_blankable
|
|
||||||
# blank_arg = helpers.get_call_argument_by_name(ctx, 'blank')
|
|
||||||
# is_blankable = False
|
|
||||||
# if blank_arg:
|
|
||||||
# is_blankable = helpers.parse_bool(blank_arg)
|
|
||||||
# model_fields_metadata[field_name]['blank'] = is_blankable
|
|
||||||
#
|
|
||||||
# # default
|
|
||||||
# default_arg = helpers.get_call_argument_by_name(ctx, 'default')
|
|
||||||
# if default_arg and not helpers.is_none_expr(default_arg):
|
|
||||||
# model_fields_metadata[field_name]['default_specified'] = True
|
|
||||||
#
|
|
||||||
# if helpers.has_any_of_bases(ctx.default_return_type.type, fullnames.RELATED_FIELDS_CLASSES):
|
|
||||||
# # to
|
|
||||||
# to_arg = helpers.get_call_argument_by_name(ctx, 'to')
|
|
||||||
# if to_arg:
|
|
||||||
# referenced_model = _parse_referenced_model(ctx, to_arg)
|
|
||||||
# if referenced_model is not None:
|
|
||||||
# model_fields_metadata[field_name]['to'] = referenced_model.fullname()
|
|
||||||
# else:
|
|
||||||
# model_fields_metadata[field_name]['to'] = to_arg.value
|
|
||||||
# # referenced_model = to_arg.value
|
|
||||||
# # raise helpers.IncompleteDefnException()
|
|
||||||
#
|
|
||||||
# # model_fields_metadata[field_name]['to'] = referenced_model.fullname()
|
|
||||||
# # if referenced_model is not None:
|
|
||||||
# # model_fields_metadata[field_name]['to'] = referenced_model.fullname()
|
|
||||||
# # else:
|
|
||||||
# # assert isinstance(to_arg, StrExpr)
|
|
||||||
# # model_fields_metadata[field_name]['to'] = to_arg.value
|
|
||||||
#
|
|
||||||
# # related_name
|
|
||||||
# related_name_arg = helpers.get_call_argument_by_name(ctx, 'related_name')
|
|
||||||
# if related_name_arg:
|
|
||||||
# if isinstance(related_name_arg, StrExpr):
|
|
||||||
# model_fields_metadata[field_name]['related_name'] = related_name_arg.value
|
|
||||||
# else:
|
|
||||||
# model_fields_metadata[field_name]['related_name'] = outer_model.name().lower() + '_set'
|
|
||||||
|
|||||||
@@ -108,8 +108,12 @@ def get_values_list_row_type(ctx: MethodContext, django_context: DjangoContext,
|
|||||||
|
|
||||||
|
|
||||||
def extract_proper_type_queryset_values_list(ctx: MethodContext, django_context: DjangoContext) -> MypyType:
|
def extract_proper_type_queryset_values_list(ctx: MethodContext, django_context: DjangoContext) -> MypyType:
|
||||||
|
# called on the Instance
|
||||||
assert isinstance(ctx.type, Instance)
|
assert isinstance(ctx.type, Instance)
|
||||||
assert isinstance(ctx.type.args[0], Instance)
|
|
||||||
|
# bail if queryset of Any or other non-instances
|
||||||
|
if not isinstance(ctx.type.args[0], Instance):
|
||||||
|
return AnyType(TypeOfAny.from_omitted_generics)
|
||||||
|
|
||||||
model_type = ctx.type.args[0]
|
model_type = ctx.type.args[0]
|
||||||
model_cls = django_context.get_model_class_by_fullname(model_type.type.fullname())
|
model_cls = django_context.get_model_class_by_fullname(model_type.type.fullname())
|
||||||
|
|||||||
Reference in New Issue
Block a user