mirror of
https://github.com/davidhalter/django-stubs.git
synced 2025-12-07 04:34:29 +08:00
Fix crash if model from same app referenced in RelatedField cannot be resolved (#199)
* do not crash if model from same app refd in ForeignKey cannot be resolved * bump to 1.2.0
This commit is contained in:
@@ -177,6 +177,10 @@ class DjangoContext:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
related_model = self.get_field_related_model_cls(field)
|
related_model = self.get_field_related_model_cls(field)
|
||||||
|
if related_model is None:
|
||||||
|
expected_types[field_name] = AnyType(TypeOfAny.from_error)
|
||||||
|
continue
|
||||||
|
|
||||||
if related_model._meta.proxy_for_model is not None:
|
if related_model._meta.proxy_for_model is not None:
|
||||||
related_model = related_model._meta.proxy_for_model
|
related_model = related_model._meta.proxy_for_model
|
||||||
|
|
||||||
@@ -260,6 +264,8 @@ class DjangoContext:
|
|||||||
is_nullable = self.get_field_nullability(field, method)
|
is_nullable = self.get_field_nullability(field, method)
|
||||||
if isinstance(field, RelatedField):
|
if isinstance(field, RelatedField):
|
||||||
related_model_cls = self.get_field_related_model_cls(field)
|
related_model_cls = self.get_field_related_model_cls(field)
|
||||||
|
if related_model_cls is None:
|
||||||
|
return AnyType(TypeOfAny.from_error)
|
||||||
|
|
||||||
if method == 'values':
|
if method == 'values':
|
||||||
primary_key_field = self.get_primary_key_field(related_model_cls)
|
primary_key_field = self.get_primary_key_field(related_model_cls)
|
||||||
@@ -274,7 +280,7 @@ class DjangoContext:
|
|||||||
return helpers.get_private_descriptor_type(field_info, '_pyi_private_get_type',
|
return helpers.get_private_descriptor_type(field_info, '_pyi_private_get_type',
|
||||||
is_nullable=is_nullable)
|
is_nullable=is_nullable)
|
||||||
|
|
||||||
def get_field_related_model_cls(self, field: Union[RelatedField, ForeignObjectRel]) -> Type[Model]:
|
def get_field_related_model_cls(self, field: Union[RelatedField, ForeignObjectRel]) -> Optional[Type[Model]]:
|
||||||
if isinstance(field, RelatedField):
|
if isinstance(field, RelatedField):
|
||||||
related_model_cls = field.remote_field.model
|
related_model_cls = field.remote_field.model
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -150,6 +150,8 @@ class NewSemanalDjangoPlugin(Plugin):
|
|||||||
for field in self.django_context.get_model_fields(model_class):
|
for field in self.django_context.get_model_fields(model_class):
|
||||||
if isinstance(field, RelatedField):
|
if isinstance(field, RelatedField):
|
||||||
related_model_cls = self.django_context.get_field_related_model_cls(field)
|
related_model_cls = self.django_context.get_field_related_model_cls(field)
|
||||||
|
if related_model_cls is None:
|
||||||
|
continue
|
||||||
related_model_module = related_model_cls.__module__
|
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))
|
deps.add(self._new_dependency(related_model_module))
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ def fill_descriptor_types_for_related_field(ctx: FunctionContext, django_context
|
|||||||
assert isinstance(current_field, RelatedField)
|
assert isinstance(current_field, RelatedField)
|
||||||
|
|
||||||
related_model_cls = django_context.get_field_related_model_cls(current_field)
|
related_model_cls = django_context.get_field_related_model_cls(current_field)
|
||||||
|
if related_model_cls is None:
|
||||||
|
return AnyType(TypeOfAny.from_error)
|
||||||
|
|
||||||
related_model = related_model_cls
|
related_model = related_model_cls
|
||||||
related_model_to_set = related_model_cls
|
related_model_to_set = related_model_cls
|
||||||
|
|||||||
@@ -8,11 +8,13 @@ from django.db.models.fields.reverse_related import (
|
|||||||
ManyToManyRel, ManyToOneRel, OneToOneRel,
|
ManyToManyRel, ManyToOneRel, OneToOneRel,
|
||||||
)
|
)
|
||||||
from mypy.nodes import (
|
from mypy.nodes import (
|
||||||
ARG_STAR2, MDEF, Argument, SymbolTableNode, TypeInfo, Var,
|
ARG_STAR2, MDEF, Argument, Context, SymbolTableNode, TypeInfo, Var,
|
||||||
)
|
)
|
||||||
from mypy.plugin import ClassDefContext
|
from mypy.plugin import ClassDefContext
|
||||||
from mypy.plugins import common
|
from mypy.plugins import common
|
||||||
from mypy.types import AnyType, Instance, TypeOfAny
|
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.django.context import DjangoContext
|
||||||
from mypy_django_plugin.lib import fullnames, helpers
|
from mypy_django_plugin.lib import fullnames, helpers
|
||||||
@@ -38,7 +40,7 @@ class ModelClassInitializer:
|
|||||||
field_info = self.lookup_typeinfo_or_incomplete_defn_error(fullname)
|
field_info = self.lookup_typeinfo_or_incomplete_defn_error(fullname)
|
||||||
return field_info
|
return field_info
|
||||||
|
|
||||||
def create_new_var(self, name: str, typ: Instance) -> Var:
|
def create_new_var(self, name: str, typ: MypyType) -> Var:
|
||||||
# type=: type of the variable itself
|
# type=: type of the variable itself
|
||||||
var = Var(name=name, type=typ)
|
var = Var(name=name, type=typ)
|
||||||
# var.info: type of the object variable is bound to
|
# var.info: type of the object variable is bound to
|
||||||
@@ -48,7 +50,7 @@ class ModelClassInitializer:
|
|||||||
var.is_inferred = True
|
var.is_inferred = True
|
||||||
return var
|
return var
|
||||||
|
|
||||||
def add_new_node_to_model_class(self, name: str, typ: Instance) -> None:
|
def add_new_node_to_model_class(self, name: str, typ: MypyType) -> None:
|
||||||
var = self.create_new_var(name, typ)
|
var = self.create_new_var(name, typ)
|
||||||
self.model_classdef.info.names[name] = SymbolTableNode(MDEF, var, plugin_generated=True)
|
self.model_classdef.info.names[name] = SymbolTableNode(MDEF, var, plugin_generated=True)
|
||||||
|
|
||||||
@@ -100,6 +102,18 @@ class AddRelatedModelsId(ModelClassInitializer):
|
|||||||
for field in model_cls._meta.get_fields():
|
for field in model_cls._meta.get_fields():
|
||||||
if isinstance(field, ForeignKey):
|
if isinstance(field, ForeignKey):
|
||||||
related_model_cls = self.django_context.get_field_related_model_cls(field)
|
related_model_cls = self.django_context.get_field_related_model_cls(field)
|
||||||
|
if related_model_cls is None:
|
||||||
|
error_context: Context = self.ctx.cls
|
||||||
|
field_sym = self.ctx.cls.info.get(field.name)
|
||||||
|
if field_sym is not None and field_sym.node is not None:
|
||||||
|
error_context = field_sym.node
|
||||||
|
self.api.fail(f'Cannot find model {field.related_model!r} '
|
||||||
|
f'referenced in field {field.name!r} ',
|
||||||
|
ctx=error_context)
|
||||||
|
self.add_new_node_to_model_class(field.attname,
|
||||||
|
AnyType(TypeOfAny.explicit))
|
||||||
|
continue
|
||||||
|
|
||||||
rel_primary_key_field = self.django_context.get_primary_key_field(related_model_cls)
|
rel_primary_key_field = self.django_context.get_primary_key_field(related_model_cls)
|
||||||
field_info = self.lookup_class_typeinfo_or_incomplete_defn_error(rel_primary_key_field.__class__)
|
field_info = self.lookup_class_typeinfo_or_incomplete_defn_error(rel_primary_key_field.__class__)
|
||||||
is_nullable = self.django_context.get_field_nullability(field, None)
|
is_nullable = self.django_context.get_field_nullability(field, None)
|
||||||
@@ -163,8 +177,10 @@ class AddRelatedManagers(ModelClassInitializer):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
related_model_cls = self.django_context.get_field_related_model_cls(relation)
|
related_model_cls = self.django_context.get_field_related_model_cls(relation)
|
||||||
related_model_info = self.lookup_class_typeinfo_or_incomplete_defn_error(related_model_cls)
|
if related_model_cls is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
related_model_info = self.lookup_class_typeinfo_or_incomplete_defn_error(related_model_cls)
|
||||||
if isinstance(relation, OneToOneRel):
|
if isinstance(relation, OneToOneRel):
|
||||||
self.add_new_node_to_model_class(attname, Instance(related_model_info, []))
|
self.add_new_node_to_model_class(attname, Instance(related_model_info, []))
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ def get_field_type_from_lookup(ctx: MethodContext, django_context: DjangoContext
|
|||||||
|
|
||||||
if isinstance(lookup_field, RelatedField) and lookup_field.column == lookup:
|
if isinstance(lookup_field, RelatedField) and lookup_field.column == lookup:
|
||||||
related_model_cls = django_context.get_field_related_model_cls(lookup_field)
|
related_model_cls = django_context.get_field_related_model_cls(lookup_field)
|
||||||
|
if related_model_cls is None:
|
||||||
|
return AnyType(TypeOfAny.from_error)
|
||||||
lookup_field = django_context.get_primary_key_field(related_model_cls)
|
lookup_field = django_context.get_primary_key_field(related_model_cls)
|
||||||
|
|
||||||
field_get_type = django_context.get_field_get_type(helpers.get_typechecker_api(ctx),
|
field_get_type = django_context.get_field_get_type(helpers.get_typechecker_api(ctx),
|
||||||
|
|||||||
4
setup.py
4
setup.py
@@ -21,14 +21,14 @@ with open('README.md', 'r') as f:
|
|||||||
readme = f.read()
|
readme = f.read()
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
'mypy>=0.730,<0.740',
|
'mypy>=0.730',
|
||||||
'typing-extensions',
|
'typing-extensions',
|
||||||
'django',
|
'django',
|
||||||
]
|
]
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="django-stubs",
|
name="django-stubs",
|
||||||
version="1.1.0",
|
version="1.2.0",
|
||||||
description='Mypy stubs for Django',
|
description='Mypy stubs for Django',
|
||||||
long_description=readme,
|
long_description=readme,
|
||||||
long_description_content_type='text/markdown',
|
long_description_content_type='text/markdown',
|
||||||
|
|||||||
@@ -387,22 +387,52 @@
|
|||||||
class Book2(models.Model):
|
class Book2(models.Model):
|
||||||
publisher = models.ForeignKey(to=Publisher2, on_delete=models.CASCADE)
|
publisher = models.ForeignKey(to=Publisher2, on_delete=models.CASCADE)
|
||||||
|
|
||||||
- case: if_model_is_defined_as_name_of_the_class_look_for_it_in_the_same_file
|
- case: if_model_is_defined_as_name_of_the_class_look_for_it_in_the_same_app
|
||||||
main: |
|
main: |
|
||||||
from myapp.models import Book
|
from myapp.models import Book
|
||||||
reveal_type(Book().publisher) # N: Revealed type is 'myapp.models.Publisher*'
|
reveal_type(Book().publisher) # N: Revealed type is 'myapp.models.publisher.Publisher*'
|
||||||
installed_apps:
|
installed_apps:
|
||||||
- myapp
|
- myapp
|
||||||
files:
|
files:
|
||||||
- path: myapp/__init__.py
|
- path: myapp/__init__.py
|
||||||
- path: myapp/models.py
|
- path: myapp/models/__init__.py
|
||||||
|
content: |
|
||||||
|
from .publisher import Publisher
|
||||||
|
from .book import Book
|
||||||
|
- path: myapp/models/publisher.py
|
||||||
content: |
|
content: |
|
||||||
from django.db import models
|
from django.db import models
|
||||||
class Publisher(models.Model):
|
class Publisher(models.Model):
|
||||||
pass
|
pass
|
||||||
|
- path: myapp/models/book.py
|
||||||
|
content: |
|
||||||
|
from django.db import models
|
||||||
class Book(models.Model):
|
class Book(models.Model):
|
||||||
publisher = models.ForeignKey(to='Publisher', on_delete=models.CASCADE)
|
publisher = models.ForeignKey(to='Publisher', on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
|
||||||
|
- case: fail_if_no_model_in_the_same_app_models_init_py
|
||||||
|
main: |
|
||||||
|
from myapp.models import Book
|
||||||
|
installed_apps:
|
||||||
|
- myapp
|
||||||
|
files:
|
||||||
|
- path: myapp/__init__.py
|
||||||
|
- path: myapp/models/__init__.py
|
||||||
|
content: |
|
||||||
|
from .book import Book
|
||||||
|
- path: myapp/models/publisher.py
|
||||||
|
content: |
|
||||||
|
from django.db import models
|
||||||
|
class Publisher(models.Model):
|
||||||
|
pass
|
||||||
|
- path: myapp/models/book.py
|
||||||
|
content: |
|
||||||
|
from django.db import models
|
||||||
|
class Book(models.Model):
|
||||||
|
publisher = models.ForeignKey(to='Publisher', on_delete=models.CASCADE) # E: Cannot find model 'Publisher' referenced in field 'publisher'
|
||||||
|
|
||||||
|
|
||||||
- case: test_foreign_key_field_without_backwards_relation
|
- case: test_foreign_key_field_without_backwards_relation
|
||||||
main: |
|
main: |
|
||||||
from myapp.models import Book, Publisher
|
from myapp.models import Book, Publisher
|
||||||
|
|||||||
Reference in New Issue
Block a user