Merge pull request #238 from mkurnikov/mypy-750

Mypy 0.750 support
This commit is contained in:
Maksim Kurnikov
2019-11-30 22:27:50 +03:00
committed by GitHub
15 changed files with 93 additions and 37 deletions

View File

@@ -22,6 +22,7 @@ pip install django-stubs
| django-stubs | mypy version | django version | python version
| ------------ | ---- | ---- | ---- |
| 1.3.0 | 0.750 | 2.2.x | ^3.6
| 1.2.0 | 0.730 | 2.2.x | ^3.6
| 1.1.0 | 0.720 | 2.2.x | ^3.6
| 0.12.x | old semantic analyzer (<0.711), dmypy support | 2.1.x | ^3.6

View File

@@ -1,9 +1,22 @@
import decimal
import warnings
from contextlib import contextmanager
from decimal import Decimal
from io import StringIO
from typing import Any, Callable, Dict, Iterable, Iterator, List, Mapping, Optional, Set, Tuple, Type, Union
from typing import (
Any,
Callable,
Dict,
Iterable,
Iterator,
List,
Mapping,
Optional,
Set,
Tuple,
Type,
Union,
ContextManager,
)
from django.apps.registry import Apps
from django.core.checks.registry import CheckRegistry
@@ -86,7 +99,7 @@ class ignore_warnings(TestContextDecorator):
ignore_kwargs: Dict[str, Any] = ...
filter_func: Callable = ...
def __init__(self, **kwargs: Any) -> None: ...
catch_warnings: warnings.catch_warnings = ...
catch_warnings: ContextManager[Optional[list]] = ...
requires_tz_support: Any

View File

@@ -367,7 +367,7 @@ class DjangoContext:
lookup_type: MypyType = lookup_base.args[0]
# if it's Field, consider lookup_type a __get__ of current field
if (isinstance(lookup_type, Instance)
and lookup_type.type.fullname() == fullnames.FIELD_FULLNAME):
and lookup_type.type.fullname == fullnames.FIELD_FULLNAME):
field_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), field.__class__)
if field_info is None:
return AnyType(TypeOfAny.explicit)

View File

@@ -179,10 +179,10 @@ def add_new_class_for_module(module: MypyFile, name: str, bases: List[Instance],
# make new class expression
classdef = ClassDef(new_class_unique_name, Block([]))
classdef.fullname = module.fullname() + '.' + new_class_unique_name
classdef.fullname = module.fullname + '.' + new_class_unique_name
# make new TypeInfo
new_typeinfo = TypeInfo(SymbolTable(), classdef, module.fullname())
new_typeinfo = TypeInfo(SymbolTable(), classdef, module.fullname)
new_typeinfo.bases = bases
calculate_mro(new_typeinfo)
new_typeinfo.calculate_metaclass_type()
@@ -191,7 +191,7 @@ def add_new_class_for_module(module: MypyFile, name: str, bases: List[Instance],
for field_name, field_type in fields.items():
var = Var(field_name, type=field_type)
var.info = new_typeinfo
var._fullname = new_typeinfo.fullname() + '.' + field_name
var._fullname = new_typeinfo.fullname + '.' + field_name
new_typeinfo.names[field_name] = SymbolTableNode(MDEF, var, plugin_generated=True)
classdef.info = new_typeinfo
@@ -276,7 +276,7 @@ def get_typechecker_api(ctx: Union[AttributeContext, MethodContext, FunctionCont
def is_model_subclass_info(info: TypeInfo, django_context: 'DjangoContext') -> bool:
return (info.fullname() in django_context.all_registered_model_class_fullnames
return (info.fullname in django_context.all_registered_model_class_fullnames
or info.has_base(fullnames.MODEL_CLASS_FULLNAME))
@@ -293,7 +293,7 @@ def add_new_sym_for_info(info: TypeInfo, *, name: str, sym_type: MypyType) -> No
var = Var(name=name, type=sym_type)
# var.info: type of the object variable is bound to
var.info = info
var._fullname = info.fullname() + '.' + name
var._fullname = info.fullname + '.' + name
var.is_initialized_in_class = True
var.is_inferred = True
info.names[name] = SymbolTableNode(MDEF, var,

View File

@@ -121,17 +121,17 @@ class NewSemanalDjangoPlugin(Plugin):
def get_additional_deps(self, file: MypyFile) -> List[Tuple[int, str, int]]:
# for settings
if file.fullname() == 'django.conf' and self.django_context.django_settings_module:
if file.fullname == 'django.conf' and self.django_context.django_settings_module:
return [self._new_dependency(self.django_context.django_settings_module)]
# for values / values_list
if file.fullname() == 'django.db.models':
if file.fullname == 'django.db.models':
return [self._new_dependency('mypy_extensions'), self._new_dependency('typing')]
# for `get_user_model()`
if self.django_context.settings:
if (file.fullname() == 'django.contrib.auth'
or file.fullname() in {'django.http', 'django.http.request'}):
if (file.fullname == 'django.contrib.auth'
or file.fullname in {'django.http', 'django.http.request'}):
auth_user_model_name = self.django_context.settings.AUTH_USER_MODEL
try:
auth_user_module = self.django_context.apps_registry.get_model(auth_user_model_name).__module__
@@ -141,7 +141,7 @@ class NewSemanalDjangoPlugin(Plugin):
return [self._new_dependency(auth_user_module)]
# ensure that all mentioned to='someapp.SomeModel' are loaded with corresponding related Fields
defined_model_classes = self.django_context.model_modules.get(file.fullname())
defined_model_classes = self.django_context.model_modules.get(file.fullname)
if not defined_model_classes:
return []
deps = set()
@@ -153,13 +153,13 @@ class NewSemanalDjangoPlugin(Plugin):
if related_model_cls is None:
continue
related_model_module = related_model_cls.__module__
if related_model_module != file.fullname():
if related_model_module != file.fullname:
deps.add(self._new_dependency(related_model_module))
# reverse relations
for relation in model_class._meta.related_objects:
related_model_cls = self.django_context.get_field_related_model_cls(relation)
related_model_module = related_model_cls.__module__
if related_model_module != file.fullname():
if related_model_module != file.fullname:
deps.add(self._new_dependency(related_model_module))
return list(deps)

View File

@@ -29,7 +29,7 @@ def _get_current_field_from_assignment(ctx: FunctionContext, django_context: Dja
if field_name is None:
return None
model_cls = django_context.get_model_class_by_fullname(outer_model_info.fullname())
model_cls = django_context.get_model_class_by_fullname(outer_model_info.fullname)
if model_cls is None:
return None

View File

@@ -54,7 +54,7 @@ def typecheck_model_method(ctx: Union[FunctionContext, MethodContext], django_co
def redefine_and_typecheck_model_init(ctx: FunctionContext, django_context: DjangoContext) -> MypyType:
assert isinstance(ctx.default_return_type, Instance)
model_fullname = ctx.default_return_type.type.fullname()
model_fullname = ctx.default_return_type.type.fullname
model_cls = django_context.get_model_class_by_fullname(model_fullname)
if model_cls is None:
return ctx.default_return_type
@@ -67,7 +67,7 @@ def redefine_and_typecheck_model_create(ctx: MethodContext, django_context: Djan
# only work with ctx.default_return_type = model Instance
return ctx.default_return_type
model_fullname = ctx.default_return_type.type.fullname()
model_fullname = ctx.default_return_type.type.fullname
model_cls = django_context.get_model_class_by_fullname(model_fullname)
if model_cls is None:
return ctx.default_return_type

View File

@@ -28,7 +28,7 @@ def return_proper_field_type_from_get_field(ctx: MethodContext, django_context:
if not isinstance(model_type, Instance):
return ctx.default_return_type
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)
if model_cls is None:
return ctx.default_return_type

View File

@@ -1,5 +1,5 @@
from collections import OrderedDict
from typing import Type
from typing import List, Tuple, Type
from django.db.models.base import Model
from django.db.models.fields import DateField, DateTimeField
@@ -7,10 +7,11 @@ from django.db.models.fields.related import ForeignKey
from django.db.models.fields.reverse_related import (
ManyToManyRel, ManyToOneRel, OneToOneRel,
)
from mypy.nodes import ARG_STAR2, Argument, Context, TypeInfo, Var
from mypy.nodes import ARG_STAR2, Argument, Context, FuncDef, TypeInfo, Var
from mypy.plugin import ClassDefContext
from mypy.plugins import common
from mypy.types import AnyType, Instance
from mypy.plugins.common import add_method
from mypy.types import AnyType, CallableType, Instance
from mypy.types import Type as MypyType
from mypy.types import TypeOfAny
@@ -43,7 +44,7 @@ class ModelClassInitializer:
var = Var(name=name, type=typ)
# 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._fullname = self.model_classdef.info.fullname + '.' + name
var.is_initialized_in_class = True
var.is_inferred = True
return var
@@ -126,7 +127,7 @@ class AddRelatedModelsId(ModelClassInitializer):
class AddManagers(ModelClassInitializer):
def _is_manager_any(self, typ: Instance) -> bool:
return typ.type.fullname() == fullnames.MANAGER_CLASS_FULLNAME and type(typ.args[0]) == AnyType
return typ.type.fullname == fullnames.MANAGER_CLASS_FULLNAME and type(typ.args[0]) == AnyType
def run_with_model_cls(self, model_cls: Type[Model]) -> None:
for manager_name, manager in model_cls._meta.managers_map.items():
@@ -158,18 +159,46 @@ class AddManagers(ModelClassInitializer):
bases=bases,
fields=OrderedDict())
# copy fields to a new manager
new_cls_def_context = ClassDefContext(cls=custom_manager_info.defn,
reason=self.ctx.reason,
api=self.api)
custom_manager_type = Instance(custom_manager_info, [Instance(self.model_classdef.info, [])])
for name, sym in manager_info.names.items():
# replace self type with new class, if copying method
if isinstance(sym.node, FuncDef):
arguments, return_type = self.prepare_new_method_arguments(sym.node)
add_method(new_cls_def_context,
name,
args=arguments,
return_type=return_type,
self_type=custom_manager_type)
continue
new_sym = sym.copy()
if isinstance(new_sym.node, Var):
new_var = Var(name, type=sym.type)
new_var.info = custom_manager_info
new_var._fullname = custom_manager_info.fullname() + '.' + name
new_var._fullname = custom_manager_info.fullname + '.' + name
new_sym.node = new_var
custom_manager_info.names[name] = new_sym
custom_manager_type = Instance(custom_manager_info, [Instance(self.model_classdef.info, [])])
self.add_new_node_to_model_class(manager_name, custom_manager_type)
def prepare_new_method_arguments(self, node: FuncDef) -> Tuple[List[Argument], MypyType]:
arguments = []
for argument in node.arguments[1:]:
if argument.type_annotation is None:
argument.type_annotation = AnyType(TypeOfAny.unannotated)
arguments.append(argument)
if isinstance(node.type, CallableType):
return_type = node.type.ret_type
else:
return_type = AnyType(TypeOfAny.unannotated)
return arguments, return_type
class AddDefaultManagerAttribute(ModelClassInitializer):
def run_with_model_cls(self, model_cls: Type[Model]) -> None:

View File

@@ -16,7 +16,7 @@ def typecheck_queryset_filter(ctx: MethodContext, django_context: DjangoContext)
if not ctx.type.args or not isinstance(ctx.type.args[0], Instance):
return ctx.default_return_type
model_cls_fullname = ctx.type.args[0].type.fullname()
model_cls_fullname = ctx.type.args[0].type.fullname
model_cls = django_context.get_model_class_by_fullname(model_cls_fullname)
if model_cls is None:
return ctx.default_return_type
@@ -44,7 +44,7 @@ def typecheck_queryset_filter(ctx: MethodContext, django_context: DjangoContext)
def resolve_combinable_type(combinable_type: Instance, django_context: DjangoContext) -> MypyType:
if combinable_type.type.fullname() != fullnames.F_EXPRESSION_FULLNAME:
if combinable_type.type.fullname != fullnames.F_EXPRESSION_FULLNAME:
# Combinables aside from F expressions are unsupported
return AnyType(TypeOfAny.explicit)

View File

@@ -117,7 +117,7 @@ def extract_proper_type_queryset_values_list(ctx: MethodContext, django_context:
if model_type is None:
return AnyType(TypeOfAny.from_omitted_generics)
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)
if model_cls is None:
return ctx.default_return_type
@@ -166,7 +166,7 @@ def extract_proper_type_queryset_values(ctx: MethodContext, django_context: Djan
if model_type is None:
return AnyType(TypeOfAny.from_omitted_generics)
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)
if model_cls is None:
return ctx.default_return_type

View File

@@ -37,7 +37,6 @@ IGNORED_ERRORS = {
'Argument after ** must be a mapping',
'note:',
re.compile(r'Item "None" of "[a-zA-Z_ ,\[\]]+" has no attribute'),
'"Optional[List[_Record]]"',
'"Callable[..., None]" has no attribute',
'does not return a value',
'has no attribute "alternatives"',
@@ -257,7 +256,6 @@ IGNORED_ERRORS = {
'Item "Field[Any, Any]" of "Union[Field[Any, Any], ForeignObjectRel]" has no attribute',
'Incompatible types in assignment (expression has type "Type[Person',
'Incompatible types in assignment (expression has type "FloatModel", variable has type',
'No overload variant of "bytes" matches argument type "Container[int]"',
'"ImageFile" has no attribute "was_opened"',
],
'model_indexes': [
@@ -278,6 +276,7 @@ IGNORED_ERRORS = {
'Incompatible type for "department" of "Worker"',
'"PickledModel" has no attribute',
'"Department" has no attribute "evaluate"',
'Unsupported target for indexed assignment',
],
'model_formsets_regress': [
'Incompatible types in assignment (expression has type "int", target has type "str")',
@@ -352,7 +351,8 @@ IGNORED_ERRORS = {
'Argument 1 to "append" of "list" has incompatible type "Tuple[Any, Any, Optional[Any], Any]";'
],
'sites_framework': [
'expression has type "CurrentSiteManager[CustomArticle]", base class "AbstractArticle"'
'expression has type "CurrentSiteManager[CustomArticle]", base class "AbstractArticle"',
"Name 'Optional' is not defined",
],
'syndication_tests': [
'List or tuple expected as variable arguments'

View File

@@ -21,7 +21,7 @@ with open('README.md', 'r') as f:
readme = f.read()
dependencies = [
'mypy>=0.740,<0.750',
'mypy>=0.750,<0.760',
'typing-extensions',
'django',
]

View File

@@ -307,9 +307,18 @@
- case: custom_manager_returns_proper_model_types
main: |
from myapp.models import User
reveal_type(User.objects.get()) # N: Revealed type is 'myapp.models.User*'
reveal_type(User.objects) # N: Revealed type is 'myapp.models.User_MyManager[myapp.models.User]'
reveal_type(User.objects.select_related()) # N: Revealed type is 'myapp.models.User_MyManager[myapp.models.User]'
reveal_type(User.objects.get()) # N: Revealed type is 'myapp.models.User*'
reveal_type(User.objects.get_instance()) # N: Revealed type is 'builtins.int'
reveal_type(User.objects.get_instance_untyped('hello')) # N: Revealed type is 'Any'
from myapp.models import ChildUser
reveal_type(ChildUser.objects) # N: Revealed type is 'myapp.models.ChildUser_MyManager[myapp.models.ChildUser]'
reveal_type(ChildUser.objects.select_related()) # N: Revealed type is 'myapp.models.ChildUser_MyManager[myapp.models.ChildUser]'
reveal_type(ChildUser.objects.get()) # N: Revealed type is 'myapp.models.ChildUser*'
reveal_type(ChildUser.objects.get_instance()) # N: Revealed type is 'builtins.int'
reveal_type(ChildUser.objects.get_instance_untyped('hello')) # N: Revealed type is 'Any'
installed_apps:
- myapp
files:
@@ -320,5 +329,9 @@
class MyManager(models.Manager):
def get_instance(self) -> int:
pass
def get_instance_untyped(self, name):
pass
class User(models.Model):
objects = MyManager()
class ChildUser(models.Model):
objects = MyManager()