Skip to content

Replace deepcopy of the Q object #543

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open

Conversation

rrauenza
Copy link

@rrauenza rrauenza commented May 9, 2023

No description provided.

@rrauenza rrauenza marked this pull request as ready for review May 9, 2023 19:41
@rrauenza
Copy link
Author

rrauenza commented May 9, 2023

Still trying to run unit tests locally, hoping marking this PR as ready would trigger some on project side.

@rrauenza
Copy link
Author

rrauenza commented May 9, 2023

See #542

@rrauenza rrauenza changed the title Replace a deepcopy of the Q object with Q.all() Replace a deepcopy of the Q object May 9, 2023
@rrauenza rrauenza changed the title Replace a deepcopy of the Q object Replace deepcopy of the Q object May 9, 2023
@rrauenza rrauenza force-pushed the patch0 branch 7 times, most recently from ebf9c4d to 8199c3d Compare May 11, 2023 00:08
@TomHaii
Copy link

TomHaii commented Jun 7, 2023

Can also confirm that is indeed fixing the issue in deepcopying Q objects. (reproduced on Python 3.7)

@alphatownsman
Copy link

Is there some unittest can cover the case it fixes?

@rrauenza
Copy link
Author

rrauenza commented Jul 7, 2023

I think if the existing unit tests work, that is sufficient, since it shows the change doesn't break from the current method of copying.

We could assign a non picklable object attr to one of the children to prevent future regression?

j-antunes pushed a commit to j-antunes/django-polymorphic that referenced this pull request Nov 14, 2023
@AdamDonna
Copy link
Contributor

+1 to adding a non picklable object attr to prevent future regressions or even an explicit scenario that should break in 3.7 without this fix

@rrauenza
Copy link
Author

rrauenza commented Apr 22, 2025

@AdamDonna It's been a while -- I came back to try to add a test.

The test is successful, and it fails if I swap deepcopy back in. I'm not happy with the test data, but I could not figure out how to trigger the code path into django's trees.py.

Can we accept this PR so I can eventually remove my patch from my project?

(Also the pre commit failures look pre-existing..)

@rrauenza
Copy link
Author

rrauenza commented Apr 23, 2025

I am wondering, though, if this code is ok:

   def _copy_child(child):                                                               
        if isinstance(child, tuple):                                                      
            return child  # tuples are immutable, no need to make a copy.                 
        elif isinstance(child, Q):                                                        
            return _deepcopy_q_object(child)                                              
        else:                                                                             
            raise RuntimeError("Unknown child type: %s", type(child))       def _copy_child(child):                                                               
        if isinstance(child, tuple):                                                      
            return child  # tuples are immutable, no need to make a copy.                 
        elif isinstance(child, Q):                                                        
            return _deepcopy_q_object(child)                                              
        else:                                                                             
            raise RuntimeError("Unknown child type: %s", type(child))    

Yes, tuples are immutable.... but what they contain are not necessarilly, if, say it contained a dict.

@rrauenza
Copy link
Author

Q() derives from tree.Node, which adds copy in Django 4.2+, and does this (from django source):

   def __copy__(self):
        obj = self.create(connector=self.connector, negated=self.negated)
        obj.children = self.children  # Don't [:] as .__init__() via .create() does.
        return obj

    copy = __copy__

Let's jump into the debugger and see what happens.

polymorphic/tests/test_admin.py .
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> PDB set_trace (IO-capturing turned off) >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
> /home.local/rich/src/django-polymorphic/polymorphic/query_translate.py(99)_deepcopy_q_object()
-> obj = q.copy()
(Pdb) pp q
<Q: (OR: ('one2one__in', [<Model2D: id 1, field1 (CharField), field2 (CharField), field3 (CharField), field4 (CharField)>]))>
(Pdb) 
(Pdb) pp q.children
[('one2one__in',
  [<Model2D: id 1, field1 (CharField), field2 (CharField), field3 (CharField), field4 (CharField)>])]

Step past q.copy() and we get:

(Pdb) pp obj
<Q: (OR: ('one2one__in', [<Model2D: id 1, field1 (CharField), field2 (CharField), field3 (CharField), field4 (CharField)>]))>
(Pdb) pp obj.children
[('one2one__in',
  [<Model2D: id 1, field1 (CharField), field2 (CharField), field3 (CharField), field4 (CharField)>])]
(Pdb) 

Are these objects actually deep copied in django's copy()?

No. both the tuple and the Model are just references to the original. So my implementation for Django < 4.2 doesn't do anything different, IMHO.

(Pdb) id(obj.children[0][1][0])
140227184738432
(Pdb) id(q.children[0][1][0])
140227184738432
(Pdb) id(obj.children[0])
140364539161792
(Pdb) id(q.children[0])
140364539161792

@rrauenza
Copy link
Author

There is one oddity, though, that I've highlighted in my latest push. Django's copy seems to change the case of some of the Q() parameters. See the comment in the code for more info.

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

Successfully merging this pull request may close these issues.

4 participants