Add fallback related manager in final iteration of AddRelatedManagers (#993)

If a django model has a Manager class that cannot be resolved statically
(if it is generated in a way where we cannot import it, like `objects =
my_manager_factory()`), we fallback to the default related manager, so
you at least get a base level of working type checking.
This commit is contained in:
Aleksander Vognild Burkow
2022-06-15 17:50:53 +02:00
committed by GitHub
parent 32e13c37a6
commit 9044a354cb
3 changed files with 59 additions and 0 deletions

View File

@@ -1,3 +1,4 @@
from mypy.errorcodes import ErrorCode
MANAGER_UNTYPED = ErrorCode("django-manager", "Untyped manager disallowed", "Django")
MANAGER_MISSING = ErrorCode("django-manager-missing", "Couldn't resolve related manager for model", "Django")

View File

@@ -14,6 +14,7 @@ from mypy.types import Type as MypyType
from mypy.types import TypedDictType, TypeOfAny
from mypy_django_plugin.django.context import DjangoContext
from mypy_django_plugin.errorcodes import MANAGER_MISSING
from mypy_django_plugin.lib import fullnames, helpers
from mypy_django_plugin.lib.fullnames import ANNOTATIONS_FULLNAME, ANY_ATTR_ALLOWED_CLASS_FULLNAME, MODEL_CLASS_FULLNAME
from mypy_django_plugin.lib.helpers import add_new_class_for_module
@@ -345,6 +346,7 @@ class AddRelatedManagers(ModelClassInitializer):
continue
if isinstance(relation, (ManyToOneRel, ManyToManyRel)):
related_manager_info = None
try:
related_manager_info = self.lookup_typeinfo_or_incomplete_defn_error(
fullnames.RELATED_MANAGER_CLASS
@@ -356,6 +358,26 @@ class AddRelatedManagers(ModelClassInitializer):
if not self.api.final_iteration:
raise exc
else:
if related_manager_info:
"""
If a django model has a Manager class that cannot be
resolved statically (if it is generated in a way
where we cannot import it, like `objects = my_manager_factory()`),
we fallback to the default related manager, so you
at least get a base level of working type checking.
See https://github.com/typeddjango/django-stubs/pull/993
for more information on when this error can occur.
"""
self.add_new_node_to_model_class(
attname, Instance(related_manager_info, [Instance(related_model_info, [])])
)
related_model_fullname = related_model_cls.__module__ + "." + related_model_cls.__name__
self.ctx.api.fail(
f"Couldn't resolve related manager for relation {relation.name!r} (from {related_model_fullname}.{relation.field}).",
self.ctx.cls,
code=MANAGER_MISSING,
)
continue
# Check if the related model has a related manager subclassed from the default manager

View File

@@ -640,6 +640,42 @@
pass
- case: test_related_managers_when_manager_is_dynamically_generated_and_cannot_be_imported
main: |
from myapp import models
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class User(models.Model):
name = models.TextField()
def DynamicManager() -> models.Manager:
class InnerManager(models.Manager):
def some_method(self, arg: str) -> None:
return None
return InnerManager()
class Booking(models.Model):
renter = models.ForeignKey(User, on_delete=models.PROTECT)
owner = models.ForeignKey(User, on_delete=models.PROTECT, related_name='bookingowner_set')
objects = DynamicManager()
def process_booking(user: User):
reveal_type(user.bookingowner_set)
reveal_type(user.booking_set)
out: |
myapp/models:3: error: Couldn't resolve related manager for relation 'booking' (from myapp.models.Booking.myapp.Booking.renter).
myapp/models:3: error: Couldn't resolve related manager for relation 'bookingowner_set' (from myapp.models.Booking.myapp.Booking.owner).
myapp/models:20: note: Revealed type is "django.db.models.manager.RelatedManager[myapp.models.Booking]"
myapp/models:21: note: Revealed type is "django.db.models.manager.RelatedManager[myapp.models.Booking]"
- case: foreign_key_relationship_for_models_with_custom_manager
main: |
from myapp.models import Transaction