Last active
January 1, 2025 23:38
-
-
Save Spitfire1900/ce2adf8bf2f68176f8b30b5500fdc770 to your computer and use it in GitHub Desktop.
Rust, Go, and custom Union style Error as Values for Python
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import enum | |
import typing | |
from enum import Enum | |
from typing import Any, Callable, Generic, Literal, ParamSpec, Tuple, TypeVar, Union | |
P = ParamSpec("P") | |
R = TypeVar("R") | |
T = TypeVar("T") | |
__all__: list[str] = [ | |
"box", | |
"split", | |
"either", | |
"WrappedException", | |
"Result", | |
] | |
class WrappedException(Exception): | |
''' | |
Exception class that wraps another exception for clearer error handling. | |
This class allows exceptions to be caught and handled in a structured way, | |
providing a clear indication of the original error. | |
Args: | |
exception (Exception): The exception to wrap. | |
''' | |
__match_args__: Tuple[Literal['__cause__']] = ("__cause__", ) | |
def __init__(self, exception: Exception) -> None: | |
self.__context__ = exception | |
self.__cause__ = exception | |
class ResultType(Enum): | |
OK = enum.auto() | |
ERR = enum.auto() | |
class Result(Generic[T]): | |
''' | |
Represents the outcome of an operation, encapsulating either a successful result or an error. | |
Inspired by the Result type in Rust, this class provides a way to handle success and error states | |
in a type-safe manner. It allows functions to return values without relying on exceptions for control flow. | |
The Result class has two possible states: OK, which contains a valid result, and ERR, which contains | |
an error wrapped in a WrappedException. This design encourages explicit error handling in client code. | |
Attributes: | |
result_type (ResultType): The type indicating whether the result is OK or an error. | |
val (T): The value of the result, which can be a successful value or a WrappedException. | |
Args: | |
result_type (ResultType): The result type indicating success (OK) or failure (ERR). | |
val (T): The value to encapsulate in the Result, which may be a valid result or an error. | |
Methods: | |
__repr__(): Returns a string representation of the Result. | |
is_ok() -> bool: Returns True if the result is OK, otherwise False. | |
is_err() -> bool: Returns True if the result is an error, otherwise False. | |
unwrap() -> T: Returns the encapsulated value if it is OK; raises a WrappedException if it is an error. | |
''' | |
def __init__(self, result_type: ResultType, | |
val: T | WrappedException) -> None: | |
self.result_type: ResultType = result_type | |
self.val: T | WrappedException | |
match result_type: | |
case ResultType.OK: | |
match val: | |
case Exception(): | |
raise ValueError( | |
f"Cannot create an OK Result with an exception: {val}" | |
) | |
case _: | |
self.val = val | |
case ResultType.ERR: | |
match val: | |
case WrappedException(): | |
self.val = val | |
case Exception(): | |
self.val = WrappedException(val) | |
case _: | |
raise ValueError( | |
f"Cannot create an ERR Result with a non-exception: {val}" | |
) | |
def __repr__(self) -> str: | |
return f"Result({repr(self.val)})" | |
def is_ok(self) -> bool: | |
return not isinstance(self.val, Exception) | |
def is_err(self) -> bool: | |
return isinstance(self.val, Exception) | |
def unwrap(self) -> T: | |
''' | |
Returns the value of the result if it's successful. | |
Raises: | |
WrappedException: If the result is an error, raises the wrapped exception. | |
''' | |
match self.val: | |
case WrappedException() as err: | |
raise err | |
case _: | |
return self.val | |
def box(fn: Callable[P, R], *my_args: P.args, | |
**my_kwargs: P.kwargs) -> Result[WrappedException] | Result[R]: | |
''' | |
Executes a function and returns a Result indicating success or failure. | |
Inspired by the Rust programming language's error handling style, | |
this function will return a Result that either contains a successful | |
result or a WrappedException if an error occurs. | |
If the function executes successfully, returns a Result containing the result. | |
If an exception occurs, returns a Result containing a WrappedException. | |
Args: | |
fn (Callable[P, R]): The function to execute. | |
*my_args: Positional arguments to pass to the function. | |
**my_kwargs: Keyword arguments to pass to the function. | |
Returns: | |
Result[WrappedException] | Result[R]: The result of the function execution. | |
''' | |
try: | |
val = fn(*my_args, **my_kwargs) | |
return Result(result_type=ResultType.OK, val=val) | |
except Exception as _err: # pylint: disable=broad-exception-caught | |
return Result(result_type=ResultType.ERR, val=WrappedException(_err)) | |
def split(fn: Callable[P, R], *my_args: P.args, | |
**my_kwargs: P.kwargs) -> Tuple[R, None] | Tuple[None, Exception]: | |
''' | |
Executes a function and returns a tuple indicating success or failure. | |
Inspired by Go's error handling idiom, this function will return a tuple | |
where the first element is the successful result (or None in case of error) | |
and the second element is an Exception (or None if successful). | |
If the function executes successfully, returns a tuple containing the result and None. | |
If an exception occurs, returns a tuple containing None and a WrappedException. | |
Args: | |
fn (Callable[P, R]): The function to execute. | |
*my_args: Positional arguments to pass to the function. | |
**my_kwargs: Keyword arguments to pass to the function. | |
Returns: | |
Tuple[R, None] | Tuple[None, Exception]: The result of the function execution. | |
''' | |
try: | |
return (fn(*my_args, **my_kwargs), None) | |
except Exception as _err: # pylint: disable=broad-exception-caught | |
return (None, WrappedException(_err)) | |
def either(fn: Callable[P, R], *my_args: P.args, | |
**my_kwargs: P.kwargs) -> Union[WrappedException, R]: | |
''' | |
Executes a function and returns either the result or a WrappedException. | |
Inspired by functional programming practices, this function provides a | |
straightforward way to handle errors by returning either the successful result | |
or a WrappedException if an error occurs. | |
If the function executes successfully, returns the result. | |
If an exception occurs, returns a WrappedException. | |
Args: | |
fn (Callable[P, R]): The function to execute. | |
*my_args: Positional arguments to pass to the function. | |
**my_kwargs: Keyword arguments to pass to the function. | |
Returns: | |
Union[WrappedException, R]: The result of the function execution or a WrappedException. | |
''' | |
try: | |
# | |
return fn(*my_args, **my_kwargs) | |
except Exception as _err: # pylint: disable=broad-ex | |
return WrappedException(_err) | |
def add(x: typing.Any, y: typing.Any = 4) -> typing.Any: | |
return x + y | |
def divide(x: typing.Any, y: typing.Any = 4) -> typing.Any: | |
return x / y | |
_box: Result[WrappedException] | Result[Any] = box(divide, 4, y=4) | |
match _box.val: | |
case WrappedException(TypeError()) as e: | |
raise RuntimeError("You need to pass a int for each value") from e | |
case WrappedException() as e: | |
raise e from e | |
case val: | |
print(f"everything was fine, got {val}") | |
v = val | |
r = Result(result_type=ResultType.OK, val=5) | |
if r.is_ok(): | |
v: int = r.unwrap() + 5 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment