dry-python / dry-python/returns

can't compose @future_safe coroutines that specify picky exceptions

Offen
#2,080 3 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen
Vorherrschende Sprache
Python
Sterne
4.4k
Forks
155
Ø Merge
2 Std. 27 Min.
Gemergte PRs (30 T.)
22

Beschreibung

Given coroutines `coro1`, `coro2`:

```python
@future_safe(exceptions=(ConnectionError,))
async def coro1(c: int | None) -> int:
if c is None:
raise ConnectionError("not connected")
await asyncio.sleep(0) # emulate I/O
return c

@future_safe(exceptions=(ZeroDivisionError,))
async def coro2(n: int) -> float:
await asyncio.sleep(0) # emulate I/O
return 1 / n
```

I want to combine them via bind: `coro1(c).bind(coro2)`. It results in type error:

```shell
$ uvx --with 'returns[compatible-mypy]==0.25.0' mypy mre.py; ./mre.py
mre.py:30: error: Argument 1 to "bind" of "FutureResult" has incompatible type "Callable[[int], FutureResult[float, ZeroDivisionError]]"; expected "Callable[[int], KindN[FutureResult[Any, Any], float, ConnectionError, Any]]" [arg-type]
```

where `mre.py`:

```python
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "returns >=0.25.0",
# ]
# ///
from typing import assert_never
from returns.result import Success, Failure
from returns.io import IOSuccess, IOFailure
from returns.future import future_safe

@future_safe(exceptions=(ConnectionError,))
async def coro1(c: int | None) -> int:
if c is None:
raise ConnectionError("not connected")
await asyncio.sleep(0) # emulate I/O
return c

@future_safe(exceptions=(ZeroDivisionError,))
async def coro2(n: int) -> float:
await asyncio.sleep(0) # emulate I/O
return 1 / n

async def run() -> None:
for c in [2, 0, None]:
match await coro1(c).bind(coro2):
case IOSuccess(Success(r)):
assert r == 1 / 2, r
case IOFailure(Failure(ZeroDivisionError(args=(msg,)))):
assert msg == "division by zero", msg
case IOFailure(Failure(ConnectionError(args=(msg,)))):
assert msg == "not connected", msg
case _ as unreachable:
assert_never(unreachable) # type: ignore[arg-type]

if __name__ == "__main__":
import asyncio

asyncio.run(run())
```

If picky exceptions are removed:
```python
@future_safe
async def coro1(c: int | None) -> int: ...

@future_safe
async def coro2(n: int) -> float: ...
```
I get the desired behavior that coroutines are composed without type errors but these declarations catch too much.

Beitragsleitfaden

Beitragsleitfaden öffnen

Bewertung

Dieses Issue wurde noch nicht bewertet.

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.