result


Nameresult JSON
Version 0.17.0 PyPI version JSON
download
home_pagehttps://github.com/rustedpy/result
SummaryA Rust-like result type for Python
upload_time2024-06-02 16:39:54
maintainerrustedpy github org members (https://github.com/rustedpy)
docs_urlNone
authorDanilo Bargen
requires_python>=3.8
licenseMIT
keywords rust result enum
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Result

[![GitHub Workflow Status (branch)](https://img.shields.io/github/actions/workflow/status/rustedpy/result/ci.yml?branch=main)](https://github.com/rustedpy/result/actions/workflows/ci.yml?query=branch%3Amain)
[![Coverage](https://codecov.io/gh/rustedpy/result/branch/main/graph/badge.svg)](https://codecov.io/gh/rustedpy/result)

A simple Result type for Python 3 [inspired by
Rust](https://doc.rust-lang.org/std/result/), fully type annotated.

## Installation

Latest release:

``` sh
$ pip install result
```

Latest GitHub `main` branch version:

``` sh
$ pip install git+https://github.com/rustedpy/result
```

## Summary

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:

``` 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, 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:

``` python
from result import Ok, Err, Result, is_ok, is_err

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): # or `is_ok(user_result)`
    # type(user_result.ok_value) == User
    do_something(user_result.ok_value)
else: # or `elif is_err(user_result)`
    # type(user_result.err_value) == str
    raise RuntimeError('Could not fetch user: %s' % user_result.err_value)
```

Note that `.ok_value` exists only on an instance of `Ok` and
`.err_value` exists only on an instance of `Err`.

And if you're using python version `3.10` or later, you can use the
elegant `match` statement as well:

``` python
from result import Result, Ok, Err

def divide(a: int, b: int) -> Result[int, str]:
    if b == 0:
        return Err("Cannot divide by zero")
    return Ok(a // b)

values = [(10, 0), (10, 5)]
for a, b in values:
    match divide(a, b):
        case Ok(value):
            print(f"{a} // {b} == {value}")
        case Err(e):
            print(e)
```

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](https://mypy.readthedocs.io/)
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

Auto generated API docs are also available at
[./docs/README.md](./docs/README.md).

Creating an instance:

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

Checking whether a result is `Ok` or `Err`. You can either use `is_ok`
and `is_err` type guard **functions** or `isinstance`. This way 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:

``` python
>>> res = Ok('yay')
>>> isinstance(res, Ok)
True
>>> is_ok(res)
True
>>> isinstance(res, Err)
False
>>> is_err(res)
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:

``` python
>>> res1 = Ok('yay')
>>> res2 = Err('nay')
>>> isinstance(res1, OkErr)
True
>>> isinstance(res2, OkErr)
True
>>> isinstance(1, OkErr)
False
>>> isinstance(res1, (Ok, Err))
True
```

The benefit of `isinstance` is better type checking that type guards currently
do not offer,

```python
res1: Result[int, str] = some_result()
if isinstance(res1, Err):
    print("Error...:", res1.err_value) # res1 is narrowed to an Err
    return
res1.ok()

res2: Result[int, str] = some_result()
if res1.is_err():
    print("Error...:", res2.err_value) # res1 is NOT narrowed to an Err here
    return
res1.ok()
```

There is a proposed [PEP 724 – Stricter Type Guards](https://peps.python.org/pep-0724/)
which may allow the `is_ok` and `is_err` type guards to work as expected in
future versions of Python.

Convert a `Result` to the value or `None`:

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

Convert a `Result` to the error or `None`:

``` python
>>> res1 = Ok('yay')
>>> res2 = Err('nay')
>>> res1.err()
None
>>> res2.err()
'nay'
```

Access the value directly, without any other checks:

``` python
>>> res1 = Ok('yay')
>>> res2 = Err('nay')
>>> res1.ok_value
'yay'
>>> res2.err_value
'nay'
```

Note that this is a property, you cannot assign to it. Results are
immutable.

When the value inside is irrelevant, we suggest using `None` or a
`bool`, but you're free to use any value you think works best. An
instance of a `Result` (`Ok` or `Err`) must always contain something. If
you're looking for a type that might contain a value you may be
interested in a [maybe](https://github.com/rustedpy/maybe).

The `unwrap` method returns the value if `Ok` and `unwrap_err` method
returns the error value if `Err`, otherwise it raises an `UnwrapError`:

``` python
>>> 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`:

``` python
>>> 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` or
`unwrap_or_else`:

``` python
>>> res1 = Ok('yay')
>>> res2 = Err('nay')
>>> res1.unwrap_or('default')
'yay'
>>> res2.unwrap_or('default')
'default'
>>> res1.unwrap_or_else(str.upper)
'yay'
>>> res2.unwrap_or_else(str.upper)
'NAY'
```

The `unwrap` method will raised an `UnwrapError`. A custom exception can
be raised by using the `unwrap_or_raise` method instead:

``` python
>>> res1 = Ok('yay')
>>> res2 = Err('nay')
>>> res1.unwrap_or_raise(ValueError)
'yay'
>>> res2.unwrap_or_raise(ValueError)
ValueError: nay
```

Values and errors can be mapped using `map`, `map_or`, `map_or_else` and
`map_err`:

``` python
>>> 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)
```

To save memory, both the `Ok` and `Err` classes are ‘slotted’, i.e. they
define `__slots__`. This means assigning arbitrary attributes to
instances will raise `AttributeError`.

### `as_result` Decorator

The `as_result()` decorator can be used to quickly turn ‘normal’
functions into `Result` returning ones by specifying one or more
exception types:

``` python
@as_result(ValueError, IndexError)
def f(value: int) -> int:
    if value == 0:
        raise ValueError  # becomes Err
    elif value == 1:
        raise IndexError  # becomes Err
    elif value == 2:
        raise KeyError  # raises Exception
    else:
        return value  # becomes Ok

res = f(0)  # Err[ValueError()]
res = f(1)  # Err[IndexError()]
res = f(2)  # raises KeyError
res = f(3)  # Ok[3]
```

`Exception` (or even `BaseException`) can be specified to create a
‘catch all’ `Result` return type. This is effectively the same as `try`
followed by `except Exception`, which is not considered good practice in
most scenarios, and hence this requires explicit opt-in.

Since `as_result` is a regular decorator, it can be used to wrap
existing functions (also from other libraries), albeit with a slightly
unconventional syntax (without the usual `@`):

``` python
import third_party

x = third_party.do_something(...)  # could raise; who knows?

safe_do_something = as_result(Exception)(third_party.do_something)

res = safe_do_something(...)  # Ok(...) or Err(...)
if isinstance(res, Ok):
    print(res.ok_value)
```

### Do notation

Do notation is syntactic sugar for a sequence of `and_then()` calls.
Much like the equivalent in Rust or Haskell, but with different syntax.
Instead of `x <- Ok(1)` we write `for x in Ok(1)`. Since the syntax is
generator-based, the final result must be the first line, not the last.

``` python
final_result: Result[int, str] = do(
    Ok(x + y)
    for x in Ok(1)
    for y in Ok(2)
)
```

Note that if you exclude the type annotation,
`final_result: Result[float, int] = ...`, your type checker may be
unable to infer the return type. To avoid an errors or warnings from
your type checker, you should add a type hint when using the `do`
function.

This is similar to Rust's [m!
macro](https://docs.rs/do-notation/latest/do_notation/):

``` rust
use do_notation::m;
let r = m! {
    x <- Some(1);
    y <- Some(2);
    Some(x + y)
};
```

Note that if your do statement has multiple <span
class="title-ref">for\`s, you can access an identifier bound in a
previous \`for</span>. Example:

``` python
my_result: Result[int, str] = do(
    f(x, y, z)
    for x in get_x()
    for y in calculate_y_from_x(x)
    for z in calculate_z_from_x_y(x, y)
)
```

You can use `do()` with awaited values as follows:

``` python
async def process_data(data) -> Result[int, str]:
    res1 = await get_result_1(data)
    res2 = await get_result_2(data)
    return do(
        Ok(x + y)
        for x in res1
        for y in res2
    )
```

However, if you want to await something inside the expression, use
`do_async()`:

``` python
async def process_data(data) -> Result[int, str]:
    return do_async(
        Ok(x + y)
        for x in await get_result_1(data)
        for y in await get_result_2(data)
    )
```

Troubleshooting `do()` calls:

``` python
TypeError("Got async_generator but expected generator")
```

Sometimes regular `do()` can handle async values, but this error means
you have hit a case where it does not. You should use `do_async()` here
instead.

## Contributing

These steps should work on any Unix-based system (Linux, macOS, etc) with Python
and `make` installed. On Windows, you will need to refer to the Python
documentation (linked below) and reference the `Makefile` for commands to run
from the non-unix shell you're using on Windows.

1. Setup and activate a virtual environment. See [Python docs][pydocs-venv] for more
   information about virtual environments and setup.
2. Run `make install` to install dependencies
3. Switch to a new git branch and make your changes
4. Test your changes:
  - `make test`
  - `make lint`
  - You can also start a Python REPL and import `result`
5. Update documentation
  - Edit any relevant docstrings, markdown files
  - Run `make docs`
6. Add an entry to the [changelog](./CHANGELOG.md)
5. Git commit all your changes and create a new PR.

[pydocs-venv]: https://docs.python.org/3/library/venv.html

## FAQ

-   **Why do I get the "Cannot infer type argument" error with MyPy?**

There is [a bug in MyPy](https://github.com/python/mypy/issues/230)
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](https://github.com/python/mypy/issues/3889#issuecomment-325997911)
can help.

## Related Projects

- [dry-python/returns: Make your functions return something meaningful, typed, and safe!](https://github.com/dry-python/returns)
- [alexandermalyga/poltergeist: Rust-like error handling in Python, with type-safety in mind.](https://github.com/alexandermalyga/poltergeist)

## License

MIT License

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/rustedpy/result",
    "name": "result",
    "maintainer": "rustedpy github org members (https://github.com/rustedpy)",
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": null,
    "keywords": "rust, result, enum",
    "author": "Danilo Bargen",
    "author_email": "mail@dbrgn.ch",
    "download_url": "https://files.pythonhosted.org/packages/a3/47/2175be65744aa4d8419c27bd3a7a7d65af5bcad7a4dc6a812c00778754f0/result-0.17.0.tar.gz",
    "platform": null,
    "description": "# Result\n\n[![GitHub Workflow Status (branch)](https://img.shields.io/github/actions/workflow/status/rustedpy/result/ci.yml?branch=main)](https://github.com/rustedpy/result/actions/workflows/ci.yml?query=branch%3Amain)\n[![Coverage](https://codecov.io/gh/rustedpy/result/branch/main/graph/badge.svg)](https://codecov.io/gh/rustedpy/result)\n\nA simple Result type for Python 3 [inspired by\nRust](https://doc.rust-lang.org/std/result/), fully type annotated.\n\n## Installation\n\nLatest release:\n\n``` sh\n$ pip install result\n```\n\nLatest GitHub `main` branch version:\n\n``` sh\n$ pip install git+https://github.com/rustedpy/result\n```\n\n## Summary\n\nThe idea is that a result value can be either `Ok(value)` or\n`Err(error)`, with a way to differentiate between the two. `Ok` and\n`Err` are both classes encapsulating an arbitrary value. `Result[T, E]`\nis a generic type alias for `typing.Union[Ok[T], Err[E]]`. It will\nchange code like this:\n\n``` python\ndef get_user_by_email(email: str) -> Tuple[Optional[User], Optional[str]]:\n    \"\"\"\n    Return the user instance or an error message.\n    \"\"\"\n    if not user_exists(email):\n        return None, 'User does not exist'\n    if not user_active(email):\n        return None, 'User is inactive'\n    user = get_user(email)\n    return user, None\n\nuser, reason = get_user_by_email('ueli@example.com')\nif user is None:\n    raise RuntimeError('Could not fetch user: %s' % reason)\nelse:\n    do_something(user)\n```\n\nTo something like this:\n\n``` python\nfrom result import Ok, Err, Result, is_ok, is_err\n\ndef get_user_by_email(email: str) -> Result[User, str]:\n    \"\"\"\n    Return the user instance or an error message.\n    \"\"\"\n    if not user_exists(email):\n        return Err('User does not exist')\n    if not user_active(email):\n        return Err('User is inactive')\n    user = get_user(email)\n    return Ok(user)\n\nuser_result = get_user_by_email(email)\nif isinstance(user_result, Ok): # or `is_ok(user_result)`\n    # type(user_result.ok_value) == User\n    do_something(user_result.ok_value)\nelse: # or `elif is_err(user_result)`\n    # type(user_result.err_value) == str\n    raise RuntimeError('Could not fetch user: %s' % user_result.err_value)\n```\n\nNote that `.ok_value` exists only on an instance of `Ok` and\n`.err_value` exists only on an instance of `Err`.\n\nAnd if you're using python version `3.10` or later, you can use the\nelegant `match` statement as well:\n\n``` python\nfrom result import Result, Ok, Err\n\ndef divide(a: int, b: int) -> Result[int, str]:\n    if b == 0:\n        return Err(\"Cannot divide by zero\")\n    return Ok(a // b)\n\nvalues = [(10, 0), (10, 5)]\nfor a, b in values:\n    match divide(a, b):\n        case Ok(value):\n            print(f\"{a} // {b} == {value}\")\n        case Err(e):\n            print(e)\n```\n\nNot all methods\n(<https://doc.rust-lang.org/std/result/enum.Result.html>) have been\nimplemented, only the ones that make sense in the Python context. By\nusing `isinstance` to check for `Ok` or `Err` you get type safe access\nto the contained value when using [MyPy](https://mypy.readthedocs.io/)\nto typecheck your code. All of this in a package allowing easier\nhandling of values that can be OK or not, without resorting to custom\nexceptions.\n\n## API\n\nAuto generated API docs are also available at\n[./docs/README.md](./docs/README.md).\n\nCreating an instance:\n\n``` python\n>>> from result import Ok, Err\n>>> res1 = Ok('yay')\n>>> res2 = Err('nay')\n```\n\nChecking whether a result is `Ok` or `Err`. You can either use `is_ok`\nand `is_err` type guard **functions** or `isinstance`. This way you get\ntype safe access that can be checked with MyPy. The `is_ok()` or\n`is_err()` **methods** can be used if you don't need the type safety\nwith MyPy:\n\n``` python\n>>> res = Ok('yay')\n>>> isinstance(res, Ok)\nTrue\n>>> is_ok(res)\nTrue\n>>> isinstance(res, Err)\nFalse\n>>> is_err(res)\nFalse\n>>> res.is_ok()\nTrue\n>>> res.is_err()\nFalse\n```\n\nYou can also check if an object is `Ok` or `Err` by using the `OkErr`\ntype. Please note that this type is designed purely for convenience, and\nshould not be used for anything else. Using `(Ok, Err)` also works fine:\n\n``` python\n>>> res1 = Ok('yay')\n>>> res2 = Err('nay')\n>>> isinstance(res1, OkErr)\nTrue\n>>> isinstance(res2, OkErr)\nTrue\n>>> isinstance(1, OkErr)\nFalse\n>>> isinstance(res1, (Ok, Err))\nTrue\n```\n\nThe benefit of `isinstance` is better type checking that type guards currently\ndo not offer,\n\n```python\nres1: Result[int, str] = some_result()\nif isinstance(res1, Err):\n    print(\"Error...:\", res1.err_value) # res1 is narrowed to an Err\n    return\nres1.ok()\n\nres2: Result[int, str] = some_result()\nif res1.is_err():\n    print(\"Error...:\", res2.err_value) # res1 is NOT narrowed to an Err here\n    return\nres1.ok()\n```\n\nThere is a proposed [PEP 724 \u2013 Stricter Type Guards](https://peps.python.org/pep-0724/)\nwhich may allow the `is_ok` and `is_err` type guards to work as expected in\nfuture versions of Python.\n\nConvert a `Result` to the value or `None`:\n\n``` python\n>>> res1 = Ok('yay')\n>>> res2 = Err('nay')\n>>> res1.ok()\n'yay'\n>>> res2.ok()\nNone\n```\n\nConvert a `Result` to the error or `None`:\n\n``` python\n>>> res1 = Ok('yay')\n>>> res2 = Err('nay')\n>>> res1.err()\nNone\n>>> res2.err()\n'nay'\n```\n\nAccess the value directly, without any other checks:\n\n``` python\n>>> res1 = Ok('yay')\n>>> res2 = Err('nay')\n>>> res1.ok_value\n'yay'\n>>> res2.err_value\n'nay'\n```\n\nNote that this is a property, you cannot assign to it. Results are\nimmutable.\n\nWhen the value inside is irrelevant, we suggest using `None` or a\n`bool`, but you're free to use any value you think works best. An\ninstance of a `Result` (`Ok` or `Err`) must always contain something. If\nyou're looking for a type that might contain a value you may be\ninterested in a [maybe](https://github.com/rustedpy/maybe).\n\nThe `unwrap` method returns the value if `Ok` and `unwrap_err` method\nreturns the error value if `Err`, otherwise it raises an `UnwrapError`:\n\n``` python\n>>> res1 = Ok('yay')\n>>> res2 = Err('nay')\n>>> res1.unwrap()\n'yay'\n>>> res2.unwrap()\nTraceback (most recent call last):\nFile \"<stdin>\", line 1, in <module>\nFile \"C:\\project\\result\\result.py\", line 107, in unwrap\n    return self.expect(\"Called `Result.unwrap()` on an `Err` value\")\nFile \"C:\\project\\result\\result.py\", line 101, in expect\n    raise UnwrapError(message)\nresult.result.UnwrapError: Called `Result.unwrap()` on an `Err` value\n>>> res1.unwrap_err()\nTraceback (most recent call last):\n...\n>>>res2.unwrap_err()\n'nay'\n```\n\nA custom error message can be displayed instead by using `expect` and\n`expect_err`:\n\n``` python\n>>> res1 = Ok('yay')\n>>> res2 = Err('nay')\n>>> res1.expect('not ok')\n'yay'\n>>> res2.expect('not ok')\nTraceback (most recent call last):\nFile \"<stdin>\", line 1, in <module>\nFile \"C:\\project\\result\\result.py\", line 101, in expect\n    raise UnwrapError(message)\nresult.result.UnwrapError: not ok\n>>> res1.expect_err('not err')\nTraceback (most recent call last):\n...\n>>> res2.expect_err('not err')\n'nay'\n```\n\nA default value can be returned instead by using `unwrap_or` or\n`unwrap_or_else`:\n\n``` python\n>>> res1 = Ok('yay')\n>>> res2 = Err('nay')\n>>> res1.unwrap_or('default')\n'yay'\n>>> res2.unwrap_or('default')\n'default'\n>>> res1.unwrap_or_else(str.upper)\n'yay'\n>>> res2.unwrap_or_else(str.upper)\n'NAY'\n```\n\nThe `unwrap` method will raised an `UnwrapError`. A custom exception can\nbe raised by using the `unwrap_or_raise` method instead:\n\n``` python\n>>> res1 = Ok('yay')\n>>> res2 = Err('nay')\n>>> res1.unwrap_or_raise(ValueError)\n'yay'\n>>> res2.unwrap_or_raise(ValueError)\nValueError: nay\n```\n\nValues and errors can be mapped using `map`, `map_or`, `map_or_else` and\n`map_err`:\n\n``` python\n>>> Ok(1).map(lambda x: x + 1)\nOk(2)\n>>> Err('nay').map(lambda x: x + 1)\nErr('nay')\n>>> Ok(1).map_or(-1, lambda x: x + 1)\n2\n>>> Err(1).map_or(-1, lambda x: x + 1)\n-1\n>>> Ok(1).map_or_else(lambda: 3, lambda x: x + 1)\n2\n>>> Err('nay').map_or_else(lambda: 3, lambda x: x + 1)\n3\n>>> Ok(1).map_err(lambda x: x + 1)\nOk(1)\n>>> Err(1).map_err(lambda x: x + 1)\nErr(2)\n```\n\nTo save memory, both the `Ok` and `Err` classes are \u2018slotted\u2019, i.e. they\ndefine `__slots__`. This means assigning arbitrary attributes to\ninstances will raise `AttributeError`.\n\n### `as_result` Decorator\n\nThe `as_result()` decorator can be used to quickly turn \u2018normal\u2019\nfunctions into `Result` returning ones by specifying one or more\nexception types:\n\n``` python\n@as_result(ValueError, IndexError)\ndef f(value: int) -> int:\n    if value == 0:\n        raise ValueError  # becomes Err\n    elif value == 1:\n        raise IndexError  # becomes Err\n    elif value == 2:\n        raise KeyError  # raises Exception\n    else:\n        return value  # becomes Ok\n\nres = f(0)  # Err[ValueError()]\nres = f(1)  # Err[IndexError()]\nres = f(2)  # raises KeyError\nres = f(3)  # Ok[3]\n```\n\n`Exception` (or even `BaseException`) can be specified to create a\n\u2018catch all\u2019 `Result` return type. This is effectively the same as `try`\nfollowed by `except Exception`, which is not considered good practice in\nmost scenarios, and hence this requires explicit opt-in.\n\nSince `as_result` is a regular decorator, it can be used to wrap\nexisting functions (also from other libraries), albeit with a slightly\nunconventional syntax (without the usual `@`):\n\n``` python\nimport third_party\n\nx = third_party.do_something(...)  # could raise; who knows?\n\nsafe_do_something = as_result(Exception)(third_party.do_something)\n\nres = safe_do_something(...)  # Ok(...) or Err(...)\nif isinstance(res, Ok):\n    print(res.ok_value)\n```\n\n### Do notation\n\nDo notation is syntactic sugar for a sequence of `and_then()` calls.\nMuch like the equivalent in Rust or Haskell, but with different syntax.\nInstead of `x <- Ok(1)` we write `for x in Ok(1)`. Since the syntax is\ngenerator-based, the final result must be the first line, not the last.\n\n``` python\nfinal_result: Result[int, str] = do(\n    Ok(x + y)\n    for x in Ok(1)\n    for y in Ok(2)\n)\n```\n\nNote that if you exclude the type annotation,\n`final_result: Result[float, int] = ...`, your type checker may be\nunable to infer the return type. To avoid an errors or warnings from\nyour type checker, you should add a type hint when using the `do`\nfunction.\n\nThis is similar to Rust's [m!\nmacro](https://docs.rs/do-notation/latest/do_notation/):\n\n``` rust\nuse do_notation::m;\nlet r = m! {\n    x <- Some(1);\n    y <- Some(2);\n    Some(x + y)\n};\n```\n\nNote that if your do statement has multiple <span\nclass=\"title-ref\">for\\`s, you can access an identifier bound in a\nprevious \\`for</span>. Example:\n\n``` python\nmy_result: Result[int, str] = do(\n    f(x, y, z)\n    for x in get_x()\n    for y in calculate_y_from_x(x)\n    for z in calculate_z_from_x_y(x, y)\n)\n```\n\nYou can use `do()` with awaited values as follows:\n\n``` python\nasync def process_data(data) -> Result[int, str]:\n    res1 = await get_result_1(data)\n    res2 = await get_result_2(data)\n    return do(\n        Ok(x + y)\n        for x in res1\n        for y in res2\n    )\n```\n\nHowever, if you want to await something inside the expression, use\n`do_async()`:\n\n``` python\nasync def process_data(data) -> Result[int, str]:\n    return do_async(\n        Ok(x + y)\n        for x in await get_result_1(data)\n        for y in await get_result_2(data)\n    )\n```\n\nTroubleshooting `do()` calls:\n\n``` python\nTypeError(\"Got async_generator but expected generator\")\n```\n\nSometimes regular `do()` can handle async values, but this error means\nyou have hit a case where it does not. You should use `do_async()` here\ninstead.\n\n## Contributing\n\nThese steps should work on any Unix-based system (Linux, macOS, etc) with Python\nand `make` installed. On Windows, you will need to refer to the Python\ndocumentation (linked below) and reference the `Makefile` for commands to run\nfrom the non-unix shell you're using on Windows.\n\n1. Setup and activate a virtual environment. See [Python docs][pydocs-venv] for more\n   information about virtual environments and setup.\n2. Run `make install` to install dependencies\n3. Switch to a new git branch and make your changes\n4. Test your changes:\n  - `make test`\n  - `make lint`\n  - You can also start a Python REPL and import `result`\n5. Update documentation\n  - Edit any relevant docstrings, markdown files\n  - Run `make docs`\n6. Add an entry to the [changelog](./CHANGELOG.md)\n5. Git commit all your changes and create a new PR.\n\n[pydocs-venv]: https://docs.python.org/3/library/venv.html\n\n## FAQ\n\n-   **Why do I get the \"Cannot infer type argument\" error with MyPy?**\n\nThere is [a bug in MyPy](https://github.com/python/mypy/issues/230)\nwhich can be triggered in some scenarios. Using `if isinstance(res, Ok)`\ninstead of `if res.is_ok()` will help in some cases. Otherwise using\n[one of these\nworkarounds](https://github.com/python/mypy/issues/3889#issuecomment-325997911)\ncan help.\n\n## Related Projects\n\n- [dry-python/returns: Make your functions return something meaningful, typed, and safe!](https://github.com/dry-python/returns)\n- [alexandermalyga/poltergeist: Rust-like error handling in Python, with type-safety in mind.](https://github.com/alexandermalyga/poltergeist)\n\n## License\n\nMIT License\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "A Rust-like result type for Python",
    "version": "0.17.0",
    "project_urls": {
        "Homepage": "https://github.com/rustedpy/result"
    },
    "split_keywords": [
        "rust",
        " result",
        " enum"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "e29019110ce9374c3db619e2df0816f2c58e4ddc5cdad5f7284cd81d8b30b7cb",
                "md5": "9ebea88be419c89c3126c52bbce3e7b2",
                "sha256": "49fd668b4951ad15800b8ccefd98b6b94effc789607e19c65064b775570933e8"
            },
            "downloads": -1,
            "filename": "result-0.17.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "9ebea88be419c89c3126c52bbce3e7b2",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 11689,
            "upload_time": "2024-06-02T16:39:52",
            "upload_time_iso_8601": "2024-06-02T16:39:52.715982Z",
            "url": "https://files.pythonhosted.org/packages/e2/90/19110ce9374c3db619e2df0816f2c58e4ddc5cdad5f7284cd81d8b30b7cb/result-0.17.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "a3472175be65744aa4d8419c27bd3a7a7d65af5bcad7a4dc6a812c00778754f0",
                "md5": "1c7b8358842acd5401384982a25ef452",
                "sha256": "b73da420c0cb1a3bf741dbd41ff96dedafaad6a1b3ef437a9e33e380bb0d91cf"
            },
            "downloads": -1,
            "filename": "result-0.17.0.tar.gz",
            "has_sig": false,
            "md5_digest": "1c7b8358842acd5401384982a25ef452",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 20180,
            "upload_time": "2024-06-02T16:39:54",
            "upload_time_iso_8601": "2024-06-02T16:39:54.510219Z",
            "url": "https://files.pythonhosted.org/packages/a3/47/2175be65744aa4d8419c27bd3a7a7d65af5bcad7a4dc6a812c00778754f0/result-0.17.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-06-02 16:39:54",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "rustedpy",
    "github_project": "result",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "tox": true,
    "lcname": "result"
}
        
Elapsed time: 0.26532s