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
Feature: pickle support for StreamField #10654
Conversation
Manage this branch in SquashTest this branch here: https://pysilverfix-pickle-support-71fg9.squash.io |
I will take a look if that is possible to add pickle support for Block as it is the blocker for implementation without passing field reference. |
Ok, with some help of ChatGPT, I was able to make StreamField compatible with pickle in a proper, clean way. I assume it is now safe to merge. |
wagtail/blocks/field_block.py
Outdated
@@ -530,40 +530,57 @@ def _get_callable_choices(self, choices, blank_choice=True): | |||
choices list with the addition of a blank choice (if blank_choice=True and one does not | |||
already exist). | |||
""" | |||
return self.choices_callable(choices, blank_choice) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't seem right... the old version of this method defined choices_callable
(a function that takes no arguments and returns a list when called), and returns that callable. Here, you've made choices_callable
into a method, and you're calling that method immediately - which means that _get_callable_choices
will now return a list, rather than a callable.
I suspect that this change will cause a callable choices
argument to be evaluated once on initialisation, rather than being evaluated each time the widget is rendered, as we want.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll verify that and make proper modifications. Callables are tricky to serialize, so method has to be refactored a little bit anyway (so callable isn't nested in a method)
@@ -1800,3 +1802,40 @@ def test_post_with_comment_notifications_switched_off(self): | |||
|
|||
self.assertEqual(subscription.user, self.user) | |||
self.assertFalse(subscription.comment_notifications) | |||
|
|||
|
|||
class TestPagePickleSupport(WagtailTestUtils, TestCase): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test would be better placed in wagtail.tests.test_streamfield
since it's not testing anything to do with the admin module (and the self.login()
is redundant).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point! I was looking for a proper place. I'll move it there.
Unfortunately I don't think the issue with callable I think the only reasonable way forward here is to somehow exclude the
Just as you'd expect to pickle a Python object, stop the program to make some changes to its class definition, start it again, unpickle the object and end up with an object that behaves according to the updated class definition - it seems logical that if you make any changes to a StreamField's definition, any unpickled StreamValues should then follow the new definition, not an older one that was frozen along with the data. Unfortunately, I can't see a good way of detaching the |
@gasman thanks for such a detailed review. You are right - it is impossible to pickle choice blocks in the current implementation. The only viable solution is to make a link between StreamField and StreamValue. It's actually my initial approach and the one I'm using for the last two years. I've moved tests into a proposed location and also made ugliness a little less ugly. Looking forward to your review. It might look like a hack but for ones who don't need |
Side note: Using For example:
https://github.com/uqfoundation/dill Updated with some benchmarks:
another one: uqfoundation/dill#292 I think it can be heavy on CPU ... So no. Let's support pickle. |
@gasman have you had a chance to look at the latest changes? |
@pySilver I've had a quick look, but not a full review yet - there are a lot of details here that will need careful consideration, like what happens to inner StreamValues from StreamBlocks nested inside other blocks - presumably those won't have the StreamField reference, and don't need it, but in that case the pickling logic needs to guard against that reference not existing. I'm really not keen on having that reference to StreamField there - it's creating a coupling between the Django ORM and the streams/blocks framework that's been mostly independent of that up to now - and I'd like to spend some more time investigating whether it's possible to avoid that. Incidentally, I don't think that moving the assignment into a decorator (4cbcf66) is really an improvement - it's just adding an extra step of indirection that you have to follow to fully understand the code, when it could just be a single line assignment within to_python. I'd also suggest changing |
@gasman thanks for your input! I've simplified the code per your recommendations.
Well, let's start with Will it fail if we try to pickle StreamValue instance without StreamField reference? Yes, indeed. As much as it will fail without this PR, due to the fact that Do we need some guarding code in case I am also not very happy about this implementation but this is probably the only way at the moment. At least if we want to continue wrapping even static choices into a callable. |
Thanks for the clarification! I was under the impression that pickling a StreamValue would require recursively pickling its children, which meant there would be a possibility of encountering another StreamValue further down - but now I see that the call to |
@gasman What can I do to make this PR happen? :) |
Hey 👋, Just a quick nudge on this PR. I get it is not ideal solution but I don't see how we can improve while maintaining callable choices wrapper. Chances we will make it to the master? I see people are already building their images with this patch, I myself currently use it with monkey patch with no problems. |
Sorry for the delay in getting back to this! Have merged in eadf9a6 (with a couple of tweaks - some additional comments / error-checking, and reverting the addition of the callable choices block to the test StreamPage, which I don't think is needed now that we're not attempting to pickle the StreamBlock definition). |
This is a rework of my original PR #5998 where I described the problem of lack of pickle support. Sadly the implementation is a little bit ugly but after many hours of thinking I didn't find any other way to make it work. I'm open to discussing any other solutions.
Please check the following:
make lint
from the Wagtail root.