{"id":13468807,"url":"https://github.com/rustedpy/result","last_synced_at":"2025-05-14T06:13:15.673Z","repository":{"id":39612475,"uuid":"47977681","full_name":"rustedpy/result","owner":"rustedpy","description":"NOT MAINTAINED - A simple Rust like Result type for Python 3. Fully type annotated.","archived":false,"fork":false,"pushed_at":"2024-08-19T18:43:16.000Z","size":198,"stargazers_count":1659,"open_issues_count":16,"forks_count":83,"subscribers_count":12,"default_branch":"main","last_synced_at":"2025-05-06T23:41:22.269Z","etag":null,"topics":["functional-programming","mypy","python","python3","railway-oriented-programming","rust","strongly-typed","type-safety","typechecking"],"latest_commit_sha":null,"homepage":"https://github.com/rustedpy/result/issues/201","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rustedpy.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-12-14T13:49:05.000Z","updated_at":"2025-05-04T06:58:35.000Z","dependencies_parsed_at":"2023-11-25T02:23:11.902Z","dependency_job_id":"913ade01-f7ea-4b96-a97b-4e93c9baebfe","html_url":"https://github.com/rustedpy/result","commit_stats":{"total_commits":102,"total_committers":20,"mean_commits":5.1,"dds":0.6470588235294117,"last_synced_commit":"5da3241706fd3d0c178738aab2528261ae0bd021"},"previous_names":["dbrgn/result"],"tags_count":27,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rustedpy%2Fresult","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rustedpy%2Fresult/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rustedpy%2Fresult/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rustedpy%2Fresult/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rustedpy","download_url":"https://codeload.github.com/rustedpy/result/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254081588,"owners_count":22011651,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["functional-programming","mypy","python","python3","railway-oriented-programming","rust","strongly-typed","type-safety","typechecking"],"created_at":"2024-07-31T15:01:19.462Z","updated_at":"2025-05-14T06:13:15.641Z","avatar_url":"https://github.com/rustedpy.png","language":"Python","readme":"# 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) -\u003e 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) -\u003e 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 is_ok(user_result):\n    # type(user_result.ok_value) == User\n    do_something(user_result.ok_value)\nelse:\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) -\u003e 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(\u003chttps://doc.rust-lang.org/std/result/enum.Result.html\u003e) have been\nimplemented, only the ones that make sense in the Python context.\nAll of this in a package allowing easier handling of values that can\nbe OK or not, without resorting to custom exceptions.\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\u003e\u003e\u003e from result import Ok, Err\n\u003e\u003e\u003e res1 = Ok('yay')\n\u003e\u003e\u003e res2 = Err('nay')\n```\n\nChecking whether a result is `Ok` or `Err`:\n\n``` python\nif is_err(result):\n    raise RuntimeError(result.err_value)\ndo_something(result.ok_value)\n```\nor\n``` python\nif is_ok(result):\n    do_something(result.ok_value)\nelse:\n    raise RuntimeError(result.err_value)\n```\n\nAlternatively, `isinstance` can be used (interchangeably to type guard functions\n`is_ok` and `is_err`). However, relying on `isinstance` may result in code that\nis slightly less readable and less concise:\n\n``` python\nif isinstance(result, Err):\n    raise RuntimeError(result.err_value)\ndo_something(result.ok_value)\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\u003e\u003e\u003e res1 = Ok('yay')\n\u003e\u003e\u003e res2 = Err('nay')\n\u003e\u003e\u003e isinstance(res1, OkErr)\nTrue\n\u003e\u003e\u003e isinstance(res2, OkErr)\nTrue\n\u003e\u003e\u003e isinstance(1, OkErr)\nFalse\n\u003e\u003e\u003e isinstance(res1, (Ok, Err))\nTrue\n```\n\nConvert a `Result` to the value or `None`:\n\n``` python\n\u003e\u003e\u003e res1 = Ok('yay')\n\u003e\u003e\u003e res2 = Err('nay')\n\u003e\u003e\u003e res1.ok()\n'yay'\n\u003e\u003e\u003e res2.ok()\nNone\n```\n\nConvert a `Result` to the error or `None`:\n\n``` python\n\u003e\u003e\u003e res1 = Ok('yay')\n\u003e\u003e\u003e res2 = Err('nay')\n\u003e\u003e\u003e res1.err()\nNone\n\u003e\u003e\u003e res2.err()\n'nay'\n```\n\nAccess the value directly, without any other checks:\n\n``` python\n\u003e\u003e\u003e res1 = Ok('yay')\n\u003e\u003e\u003e res2 = Err('nay')\n\u003e\u003e\u003e res1.ok_value\n'yay'\n\u003e\u003e\u003e 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\u003e\u003e\u003e res1 = Ok('yay')\n\u003e\u003e\u003e res2 = Err('nay')\n\u003e\u003e\u003e res1.unwrap()\n'yay'\n\u003e\u003e\u003e res2.unwrap()\nTraceback (most recent call last):\nFile \"\u003cstdin\u003e\", line 1, in \u003cmodule\u003e\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\u003e\u003e\u003e res1.unwrap_err()\nTraceback (most recent call last):\n...\n\u003e\u003e\u003eres2.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\u003e\u003e\u003e res1 = Ok('yay')\n\u003e\u003e\u003e res2 = Err('nay')\n\u003e\u003e\u003e res1.expect('not ok')\n'yay'\n\u003e\u003e\u003e res2.expect('not ok')\nTraceback (most recent call last):\nFile \"\u003cstdin\u003e\", line 1, in \u003cmodule\u003e\nFile \"C:\\project\\result\\result.py\", line 101, in expect\n    raise UnwrapError(message)\nresult.result.UnwrapError: not ok\n\u003e\u003e\u003e res1.expect_err('not err')\nTraceback (most recent call last):\n...\n\u003e\u003e\u003e 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\u003e\u003e\u003e res1 = Ok('yay')\n\u003e\u003e\u003e res2 = Err('nay')\n\u003e\u003e\u003e res1.unwrap_or('default')\n'yay'\n\u003e\u003e\u003e res2.unwrap_or('default')\n'default'\n\u003e\u003e\u003e res1.unwrap_or_else(str.upper)\n'yay'\n\u003e\u003e\u003e 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\u003e\u003e\u003e res1 = Ok('yay')\n\u003e\u003e\u003e res2 = Err('nay')\n\u003e\u003e\u003e res1.unwrap_or_raise(ValueError)\n'yay'\n\u003e\u003e\u003e 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\u003e\u003e\u003e Ok(1).map(lambda x: x + 1)\nOk(2)\n\u003e\u003e\u003e Err('nay').map(lambda x: x + 1)\nErr('nay')\n\u003e\u003e\u003e Ok(1).map_or(-1, lambda x: x + 1)\n2\n\u003e\u003e\u003e Err(1).map_or(-1, lambda x: x + 1)\n-1\n\u003e\u003e\u003e Ok(1).map_or_else(lambda: 3, lambda x: x + 1)\n2\n\u003e\u003e\u003e Err('nay').map_or_else(lambda: 3, lambda x: x + 1)\n3\n\u003e\u003e\u003e Ok(1).map_err(lambda x: x + 1)\nOk(1)\n\u003e\u003e\u003e Err(1).map_err(lambda x: x + 1)\nErr(2)\n```\n\nTo save memory, both the `Ok` and `Err` classes are ‘slotted’, 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 ‘normal’\nfunctions into `Result` returning ones by specifying one or more\nexception types:\n\n``` python\n@as_result(ValueError, IndexError)\ndef f(value: int) -\u003e 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‘catch all’ `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 is_ok(res):\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 \u003c- 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 \u003c- Some(1);\n    y \u003c- Some(2);\n    Some(x + y)\n};\n```\n\nNote that if your do statement has multiple \u003cspan\nclass=\"title-ref\"\u003efor\\`s, you can access an identifier bound in a\nprevious \\`for\u003c/span\u003e. 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) -\u003e 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) -\u003e 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 should I use the `is_ok` (`is_err`) type guard function over the `is_ok` (`is_err`) method?**\n\nAs you can see in the following example, MyPy can only narrow the type correctly\nwhile using the type guard **functions**:\n```python\nresult: Result[int, str]\n\nif is_ok(result):\n    reveal_type(result)  # \"result.result.Ok[builtins.int]\"\nelse:\n    reveal_type(result)  # \"result.result.Err[builtins.str]\"\n\nif result.is_ok():\n    reveal_type(result)  # \"Union[result.result.Ok[builtins.int], result.result.Err[builtins.str]]\"\nelse:\n    reveal_type(result)  # \"Union[result.result.Ok[builtins.int], result.result.Err[builtins.str]]\"\n```\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","funding_links":[],"categories":["Python","Utilities","Awesome Functional Python"],"sub_categories":["Libraries"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frustedpy%2Fresult","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frustedpy%2Fresult","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frustedpy%2Fresult/lists"}