Skip to content

Commit 6023b9e

Browse files
committed
Replace copy.deepcopy of the Q object to custom deep copy
1 parent 3a81c0e commit 6023b9e

File tree

2 files changed

+83
-2
lines changed

2 files changed

+83
-2
lines changed

polymorphic/query_translate.py

+31-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
PolymorphicQuerySet support functions
33
"""
44

5-
import copy
65
from collections import deque
76

87
from django.apps import apps
@@ -80,6 +79,35 @@ def tree_node_correct_field_specs(my_model, node):
8079
return potential_q_object
8180

8281

82+
def _deepcopy_q_object(q):
83+
"""
84+
Make a deepcopy of a Q-object.
85+
"""
86+
87+
def _copy_child(child):
88+
if isinstance(child, tuple):
89+
return child # tuples are immutable, no need to make a copy.
90+
elif isinstance(child, Q):
91+
return _deepcopy_q_object(child)
92+
else:
93+
raise RuntimeError("Unknown child type: %s", type(child))
94+
95+
children = [_copy_child(c) for c in q.children]
96+
97+
if hasattr(q, "copy"): # Django 4.2+
98+
obj = q.copy()
99+
# This assignment of obj.children from children (created above)
100+
# is required for test_query_filter_exclude_is_immutable to pass
101+
# because somehow the capitalization changed for q_to_reuse?
102+
#
103+
# assert q_to_reuse.children == untouched_q_object.children
104+
# AssertionError: assert [('model2b__f... 'something')] == [('Model2B___... 'something')]
105+
obj.children = children
106+
else:
107+
obj = Q(*children, _connector=q.connector, _negated=q.negated)
108+
return obj
109+
110+
83111
def translate_polymorphic_filter_definitions_in_args(queryset_model, args, using=DEFAULT_DB_ALIAS):
84112
"""
85113
Translate the non-keyword argument list for PolymorphicQuerySet.filter()
@@ -92,7 +120,8 @@ def translate_polymorphic_filter_definitions_in_args(queryset_model, args, using
92120
Returns: modified Q objects
93121
"""
94122
return [
95-
translate_polymorphic_Q_object(queryset_model, copy.deepcopy(q), using=using) for q in args
123+
translate_polymorphic_Q_object(queryset_model, _deepcopy_q_object(q), using=using)
124+
for q in args
96125
]
97126

98127

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import copy
2+
import tempfile
3+
import pickle
4+
import threading
5+
6+
from django.db.models import Q
7+
from django.test import TestCase
8+
9+
from polymorphic.tests.models import Bottom, Middle, Top
10+
from polymorphic.query_translate import translate_polymorphic_filter_definitions_in_args
11+
12+
13+
class QueryTranslateTests(TestCase):
14+
def test_translate_with_not_pickleable_query(self):
15+
"""
16+
In some cases, Django may attacha _thread object to the query and we
17+
will get the following when we try to deepcopy inside of
18+
translate_polymorphic_filter_definitions_in_args:
19+
20+
TypeError: cannot pickle '_thread.lock' object
21+
22+
23+
For this to trigger, we need to somehoe go down this path:
24+
25+
File "/perfdash/.venv/lib64/python3.12/site-packages/polymorphic/query_translate.py", line 95, in translate_polymorphic_filter_definitions_in_args
26+
translate_polymorphic_Q_object(queryset_model, copy.deepcopy(q), using=using) for q in args
27+
^^^^^^^^^^^^^^^^
28+
File "/usr/lib64/python3.12/copy.py", line 143, in deepcopy
29+
y = copier(memo)
30+
^^^^^^^^^^^^
31+
File "/perfdash/.venv/lib64/python3.12/site-packages/django/utils/tree.py", line 53, in __deepcopy__
32+
obj.children = copy.deepcopy(self.children, memodict)
33+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
34+
File "/usr/lib64/python3.12/copy.py", line 136, in deepcopy
35+
y = copier(x, memo)
36+
^^^^^^^^^^^^^^^
37+
38+
Internals in Django, somehow we must trigger this tree.py code in django via
39+
the deepcopy in order to trigger this.
40+
41+
"""
42+
43+
with tempfile.TemporaryFile() as fd:
44+
# verify this is definitely not pickleable
45+
with self.assertRaises(TypeError):
46+
pickle.dumps(threading.Lock())
47+
48+
# I know this doesn't make sense to pass as a Q(), but
49+
# I haven't found another way to trigger the copy.deepcopy failing.
50+
q = Q(blog__info="blog info") | Q(blog__info=threading.Lock())
51+
52+
translate_polymorphic_filter_definitions_in_args(Bottom, args=[q])

0 commit comments

Comments
 (0)