Resulty
is a Python library that provides a Result
type for handling errors and exceptions in a functional programming style, inspired by the Result
type in Rust ๐ฆ. It offers a wide range of methods and features to handle errors and exceptions in a more expressive and composable way.
Result
type for representing success (Ok
) or failure (Err
) outcomes- Extensive set of methods for checking the state of a
Result
, unwrapping values, handling errors, and transformingResult
values (near 1:1 mapping with Rust'sResult
methods) - Unique decorator-based error propagation with
propagate_result
andasync_propagate_result
, mimicking the?
operator in Rust - Supports both synchronous and asynchronous code
- Installation
- Creating a
Result
- Checking the State of a
Result
- Unwrapping Values and Handling Errors
- Transforming and Mapping
Result
Values - Combining
Result
Values - Error Propagation with Decorators
- Contributing
- License
You can install resulty
using pip:
pip install resulty
To create a Result
instance, you can use the Ok
and Err
functions:
from resulty import Ok, Err
# Creating an Ok result
ok_result = Ok(42)
# Creating an Err result
err_result = Err("An error occurred")
Both Ok
and Err
functions can wrap any value.
resulty
provides several methods to check the state of a Result
instance and perform conditional operations based on its variant (Ok
or Err
).
The is_ok()
method returns True
if the Result
is an Ok
variant, and False
otherwise.
Example:
from resulty import Ok, Err
result = Ok(42)
print(result.is_ok()) # Output: True
result = Err("An error occurred")
print(result.is_ok()) # Output: False
The is_ok_and(predicate)
method returns True
if the Result
is an Ok
variant and the provided predicate function returns True
for the contained value. It returns False
otherwise.
Example:
from resulty import Ok, Err
result = Ok(42)
print(result.is_ok_and(lambda x: x > 0)) # Output: True
print(result.is_ok_and(lambda x: x < 0)) # Output: False
result = Err("An error occurred")
print(result.is_ok_and(lambda x: True)) # Output: False
The is_err()
method returns True
if the Result
is an Err
variant, and False
otherwise.
Example:
from resulty import Ok, Err
result = Ok(42)
print(result.is_err()) # Output: False
result = Err("An error occurred")
print(result.is_err()) # Output: True
The is_err_and(predicate)
method returns True
if the Result
is an Err
variant and the provided predicate function returns True
for the contained error. It returns False
otherwise.
Example:
from resulty import Ok, Err
result = Ok(42)
print(result.is_err_and(lambda x: True)) # Output: False
result = Err("An error occurred")
print(result.is_err_and(lambda x: len(x) > 0)) # Output: True
print(result.is_err_and(lambda x: len(x) == 0)) # Output: False
The and_also(result)
method returns the provided result
if the current Result
is an Ok
variant. If the current Result
is an Err
variant, it returns the current Result
.
Example:
from resulty import Ok, Err
ok_result = Ok(42)
other_result = Ok("Hello")
print(ok_result.and_also(other_result)) # Output: Ok("Hello")
err_result = Err("An error occurred")
print(err_result.and_also(other_result)) # Output: Err("An error occurred")
other_err_result = Err("Another error occurred")
print(err_result.and_also(other_err_result)) # Output: Err("An error occurred")
The or_if(result)
method returns the current Result
if it is an Ok
variant. If the current Result
is an Err
variant and the provided result
is an Ok
variant, it returns the provided result
. If both the current Result
and the provided result
are Err
variants, it returns the provided result
.
Example:
from resulty import Ok, Err
ok_result = Ok(42)
other_ok_result = Ok(0)
err_result = Err("An error occurred")
other_err_result = Err("Another error occurred")
print(ok_result.or_if(other_ok_result)) # Output: Ok(42)
print(ok_result.or_if(err_result)) # Output: Ok(42)
print(err_result.or_if(other_ok_result)) # Output: Ok(0)
print(err_result.or_if(other_err_result)) # Output: Err("Another error occurred")
resulty
provides several methods to safely unwrap the value contained in a Result
instance and handle potential errors. These methods allow you to access the contained value or error, or provide default values in case of an Err
variant.
The ok()
method returns the contained value if the Result
is an Ok
variant, and None
otherwise.
Example:
from resulty import Ok, Err
ok_result = Ok(42)
print(ok_result.ok()) # Output: 42
err_result = Err("An error occurred")
print(err_result.ok()) # Output: None
The err()
method returns the contained error if the Result
is an Err
variant, and None
otherwise.
Example:
from resulty import Ok, Err
ok_result = Ok(42)
print(ok_result.err()) # Output: None
err_result = Err("An error occurred")
print(err_result.err()) # Output: "An error occurred"
The expect(msg)
method returns the contained value if the Result
is an Ok
variant. If the Result
is an Err
variant, it raises a ResultException
with the provided error message.
Example:
from resulty import Ok, Err
ok_result = Ok(42)
print(ok_result.expect("Expected an Ok value")) # Output: 42
err_result = Err("An error occurred")
# Raises ResultException with the message "ResultException: Expected an Ok value\nErr(An error occurred)"
err_result.expect("Expected an Ok value")
The expect_err(msg)
method returns the contained error if the Result
is an Err
variant. If the Result
is an Ok
variant, it raises a ResultException
with the provided error message.
Example:
from resulty import Ok, Err
ok_result = Ok(42)
# Raises ResultException with the message "ResultException: Expected an Err value\nOk(42)"
ok_result.expect_err("Expected an Err value")
err_result = Err("An error occurred")
print(err_result.expect_err("Expected an Err value")) # Output: "An error occurred"
The unwrap()
method returns the contained value if the Result
is an Ok
variant. If the Result
is an Err
variant, it raises a ResultException
.
Example:
from resulty import Ok, Err
ok_result = Ok(42)
print(ok_result.unwrap()) # Output: 42
err_result = Err("An error occurred")
# Raises ResultException with the message "ResultException: called `.unwrap()` on an `Err` value\nErr(An error occurred)"
err_result.unwrap()
The unwrap_err()
method returns the contained error if the Result
is an Err
variant. If the Result
is an Ok
variant, it raises a ResultException
.
Example:
from resulty import Ok, Err
ok_result = Ok(42)
# Raises ResultException with the message "ResultException: called `.unwrap_err()` on an `Ok` value\nOk(42)"
ok_result.unwrap_err()
err_result = Err("An error occurred")
print(err_result.unwrap_err()) # Output: "An error occurred"
The unwrap_or(default)
method returns the contained value if the Result
is an Ok
variant. If the Result
is an Err
variant, it returns the provided default value.
Example:
from resulty import Ok, Err
ok_result = Ok(42)
print(ok_result.unwrap_or(0)) # Output: 42
err_result = Err("An error occurred")
print(err_result.unwrap_or(0)) # Output: 0
The unwrap_or_else(f)
method returns the contained value if the Result
is an Ok
variant. If the Result
is an Err
variant, it calls the provided function f
with the contained error and returns the result.
Example:
from resulty import Ok, Err
ok_result = Ok(42)
print(ok_result.unwrap_or_else(lambda x: 0)) # Output: 42
err_result = Err("An error occurred")
print(err_result.unwrap_or_else(lambda x: len(x))) # Output: 17 (length of the error message)
resulty
provides several methods to transform and map the value contained in a Result
instance. These methods allow you to apply functions to the contained value or error, or provide default values in case of an Err
variant.
The map(f)
method applies the provided function f
to the contained value if the Result
is an Ok
variant, and returns a new Result
with the mapped value. If the Result
is an Err
variant, it returns the original Result
without applying the function.
Example:
from resulty import Ok, Err
ok_result = Ok(42)
mapped_result = ok_result.map(lambda x: x * 2)
print(mapped_result) # Output: Ok(84)
err_result = Err("An error occurred")
mapped_result = err_result.map(lambda x: x * 2)
print(mapped_result) # Output: Err("An error occurred")
The map_err(f)
method applies the provided function f
to the contained error if the Result
is an Err
variant, and returns a new Result
with the mapped error. If the Result
is an Ok
variant, it returns the original Result
without applying the function.
Example:
from resulty import Ok, Err
ok_result = Ok(42)
mapped_result = ok_result.map_err(lambda x: f"Error: {x}")
print(mapped_result) # Output: Ok(42)
err_result = Err("An error occurred")
mapped_result = err_result.map_err(lambda x: f"Error: {x}")
print(mapped_result) # Output: Err("Error: An error occurred")
The map_or(default, f)
method applies the provided function f
to the contained value if the Result
is an Ok
variant, and returns the result. If the Result
is an Err
variant, it returns the provided default value.
Example:
from resulty import Ok, Err
ok_result = Ok(42)
mapped_value = ok_result.map_or(0, lambda x: x * 2)
print(mapped_value) # Output: 84
err_result = Err("An error occurred")
mapped_value = err_result.map_or(0, lambda x: x * 2)
print(mapped_value) # Output: 0
The map_or_else(default, f)
method applies the provided function f
to the contained value if the Result
is an Ok
variant, and returns the result. If the Result
is an Err
variant, it calls the provided function default
with the contained error and returns the result.
Example:
from resulty import Ok, Err
ok_result = Ok(42)
mapped_value = ok_result.map_or_else(lambda x: 0, lambda x: x * 2)
print(mapped_value) # Output: 84
err_result = Err("An error occurred")
mapped_value = err_result.map_or_else(lambda x: len(x), lambda x: x * 2)
print(mapped_value) # Output: 18
resulty
provides several methods to combine and chain multiple Result
values together. These methods allow you to perform operations on the contained values or errors of multiple Result
instances.
The and_then(f)
method applies the provided function f
to the contained value if the Result
is an Ok
variant, and returns the result of f
as a new Result
. If the Result
is an Err
variant, it returns the original Result
without applying the function.
Example:
from resulty import Ok, Err
ok_result = Ok(42)
combined_result = ok_result.and_then(lambda x: Ok(x * 2))
print(combined_result) # Output: Ok(84)
err_result = Err("An error occurred")
combined_result = err_result.and_then(lambda x: Ok(x * 2))
print(combined_result) # Output: Err("An error occurred")
The or_else(f)
method applies the provided function f
to the contained error if the Result
is an Err
variant, and returns the result of f
as a new Result
. If the Result
is an Ok
variant, it returns the original Result
without applying the function.
Example:
from resulty import Ok, Err
ok_result = Ok(42)
combined_result = ok_result.or_else(lambda x: Err(f"Error: {x}"))
print(combined_result) # Output: Ok(42)
err_result = Err("An error occurred")
combined_result = err_result.or_else(lambda x: Ok(0))
print(combined_result) # Output: Ok(0)
The inspect(f)
method applies the provided function f
to the contained value if the Result
is an Ok
variant, and returns the original Result
. The function f
is executed for its side effects and does not affect the returned Result
.
Example:
from resulty import Ok, Err
ok_result = Ok(42)
inspected_result = ok_result.inspect(lambda x: print(f"Value: {x}")) # Output: Value: 42
print(inspected_result) # Output: Ok(42)
err_result = Err("An error occurred")
inspected_result = err_result.inspect(lambda x: print(f"Value: {x}")) # No output
print(inspected_result) # Output: Err("An error occurred")
The inspect_err(f)
method applies the provided function f
to the contained error if the Result
is an Err
variant, and returns the original Result
. The function f
is executed for its side effects and does not affect the returned Result
.
Example:
from resulty import Ok, Err
ok_result = Ok(42)
inspected_result = ok_result.inspect_err(lambda x: print(f"Error: {x}")) # No output
print(inspected_result) # Output: Ok(42)
err_result = Err("An error occurred")
inspected_result = err_result.inspect_err(lambda x: print(f"Error: {x}")) # Output: Error: An error occurred
print(inspected_result) # Output: Err("An error occurred")
resulty
provides decorators that simplify error propagation and handling in function calls. These decorators allow you to automatically propagate errors when calling other functions that return a Result
and handle them in a more concise and readable way. They try to mimic the ?
operator in Rust for error propagation.
The @propagate_result
decorator is used to automatically propagate errors from called functions that return a Result
. When a function decorated with @propagate_result
calls another function that returns a Result
, it can use the up()
method to unwrap the value if it's an Ok
variant, or propagate the error if it's an Err
variant.
Example:
from resulty import Result, Ok, Err, propagate_result
def divide(a: int, b: int) -> Result[float, str]:
if b == 0:
return Err("Cannot divide by zero")
return Ok(a / b)
@propagate_result
def calculate() -> Result[float, str]:
result1 = divide(10, 2).up()
result2 = divide(20, 4).up()
return Ok(result1 + result2)
result = calculate()
print(result) # Output: Ok(10.0)
@propagate_result
def calculate_with_error() -> Result[float, str]:
result1 = divide(10, 2).up()
result2 = divide(20, 0).up() # Raises PropagatedResultException
return Ok(result1 + result2)
result = calculate_with_error()
print(result) # Output: Err("Cannot divide by zero")
In this example, the calculate
function is decorated with @propagate_result
. It calls the divide
function twice and uses the up()
method to unwrap the values if they are Ok
variants. If any of the calls to divide
return an Err
variant, the up()
method will raise a PropagatedResultException
, which will be caught by the @propagate_result
decorator and returned as an Err
variant.
The @async_propagate_result
decorator works similarly to @propagate_result
, but it is used for asynchronous functions. It automatically propagates errors when calling other functions that return a Result
and uses the up()
method to unwrap the values or propagate the errors.
Example:
from resulty import Result, Ok, Err, async_propagate_result
async def async_divide(a: int, b: int) -> Result[float, str]:
if b == 0:
return Err("Cannot divide by zero")
return Ok(a / b)
@async_propagate_result
async def async_calculate() -> Result[float, str]:
result1 = (await async_divide(10, 2)).up()
result2 = (await async_divide(20, 4)).up()
return Ok(result1 + result2)
async def main():
result = await async_calculate()
print(result) # Output: Ok(10.0)
import asyncio
asyncio.run(main())
In this example, the async_calculate
function is decorated with @async_propagate_result
. It calls the async_divide
function twice and uses the up()
method to unwrap the values if they are Ok
variants. If any of the calls to async_divide
return an Err
variant, the up()
method will raise a PropagatedResultException
, which will be caught by the @async_propagate_result
decorator and returned as an Err
variant.
These decorators provide a convenient way to handle error propagation when calling other functions that return a Result
. By using these decorators and the up()
method, you can write more concise and readable code when working with Result
values and error handling.
Contributions are welcome! If you find any issues or have suggestions for improvements, please open an issue or submit a pull request.
resulty
is licensed under the MIT License.