attempt to add flake8 and isort

This commit is contained in:
Maxim Kurnikov
2019-03-01 02:07:53 +03:00
parent 70c3126348
commit c962b8ac68
16 changed files with 142 additions and 72 deletions

View File

@@ -4,10 +4,6 @@ dist: xenial
sudo: required
jobs:
include:
- name: Typecheck Django test suite
python: 3.7
script: 'python ./scripts/typecheck_tests.py'
- name: Run plugin test suite with python 3.7
python: 3.7
script: |
@@ -20,10 +16,22 @@ jobs:
set -e
pytest
- name: Typecheck Django test suite
python: 3.7
script: 'python ./scripts/typecheck_tests.py'
- name: Lint with black
python: 3.7
script: 'black --check --line-length=120 django-stubs/'
- name: Lint plugin code with flake8
python: 3.7
script: 'flake8'
- name: Lint plugin code with isort
python: 3.7
script: 'isort --check'
before_install: |
# Upgrade pip, setuptools, and wheel
pip install -U pip setuptools wheel

View File

@@ -1,3 +1,5 @@
black
pytest-mypy-plugins
flake8
isort==4.3.4
-e .

View File

@@ -1,5 +1,5 @@
from configparser import ConfigParser
from typing import List, Optional
from typing import Optional
from dataclasses import dataclass
@@ -16,10 +16,12 @@ class Config:
if not ini_config.has_section('mypy_django_plugin'):
raise ValueError('Invalid config file: no [mypy_django_plugin] section')
django_settings = ini_config.get('mypy_django_plugin', 'django_settings',
fallback=None)
if django_settings:
django_settings = ini_config.get('mypy_django_plugin', 'django_settings')
if type(django_settings) == object:
django_settings = None
else:
django_settings = django_settings.strip()
return Config(django_settings_module=django_settings,
ignore_missing_settings=ini_config.get('mypy_django_plugin', 'ignore_missing_settings',
fallback=False))
ignore_missing_settings=bool(ini_config.get('mypy_django_plugin', 'ignore_missing_settings',
fallback=False)))

View File

@@ -2,10 +2,13 @@ import typing
from typing import Dict, Optional
from mypy.checker import TypeChecker
from mypy.nodes import AssignmentStmt, ClassDef, Expression, ImportedName, Lvalue, MypyFile, NameExpr, SymbolNode, \
TypeInfo
from mypy.nodes import (
AssignmentStmt, ClassDef, Expression, ImportedName, Lvalue, MypyFile, NameExpr, SymbolNode, TypeInfo,
)
from mypy.plugin import FunctionContext, MethodContext
from mypy.types import AnyType, Instance, NoneTyp, Type, TypeOfAny, TypeVarType, UnionType
from mypy.types import (
AnyType, Instance, NoneTyp, Type, TypeOfAny, TypeVarType, UnionType,
)
MODEL_CLASS_FULLNAME = 'django.db.models.base.Model'
FIELD_FULLNAME = 'django.db.models.fields.Field'

View File

@@ -2,19 +2,28 @@ import os
from typing import Callable, Dict, Optional, Union, cast
from mypy.checker import TypeChecker
from mypy.nodes import MemberExpr, TypeInfo, NameExpr
from mypy.nodes import MemberExpr, NameExpr, TypeInfo
from mypy.options import Options
from mypy.plugin import AttributeContext, ClassDefContext, FunctionContext, MethodContext, Plugin
from mypy.types import AnyType, Instance, Type, TypeOfAny, TypeType, UnionType, CallableType, NoneTyp
from mypy.plugin import (
AttributeContext, ClassDefContext, FunctionContext, MethodContext, Plugin,
)
from mypy.types import (
AnyType, CallableType, Instance, NoneTyp, Type, TypeOfAny, TypeType, UnionType,
)
from mypy_django_plugin import helpers, monkeypatch
from mypy_django_plugin.config import Config
from mypy_django_plugin.transformers import fields, init_create
from mypy_django_plugin.transformers.forms import make_meta_nested_class_inherit_from_any
from mypy_django_plugin.transformers.migrations import determine_model_cls_from_string_for_migrations, \
get_string_value_from_expr
from mypy_django_plugin.transformers.forms import (
make_meta_nested_class_inherit_from_any,
)
from mypy_django_plugin.transformers.migrations import (
determine_model_cls_from_string_for_migrations, get_string_value_from_expr,
)
from mypy_django_plugin.transformers.models import process_model_class
from mypy_django_plugin.transformers.settings import AddSettingValuesToDjangoConfObject, get_settings_metadata
from mypy_django_plugin.transformers.settings import (
AddSettingValuesToDjangoConfObject, get_settings_metadata,
)
def transform_model_class(ctx: ClassDefContext) -> None:

View File

@@ -1,4 +0,0 @@
from .dependencies import (add_modules_as_a_source_seed_files,
inject_modules_as_dependencies_for_django_conf_settings,
restore_original_load_graph,
restore_original_dependencies_handling)

View File

@@ -3,9 +3,11 @@ from typing import Optional, cast
from mypy.checker import TypeChecker
from mypy.nodes import ListExpr, NameExpr, StrExpr, TupleExpr, TypeInfo, Var
from mypy.plugin import FunctionContext
from mypy.types import AnyType, CallableType, Instance, TupleType, Type, TypeOfAny, UnionType
from mypy.types import (
AnyType, CallableType, Instance, TupleType, Type, TypeOfAny, UnionType,
)
from mypy_django_plugin import helpers
from mypy_django_plugin.transformers.models import iter_over_assignments
def extract_referred_to_type(ctx: FunctionContext) -> Optional[Instance]:
@@ -154,7 +156,7 @@ def record_field_properties_into_outer_model_class(ctx: FunctionContext) -> None
return
field_name = None
for name_expr, stmt in iter_over_assignments(outer_model.defn):
for name_expr, stmt in helpers.iter_over_assignments(outer_model.defn):
if stmt == ctx.context and isinstance(name_expr, NameExpr):
field_name = name_expr.name
break

View File

@@ -1,4 +1,5 @@
from mypy.plugin import ClassDefContext
from mypy_django_plugin import helpers

View File

@@ -4,8 +4,8 @@ from mypy.checker import TypeChecker
from mypy.nodes import TypeInfo, Var
from mypy.plugin import FunctionContext, MethodContext
from mypy.types import AnyType, Instance, Type, TypeOfAny
from mypy_django_plugin import helpers
from mypy_django_plugin.helpers import extract_field_setter_type, extract_explicit_set_type_of_model_primary_key, get_fields_metadata
from mypy_django_plugin.transformers.fields import get_private_descriptor_type
@@ -106,7 +106,7 @@ def redefine_and_typecheck_model_create(ctx: MethodContext) -> Type:
def extract_choices_type(model: TypeInfo, field_name: str) -> Optional[str]:
field_metadata = get_fields_metadata(model).get(field_name, {})
field_metadata = helpers.get_fields_metadata(model).get(field_name, {})
if 'choices' in field_metadata:
return field_metadata['choices']
return None
@@ -117,7 +117,7 @@ def extract_expected_types(ctx: FunctionContext, model: TypeInfo,
api = cast(TypeChecker, ctx.api)
expected_types: Dict[str, Type] = {}
primary_key_type = extract_explicit_set_type_of_model_primary_key(model)
primary_key_type = helpers.extract_explicit_set_type_of_model_primary_key(model)
if not primary_key_type:
# no explicit primary key, set pk to Any and add id
primary_key_type = AnyType(TypeOfAny.special_form)
@@ -143,7 +143,7 @@ def extract_expected_types(ctx: FunctionContext, model: TypeInfo,
expected_types[name + '_id'] = AnyType(TypeOfAny.from_unimported_type)
elif isinstance(typ, Instance):
field_type = extract_field_setter_type(typ)
field_type = helpers.extract_field_setter_type(typ)
if field_type is None:
continue
@@ -156,8 +156,9 @@ def extract_expected_types(ctx: FunctionContext, model: TypeInfo,
if is_nullable:
referred_to_model = helpers.make_required(typ.args[1])
if isinstance(referred_to_model, Instance) and referred_to_model.type.has_base(helpers.MODEL_CLASS_FULLNAME):
pk_type = extract_explicit_set_type_of_model_primary_key(referred_to_model.type)
if isinstance(referred_to_model, Instance) and referred_to_model.type.has_base(
helpers.MODEL_CLASS_FULLNAME):
pk_type = helpers.extract_explicit_set_type_of_model_primary_key(referred_to_model.type)
if not pk_type:
# extract set type of AutoField
autofield_info = api.lookup_typeinfo('django.db.models.fields.AutoField')
@@ -170,7 +171,7 @@ def extract_expected_types(ctx: FunctionContext, model: TypeInfo,
expected_types[name + '_id'] = related_primary_key_type
field_metadata = get_fields_metadata(model).get(name, {})
field_metadata = helpers.get_fields_metadata(model).get(name, {})
if field_type:
# related fields could be None in __init__ (but should be specified before save())
if helpers.has_any_of_bases(typ.type, (helpers.FOREIGN_KEY_FULLNAME,
@@ -178,7 +179,8 @@ def extract_expected_types(ctx: FunctionContext, model: TypeInfo,
field_type = helpers.make_optional(field_type)
# if primary_key=True and default specified
elif field_metadata.get('primary_key', False) and field_metadata.get('default_specified', False):
elif field_metadata.get('primary_key', False) and field_metadata.get('default_specified',
False):
field_type = helpers.make_optional(field_type)
expected_types[name] = field_type

View File

@@ -4,6 +4,7 @@ from mypy.checker import TypeChecker
from mypy.nodes import Expression, StrExpr, TypeInfo
from mypy.plugin import MethodContext
from mypy.types import Instance, Type, TypeType
from mypy_django_plugin import helpers

View File

@@ -2,14 +2,16 @@ from abc import ABCMeta, abstractmethod
from typing import Dict, Iterator, List, Optional, Tuple, cast
import dataclasses
from mypy.nodes import ARG_STAR, ARG_STAR2, Argument, CallExpr, ClassDef, Expression, IndexExpr, \
Lvalue, MDEF, MemberExpr, MypyFile, NameExpr, StrExpr, SymbolTableNode, TypeInfo, Var
from mypy.nodes import (
ARG_STAR, ARG_STAR2, MDEF, Argument, CallExpr, ClassDef, Expression, IndexExpr, Lvalue, MemberExpr, MypyFile,
NameExpr, StrExpr, SymbolTableNode, TypeInfo, Var,
)
from mypy.plugin import ClassDefContext
from mypy.plugins.common import add_method
from mypy.semanal import SemanticAnalyzerPass2
from mypy.types import AnyType, Instance, NoneTyp, TypeOfAny
from mypy_django_plugin import helpers
from mypy_django_plugin.helpers import iter_over_assignments
@dataclasses.dataclass
@@ -48,7 +50,7 @@ class ModelClassInitializer(metaclass=ABCMeta):
def iter_call_assignments(klass: ClassDef) -> Iterator[Tuple[Lvalue, CallExpr]]:
for lvalue, rvalue in iter_over_assignments(klass):
for lvalue, rvalue in helpers.iter_over_assignments(klass):
if isinstance(rvalue, CallExpr):
yield lvalue, rvalue

View File

@@ -1,6 +1,8 @@
from typing import Iterable, List, Optional, cast
from mypy.nodes import ClassDef, Context, ImportAll, MypyFile, SymbolNode, SymbolTableNode, TypeInfo, Var
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
@@ -56,7 +58,7 @@ def load_settings_from_names(settings_classdef: ClassDef,
settings_classdef.info.names[name] = copied
else:
var = Var(name, AnyType(TypeOfAny.unannotated))
var.info = api.named_type('__builtins__.object').type
var.info = api.named_type('__builtins__.object').type # outer class type
settings_classdef.info.names[name] = SymbolTableNode(sym.kind, var)
settings_metadata[name] = module.fullname()
@@ -67,11 +69,12 @@ def get_import_star_modules(api: SemanticAnalyzerPass2, module: MypyFile) -> Lis
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)
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)):
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

View File

@@ -1,5 +1,4 @@
[pytest]
testpaths = ./test-data
addopts =
--tb=native

View File

@@ -37,7 +37,8 @@ IGNORED_ERRORS = {
# settings
re.compile(r'Module has no attribute "[A-Z_]+"'),
# attributes assigned to test functions
re.compile(r'"Callable\[(\[(Any(, )?)*((, )?VarArg\(Any\))?((, )?KwArg\(Any\))?\]|\.\.\.), Any\]" has no attribute'),
re.compile(
r'"Callable\[(\[(Any(, )?)*((, )?VarArg\(Any\))?((, )?KwArg\(Any\))?\]|\.\.\.), Any\]" has no attribute'),
# assign empty tuple
re.compile(r'Incompatible types in assignment \(expression has type "Tuple\[\]", '
r'variable has type "Tuple\[[A-Za-z, ]+\]"'),
@@ -54,8 +55,9 @@ IGNORED_ERRORS = {
'ValuesIterable',
'Value of type "Optional[Dict[str, Any]]" is not indexable',
'Argument 1 to "len" has incompatible type "Optional[List[_Record]]"; expected "Sized"',
'Argument 1 to "loads" has incompatible type "Union[bytes, str, None]"; expected "Union[str, bytes, bytearray]"',
'Incompatible types in assignment (expression has type "None", variable has type Module)'
'Argument 1 to "loads" has incompatible type "Union[bytes, str, None]"; '
+ 'expected "Union[str, bytes, bytearray]"',
'Incompatible types in assignment (expression has type "None", variable has type Module)',
],
'admin_changelist': [
'Incompatible types in assignment (expression has type "FilteredChildAdmin", variable has type "ChildAdmin")'
@@ -65,8 +67,9 @@ IGNORED_ERRORS = {
],
'admin_widgets': [
'Incompatible types in assignment (expression has type "RelatedFieldWidgetWrapper", '
'variable has type "AdminRadioSelect")',
'Incompatible types in assignment (expression has type "Union[Widget, Any]", variable has type "AutocompleteSelect")'
+ 'variable has type "AdminRadioSelect")',
'Incompatible types in assignment (expression has type "Union[Widget, Any]", '
+ 'variable has type "AutocompleteSelect")'
],
'admin_utils': [
re.compile(r'Argument [0-9] to "lookup_field" has incompatible type'),
@@ -91,11 +94,13 @@ IGNORED_ERRORS = {
'Incompatible types in assignment (expression has type "FlatValuesListIterable", '
+ 'variable has type "ValuesListIterable")',
'Incompatible type for "contact" of "Book" (got "Optional[Author]", expected "Union[Author, Combinable]")',
'Incompatible type for "publisher" of "Book" (got "Optional[Publisher]", expected "Union[Publisher, Combinable]")'
'Incompatible type for "publisher" of "Book" (got "Optional[Publisher]", '
+ 'expected "Union[Publisher, Combinable]")'
],
'aggregation_regress': [
'Incompatible types in assignment (expression has type "List[str]", variable has type "QuerySet[Author]")',
'Incompatible types in assignment (expression has type "FlatValuesListIterable", variable has type "QuerySet[Any]")',
'Incompatible types in assignment (expression has type "FlatValuesListIterable", '
+ 'variable has type "QuerySet[Any]")',
'Too few arguments for "count" of "Sequence"'
],
'apps': [
@@ -176,8 +181,8 @@ IGNORED_ERRORS = {
'variable has type "SongForm"',
'"full_clean" of "BaseForm" does not return a value',
'No overload variant of "zip" matches argument types "Tuple[str, str, str]", "object"',
'note:',
'Incompatible types in assignment (expression has type "GetDateShowHiddenInitial", variable has type "GetDate")',
'Incompatible types in assignment (expression has type "GetDateShowHiddenInitial", '
+ 'variable has type "GetDate")',
re.compile(r'Incompatible types in assignment \(expression has type "[a-zA-Z]+Field", '
r'base class "BaseForm" defined the type as "Dict\[str, Any\]"\)'),
'List or tuple expected as variable arguments',
@@ -216,13 +221,13 @@ IGNORED_ERRORS = {
],
'lookup': [
'Unexpected keyword argument "headline__startswith" for "in_bulk" of "QuerySet"',
'note: '
],
'many_to_one': [
'Incompatible type for "parent" of "Child" (got "None", expected "Union[Parent, Combinable]")'
],
'model_inheritance_regress': [
'Incompatible types in assignment (expression has type "List[Supplier]", variable has type "QuerySet[Supplier]")'
'Incompatible types in assignment (expression has type "List[Supplier]", '
+ 'variable has type "QuerySet[Supplier]")'
],
'model_meta': [
'"object" has no attribute "items"',
@@ -238,10 +243,10 @@ IGNORED_ERRORS = {
'Cannot assign multiple types to name "PersonTwoImages" without an explicit "Type[...]" annotation',
'Incompatible types in assignment (expression has type "Type[Person]", '
+ 'base class "ImageFieldTestMixin" defined the type as "Type[PersonWithHeightAndWidth]")',
'note: "Person" defined here'
],
'model_formsets': [
'Incompatible types in string interpolation (expression has type "object", placeholder has type "Union[int, float]")'
'Incompatible types in string interpolation (expression has type "object", '
+ 'placeholder has type "Union[int, float]")'
],
'model_formsets_regress': [
'Incompatible types in assignment (expression has type "Model", variable has type "User")'
@@ -287,9 +292,11 @@ IGNORED_ERRORS = {
'Incompatible types in assignment (expression has type "Type[Field[Any, Any]]',
'DummyArrayField',
'DummyJSONField',
'Argument "encoder" to "JSONField" has incompatible type "DjangoJSONEncoder"; expected "Optional[Type[JSONEncoder]]"',
'Argument "encoder" to "JSONField" has incompatible type "DjangoJSONEncoder"; '
+ 'expected "Optional[Type[JSONEncoder]]"',
'for model "CITestModel"',
'Incompatible type for "field" of "IntegerArrayModel" (got "None", expected "Union[Sequence[int], Combinable]")'
'Incompatible type for "field" of "IntegerArrayModel" (got "None", '
+ 'expected "Union[Sequence[int], Combinable]")'
],
'properties': [
re.compile('Unexpected attribute "(full_name|full_name_2)" for model "Person"')
@@ -334,11 +341,14 @@ IGNORED_ERRORS = {
'Incompatible types in assignment (expression has type "Thread", variable has type "Callable[[], Any]")'
],
'test_client': [
'Incompatible types in assignment (expression has type "StreamingHttpResponse", variable has type "HttpResponse")',
'Incompatible types in assignment (expression has type "HttpResponse", variable has type "StreamingHttpResponse")'
'Incompatible types in assignment (expression has type "StreamingHttpResponse", '
+ 'variable has type "HttpResponse")',
'Incompatible types in assignment (expression has type "HttpResponse", '
+ 'variable has type "StreamingHttpResponse")'
],
'test_client_regress': [
'Incompatible types in assignment (expression has type "Dict[<nothing>, <nothing>]", variable has type "SessionBase")',
'Incompatible types in assignment (expression has type "Dict[<nothing>, <nothing>]", '
+ 'variable has type "SessionBase")',
'Unsupported left operand type for + ("None")',
'Both left and right operands are unions'
],
@@ -348,7 +358,8 @@ IGNORED_ERRORS = {
'test_runner': [
'Value of type "TestSuite" is not indexable',
'"TestSuite" has no attribute "_tests"',
'Argument "result" to "run" of "TestCase" has incompatible type "RemoteTestResult"; expected "Optional[TestResult]"',
'Argument "result" to "run" of "TestCase" has incompatible type "RemoteTestResult"; '
+ 'expected "Optional[TestResult]"',
'Item "TestSuite" of "Union[TestCase, TestSuite]" has no attribute "id"',
'MockTestRunner',
'Incompatible types in assignment (expression has type "Tuple[Union[TestCase, TestSuite], ...]", '

29
setup.cfg Normal file
View File

@@ -0,0 +1,29 @@
[isort]
skip =
django-sources,
django-stubs,
test-data
include_trailing_comma = true
multi_line_output = 5
wrap_length = 120
[flake8]
exclude =
django-sources,
django-stubs,
test-data
max_line_length = 120
[tool:pytest]
testpaths = ./test-data
addopts =
--tb=native
--mypy-ini-file=./test-data/plugins.ini
-s
-v
[bdist_wheel]
universal = 1
[metadata]
license_file = LICENSE.txt