A simple Rust like Result type for Python 3. Fully type annotated.
.. image:: https://img.shields.io/github/workflow/status/dbrgn/result/CI/master :alt: GitHub Workflow Status (branch) :target: https://github.com/dbrgn/result/actions?query=workflow%3ACI+branch%3Amaster
.. image:: https://codecov.io/gh/dbrgn/result/branch/master/graph/badge.svg :alt: Coverage :target: https://codecov.io/gh/dbrgn/result
A simple Result type for Python 3
inspired by Rust__, fully type annotated.
The idea is that a result value can be either
Ok(value)or
Err(error), with a way to differentiate between the two.
Okand
Errare both classes encapsulating an arbitrary value.
Result[T, E]is a generic type alias for
typing.Union[Ok[T], Err[E]]. It will change code like this:
.. sourcecode:: python
def get_user_by_email(email: str) -> Tuple[Optional[User], Optional[str]]: """ Return the user instance or an error message. """ if not user_exists(email): return None, 'User does not exist' if not user_active(email): return None, 'User is inactive' user = get_user(email) return user, Noneuser, reason = get_user_by_email('[email protected]') if user is None: raise RuntimeError('Could not fetch user: %s' % reason) else: do_something(user)
To something like this:
.. sourcecode:: python
from result import Ok, Err, Resultdef get_user_by_email(email: str) -> Result[User, str]: """ Return the user instance or an error message. """ if not user_exists(email): return Err('User does not exist') if not user_active(email): return Err('User is inactive') user = get_user(email) return Ok(user)
user_result = get_user_by_email(email) if isinstance(user_result, Ok): # type(user_result.value) == User do_something(user_result.value) else: # type(user_result.value) == str raise RuntimeError('Could not fetch user: %s' % user_result.value)
As this is Python and not Rust, you will lose some of the advantages that it brings, like elegant combinations with the
matchstatement. On the other side, you don't have to return semantically unclear tuples anymore.
Not all methods (https://doc.rust-lang.org/std/result/enum.Result.html) have been implemented, only the ones that make sense in the Python context. By using
isinstanceto check for
Okor
Erryou get type safe access to the contained value when using
MyPy__ to typecheck your code. All of this in a package allowing easier handling of values that can be OK or not, without resorting to custom exceptions.
Creating an instance::
>>> from result import Ok, Err >>> res1 = Ok('yay') >>> res2 = Err('nay')
Checking whether a result is
Okor
Err. With
isinstanceyou get type safe access that can be checked with MyPy. The
is_ok()or
is_err()methods can be used if you don't need the type safety with MyPy::
>>> res = Ok('yay') >>> isinstance(res, Ok) True >>> isinstance(res, Err) False >>> res.is_ok() True >>> res.is_err() False
You can also check if an object is
Okor
Errby using the
OkErrtype. Please note that this type is designed purely for convenience, and should not be used for anything else. Using
(Ok, Err)also works fine::
>>> res1 = Ok('yay') >>> res2 = Err('nay') >>> isinstance(res1, OkErr) True >>> isinstance(res2, OkErr) True >>> isinstance(1, OkErr) False >>> isinstance(res1, (Ok, Err)) True
Convert a
Resultto the value or
None::
>>> res1 = Ok('yay') >>> res2 = Err('nay') >>> res1.ok() 'yay' >>> res2.ok() None
Convert a
Resultto the error or
None::
>>> res1 = Ok('yay') >>> res2 = Err('nay') >>> res1.err() None >>> res2.err() 'nay'
Access the value directly, without any other checks::
>>> res1 = Ok('yay') >>> res2 = Err('nay') >>> res1.value 'yay' >>> res2.value 'nay'
Note that this is a property, you cannot assign to it. Results are immutable.
For your convenience, simply creating an
Okresult without value is the same as using
True::
>>> res1 = Ok() >>> res1.value True
The
unwrapmethod returns the value if
Okand
unwrap_errmethod returns the error value if
Err, otherwise it raises an
UnwrapError::
>>> res1 = Ok('yay') >>> res2 = Err('nay') >>> res1.unwrap() 'yay' >>> res2.unwrap() Traceback (most recent call last): File "", line 1, in File "C:\project\result\result.py", line 107, in unwrap return self.expect("Called `Result.unwrap()` on an `Err` value") File "C:\project\result\result.py", line 101, in expect raise UnwrapError(message) result.result.UnwrapError: Called `Result.unwrap()` on an `Err` value >>> res1.unwrap_err() Traceback (most recent call last): ... >>>res2.unwrap_err() 'nay'
A custom error message can be displayed instead by using
expectand
expect_err::
>>> res1 = Ok('yay') >>> res2 = Err('nay') >>> res1.expect('not ok') 'yay' >>> res2.expect('not ok') Traceback (most recent call last): File "", line 1, in File "C:\project\result\result.py", line 101, in expect raise UnwrapError(message) result.result.UnwrapError: not ok >>> res1.expect_err('not err') Traceback (most recent call last): ... >>> res2.expect_err('not err') 'nay'
A default value can be returned instead by using
unwrap_or::
>>> res1 = Ok('yay') >>> res2 = Err('nay') >>> res1.unwrap_or('default') 'yay' >>> res2.unwrap_or('default') 'default'
Values and errors can be mapped using
map,
map_or,
map_or_elseand
map_err::
Ok(1).map(lambda x: x + 1) Ok(2) Err('nay').map(lambda x: x + 1) Err('nay') Ok(1).mapor(-1, lambda x: x + 1) 2 Err(1).mapor(-1, lambda x: x + 1) -1 Ok(1).maporelse(lambda: 3, lambda x: x + 1) 2 Err('nay').maporelse(lambda: 3, lambda x: x + 1) 3 Ok(1).maperr(lambda x: x + 1) Ok(1) Err(1).maperr(lambda x: x + 1) Err(2)
There is
a bug in MyPy_ which can be triggered in some scenarios. Using
if isinstance(res, Ok)instead of
if res.is_ok()will help in some cases. Otherwise using
one of these workarounds_ can help.
MIT License