From a63b945fe51c46273fd18af44d4ade3bfa29ea49 Mon Sep 17 00:00:00 2001 From: "Tim D. Smith" Date: Thu, 15 May 2025 08:12:25 -0700 Subject: [PATCH] Make NoSolutionError pickleable Sometimes you have an unsolveable optimization problem running in a subprocess. It's useful to allow NoSolutionError to be pickleable so that the exception can propagate back to the main process. Without this change, unpickling the exception fails. BaseException has an implementation of __reduce__, which defines the state to pickle, which remembers the args that its constructor received. Upon deserialization, the constructor of the exception is called with the arguments that the BaseException constructor received. If the BaseException constructor does not receive NoSolutionError's mandatory `problem` argument, we will try to call the NoSolutionError constructor without it, which will fail. An alternative is reimplementing __reduce__ on NoSolutionError. --- dnachisel/DnaOptimizationProblem/NoSolutionError.py | 5 ++++- tests/test_NoSolutionError.py | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/dnachisel/DnaOptimizationProblem/NoSolutionError.py b/dnachisel/DnaOptimizationProblem/NoSolutionError.py index b104676..b727be5 100644 --- a/dnachisel/DnaOptimizationProblem/NoSolutionError.py +++ b/dnachisel/DnaOptimizationProblem/NoSolutionError.py @@ -5,7 +5,10 @@ class NoSolutionError(Exception): def __init__(self, message, problem, constraint=None, location=None): """Initialize.""" - Exception.__init__(self, message) + # Passing all of our args into the superclass constructor allows + # this exception to roundtrip through pickle; + # https://stackoverflow.com/a/41809333 + Exception.__init__(self, message, problem, constraint, location) self.message = message self.problem = problem self.constraint = constraint diff --git a/tests/test_NoSolutionError.py b/tests/test_NoSolutionError.py index 888991d..d9fb1d8 100644 --- a/tests/test_NoSolutionError.py +++ b/tests/test_NoSolutionError.py @@ -1,3 +1,4 @@ +import pickle import pytest from dnachisel import ( DnaOptimizationProblem, @@ -45,3 +46,9 @@ def test_no_solution_error_exhaustive_search(): with pytest.raises(NoSolutionError) as err: problem.resolve_constraints() assert "Exhaustive search failed" in str(err.value) + + +def test_nosolutionerror_roundtrips_through_pickle(): + err = NoSolutionError("my message", "my problem", "my constraint", "my location") + roundtripped = pickle.loads(pickle.dumps(err)) + assert dir(err) == dir(roundtripped)