diff --git a/tests/test_result.py b/tests/test_result.py index 13e934f..45f4ce3 100644 --- a/tests/test_result.py +++ b/tests/test_result.py @@ -1,10 +1,10 @@ -from typing import Any, Callable +from typing import Any, Callable, Iterable import pytest from tests.utils import add, add_async, is_even, is_even_async, set_async from tibia import mapping -from tibia.result import Err, Ok, Result, match_ok, safe, safe_from, wraps +from tibia.result import Err, Ok, Result, match_ok, safe, safe_from, safe_iterate, wraps @pytest.mark.parametrize( @@ -455,3 +455,18 @@ def test_wraps_new(): assert isinstance(result, Ok) assert result == 2 + + +def test_safe_iterate(): + @safe_iterate + def gen() -> Iterable[int]: + yield 1 + raise Exception() + yield 2 + + items = list(gen()) + + assert len(items) == 2 + assert isinstance(items[0], Ok) + assert items[0].unwrap() == 1 + assert isinstance(items[1], Err) diff --git a/tibia/result.py b/tibia/result.py index df9efe5..249e811 100644 --- a/tibia/result.py +++ b/tibia/result.py @@ -3,7 +3,7 @@ import functools import warnings from dataclasses import dataclass -from typing import Any, Awaitable, Callable, Concatenate, cast +from typing import Any, Awaitable, Callable, Concatenate, Iterable, cast from tibia import future_result as fr from tibia.future import Future @@ -202,8 +202,7 @@ def inspect_err_async[**P]( def wraps[**P, R](func: Callable[P, R]) -> Callable[P, Result[R, Exception]]: warnings.simplefilter("always", DeprecationWarning) warnings.warn( - "Result.wraps will be deprecated in tibia@3.0.0, " - "use result.wraps instead", + "Result.wraps will be deprecated in tibia@3.0.0, use result.wraps instead", DeprecationWarning, ) warnings.simplefilter("default", DeprecationWarning) @@ -589,3 +588,20 @@ def _safe(*args: P.args, **kwargs: P.kwargs) -> Result[T, Exception]: return Err(exc) return _safe + + +def safe_iterate[**P, T]( + fn: Callable[P, Iterable[T]], +) -> Callable[P, Iterable[Result[T, Exception]]]: + @functools.wraps(fn) + def _safe_iterate( + *args: P.args, + **kwargs: P.kwargs, + ) -> Iterable[Result[T, Exception]]: + try: + for item in fn(*args, **kwargs): # pragma: no cover + yield Ok(item) + except Exception as exc: + yield Err(exc) + + return _safe_iterate