Skip to content
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

Fix unpickling in Python 3 #353

Merged
merged 1 commit into from
Oct 20, 2017
Merged

Fix unpickling in Python 3 #353

merged 1 commit into from
Oct 20, 2017

Conversation

ob-stripe
Copy link
Contributor

@ob-stripe ob-stripe commented Oct 20, 2017

r? @brandur-stripe
cc @stripe/api-libraries

Fixes #352.

This was annoying :/

Basically, the issue is that when unpickling StripeObjects, Python 3 calls our custom __setitem__ method which forbids empty strings.

In order to directly reset the dict values, it is necessary to create a custom __setstate__ method that will use update instead.

However, because StripeObject is a subclass of dict, __setstate__ is not called automatically when unpickling the instance, and I had to define a __reduce__ method to force the pickler to treat the instance as a custom class and not as a normal dict.

I'm not super happy with this fix as it's relatively brittle (__reduce__ needs to be consistent with the class constructor) but it's an unfortunate consequence of having StripeObject directly subclass dict. I wish we had gone with composition over inheritance here, but that's another discussion...

def __setstate__(self, state):
self.update(state)

def __reduce__(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we do comments on both __setstate__ and __reduce__ that follow roughly exactly what you said in the PR description?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Someone reading this who didn't see the original PR might wonder what exactly is going on here.)

obj.refresh_from(
{
'fala': 'lalala',
'emptystring': '',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we put a comment here explaining that emptystring is trying to uncover a specific Python 3 unpickling issue?

@brandur-stripe
Copy link
Contributor

Thanks for tracking this down OB!

I'm not super happy with this fix as it's relatively brittle (reduce needs to be consistent with the class constructor) but it's an unfortunate consequence of having StripeObject directly subclass dict. I wish we had gone with composition over inheritance here, but that's another discussion...

Yeah, the dict subclass thing is so brutal. We should definitely consider squashing that at some point.

@ob-stripe
Copy link
Contributor Author

Added some comments as requested.

@brandur-stripe
Copy link
Contributor

Excellent. Thanks!

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

Successfully merging this pull request may close these issues.

StripeObjects are unpickleable -- when statement_descriptor is None or empty string
2 participants