self reference support for foreignkeys

This commit is contained in:
Maxim Kurnikov
2018-12-21 23:46:10 +03:00
parent 783e427d64
commit f070098ce5
4 changed files with 33 additions and 7 deletions

View File

@@ -50,9 +50,15 @@ class InvalidModelString(ValueError):
self.model_string = model_string self.model_string = model_string
def get_model_fullname_from_string(expr: StrExpr, class SelfReference(ValueError):
pass
def get_model_fullname_from_string(model_string: str,
all_modules: Dict[str, MypyFile]) -> Optional[str]: all_modules: Dict[str, MypyFile]) -> Optional[str]:
model_string = expr.value if model_string == 'self':
raise SelfReference()
if '.' not in model_string: if '.' not in model_string:
raise InvalidModelString(model_string) raise InvalidModelString(model_string)

View File

@@ -132,6 +132,8 @@ class AddRelatedManagers(ModelClassInitializer):
ref_to_fullname = extract_ref_to_fullname(rvalue, ref_to_fullname = extract_ref_to_fullname(rvalue,
module_file=module_file, module_file=module_file,
all_modules=self.api.modules) all_modules=self.api.modules)
except helpers.SelfReference:
ref_to_fullname = defn.fullname
except helpers.InvalidModelString as exc: except helpers.InvalidModelString as exc:
self.api.fail(f'Invalid value for a to= parameter: {exc.model_string!r}', self.api.fail(f'Invalid value for a to= parameter: {exc.model_string!r}',
Context(line=rvalue.line)) Context(line=rvalue.line))
@@ -183,7 +185,7 @@ def extract_ref_to_fullname(rvalue_expr: CallExpr,
if isinstance(to_expr, NameExpr): if isinstance(to_expr, NameExpr):
return module_file.names[to_expr.name].fullname return module_file.names[to_expr.name].fullname
elif isinstance(to_expr, StrExpr): elif isinstance(to_expr, StrExpr):
typ_fullname = helpers.get_model_fullname_from_string(to_expr, all_modules) typ_fullname = helpers.get_model_fullname_from_string(to_expr.value, all_modules)
if typ_fullname is None: if typ_fullname is None:
return None return None
return typ_fullname return typ_fullname

View File

@@ -2,7 +2,7 @@ import typing
from typing import Optional, cast from typing import Optional, cast
from mypy.checker import TypeChecker from mypy.checker import TypeChecker
from mypy.nodes import StrExpr, TypeInfo, Context from mypy.nodes import StrExpr, TypeInfo
from mypy.plugin import FunctionContext from mypy.plugin import FunctionContext
from mypy.types import Type, CallableType, Instance, AnyType, TypeOfAny from mypy.types import Type, CallableType, Instance, AnyType, TypeOfAny
@@ -31,8 +31,12 @@ def get_valid_to_value_or_none(ctx: FunctionContext) -> Optional[Instance]:
if not isinstance(to_arg_expr, StrExpr): if not isinstance(to_arg_expr, StrExpr):
# not string, not supported # not string, not supported
return None return None
model_fullname = helpers.get_model_fullname_from_string(to_arg_expr, try:
all_modules=api.modules) model_fullname = helpers.get_model_fullname_from_string(to_arg_expr.value,
all_modules=api.modules)
except helpers.SelfReference:
model_fullname = api.tscope.classes[-1].fullname()
if model_fullname is None: if model_fullname is None:
return None return None
model_info = helpers.lookup_fully_qualified_generic(model_fullname, model_info = helpers.lookup_fully_qualified_generic(model_fullname,

View File

@@ -213,4 +213,18 @@ reveal_type(Member().apps) # E: Revealed type is 'django.db.models.manager.Rela
from django.db import models from django.db import models
class App(models.Model): class App(models.Model):
pass pass
[out] [out]
[CASE foreign_key_with_self]
from django.db import models
class User(models.Model):
parent = models.ForeignKey('self', on_delete=models.CASCADE)
reveal_type(User().parent) # E: Revealed type is 'main.User*'
[out]
[CASE many_to_many_with_self]
from django.db import models
class User(models.Model):
friends = models.ManyToManyField('self', on_delete=models.CASCADE)
reveal_type(User().friends) # E: Revealed type is 'django.db.models.manager.RelatedManager[main.User*]'
[out]