Skip to content

Commit 1c3f7ac

Browse files
Fixes custom pickle protocol to handle None values as well (#1469)
* Fixes custom pickle protocol to handle `None` values as well * Updates `CHANGELOG.md` * Update test_pickle.py * Update tests/test_primitives/test_container/test_base_container/test_pickle.py Co-authored-by: Nikita Sobolev <mail@sobolevn.me>
1 parent f6f3d31 commit 1c3f7ac

File tree

4 files changed

+60
-6
lines changed

4 files changed

+60
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ See [0Ver](https://0ver.org/).
1111
### Bugfixes
1212

1313
- Fixes a problem with `do-notation` and type aliases
14+
- Fixes custom pickle protocol to handle `None` values gracefully
1415

1516

1617
## 0.19.0 aka The Do Notation

returns/primitives/container.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,28 @@
11
from abc import ABCMeta
22
from typing import Any, TypeVar
33

4+
from typing_extensions import TypedDict
5+
46
from returns.interfaces.equable import Equable
57
from returns.primitives.hkt import Kind1
68
from returns.primitives.types import Immutable
79

810
_EqualType = TypeVar('_EqualType', bound=Equable)
911

1012

13+
class _PickleState(TypedDict):
14+
"""Dict to represent the `BaseContainer` state to be pickled."""
15+
16+
# TODO: Remove `__slots__` from here when `slotscheck` allow ignore classes
17+
# by using comments. We don't need the slots here since this class is just
18+
# a representation of a dictionary and should not be instantiated by any
19+
# means.
20+
# See: https://github.com/ariebovenberg/slotscheck/issues/71
21+
__slots__ = ('container_value',) # type: ignore
22+
23+
container_value: Any
24+
25+
1126
class BaseContainer(Immutable, metaclass=ABCMeta):
1227
"""Utility class to provide all needed magic methods to the context."""
1328

@@ -37,13 +52,15 @@ def __hash__(self) -> int:
3752
"""Used to use this value as a key."""
3853
return hash(self._inner_value)
3954

40-
def __getstate__(self) -> Any:
55+
def __getstate__(self) -> _PickleState:
4156
"""That's how this object will be pickled."""
42-
return self._inner_value
57+
return {'container_value': self._inner_value} # type: ignore
4358

44-
def __setstate__(self, state: Any) -> None:
59+
def __setstate__(self, state: _PickleState) -> None:
4560
"""Loading state from pickled data."""
46-
object.__setattr__(self, '_inner_value', state) # noqa: WPS609
61+
object.__setattr__( # noqa: WPS609
62+
self, '_inner_value', state['container_value'],
63+
)
4764

4865

4966
def container_equality(

tests/test_io/test_io_container/test_io_pickle.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33

44
def test_io_pickle():
55
"""Tests how pickle protocol works for containers."""
6-
assert IO(1).__getstate__() == 1 # noqa: WPS609
6+
assert IO(1).__getstate__() == {'container_value': 1} # noqa: WPS609
77

88

99
def test_io_pickle_restore():
1010
"""Ensures that object can be restored."""
1111
container = IO(2)
12-
container.__setstate__(1) # noqa: WPS609
12+
container.__setstate__({'container_value': 1}) # type: ignore # noqa: WPS609, E501
1313
assert container == IO(1)
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import pickle # noqa: S403
2+
from typing import Any
3+
4+
from hypothesis import example, given
5+
from hypothesis import strategies as st
6+
7+
from returns.primitives.container import BaseContainer
8+
9+
10+
class _CustomClass(object):
11+
def __init__(self, inner_value: Any) -> None:
12+
self.inner_value = inner_value
13+
14+
def __eq__(self, other: Any) -> bool:
15+
return (
16+
type(other) == type(self) and # noqa: WPS516
17+
self.inner_value == other.inner_value
18+
)
19+
20+
21+
@given(
22+
st.one_of(
23+
st.integers(),
24+
st.floats(allow_nan=False),
25+
st.text(),
26+
st.booleans(),
27+
st.lists(st.text()),
28+
st.dictionaries(st.text(), st.integers()),
29+
st.builds(_CustomClass, st.text()),
30+
),
31+
)
32+
@example(None)
33+
def test_pickle(container_value: Any):
34+
"""Ensures custom pickle protocol works as expected."""
35+
container = BaseContainer(container_value)
36+
assert pickle.loads(pickle.dumps(container)) == container # noqa: S301

0 commit comments

Comments
 (0)