Skip to content

Commit f5e65d2

Browse files
authored
Resolve dynamic manager methods through manager MRO (#1701)
This mainly affects reverse managers as we can't inherit dynamic managers in any other way. It also renders a manual code path for method name lookup irrelevant as we can let mypy do that for us.
1 parent c9c3570 commit f5e65d2

File tree

2 files changed

+12
-43
lines changed

2 files changed

+12
-43
lines changed

mypy_django_plugin/transformers/managers.py

+4-39
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,11 @@ def get_method_type_from_dynamic_manager(
5858
Attempt to resolve a method on a manager that was built from '.from_queryset'
5959
"""
6060

61-
manager_type_info = manager_instance.type
61+
manager_type_info = manager_instance.type.get_containing_type_info(method_name)
6262

6363
if (
64-
"django" not in manager_type_info.metadata
64+
manager_type_info is None
65+
or "django" not in manager_type_info.metadata
6566
or "from_queryset_manager" not in manager_type_info.metadata["django"]
6667
):
6768
# Manager isn't dynamically added
@@ -235,45 +236,9 @@ def _replace_type_var(ret_type: MypyType, to_replace: str, replace_by: MypyType)
235236
return ret_type
236237

237238

238-
def get_method_type_from_reverse_manager(
239-
api: TypeChecker, method_name: str, manager_type_info: TypeInfo
240-
) -> Optional[ProperType]:
241-
"""
242-
Attempts to resolve a reverse manager's method via the '_default_manager' manager on the related model
243-
From Django docs:
244-
"By default the RelatedManager used for reverse relations is a subclass of the default manager for that model."
245-
Ref: https://docs.djangoproject.com/en/dev/topics/db/queries/#using-a-custom-reverse-manager
246-
"""
247-
is_reverse_manager = (
248-
"django" in manager_type_info.metadata and "related_manager_to_model" in manager_type_info.metadata["django"]
249-
)
250-
if not is_reverse_manager:
251-
return None
252-
253-
related_model_fullname = manager_type_info.metadata["django"]["related_manager_to_model"]
254-
assert isinstance(related_model_fullname, str)
255-
model_info = helpers.lookup_fully_qualified_typeinfo(api, related_model_fullname)
256-
if model_info is None:
257-
return None
258-
259-
# We should _always_ have a '_default_manager' on a model
260-
assert "_default_manager" in model_info.names
261-
assert isinstance(model_info.names["_default_manager"].node, Var)
262-
manager_instance = model_info.names["_default_manager"].node.type
263-
return (
264-
get_method_type_from_dynamic_manager(api, method_name, manager_instance)
265-
# TODO: Can we assert on None and Instance?
266-
if manager_instance is not None and isinstance(manager_instance, Instance)
267-
else None
268-
)
269-
270-
271239
def resolve_manager_method_from_instance(instance: Instance, method_name: str, ctx: AttributeContext) -> MypyType:
272240
api = helpers.get_typechecker_api(ctx)
273-
method_type = get_method_type_from_dynamic_manager(
274-
api, method_name, instance
275-
) or get_method_type_from_reverse_manager(api, method_name, instance.type)
276-
241+
method_type = get_method_type_from_dynamic_manager(api, method_name, instance)
277242
return method_type if method_type is not None else ctx.default_attr_type
278243

279244

tests/typecheck/fields/test_related.yml

+8-4
Original file line numberDiff line numberDiff line change
@@ -642,8 +642,10 @@
642642
reveal_type(user.article_set) # N: Revealed type is "myapp.models.Article_RelatedManager"
643643
reveal_type(user.book_set.add) # N: Revealed type is "def (*objs: Union[myapp.models.Book, builtins.int], *, bulk: builtins.bool =)"
644644
reveal_type(user.article_set.add) # N: Revealed type is "def (*objs: Union[myapp.models.Article, builtins.int], *, bulk: builtins.bool =)"
645-
reveal_type(user.book_set.filter) # N: Revealed type is "def (*args: Any, **kwargs: Any) -> myapp.models.LibraryEntityQuerySet[myapp.models.Book]"
646-
reveal_type(user.article_set.filter) # N: Revealed type is "def (*args: Any, **kwargs: Any) -> myapp.models.LibraryEntityQuerySet[myapp.models.Article]"
645+
reveal_type(user.book_set.filter) # N: Revealed type is "def (*args: Any, **kwargs: Any) -> myapp.models.LibraryEntityQuerySet"
646+
reveal_type(user.book_set.get()) # N: Revealed type is "myapp.models.Book"
647+
reveal_type(user.article_set.filter) # N: Revealed type is "def (*args: Any, **kwargs: Any) -> myapp.models.LibraryEntityQuerySet"
648+
reveal_type(user.article_set.get()) # N: Revealed type is "myapp.models.Article"
647649
reveal_type(user.book_set.queryset_method()) # N: Revealed type is "builtins.int"
648650
reveal_type(user.article_set.queryset_method()) # N: Revealed type is "builtins.int"
649651
installed_apps:
@@ -798,11 +800,13 @@
798800
from myapp.models.user import User
799801
reveal_type(Store().purchases) # N: Revealed type is "myapp.models.purchase.Purchase_RelatedManager"
800802
reveal_type(Store().purchases.queryset_method()) # N: Revealed type is "myapp.models.querysets.PurchaseQuerySet"
801-
reveal_type(Store().purchases.filter()) # N: Revealed type is "myapp.models.querysets.PurchaseQuerySet[myapp.models.purchase.Purchase]"
803+
reveal_type(Store().purchases.filter()) # N: Revealed type is "myapp.models.querysets.PurchaseQuerySet"
804+
reveal_type(Store().purchases.get()) # N: Revealed type is "myapp.models.purchase.Purchase"
802805
reveal_type(Store().purchases.filter().queryset_method()) # N: Revealed type is "myapp.models.querysets.PurchaseQuerySet"
803806
reveal_type(User().purchases) # N: Revealed type is "myapp.models.purchase.Purchase_RelatedManager"
804807
reveal_type(User().purchases.queryset_method()) # N: Revealed type is "myapp.models.querysets.PurchaseQuerySet"
805-
reveal_type(User().purchases.filter()) # N: Revealed type is "myapp.models.querysets.PurchaseQuerySet[myapp.models.purchase.Purchase]"
808+
reveal_type(User().purchases.filter()) # N: Revealed type is "myapp.models.querysets.PurchaseQuerySet"
809+
reveal_type(User().purchases.get()) # N: Revealed type is "myapp.models.purchase.Purchase"
806810
reveal_type(User().purchases.filter().queryset_method()) # N: Revealed type is "myapp.models.querysets.PurchaseQuerySet"
807811
installed_apps:
808812
- myapp

0 commit comments

Comments
 (0)