ITADN

can't compose @future_safe coroutines that specify picky exceptions

#2080Openzed 创建于 2025-03-23
Z
zedcommented
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.
3 条评论