Opened 4 months ago

Closed 9 days ago

#36426 closed Cleanup/optimization (fixed)

prefetch_related_objects() is doc'd to accept iterables but only accepts sequences

Reported by: Jacob Walls Owned by: Luna
Component: Database layer (models, ORM) Version: dev
Severity: Normal Keywords:
Cc: Triage Stage: Ready for checkin
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: yes UI/UX: no

Description (last modified by Jacob Walls)

The docs say prefetch_related_objects() accepts iterables, but it only accepts sequences.

In [1]: from django.db.models import prefetch_related_objects

In [2]: objs = set(Graph.objects.all())

In [3]: prefetch_related_objects(objs, "node_set")
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[3], line 1
----> 1 prefetch_related_objects(objs, "node_set")

File ~/py313/lib/python3.13/site-packages/django/db/models/query.py:2361, in prefetch_related_objects(model_instances, *related_lookups)
   2355     break
   2357 # Descend down tree
   2358 
   2359 # We assume that objects retrieved are homogeneous (which is the premise
   2360 # of prefetch_related), so what applies to first object applies to all.
-> 2361 first_obj = obj_list[0]
   2362 to_attr = lookup.get_current_to_attr(level)[0]
   2363 prefetcher, descriptor, attr_found, is_fetched = get_prefetcher(
   2364     first_obj, through_attr, to_attr
   2365 )

TypeError: 'set' object is not subscriptable

We could adjust the docs, but at a glance we could probably instead adjust the implementation to just next() instead of [0] and accept iterables as documented.

django-stubs types the argument as Iterable[_Model].

Change History (17)

comment:1 by Jacob Walls, 4 months ago

Description: modified (diff)

comment:2 by Luna, 4 months ago

Owner: set to Luna
Status: newassigned

comment:3 by Natalia Bidart, 4 months ago

Triage Stage: UnreviewedAccepted
Type: BugCleanup/optimization

Thank you Jacob for creating this ticket!

My suggestion would be to adjust the docs, since there are Iterable types that I think make less sense to support in this context, for example, str or dict. Also, by definition, an Iterable only needs to implement __iter__, which means it wouldn't necessarily support __next__ (that would make it an Iterator). The difference is subtle, though, and even a list, while Iterable, is not an Iterator.

I'd suggest we update the documentation to clarify that the argument should be a sequence of model instances, like a list or tuple, rather than any arbitrary iterable. For example:

Prefetches the given lookups on a sequence of model instances, such as a list or tuple.

This keeps the expectations clear and avoids surprising behavior with edge cases like passing a string or dictionary.

comment:4 by Jacob Walls, 4 months ago

Sorry, I didn't realize I left out details: I figured next(iter(model_instances))) would accept iterables simply.

This keeps the expectations clear and avoids surprising behavior with edge cases like passing a string or dictionary.

Certainly a string makes no sense, but a string is both an iterable and a sequence, so it's the same no matter what we choose here. I'm not proposing we add a strict type check for model instances: the current duck-typing approach seems fine.

I used set in the bug report, but where I encountered this in practice was with a dict_values. I could have just as easily ended up trying dict or dict_keys. I guess expected them all to just work.

comment:5 by bistasulove, 3 months ago

Hi Jacob and Natalia, is the documentation fix being worked on? I can raise the PR otherwise. Let me know, thanks

comment:6 by Jacob Walls, 3 months ago

This ticket is assigned to someone. Let's allow them some more time. We don't yet have clear acceptance of the idea to accept iterables, but since the suggested change is small and is a pattern already used in the project, seeing a tested PR might help with decision-making.

in reply to:  4 comment:7 by Natalia Bidart, 3 months ago

Version: 5.2dev

Replying to Jacob Walls:

Sorry, I didn't realize I left out details: I figured next(iter(model_instances))) would accept iterables simply.

Makes sense :-)

This keeps the expectations clear and avoids surprising behavior with edge cases like passing a string or dictionary.

Certainly a string makes no sense, but a string is both an iterable and a sequence, so it's the same no matter what we choose here.

Good point.

I'm not proposing we add a strict type check for model instances: the current duck-typing approach seems fine.

I agree.

I used set in the bug report, but where I encountered this in practice was with a dict_values. I could have just as easily ended up trying dict or dict_keys. I guess expected them all to just work.

Your points make sense and I think changing the code (with tests) to replace first_obj = obj_list[0] with next(iter(obj_list)) makes the code more robust, and as you say, it aligns with the docs.

comment:8 by Luna, 2 months ago

Has patch: set

comment:9 by Luna, 2 months ago

Hello,

I have just submitted a PR related to this ticket for review. Following the suggested approach, the patch modifies the implementation to accept any iterable by using next(iter(...)) instead of indexing. This change aligns with the documentation and improves code robustness.

Please let me know if there are any further improvements or feedback needed.

comment:10 by Sarah Boyce, 8 weeks ago

Patch needs improvement: set

comment:11 by JaeHyuckSa, 5 weeks ago

Patch needs improvement: unset

comment:12 by Sarah Boyce, 4 weeks ago

Patch needs improvement: set

Not all PR comments have been addressed

comment:13 by JaeHyuckSa, 4 weeks ago

Patch needs improvement: unset

comment:14 by JaeHyuckSa, 4 weeks ago

Patch needs improvement: set

I didn’t find any further changes.

comment:15 by Luna, 9 days ago

Patch needs improvement: unset

comment:16 by Jacob Walls, 9 days ago

Triage Stage: AcceptedReady for checkin

comment:17 by Jacob Walls <jacobtylerwalls@…>, 9 days ago

Resolution: fixed
Status: assignedclosed

In e08fa42:

Fixed #36426 -- Added support for further iterables in prefetch_related_objects().

Thanks Sarah Boyce for the review.

Note: See TracTickets for help on using tickets.
Back to Top