ResultContainer is a python module that contains the Result class that mimics the Result Result Enum. The Result Enum is used for situations when errors are expected and are easily handled, or you do not want to have lots of try/except clauses. The Result enum wraps a value in an Ok variant, until there is an exception or error raised, and then it is converted to the Err variant.
The two Result states are:
Result.Ok(value)valueis wrapped within anOk.
Result.Err(e)eis an error message wrapped within anErr.
- Represents success (non-error state). The
valueis wrapped within theOk(). - Can be initialized with
Ok(value)Ok(value)➥ syntactic-sugar for ➥Result.Ok(value)
- Math operations are redirected to
valueand rewrap the solution or concatenate the errors.Ok(value1) + Ok(value2)➣Ok(value1 + value2)Ok(value1) + Err(e)➣Err(e + "a + b with b as Err")Err(e1) + Err(e2)➣Err(e1 + "a + b with a and b as Err.")
- All attributes and methods not associated with
Resultare redirected tovalue.Ok(value).method()is equivalent toOk(value.method())andOk(value).attribis equivalent toOk(value.attrib).Ok(value).raises()does NOT becomeOk(value.raises())becauseResult.raises()exists.
- Comparisons redirect to comparing the wrapped
valueifOk. But mixed comparisons assume:Err(e) < Ok(value)andErr(e1) == Err(e2)for anyvalue,e,e1, ande2.Ok(value1) < Ok(value2)➣value1 < valueErr(e) < Ok(value2)➣TrueOk(value1) < Err(e)➣FalseErr(e1) < Err(e2)➣FalseErr(e1) <= Err(e2)➣True
-
Represents a failure (error-state) and contains one more more error messages.
-
Can be initialized with
Err(error_msg)Err(e)➥ syntactic-sugar for ➥Result.Err(e)
-
If an
Ok(value)operation fails, then it is converted to anErr(e), whereestores the error message.econtains an error message, error code, and traceback information where the error occured.
-
Any operation on
Err(e)results in another error message being appended toe.
There are methods built into Result to check if an error has been raised, or the unwrap the value/error to get its contents.
- Variants for Success and Failure: Two variants,
Ok(value)for successful outcomes, andErr(e)for errors that have resulted. Provides a flexible mechanism for chaining operations on theOkvalue while propagating errors throughErr. - Attribute and Method Transparency: Automatically passes attributes, methods, and math operations to the value contained within an
Ok, otherwise propagates theErr(e). - Utility Methods: Implements helper methods for error propagation, transformation, and querying (e.g.,
.map(),.apply(),.unwrap_or(),.expect(),.raises()) for concise and readable handling of success and error cases.
To install the module
pip install --upgrade git+https://github.com/ScottBoyce-Python/ResultContainer.gitor you can clone the respository with
git clone https://github.com/ScottBoyce-Python/ResultContainer.gitand then move the file ResultContainer/ResultContainer.py to wherever you want to use it.
Below are examples showcasing how to create and interact with a ResultContainer.
from ResultContainer import Result, Ok, Err
# Wrap values in Ok state:
a = Result(5) # Default is to store as Ok.
# Explicitly wrap values in Ok state:
a = Result.Ok(5)
# Wrap values in Ok state, Ok(value) is equalivent to Result.Ok()
a = Ok(5)
# Wrap values as an error
a = Result(5, False) # Flag says it is an error, so stored as Err("5")
# Note "5" is the error message and NOT the value.
# Explicitly wrap values in Err state:
a = Result.Err(5)
# Wrap values in Err state, Err(value) is equalivent to Result.Err()
a = Err(5)# Addition, Subtraction, Multiplication and Division
a = Result.Ok(5)
b = Result.Ok(50)
c = a + b # c = Ok(55)
d = c - 20 # d = Ok(35)
e = d * 2 # e = Ok(70)
e /= 10 # e = Ok(7)
f = e / 0 # f = Err("a / b resulted in an Exception. | ZeroDivisionError: division by zero")
g = f + 1 # g = Err("a / b resulted in an Exception. | ZeroDivisionError: division by zero | a + b with a as Err.")from datetime import datetime, timedelta
# Wrap a datetime.datetime object
dt = Ok(datetime(2024, 12, 19, 12, 0, 0)) # dt = Ok(2024-12-19 12:00:00)
# Grab the attributes
y1 = dt.year # y1 = Ok(2024)
y2 = dt.year.expect() # y2 = 2024 -> raises a ResultErr exception if not Ok.
# Use the methods
new_dt = dt + timedelta(days=5) # new_dt = Ok(2024-12-24 12:00:00)
new_dt_sub = dt + timedelta(days=-5) # new_dt = Ok(2024-12-14 12:00:00)
# Produce an invalid state
dt_large = Ok(datetime(9999, 12, 31)) # dt_large = Ok(9999-12-31 00:00:00)
bad_dt = dt + timedelta(days=10000) # bad_dt = Err("a + b resulted in an Exception. | OverflowError: date value out of range")
bad_dt.raises() # raises a ResultErr exceptionfrom ResultContainer import Result, Ok, Err
# raises() is a powerful check when chaining methods.
# It raises an exception if Err, otherwise returns the original Ok(value)
x = Result(10) # x = Ok(10)
y = x.raises() # y = Ok(10)
x /= 0 # x = Err("a /= b resulted in an Exception. | ZeroDivisionError: division by zero")
y = x.raises() # Raises the following exception:
# Traceback (most recent call last):
# File "example.py", line 7, in <module>
# x.raises()
# ^^^^^^^^^^
# File "ResultContainer/ResultContainer.py", line 957, in raises
# raise self._Err
# ResultErr:
# [1] a /= b resulted in an Exception.
# [12] ZeroDivisionError: division by zero1 | from ResultContainer import Result, Ok, Err
2 | from datetime import datetime, timedelta
3 |
4 | dt = Result(datetime(9999, 12, 31))
5 |
6 | bad_dt = dt + timedelta(days=10000)
7 |
8 | bad_dt.raises()
# Raises the following exception.
# Note the exception says it occured on `line 6` despite being called on `line 8`
# Traceback (most recent call last):
# File "example.py", line 8, in <module>
# bad_dt.raises()
# File "ResultContainer/ResultContainer.py", line 957, in raises
# raise self._Err
# ResultErr:
# File "ResultContainer/example.py", line 6, in <module>
# bad_dt = dt + timedelta(days=10000)
#
# [1] a + b resulted in an Exception.
# [12] OverflowError: date value out of rangefrom math import sqrt
# to use an external function, like sqrt
# It must be passed to either apply or map or extracted with expect.
# apply converts Ok to Err if the func fails, while map raises an exception.
a = Ok(9) # Ok(9)
b = a.apply(sqrt) # Ok(3.0)
c = Ok(-9) # Ok(-9)
d = c.apply(sqrt) # Err("Result.apply exception. | ValueError: math domain error")
e = sqrt(c.expect()) # raises an error
plus1 = lambda x: x + 1
a = Ok(5)
b = Ok(0)
c = (a / b).map_or(10, plus1).map_or(20, plus1).map_or(30, plus1) # c = Err() -> Ok(10) -> Ok(11) -> Ok(12)
d = (a / 0).map_or(10, plus1).map_or(20, plus1).map_or(30, plus1) # d = Err() -> Ok(10) -> Ok(11) -> Ok(12)This project uses pytest and pytest-xdist for testing. Tests are located in the tests folder. To run tests, install the required packages and execute the following command:
pip install pytest pytest-xdist
pytest # run all tests, note options are set in the pyproject.toml file
Note, that the pyproject.toml contains the flags used for pytest.
This project is licensed under the MIT License. See the LICENSE file for details.
Contributions are welcome! Please open an issue or submit a pull request for any improvements or bug fixes.
Scott E. Boyce