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 )
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 , 4 months ago
Description: | modified (diff) |
---|
comment:2 by , 4 months ago
Owner: | set to |
---|---|
Status: | new → assigned |
comment:3 by , 4 months ago
Triage Stage: | Unreviewed → Accepted |
---|---|
Type: | Bug → Cleanup/optimization |
follow-up: 7 comment:4 by , 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 , 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 , 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.
comment:7 by , 3 months ago
Version: | 5.2 → dev |
---|
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 adict_values
. I could have just as easily ended up tryingdict
ordict_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 , 2 months ago
Has patch: | set |
---|
comment:9 by , 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 , 8 weeks ago
Patch needs improvement: | set |
---|
comment:11 by , 5 weeks ago
Patch needs improvement: | unset |
---|
comment:13 by , 4 weeks ago
Patch needs improvement: | unset |
---|
comment:15 by , 9 days ago
Patch needs improvement: | unset |
---|
comment:16 by , 9 days ago
Triage Stage: | Accepted → Ready for checkin |
---|
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
ordict
. Also, by definition, anIterable
only needs to implement__iter__
, which means it wouldn't necessarily support__next__
(that would make it anIterator
). The difference is subtle, though, and even alist
, whileIterable
, is not anIterator
.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:
This keeps the expectations clear and avoids surprising behavior with edge cases like passing a string or dictionary.