From 62dac1d4688d3e2ef4c916e349f4c09cc5d2e3d5 Mon Sep 17 00:00:00 2001 From: Maxim Kurnikov Date: Thu, 25 Jul 2019 19:53:12 +0300 Subject: [PATCH] remove some asserts that could be validly triggered --- mypy.ini | 3 --- mypy_django_plugin/django/context.py | 26 ++++++++++++------- mypy_django_plugin/lib/helpers.py | 16 ++++++++---- mypy_django_plugin/main.py | 4 ++- mypy_django_plugin/transformers/fields.py | 21 ++++++++++----- .../transformers/init_create.py | 3 ++- mypy_django_plugin/transformers/meta.py | 4 +-- mypy_django_plugin/transformers/querysets.py | 6 +++-- mypy_django_plugin/transformers/settings.py | 13 ++++++---- test-data/typecheck/test_settings.yml | 16 +++++++++++- 10 files changed, 77 insertions(+), 35 deletions(-) diff --git a/mypy.ini b/mypy.ini index 0900f01..f8b1844 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,4 +1 @@ [mypy] - -[mypy-mypy_django_plugin.monkeypatch.*] -ignore_errors = True \ No newline at end of file diff --git a/mypy_django_plugin/django/context.py b/mypy_django_plugin/django/context.py index 4b7500f..778caf4 100644 --- a/mypy_django_plugin/django/context.py +++ b/mypy_django_plugin/django/context.py @@ -1,20 +1,21 @@ import os from collections import defaultdict from contextlib import contextmanager -from typing import Dict, Iterator, List, Optional, TYPE_CHECKING, Tuple, Type +from typing import TYPE_CHECKING, Dict, Iterator, List, Optional, Tuple, Type +from django.contrib.postgres.fields import ArrayField from django.core.exceptions import FieldError from django.db.models.base import Model +from django.db.models.fields import AutoField, CharField, Field from django.db.models.fields.related import ForeignKey, RelatedField from django.db.models.fields.reverse_related import ForeignObjectRel from django.db.models.sql.query import Query from django.utils.functional import cached_property from mypy.checker import TypeChecker -from mypy.nodes import TypeInfo -from mypy.types import AnyType, Instance, Type as MypyType, TypeOfAny +from mypy.types import AnyType, Instance +from mypy.types import Type as MypyType +from mypy.types import TypeOfAny -from django.contrib.postgres.fields import ArrayField -from django.db.models.fields import AutoField, CharField, Field from mypy_django_plugin.lib import helpers if TYPE_CHECKING: @@ -104,7 +105,8 @@ class DjangoFieldsContext: def get_field_get_type(self, api: TypeChecker, field: Field, *, method: str) -> MypyType: """ Get a type of __get__ for this specific Django field. """ field_info = helpers.lookup_class_typeinfo(api, field.__class__) - assert isinstance(field_info, TypeInfo) + if field_info is None: + return AnyType(TypeOfAny.unannotated) is_nullable = self.get_field_nullability(field, method) if isinstance(field, RelatedField): @@ -113,7 +115,8 @@ class DjangoFieldsContext: return self.get_field_get_type(api, primary_key_field, method=method) model_info = helpers.lookup_class_typeinfo(api, field.related_model) - assert isinstance(model_info, TypeInfo) + if model_info is None: + return AnyType(TypeOfAny.unannotated) return Instance(model_info, []) else: @@ -215,14 +218,19 @@ class DjangoContext: if isinstance(field, ForeignKey): field_name = field.name foreign_key_info = helpers.lookup_class_typeinfo(api, field.__class__) - assert isinstance(foreign_key_info, TypeInfo) + if foreign_key_info is None: + # maybe there's no type annotation for the field + expected_types[field_name] = AnyType(TypeOfAny.unannotated) + continue related_model = field.related_model if related_model._meta.proxy_for_model: related_model = field.related_model._meta.proxy_for_model related_model_info = helpers.lookup_class_typeinfo(api, related_model) - assert isinstance(related_model_info, TypeInfo) + if related_model_info is None: + expected_types[field_name] = AnyType(TypeOfAny.unannotated) + continue is_nullable = self.fields_context.get_field_nullability(field, method) foreign_key_set_type = helpers.get_private_descriptor_type(foreign_key_info, diff --git a/mypy_django_plugin/lib/helpers.py b/mypy_django_plugin/lib/helpers.py index 9b9fa55..3555e5a 100644 --- a/mypy_django_plugin/lib/helpers.py +++ b/mypy_django_plugin/lib/helpers.py @@ -1,13 +1,19 @@ from collections import OrderedDict -from typing import Any, Dict, List, Optional, Set, TYPE_CHECKING, Union, cast +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Union, cast from mypy import checker from mypy.checker import TypeChecker from mypy.mro import calculate_mro -from mypy.nodes import (Block, ClassDef, Expression, GDEF, MDEF, MemberExpr, MypyFile, NameExpr, StrExpr, SymbolNode, - SymbolTable, SymbolTableNode, TypeInfo, Var) -from mypy.plugin import AttributeContext, CheckerPluginInterface, FunctionContext, MethodContext -from mypy.types import AnyType, Instance, NoneTyp, TupleType, Type as MypyType, TypeOfAny, TypedDictType, UnionType +from mypy.nodes import ( + GDEF, MDEF, Block, ClassDef, Expression, MemberExpr, MypyFile, NameExpr, StrExpr, SymbolNode, SymbolTable, + SymbolTableNode, TypeInfo, Var, +) +from mypy.plugin import ( + AttributeContext, CheckerPluginInterface, FunctionContext, MethodContext, +) +from mypy.types import AnyType, Instance, NoneTyp, TupleType +from mypy.types import Type as MypyType +from mypy.types import TypedDictType, TypeOfAny, UnionType if TYPE_CHECKING: from mypy_django_plugin.django.context import DjangoContext diff --git a/mypy_django_plugin/main.py b/mypy_django_plugin/main.py index 4f2fd49..dbd8342 100644 --- a/mypy_django_plugin/main.py +++ b/mypy_django_plugin/main.py @@ -6,7 +6,9 @@ from django.db.models.fields.related import RelatedField from mypy.errors import Errors from mypy.nodes import MypyFile, TypeInfo from mypy.options import Options -from mypy.plugin import AnalyzeTypeContext, AttributeContext, ClassDefContext, FunctionContext, MethodContext, Plugin +from mypy.plugin import ( + AnalyzeTypeContext, AttributeContext, ClassDefContext, FunctionContext, MethodContext, Plugin, +) from mypy.types import Type as MypyType from mypy_django_plugin.django.context import DjangoContext diff --git a/mypy_django_plugin/transformers/fields.py b/mypy_django_plugin/transformers/fields.py index 23d095e..b0793d7 100644 --- a/mypy_django_plugin/transformers/fields.py +++ b/mypy_django_plugin/transformers/fields.py @@ -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, NameExpr +from mypy.nodes import AssignmentStmt, NameExpr, TypeInfo from mypy.plugin import FunctionContext from mypy.types import AnyType, Instance from mypy.types import Type as MypyType @@ -22,7 +22,8 @@ def _get_current_field_from_assignment(ctx: FunctionContext, django_context: Dja for stmt in outer_model_info.defn.defs.body: if isinstance(stmt, AssignmentStmt): if stmt.rvalue == ctx.context: - assert isinstance(stmt.lvalues[0], NameExpr) + if not isinstance(stmt.lvalues[0], NameExpr): + return None field_name = stmt.lvalues[0].name break if field_name is None: @@ -50,16 +51,24 @@ def fill_descriptor_types_for_related_field(ctx: FunctionContext, django_context typechecker_api = helpers.get_typechecker_api(ctx) related_model_info = helpers.lookup_class_typeinfo(typechecker_api, related_model) - assert isinstance(related_model_info, TypeInfo) + if related_model_info is None: + # maybe no type stub + related_model_type = AnyType(TypeOfAny.unannotated) + else: + related_model_type = Instance(related_model_info, []) # type: ignore related_model_to_set_info = helpers.lookup_class_typeinfo(typechecker_api, related_model_to_set) - assert isinstance(related_model_to_set_info, TypeInfo) + if related_model_to_set_info is None: + # maybe no type stub + related_model_to_set_type = AnyType(TypeOfAny.unannotated) + else: + related_model_to_set_type = Instance(related_model_to_set_info, []) # type: ignore default_related_field_type = set_descriptor_types_for_field(ctx) # replace Any with referred_to_type args = [ - helpers.convert_any_to_type(default_related_field_type.args[0], Instance(related_model_to_set_info, [])), - helpers.convert_any_to_type(default_related_field_type.args[1], Instance(related_model_info, [])), + helpers.convert_any_to_type(default_related_field_type.args[0], related_model_to_set_type), + helpers.convert_any_to_type(default_related_field_type.args[1], related_model_type), ] return helpers.reparametrize_instance(default_related_field_type, new_args=args) diff --git a/mypy_django_plugin/transformers/init_create.py b/mypy_django_plugin/transformers/init_create.py index e725948..d143234 100644 --- a/mypy_django_plugin/transformers/init_create.py +++ b/mypy_django_plugin/transformers/init_create.py @@ -2,7 +2,8 @@ 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, Type as MypyType +from mypy.types import Instance +from mypy.types import Type as MypyType from mypy_django_plugin.django.context import DjangoContext from mypy_django_plugin.lib import helpers diff --git a/mypy_django_plugin/transformers/meta.py b/mypy_django_plugin/transformers/meta.py index 3046cbd..007d8e4 100644 --- a/mypy_django_plugin/transformers/meta.py +++ b/mypy_django_plugin/transformers/meta.py @@ -1,5 +1,4 @@ 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 @@ -12,7 +11,8 @@ 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(helpers.get_typechecker_api(ctx), field_fullname) - assert isinstance(field_info, TypeInfo) + if field_info is None: + return AnyType(TypeOfAny.unannotated) return Instance(field_info, [AnyType(TypeOfAny.explicit), AnyType(TypeOfAny.explicit)]) diff --git a/mypy_django_plugin/transformers/querysets.py b/mypy_django_plugin/transformers/querysets.py index b1bdce3..7145d39 100644 --- a/mypy_django_plugin/transformers/querysets.py +++ b/mypy_django_plugin/transformers/querysets.py @@ -5,8 +5,10 @@ from django.core.exceptions import FieldError from django.db.models.base import Model from mypy.newsemanal.typeanal import TypeAnalyser from mypy.nodes import Expression, NameExpr, TypeInfo -from mypy.plugin import FunctionContext, MethodContext, AnalyzeTypeContext -from mypy.types import AnyType, Instance, Type as MypyType, TypeOfAny +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_django_plugin.django.context import DjangoContext from mypy_django_plugin.lib import fullnames, helpers diff --git a/mypy_django_plugin/transformers/settings.py b/mypy_django_plugin/transformers/settings.py index e8ec089..ba6490b 100644 --- a/mypy_django_plugin/transformers/settings.py +++ b/mypy_django_plugin/transformers/settings.py @@ -1,6 +1,8 @@ -from mypy.nodes import MemberExpr, TypeInfo +from mypy.nodes import MemberExpr from mypy.plugin import AttributeContext, FunctionContext -from mypy.types import Instance, Type as MypyType, TypeType +from mypy.types import AnyType, Instance +from mypy.types import Type as MypyType +from mypy.types import TypeOfAny, TypeType from mypy_django_plugin.django.context import DjangoContext from mypy_django_plugin.lib import helpers @@ -11,9 +13,10 @@ 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, - helpers.get_typechecker_api(ctx).modules) - assert isinstance(model_info, TypeInfo) + model_info = helpers.lookup_fully_qualified_typeinfo(helpers.get_typechecker_api(ctx), + model_cls_fullname) + if model_info is None: + return AnyType(TypeOfAny.unannotated) return TypeType(Instance(model_info, [])) diff --git a/test-data/typecheck/test_settings.yml b/test-data/typecheck/test_settings.yml index 4915c6f..507c319 100644 --- a/test-data/typecheck/test_settings.yml +++ b/test-data/typecheck/test_settings.yml @@ -33,4 +33,18 @@ reveal_type(settings.NOT_EXISTING) out: | main:2: note: Revealed type is 'Any' - main:2: error: 'Settings' object has no attribute 'NOT_EXISTING' \ No newline at end of file + main:2: error: 'Settings' object has no attribute 'NOT_EXISTING' + +- case: override_default_setting_with_different_type_in_the_different_module + custom_settings: | + from settings.basic_settings import * + main: | + from django.conf import settings + reveal_type(settings.MEDIA_ROOT) # N: Revealed type is 'pathlib.Path' + reveal_type(settings.MEDIA_ROOT / 'part') # N: Revealed type is 'pathlib.Path*' + files: + - path: settings/__init__.py + - path: settings/basic_settings.py + content: | + from pathlib import Path + MEDIA_ROOT = Path()