From b8836dbe583fa334e7c105e7b02bcef1f0107f50 Mon Sep 17 00:00:00 2001 From: proboscis Date: Thu, 27 Feb 2025 23:20:35 +0900 Subject: [PATCH 01/10] Fix: Add pickle support for UnwrapFailedError (#2048) --- CHANGELOG.md | 3 ++ returns/primitives/exceptions.py | 9 +++++ .../test_pickle_unwrap_failed_error.py | 37 +++++++++++++++++++ 3 files changed, 49 insertions(+) create mode 100644 tests/test_primitives/test_exceptions/test_pickle_unwrap_failed_error.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e81def09..cbf753f4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,9 @@ See [0Ver](https://0ver.org/). - Add `default_error` parameter to `returns.converters.maybe_to_result`, which provides a default error value for `Failure` +### Bugfixes + +- Add pickling support for `UnwrapFailedError` exception ## 0.23.0 diff --git a/returns/primitives/exceptions.py b/returns/primitives/exceptions.py index b5e4a3795..389e2e5c2 100644 --- a/returns/primitives/exceptions.py +++ b/returns/primitives/exceptions.py @@ -21,6 +21,15 @@ def __init__(self, container: Unwrappable) -> None: super().__init__() self.halted_container = container + def __reduce__(self): + """Custom reduce method for pickle protocol. + + This helps properly reconstruct the exception during unpickling. + """ + return ( + self.__class__, # callable + (self.halted_container,), # args to callable + ) class ImmutableStateError(AttributeError): """ diff --git a/tests/test_primitives/test_exceptions/test_pickle_unwrap_failed_error.py b/tests/test_primitives/test_exceptions/test_pickle_unwrap_failed_error.py new file mode 100644 index 000000000..e4f16ecfd --- /dev/null +++ b/tests/test_primitives/test_exceptions/test_pickle_unwrap_failed_error.py @@ -0,0 +1,37 @@ +import pickle # noqa: S403 + +from returns.maybe import Nothing, Some +from returns.primitives.exceptions import UnwrapFailedError +from returns.result import Failure, Success + + +def test_pickle_unwrap_failed_error_from_maybe(): + """Ensures that UnwrapFailedError with Maybe can be pickled.""" + try: + Nothing.unwrap() # This will raise UnwrapFailedError + except UnwrapFailedError as error: + # Serialize the error + serialized = pickle.dumps(error) + + # Deserialize + deserialized_error = pickle.loads(serialized) # noqa: S301 + + # Check that halted_container is preserved + assert deserialized_error.halted_container == Nothing + assert deserialized_error.halted_container != Some(None) + + +def test_pickle_unwrap_failed_error_from_result(): + """Ensures that UnwrapFailedError with Result can be pickled.""" + try: + Failure('error').unwrap() # This will raise UnwrapFailedError + except UnwrapFailedError as error: + # Serialize the error + serialized = pickle.dumps(error) + + # Deserialize + deserialized_error = pickle.loads(serialized) # noqa: S301 + + # Check that halted_container is preserved + assert deserialized_error.halted_container == Failure('error') + assert deserialized_error.halted_container != Success('error') \ No newline at end of file From 7c1b84cafdcc66f4048ef08f4b9ee5f2439b1d7d Mon Sep 17 00:00:00 2001 From: proboscis Date: Thu, 27 Feb 2025 23:21:53 +0900 Subject: [PATCH 02/10] Fix: Add noqa comment for __reduce__ method to fix flake8 warning --- returns/primitives/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/returns/primitives/exceptions.py b/returns/primitives/exceptions.py index 389e2e5c2..4399fa8de 100644 --- a/returns/primitives/exceptions.py +++ b/returns/primitives/exceptions.py @@ -21,7 +21,7 @@ def __init__(self, container: Unwrappable) -> None: super().__init__() self.halted_container = container - def __reduce__(self): + def __reduce__(self): # noqa: WPS603 """Custom reduce method for pickle protocol. This helps properly reconstruct the exception during unpickling. From be2417c34f03b3cdda9b45ef22f98abdf6fa5ebb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 27 Feb 2025 14:26:48 +0000 Subject: [PATCH 03/10] [pre-commit.ci] auto fixes from pre-commit.com hooks --- returns/primitives/exceptions.py | 3 ++- .../test_exceptions/test_pickle_unwrap_failed_error.py | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/returns/primitives/exceptions.py b/returns/primitives/exceptions.py index 4399fa8de..21a65d013 100644 --- a/returns/primitives/exceptions.py +++ b/returns/primitives/exceptions.py @@ -23,7 +23,7 @@ def __init__(self, container: Unwrappable) -> None: def __reduce__(self): # noqa: WPS603 """Custom reduce method for pickle protocol. - + This helps properly reconstruct the exception during unpickling. """ return ( @@ -31,6 +31,7 @@ def __reduce__(self): # noqa: WPS603 (self.halted_container,), # args to callable ) + class ImmutableStateError(AttributeError): """ Raised when a container is forced to be mutated. diff --git a/tests/test_primitives/test_exceptions/test_pickle_unwrap_failed_error.py b/tests/test_primitives/test_exceptions/test_pickle_unwrap_failed_error.py index e4f16ecfd..81a91ad29 100644 --- a/tests/test_primitives/test_exceptions/test_pickle_unwrap_failed_error.py +++ b/tests/test_primitives/test_exceptions/test_pickle_unwrap_failed_error.py @@ -12,10 +12,10 @@ def test_pickle_unwrap_failed_error_from_maybe(): except UnwrapFailedError as error: # Serialize the error serialized = pickle.dumps(error) - + # Deserialize deserialized_error = pickle.loads(serialized) # noqa: S301 - + # Check that halted_container is preserved assert deserialized_error.halted_container == Nothing assert deserialized_error.halted_container != Some(None) @@ -28,10 +28,10 @@ def test_pickle_unwrap_failed_error_from_result(): except UnwrapFailedError as error: # Serialize the error serialized = pickle.dumps(error) - + # Deserialize deserialized_error = pickle.loads(serialized) # noqa: S301 - + # Check that halted_container is preserved assert deserialized_error.halted_container == Failure('error') - assert deserialized_error.halted_container != Success('error') \ No newline at end of file + assert deserialized_error.halted_container != Success('error') From e3002ac7fd3f8756551081717ea7e43cc402fafc Mon Sep 17 00:00:00 2001 From: proboscis Date: Thu, 27 Feb 2025 23:34:35 +0900 Subject: [PATCH 04/10] Fix: Update CHANGELOG to add 0.24.1 section as per review --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cbf753f4a..ec60379b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,10 +21,14 @@ See [0Ver](https://0ver.org/). - Add `default_error` parameter to `returns.converters.maybe_to_result`, which provides a default error value for `Failure` + +## 0.24.1 + ### Bugfixes - Add pickling support for `UnwrapFailedError` exception + ## 0.23.0 ### Features From 00be68299753c8ef6d005491f7c29154cbe5e93d Mon Sep 17 00:00:00 2001 From: proboscis Date: Thu, 27 Feb 2025 23:39:43 +0900 Subject: [PATCH 05/10] Fix: Update test to move test logic outside of except block as per review --- .../test_pickle_unwrap_failed_error.py | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/tests/test_primitives/test_exceptions/test_pickle_unwrap_failed_error.py b/tests/test_primitives/test_exceptions/test_pickle_unwrap_failed_error.py index 81a91ad29..2ef008a13 100644 --- a/tests/test_primitives/test_exceptions/test_pickle_unwrap_failed_error.py +++ b/tests/test_primitives/test_exceptions/test_pickle_unwrap_failed_error.py @@ -7,31 +7,42 @@ def test_pickle_unwrap_failed_error_from_maybe(): """Ensures that UnwrapFailedError with Maybe can be pickled.""" + error = None + serialized = None + error = None try: Nothing.unwrap() # This will raise UnwrapFailedError except UnwrapFailedError as error: # Serialize the error serialized = pickle.dumps(error) - # Deserialize - deserialized_error = pickle.loads(serialized) # noqa: S301 + assert error is not None + assert serialized is not None - # Check that halted_container is preserved - assert deserialized_error.halted_container == Nothing - assert deserialized_error.halted_container != Some(None) + # Deserialize + deserialized_error = pickle.loads(serialized) # noqa: S301 + + # Check that halted_container is preserved + assert deserialized_error.halted_container == Nothing + assert deserialized_error.halted_container != Some(None) def test_pickle_unwrap_failed_error_from_result(): """Ensures that UnwrapFailedError with Result can be pickled.""" + error = None + serialized = None + error = None try: Failure('error').unwrap() # This will raise UnwrapFailedError except UnwrapFailedError as error: # Serialize the error serialized = pickle.dumps(error) - # Deserialize - deserialized_error = pickle.loads(serialized) # noqa: S301 + assert error is not None + assert serialized is not None - # Check that halted_container is preserved - assert deserialized_error.halted_container == Failure('error') - assert deserialized_error.halted_container != Success('error') + # Deserialize + deserialized_error = pickle.loads(serialized) # noqa: S301 + # Check that halted_container is preserved + assert deserialized_error.halted_container == Failure('error') + assert deserialized_error.halted_container != Success('error') From 7e3681afc6d82e945d4558b7405ef55b5ea47940 Mon Sep 17 00:00:00 2001 From: proboscis Date: Thu, 27 Feb 2025 23:56:38 +0900 Subject: [PATCH 06/10] Fix: Clean up test code based on review feedback --- returns/primitives/exceptions.py | 2 +- .../test_exceptions/test_pickle_unwrap_failed_error.py | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/returns/primitives/exceptions.py b/returns/primitives/exceptions.py index 21a65d013..dc389a5c9 100644 --- a/returns/primitives/exceptions.py +++ b/returns/primitives/exceptions.py @@ -23,7 +23,7 @@ def __init__(self, container: Unwrappable) -> None: def __reduce__(self): # noqa: WPS603 """Custom reduce method for pickle protocol. - + This helps properly reconstruct the exception during unpickling. """ return ( diff --git a/tests/test_primitives/test_exceptions/test_pickle_unwrap_failed_error.py b/tests/test_primitives/test_exceptions/test_pickle_unwrap_failed_error.py index 2ef008a13..95c2d89cb 100644 --- a/tests/test_primitives/test_exceptions/test_pickle_unwrap_failed_error.py +++ b/tests/test_primitives/test_exceptions/test_pickle_unwrap_failed_error.py @@ -9,16 +9,12 @@ def test_pickle_unwrap_failed_error_from_maybe(): """Ensures that UnwrapFailedError with Maybe can be pickled.""" error = None serialized = None - error = None try: Nothing.unwrap() # This will raise UnwrapFailedError except UnwrapFailedError as error: # Serialize the error serialized = pickle.dumps(error) - assert error is not None - assert serialized is not None - # Deserialize deserialized_error = pickle.loads(serialized) # noqa: S301 @@ -31,16 +27,12 @@ def test_pickle_unwrap_failed_error_from_result(): """Ensures that UnwrapFailedError with Result can be pickled.""" error = None serialized = None - error = None try: Failure('error').unwrap() # This will raise UnwrapFailedError except UnwrapFailedError as error: # Serialize the error serialized = pickle.dumps(error) - assert error is not None - assert serialized is not None - # Deserialize deserialized_error = pickle.loads(serialized) # noqa: S301 # Check that halted_container is preserved From 0258abff4c6b7ee144b3ebb1173b0a777fb1a84e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 27 Feb 2025 14:59:18 +0000 Subject: [PATCH 07/10] [pre-commit.ci] auto fixes from pre-commit.com hooks --- returns/primitives/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/returns/primitives/exceptions.py b/returns/primitives/exceptions.py index dc389a5c9..21a65d013 100644 --- a/returns/primitives/exceptions.py +++ b/returns/primitives/exceptions.py @@ -23,7 +23,7 @@ def __init__(self, container: Unwrappable) -> None: def __reduce__(self): # noqa: WPS603 """Custom reduce method for pickle protocol. - + This helps properly reconstruct the exception during unpickling. """ return ( From b53843b5ea584a698db2867cdaf727d71ab9ecad Mon Sep 17 00:00:00 2001 From: proboscis Date: Fri, 28 Feb 2025 00:10:31 +0900 Subject: [PATCH 08/10] Fix: Remove redundant error variable initialization in test --- .../test_exceptions/test_pickle_unwrap_failed_error.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_primitives/test_exceptions/test_pickle_unwrap_failed_error.py b/tests/test_primitives/test_exceptions/test_pickle_unwrap_failed_error.py index 95c2d89cb..0cb0460b2 100644 --- a/tests/test_primitives/test_exceptions/test_pickle_unwrap_failed_error.py +++ b/tests/test_primitives/test_exceptions/test_pickle_unwrap_failed_error.py @@ -7,7 +7,6 @@ def test_pickle_unwrap_failed_error_from_maybe(): """Ensures that UnwrapFailedError with Maybe can be pickled.""" - error = None serialized = None try: Nothing.unwrap() # This will raise UnwrapFailedError @@ -25,7 +24,6 @@ def test_pickle_unwrap_failed_error_from_maybe(): def test_pickle_unwrap_failed_error_from_result(): """Ensures that UnwrapFailedError with Result can be pickled.""" - error = None serialized = None try: Failure('error').unwrap() # This will raise UnwrapFailedError From b6a2e81e122c957f170a5ac8708603d5aa097399 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 27 Feb 2025 18:30:41 +0300 Subject: [PATCH 09/10] Update test_pickle_unwrap_failed_error.py --- .../test_exceptions/test_pickle_unwrap_failed_error.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tests/test_primitives/test_exceptions/test_pickle_unwrap_failed_error.py b/tests/test_primitives/test_exceptions/test_pickle_unwrap_failed_error.py index 0cb0460b2..51d0ab5f2 100644 --- a/tests/test_primitives/test_exceptions/test_pickle_unwrap_failed_error.py +++ b/tests/test_primitives/test_exceptions/test_pickle_unwrap_failed_error.py @@ -11,15 +11,10 @@ def test_pickle_unwrap_failed_error_from_maybe(): try: Nothing.unwrap() # This will raise UnwrapFailedError except UnwrapFailedError as error: - # Serialize the error serialized = pickle.dumps(error) - # Deserialize deserialized_error = pickle.loads(serialized) # noqa: S301 - - # Check that halted_container is preserved assert deserialized_error.halted_container == Nothing - assert deserialized_error.halted_container != Some(None) def test_pickle_unwrap_failed_error_from_result(): @@ -28,11 +23,7 @@ def test_pickle_unwrap_failed_error_from_result(): try: Failure('error').unwrap() # This will raise UnwrapFailedError except UnwrapFailedError as error: - # Serialize the error serialized = pickle.dumps(error) - # Deserialize deserialized_error = pickle.loads(serialized) # noqa: S301 - # Check that halted_container is preserved assert deserialized_error.halted_container == Failure('error') - assert deserialized_error.halted_container != Success('error') From d6ff6f7ff3cb6c016b9aaf974ee839fc92f485a2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 27 Feb 2025 15:31:31 +0000 Subject: [PATCH 10/10] [pre-commit.ci] auto fixes from pre-commit.com hooks --- .../test_exceptions/test_pickle_unwrap_failed_error.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_primitives/test_exceptions/test_pickle_unwrap_failed_error.py b/tests/test_primitives/test_exceptions/test_pickle_unwrap_failed_error.py index 51d0ab5f2..6dda4b9a3 100644 --- a/tests/test_primitives/test_exceptions/test_pickle_unwrap_failed_error.py +++ b/tests/test_primitives/test_exceptions/test_pickle_unwrap_failed_error.py @@ -1,8 +1,8 @@ import pickle # noqa: S403 -from returns.maybe import Nothing, Some +from returns.maybe import Nothing from returns.primitives.exceptions import UnwrapFailedError -from returns.result import Failure, Success +from returns.result import Failure def test_pickle_unwrap_failed_error_from_maybe():