Skip to main content

A rust-like result type for Python

Project description

GitHub Workflow Status (branch) Coverage

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. Ok and Err are 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:

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, None

user, reason = get_user_by_email('ueli@example.com')
if user is None:
    raise RuntimeError('Could not fetch user: %s' % reason)
else:
    do_something(user)

To something like this:

from result import Ok, Err, Result

def 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 match statement. 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 isinstance to check for Ok or Err you 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.

API

Creating an instance:

>>> from result import Ok, Err
>>> res1 = Ok('yay')
>>> res2 = Err('nay')

Checking whether a result is Ok or Err. With isinstance you 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 Ok or Err by using the OkErr type. 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 Result to the value or None:

>>> res1 = Ok('yay')
>>> res2 = Err('nay')
>>> res1.ok()
'yay'
>>> res2.ok()
None

Convert a Result to 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 Ok result without value is the same as using True:

>>> res1 = Ok()
>>> res1.value
True

The unwrap method returns the value if Ok and unwrap_err method 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 "<stdin>", line 1, in <module>
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 expect and expect_err:

>>> res1 = Ok('yay')
>>> res2 = Err('nay')
>>> res1.expect('not ok')
'yay'
>>> res2.expect('not ok')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
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_else and map_err:

>>> Ok(1).map(lambda x: x + 1)
Ok(2)
>>> Err('nay').map(lambda x: x + 1)
Err('nay')
>>> Ok(1).map_or(-1, lambda x: x + 1)
2
>>> Err(1).map_or(-1, lambda x: x + 1)
-1
>>> Ok(1).map_or_else(lambda: 3, lambda x: x + 1)
2
>>> Err('nay').map_or_else(lambda: 3, lambda x: x + 1)
3
>>> Ok(1).map_err(lambda x: x + 1)
Ok(1)
>>> Err(1).map_err(lambda x: x + 1)
Err(2)

FAQ

  • Why do I get the “Cannot infer type argument” error with MyPy?

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.

License

MIT License

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

result-0.6.0rc1.tar.gz (8.7 kB view details)

Uploaded Source

Built Distribution

result-0.6.0rc1-py3-none-any.whl (7.6 kB view details)

Uploaded Python 3

File details

Details for the file result-0.6.0rc1.tar.gz.

File metadata

  • Download URL: result-0.6.0rc1.tar.gz
  • Upload date:
  • Size: 8.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/2.0.0 pkginfo/1.5.0.1 requests/2.23.0 setuptools/46.1.3 requests-toolbelt/0.9.1 tqdm/4.45.0 CPython/3.8.2

File hashes

Hashes for result-0.6.0rc1.tar.gz
Algorithm Hash digest
SHA256 5501a1ea0037c8b3f3d6211e2f0684a777b7d828ed9b29cb90fad6edb2fa58d5
MD5 d0aa49d461b69324dcabed5e61d6029b
BLAKE2b-256 41391340d5d6778e448a47f9243d4bd6083d08a14bdde81c4d3b776e66ac3c0d

See more details on using hashes here.

File details

Details for the file result-0.6.0rc1-py3-none-any.whl.

File metadata

  • Download URL: result-0.6.0rc1-py3-none-any.whl
  • Upload date:
  • Size: 7.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/2.0.0 pkginfo/1.5.0.1 requests/2.23.0 setuptools/46.1.3 requests-toolbelt/0.9.1 tqdm/4.45.0 CPython/3.8.2

File hashes

Hashes for result-0.6.0rc1-py3-none-any.whl
Algorithm Hash digest
SHA256 ab1dc84fff83a5b43abd620fd3476505be6ac42794566e6a797bfaf85415c291
MD5 a7bb75dde9fef3e46f1d006ec31d6fa8
BLAKE2b-256 24079ba25a7dd3c42497b78f0a2dd4c98a0074c705cf0271cd2c964947bcde94

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page