fates 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fates-0.1.0/PKG-INFO +112 -0
- fates-0.1.0/README.md +102 -0
- fates-0.1.0/pyproject.toml +83 -0
- fates-0.1.0/src/fates/__init__.py +6 -0
- fates-0.1.0/src/fates/_async.py +127 -0
- fates-0.1.0/src/fates/_err.py +183 -0
- fates-0.1.0/src/fates/_ok.py +172 -0
- fates-0.1.0/src/fates/_result.py +277 -0
- fates-0.1.0/src/fates/_types.py +20 -0
- fates-0.1.0/src/fates/_typevars.py +9 -0
- fates-0.1.0/src/fates/exceptions.py +2 -0
- fates-0.1.0/src/fates/py.typed +0 -0
fates-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: fates
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A generic Result[T, E] type for explicit error propagation.
|
|
5
|
+
Author: Evgeny Goryachev
|
|
6
|
+
Author-email: Evgeny Goryachev <saladware46@gmail.com>
|
|
7
|
+
Requires-Dist: typing-extensions>=4.0.0 ; python_full_version < '3.12'
|
|
8
|
+
Requires-Python: >=3.7
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# fates 🔮
|
|
12
|
+
|
|
13
|
+
A robust, fully-typed, and async-ready Result pattern implementation for Python.
|
|
14
|
+
|
|
15
|
+
`fates` brings expressive, functional error handling to Python, completely removing the need for defensive `try/except` blocks. It helps you build predictable pipelines with absolute type safety for both synchronous and asynchronous operations.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## ✨ Features
|
|
20
|
+
|
|
21
|
+
- **🛡️ 100% Type Safe:** Full `mypy` / `pyright` compliance using generic protocols and `TypeVar` covariance.
|
|
22
|
+
- **⚡ Async Native:** First-class support for asynchronous mapping and monadic binding.
|
|
23
|
+
- **🧩 Zero Dependencies:** Lightweight and clean, relying only on standard library primitives (and `typing_extensions` for older Python versions).
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 🚀 Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install fates
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 📖 Quick Start
|
|
36
|
+
|
|
37
|
+
### Synchronous Usage
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from fates import Result, Ok, Err
|
|
41
|
+
|
|
42
|
+
def divide(a: int, b: int) -> Result[float, str]:
|
|
43
|
+
if b == 0:
|
|
44
|
+
return Err("Cannot divide by zero")
|
|
45
|
+
return Ok(a / b)
|
|
46
|
+
|
|
47
|
+
# Monadic binding and mapping
|
|
48
|
+
result = (
|
|
49
|
+
divide(10, 2)
|
|
50
|
+
.map(lambda x: x * 2)
|
|
51
|
+
.unwrap_or(0.0)
|
|
52
|
+
)
|
|
53
|
+
print(result) # Output: 10.0
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Asynchronous Pipelines
|
|
57
|
+
|
|
58
|
+
`fates` shines when working with async databases or HTTP clients. Use `amap` and `abind` to pipe async operations seamlessly.
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
import asyncio
|
|
62
|
+
from fates import Ok, Err, Result
|
|
63
|
+
|
|
64
|
+
async def fetch_user_id(username: str) -> Result[int, str]:
|
|
65
|
+
# Simulating async DB call
|
|
66
|
+
await asyncio.sleep(0.1)
|
|
67
|
+
return Ok(42) if username == "admin" else Err("User not found")
|
|
68
|
+
|
|
69
|
+
async def get_user_role_async(user_id: int) -> Result[str, str]:
|
|
70
|
+
await asyncio.sleep(0.1)
|
|
71
|
+
return Ok("superuser")
|
|
72
|
+
|
|
73
|
+
async def main():
|
|
74
|
+
# Chain async functions using abind
|
|
75
|
+
pipeline = await fetch_user_id("admin").abind(get_user_role_async)
|
|
76
|
+
|
|
77
|
+
print(pipeline) # Output: Ok('superuser')
|
|
78
|
+
print(pipeline.unwrap()) # Output: superuser
|
|
79
|
+
|
|
80
|
+
asyncio.run(main())
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## 🛠️ API Reference
|
|
86
|
+
|
|
87
|
+
### Extracting Values
|
|
88
|
+
|
|
89
|
+
- `.unwrap()` — Returns the success value or raises an `UnwrapError`.
|
|
90
|
+
- `.unwrap_or(default)` — Returns the success value or a fallback value.
|
|
91
|
+
- `.unwrap_err()` — Returns the error value or raises an `UnwrapError`.
|
|
92
|
+
- `.expect(note)` — Returns the success value or crashes with a custom message.
|
|
93
|
+
|
|
94
|
+
### Transforming Results
|
|
95
|
+
|
|
96
|
+
- `.map(mapper)` — Transforms the success value inside `Ok`.
|
|
97
|
+
- `.map_err(mapper)` — Transforms the error value inside `Err`.
|
|
98
|
+
- `.bind(binder)` — Monadic bind. Chains another operation that returns a `Result`.
|
|
99
|
+
- `.catch(binder)` — Recovers from an error by returning an alternative `Result`.
|
|
100
|
+
- `.resolve(mapper)` — Merges both paths into a single success-type value.
|
|
101
|
+
|
|
102
|
+
### Async Operations
|
|
103
|
+
|
|
104
|
+
- `.amap(async_mapper)` — Asynchronously transforms the success value.
|
|
105
|
+
- `.amap_err(async_mapper)` — Asynchronously transforms the error value.
|
|
106
|
+
- `.abind(async_binder)` — Asynchronously chains another operation returning a `Result`.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## ⚖️ License
|
|
111
|
+
|
|
112
|
+
This project is licensed under the [MIT License](LICENSE).
|
fates-0.1.0/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# fates 🔮
|
|
2
|
+
|
|
3
|
+
A robust, fully-typed, and async-ready Result pattern implementation for Python.
|
|
4
|
+
|
|
5
|
+
`fates` brings expressive, functional error handling to Python, completely removing the need for defensive `try/except` blocks. It helps you build predictable pipelines with absolute type safety for both synchronous and asynchronous operations.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## ✨ Features
|
|
10
|
+
|
|
11
|
+
- **🛡️ 100% Type Safe:** Full `mypy` / `pyright` compliance using generic protocols and `TypeVar` covariance.
|
|
12
|
+
- **⚡ Async Native:** First-class support for asynchronous mapping and monadic binding.
|
|
13
|
+
- **🧩 Zero Dependencies:** Lightweight and clean, relying only on standard library primitives (and `typing_extensions` for older Python versions).
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 🚀 Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install fates
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 📖 Quick Start
|
|
26
|
+
|
|
27
|
+
### Synchronous Usage
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from fates import Result, Ok, Err
|
|
31
|
+
|
|
32
|
+
def divide(a: int, b: int) -> Result[float, str]:
|
|
33
|
+
if b == 0:
|
|
34
|
+
return Err("Cannot divide by zero")
|
|
35
|
+
return Ok(a / b)
|
|
36
|
+
|
|
37
|
+
# Monadic binding and mapping
|
|
38
|
+
result = (
|
|
39
|
+
divide(10, 2)
|
|
40
|
+
.map(lambda x: x * 2)
|
|
41
|
+
.unwrap_or(0.0)
|
|
42
|
+
)
|
|
43
|
+
print(result) # Output: 10.0
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Asynchronous Pipelines
|
|
47
|
+
|
|
48
|
+
`fates` shines when working with async databases or HTTP clients. Use `amap` and `abind` to pipe async operations seamlessly.
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
import asyncio
|
|
52
|
+
from fates import Ok, Err, Result
|
|
53
|
+
|
|
54
|
+
async def fetch_user_id(username: str) -> Result[int, str]:
|
|
55
|
+
# Simulating async DB call
|
|
56
|
+
await asyncio.sleep(0.1)
|
|
57
|
+
return Ok(42) if username == "admin" else Err("User not found")
|
|
58
|
+
|
|
59
|
+
async def get_user_role_async(user_id: int) -> Result[str, str]:
|
|
60
|
+
await asyncio.sleep(0.1)
|
|
61
|
+
return Ok("superuser")
|
|
62
|
+
|
|
63
|
+
async def main():
|
|
64
|
+
# Chain async functions using abind
|
|
65
|
+
pipeline = await fetch_user_id("admin").abind(get_user_role_async)
|
|
66
|
+
|
|
67
|
+
print(pipeline) # Output: Ok('superuser')
|
|
68
|
+
print(pipeline.unwrap()) # Output: superuser
|
|
69
|
+
|
|
70
|
+
asyncio.run(main())
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## 🛠️ API Reference
|
|
76
|
+
|
|
77
|
+
### Extracting Values
|
|
78
|
+
|
|
79
|
+
- `.unwrap()` — Returns the success value or raises an `UnwrapError`.
|
|
80
|
+
- `.unwrap_or(default)` — Returns the success value or a fallback value.
|
|
81
|
+
- `.unwrap_err()` — Returns the error value or raises an `UnwrapError`.
|
|
82
|
+
- `.expect(note)` — Returns the success value or crashes with a custom message.
|
|
83
|
+
|
|
84
|
+
### Transforming Results
|
|
85
|
+
|
|
86
|
+
- `.map(mapper)` — Transforms the success value inside `Ok`.
|
|
87
|
+
- `.map_err(mapper)` — Transforms the error value inside `Err`.
|
|
88
|
+
- `.bind(binder)` — Monadic bind. Chains another operation that returns a `Result`.
|
|
89
|
+
- `.catch(binder)` — Recovers from an error by returning an alternative `Result`.
|
|
90
|
+
- `.resolve(mapper)` — Merges both paths into a single success-type value.
|
|
91
|
+
|
|
92
|
+
### Async Operations
|
|
93
|
+
|
|
94
|
+
- `.amap(async_mapper)` — Asynchronously transforms the success value.
|
|
95
|
+
- `.amap_err(async_mapper)` — Asynchronously transforms the error value.
|
|
96
|
+
- `.abind(async_binder)` — Asynchronously chains another operation returning a `Result`.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## ⚖️ License
|
|
101
|
+
|
|
102
|
+
This project is licensed under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "fates"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "A generic Result[T, E] type for explicit error propagation."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [{ name = "Evgeny Goryachev", email = "saladware46@gmail.com" }]
|
|
7
|
+
requires-python = ">=3.7"
|
|
8
|
+
dependencies = ["typing_extensions>=4.0.0; python_version < '3.12'"]
|
|
9
|
+
|
|
10
|
+
[build-system]
|
|
11
|
+
requires = ["uv_build>=0.11.6,<0.12.0"]
|
|
12
|
+
build-backend = "uv_build"
|
|
13
|
+
|
|
14
|
+
[tool.ruff.lint]
|
|
15
|
+
select = ["ALL"]
|
|
16
|
+
ignore = ["COM812"]
|
|
17
|
+
external = ["WPS"]
|
|
18
|
+
|
|
19
|
+
[tool.ruff.lint.per-file-ignores]
|
|
20
|
+
"**/test_*.py" = ["S101", "D", "PLR2004"]
|
|
21
|
+
|
|
22
|
+
[tool.ty.rules]
|
|
23
|
+
all = "error"
|
|
24
|
+
|
|
25
|
+
[tool.mypy]
|
|
26
|
+
strict = true
|
|
27
|
+
packages = ["fates", "tests"]
|
|
28
|
+
|
|
29
|
+
[tool.pytest.ini_options]
|
|
30
|
+
|
|
31
|
+
asyncio_mode = "auto"
|
|
32
|
+
addopts = [
|
|
33
|
+
"--cov=fates",
|
|
34
|
+
"--cov-report=html",
|
|
35
|
+
"--cov-report=term",
|
|
36
|
+
"--xdoc",
|
|
37
|
+
"--xdoc-global-exec=from fates import Ok, Err, Result",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[tool.coverage.report]
|
|
41
|
+
exclude_also = ["\\A(?s:.*# pragma: exclude file.*)\\Z"]
|
|
42
|
+
|
|
43
|
+
[tool.ruff.lint.pydocstyle]
|
|
44
|
+
convention = "google"
|
|
45
|
+
|
|
46
|
+
[tool.flake8]
|
|
47
|
+
select = ["WPS"]
|
|
48
|
+
ignore = ["WPS214"]
|
|
49
|
+
allowed-domain-names = ["result"]
|
|
50
|
+
|
|
51
|
+
[tool.pyrefly]
|
|
52
|
+
project-includes = ["src/fates"]
|
|
53
|
+
|
|
54
|
+
[tool.taskipy.tasks]
|
|
55
|
+
check = "mypy && ty check && flake8 src && pytest"
|
|
56
|
+
cov = "pytest && python -c 'import webbrowser; webbrowser.open(\"htmlcov/index.html\")'"
|
|
57
|
+
|
|
58
|
+
[dependency-groups]
|
|
59
|
+
dev = [
|
|
60
|
+
"flake8-pyproject>=1.2.4",
|
|
61
|
+
"mypy>=2.1.0",
|
|
62
|
+
"pyrefly>=0.14.0",
|
|
63
|
+
"pytest-asyncio>=1.4.0",
|
|
64
|
+
"pytest-cov>=4.1.0",
|
|
65
|
+
"pytest-cov>=7.1.0",
|
|
66
|
+
"ruff>=0.15.15",
|
|
67
|
+
"taskipy>=1.14.1",
|
|
68
|
+
"ty>=0.0.40",
|
|
69
|
+
"wemake-python-styleguide>=1.6.2",
|
|
70
|
+
"xdoctest>=1.1.6",
|
|
71
|
+
]
|
|
72
|
+
docs = [
|
|
73
|
+
"furo>=2025.12.19",
|
|
74
|
+
"myst-parser>=5.1.0",
|
|
75
|
+
"sphinx>=9.1.0",
|
|
76
|
+
"sphinx-autobuild>=2025.8.25",
|
|
77
|
+
"sphinx-autodoc-typehints>=3.10.5",
|
|
78
|
+
"sphinx-autodoc2>=0.5.0",
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
[tool.uv.dependency-groups]
|
|
82
|
+
docs = { requires-python = ">=3.12" }
|
|
83
|
+
dev = { requires-python = ">=3.12" }
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from asyncio import ensure_future
|
|
4
|
+
from typing import TYPE_CHECKING, Generic, cast
|
|
5
|
+
|
|
6
|
+
from fates._typevars import DefaultT, E_co, NewE, NewT, T_co
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from asyncio import Task
|
|
10
|
+
from collections.abc import Awaitable, Generator
|
|
11
|
+
|
|
12
|
+
from fates._result import Result
|
|
13
|
+
from fates._types import AsyncBinder, AsyncMapper, Binder, Mapper
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AsyncResult(Generic[T_co, E_co]):
|
|
17
|
+
def __init__(self, awaitable: Awaitable[Result[T_co, E_co]]) -> None:
|
|
18
|
+
self._awaitable = awaitable
|
|
19
|
+
self._cached_task: Task[Result[T_co, E_co]] | None = None
|
|
20
|
+
|
|
21
|
+
def __await__(self) -> Generator[object, None, Result[T_co, E_co]]:
|
|
22
|
+
if self._cached_task is None:
|
|
23
|
+
self._cached_task = ensure_future(self._awaitable)
|
|
24
|
+
return self._cached_task.__await__()
|
|
25
|
+
|
|
26
|
+
async def unwrap(self) -> T_co:
|
|
27
|
+
res = await self
|
|
28
|
+
return res.unwrap()
|
|
29
|
+
|
|
30
|
+
async def unwrap_or(self, default: DefaultT) -> T_co | DefaultT:
|
|
31
|
+
res = await self
|
|
32
|
+
return res.unwrap_or(default)
|
|
33
|
+
|
|
34
|
+
async def unwrap_err(self) -> E_co:
|
|
35
|
+
res = await self
|
|
36
|
+
return res.unwrap_err()
|
|
37
|
+
|
|
38
|
+
async def expect(self, note: str) -> T_co:
|
|
39
|
+
res = await self
|
|
40
|
+
return res.expect(note)
|
|
41
|
+
|
|
42
|
+
def map(self, mapper: Mapper[T_co, NewT]) -> AsyncResult[NewT, E_co]:
|
|
43
|
+
return AsyncResult(self._map(mapper))
|
|
44
|
+
|
|
45
|
+
def map_err(self, mapper: Mapper[E_co, NewE]) -> AsyncResult[T_co, NewE]:
|
|
46
|
+
return AsyncResult(self._map_err(mapper))
|
|
47
|
+
|
|
48
|
+
def bind(
|
|
49
|
+
self,
|
|
50
|
+
binder: Binder[T_co, NewT, NewE],
|
|
51
|
+
) -> AsyncResult[NewT, E_co | NewE]:
|
|
52
|
+
return AsyncResult(self._bind(binder))
|
|
53
|
+
|
|
54
|
+
def catch(
|
|
55
|
+
self, binder: Binder[E_co, NewT, NewE]
|
|
56
|
+
) -> AsyncResult[NewT, NewE] | AsyncResult[T_co, E_co]:
|
|
57
|
+
return cast(
|
|
58
|
+
"AsyncResult[T_co, E_co] | AsyncResult[NewT, NewE]",
|
|
59
|
+
AsyncResult(self._catch(binder)),
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
async def resolve(self, mapper: Mapper[E_co, NewT]) -> T_co | NewT:
|
|
63
|
+
res = await self
|
|
64
|
+
return res.resolve(mapper)
|
|
65
|
+
|
|
66
|
+
def amap(self, mapper: AsyncMapper[T_co, NewT]) -> AsyncResult[NewT, E_co]:
|
|
67
|
+
return AsyncResult(self._amap(mapper))
|
|
68
|
+
|
|
69
|
+
def amap_err(self, mapper: AsyncMapper[E_co, NewE]) -> AsyncResult[T_co, NewE]:
|
|
70
|
+
return AsyncResult(self._amap_err(mapper))
|
|
71
|
+
|
|
72
|
+
def abind(
|
|
73
|
+
self, binder: AsyncBinder[T_co, NewT, NewE]
|
|
74
|
+
) -> AsyncResult[NewT, E_co | NewE]:
|
|
75
|
+
return AsyncResult(self._abind(binder))
|
|
76
|
+
|
|
77
|
+
def acatch(
|
|
78
|
+
self, binder: AsyncBinder[E_co, NewT, NewE]
|
|
79
|
+
) -> AsyncResult[T_co, E_co] | AsyncResult[NewT, NewE]:
|
|
80
|
+
return cast(
|
|
81
|
+
"AsyncResult[T_co, E_co] | AsyncResult[NewT, NewE]",
|
|
82
|
+
AsyncResult(self._acatch(binder)),
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
async def aresolve(self, mapper: AsyncMapper[E_co, NewT]) -> T_co | NewT:
|
|
86
|
+
res = await self
|
|
87
|
+
return await res.aresolve(mapper)
|
|
88
|
+
|
|
89
|
+
async def _catch(
|
|
90
|
+
self, binder: Binder[E_co, NewT, NewE]
|
|
91
|
+
) -> Result[T_co, E_co] | Result[NewT, NewE]:
|
|
92
|
+
res = await self
|
|
93
|
+
return res.catch(binder)
|
|
94
|
+
|
|
95
|
+
async def _acatch(
|
|
96
|
+
self, binder: AsyncBinder[E_co, NewT, NewE]
|
|
97
|
+
) -> Result[T_co, E_co] | Result[NewT, NewE]:
|
|
98
|
+
res = await self
|
|
99
|
+
return await res.acatch(binder) # ty: ignore[invalid-return-type]
|
|
100
|
+
|
|
101
|
+
async def _map(self, mapper: Mapper[T_co, NewT]) -> Result[NewT, E_co]:
|
|
102
|
+
res = await self
|
|
103
|
+
return res.map(mapper)
|
|
104
|
+
|
|
105
|
+
async def _map_err(self, mapper: Mapper[E_co, NewE]) -> Result[T_co, NewE]:
|
|
106
|
+
res = await self
|
|
107
|
+
return res.map_err(mapper)
|
|
108
|
+
|
|
109
|
+
async def _bind(
|
|
110
|
+
self, binder: Binder[T_co, NewT, NewE]
|
|
111
|
+
) -> Result[NewT, E_co | NewE]:
|
|
112
|
+
res = await self
|
|
113
|
+
return res.bind(binder)
|
|
114
|
+
|
|
115
|
+
async def _amap(self, mapper: AsyncMapper[T_co, NewT]) -> Result[NewT, E_co]:
|
|
116
|
+
res = await self
|
|
117
|
+
return await res.amap(mapper)
|
|
118
|
+
|
|
119
|
+
async def _amap_err(self, mapper: AsyncMapper[E_co, NewE]) -> Result[T_co, NewE]:
|
|
120
|
+
res = await self
|
|
121
|
+
return await res.amap_err(mapper)
|
|
122
|
+
|
|
123
|
+
async def _abind(
|
|
124
|
+
self, binder: AsyncBinder[T_co, NewT, NewE]
|
|
125
|
+
) -> Result[NewT, E_co | NewE]:
|
|
126
|
+
res = await self
|
|
127
|
+
return await res.abind(binder)
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from typing import TYPE_CHECKING, Awaitable, cast, final
|
|
5
|
+
|
|
6
|
+
if sys.version_info >= (3, 12):
|
|
7
|
+
from typing import Never, Self, override # pragma: no cover
|
|
8
|
+
else:
|
|
9
|
+
from typing_extensions import Never, Self, override # pragma: no cover
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
from fates._async import AsyncResult
|
|
13
|
+
from fates._result import Result
|
|
14
|
+
from fates._typevars import DefaultT, E_co, NewE, NewT
|
|
15
|
+
from fates.exceptions import UnwrapError
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from collections.abc import Generator
|
|
19
|
+
|
|
20
|
+
from fates._types import AsyncBinder, AsyncMapper, Binder, Mapper
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@final
|
|
24
|
+
class Err(Result[Never, E_co]):
|
|
25
|
+
__slots__ = ("_boxed_err",)
|
|
26
|
+
__match_args__ = ("_boxed_err",)
|
|
27
|
+
|
|
28
|
+
def __init__(self, boxed_err: E_co) -> None:
|
|
29
|
+
self._boxed_err = boxed_err
|
|
30
|
+
|
|
31
|
+
def __repr__(self) -> str:
|
|
32
|
+
return f"Err({self._boxed_err!r})"
|
|
33
|
+
|
|
34
|
+
def __hash__(self) -> int:
|
|
35
|
+
return self._boxed_err.__hash__() # pragma: no cover
|
|
36
|
+
|
|
37
|
+
def __eq__(self, other: object) -> bool:
|
|
38
|
+
if isinstance(other, Err):
|
|
39
|
+
other = cast("Err[object]", other) # ty: ignore[redundant-cast]
|
|
40
|
+
return self._boxed_err == other._boxed_err
|
|
41
|
+
return False
|
|
42
|
+
|
|
43
|
+
@override
|
|
44
|
+
def unwrap(self) -> Never:
|
|
45
|
+
err = UnwrapError(repr(self._boxed_err))
|
|
46
|
+
if isinstance(self._boxed_err, BaseException):
|
|
47
|
+
raise err from self._boxed_err
|
|
48
|
+
raise err
|
|
49
|
+
|
|
50
|
+
@override
|
|
51
|
+
def unwrap_or(self, default: DefaultT) -> DefaultT:
|
|
52
|
+
return default
|
|
53
|
+
|
|
54
|
+
@override
|
|
55
|
+
def unwrap_err(self) -> E_co:
|
|
56
|
+
return self._boxed_err
|
|
57
|
+
|
|
58
|
+
@override
|
|
59
|
+
def expect(self, note: str) -> Never:
|
|
60
|
+
err_msg = f"{note}\nError details: {self._boxed_err!r}"
|
|
61
|
+
err = UnwrapError(err_msg)
|
|
62
|
+
if isinstance(self._boxed_err, BaseException):
|
|
63
|
+
raise err from self._boxed_err
|
|
64
|
+
raise err
|
|
65
|
+
|
|
66
|
+
@override
|
|
67
|
+
def map(self, mapper: Mapper[Never, object]) -> Self:
|
|
68
|
+
return self
|
|
69
|
+
|
|
70
|
+
@override
|
|
71
|
+
def map_err(self, mapper: Mapper[E_co, NewE]) -> Err[NewE]:
|
|
72
|
+
return Err(mapper(self._boxed_err))
|
|
73
|
+
|
|
74
|
+
@override
|
|
75
|
+
def bind(
|
|
76
|
+
self,
|
|
77
|
+
binder: Binder[Never, object, object],
|
|
78
|
+
) -> Self:
|
|
79
|
+
return self
|
|
80
|
+
|
|
81
|
+
@override
|
|
82
|
+
def catch(self, binder: Binder[E_co, NewT, NewE]) -> Result[NewT, NewE]:
|
|
83
|
+
return binder(self._boxed_err)
|
|
84
|
+
|
|
85
|
+
@override
|
|
86
|
+
def resolve(self, mapper: Mapper[E_co, NewT]) -> NewT:
|
|
87
|
+
return mapper(self._boxed_err)
|
|
88
|
+
|
|
89
|
+
@override
|
|
90
|
+
def amap(self, mapper: AsyncMapper[Never, object]) -> AsyncErr[E_co]:
|
|
91
|
+
return AsyncErr(self._return_self())
|
|
92
|
+
|
|
93
|
+
@override
|
|
94
|
+
def amap_err(self, mapper: AsyncMapper[E_co, NewE]) -> AsyncErr[NewE]:
|
|
95
|
+
return AsyncErr(self._async_map_err(mapper))
|
|
96
|
+
|
|
97
|
+
@override
|
|
98
|
+
def abind(self, binder: AsyncBinder[Never, object, object]) -> AsyncErr[E_co]:
|
|
99
|
+
return AsyncErr(self._return_self())
|
|
100
|
+
|
|
101
|
+
@override
|
|
102
|
+
def acatch(self, binder: AsyncBinder[E_co, NewT, NewE]) -> AsyncResult[NewT, NewE]:
|
|
103
|
+
return AsyncResult(self._acatch(binder))
|
|
104
|
+
|
|
105
|
+
@override
|
|
106
|
+
async def aresolve(self, mapper: AsyncMapper[E_co, NewT]) -> NewT:
|
|
107
|
+
return await mapper(self._boxed_err)
|
|
108
|
+
|
|
109
|
+
async def _return_self(self) -> Self:
|
|
110
|
+
return self
|
|
111
|
+
|
|
112
|
+
async def _acatch(
|
|
113
|
+
self, binder: AsyncBinder[E_co, NewT, NewE]
|
|
114
|
+
) -> Result[NewT, NewE]:
|
|
115
|
+
return await binder(self._boxed_err)
|
|
116
|
+
|
|
117
|
+
async def _async_map_err(self, mapper: AsyncMapper[E_co, NewE]) -> Err[NewE]:
|
|
118
|
+
return Err(await mapper(self._boxed_err))
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@final
|
|
122
|
+
class AsyncErr(AsyncResult[Never, E_co]):
|
|
123
|
+
@override
|
|
124
|
+
def __init__(self, awaitable: Awaitable[Err[E_co]]) -> None:
|
|
125
|
+
super().__init__(awaitable)
|
|
126
|
+
|
|
127
|
+
@override
|
|
128
|
+
def __await__(self) -> Generator[object, None, Err[E_co]]:
|
|
129
|
+
return cast("Generator[object, None, Err[E_co]]", super().__await__())
|
|
130
|
+
|
|
131
|
+
@override
|
|
132
|
+
def map(self, mapper: Mapper[Never, object]) -> Self:
|
|
133
|
+
return self
|
|
134
|
+
|
|
135
|
+
@override
|
|
136
|
+
def amap(self, mapper: AsyncMapper[Never, object]) -> Self:
|
|
137
|
+
return self
|
|
138
|
+
|
|
139
|
+
@override
|
|
140
|
+
def bind(self, binder: Binder[Never, object, object]) -> Self:
|
|
141
|
+
return self
|
|
142
|
+
|
|
143
|
+
@override
|
|
144
|
+
def abind(self, binder: AsyncBinder[Never, object, object]) -> Self:
|
|
145
|
+
return self
|
|
146
|
+
|
|
147
|
+
@override
|
|
148
|
+
def map_err(self, mapper: Mapper[E_co, NewE]) -> AsyncErr[NewE]:
|
|
149
|
+
return AsyncErr(self._map_err(mapper))
|
|
150
|
+
|
|
151
|
+
@override
|
|
152
|
+
def amap_err(self, mapper: AsyncMapper[E_co, NewE]) -> AsyncErr[NewE]:
|
|
153
|
+
return AsyncErr(self._amap_err(mapper))
|
|
154
|
+
|
|
155
|
+
@override
|
|
156
|
+
def catch(self, binder: Binder[E_co, NewT, NewE]) -> AsyncResult[NewT, NewE]:
|
|
157
|
+
return AsyncResult(self._catch(binder))
|
|
158
|
+
|
|
159
|
+
@override
|
|
160
|
+
def acatch(self, binder: AsyncBinder[E_co, NewT, NewE]) -> AsyncResult[NewT, NewE]:
|
|
161
|
+
return AsyncResult(self._acatch(binder))
|
|
162
|
+
|
|
163
|
+
@override
|
|
164
|
+
async def _map_err(self, mapper: Mapper[E_co, NewE]) -> Err[NewE]:
|
|
165
|
+
res = await self
|
|
166
|
+
return res.map_err(mapper)
|
|
167
|
+
|
|
168
|
+
@override
|
|
169
|
+
async def _amap_err(self, mapper: AsyncMapper[E_co, NewE]) -> Err[NewE]:
|
|
170
|
+
res = await self
|
|
171
|
+
return await res.amap_err(mapper)
|
|
172
|
+
|
|
173
|
+
@override
|
|
174
|
+
async def _catch(self, binder: Binder[E_co, NewT, NewE]) -> Result[NewT, NewE]:
|
|
175
|
+
res = await self
|
|
176
|
+
return res.catch(binder)
|
|
177
|
+
|
|
178
|
+
@override
|
|
179
|
+
async def _acatch(
|
|
180
|
+
self, binder: AsyncBinder[E_co, NewT, NewE]
|
|
181
|
+
) -> Result[NewT, NewE]:
|
|
182
|
+
res = await self
|
|
183
|
+
return await res.acatch(binder)
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from typing import TYPE_CHECKING, Awaitable, Generator, cast, final
|
|
5
|
+
|
|
6
|
+
if sys.version_info >= (3, 12):
|
|
7
|
+
from typing import Never, Self, override # pragma: no cover
|
|
8
|
+
else:
|
|
9
|
+
from typing_extensions import Never, Self, override # pragma: no cover
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
from fates._async import AsyncResult
|
|
13
|
+
from fates._result import Result
|
|
14
|
+
from fates._typevars import NewE, NewT, T_co
|
|
15
|
+
from fates.exceptions import UnwrapError
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from fates._types import AsyncBinder, AsyncMapper, Binder, Mapper
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@final
|
|
22
|
+
class Ok(Result[T_co, Never]):
|
|
23
|
+
__slots__ = ("_boxed_val",)
|
|
24
|
+
__match_args__ = ("_boxed_val",)
|
|
25
|
+
|
|
26
|
+
def __init__(self, boxed_val: T_co) -> None:
|
|
27
|
+
self._boxed_val = boxed_val
|
|
28
|
+
|
|
29
|
+
def __repr__(self) -> str:
|
|
30
|
+
return f"Ok({self._boxed_val!r})"
|
|
31
|
+
|
|
32
|
+
def __eq__(self, other: object) -> bool:
|
|
33
|
+
if isinstance(other, Ok):
|
|
34
|
+
other = cast("Ok[object]", other) # ty: ignore[redundant-cast]
|
|
35
|
+
return self._boxed_val == other._boxed_val
|
|
36
|
+
return False
|
|
37
|
+
|
|
38
|
+
def __hash__(self) -> int:
|
|
39
|
+
return self._boxed_val.__hash__() # pragma: no cover
|
|
40
|
+
|
|
41
|
+
@override
|
|
42
|
+
def unwrap(self) -> T_co:
|
|
43
|
+
return self._boxed_val
|
|
44
|
+
|
|
45
|
+
@override
|
|
46
|
+
def unwrap_or(self, default: object) -> T_co:
|
|
47
|
+
return self._boxed_val
|
|
48
|
+
|
|
49
|
+
@override
|
|
50
|
+
def unwrap_err(self) -> Never:
|
|
51
|
+
msg = f"unwrap_err() called on {self}"
|
|
52
|
+
raise UnwrapError(msg)
|
|
53
|
+
|
|
54
|
+
@override
|
|
55
|
+
def expect(self, note: str) -> T_co:
|
|
56
|
+
return self._boxed_val
|
|
57
|
+
|
|
58
|
+
@override
|
|
59
|
+
def map(self, mapper: Mapper[T_co, NewT]) -> Ok[NewT]:
|
|
60
|
+
return Ok(mapper(self._boxed_val))
|
|
61
|
+
|
|
62
|
+
@override
|
|
63
|
+
def map_err(self, mapper: Mapper[Never, object]) -> Self:
|
|
64
|
+
return self
|
|
65
|
+
|
|
66
|
+
@override
|
|
67
|
+
def bind(self, binder: Binder[T_co, NewT, NewE]) -> Result[NewT, NewE]:
|
|
68
|
+
return binder(self._boxed_val)
|
|
69
|
+
|
|
70
|
+
@override
|
|
71
|
+
def catch(self, binder: Binder[Never, object, object]) -> Self:
|
|
72
|
+
return self
|
|
73
|
+
|
|
74
|
+
@override
|
|
75
|
+
def resolve(self, mapper: Mapper[Never, object]) -> T_co:
|
|
76
|
+
return self._boxed_val
|
|
77
|
+
|
|
78
|
+
@override
|
|
79
|
+
def amap(self, mapper: AsyncMapper[T_co, NewT]) -> AsyncOk[NewT]:
|
|
80
|
+
return AsyncOk(self._async_map(mapper))
|
|
81
|
+
|
|
82
|
+
@override
|
|
83
|
+
def amap_err(self, mapper: AsyncMapper[Never, object]) -> AsyncOk[T_co]:
|
|
84
|
+
return AsyncOk(self._return_self())
|
|
85
|
+
|
|
86
|
+
@override
|
|
87
|
+
def abind(self, binder: AsyncBinder[T_co, NewT, NewE]) -> AsyncResult[NewT, NewE]:
|
|
88
|
+
return AsyncResult(self._async_bind(binder))
|
|
89
|
+
|
|
90
|
+
@override
|
|
91
|
+
def acatch(self, binder: AsyncBinder[Never, object, object]) -> AsyncOk[T_co]:
|
|
92
|
+
return AsyncOk(self._return_self())
|
|
93
|
+
|
|
94
|
+
@override
|
|
95
|
+
async def aresolve(self, mapper: AsyncMapper[Never, object]) -> T_co:
|
|
96
|
+
return self._boxed_val
|
|
97
|
+
|
|
98
|
+
async def _async_map(self, mapper: AsyncMapper[T_co, NewT]) -> Ok[NewT]:
|
|
99
|
+
return Ok(await mapper(self._boxed_val))
|
|
100
|
+
|
|
101
|
+
async def _async_bind(
|
|
102
|
+
self, binder: AsyncBinder[T_co, NewT, NewE]
|
|
103
|
+
) -> Result[NewT, NewE]:
|
|
104
|
+
return await binder(self._boxed_val)
|
|
105
|
+
|
|
106
|
+
async def _return_self(self) -> Self:
|
|
107
|
+
return self
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@final
|
|
111
|
+
class AsyncOk(AsyncResult[T_co, Never]):
|
|
112
|
+
@override
|
|
113
|
+
def __init__(self, awaitable: Awaitable[Ok[T_co]]) -> None:
|
|
114
|
+
super().__init__(awaitable)
|
|
115
|
+
|
|
116
|
+
@override
|
|
117
|
+
def __await__(self) -> Generator[object, None, Ok[T_co]]:
|
|
118
|
+
return cast("Generator[object, None, Ok[T_co]]", super().__await__())
|
|
119
|
+
|
|
120
|
+
@override
|
|
121
|
+
async def unwrap_or(self, default: object) -> T_co:
|
|
122
|
+
res = await self
|
|
123
|
+
return res.unwrap()
|
|
124
|
+
|
|
125
|
+
@override
|
|
126
|
+
async def expect(self, note: str) -> T_co:
|
|
127
|
+
res = await self
|
|
128
|
+
return res.unwrap()
|
|
129
|
+
|
|
130
|
+
@override
|
|
131
|
+
async def resolve(self, mapper: Mapper[Never, object]) -> T_co:
|
|
132
|
+
res = await self
|
|
133
|
+
return res.unwrap()
|
|
134
|
+
|
|
135
|
+
@override
|
|
136
|
+
async def aresolve(self, mapper: AsyncMapper[Never, object]) -> T_co:
|
|
137
|
+
res = await self
|
|
138
|
+
return res.unwrap()
|
|
139
|
+
|
|
140
|
+
@override
|
|
141
|
+
def map(self, mapper: Mapper[T_co, NewT]) -> AsyncOk[NewT]:
|
|
142
|
+
return AsyncOk(self._map(mapper))
|
|
143
|
+
|
|
144
|
+
@override
|
|
145
|
+
def amap(self, mapper: AsyncMapper[T_co, NewT]) -> AsyncOk[NewT]:
|
|
146
|
+
return AsyncOk(self._amap(mapper))
|
|
147
|
+
|
|
148
|
+
@override
|
|
149
|
+
def map_err(self, mapper: Mapper[Never, object]) -> Self:
|
|
150
|
+
return self
|
|
151
|
+
|
|
152
|
+
@override
|
|
153
|
+
def catch(self, binder: Binder[Never, object, object]) -> Self:
|
|
154
|
+
return self
|
|
155
|
+
|
|
156
|
+
@override
|
|
157
|
+
def acatch(self, binder: AsyncBinder[Never, object, object]) -> Self:
|
|
158
|
+
return self
|
|
159
|
+
|
|
160
|
+
@override
|
|
161
|
+
def amap_err(self, mapper: AsyncMapper[Never, object]) -> Self:
|
|
162
|
+
return self
|
|
163
|
+
|
|
164
|
+
@override
|
|
165
|
+
async def _map(self, mapper: Mapper[T_co, NewT]) -> Ok[NewT]:
|
|
166
|
+
res = await self
|
|
167
|
+
return res.map(mapper)
|
|
168
|
+
|
|
169
|
+
@override
|
|
170
|
+
async def _amap(self, mapper: AsyncMapper[T_co, NewT]) -> Ok[NewT]:
|
|
171
|
+
res = await self
|
|
172
|
+
return await res.amap(mapper)
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from typing import TYPE_CHECKING, Generic, Protocol
|
|
5
|
+
|
|
6
|
+
from fates._typevars import DefaultT, E_co, NewE, NewT, T_co
|
|
7
|
+
|
|
8
|
+
if sys.version_info >= (3, 11):
|
|
9
|
+
from typing import Self # pragma: no cover
|
|
10
|
+
else:
|
|
11
|
+
from typing_extensions import Self # pragma: no cover
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from fates._async import AsyncResult
|
|
15
|
+
from fates._types import AsyncBinder, AsyncMapper, Binder, Mapper
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Result(Protocol, Generic[T_co, E_co]):
|
|
19
|
+
def unwrap(self) -> T_co:
|
|
20
|
+
"""Extracts the success value or raises an exception if it is a failure.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
T_co: The contained success value.
|
|
24
|
+
|
|
25
|
+
Raises:
|
|
26
|
+
UnwrapError: If the result is an Err.
|
|
27
|
+
|
|
28
|
+
Examples:
|
|
29
|
+
>>> Ok(42).unwrap()
|
|
30
|
+
42
|
|
31
|
+
>>> Err("error").unwrap()
|
|
32
|
+
Traceback (most recent call last):
|
|
33
|
+
fates.exceptions.UnwrapError: 'error'
|
|
34
|
+
"""
|
|
35
|
+
...
|
|
36
|
+
|
|
37
|
+
def unwrap_or(self, default: DefaultT) -> T_co | DefaultT:
|
|
38
|
+
"""Returns the success value or a default value if it is a failure.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
default (DefaultT): The fallback value to return.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
T_co | DefaultT: The contained value or the default value.
|
|
45
|
+
|
|
46
|
+
Examples:
|
|
47
|
+
>>> Ok(42).unwrap_or(0)
|
|
48
|
+
42
|
|
49
|
+
>>> Err("error").unwrap_or(0)
|
|
50
|
+
0
|
|
51
|
+
"""
|
|
52
|
+
...
|
|
53
|
+
|
|
54
|
+
def unwrap_err(self) -> E_co:
|
|
55
|
+
"""Extracts the error value or raises an exception if it is a success.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
E_co: The contained error value.
|
|
59
|
+
|
|
60
|
+
Raises:
|
|
61
|
+
UnwrapError: If the result is an Ok.
|
|
62
|
+
|
|
63
|
+
Examples:
|
|
64
|
+
>>> Err("danger").unwrap_err()
|
|
65
|
+
'danger'
|
|
66
|
+
>>> Ok(42).unwrap_err()
|
|
67
|
+
Traceback (most recent call last):
|
|
68
|
+
fates.exceptions.UnwrapError: unwrap_err() called on Ok(42)
|
|
69
|
+
"""
|
|
70
|
+
...
|
|
71
|
+
|
|
72
|
+
def expect(self, note: str) -> T_co:
|
|
73
|
+
"""Extracts the value or raises an exception with a custom message.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
note (str): The custom error message to include in the exception.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
T_co: The contained success value.
|
|
80
|
+
|
|
81
|
+
Raises:
|
|
82
|
+
UnwrapError: If the result is an Err, containing the note.
|
|
83
|
+
|
|
84
|
+
Examples:
|
|
85
|
+
>>> Ok(42).expect("Should be a number")
|
|
86
|
+
42
|
|
87
|
+
>>> Err("db_error").expect("Database connection failed")
|
|
88
|
+
Traceback (most recent call last):
|
|
89
|
+
fates.exceptions.UnwrapError: Database connection failed
|
|
90
|
+
Error details: 'db_error'
|
|
91
|
+
"""
|
|
92
|
+
...
|
|
93
|
+
|
|
94
|
+
def map(
|
|
95
|
+
self,
|
|
96
|
+
mapper: Mapper[T_co, NewT],
|
|
97
|
+
) -> Result[NewT, E_co]:
|
|
98
|
+
"""Applies a function to the success value, leaving an error untouched.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
mapper (Mapper[T_co, NewT]): A function to transform the success value.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
Result[NewT, E_co]: A new Result with
|
|
105
|
+
the transformed value or original error.
|
|
106
|
+
|
|
107
|
+
Examples:
|
|
108
|
+
>>> Ok(2).map(lambda x: x * 2)
|
|
109
|
+
Ok(4)
|
|
110
|
+
>>> Err("error").map(lambda x: x * 2)
|
|
111
|
+
Err('error')
|
|
112
|
+
"""
|
|
113
|
+
...
|
|
114
|
+
|
|
115
|
+
def map_err(
|
|
116
|
+
self,
|
|
117
|
+
mapper: Mapper[E_co, NewE],
|
|
118
|
+
) -> Result[T_co, NewE]:
|
|
119
|
+
"""Applies a function to the error value, leaving a success untouched.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
mapper (Mapper[E_co, NewE]): A function to transform the error value.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
Result[T_co, NewE]: A new Result with the
|
|
126
|
+
transformed error or original value.
|
|
127
|
+
|
|
128
|
+
Examples:
|
|
129
|
+
>>> Err("failed").map_err(lambda e: f"Log: {e}")
|
|
130
|
+
Err('Log: failed')
|
|
131
|
+
>>> Ok(42).map_err(lambda e: f"Log: {e}")
|
|
132
|
+
Ok(42)
|
|
133
|
+
"""
|
|
134
|
+
...
|
|
135
|
+
|
|
136
|
+
def bind(
|
|
137
|
+
self,
|
|
138
|
+
binder: Binder[T_co, NewT, NewE],
|
|
139
|
+
) -> Result[NewT, E_co | NewE]:
|
|
140
|
+
"""Monadic bind. Transforms the success value into a new Result.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
binder (Binder[T_co, NewT, NewE]): A function that takes the success
|
|
144
|
+
value and returns a new Result.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
Result[NewT, E_co | NewE]: The new Result, or the original Err.
|
|
148
|
+
|
|
149
|
+
Examples:
|
|
150
|
+
>>> def check_positive(x: int) -> Result[int, str]:
|
|
151
|
+
... return Ok(x) if x > 0 else Err("negative")
|
|
152
|
+
>>> Ok(5).bind(check_positive)
|
|
153
|
+
Ok(5)
|
|
154
|
+
>>> Ok(-1).bind(check_positive)
|
|
155
|
+
Err('negative')
|
|
156
|
+
>>> Err("not_a_number").bind(check_positive)
|
|
157
|
+
Err('not_a_number')
|
|
158
|
+
"""
|
|
159
|
+
...
|
|
160
|
+
|
|
161
|
+
def catch(
|
|
162
|
+
self,
|
|
163
|
+
binder: Binder[E_co, NewT, NewE],
|
|
164
|
+
) -> Self | Result[NewT, NewE]:
|
|
165
|
+
"""Handles an error by transforming it into a new Result.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
binder (Binder[E_co, NewT, NewE]): A function that takes the error
|
|
169
|
+
value and returns a alternative Result.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
Self | Result[NewT, NewE]: The original Ok,
|
|
173
|
+
or the new Result from the binder.
|
|
174
|
+
|
|
175
|
+
Examples:
|
|
176
|
+
>>> def recover(e: str) -> Result[int, str]:
|
|
177
|
+
... return Ok(0) if e == "recoverable" else Err("fatal")
|
|
178
|
+
>>> Ok(42).catch(recover)
|
|
179
|
+
Ok(42)
|
|
180
|
+
>>> Err("recoverable").catch(recover)
|
|
181
|
+
Ok(0)
|
|
182
|
+
>>> Err("boom").catch(recover)
|
|
183
|
+
Err('fatal')
|
|
184
|
+
"""
|
|
185
|
+
...
|
|
186
|
+
|
|
187
|
+
def resolve(self, mapper: Mapper[E_co, NewT]) -> T_co | NewT:
|
|
188
|
+
"""Returns the success value or transforms the error into a success type.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
mapper (Mapper[E_co, NewT]): A function to transform the error value.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
T_co | NewT: The original success value or the transformed error value.
|
|
195
|
+
|
|
196
|
+
Examples:
|
|
197
|
+
>>> Ok(100).resolve(lambda e: 0)
|
|
198
|
+
100
|
|
199
|
+
>>> Err("missing").resolve(lambda e: 0)
|
|
200
|
+
0
|
|
201
|
+
"""
|
|
202
|
+
...
|
|
203
|
+
|
|
204
|
+
def amap(
|
|
205
|
+
self,
|
|
206
|
+
mapper: AsyncMapper[T_co, NewT],
|
|
207
|
+
) -> AsyncResult[NewT, E_co]:
|
|
208
|
+
"""Asynchronously maps the success value using an async function.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
mapper (AsyncMapper[T_co, NewT]): An async function to transform the value.
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
AsyncResult[NewT, E_co]: An async result wrapper.
|
|
215
|
+
|
|
216
|
+
Examples:
|
|
217
|
+
>>> async def async_double(x: int) -> int:
|
|
218
|
+
... return x * 2
|
|
219
|
+
>>> await Ok(2).amap(async_double)
|
|
220
|
+
Ok(4)
|
|
221
|
+
>>> await Err("error").amap(async_double)
|
|
222
|
+
Err('error')
|
|
223
|
+
"""
|
|
224
|
+
...
|
|
225
|
+
|
|
226
|
+
def amap_err(
|
|
227
|
+
self,
|
|
228
|
+
mapper: AsyncMapper[E_co, NewE],
|
|
229
|
+
) -> AsyncResult[T_co, NewE]:
|
|
230
|
+
"""Asynchronously maps the error value using an async function.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
mapper (AsyncMapper[E_co, NewE]): An async function to transform the error.
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
AsyncResult[T_co, NewE]: An async result wrapper.
|
|
237
|
+
|
|
238
|
+
Examples:
|
|
239
|
+
>>> async def async_log(e: str) -> str:
|
|
240
|
+
... return f"logged_{e}"
|
|
241
|
+
>>> await Err("fail").amap_err(async_log)
|
|
242
|
+
Err('logged_fail')
|
|
243
|
+
>>> await Ok(42).amap_err(async_log)
|
|
244
|
+
Ok(42)
|
|
245
|
+
"""
|
|
246
|
+
...
|
|
247
|
+
|
|
248
|
+
def abind(
|
|
249
|
+
self, binder: AsyncBinder[T_co, NewT, NewE]
|
|
250
|
+
) -> AsyncResult[NewT, E_co | NewE]:
|
|
251
|
+
"""
|
|
252
|
+
Asynchronously binds the success value to an async
|
|
253
|
+
function returning a Result.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
binder (AsyncBinder[T_co, NewT, NewE]): An async
|
|
257
|
+
function returning a Result.
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
AsyncResult[NewT, E_co | NewE]: An async result wrapper.
|
|
261
|
+
|
|
262
|
+
Examples:
|
|
263
|
+
>>> async def async_check(x: int) -> Result[int, str]:
|
|
264
|
+
... return Ok(x) if x > 0 else Err("negative")
|
|
265
|
+
>>> await Ok(5).abind(async_check)
|
|
266
|
+
Ok(5)
|
|
267
|
+
>>> await Err("error").abind(async_check)
|
|
268
|
+
Err('error')
|
|
269
|
+
"""
|
|
270
|
+
...
|
|
271
|
+
|
|
272
|
+
def acatch(
|
|
273
|
+
self,
|
|
274
|
+
binder: AsyncBinder[E_co, NewT, NewE],
|
|
275
|
+
) -> AsyncResult[T_co, E_co] | AsyncResult[NewT, NewE]: ...
|
|
276
|
+
|
|
277
|
+
async def aresolve(self, mapper: AsyncMapper[E_co, NewT]) -> T_co | NewT: ...
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# pragma: exclude file
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from collections.abc import Awaitable, Callable
|
|
5
|
+
|
|
6
|
+
if sys.version_info >= (3, 10):
|
|
7
|
+
from typing import TypeAlias
|
|
8
|
+
else:
|
|
9
|
+
from typing_extensions import TypeAlias
|
|
10
|
+
|
|
11
|
+
from fates._result import Result
|
|
12
|
+
from fates._typevars import New, NewE, NewT, Old, T_co
|
|
13
|
+
|
|
14
|
+
Mapper: TypeAlias = Callable[[Old], New]
|
|
15
|
+
|
|
16
|
+
AsyncMapper: TypeAlias = Callable[[Old], Awaitable[New]]
|
|
17
|
+
|
|
18
|
+
Binder: TypeAlias = Callable[[T_co], Result[NewT, NewE]]
|
|
19
|
+
|
|
20
|
+
AsyncBinder: TypeAlias = Callable[[T_co], Awaitable[Result[NewT, NewE]]]
|
|
File without changes
|