Skip to content

Conversation

UnknownPlatypus
Copy link
Contributor

@UnknownPlatypus UnknownPlatypus commented Aug 22, 2025

I have made things!

A bit more involved than I expected. I was close to reuse some mypy plugin utils for the argument inference but they currently do no support class instantiation so I've adapted from it.
The logic is very similar to what is done in the .annotate() plugin code.

I left a few todos for improvements in later PR's, this one is already quite big.

todos

Correctly handle mixed annotate/prefetch_related on the same key -- ✅ in #2791

Currently it applies the last annotation, in reality, only the annotate matter and the prefetch is ignored + we should probably raise type errors?

-   case: prefetch_related_conflict_with_annotate
    main: |
        from django.db.models import Prefetch, F
        from django.contrib.auth.models import User, Group

        # When mixing `.annotate(foo=...)` and `prefetch_related(Prefetch(...,to_attr=foo))`
        # The last annotate in the chain takes precedence (even if it is prior to the prefetch_related)
        # TODO: Whould be nice to raise errors here
        reveal_type(
            User.objects # N: Revealed type is "builtins.list[django.contrib.auth.models.Group]"  <-- Wrong, runtime type is F("username")
            .annotate(foo=F("username"))
            .prefetch_related(Prefetch("groups", Group.objects.all(), to_attr="foo"))
            .get().foo
        )
        reveal_type(
            User.objects # N: Revealed type is "Any"
            .prefetch_related(Prefetch("groups", Group.objects.all(), to_attr="foo"))
            .annotate(foo=F("username"))
            .get().foo
        )
    installed_apps:
        - django.contrib.auth

Narrow type when no queryset is provided -- ✅ in #2786

Using the existing lookup_type infrastructure.

  • create get_model_info_from_qs_ctx
  • reuse / merge with _extract_model_type_from_queryset
# Prefetch with `to_attr` arg but without the `queryset` arg
# TODO: We should be able to resolve a more accurate type using existing lookup `resolve_lookup_expected_type` machinery
reveal_type(
    Article.objects.prefetch_related(
-     models.Prefetch("tags", to_attr="tags")).get().tags)  # N: Revealed type is "builtins.list[Any]"
+     models.Prefetch("tags", to_attr="tags")).get().tags)  # N: Revealed type is "builtins.list[myapp.models.Tag]"
    )
)

Related issues

Fixes #795

Copy link
Member

@sobolevn sobolevn left a comment

Choose a reason for hiding this comment

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

Thanks! Looks great, exept one thing :(

Copy link
Member

@sobolevn sobolevn left a comment

Choose a reason for hiding this comment

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

Looks great! Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

Prefetch's to_attr raises "Model" has no attribute "prefetched_field"
2 participants