From ece6638fa6b0edca8f2d3897a65865cf048824e8 Mon Sep 17 00:00:00 2001 From: ed cuss Date: Wed, 8 Apr 2026 20:05:31 +0100 Subject: [PATCH 1/2] fix: revert safe back to wrapper fn --- src/danom/_safe.py | 23 +++++++++-------------- src/danom/_stream.py | 4 ++-- tests/conftest.py | 6 +++++- tests/test_safe.py | 11 ++++++++--- 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/danom/_safe.py b/src/danom/_safe.py index 46bf8d4..92ae598 100644 --- a/src/danom/_safe.py +++ b/src/danom/_safe.py @@ -11,8 +11,8 @@ E = TypeVar("E") -class safe[**P, U]: # noqa: N801 - """Decorator for functions that wraps the function in a try except returns ``Ok`` on success else ``Err``. +def safe[**P, U](func: Callable[P, U]) -> Callable[P, Result[U, Exception]]: + """Decorator for functions that wraps the function in a try except returns `Ok` on success else `Err`. .. code-block:: python @@ -25,17 +25,14 @@ def add_one(a: int) -> int: add_one(1) == Ok(inner=2) """ - def __init__(self, func: Callable[P, U]) -> None: - self.func = func - functools.update_wrapper(self, func) - - def __call__(self, *args: P.args, **kwargs: P.kwargs) -> Result[U, Exception]: + @functools.wraps(func) + def wrapper(*args: P.args, **kwargs: P.kwargs) -> Result[U, Exception]: try: - return Ok(self.func(*args, **kwargs)) + return Ok(func(*args, **kwargs)) except Exception as e: # noqa: BLE001 - return Err( - error=e, input_args=(args, kwargs), traceback=traceback.format_exc() - ) # ty: ignore[invalid-return-type] + return Err(error=e, input_args=(args, kwargs), traceback=traceback.format_exc()) # ty: ignore[invalid-return-type] + + return wrapper def safe_method[T, **P, U]( @@ -63,8 +60,6 @@ def wrapper(self: T, *args: P.args, **kwargs: P.kwargs) -> Result[U, Exception]: try: return Ok(func(self, *args, **kwargs)) except Exception as e: # noqa: BLE001 - return Err( - error=e, input_args=(self, args, kwargs), traceback=traceback.format_exc() - ) # ty: ignore[invalid-return-type] + return Err(error=e, input_args=(self, args, kwargs), traceback=traceback.format_exc()) # ty: ignore[invalid-return-type] return wrapper diff --git a/src/danom/_stream.py b/src/danom/_stream.py index 7ea7c8d..8342f7a 100644 --- a/src/danom/_stream.py +++ b/src/danom/_stream.py @@ -506,7 +506,7 @@ class _Nothing(Enum): AsyncPlannedOps = tuple[str, AsyncStreamFn] -def _apply_fns_worker[T](args: tuple[tuple[T], tuple[PlannedOps, ...]]) -> tuple[T]: +def _apply_fns_worker[T](args: tuple[tuple[T], tuple[PlannedOps, ...]]) -> tuple[T, ...]: seq, ops = args return _par_apply_fns(seq, ops) @@ -535,7 +535,7 @@ def _apply_fns[T](elements: tuple[T], ops: tuple[PlannedOps, ...]) -> tuple[T, . return tuple(pipeline) # ty: ignore[invalid-return-type] -def _par_apply_fns[T](elements: tuple[T], ops: tuple[PlannedOps, ...]) -> tuple[T]: +def _par_apply_fns[T](elements: tuple[T], ops: tuple[PlannedOps, ...]) -> tuple[T, ...]: results = [] for elem in elements: valid = True diff --git a/tests/conftest.py b/tests/conftest.py index e1c82f4..6e55380 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -76,7 +76,7 @@ def lt_10(x: float) -> bool: async def async_is_file(path: Path) -> bool: - return path.is_file() + return path.is_file() # noqa: ASYNC240 async def async_read_text(path: str) -> str: @@ -130,6 +130,10 @@ def add(self, a: int, b: int) -> Self: def cls_raises(self, *_args: tuple, **_kwargs: dict) -> None: raise ValueError + @safe + def safe_add(self, a: int, b: int) -> int: + return a + b + class ValueLogger: def __init__(self, values: list | ListProxy | None = None) -> None: diff --git a/tests/test_safe.py b/tests/test_safe.py index 562802d..fe9bb84 100644 --- a/tests/test_safe.py +++ b/tests/test_safe.py @@ -2,7 +2,7 @@ import pytest -from danom._result import Err +from danom._result import Err, Ok from tests.conftest import ( REPO_ROOT, Adder, @@ -65,8 +65,8 @@ def test_traceback(): expected_lines = [ "Traceback (most recent call last):", - ' File "./src/danom/_safe.py", line 34, in __call__', - " return Ok(self.func(*args, **kwargs))", + ' File "./src/danom/_safe.py", line 31, in wrapper', + " return Ok(func(*args, **kwargs))", ' File "./tests/conftest.py", line 117, in div_zero', " return x / 0", "ZeroDivisionError: division by zero", @@ -80,3 +80,8 @@ def test_traceback(): missing_lines = [line for line in expected_lines if line not in tb_lines] assert missing_lines == [], f"lines don't match {tb_lines = }" + + +def test_safe_on_method(): + cls = Adder() + assert cls.safe_add(2, 2) == Ok(4) From 24e2f295eb685a5b244f9ee14932cacc352281be Mon Sep 17 00:00:00 2001 From: ed cuss Date: Wed, 8 Apr 2026 20:06:30 +0100 Subject: [PATCH 2/2] bump: => 0.12.1 --- pyproject.toml | 2 +- uv.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 73e3006..9cd6c10 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "danom" -version = "0.12.0" +version = "0.12.1" description = "Functional streams and monads" readme = "README.md" license = "MIT" diff --git a/uv.lock b/uv.lock index c71dfcf..1a53c8d 100644 --- a/uv.lock +++ b/uv.lock @@ -343,7 +343,7 @@ wheels = [ [[package]] name = "danom" -version = "0.12.0" +version = "0.12.1" source = { editable = "." } dependencies = [ { name = "attrs" },