Skip to content

New semanal-progress rebased on master #445

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 76 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
77daf90
refactor, fix method copying
mkurnikov Jan 4, 2020
db925ac
QuerySet.as_manager() support
mkurnikov Jan 4, 2020
74ccc4d
allow manager classes nested inside model classes
mkurnikov Jan 4, 2020
f58cbea
lint fixes
mkurnikov Jan 4, 2020
4bf5ec9
fix tests
mkurnikov Jan 5, 2020
585273a
add two new as_manager tests
mkurnikov Jan 5, 2020
c0f41f3
wip
mkurnikov Feb 2, 2020
e9bdc50
wip
mkurnikov Mar 14, 2020
b51ec9d
remove as_manager support
mkurnikov Jun 19, 2020
7d004ff
remove generics support for tests
mkurnikov Jun 19, 2020
13cec79
remove as_manager/from_queryset leftovers and fix tests
mkurnikov Jun 19, 2020
c8641fb
remove files
mkurnikov Jun 19, 2020
05e0f40
rebase
mkurnikov Jun 19, 2020
4749d15
proper rebase
mkurnikov Jun 19, 2020
59ff90e
FormCallback
mkurnikov Jun 19, 2020
5093939
get_user_model support for new callbacks api
mkurnikov Jun 19, 2020
c0e5c46
lints
mkurnikov Jun 19, 2020
56515da
isort
mkurnikov Jun 19, 2020
bf48ef2
remove one more irrelevant file
mkurnikov Jun 19, 2020
196179b
disable test suites checking in CI
mkurnikov Jun 19, 2020
59ce2ed
remove transformers/models
mkurnikov Jun 19, 2020
b4d8df1
more changes to new API
mkurnikov Jun 19, 2020
774c14f
remove unused import
mkurnikov Jun 19, 2020
4af54e7
remove some dead code
mkurnikov Jun 19, 2020
cca106d
lints
mkurnikov Jun 19, 2020
bd325e0
replace field callback with new API
mkurnikov Jun 19, 2020
fc60ceb
move meta to new API
mkurnikov Jun 19, 2020
57ee7c9
move orm_lookups to new API
mkurnikov Jun 19, 2020
43f488b
move init_create to new API
mkurnikov Jun 19, 2020
6debf6d
move querysets to new API
mkurnikov Jun 19, 2020
9e0f438
transformers2 -> transformers
mkurnikov Jun 19, 2020
d67a117
remove a bunch of dead code
mkurnikov Jun 19, 2020
a9fa3c0
add callback class for from_queryset()
Jul 16, 2020
04345bb
helpers
Jul 16, 2020
3b3f5e1
typos
Jul 16, 2020
c731711
method for copying methods
Jul 17, 2020
627fe61
cleanp after 1st CR
Jul 19, 2020
040c0d9
cleanup after 2nd CR, exceptions instead of returns
Jul 20, 2020
86ed790
fallback to Any if can not resolve from_queryset Manager
Jul 23, 2020
e8f1f19
implemented AddManagers.run_with_model_cls() from master
Jul 25, 2020
c3ee1a5
tests for from_queryset
Jul 25, 2020
deee98c
remove print
Jul 25, 2020
0c8735f
flak8 cleanup
Jul 25, 2020
707569b
reverted to old way of creating typeinfo, probably refactor needed on…
Jul 28, 2020
78aa41f
fix 2 of failing tests
Aug 1, 2020
2b75646
fix 3rd test
Aug 1, 2020
5a1aff7
fix the last test
Aug 1, 2020
0890d29
isort
Aug 1, 2020
3d1014a
fix first mypy plugin code error
Aug 2, 2020
e6cef08
another try to fix mypy for plugin code errors
Aug 2, 2020
f64932d
fix isort
Aug 2, 2020
818db9b
added type ignore comments in places where mypy doesnt resolve type c…
Aug 3, 2020
3a0c9a4
flake8
Aug 3, 2020
f048a28
error message formatted properly
Aug 3, 2020
8390de3
unused type ignore comment
Aug 3, 2020
e6d91ef
use of cast instead of type ignore
Aug 5, 2020
19b6eb6
fix issue #438 manager method is forward reference
Aug 5, 2020
51b021f
typo in test
Aug 5, 2020
c5f4256
fixed condition for defering
Aug 6, 2020
50fe47a
assert instead of return
Aug 11, 2020
3cad5e3
uncommented tests
kszmigiel Aug 17, 2020
20ef55e
isort
kszmigiel Aug 18, 2020
61a330c
fix test (note assertion on HttpRequest.user)
kszmigiel Aug 18, 2020
ab07ffc
isort and test revert
kszmigiel Aug 18, 2020
1ae0d85
fix for bound_arg_type
kszmigiel Aug 18, 2020
c54eced
django typecheck fix (?)
kszmigiel Aug 18, 2020
9b42185
fix typecheck test suite WIP
kszmigiel Aug 21, 2020
d92ea39
WIP django test suite bugfixing
kszmigiel Aug 24, 2020
998cc56
remove comment to check tests
kszmigiel Aug 30, 2020
125aa64
fix running tests, flak8
kszmigiel Aug 30, 2020
6691967
add ignore error
kszmigiel Aug 30, 2020
4d051e3
SmallAutoField stub
kszmigiel Aug 31, 2020
b4bf1d4
MemberExpr NameExpr return default
kszmigiel Sep 24, 2020
6d034bd
fix error_context type
kszmigiel Sep 24, 2020
4c3e588
add error ignore for test model inharitence
kszmigiel Sep 25, 2020
da09a4c
ignore error in test_abstract_inheritance
kszmigiel Oct 12, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions django-stubs/db/models/fields/__init__.pyi
Original file line number Diff line number Diff line change
@@ -406,4 +406,5 @@ class DurationField(Field[_ST, _GT]):
_pyi_private_get_type: timedelta

class BigAutoField(AutoField[_ST, _GT]): ...
class SmallAutoField(AutoField[_ST, _GT]): ...
class CommaSeparatedIntegerField(CharField[_ST, _GT]): ...
31 changes: 14 additions & 17 deletions mypy_django_plugin/django/context.py
Original file line number Diff line number Diff line change
@@ -16,12 +16,13 @@
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.plugin import MethodContext
from mypy.types import AnyType, Instance
from mypy.types import Type as MypyType
from mypy.types import TypeOfAny, UnionType

from mypy_django_plugin.lib import fullnames, helpers
from mypy_django_plugin.lib import chk_helpers, fullnames, helpers

try:
from django.contrib.postgres.fields import ArrayField
@@ -52,14 +53,6 @@ def initialize_django(settings_module: str) -> Tuple['Apps', 'LazySettings']:
# add current directory to sys.path
sys.path.append(os.getcwd())

def noop_class_getitem(cls, key):
Copy link
Member

@sobolevn sobolevn Aug 17, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will only affect django>=3.1
We do need legacy support.

return cls

from django.db import models

models.QuerySet.__class_getitem__ = classmethod(noop_class_getitem) # type: ignore
models.Manager.__class_getitem__ = classmethod(noop_class_getitem) # type: ignore

from django.conf import settings
from django.apps import apps

@@ -119,10 +112,10 @@ def get_model_fields(self, model_cls: Type[Model]) -> Iterator[Field]:
if isinstance(field, Field):
yield field

def get_model_relations(self, model_cls: Type[Model]) -> Iterator[ForeignObjectRel]:
for field in model_cls._meta.get_fields():
if isinstance(field, ForeignObjectRel):
yield field
def get_model_relations(self, model_cls: Type[Model]) -> Iterator[Tuple[Optional[str], ForeignObjectRel]]:
for relation in model_cls._meta.get_fields():
if isinstance(relation, ForeignObjectRel):
yield relation.get_accessor_name(), relation

def get_field_lookup_exact_type(self, api: TypeChecker, field: Union[Field, ForeignObjectRel]) -> MypyType:
if isinstance(field, (RelatedField, ForeignObjectRel)):
@@ -222,11 +215,15 @@ def all_registered_model_classes(self) -> Set[Type[models.Model]]:
def all_registered_model_class_fullnames(self) -> Set[str]:
return {helpers.get_class_fullname(cls) for cls in self.all_registered_model_classes}

def is_model_subclass(self, info: TypeInfo) -> bool:
return (info.fullname in self.all_registered_model_class_fullnames
or info.has_base(fullnames.MODEL_CLASS_FULLNAME))

def get_attname(self, field: Field) -> str:
attname = field.attname
return attname

def get_field_nullability(self, field: Union[Field, ForeignObjectRel], method: Optional[str]) -> bool:
def get_field_nullability(self, field: Union[Field, ForeignObjectRel], method: Optional[str] = None) -> bool:
nullable = field.null
if not nullable and isinstance(field, CharField) and field.blank:
return True
@@ -356,11 +353,11 @@ def resolve_lookup_expected_type(self, ctx: MethodContext, model_cls: Type[Model
return AnyType(TypeOfAny.explicit)

if lookup_cls is None or isinstance(lookup_cls, Exact):
return self.get_field_lookup_exact_type(helpers.get_typechecker_api(ctx), field)
return self.get_field_lookup_exact_type(chk_helpers.get_typechecker_api(ctx), field)

assert lookup_cls is not None

lookup_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), lookup_cls)
lookup_info = helpers.lookup_class_typeinfo(chk_helpers.get_typechecker_api(ctx), lookup_cls)
if lookup_info is None:
return AnyType(TypeOfAny.explicit)

@@ -370,7 +367,7 @@ def resolve_lookup_expected_type(self, ctx: MethodContext, model_cls: Type[Model
# 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):
field_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), field.__class__)
field_info = helpers.lookup_class_typeinfo(chk_helpers.get_typechecker_api(ctx), field.__class__)
if field_info is None:
return AnyType(TypeOfAny.explicit)
lookup_type = helpers.get_private_descriptor_type(field_info, '_pyi_private_get_type',
126 changes: 126 additions & 0 deletions mypy_django_plugin/lib/chk_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
from typing import Dict, List, Optional, Set, Union

from mypy import checker
from mypy.checker import TypeChecker
from mypy.mro import calculate_mro
from mypy.nodes import (
GDEF, MDEF, Block, ClassDef, Expression, MypyFile, SymbolTable, SymbolTableNode, TypeInfo, Var,
)
from mypy.plugin import (
AttributeContext, CheckerPluginInterface, FunctionContext, MethodContext,
)
from mypy.types import AnyType, Instance, TupleType
from mypy.types import Type as MypyType
from mypy.types import TypedDictType, TypeOfAny

from mypy_django_plugin.lib import helpers


def add_new_class_for_current_module(current_module: MypyFile,
name: str,
bases: List[Instance],
fields: Optional[Dict[str, MypyType]] = None
) -> TypeInfo:
new_class_unique_name = checker.gen_unique_name(name, current_module.names)

# make new class expression
classdef = ClassDef(new_class_unique_name, Block([]))
classdef.fullname = current_module.fullname + '.' + new_class_unique_name

# make new TypeInfo
new_typeinfo = TypeInfo(SymbolTable(), classdef, current_module.fullname)
new_typeinfo.bases = bases
calculate_mro(new_typeinfo)
new_typeinfo.calculate_metaclass_type()

# add fields
if fields:
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
new_typeinfo.names[field_name] = SymbolTableNode(MDEF, var, plugin_generated=True)

classdef.info = new_typeinfo
current_module.names[new_class_unique_name] = SymbolTableNode(GDEF, new_typeinfo, plugin_generated=True)
return new_typeinfo


def make_oneoff_named_tuple(api: TypeChecker, name: str, fields: 'Dict[str, MypyType]') -> TupleType:
current_module = helpers.get_current_module(api)
namedtuple_info = add_new_class_for_current_module(current_module, name,
bases=[api.named_generic_type('typing.NamedTuple', [])],
fields=fields)
return TupleType(list(fields.values()), fallback=Instance(namedtuple_info, []))


def make_tuple(api: 'TypeChecker', fields: List[MypyType]) -> TupleType:
# fallback for tuples is any builtins.tuple instance
fallback = api.named_generic_type('builtins.tuple',
[AnyType(TypeOfAny.special_form)])
return TupleType(fields, fallback=fallback)


def make_oneoff_typeddict(api: CheckerPluginInterface, fields: 'Dict[str, MypyType]',
required_keys: Set[str]) -> TypedDictType:
object_type = api.named_generic_type('mypy_extensions._TypedDict', [])
typed_dict_type = TypedDictType(fields, # type: ignore
required_keys=required_keys,
fallback=object_type)
return typed_dict_type


def get_typechecker_api(ctx: Union[AttributeContext, MethodContext, FunctionContext]) -> TypeChecker:
if not isinstance(ctx.api, TypeChecker):
raise ValueError('Not a TypeChecker')
return ctx.api


def check_types_compatible(ctx: Union[FunctionContext, MethodContext],
*, expected_type: MypyType, actual_type: MypyType, error_message: str) -> None:
api = get_typechecker_api(ctx)
api.check_subtype(actual_type, expected_type,
ctx.context, error_message,
'got', 'expected')


def get_call_argument_by_name(ctx: Union[FunctionContext, MethodContext], name: str) -> Optional[Expression]:
"""
Return the expression for the specific argument.
This helper should only be used with non-star arguments.
"""
if name not in ctx.callee_arg_names:
return None
idx = ctx.callee_arg_names.index(name)
args = ctx.args[idx]
if len(args) != 1:
# Either an error or no value passed.
return None
return args[0]


def get_call_argument_type_by_name(ctx: Union[FunctionContext, MethodContext], name: str) -> Optional[MypyType]:
"""Return the type for the specific argument.
This helper should only be used with non-star arguments.
"""
if name not in ctx.callee_arg_names:
return None
idx = ctx.callee_arg_names.index(name)
arg_types = ctx.arg_types[idx]
if len(arg_types) != 1:
# Either an error or no value passed.
return None
return arg_types[0]


def add_new_sym_for_info(info: TypeInfo, *, name: str, sym_type: MypyType) -> None:
# type=: type of the variable itself
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.is_initialized_in_class = True
var.is_inferred = True
info.names[name] = SymbolTableNode(MDEF, var,
plugin_generated=True)
6 changes: 6 additions & 0 deletions mypy_django_plugin/lib/generics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
def make_classes_generic(*klasses: type) -> None:
for klass in klasses:
def fake_classgetitem(cls, *args, **kwargs):
return cls

klass.__class_getitem__ = classmethod(fake_classgetitem) # type: ignore
Loading