From 74cab2da65d9c1e6d44dd850aa20d9f3f1d2c592 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 11 Apr 2025 07:25:25 -0500 Subject: [PATCH 1/8] Add notes to result.safe --- returns/result.py | 97 ++++++++++++++++++------- tests/test_result/test_add_note_safe.py | 45 ++++++++++++ 2 files changed, 117 insertions(+), 25 deletions(-) create mode 100644 tests/test_result/test_add_note_safe.py diff --git a/returns/result.py b/returns/result.py index 7ca217d2a..2354d672f 100644 --- a/returns/result.py +++ b/returns/result.py @@ -1,3 +1,4 @@ +import sys from abc import ABC from collections.abc import Callable, Generator, Iterator from functools import wraps @@ -12,18 +13,18 @@ from returns.primitives.hkt import Kind2, SupportsKind2 # Definitions: -_ValueType_co = TypeVar('_ValueType_co', covariant=True) -_NewValueType = TypeVar('_NewValueType') -_ErrorType_co = TypeVar('_ErrorType_co', covariant=True) -_NewErrorType = TypeVar('_NewErrorType') +_ValueType_co = TypeVar("_ValueType_co", covariant=True) +_NewValueType = TypeVar("_NewValueType") +_ErrorType_co = TypeVar("_ErrorType_co", covariant=True) +_NewErrorType = TypeVar("_NewErrorType") -_FirstType = TypeVar('_FirstType') -_FuncParams = ParamSpec('_FuncParams') +_FirstType = TypeVar("_FirstType") +_FuncParams = ParamSpec("_FuncParams") class Result( # type: ignore[type-var] BaseContainer, - SupportsKind2['Result', _ValueType_co, _ErrorType_co], + SupportsKind2["Result", _ValueType_co, _ErrorType_co], result.ResultBased2[_ValueType_co, _ErrorType_co], ABC, ): @@ -39,8 +40,8 @@ class Result( # type: ignore[type-var] """ - __slots__ = ('_trace',) - __match_args__ = ('_inner_value',) + __slots__ = ("_trace",) + __match_args__ = ("_inner_value",) _inner_value: _ValueType_co | _ErrorType_co _trace: list[FrameInfo] | None @@ -53,7 +54,7 @@ def trace(self) -> list[FrameInfo] | None: """Returns a list with stack trace when :func:`~Failure` was called.""" return self._trace - def swap(self) -> 'Result[_ErrorType_co, _ValueType_co]': + def swap(self) -> "Result[_ErrorType_co, _ValueType_co]": """ Swaps value and error types. @@ -73,7 +74,7 @@ def swap(self) -> 'Result[_ErrorType_co, _ValueType_co]': def map( self, function: Callable[[_ValueType_co], _NewValueType], - ) -> 'Result[_NewValueType, _ErrorType_co]': + ) -> "Result[_NewValueType, _ErrorType_co]": """ Composes successful container with a pure function. @@ -92,11 +93,11 @@ def map( def apply( self, container: Kind2[ - 'Result', + "Result", Callable[[_ValueType_co], _NewValueType], _ErrorType_co, ], - ) -> 'Result[_NewValueType, _ErrorType_co]': + ) -> "Result[_NewValueType, _ErrorType_co]": """ Calls a wrapped function in a container on this container. @@ -119,9 +120,9 @@ def bind( self, function: Callable[ [_ValueType_co], - Kind2['Result', _NewValueType, _ErrorType_co], + Kind2["Result", _NewValueType, _ErrorType_co], ], - ) -> 'Result[_NewValueType, _ErrorType_co]': + ) -> "Result[_NewValueType, _ErrorType_co]": """ Composes successful container with a function that returns a container. @@ -146,7 +147,7 @@ def bind( def alt( self, function: Callable[[_ErrorType_co], _NewErrorType], - ) -> 'Result[_ValueType_co, _NewErrorType]': + ) -> "Result[_ValueType_co, _NewErrorType]": """ Composes failed container with a pure function to modify failure. @@ -166,9 +167,9 @@ def lash( self, function: Callable[ [_ErrorType_co], - Kind2['Result', _ValueType_co, _NewErrorType], + Kind2["Result", _ValueType_co, _NewErrorType], ], - ) -> 'Result[_ValueType_co, _NewErrorType]': + ) -> "Result[_ValueType_co, _NewErrorType]": """ Composes failed container with a function that returns a container. @@ -195,7 +196,7 @@ def __iter__(self) -> Iterator[_ValueType_co]: def do( cls, expr: Generator[_NewValueType, None, None], - ) -> 'Result[_NewValueType, _NewErrorType]': + ) -> "Result[_NewValueType, _NewErrorType]": """ Allows working with unwrapped values of containers in a safe way. @@ -277,7 +278,7 @@ def failure(self) -> _ErrorType_co: def from_value( cls, inner_value: _NewValueType, - ) -> 'Result[_NewValueType, Any]': + ) -> "Result[_NewValueType, Any]": """ One more value to create success unit values. @@ -298,7 +299,7 @@ def from_value( def from_failure( cls, inner_value: _NewErrorType, - ) -> 'Result[Any, _NewErrorType]': + ) -> "Result[Any, _NewErrorType]": """ One more value to create failure unit values. @@ -318,8 +319,8 @@ def from_failure( @classmethod def from_result( cls, - inner_value: 'Result[_NewValueType, _NewErrorType]', - ) -> 'Result[_NewValueType, _NewErrorType]': + inner_value: "Result[_NewValueType, _NewErrorType]", + ) -> "Result[_NewValueType, _NewErrorType]": """ Creates a new ``Result`` instance from existing ``Result`` instance. @@ -350,7 +351,7 @@ class Failure(Result[Any, _ErrorType_co]): # noqa: WPS338 def __init__(self, inner_value: _ErrorType_co) -> None: """Failure constructor.""" super().__init__(inner_value) - object.__setattr__(self, '_trace', self._get_trace()) + object.__setattr__(self, "_trace", self._get_trace()) if not TYPE_CHECKING: # noqa: WPS604 # pragma: no branch @@ -467,7 +468,7 @@ def failure(self) -> Never: # Decorators: -_ExceptionType = TypeVar('_ExceptionType', bound=Exception) +_ExceptionType = TypeVar("_ExceptionType", bound=Exception) @overload @@ -480,16 +481,19 @@ def safe( @overload def safe( exceptions: tuple[type[_ExceptionType], ...], + add_note_on_failure: bool | str = False, ) -> Callable[ [Callable[_FuncParams, _ValueType_co]], Callable[_FuncParams, Result[_ValueType_co, _ExceptionType]], ]: ... +# add_note_on_failure is optional for backwards compatibility. def safe( # noqa: WPS234 exceptions: ( Callable[_FuncParams, _ValueType_co] | tuple[type[_ExceptionType], ...] ), + add_note_on_failure: bool | str = False, ) -> ( Callable[_FuncParams, ResultE[_ValueType_co]] | Callable[ @@ -534,6 +538,30 @@ def safe( # noqa: WPS234 In this case, only exceptions that are explicitly listed are going to be caught. + In order to add a note to the exception, you can use the + ``add_note_on_failure`` argument. It can be a string or a boolean value. + Either way, a generic note will be added to the exception that calls out + the file, line number, and function name where the error occured. If a + string is provided, it will be added as an additional note to the + exception. + + This feature can help with logging and debugging. + + Note that if you use this option, you must provide a tuple of exception + types as the first argument. + + .. code:: python + + >>> from returns.result import safe + + >>> @safe((Exception,), add_note_on_failure=True) + ... def error_throwing_function() -> None: + ... raise ValueError("This is an error!") + + >>> @safe((Exception,), add_note_on_failure="A custom message") + ... def error_throwing_function() -> None: + ... raise ValueError("This is an error!") + Similar to :func:`returns.io.impure_safe` and :func:`returns.future.future_safe` decorators. """ @@ -550,6 +578,25 @@ def decorator( try: return Success(inner_function(*args, **kwargs)) except inner_exceptions as exc: + if add_note_on_failure: + # If the user provides a custom message, add it as a note + # to the exception. Otherwise just add a generic note. + if isinstance(add_note_on_failure, str): + exc.add_note(add_note_on_failure) + + # Add the generic note. + exc_traceback = exc.__traceback__ + if exc_traceback is not None: + filename = ( + exc_traceback.tb_next.tb_frame.f_code.co_filename + ) + line_number = exc_traceback.tb_next.tb_lineno + exc.add_note( + f"Exception occurred in {inner_function.__name__} " + f"of {filename} " + f"at line number {line_number}." + ) + return Failure(exc) return decorator diff --git a/tests/test_result/test_add_note_safe.py b/tests/test_result/test_add_note_safe.py new file mode 100644 index 000000000..1a050822d --- /dev/null +++ b/tests/test_result/test_add_note_safe.py @@ -0,0 +1,45 @@ +from returns.result import safe +from returns.pipeline import is_successful + + +@safe((Exception,), add_note_on_failure=True) +def error_throwing_function() -> None: + """Raises an exception.""" + raise ValueError("This is an error!") + + +@safe((Exception,), add_note_on_failure="A custom message") +def error_throwing_function_with_message() -> None: + """Raises an exception.""" + raise ValueError("This is an error!") + + +def test_add_note_safe() -> None: + """Tests the add_note decorator with safe.""" + + result = error_throwing_function() + + print(result) + print(result.failure().__notes__) + print(result.failure()) + assert not is_successful(result) + assert ( + "Exception occurred in error_throwing_function" + in result.failure().__notes__[0] + ) + + +def test_add_note_safe_with_message() -> None: + """Tests the add_note decorator with safe.""" + + result = error_throwing_function_with_message() + + print(result) + print(result.failure().__notes__) + print(result.failure()) + assert not is_successful(result) + assert "A custom message" in result.failure().__notes__ + assert ( + "Exception occurred in error_throwing_function_with_message" + in result.failure().__notes__[1] + ) From aedc2d14fb0d6fca0f69673388d7b3277b5cc9ae Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 11 Apr 2025 07:31:40 -0500 Subject: [PATCH 2/8] add notes to changelog.md --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8c474b29..1bb1813c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ incremental in minor, bugfixes only are patches. See [0Ver](https://0ver.org/). +### Features + + - Add the ability to add debugging notes to the `safe` decorator. + + ## 0.25.0 ### Features From c7aaef957355d619a843e47e340fa61b64593da0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 11 Apr 2025 12:37:27 +0000 Subject: [PATCH 3/8] [pre-commit.ci] auto fixes from pre-commit.com hooks --- returns/result.py | 57 ++++++++++++------------- tests/test_result/test_add_note_safe.py | 16 +++---- 2 files changed, 35 insertions(+), 38 deletions(-) diff --git a/returns/result.py b/returns/result.py index 2354d672f..de847383c 100644 --- a/returns/result.py +++ b/returns/result.py @@ -1,4 +1,3 @@ -import sys from abc import ABC from collections.abc import Callable, Generator, Iterator from functools import wraps @@ -13,18 +12,18 @@ from returns.primitives.hkt import Kind2, SupportsKind2 # Definitions: -_ValueType_co = TypeVar("_ValueType_co", covariant=True) -_NewValueType = TypeVar("_NewValueType") -_ErrorType_co = TypeVar("_ErrorType_co", covariant=True) -_NewErrorType = TypeVar("_NewErrorType") +_ValueType_co = TypeVar('_ValueType_co', covariant=True) +_NewValueType = TypeVar('_NewValueType') +_ErrorType_co = TypeVar('_ErrorType_co', covariant=True) +_NewErrorType = TypeVar('_NewErrorType') -_FirstType = TypeVar("_FirstType") -_FuncParams = ParamSpec("_FuncParams") +_FirstType = TypeVar('_FirstType') +_FuncParams = ParamSpec('_FuncParams') class Result( # type: ignore[type-var] BaseContainer, - SupportsKind2["Result", _ValueType_co, _ErrorType_co], + SupportsKind2['Result', _ValueType_co, _ErrorType_co], result.ResultBased2[_ValueType_co, _ErrorType_co], ABC, ): @@ -40,8 +39,8 @@ class Result( # type: ignore[type-var] """ - __slots__ = ("_trace",) - __match_args__ = ("_inner_value",) + __slots__ = ('_trace',) + __match_args__ = ('_inner_value',) _inner_value: _ValueType_co | _ErrorType_co _trace: list[FrameInfo] | None @@ -54,7 +53,7 @@ def trace(self) -> list[FrameInfo] | None: """Returns a list with stack trace when :func:`~Failure` was called.""" return self._trace - def swap(self) -> "Result[_ErrorType_co, _ValueType_co]": + def swap(self) -> 'Result[_ErrorType_co, _ValueType_co]': """ Swaps value and error types. @@ -74,7 +73,7 @@ def swap(self) -> "Result[_ErrorType_co, _ValueType_co]": def map( self, function: Callable[[_ValueType_co], _NewValueType], - ) -> "Result[_NewValueType, _ErrorType_co]": + ) -> 'Result[_NewValueType, _ErrorType_co]': """ Composes successful container with a pure function. @@ -93,11 +92,11 @@ def map( def apply( self, container: Kind2[ - "Result", + 'Result', Callable[[_ValueType_co], _NewValueType], _ErrorType_co, ], - ) -> "Result[_NewValueType, _ErrorType_co]": + ) -> 'Result[_NewValueType, _ErrorType_co]': """ Calls a wrapped function in a container on this container. @@ -120,9 +119,9 @@ def bind( self, function: Callable[ [_ValueType_co], - Kind2["Result", _NewValueType, _ErrorType_co], + Kind2['Result', _NewValueType, _ErrorType_co], ], - ) -> "Result[_NewValueType, _ErrorType_co]": + ) -> 'Result[_NewValueType, _ErrorType_co]': """ Composes successful container with a function that returns a container. @@ -147,7 +146,7 @@ def bind( def alt( self, function: Callable[[_ErrorType_co], _NewErrorType], - ) -> "Result[_ValueType_co, _NewErrorType]": + ) -> 'Result[_ValueType_co, _NewErrorType]': """ Composes failed container with a pure function to modify failure. @@ -167,9 +166,9 @@ def lash( self, function: Callable[ [_ErrorType_co], - Kind2["Result", _ValueType_co, _NewErrorType], + Kind2['Result', _ValueType_co, _NewErrorType], ], - ) -> "Result[_ValueType_co, _NewErrorType]": + ) -> 'Result[_ValueType_co, _NewErrorType]': """ Composes failed container with a function that returns a container. @@ -196,7 +195,7 @@ def __iter__(self) -> Iterator[_ValueType_co]: def do( cls, expr: Generator[_NewValueType, None, None], - ) -> "Result[_NewValueType, _NewErrorType]": + ) -> 'Result[_NewValueType, _NewErrorType]': """ Allows working with unwrapped values of containers in a safe way. @@ -278,7 +277,7 @@ def failure(self) -> _ErrorType_co: def from_value( cls, inner_value: _NewValueType, - ) -> "Result[_NewValueType, Any]": + ) -> 'Result[_NewValueType, Any]': """ One more value to create success unit values. @@ -299,7 +298,7 @@ def from_value( def from_failure( cls, inner_value: _NewErrorType, - ) -> "Result[Any, _NewErrorType]": + ) -> 'Result[Any, _NewErrorType]': """ One more value to create failure unit values. @@ -319,8 +318,8 @@ def from_failure( @classmethod def from_result( cls, - inner_value: "Result[_NewValueType, _NewErrorType]", - ) -> "Result[_NewValueType, _NewErrorType]": + inner_value: 'Result[_NewValueType, _NewErrorType]', + ) -> 'Result[_NewValueType, _NewErrorType]': """ Creates a new ``Result`` instance from existing ``Result`` instance. @@ -351,7 +350,7 @@ class Failure(Result[Any, _ErrorType_co]): # noqa: WPS338 def __init__(self, inner_value: _ErrorType_co) -> None: """Failure constructor.""" super().__init__(inner_value) - object.__setattr__(self, "_trace", self._get_trace()) + object.__setattr__(self, '_trace', self._get_trace()) if not TYPE_CHECKING: # noqa: WPS604 # pragma: no branch @@ -468,7 +467,7 @@ def failure(self) -> Never: # Decorators: -_ExceptionType = TypeVar("_ExceptionType", bound=Exception) +_ExceptionType = TypeVar('_ExceptionType', bound=Exception) @overload @@ -592,9 +591,9 @@ def decorator( ) line_number = exc_traceback.tb_next.tb_lineno exc.add_note( - f"Exception occurred in {inner_function.__name__} " - f"of {filename} " - f"at line number {line_number}." + f'Exception occurred in {inner_function.__name__} ' + f'of {filename} ' + f'at line number {line_number}.' ) return Failure(exc) diff --git a/tests/test_result/test_add_note_safe.py b/tests/test_result/test_add_note_safe.py index 1a050822d..5d9a62fa6 100644 --- a/tests/test_result/test_add_note_safe.py +++ b/tests/test_result/test_add_note_safe.py @@ -1,22 +1,21 @@ -from returns.result import safe from returns.pipeline import is_successful +from returns.result import safe @safe((Exception,), add_note_on_failure=True) def error_throwing_function() -> None: """Raises an exception.""" - raise ValueError("This is an error!") + raise ValueError('This is an error!') -@safe((Exception,), add_note_on_failure="A custom message") +@safe((Exception,), add_note_on_failure='A custom message') def error_throwing_function_with_message() -> None: """Raises an exception.""" - raise ValueError("This is an error!") + raise ValueError('This is an error!') def test_add_note_safe() -> None: """Tests the add_note decorator with safe.""" - result = error_throwing_function() print(result) @@ -24,22 +23,21 @@ def test_add_note_safe() -> None: print(result.failure()) assert not is_successful(result) assert ( - "Exception occurred in error_throwing_function" + 'Exception occurred in error_throwing_function' in result.failure().__notes__[0] ) def test_add_note_safe_with_message() -> None: """Tests the add_note decorator with safe.""" - result = error_throwing_function_with_message() print(result) print(result.failure().__notes__) print(result.failure()) assert not is_successful(result) - assert "A custom message" in result.failure().__notes__ + assert 'A custom message' in result.failure().__notes__ assert ( - "Exception occurred in error_throwing_function_with_message" + 'Exception occurred in error_throwing_function_with_message' in result.failure().__notes__[1] ) From 736264e5f357c1088db1fbd9606e55b52ae0d3a6 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 11 Apr 2025 21:39:59 -0400 Subject: [PATCH 4/8] Update to address changes needed to complete pull request --- CHANGELOG.md | 3 +- returns/future.py | 170 +++++++++++------- returns/io.py | 120 ++++++++----- returns/result.py | 60 ++++--- .../test_future/test_add_note_future_safe.py | 84 +++++++++ tests/test_io/test_add_note_impure_safe.py | 83 +++++++++ tests/test_result/test_add_note_safe.py | 38 ++++ .../test_future_safe_decorator.yml | 15 ++ .../test_impure_safe.yml | 12 ++ typesafety/test_result/test_safe.yml | 14 +- 10 files changed, 470 insertions(+), 129 deletions(-) create mode 100644 tests/test_future/test_add_note_future_safe.py create mode 100644 tests/test_io/test_add_note_impure_safe.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bb1813c5..20b914e9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,8 @@ See [0Ver](https://0ver.org/). ### Features - - Add the ability to add debugging notes to the `safe` decorator. + - Add the ability to add debugging notes to the `safe`, `impure_safe`, and + `future_safe` decorators. ## 0.25.0 diff --git a/returns/future.py b/returns/future.py index ecee5cf4d..80442fd46 100644 --- a/returns/future.py +++ b/returns/future.py @@ -25,19 +25,19 @@ dekind, ) from returns.primitives.reawaitable import ReAwaitable -from returns.result import Failure, Result, Success +from returns.result import Failure, Result, Success, add_note_to_exception # Definitions: -_ValueType_co = TypeVar('_ValueType_co', covariant=True) -_NewValueType = TypeVar('_NewValueType') -_ErrorType_co = TypeVar('_ErrorType_co', covariant=True) -_NewErrorType = TypeVar('_NewErrorType') +_ValueType_co = TypeVar("_ValueType_co", covariant=True) +_NewValueType = TypeVar("_NewValueType") +_ErrorType_co = TypeVar("_ErrorType_co", covariant=True) +_NewErrorType = TypeVar("_NewErrorType") -_FuncParams = ParamSpec('_FuncParams') +_FuncParams = ParamSpec("_FuncParams") # Aliases: -_FirstType = TypeVar('_FirstType') -_SecondType = TypeVar('_SecondType') +_FirstType = TypeVar("_FirstType") +_SecondType = TypeVar("_SecondType") # Public composition helpers: @@ -67,7 +67,7 @@ async def async_identity(instance: _FirstType) -> _FirstType: # noqa: RUF029 @final class Future( # type: ignore[type-var] BaseContainer, - SupportsKind1['Future', _ValueType_co], + SupportsKind1["Future", _ValueType_co], FutureBased1[_ValueType_co], ): """ @@ -172,7 +172,7 @@ async def awaitable(self) -> IO[_ValueType_co]: def map( self, function: Callable[[_ValueType_co], _NewValueType], - ) -> 'Future[_NewValueType]': + ) -> "Future[_NewValueType]": """ Applies function to the inner value. @@ -199,8 +199,8 @@ def map( def apply( self, - container: Kind1['Future', Callable[[_ValueType_co], _NewValueType]], - ) -> 'Future[_NewValueType]': + container: Kind1["Future", Callable[[_ValueType_co], _NewValueType]], + ) -> "Future[_NewValueType]": """ Calls a wrapped function in a container on this container. @@ -219,12 +219,14 @@ def apply( ... ) == IO('1b') """ - return Future(_future.async_apply(dekind(container), self._inner_value)) + return Future( + _future.async_apply(dekind(container), self._inner_value) + ) def bind( self, - function: Callable[[_ValueType_co], Kind1['Future', _NewValueType]], - ) -> 'Future[_NewValueType]': + function: Callable[[_ValueType_co], Kind1["Future", _NewValueType]], + ) -> "Future[_NewValueType]": """ Applies 'function' to the result of a previous calculation. @@ -254,9 +256,9 @@ def bind_async( self, function: Callable[ [_ValueType_co], - Awaitable[Kind1['Future', _NewValueType]], + Awaitable[Kind1["Future", _NewValueType]], ], - ) -> 'Future[_NewValueType]': + ) -> "Future[_NewValueType]": """ Compose a container and ``async`` function returning a container. @@ -285,8 +287,8 @@ def bind_async( def bind_awaitable( self, - function: Callable[[_ValueType_co], 'Awaitable[_NewValueType]'], - ) -> 'Future[_NewValueType]': + function: Callable[[_ValueType_co], "Awaitable[_NewValueType]"], + ) -> "Future[_NewValueType]": """ Allows to compose a container and a regular ``async`` function. @@ -318,7 +320,7 @@ def bind_awaitable( def bind_io( self, function: Callable[[_ValueType_co], IO[_NewValueType]], - ) -> 'Future[_NewValueType]': + ) -> "Future[_NewValueType]": """ Applies 'function' to the result of a previous calculation. @@ -353,7 +355,7 @@ async def factory() -> AsyncGenerator[_ValueType_co, None]: def do( cls, expr: AsyncGenerator[_NewValueType, None], - ) -> 'Future[_NewValueType]': + ) -> "Future[_NewValueType]": """ Allows working with unwrapped values of containers in a safe way. @@ -382,7 +384,7 @@ async def factory() -> _NewValueType: return Future(factory()) @classmethod - def from_value(cls, inner_value: _NewValueType) -> 'Future[_NewValueType]': + def from_value(cls, inner_value: _NewValueType) -> "Future[_NewValueType]": """ Allows to create a ``Future`` from a plain value. @@ -406,8 +408,8 @@ def from_value(cls, inner_value: _NewValueType) -> 'Future[_NewValueType]': @classmethod def from_future( cls, - inner_value: 'Future[_NewValueType]', - ) -> 'Future[_NewValueType]': + inner_value: "Future[_NewValueType]", + ) -> "Future[_NewValueType]": """ Creates a new ``Future`` from the existing one. @@ -425,7 +427,9 @@ def from_future( return inner_value @classmethod - def from_io(cls, inner_value: IO[_NewValueType]) -> 'Future[_NewValueType]': + def from_io( + cls, inner_value: IO[_NewValueType] + ) -> "Future[_NewValueType]": """ Allows to create a ``Future`` from ``IO`` container. @@ -446,8 +450,8 @@ def from_io(cls, inner_value: IO[_NewValueType]) -> 'Future[_NewValueType]': @classmethod def from_future_result( cls, - inner_value: 'FutureResult[_NewValueType, _NewErrorType]', - ) -> 'Future[Result[_NewValueType, _NewErrorType]]': + inner_value: "FutureResult[_NewValueType, _NewErrorType]", + ) -> "Future[Result[_NewValueType, _NewErrorType]]": """ Creates ``Future[Result[a, b]]`` instance from ``FutureResult[a, b]``. @@ -555,7 +559,7 @@ async def decorator( # noqa: RUF029 @final class FutureResult( # type: ignore[type-var] BaseContainer, - SupportsKind2['FutureResult', _ValueType_co, _ErrorType_co], + SupportsKind2["FutureResult", _ValueType_co, _ErrorType_co], FutureResultBased2[_ValueType_co, _ErrorType_co], ): """ @@ -671,7 +675,7 @@ async def awaitable(self) -> IOResult[_ValueType_co, _ErrorType_co]: """ return IOResult.from_result(await self._inner_value) - def swap(self) -> 'FutureResult[_ErrorType_co, _ValueType_co]': + def swap(self) -> "FutureResult[_ErrorType_co, _ValueType_co]": """ Swaps value and error types. @@ -696,7 +700,7 @@ def swap(self) -> 'FutureResult[_ErrorType_co, _ValueType_co]': def map( self, function: Callable[[_ValueType_co], _NewValueType], - ) -> 'FutureResult[_NewValueType, _ErrorType_co]': + ) -> "FutureResult[_NewValueType, _ErrorType_co]": """ Applies function to the inner value. @@ -732,11 +736,11 @@ def map( def apply( self, container: Kind2[ - 'FutureResult', + "FutureResult", Callable[[_ValueType_co], _NewValueType], _ErrorType_co, ], - ) -> 'FutureResult[_NewValueType, _ErrorType_co]': + ) -> "FutureResult[_NewValueType, _ErrorType_co]": """ Calls a wrapped function in a container on this container. @@ -783,9 +787,9 @@ def bind( self, function: Callable[ [_ValueType_co], - Kind2['FutureResult', _NewValueType, _ErrorType_co], + Kind2["FutureResult", _NewValueType, _ErrorType_co], ], - ) -> 'FutureResult[_NewValueType, _ErrorType_co]': + ) -> "FutureResult[_NewValueType, _ErrorType_co]": """ Applies 'function' to the result of a previous calculation. @@ -824,9 +828,9 @@ def bind_async( self, function: Callable[ [_ValueType_co], - Awaitable[Kind2['FutureResult', _NewValueType, _ErrorType_co]], + Awaitable[Kind2["FutureResult", _NewValueType, _ErrorType_co]], ], - ) -> 'FutureResult[_NewValueType, _ErrorType_co]': + ) -> "FutureResult[_NewValueType, _ErrorType_co]": """ Composes a container and ``async`` function returning container. @@ -865,7 +869,7 @@ def bind_async( def bind_awaitable( self, function: Callable[[_ValueType_co], Awaitable[_NewValueType]], - ) -> 'FutureResult[_NewValueType, _ErrorType_co]': + ) -> "FutureResult[_NewValueType, _ErrorType_co]": """ Allows to compose a container and a regular ``async`` function. @@ -902,7 +906,7 @@ def bind_result( function: Callable[ [_ValueType_co], Result[_NewValueType, _ErrorType_co] ], - ) -> 'FutureResult[_NewValueType, _ErrorType_co]': + ) -> "FutureResult[_NewValueType, _ErrorType_co]": """ Binds a function returning ``Result[a, b]`` container. @@ -936,7 +940,7 @@ def bind_ioresult( function: Callable[ [_ValueType_co], IOResult[_NewValueType, _ErrorType_co] ], - ) -> 'FutureResult[_NewValueType, _ErrorType_co]': + ) -> "FutureResult[_NewValueType, _ErrorType_co]": """ Binds a function returning ``IOResult[a, b]`` container. @@ -967,7 +971,7 @@ def bind_ioresult( def bind_io( self, function: Callable[[_ValueType_co], IO[_NewValueType]], - ) -> 'FutureResult[_NewValueType, _ErrorType_co]': + ) -> "FutureResult[_NewValueType, _ErrorType_co]": """ Binds a function returning ``IO[a]`` container. @@ -998,7 +1002,7 @@ def bind_io( def bind_future( self, function: Callable[[_ValueType_co], Future[_NewValueType]], - ) -> 'FutureResult[_NewValueType, _ErrorType_co]': + ) -> "FutureResult[_NewValueType, _ErrorType_co]": """ Binds a function returning ``Future[a]`` container. @@ -1028,8 +1032,10 @@ def bind_future( def bind_async_future( self, - function: Callable[[_ValueType_co], Awaitable['Future[_NewValueType]']], - ) -> 'FutureResult[_NewValueType, _ErrorType_co]': + function: Callable[ + [_ValueType_co], Awaitable["Future[_NewValueType]"] + ], + ) -> "FutureResult[_NewValueType, _ErrorType_co]": """ Composes a container and ``async`` function returning ``Future``. @@ -1065,7 +1071,7 @@ def bind_async_future( def alt( self, function: Callable[[_ErrorType_co], _NewErrorType], - ) -> 'FutureResult[_ValueType_co, _NewErrorType]': + ) -> "FutureResult[_ValueType_co, _NewErrorType]": """ Composes failed container with a pure function to modify failure. @@ -1097,9 +1103,9 @@ def lash( self, function: Callable[ [_ErrorType_co], - Kind2['FutureResult', _ValueType_co, _NewErrorType], + Kind2["FutureResult", _ValueType_co, _NewErrorType], ], - ) -> 'FutureResult[_ValueType_co, _NewErrorType]': + ) -> "FutureResult[_ValueType_co, _NewErrorType]": """ Composes failed container with a function that returns a container. @@ -1131,9 +1137,9 @@ def compose_result( self, function: Callable[ [Result[_ValueType_co, _ErrorType_co]], - Kind2['FutureResult', _NewValueType, _ErrorType_co], + Kind2["FutureResult", _NewValueType, _ErrorType_co], ], - ) -> 'FutureResult[_NewValueType, _ErrorType_co]': + ) -> "FutureResult[_NewValueType, _ErrorType_co]": """ Composes inner ``Result`` with ``FutureResult`` returning function. @@ -1179,7 +1185,7 @@ async def factory() -> AsyncGenerator[_ValueType_co, None]: def do( cls, expr: AsyncGenerator[_NewValueType, None], - ) -> 'FutureResult[_NewValueType, _NewErrorType]': + ) -> "FutureResult[_NewValueType, _NewErrorType]": """ Allows working with unwrapped values of containers in a safe way. @@ -1223,7 +1229,7 @@ async def factory() -> Result[_NewValueType, _NewErrorType]: def from_typecast( cls, inner_value: Future[Result[_NewValueType, _NewErrorType]], - ) -> 'FutureResult[_NewValueType, _NewErrorType]': + ) -> "FutureResult[_NewValueType, _NewErrorType]": """ Creates ``FutureResult[a, b]`` from ``Future[Result[a, b]]``. @@ -1251,7 +1257,7 @@ def from_typecast( def from_future( cls, inner_value: Future[_NewValueType], - ) -> 'FutureResult[_NewValueType, Any]': + ) -> "FutureResult[_NewValueType, Any]": """ Creates ``FutureResult`` from successful ``Future`` value. @@ -1275,7 +1281,7 @@ def from_future( def from_failed_future( cls, inner_value: Future[_NewErrorType], - ) -> 'FutureResult[Any, _NewErrorType]': + ) -> "FutureResult[Any, _NewErrorType]": """ Creates ``FutureResult`` from failed ``Future`` value. @@ -1298,8 +1304,8 @@ def from_failed_future( @classmethod def from_future_result( cls, - inner_value: 'FutureResult[_NewValueType, _NewErrorType]', - ) -> 'FutureResult[_NewValueType, _NewErrorType]': + inner_value: "FutureResult[_NewValueType, _NewErrorType]", + ) -> "FutureResult[_NewValueType, _NewErrorType]": """ Creates new ``FutureResult`` from existing one. @@ -1324,7 +1330,7 @@ def from_future_result( def from_io( cls, inner_value: IO[_NewValueType], - ) -> 'FutureResult[_NewValueType, Any]': + ) -> "FutureResult[_NewValueType, Any]": """ Creates ``FutureResult`` from successful ``IO`` value. @@ -1342,13 +1348,15 @@ def from_io( >>> anyio.run(main) """ - return FutureResult.from_value(inner_value._inner_value) # noqa: SLF001 + return FutureResult.from_value( + inner_value._inner_value + ) # noqa: SLF001 @classmethod def from_failed_io( cls, inner_value: IO[_NewErrorType], - ) -> 'FutureResult[Any, _NewErrorType]': + ) -> "FutureResult[Any, _NewErrorType]": """ Creates ``FutureResult`` from failed ``IO`` value. @@ -1366,13 +1374,15 @@ def from_failed_io( >>> anyio.run(main) """ - return FutureResult.from_failure(inner_value._inner_value) # noqa: SLF001 + return FutureResult.from_failure( + inner_value._inner_value + ) # noqa: SLF001 @classmethod def from_ioresult( cls, inner_value: IOResult[_NewValueType, _NewErrorType], - ) -> 'FutureResult[_NewValueType, _NewErrorType]': + ) -> "FutureResult[_NewValueType, _NewErrorType]": """ Creates ``FutureResult`` from ``IOResult`` value. @@ -1393,13 +1403,15 @@ def from_ioresult( >>> anyio.run(main) """ - return FutureResult(async_identity(inner_value._inner_value)) # noqa: SLF001 + return FutureResult( + async_identity(inner_value._inner_value) + ) # noqa: SLF001 @classmethod def from_result( cls, inner_value: Result[_NewValueType, _NewErrorType], - ) -> 'FutureResult[_NewValueType, _NewErrorType]': + ) -> "FutureResult[_NewValueType, _NewErrorType]": """ Creates ``FutureResult`` from ``Result`` value. @@ -1427,7 +1439,7 @@ def from_result( def from_value( cls, inner_value: _NewValueType, - ) -> 'FutureResult[_NewValueType, Any]': + ) -> "FutureResult[_NewValueType, Any]": """ Creates ``FutureResult`` from successful value. @@ -1451,7 +1463,7 @@ def from_value( def from_failure( cls, inner_value: _NewErrorType, - ) -> 'FutureResult[Any, _NewErrorType]': + ) -> "FutureResult[Any, _NewErrorType]": """ Creates ``FutureResult`` from failed value. @@ -1518,7 +1530,7 @@ def FutureFailure( # noqa: N802 FutureResultE: TypeAlias = FutureResult[_ValueType_co, Exception] -_ExceptionType = TypeVar('_ExceptionType', bound=Exception) +_ExceptionType = TypeVar("_ExceptionType", bound=Exception) # Decorators: @@ -1537,6 +1549,7 @@ def future_safe( @overload def future_safe( exceptions: tuple[type[_ExceptionType], ...], + add_note_on_failure: bool | str = False, ) -> Callable[ [ Callable[ @@ -1556,6 +1569,7 @@ def future_safe( # noqa: WPS212, WPS234, ] | tuple[type[_ExceptionType], ...] ), + add_note_on_failure: bool | str = False, ) -> ( Callable[_FuncParams, FutureResultE[_ValueType_co]] | Callable[ @@ -1615,6 +1629,33 @@ def future_safe( # noqa: WPS212, WPS234, In this case, only exceptions that are explicitly listed are going to be caught. + In order to add a note to the exception, you can use the + ``add_note_on_failure`` argument. It can be a string or a boolean value. + Either way, a generic note will be added to the exception that calls out + the file, line number, and function name where the error occured. If a + string is provided, it will be added as an additional note to the + exception. + + This feature can help with logging and debugging. + + Note that if you use this option, you must provide a tuple of exception + types as the first argument. + + Note that passing a blank string to ``add_note_on_failure`` will be treated + the same as passing False, and will not add a note. + + .. code:: python + + >>> from returns.future import future_safe + + >>> @future_safe((Exception,), add_note_on_failure=True) + ... def error_throwing_function() -> None: + ... raise ValueError("This is an error!") + + >>> @future_safe((Exception,), add_note_on_failure="A custom message") + ... def error_throwing_function() -> None: + ... raise ValueError("This is an error!") + Similar to :func:`returns.io.impure_safe` and :func:`returns.result.safe` decorators, but works with ``async`` functions. @@ -1634,6 +1675,7 @@ async def factory( try: return Success(await function(*args, **kwargs)) except inner_exceptions as exc: + exc = add_note_to_exception(exc, add_note_on_failure, function) return Failure(exc) @wraps(function) diff --git a/returns/io.py b/returns/io.py index 53933157a..b815234f7 100644 --- a/returns/io.py +++ b/returns/io.py @@ -16,21 +16,21 @@ SupportsKind2, dekind, ) -from returns.result import Failure, Result, Success +from returns.result import Failure, Result, Success, add_note_to_exception -_ValueType_co = TypeVar('_ValueType_co', covariant=True) -_NewValueType = TypeVar('_NewValueType') +_ValueType_co = TypeVar("_ValueType_co", covariant=True) +_NewValueType = TypeVar("_NewValueType") -_FuncParams = ParamSpec('_FuncParams') +_FuncParams = ParamSpec("_FuncParams") # Result related: -_ErrorType_co = TypeVar('_ErrorType_co', covariant=True) -_NewErrorType = TypeVar('_NewErrorType') +_ErrorType_co = TypeVar("_ErrorType_co", covariant=True) +_NewErrorType = TypeVar("_NewErrorType") class IO( # type: ignore[type-var] BaseContainer, - SupportsKind1['IO', _ValueType_co], + SupportsKind1["IO", _ValueType_co], io.IOLike1[_ValueType_co], ): """ @@ -80,7 +80,7 @@ def __init__(self, inner_value: _ValueType_co) -> None: def map( self, function: Callable[[_ValueType_co], _NewValueType], - ) -> 'IO[_NewValueType]': + ) -> "IO[_NewValueType]": """ Applies function to the inner value. @@ -101,8 +101,8 @@ def map( def apply( self, - container: Kind1['IO', Callable[[_ValueType_co], _NewValueType]], - ) -> 'IO[_NewValueType]': + container: Kind1["IO", Callable[[_ValueType_co], _NewValueType]], + ) -> "IO[_NewValueType]": """ Calls a wrapped function in a container on this container. @@ -129,8 +129,8 @@ def apply( def bind( self, - function: Callable[[_ValueType_co], Kind1['IO', _NewValueType]], - ) -> 'IO[_NewValueType]': + function: Callable[[_ValueType_co], Kind1["IO", _NewValueType]], + ) -> "IO[_NewValueType]": """ Applies 'function' to the result of a previous calculation. @@ -158,7 +158,7 @@ def __iter__(self) -> Iterator[_ValueType_co]: def do( cls, expr: Generator[_NewValueType, None, None], - ) -> 'IO[_NewValueType]': + ) -> "IO[_NewValueType]": """ Allows working with unwrapped values of containers in a safe way. @@ -177,7 +177,7 @@ def do( return IO(next(expr)) @classmethod - def from_value(cls, inner_value: _NewValueType) -> 'IO[_NewValueType]': + def from_value(cls, inner_value: _NewValueType) -> "IO[_NewValueType]": """ Unit function to construct new ``IO`` values. @@ -194,7 +194,7 @@ def from_value(cls, inner_value: _NewValueType) -> 'IO[_NewValueType]': return IO(inner_value) @classmethod - def from_io(cls, inner_value: 'IO[_NewValueType]') -> 'IO[_NewValueType]': + def from_io(cls, inner_value: "IO[_NewValueType]") -> "IO[_NewValueType]": """ Unit function to construct new ``IO`` values from existing ``IO``. @@ -211,8 +211,8 @@ def from_io(cls, inner_value: 'IO[_NewValueType]') -> 'IO[_NewValueType]': @classmethod def from_ioresult( cls, - inner_value: 'IOResult[_NewValueType, _NewErrorType]', - ) -> 'IO[Result[_NewValueType, _NewErrorType]]': + inner_value: "IOResult[_NewValueType, _NewErrorType]", + ) -> "IO[Result[_NewValueType, _NewErrorType]]": """ Converts ``IOResult[a, b]`` back to ``IO[Result[a, b]]``. @@ -270,7 +270,7 @@ def decorator( class IOResult( # type: ignore[type-var] BaseContainer, - SupportsKind2['IOResult', _ValueType_co, _ErrorType_co], + SupportsKind2["IOResult", _ValueType_co, _ErrorType_co], ioresult.IOResultBased2[_ValueType_co, _ErrorType_co], ABC, ): @@ -332,7 +332,7 @@ class IOResult( # type: ignore[type-var] __slots__ = () _inner_value: Result[_ValueType_co, _ErrorType_co] - __match_args__ = ('_inner_value',) + __match_args__ = ("_inner_value",) #: Typesafe equality comparison with other `IOResult` objects. equals = container_equality @@ -361,14 +361,14 @@ def __repr__(self) -> str: '>' """ - return f'' + return f"" @property def trace(self) -> list[FrameInfo] | None: """Returns a stack trace when :func:`~IOFailure` was called.""" return self._inner_value.trace - def swap(self) -> 'IOResult[_ErrorType_co, _ValueType_co]': + def swap(self) -> "IOResult[_ErrorType_co, _ValueType_co]": """ Swaps value and error types. @@ -389,7 +389,7 @@ def swap(self) -> 'IOResult[_ErrorType_co, _ValueType_co]': def map( self, function: Callable[[_ValueType_co], _NewValueType], - ) -> 'IOResult[_NewValueType, _ErrorType_co]': + ) -> "IOResult[_NewValueType, _ErrorType_co]": """ Composes successful container with a pure function. @@ -404,11 +404,11 @@ def map( def apply( self, container: Kind2[ - 'IOResult', + "IOResult", Callable[[_ValueType_co], _NewValueType], _ErrorType_co, ], - ) -> 'IOResult[_NewValueType, _ErrorType_co]': + ) -> "IOResult[_NewValueType, _ErrorType_co]": """ Calls a wrapped function in a container on this container. @@ -444,9 +444,9 @@ def bind( self, function: Callable[ [_ValueType_co], - Kind2['IOResult', _NewValueType, _ErrorType_co], + Kind2["IOResult", _NewValueType, _ErrorType_co], ], - ) -> 'IOResult[_NewValueType, _ErrorType_co]': + ) -> "IOResult[_NewValueType, _ErrorType_co]": """ Composes successful container with a function that returns a container. @@ -473,7 +473,7 @@ def bind_result( [_ValueType_co], Result[_NewValueType, _ErrorType_co], ], - ) -> 'IOResult[_NewValueType, _ErrorType_co]': + ) -> "IOResult[_NewValueType, _ErrorType_co]": """ Composes successful container with a function that returns a container. @@ -500,7 +500,7 @@ def bind_result( def bind_io( self, function: Callable[[_ValueType_co], IO[_NewValueType]], - ) -> 'IOResult[_NewValueType, _ErrorType_co]': + ) -> "IOResult[_NewValueType, _ErrorType_co]": """ Composes successful container with a function that returns a container. @@ -522,7 +522,7 @@ def bind_io( def alt( self, function: Callable[[_ErrorType_co], _NewErrorType], - ) -> 'IOResult[_ValueType_co, _NewErrorType]': + ) -> "IOResult[_ValueType_co, _NewErrorType]": """ Composes failed container with a pure function to modify failure. @@ -538,9 +538,9 @@ def lash( self, function: Callable[ [_ErrorType_co], - Kind2['IOResult', _ValueType_co, _NewErrorType], + Kind2["IOResult", _ValueType_co, _NewErrorType], ], - ) -> 'IOResult[_ValueType_co, _NewErrorType]': + ) -> "IOResult[_ValueType_co, _NewErrorType]": """ Composes failed container with a function that returns a container. @@ -614,9 +614,9 @@ def compose_result( self, function: Callable[ [Result[_ValueType_co, _ErrorType_co]], - Kind2['IOResult', _NewValueType, _ErrorType_co], + Kind2["IOResult", _NewValueType, _ErrorType_co], ], - ) -> 'IOResult[_NewValueType, _ErrorType_co]': + ) -> "IOResult[_NewValueType, _ErrorType_co]": """ Composes inner ``Result`` with ``IOResult`` returning function. @@ -647,7 +647,7 @@ def __iter__(self) -> Iterator[_ValueType_co]: def do( cls, expr: Generator[_NewValueType, None, None], - ) -> 'IOResult[_NewValueType, _NewErrorType]': + ) -> "IOResult[_NewValueType, _NewErrorType]": """ Allows working with unwrapped values of containers in a safe way. @@ -680,7 +680,7 @@ def do( def from_typecast( cls, inner_value: IO[Result[_NewValueType, _NewErrorType]], - ) -> 'IOResult[_NewValueType, _NewErrorType]': + ) -> "IOResult[_NewValueType, _NewErrorType]": """ Converts ``IO[Result[_ValueType_co, _ErrorType_co]]`` to ``IOResult``. @@ -703,7 +703,7 @@ def from_typecast( def from_failed_io( cls, inner_value: IO[_NewErrorType], - ) -> 'IOResult[Any, _NewErrorType]': + ) -> "IOResult[Any, _NewErrorType]": """ Creates new ``IOResult`` from "failed" ``IO`` container. @@ -720,7 +720,7 @@ def from_failed_io( def from_io( cls, inner_value: IO[_NewValueType], - ) -> 'IOResult[_NewValueType, Any]': + ) -> "IOResult[_NewValueType, Any]": """ Creates new ``IOResult`` from "successful" ``IO`` container. @@ -737,7 +737,7 @@ def from_io( def from_result( cls, inner_value: Result[_NewValueType, _NewErrorType], - ) -> 'IOResult[_NewValueType, _NewErrorType]': + ) -> "IOResult[_NewValueType, _NewErrorType]": """ Creates ``IOResult`` from ``Result`` value. @@ -757,8 +757,8 @@ def from_result( @classmethod def from_ioresult( cls, - inner_value: 'IOResult[_NewValueType, _NewErrorType]', - ) -> 'IOResult[_NewValueType, _NewErrorType]': + inner_value: "IOResult[_NewValueType, _NewErrorType]", + ) -> "IOResult[_NewValueType, _NewErrorType]": """ Creates ``IOResult`` from existing ``IOResult`` value. @@ -776,7 +776,7 @@ def from_ioresult( def from_value( cls, inner_value: _NewValueType, - ) -> 'IOResult[_NewValueType, Any]': + ) -> "IOResult[_NewValueType, Any]": """ One more value to create success unit values. @@ -797,7 +797,7 @@ def from_value( def from_failure( cls, inner_value: _NewErrorType, - ) -> 'IOResult[Any, _NewErrorType]': + ) -> "IOResult[Any, _NewErrorType]": """ One more value to create failure unit values. @@ -892,7 +892,7 @@ def lash(self, function): # impure_safe decorator: -_ExceptionType = TypeVar('_ExceptionType', bound=Exception) +_ExceptionType = TypeVar("_ExceptionType", bound=Exception) @overload @@ -905,6 +905,7 @@ def impure_safe( @overload def impure_safe( exceptions: tuple[type[_ExceptionType], ...], + add_note_on_failure: bool | str = False, ) -> Callable[ [Callable[_FuncParams, _NewValueType]], Callable[_FuncParams, IOResult[_NewValueType, _ExceptionType]], @@ -915,6 +916,7 @@ def impure_safe( # noqa: WPS234 exceptions: ( Callable[_FuncParams, _NewValueType] | tuple[type[_ExceptionType], ...] ), + add_note_on_failure: bool | str = False, ) -> ( Callable[_FuncParams, IOResultE[_NewValueType]] | Callable[ @@ -960,6 +962,33 @@ def impure_safe( # noqa: WPS234 In this case, only exceptions that are explicitly listed are going to be caught. + In order to add a note to the exception, you can use the + ``add_note_on_failure`` argument. It can be a string or a boolean value. + Either way, a generic note will be added to the exception that calls out + the file, line number, and function name where the error occured. If a + string is provided, it will be added as an additional note to the + exception. + + This feature can help with logging and debugging. + + Note that if you use this option, you must provide a tuple of exception + types as the first argument. + + Note that passing a blank string to ``add_note_on_failure`` will be treated + the same as passing False, and will not add a note. + + .. code:: python + + >>> from returns.io import impure_safe + + >>> @impure_safe((Exception,), add_note_on_failure=True) + ... def error_throwing_function() -> None: + ... raise ValueError("This is an error!") + + >>> @impure_safe((Exception,), add_note_on_failure="A custom message") + ... def error_throwing_function() -> None: + ... raise ValueError("This is an error!") + Similar to :func:`returns.future.future_safe` and :func:`returns.result.safe` decorators. """ @@ -976,6 +1005,11 @@ def decorator( try: return IOSuccess(inner_function(*args, **kwargs)) except inner_exceptions as exc: + exc = add_note_to_exception( + exc, + add_note_on_failure, + inner_function, + ) return IOFailure(exc) return decorator diff --git a/returns/result.py b/returns/result.py index 2354d672f..9f5ad8838 100644 --- a/returns/result.py +++ b/returns/result.py @@ -1,4 +1,3 @@ -import sys from abc import ABC from collections.abc import Callable, Generator, Iterator from functools import wraps @@ -471,6 +470,37 @@ def failure(self) -> Never: _ExceptionType = TypeVar("_ExceptionType", bound=Exception) +def add_note_to_exception( + exception: _ExceptionType, + message: bool | str, + function: Callable[_FuncParams, _ValueType_co], +) -> _ExceptionType: + """ + A utility function to add a generic note with file name, line number, and + function name to the exception. If a custom message is provided, it will be + added as an additional note to the exception. + """ + + if message: + # If the user provides a custom message, add it as a note + # to the exception. Otherwise just add a generic note. + if isinstance(message, str): + exception.add_note(message) + + # Add the generic note. + exc_traceback = exception.__traceback__ + if exc_traceback is not None: + filename = exc_traceback.tb_next.tb_frame.f_code.co_filename + line_number = exc_traceback.tb_next.tb_lineno + exception.add_note( + f"Exception occurred in {function.__name__} " + f"of {filename} " + f"at line number {line_number}." + ) + + return exception + + @overload def safe( function: Callable[_FuncParams, _ValueType_co], @@ -550,6 +580,10 @@ def safe( # noqa: WPS234 Note that if you use this option, you must provide a tuple of exception types as the first argument. + Note that passing a blank string to ``add_note_on_failure`` will be treated + the same as passing False, and will not add a note. + + .. code:: python >>> from returns.result import safe @@ -578,25 +612,11 @@ def decorator( try: return Success(inner_function(*args, **kwargs)) except inner_exceptions as exc: - if add_note_on_failure: - # If the user provides a custom message, add it as a note - # to the exception. Otherwise just add a generic note. - if isinstance(add_note_on_failure, str): - exc.add_note(add_note_on_failure) - - # Add the generic note. - exc_traceback = exc.__traceback__ - if exc_traceback is not None: - filename = ( - exc_traceback.tb_next.tb_frame.f_code.co_filename - ) - line_number = exc_traceback.tb_next.tb_lineno - exc.add_note( - f"Exception occurred in {inner_function.__name__} " - f"of {filename} " - f"at line number {line_number}." - ) - + exc = add_note_to_exception( + exc, + add_note_on_failure, + inner_function, + ) return Failure(exc) return decorator diff --git a/tests/test_future/test_add_note_future_safe.py b/tests/test_future/test_add_note_future_safe.py new file mode 100644 index 000000000..599c3673f --- /dev/null +++ b/tests/test_future/test_add_note_future_safe.py @@ -0,0 +1,84 @@ +import asyncio +from returns.future import future_safe +from returns.pipeline import is_successful + + +@future_safe((Exception,), add_note_on_failure=True) +async def error_throwing_function() -> None: + """Raises an exception.""" + raise ValueError("This is an error!") + + +@future_safe((Exception,), add_note_on_failure="A custom message") +async def error_throwing_function_with_message() -> None: + """Raises an exception.""" + raise ValueError("This is an error!") + + +@future_safe((Exception,), add_note_on_failure="") +async def error_throwing_function_with_empty_str() -> None: + """Raises an exception.""" + raise ValueError("This is an error!") + + +@future_safe +async def error_throwing_function_without_note() -> None: + """Raises an exception.""" + raise ValueError("This is an error!") + + +async def test_add_note_safe() -> None: + """Tests the add_note decorator with safe.""" + + result = await error_throwing_function() + + print(result) + print(result.failure()._inner_value.__notes__) + print(result.failure()) + assert not is_successful(result) + assert ( + "Exception occurred in error_throwing_function" + in result.failure()._inner_value.__notes__[0] + ) + + +async def test_add_note_safe_with_message() -> None: + """Tests the add_note decorator with safe.""" + + result = await error_throwing_function_with_message() + + print(result) + print(result.failure()._inner_value.__notes__) + print(result.failure()) + assert not is_successful(result) + assert "A custom message" in result.failure()._inner_value.__notes__ + assert ( + "Exception occurred in error_throwing_function_with_message" + in result.failure()._inner_value.__notes__[1] + ) + + +async def test_add_note_safe_with_empty_str() -> None: + """Tests the add_note decorator with safe.""" + + result = await error_throwing_function_with_empty_str() + + print(result) + + # Passing an empty string to add_note_on_failure should be treated as + # passing False, so no note should be added + assert not hasattr(result.failure()._inner_value, "__notes__") + assert not is_successful(result) + + +async def test_add_note_safe_without_note() -> None: + """Tests the vanilla functionality of the safe decortaor.""" + + result = await error_throwing_function_without_note() + + print(result) + + # Make sure that the add_note_on_failure functionality does not break the + # vanilla functionality of the safe decorator + assert not hasattr(result.failure()._inner_value, "__notes__") + assert not is_successful(result) diff --git a/tests/test_io/test_add_note_impure_safe.py b/tests/test_io/test_add_note_impure_safe.py new file mode 100644 index 000000000..bcf8f420d --- /dev/null +++ b/tests/test_io/test_add_note_impure_safe.py @@ -0,0 +1,83 @@ +from returns.io import impure_safe +from returns.pipeline import is_successful + + +@impure_safe((Exception,), add_note_on_failure=True) +def error_throwing_function() -> None: + """Raises an exception.""" + raise ValueError("This is an error!") + + +@impure_safe((Exception,), add_note_on_failure="A custom message") +def error_throwing_function_with_message() -> None: + """Raises an exception.""" + raise ValueError("This is an error!") + + +@impure_safe((Exception,), add_note_on_failure="") +def error_throwing_function_with_empty_str() -> None: + """Raises an exception.""" + raise ValueError("This is an error!") + + +@impure_safe +def error_throwing_function_without_note() -> None: + """Raises an exception.""" + raise ValueError("This is an error!") + + +def test_add_note_impure_safe() -> None: + """Tests the add_note decorator with impure_safe.""" + + result = error_throwing_function() + + print(result) + print(result.failure()._inner_value.__notes__) + print(result.failure()) + assert not is_successful(result) + assert ( + "Exception occurred in error_throwing_function" + in result.failure()._inner_value.__notes__[0] + ) + + +def test_add_note_impure_safe_with_message() -> None: + """Tests the add_note decorator with safe.""" + + result = error_throwing_function_with_message() + + print(result) + print(result.failure()._inner_value.__notes__) + print(result.failure()) + assert not is_successful(result) + assert "A custom message" in result.failure()._inner_value.__notes__ + assert ( + "Exception occurred in error_throwing_function_with_message" + in result.failure()._inner_value.__notes__[1] + ) + + +def test_add_note_impure_safe_with_empty_str() -> None: + """Tests the add_note decorator with safe.""" + + result = error_throwing_function_with_empty_str() + + print(result) + + # Passing an empty string to add_note_on_failure should be treated as + # passing False, so no note should be added + assert not hasattr(result.failure()._inner_value, "__notes__") + assert not is_successful(result) + + +def test_add_note_impure_safe_without_note() -> None: + """Tests the vanilla functionality of the safe decortaor.""" + + result = error_throwing_function_without_note() + + print(result) + + # Make sure that the add_note_on_failure functionality does not break the + # vanilla functionality of the safe decorator + assert not hasattr(result.failure()._inner_value, "__notes__") + assert not is_successful(result) diff --git a/tests/test_result/test_add_note_safe.py b/tests/test_result/test_add_note_safe.py index 1a050822d..87bba6aab 100644 --- a/tests/test_result/test_add_note_safe.py +++ b/tests/test_result/test_add_note_safe.py @@ -14,6 +14,18 @@ def error_throwing_function_with_message() -> None: raise ValueError("This is an error!") +@safe((Exception,), add_note_on_failure="") +def error_throwing_function_with_empty_str() -> None: + """Raises an exception.""" + raise ValueError("This is an error!") + + +@safe +def error_throwing_function_without_note() -> None: + """Raises an exception.""" + raise ValueError("This is an error!") + + def test_add_note_safe() -> None: """Tests the add_note decorator with safe.""" @@ -43,3 +55,29 @@ def test_add_note_safe_with_message() -> None: "Exception occurred in error_throwing_function_with_message" in result.failure().__notes__[1] ) + + +def test_add_note_safe_with_empty_str() -> None: + """Tests the add_note decorator with safe.""" + + result = error_throwing_function_with_empty_str() + + print(result) + + # Passing an empty string to add_note_on_failure should be treated as + # passing False, so no note should be added + assert not hasattr(result.failure(), "__notes__") + assert not is_successful(result) + + +def test_add_note_safe_without_note() -> None: + """Tests the vanilla functionality of the safe decortaor.""" + + result = error_throwing_function_without_note() + + print(result) + + # Make sure that the add_note_on_failure functionality does not break the + # vanilla functionality of the safe decorator + assert not hasattr(result.failure(), "__notes__") + assert not is_successful(result) diff --git a/typesafety/test_future/test_future_result_container/test_future_safe_decorator.yml b/typesafety/test_future/test_future_result_container/test_future_safe_decorator.yml index 99b8fe91e..fe5991dd2 100644 --- a/typesafety/test_future/test_future_result_container/test_future_safe_decorator.yml +++ b/typesafety/test_future/test_future_result_container/test_future_safe_decorator.yml @@ -42,6 +42,21 @@ reveal_type(test) # N: Revealed type is "def (first: builtins.int, second: Union[builtins.str, None] =, *, kw: builtins.bool =) -> returns.future.FutureResult[builtins.int, builtins.ValueError]" +- case: future_safe_decorator_with_pos_params_add_note + disable_cache: false + main: | + from typing import Optional + from returns.future import future_safe + + @future_safe((ValueError,), add_note_on_failure=True) + async def test( + first: int, second: Optional[str] = None, *, kw: bool = True, + ) -> int: + return 1 + + reveal_type(test) # N: Revealed type is "def (first: builtins.int, second: Union[builtins.str, None] =, *, kw: builtins.bool =) -> returns.future.FutureResult[builtins.int, builtins.ValueError]" + + - case: future_safe_decorator_with_named_params disable_cache: false main: | diff --git a/typesafety/test_io/test_ioresult_container/test_impure_safe.yml b/typesafety/test_io/test_ioresult_container/test_impure_safe.yml index fff048e76..1206ea697 100644 --- a/typesafety/test_io/test_ioresult_container/test_impure_safe.yml +++ b/typesafety/test_io/test_ioresult_container/test_impure_safe.yml @@ -26,3 +26,15 @@ return 1 reveal_type(test2) # N: Revealed type is "def (arg: builtins.str) -> returns.io.IOResult[builtins.int, builtins.ValueError]" + + +- case: impure_decorator_passing_exceptions_no_params_add_note + disable_cache: false + main: | + from returns.io import impure_safe + + @impure_safe((ValueError,), add_note_on_failure=True) + def test1(arg: str) -> int: + return 1 + + reveal_type(test1) # N: Revealed type is "def (arg: builtins.str) -> returns.io.IOResult[builtins.int, builtins.ValueError]" \ No newline at end of file diff --git a/typesafety/test_result/test_safe.yml b/typesafety/test_result/test_safe.yml index 2ecc69496..c6a9f66e4 100644 --- a/typesafety/test_result/test_safe.yml +++ b/typesafety/test_result/test_safe.yml @@ -8,7 +8,7 @@ return 1 reveal_type(test) # N: Revealed type is "def () -> returns.result.Result[builtins.int, builtins.Exception]" - + - case: safe_decorator_passing_exceptions_no_params disable_cache: false @@ -28,6 +28,18 @@ reveal_type(test2) # N: Revealed type is "def () -> returns.result.Result[builtins.int, builtins.ValueError]" +- case: safe_decorator_no_params_add_note + disable_cache: false + main: | + from returns.result import safe + + @safe((ValueError,), add_note_on_failure=True) + def test() -> int: + return 1 + + reveal_type(test) # N: Revealed type is "def () -> returns.result.Result[builtins.int, builtins.Exception]" + + - case: safe_composition_no_params disable_cache: false main: | From 6e5dd8ed302d6158333ba5c186c3c1789d79affa Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 12 Apr 2025 01:43:46 +0000 Subject: [PATCH 5/8] [pre-commit.ci] auto fixes from pre-commit.com hooks --- returns/future.py | 138 ++++++++---------- returns/io.py | 84 +++++------ returns/result.py | 57 ++++---- .../test_future/test_add_note_future_safe.py | 27 ++-- tests/test_io/test_add_note_impure_safe.py | 26 ++-- tests/test_result/test_add_note_safe.py | 12 +- .../test_impure_safe.yml | 2 +- typesafety/test_result/test_safe.yml | 2 +- 8 files changed, 162 insertions(+), 186 deletions(-) diff --git a/returns/future.py b/returns/future.py index 80442fd46..6a1207793 100644 --- a/returns/future.py +++ b/returns/future.py @@ -28,16 +28,16 @@ from returns.result import Failure, Result, Success, add_note_to_exception # Definitions: -_ValueType_co = TypeVar("_ValueType_co", covariant=True) -_NewValueType = TypeVar("_NewValueType") -_ErrorType_co = TypeVar("_ErrorType_co", covariant=True) -_NewErrorType = TypeVar("_NewErrorType") +_ValueType_co = TypeVar('_ValueType_co', covariant=True) +_NewValueType = TypeVar('_NewValueType') +_ErrorType_co = TypeVar('_ErrorType_co', covariant=True) +_NewErrorType = TypeVar('_NewErrorType') -_FuncParams = ParamSpec("_FuncParams") +_FuncParams = ParamSpec('_FuncParams') # Aliases: -_FirstType = TypeVar("_FirstType") -_SecondType = TypeVar("_SecondType") +_FirstType = TypeVar('_FirstType') +_SecondType = TypeVar('_SecondType') # Public composition helpers: @@ -67,7 +67,7 @@ async def async_identity(instance: _FirstType) -> _FirstType: # noqa: RUF029 @final class Future( # type: ignore[type-var] BaseContainer, - SupportsKind1["Future", _ValueType_co], + SupportsKind1['Future', _ValueType_co], FutureBased1[_ValueType_co], ): """ @@ -172,7 +172,7 @@ async def awaitable(self) -> IO[_ValueType_co]: def map( self, function: Callable[[_ValueType_co], _NewValueType], - ) -> "Future[_NewValueType]": + ) -> 'Future[_NewValueType]': """ Applies function to the inner value. @@ -199,8 +199,8 @@ def map( def apply( self, - container: Kind1["Future", Callable[[_ValueType_co], _NewValueType]], - ) -> "Future[_NewValueType]": + container: Kind1['Future', Callable[[_ValueType_co], _NewValueType]], + ) -> 'Future[_NewValueType]': """ Calls a wrapped function in a container on this container. @@ -219,14 +219,12 @@ def apply( ... ) == IO('1b') """ - return Future( - _future.async_apply(dekind(container), self._inner_value) - ) + return Future(_future.async_apply(dekind(container), self._inner_value)) def bind( self, - function: Callable[[_ValueType_co], Kind1["Future", _NewValueType]], - ) -> "Future[_NewValueType]": + function: Callable[[_ValueType_co], Kind1['Future', _NewValueType]], + ) -> 'Future[_NewValueType]': """ Applies 'function' to the result of a previous calculation. @@ -256,9 +254,9 @@ def bind_async( self, function: Callable[ [_ValueType_co], - Awaitable[Kind1["Future", _NewValueType]], + Awaitable[Kind1['Future', _NewValueType]], ], - ) -> "Future[_NewValueType]": + ) -> 'Future[_NewValueType]': """ Compose a container and ``async`` function returning a container. @@ -287,8 +285,8 @@ def bind_async( def bind_awaitable( self, - function: Callable[[_ValueType_co], "Awaitable[_NewValueType]"], - ) -> "Future[_NewValueType]": + function: Callable[[_ValueType_co], 'Awaitable[_NewValueType]'], + ) -> 'Future[_NewValueType]': """ Allows to compose a container and a regular ``async`` function. @@ -320,7 +318,7 @@ def bind_awaitable( def bind_io( self, function: Callable[[_ValueType_co], IO[_NewValueType]], - ) -> "Future[_NewValueType]": + ) -> 'Future[_NewValueType]': """ Applies 'function' to the result of a previous calculation. @@ -355,7 +353,7 @@ async def factory() -> AsyncGenerator[_ValueType_co, None]: def do( cls, expr: AsyncGenerator[_NewValueType, None], - ) -> "Future[_NewValueType]": + ) -> 'Future[_NewValueType]': """ Allows working with unwrapped values of containers in a safe way. @@ -384,7 +382,7 @@ async def factory() -> _NewValueType: return Future(factory()) @classmethod - def from_value(cls, inner_value: _NewValueType) -> "Future[_NewValueType]": + def from_value(cls, inner_value: _NewValueType) -> 'Future[_NewValueType]': """ Allows to create a ``Future`` from a plain value. @@ -408,8 +406,8 @@ def from_value(cls, inner_value: _NewValueType) -> "Future[_NewValueType]": @classmethod def from_future( cls, - inner_value: "Future[_NewValueType]", - ) -> "Future[_NewValueType]": + inner_value: 'Future[_NewValueType]', + ) -> 'Future[_NewValueType]': """ Creates a new ``Future`` from the existing one. @@ -427,9 +425,7 @@ def from_future( return inner_value @classmethod - def from_io( - cls, inner_value: IO[_NewValueType] - ) -> "Future[_NewValueType]": + def from_io(cls, inner_value: IO[_NewValueType]) -> 'Future[_NewValueType]': """ Allows to create a ``Future`` from ``IO`` container. @@ -450,8 +446,8 @@ def from_io( @classmethod def from_future_result( cls, - inner_value: "FutureResult[_NewValueType, _NewErrorType]", - ) -> "Future[Result[_NewValueType, _NewErrorType]]": + inner_value: 'FutureResult[_NewValueType, _NewErrorType]', + ) -> 'Future[Result[_NewValueType, _NewErrorType]]': """ Creates ``Future[Result[a, b]]`` instance from ``FutureResult[a, b]``. @@ -559,7 +555,7 @@ async def decorator( # noqa: RUF029 @final class FutureResult( # type: ignore[type-var] BaseContainer, - SupportsKind2["FutureResult", _ValueType_co, _ErrorType_co], + SupportsKind2['FutureResult', _ValueType_co, _ErrorType_co], FutureResultBased2[_ValueType_co, _ErrorType_co], ): """ @@ -675,7 +671,7 @@ async def awaitable(self) -> IOResult[_ValueType_co, _ErrorType_co]: """ return IOResult.from_result(await self._inner_value) - def swap(self) -> "FutureResult[_ErrorType_co, _ValueType_co]": + def swap(self) -> 'FutureResult[_ErrorType_co, _ValueType_co]': """ Swaps value and error types. @@ -700,7 +696,7 @@ def swap(self) -> "FutureResult[_ErrorType_co, _ValueType_co]": def map( self, function: Callable[[_ValueType_co], _NewValueType], - ) -> "FutureResult[_NewValueType, _ErrorType_co]": + ) -> 'FutureResult[_NewValueType, _ErrorType_co]': """ Applies function to the inner value. @@ -736,11 +732,11 @@ def map( def apply( self, container: Kind2[ - "FutureResult", + 'FutureResult', Callable[[_ValueType_co], _NewValueType], _ErrorType_co, ], - ) -> "FutureResult[_NewValueType, _ErrorType_co]": + ) -> 'FutureResult[_NewValueType, _ErrorType_co]': """ Calls a wrapped function in a container on this container. @@ -787,9 +783,9 @@ def bind( self, function: Callable[ [_ValueType_co], - Kind2["FutureResult", _NewValueType, _ErrorType_co], + Kind2['FutureResult', _NewValueType, _ErrorType_co], ], - ) -> "FutureResult[_NewValueType, _ErrorType_co]": + ) -> 'FutureResult[_NewValueType, _ErrorType_co]': """ Applies 'function' to the result of a previous calculation. @@ -828,9 +824,9 @@ def bind_async( self, function: Callable[ [_ValueType_co], - Awaitable[Kind2["FutureResult", _NewValueType, _ErrorType_co]], + Awaitable[Kind2['FutureResult', _NewValueType, _ErrorType_co]], ], - ) -> "FutureResult[_NewValueType, _ErrorType_co]": + ) -> 'FutureResult[_NewValueType, _ErrorType_co]': """ Composes a container and ``async`` function returning container. @@ -869,7 +865,7 @@ def bind_async( def bind_awaitable( self, function: Callable[[_ValueType_co], Awaitable[_NewValueType]], - ) -> "FutureResult[_NewValueType, _ErrorType_co]": + ) -> 'FutureResult[_NewValueType, _ErrorType_co]': """ Allows to compose a container and a regular ``async`` function. @@ -906,7 +902,7 @@ def bind_result( function: Callable[ [_ValueType_co], Result[_NewValueType, _ErrorType_co] ], - ) -> "FutureResult[_NewValueType, _ErrorType_co]": + ) -> 'FutureResult[_NewValueType, _ErrorType_co]': """ Binds a function returning ``Result[a, b]`` container. @@ -940,7 +936,7 @@ def bind_ioresult( function: Callable[ [_ValueType_co], IOResult[_NewValueType, _ErrorType_co] ], - ) -> "FutureResult[_NewValueType, _ErrorType_co]": + ) -> 'FutureResult[_NewValueType, _ErrorType_co]': """ Binds a function returning ``IOResult[a, b]`` container. @@ -971,7 +967,7 @@ def bind_ioresult( def bind_io( self, function: Callable[[_ValueType_co], IO[_NewValueType]], - ) -> "FutureResult[_NewValueType, _ErrorType_co]": + ) -> 'FutureResult[_NewValueType, _ErrorType_co]': """ Binds a function returning ``IO[a]`` container. @@ -1002,7 +998,7 @@ def bind_io( def bind_future( self, function: Callable[[_ValueType_co], Future[_NewValueType]], - ) -> "FutureResult[_NewValueType, _ErrorType_co]": + ) -> 'FutureResult[_NewValueType, _ErrorType_co]': """ Binds a function returning ``Future[a]`` container. @@ -1032,10 +1028,8 @@ def bind_future( def bind_async_future( self, - function: Callable[ - [_ValueType_co], Awaitable["Future[_NewValueType]"] - ], - ) -> "FutureResult[_NewValueType, _ErrorType_co]": + function: Callable[[_ValueType_co], Awaitable['Future[_NewValueType]']], + ) -> 'FutureResult[_NewValueType, _ErrorType_co]': """ Composes a container and ``async`` function returning ``Future``. @@ -1071,7 +1065,7 @@ def bind_async_future( def alt( self, function: Callable[[_ErrorType_co], _NewErrorType], - ) -> "FutureResult[_ValueType_co, _NewErrorType]": + ) -> 'FutureResult[_ValueType_co, _NewErrorType]': """ Composes failed container with a pure function to modify failure. @@ -1103,9 +1097,9 @@ def lash( self, function: Callable[ [_ErrorType_co], - Kind2["FutureResult", _ValueType_co, _NewErrorType], + Kind2['FutureResult', _ValueType_co, _NewErrorType], ], - ) -> "FutureResult[_ValueType_co, _NewErrorType]": + ) -> 'FutureResult[_ValueType_co, _NewErrorType]': """ Composes failed container with a function that returns a container. @@ -1137,9 +1131,9 @@ def compose_result( self, function: Callable[ [Result[_ValueType_co, _ErrorType_co]], - Kind2["FutureResult", _NewValueType, _ErrorType_co], + Kind2['FutureResult', _NewValueType, _ErrorType_co], ], - ) -> "FutureResult[_NewValueType, _ErrorType_co]": + ) -> 'FutureResult[_NewValueType, _ErrorType_co]': """ Composes inner ``Result`` with ``FutureResult`` returning function. @@ -1185,7 +1179,7 @@ async def factory() -> AsyncGenerator[_ValueType_co, None]: def do( cls, expr: AsyncGenerator[_NewValueType, None], - ) -> "FutureResult[_NewValueType, _NewErrorType]": + ) -> 'FutureResult[_NewValueType, _NewErrorType]': """ Allows working with unwrapped values of containers in a safe way. @@ -1229,7 +1223,7 @@ async def factory() -> Result[_NewValueType, _NewErrorType]: def from_typecast( cls, inner_value: Future[Result[_NewValueType, _NewErrorType]], - ) -> "FutureResult[_NewValueType, _NewErrorType]": + ) -> 'FutureResult[_NewValueType, _NewErrorType]': """ Creates ``FutureResult[a, b]`` from ``Future[Result[a, b]]``. @@ -1257,7 +1251,7 @@ def from_typecast( def from_future( cls, inner_value: Future[_NewValueType], - ) -> "FutureResult[_NewValueType, Any]": + ) -> 'FutureResult[_NewValueType, Any]': """ Creates ``FutureResult`` from successful ``Future`` value. @@ -1281,7 +1275,7 @@ def from_future( def from_failed_future( cls, inner_value: Future[_NewErrorType], - ) -> "FutureResult[Any, _NewErrorType]": + ) -> 'FutureResult[Any, _NewErrorType]': """ Creates ``FutureResult`` from failed ``Future`` value. @@ -1304,8 +1298,8 @@ def from_failed_future( @classmethod def from_future_result( cls, - inner_value: "FutureResult[_NewValueType, _NewErrorType]", - ) -> "FutureResult[_NewValueType, _NewErrorType]": + inner_value: 'FutureResult[_NewValueType, _NewErrorType]', + ) -> 'FutureResult[_NewValueType, _NewErrorType]': """ Creates new ``FutureResult`` from existing one. @@ -1330,7 +1324,7 @@ def from_future_result( def from_io( cls, inner_value: IO[_NewValueType], - ) -> "FutureResult[_NewValueType, Any]": + ) -> 'FutureResult[_NewValueType, Any]': """ Creates ``FutureResult`` from successful ``IO`` value. @@ -1348,15 +1342,13 @@ def from_io( >>> anyio.run(main) """ - return FutureResult.from_value( - inner_value._inner_value - ) # noqa: SLF001 + return FutureResult.from_value(inner_value._inner_value) @classmethod def from_failed_io( cls, inner_value: IO[_NewErrorType], - ) -> "FutureResult[Any, _NewErrorType]": + ) -> 'FutureResult[Any, _NewErrorType]': """ Creates ``FutureResult`` from failed ``IO`` value. @@ -1374,15 +1366,13 @@ def from_failed_io( >>> anyio.run(main) """ - return FutureResult.from_failure( - inner_value._inner_value - ) # noqa: SLF001 + return FutureResult.from_failure(inner_value._inner_value) @classmethod def from_ioresult( cls, inner_value: IOResult[_NewValueType, _NewErrorType], - ) -> "FutureResult[_NewValueType, _NewErrorType]": + ) -> 'FutureResult[_NewValueType, _NewErrorType]': """ Creates ``FutureResult`` from ``IOResult`` value. @@ -1403,15 +1393,13 @@ def from_ioresult( >>> anyio.run(main) """ - return FutureResult( - async_identity(inner_value._inner_value) - ) # noqa: SLF001 + return FutureResult(async_identity(inner_value._inner_value)) @classmethod def from_result( cls, inner_value: Result[_NewValueType, _NewErrorType], - ) -> "FutureResult[_NewValueType, _NewErrorType]": + ) -> 'FutureResult[_NewValueType, _NewErrorType]': """ Creates ``FutureResult`` from ``Result`` value. @@ -1439,7 +1427,7 @@ def from_result( def from_value( cls, inner_value: _NewValueType, - ) -> "FutureResult[_NewValueType, Any]": + ) -> 'FutureResult[_NewValueType, Any]': """ Creates ``FutureResult`` from successful value. @@ -1463,7 +1451,7 @@ def from_value( def from_failure( cls, inner_value: _NewErrorType, - ) -> "FutureResult[Any, _NewErrorType]": + ) -> 'FutureResult[Any, _NewErrorType]': """ Creates ``FutureResult`` from failed value. @@ -1530,7 +1518,7 @@ def FutureFailure( # noqa: N802 FutureResultE: TypeAlias = FutureResult[_ValueType_co, Exception] -_ExceptionType = TypeVar("_ExceptionType", bound=Exception) +_ExceptionType = TypeVar('_ExceptionType', bound=Exception) # Decorators: diff --git a/returns/io.py b/returns/io.py index b815234f7..dd49f60bc 100644 --- a/returns/io.py +++ b/returns/io.py @@ -18,19 +18,19 @@ ) from returns.result import Failure, Result, Success, add_note_to_exception -_ValueType_co = TypeVar("_ValueType_co", covariant=True) -_NewValueType = TypeVar("_NewValueType") +_ValueType_co = TypeVar('_ValueType_co', covariant=True) +_NewValueType = TypeVar('_NewValueType') -_FuncParams = ParamSpec("_FuncParams") +_FuncParams = ParamSpec('_FuncParams') # Result related: -_ErrorType_co = TypeVar("_ErrorType_co", covariant=True) -_NewErrorType = TypeVar("_NewErrorType") +_ErrorType_co = TypeVar('_ErrorType_co', covariant=True) +_NewErrorType = TypeVar('_NewErrorType') class IO( # type: ignore[type-var] BaseContainer, - SupportsKind1["IO", _ValueType_co], + SupportsKind1['IO', _ValueType_co], io.IOLike1[_ValueType_co], ): """ @@ -80,7 +80,7 @@ def __init__(self, inner_value: _ValueType_co) -> None: def map( self, function: Callable[[_ValueType_co], _NewValueType], - ) -> "IO[_NewValueType]": + ) -> 'IO[_NewValueType]': """ Applies function to the inner value. @@ -101,8 +101,8 @@ def map( def apply( self, - container: Kind1["IO", Callable[[_ValueType_co], _NewValueType]], - ) -> "IO[_NewValueType]": + container: Kind1['IO', Callable[[_ValueType_co], _NewValueType]], + ) -> 'IO[_NewValueType]': """ Calls a wrapped function in a container on this container. @@ -129,8 +129,8 @@ def apply( def bind( self, - function: Callable[[_ValueType_co], Kind1["IO", _NewValueType]], - ) -> "IO[_NewValueType]": + function: Callable[[_ValueType_co], Kind1['IO', _NewValueType]], + ) -> 'IO[_NewValueType]': """ Applies 'function' to the result of a previous calculation. @@ -158,7 +158,7 @@ def __iter__(self) -> Iterator[_ValueType_co]: def do( cls, expr: Generator[_NewValueType, None, None], - ) -> "IO[_NewValueType]": + ) -> 'IO[_NewValueType]': """ Allows working with unwrapped values of containers in a safe way. @@ -177,7 +177,7 @@ def do( return IO(next(expr)) @classmethod - def from_value(cls, inner_value: _NewValueType) -> "IO[_NewValueType]": + def from_value(cls, inner_value: _NewValueType) -> 'IO[_NewValueType]': """ Unit function to construct new ``IO`` values. @@ -194,7 +194,7 @@ def from_value(cls, inner_value: _NewValueType) -> "IO[_NewValueType]": return IO(inner_value) @classmethod - def from_io(cls, inner_value: "IO[_NewValueType]") -> "IO[_NewValueType]": + def from_io(cls, inner_value: 'IO[_NewValueType]') -> 'IO[_NewValueType]': """ Unit function to construct new ``IO`` values from existing ``IO``. @@ -211,8 +211,8 @@ def from_io(cls, inner_value: "IO[_NewValueType]") -> "IO[_NewValueType]": @classmethod def from_ioresult( cls, - inner_value: "IOResult[_NewValueType, _NewErrorType]", - ) -> "IO[Result[_NewValueType, _NewErrorType]]": + inner_value: 'IOResult[_NewValueType, _NewErrorType]', + ) -> 'IO[Result[_NewValueType, _NewErrorType]]': """ Converts ``IOResult[a, b]`` back to ``IO[Result[a, b]]``. @@ -270,7 +270,7 @@ def decorator( class IOResult( # type: ignore[type-var] BaseContainer, - SupportsKind2["IOResult", _ValueType_co, _ErrorType_co], + SupportsKind2['IOResult', _ValueType_co, _ErrorType_co], ioresult.IOResultBased2[_ValueType_co, _ErrorType_co], ABC, ): @@ -332,7 +332,7 @@ class IOResult( # type: ignore[type-var] __slots__ = () _inner_value: Result[_ValueType_co, _ErrorType_co] - __match_args__ = ("_inner_value",) + __match_args__ = ('_inner_value',) #: Typesafe equality comparison with other `IOResult` objects. equals = container_equality @@ -361,14 +361,14 @@ def __repr__(self) -> str: '>' """ - return f"" + return f'' @property def trace(self) -> list[FrameInfo] | None: """Returns a stack trace when :func:`~IOFailure` was called.""" return self._inner_value.trace - def swap(self) -> "IOResult[_ErrorType_co, _ValueType_co]": + def swap(self) -> 'IOResult[_ErrorType_co, _ValueType_co]': """ Swaps value and error types. @@ -389,7 +389,7 @@ def swap(self) -> "IOResult[_ErrorType_co, _ValueType_co]": def map( self, function: Callable[[_ValueType_co], _NewValueType], - ) -> "IOResult[_NewValueType, _ErrorType_co]": + ) -> 'IOResult[_NewValueType, _ErrorType_co]': """ Composes successful container with a pure function. @@ -404,11 +404,11 @@ def map( def apply( self, container: Kind2[ - "IOResult", + 'IOResult', Callable[[_ValueType_co], _NewValueType], _ErrorType_co, ], - ) -> "IOResult[_NewValueType, _ErrorType_co]": + ) -> 'IOResult[_NewValueType, _ErrorType_co]': """ Calls a wrapped function in a container on this container. @@ -444,9 +444,9 @@ def bind( self, function: Callable[ [_ValueType_co], - Kind2["IOResult", _NewValueType, _ErrorType_co], + Kind2['IOResult', _NewValueType, _ErrorType_co], ], - ) -> "IOResult[_NewValueType, _ErrorType_co]": + ) -> 'IOResult[_NewValueType, _ErrorType_co]': """ Composes successful container with a function that returns a container. @@ -473,7 +473,7 @@ def bind_result( [_ValueType_co], Result[_NewValueType, _ErrorType_co], ], - ) -> "IOResult[_NewValueType, _ErrorType_co]": + ) -> 'IOResult[_NewValueType, _ErrorType_co]': """ Composes successful container with a function that returns a container. @@ -500,7 +500,7 @@ def bind_result( def bind_io( self, function: Callable[[_ValueType_co], IO[_NewValueType]], - ) -> "IOResult[_NewValueType, _ErrorType_co]": + ) -> 'IOResult[_NewValueType, _ErrorType_co]': """ Composes successful container with a function that returns a container. @@ -522,7 +522,7 @@ def bind_io( def alt( self, function: Callable[[_ErrorType_co], _NewErrorType], - ) -> "IOResult[_ValueType_co, _NewErrorType]": + ) -> 'IOResult[_ValueType_co, _NewErrorType]': """ Composes failed container with a pure function to modify failure. @@ -538,9 +538,9 @@ def lash( self, function: Callable[ [_ErrorType_co], - Kind2["IOResult", _ValueType_co, _NewErrorType], + Kind2['IOResult', _ValueType_co, _NewErrorType], ], - ) -> "IOResult[_ValueType_co, _NewErrorType]": + ) -> 'IOResult[_ValueType_co, _NewErrorType]': """ Composes failed container with a function that returns a container. @@ -614,9 +614,9 @@ def compose_result( self, function: Callable[ [Result[_ValueType_co, _ErrorType_co]], - Kind2["IOResult", _NewValueType, _ErrorType_co], + Kind2['IOResult', _NewValueType, _ErrorType_co], ], - ) -> "IOResult[_NewValueType, _ErrorType_co]": + ) -> 'IOResult[_NewValueType, _ErrorType_co]': """ Composes inner ``Result`` with ``IOResult`` returning function. @@ -647,7 +647,7 @@ def __iter__(self) -> Iterator[_ValueType_co]: def do( cls, expr: Generator[_NewValueType, None, None], - ) -> "IOResult[_NewValueType, _NewErrorType]": + ) -> 'IOResult[_NewValueType, _NewErrorType]': """ Allows working with unwrapped values of containers in a safe way. @@ -680,7 +680,7 @@ def do( def from_typecast( cls, inner_value: IO[Result[_NewValueType, _NewErrorType]], - ) -> "IOResult[_NewValueType, _NewErrorType]": + ) -> 'IOResult[_NewValueType, _NewErrorType]': """ Converts ``IO[Result[_ValueType_co, _ErrorType_co]]`` to ``IOResult``. @@ -703,7 +703,7 @@ def from_typecast( def from_failed_io( cls, inner_value: IO[_NewErrorType], - ) -> "IOResult[Any, _NewErrorType]": + ) -> 'IOResult[Any, _NewErrorType]': """ Creates new ``IOResult`` from "failed" ``IO`` container. @@ -720,7 +720,7 @@ def from_failed_io( def from_io( cls, inner_value: IO[_NewValueType], - ) -> "IOResult[_NewValueType, Any]": + ) -> 'IOResult[_NewValueType, Any]': """ Creates new ``IOResult`` from "successful" ``IO`` container. @@ -737,7 +737,7 @@ def from_io( def from_result( cls, inner_value: Result[_NewValueType, _NewErrorType], - ) -> "IOResult[_NewValueType, _NewErrorType]": + ) -> 'IOResult[_NewValueType, _NewErrorType]': """ Creates ``IOResult`` from ``Result`` value. @@ -757,8 +757,8 @@ def from_result( @classmethod def from_ioresult( cls, - inner_value: "IOResult[_NewValueType, _NewErrorType]", - ) -> "IOResult[_NewValueType, _NewErrorType]": + inner_value: 'IOResult[_NewValueType, _NewErrorType]', + ) -> 'IOResult[_NewValueType, _NewErrorType]': """ Creates ``IOResult`` from existing ``IOResult`` value. @@ -776,7 +776,7 @@ def from_ioresult( def from_value( cls, inner_value: _NewValueType, - ) -> "IOResult[_NewValueType, Any]": + ) -> 'IOResult[_NewValueType, Any]': """ One more value to create success unit values. @@ -797,7 +797,7 @@ def from_value( def from_failure( cls, inner_value: _NewErrorType, - ) -> "IOResult[Any, _NewErrorType]": + ) -> 'IOResult[Any, _NewErrorType]': """ One more value to create failure unit values. @@ -892,7 +892,7 @@ def lash(self, function): # impure_safe decorator: -_ExceptionType = TypeVar("_ExceptionType", bound=Exception) +_ExceptionType = TypeVar('_ExceptionType', bound=Exception) @overload diff --git a/returns/result.py b/returns/result.py index 9f5ad8838..a8709badf 100644 --- a/returns/result.py +++ b/returns/result.py @@ -12,18 +12,18 @@ from returns.primitives.hkt import Kind2, SupportsKind2 # Definitions: -_ValueType_co = TypeVar("_ValueType_co", covariant=True) -_NewValueType = TypeVar("_NewValueType") -_ErrorType_co = TypeVar("_ErrorType_co", covariant=True) -_NewErrorType = TypeVar("_NewErrorType") +_ValueType_co = TypeVar('_ValueType_co', covariant=True) +_NewValueType = TypeVar('_NewValueType') +_ErrorType_co = TypeVar('_ErrorType_co', covariant=True) +_NewErrorType = TypeVar('_NewErrorType') -_FirstType = TypeVar("_FirstType") -_FuncParams = ParamSpec("_FuncParams") +_FirstType = TypeVar('_FirstType') +_FuncParams = ParamSpec('_FuncParams') class Result( # type: ignore[type-var] BaseContainer, - SupportsKind2["Result", _ValueType_co, _ErrorType_co], + SupportsKind2['Result', _ValueType_co, _ErrorType_co], result.ResultBased2[_ValueType_co, _ErrorType_co], ABC, ): @@ -39,8 +39,8 @@ class Result( # type: ignore[type-var] """ - __slots__ = ("_trace",) - __match_args__ = ("_inner_value",) + __slots__ = ('_trace',) + __match_args__ = ('_inner_value',) _inner_value: _ValueType_co | _ErrorType_co _trace: list[FrameInfo] | None @@ -53,7 +53,7 @@ def trace(self) -> list[FrameInfo] | None: """Returns a list with stack trace when :func:`~Failure` was called.""" return self._trace - def swap(self) -> "Result[_ErrorType_co, _ValueType_co]": + def swap(self) -> 'Result[_ErrorType_co, _ValueType_co]': """ Swaps value and error types. @@ -73,7 +73,7 @@ def swap(self) -> "Result[_ErrorType_co, _ValueType_co]": def map( self, function: Callable[[_ValueType_co], _NewValueType], - ) -> "Result[_NewValueType, _ErrorType_co]": + ) -> 'Result[_NewValueType, _ErrorType_co]': """ Composes successful container with a pure function. @@ -92,11 +92,11 @@ def map( def apply( self, container: Kind2[ - "Result", + 'Result', Callable[[_ValueType_co], _NewValueType], _ErrorType_co, ], - ) -> "Result[_NewValueType, _ErrorType_co]": + ) -> 'Result[_NewValueType, _ErrorType_co]': """ Calls a wrapped function in a container on this container. @@ -119,9 +119,9 @@ def bind( self, function: Callable[ [_ValueType_co], - Kind2["Result", _NewValueType, _ErrorType_co], + Kind2['Result', _NewValueType, _ErrorType_co], ], - ) -> "Result[_NewValueType, _ErrorType_co]": + ) -> 'Result[_NewValueType, _ErrorType_co]': """ Composes successful container with a function that returns a container. @@ -146,7 +146,7 @@ def bind( def alt( self, function: Callable[[_ErrorType_co], _NewErrorType], - ) -> "Result[_ValueType_co, _NewErrorType]": + ) -> 'Result[_ValueType_co, _NewErrorType]': """ Composes failed container with a pure function to modify failure. @@ -166,9 +166,9 @@ def lash( self, function: Callable[ [_ErrorType_co], - Kind2["Result", _ValueType_co, _NewErrorType], + Kind2['Result', _ValueType_co, _NewErrorType], ], - ) -> "Result[_ValueType_co, _NewErrorType]": + ) -> 'Result[_ValueType_co, _NewErrorType]': """ Composes failed container with a function that returns a container. @@ -195,7 +195,7 @@ def __iter__(self) -> Iterator[_ValueType_co]: def do( cls, expr: Generator[_NewValueType, None, None], - ) -> "Result[_NewValueType, _NewErrorType]": + ) -> 'Result[_NewValueType, _NewErrorType]': """ Allows working with unwrapped values of containers in a safe way. @@ -277,7 +277,7 @@ def failure(self) -> _ErrorType_co: def from_value( cls, inner_value: _NewValueType, - ) -> "Result[_NewValueType, Any]": + ) -> 'Result[_NewValueType, Any]': """ One more value to create success unit values. @@ -298,7 +298,7 @@ def from_value( def from_failure( cls, inner_value: _NewErrorType, - ) -> "Result[Any, _NewErrorType]": + ) -> 'Result[Any, _NewErrorType]': """ One more value to create failure unit values. @@ -318,8 +318,8 @@ def from_failure( @classmethod def from_result( cls, - inner_value: "Result[_NewValueType, _NewErrorType]", - ) -> "Result[_NewValueType, _NewErrorType]": + inner_value: 'Result[_NewValueType, _NewErrorType]', + ) -> 'Result[_NewValueType, _NewErrorType]': """ Creates a new ``Result`` instance from existing ``Result`` instance. @@ -350,7 +350,7 @@ class Failure(Result[Any, _ErrorType_co]): # noqa: WPS338 def __init__(self, inner_value: _ErrorType_co) -> None: """Failure constructor.""" super().__init__(inner_value) - object.__setattr__(self, "_trace", self._get_trace()) + object.__setattr__(self, '_trace', self._get_trace()) if not TYPE_CHECKING: # noqa: WPS604 # pragma: no branch @@ -467,7 +467,7 @@ def failure(self) -> Never: # Decorators: -_ExceptionType = TypeVar("_ExceptionType", bound=Exception) +_ExceptionType = TypeVar('_ExceptionType', bound=Exception) def add_note_to_exception( @@ -480,7 +480,6 @@ def add_note_to_exception( function name to the exception. If a custom message is provided, it will be added as an additional note to the exception. """ - if message: # If the user provides a custom message, add it as a note # to the exception. Otherwise just add a generic note. @@ -493,9 +492,9 @@ def add_note_to_exception( filename = exc_traceback.tb_next.tb_frame.f_code.co_filename line_number = exc_traceback.tb_next.tb_lineno exception.add_note( - f"Exception occurred in {function.__name__} " - f"of {filename} " - f"at line number {line_number}." + f'Exception occurred in {function.__name__} ' + f'of {filename} ' + f'at line number {line_number}.' ) return exception diff --git a/tests/test_future/test_add_note_future_safe.py b/tests/test_future/test_add_note_future_safe.py index 599c3673f..830bd079b 100644 --- a/tests/test_future/test_add_note_future_safe.py +++ b/tests/test_future/test_add_note_future_safe.py @@ -1,4 +1,3 @@ -import asyncio from returns.future import future_safe from returns.pipeline import is_successful @@ -6,30 +5,29 @@ @future_safe((Exception,), add_note_on_failure=True) async def error_throwing_function() -> None: """Raises an exception.""" - raise ValueError("This is an error!") + raise ValueError('This is an error!') -@future_safe((Exception,), add_note_on_failure="A custom message") +@future_safe((Exception,), add_note_on_failure='A custom message') async def error_throwing_function_with_message() -> None: """Raises an exception.""" - raise ValueError("This is an error!") + raise ValueError('This is an error!') -@future_safe((Exception,), add_note_on_failure="") +@future_safe((Exception,), add_note_on_failure='') async def error_throwing_function_with_empty_str() -> None: """Raises an exception.""" - raise ValueError("This is an error!") + raise ValueError('This is an error!') @future_safe async def error_throwing_function_without_note() -> None: """Raises an exception.""" - raise ValueError("This is an error!") + raise ValueError('This is an error!') async def test_add_note_safe() -> None: """Tests the add_note decorator with safe.""" - result = await error_throwing_function() print(result) @@ -37,48 +35,45 @@ async def test_add_note_safe() -> None: print(result.failure()) assert not is_successful(result) assert ( - "Exception occurred in error_throwing_function" + 'Exception occurred in error_throwing_function' in result.failure()._inner_value.__notes__[0] ) async def test_add_note_safe_with_message() -> None: """Tests the add_note decorator with safe.""" - result = await error_throwing_function_with_message() print(result) print(result.failure()._inner_value.__notes__) print(result.failure()) assert not is_successful(result) - assert "A custom message" in result.failure()._inner_value.__notes__ + assert 'A custom message' in result.failure()._inner_value.__notes__ assert ( - "Exception occurred in error_throwing_function_with_message" + 'Exception occurred in error_throwing_function_with_message' in result.failure()._inner_value.__notes__[1] ) async def test_add_note_safe_with_empty_str() -> None: """Tests the add_note decorator with safe.""" - result = await error_throwing_function_with_empty_str() print(result) # Passing an empty string to add_note_on_failure should be treated as # passing False, so no note should be added - assert not hasattr(result.failure()._inner_value, "__notes__") + assert not hasattr(result.failure()._inner_value, '__notes__') assert not is_successful(result) async def test_add_note_safe_without_note() -> None: """Tests the vanilla functionality of the safe decortaor.""" - result = await error_throwing_function_without_note() print(result) # Make sure that the add_note_on_failure functionality does not break the # vanilla functionality of the safe decorator - assert not hasattr(result.failure()._inner_value, "__notes__") + assert not hasattr(result.failure()._inner_value, '__notes__') assert not is_successful(result) diff --git a/tests/test_io/test_add_note_impure_safe.py b/tests/test_io/test_add_note_impure_safe.py index bcf8f420d..a9a2fa808 100644 --- a/tests/test_io/test_add_note_impure_safe.py +++ b/tests/test_io/test_add_note_impure_safe.py @@ -5,30 +5,29 @@ @impure_safe((Exception,), add_note_on_failure=True) def error_throwing_function() -> None: """Raises an exception.""" - raise ValueError("This is an error!") + raise ValueError('This is an error!') -@impure_safe((Exception,), add_note_on_failure="A custom message") +@impure_safe((Exception,), add_note_on_failure='A custom message') def error_throwing_function_with_message() -> None: """Raises an exception.""" - raise ValueError("This is an error!") + raise ValueError('This is an error!') -@impure_safe((Exception,), add_note_on_failure="") +@impure_safe((Exception,), add_note_on_failure='') def error_throwing_function_with_empty_str() -> None: """Raises an exception.""" - raise ValueError("This is an error!") + raise ValueError('This is an error!') @impure_safe def error_throwing_function_without_note() -> None: """Raises an exception.""" - raise ValueError("This is an error!") + raise ValueError('This is an error!') def test_add_note_impure_safe() -> None: """Tests the add_note decorator with impure_safe.""" - result = error_throwing_function() print(result) @@ -36,48 +35,45 @@ def test_add_note_impure_safe() -> None: print(result.failure()) assert not is_successful(result) assert ( - "Exception occurred in error_throwing_function" + 'Exception occurred in error_throwing_function' in result.failure()._inner_value.__notes__[0] ) def test_add_note_impure_safe_with_message() -> None: """Tests the add_note decorator with safe.""" - result = error_throwing_function_with_message() print(result) print(result.failure()._inner_value.__notes__) print(result.failure()) assert not is_successful(result) - assert "A custom message" in result.failure()._inner_value.__notes__ + assert 'A custom message' in result.failure()._inner_value.__notes__ assert ( - "Exception occurred in error_throwing_function_with_message" + 'Exception occurred in error_throwing_function_with_message' in result.failure()._inner_value.__notes__[1] ) def test_add_note_impure_safe_with_empty_str() -> None: """Tests the add_note decorator with safe.""" - result = error_throwing_function_with_empty_str() print(result) # Passing an empty string to add_note_on_failure should be treated as # passing False, so no note should be added - assert not hasattr(result.failure()._inner_value, "__notes__") + assert not hasattr(result.failure()._inner_value, '__notes__') assert not is_successful(result) def test_add_note_impure_safe_without_note() -> None: """Tests the vanilla functionality of the safe decortaor.""" - result = error_throwing_function_without_note() print(result) # Make sure that the add_note_on_failure functionality does not break the # vanilla functionality of the safe decorator - assert not hasattr(result.failure()._inner_value, "__notes__") + assert not hasattr(result.failure()._inner_value, '__notes__') assert not is_successful(result) diff --git a/tests/test_result/test_add_note_safe.py b/tests/test_result/test_add_note_safe.py index c784381fd..32f93c04d 100644 --- a/tests/test_result/test_add_note_safe.py +++ b/tests/test_result/test_add_note_safe.py @@ -14,16 +14,16 @@ def error_throwing_function_with_message() -> None: raise ValueError('This is an error!') -@safe((Exception,), add_note_on_failure="") +@safe((Exception,), add_note_on_failure='') def error_throwing_function_with_empty_str() -> None: """Raises an exception.""" - raise ValueError("This is an error!") + raise ValueError('This is an error!') @safe def error_throwing_function_without_note() -> None: """Raises an exception.""" - raise ValueError("This is an error!") + raise ValueError('This is an error!') def test_add_note_safe() -> None: @@ -57,25 +57,23 @@ def test_add_note_safe_with_message() -> None: def test_add_note_safe_with_empty_str() -> None: """Tests the add_note decorator with safe.""" - result = error_throwing_function_with_empty_str() print(result) # Passing an empty string to add_note_on_failure should be treated as # passing False, so no note should be added - assert not hasattr(result.failure(), "__notes__") + assert not hasattr(result.failure(), '__notes__') assert not is_successful(result) def test_add_note_safe_without_note() -> None: """Tests the vanilla functionality of the safe decortaor.""" - result = error_throwing_function_without_note() print(result) # Make sure that the add_note_on_failure functionality does not break the # vanilla functionality of the safe decorator - assert not hasattr(result.failure(), "__notes__") + assert not hasattr(result.failure(), '__notes__') assert not is_successful(result) diff --git a/typesafety/test_io/test_ioresult_container/test_impure_safe.yml b/typesafety/test_io/test_ioresult_container/test_impure_safe.yml index 1206ea697..3f25b2402 100644 --- a/typesafety/test_io/test_ioresult_container/test_impure_safe.yml +++ b/typesafety/test_io/test_ioresult_container/test_impure_safe.yml @@ -37,4 +37,4 @@ def test1(arg: str) -> int: return 1 - reveal_type(test1) # N: Revealed type is "def (arg: builtins.str) -> returns.io.IOResult[builtins.int, builtins.ValueError]" \ No newline at end of file + reveal_type(test1) # N: Revealed type is "def (arg: builtins.str) -> returns.io.IOResult[builtins.int, builtins.ValueError]" diff --git a/typesafety/test_result/test_safe.yml b/typesafety/test_result/test_safe.yml index c6a9f66e4..362f80c8b 100644 --- a/typesafety/test_result/test_safe.yml +++ b/typesafety/test_result/test_safe.yml @@ -8,7 +8,7 @@ return 1 reveal_type(test) # N: Revealed type is "def () -> returns.result.Result[builtins.int, builtins.Exception]" - + - case: safe_decorator_passing_exceptions_no_params disable_cache: false From 087140ed988c586bc5e2a64939c45814d7cfe978 Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 12 Apr 2025 07:23:56 -0400 Subject: [PATCH 6/8] Reduced identation for clearer code --- returns/primitives/exceptions.py | 48 ++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/returns/primitives/exceptions.py b/returns/primitives/exceptions.py index 21a65d013..01eaab38b 100644 --- a/returns/primitives/exceptions.py +++ b/returns/primitives/exceptions.py @@ -1,15 +1,21 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, TypeVar, Callable +from typing_extensions import ParamSpec if TYPE_CHECKING: from returns.interfaces.unwrappable import Unwrappable # noqa: WPS433 +_ValueType_co = TypeVar("_ValueType_co", covariant=True) +_FuncParams = ParamSpec("_FuncParams") +_ExceptionType = TypeVar("_ExceptionType", bound=Exception) + + class UnwrapFailedError(Exception): """Raised when a container can not be unwrapped into a meaningful value.""" - __slots__ = ('halted_container',) + __slots__ = ("halted_container",) def __init__(self, container: Unwrappable) -> None: """ @@ -45,3 +51,41 @@ class ImmutableStateError(AttributeError): See: https://github.com/dry-python/returns/issues/394 """ + + +def add_note_to_exception( + exception: _ExceptionType, + message: bool | str, + function: Callable[_FuncParams, _ValueType_co], +) -> _ExceptionType: + """ + A utility function to add a generic note with file name, line number, and + function name to the exception. If a custom message is provided, it will be + added as an additional note to the exception. + """ + + if not message: + return exception + + # If the user provides a custom message, add it as a note + # to the exception. Otherwise just add a generic note. + if isinstance(message, str): + exception.add_note(message) + + # Add the generic note. + exc_traceback = exception.__traceback__ + if exc_traceback is None: + return exception + + if exc_traceback.tb_next is None: + return exception + + filename = exc_traceback.tb_next.tb_frame.f_code.co_filename + line_number = exc_traceback.tb_next.tb_lineno + exception.add_note( + f"Exception occurred in {function.__name__} " + f"of {filename} " + f"at line number {line_number}." + ) + + return exception From 6790f1fc7d0d6bdb27042cb52f7636ea55cbdf4b Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 12 Apr 2025 07:24:32 -0400 Subject: [PATCH 7/8] moved add_note_to_exception, fixed comments --- returns/future.py | 134 +++++++++++++++++++++++++--------------------- returns/io.py | 92 ++++++++++++++++--------------- returns/result.py | 86 ++++++++++------------------- 3 files changed, 149 insertions(+), 163 deletions(-) diff --git a/returns/future.py b/returns/future.py index 6a1207793..5cc137ad8 100644 --- a/returns/future.py +++ b/returns/future.py @@ -16,7 +16,10 @@ from returns.interfaces.specific.future_result import FutureResultBased2 from returns.io import IO, IOResult from returns.primitives.container import BaseContainer -from returns.primitives.exceptions import UnwrapFailedError +from returns.primitives.exceptions import ( + UnwrapFailedError, + add_note_to_exception, +) from returns.primitives.hkt import ( Kind1, Kind2, @@ -25,19 +28,19 @@ dekind, ) from returns.primitives.reawaitable import ReAwaitable -from returns.result import Failure, Result, Success, add_note_to_exception +from returns.result import Failure, Result, Success # Definitions: -_ValueType_co = TypeVar('_ValueType_co', covariant=True) -_NewValueType = TypeVar('_NewValueType') -_ErrorType_co = TypeVar('_ErrorType_co', covariant=True) -_NewErrorType = TypeVar('_NewErrorType') +_ValueType_co = TypeVar("_ValueType_co", covariant=True) +_NewValueType = TypeVar("_NewValueType") +_ErrorType_co = TypeVar("_ErrorType_co", covariant=True) +_NewErrorType = TypeVar("_NewErrorType") -_FuncParams = ParamSpec('_FuncParams') +_FuncParams = ParamSpec("_FuncParams") # Aliases: -_FirstType = TypeVar('_FirstType') -_SecondType = TypeVar('_SecondType') +_FirstType = TypeVar("_FirstType") +_SecondType = TypeVar("_SecondType") # Public composition helpers: @@ -67,7 +70,7 @@ async def async_identity(instance: _FirstType) -> _FirstType: # noqa: RUF029 @final class Future( # type: ignore[type-var] BaseContainer, - SupportsKind1['Future', _ValueType_co], + SupportsKind1["Future", _ValueType_co], FutureBased1[_ValueType_co], ): """ @@ -172,7 +175,7 @@ async def awaitable(self) -> IO[_ValueType_co]: def map( self, function: Callable[[_ValueType_co], _NewValueType], - ) -> 'Future[_NewValueType]': + ) -> "Future[_NewValueType]": """ Applies function to the inner value. @@ -199,8 +202,8 @@ def map( def apply( self, - container: Kind1['Future', Callable[[_ValueType_co], _NewValueType]], - ) -> 'Future[_NewValueType]': + container: Kind1["Future", Callable[[_ValueType_co], _NewValueType]], + ) -> "Future[_NewValueType]": """ Calls a wrapped function in a container on this container. @@ -219,12 +222,14 @@ def apply( ... ) == IO('1b') """ - return Future(_future.async_apply(dekind(container), self._inner_value)) + return Future( + _future.async_apply(dekind(container), self._inner_value) + ) def bind( self, - function: Callable[[_ValueType_co], Kind1['Future', _NewValueType]], - ) -> 'Future[_NewValueType]': + function: Callable[[_ValueType_co], Kind1["Future", _NewValueType]], + ) -> "Future[_NewValueType]": """ Applies 'function' to the result of a previous calculation. @@ -254,9 +259,9 @@ def bind_async( self, function: Callable[ [_ValueType_co], - Awaitable[Kind1['Future', _NewValueType]], + Awaitable[Kind1["Future", _NewValueType]], ], - ) -> 'Future[_NewValueType]': + ) -> "Future[_NewValueType]": """ Compose a container and ``async`` function returning a container. @@ -285,8 +290,8 @@ def bind_async( def bind_awaitable( self, - function: Callable[[_ValueType_co], 'Awaitable[_NewValueType]'], - ) -> 'Future[_NewValueType]': + function: Callable[[_ValueType_co], "Awaitable[_NewValueType]"], + ) -> "Future[_NewValueType]": """ Allows to compose a container and a regular ``async`` function. @@ -318,7 +323,7 @@ def bind_awaitable( def bind_io( self, function: Callable[[_ValueType_co], IO[_NewValueType]], - ) -> 'Future[_NewValueType]': + ) -> "Future[_NewValueType]": """ Applies 'function' to the result of a previous calculation. @@ -353,7 +358,7 @@ async def factory() -> AsyncGenerator[_ValueType_co, None]: def do( cls, expr: AsyncGenerator[_NewValueType, None], - ) -> 'Future[_NewValueType]': + ) -> "Future[_NewValueType]": """ Allows working with unwrapped values of containers in a safe way. @@ -382,7 +387,7 @@ async def factory() -> _NewValueType: return Future(factory()) @classmethod - def from_value(cls, inner_value: _NewValueType) -> 'Future[_NewValueType]': + def from_value(cls, inner_value: _NewValueType) -> "Future[_NewValueType]": """ Allows to create a ``Future`` from a plain value. @@ -406,8 +411,8 @@ def from_value(cls, inner_value: _NewValueType) -> 'Future[_NewValueType]': @classmethod def from_future( cls, - inner_value: 'Future[_NewValueType]', - ) -> 'Future[_NewValueType]': + inner_value: "Future[_NewValueType]", + ) -> "Future[_NewValueType]": """ Creates a new ``Future`` from the existing one. @@ -425,7 +430,9 @@ def from_future( return inner_value @classmethod - def from_io(cls, inner_value: IO[_NewValueType]) -> 'Future[_NewValueType]': + def from_io( + cls, inner_value: IO[_NewValueType] + ) -> "Future[_NewValueType]": """ Allows to create a ``Future`` from ``IO`` container. @@ -446,8 +453,8 @@ def from_io(cls, inner_value: IO[_NewValueType]) -> 'Future[_NewValueType]': @classmethod def from_future_result( cls, - inner_value: 'FutureResult[_NewValueType, _NewErrorType]', - ) -> 'Future[Result[_NewValueType, _NewErrorType]]': + inner_value: "FutureResult[_NewValueType, _NewErrorType]", + ) -> "Future[Result[_NewValueType, _NewErrorType]]": """ Creates ``Future[Result[a, b]]`` instance from ``FutureResult[a, b]``. @@ -555,7 +562,7 @@ async def decorator( # noqa: RUF029 @final class FutureResult( # type: ignore[type-var] BaseContainer, - SupportsKind2['FutureResult', _ValueType_co, _ErrorType_co], + SupportsKind2["FutureResult", _ValueType_co, _ErrorType_co], FutureResultBased2[_ValueType_co, _ErrorType_co], ): """ @@ -671,7 +678,7 @@ async def awaitable(self) -> IOResult[_ValueType_co, _ErrorType_co]: """ return IOResult.from_result(await self._inner_value) - def swap(self) -> 'FutureResult[_ErrorType_co, _ValueType_co]': + def swap(self) -> "FutureResult[_ErrorType_co, _ValueType_co]": """ Swaps value and error types. @@ -696,7 +703,7 @@ def swap(self) -> 'FutureResult[_ErrorType_co, _ValueType_co]': def map( self, function: Callable[[_ValueType_co], _NewValueType], - ) -> 'FutureResult[_NewValueType, _ErrorType_co]': + ) -> "FutureResult[_NewValueType, _ErrorType_co]": """ Applies function to the inner value. @@ -732,11 +739,11 @@ def map( def apply( self, container: Kind2[ - 'FutureResult', + "FutureResult", Callable[[_ValueType_co], _NewValueType], _ErrorType_co, ], - ) -> 'FutureResult[_NewValueType, _ErrorType_co]': + ) -> "FutureResult[_NewValueType, _ErrorType_co]": """ Calls a wrapped function in a container on this container. @@ -783,9 +790,9 @@ def bind( self, function: Callable[ [_ValueType_co], - Kind2['FutureResult', _NewValueType, _ErrorType_co], + Kind2["FutureResult", _NewValueType, _ErrorType_co], ], - ) -> 'FutureResult[_NewValueType, _ErrorType_co]': + ) -> "FutureResult[_NewValueType, _ErrorType_co]": """ Applies 'function' to the result of a previous calculation. @@ -824,9 +831,9 @@ def bind_async( self, function: Callable[ [_ValueType_co], - Awaitable[Kind2['FutureResult', _NewValueType, _ErrorType_co]], + Awaitable[Kind2["FutureResult", _NewValueType, _ErrorType_co]], ], - ) -> 'FutureResult[_NewValueType, _ErrorType_co]': + ) -> "FutureResult[_NewValueType, _ErrorType_co]": """ Composes a container and ``async`` function returning container. @@ -865,7 +872,7 @@ def bind_async( def bind_awaitable( self, function: Callable[[_ValueType_co], Awaitable[_NewValueType]], - ) -> 'FutureResult[_NewValueType, _ErrorType_co]': + ) -> "FutureResult[_NewValueType, _ErrorType_co]": """ Allows to compose a container and a regular ``async`` function. @@ -902,7 +909,7 @@ def bind_result( function: Callable[ [_ValueType_co], Result[_NewValueType, _ErrorType_co] ], - ) -> 'FutureResult[_NewValueType, _ErrorType_co]': + ) -> "FutureResult[_NewValueType, _ErrorType_co]": """ Binds a function returning ``Result[a, b]`` container. @@ -936,7 +943,7 @@ def bind_ioresult( function: Callable[ [_ValueType_co], IOResult[_NewValueType, _ErrorType_co] ], - ) -> 'FutureResult[_NewValueType, _ErrorType_co]': + ) -> "FutureResult[_NewValueType, _ErrorType_co]": """ Binds a function returning ``IOResult[a, b]`` container. @@ -967,7 +974,7 @@ def bind_ioresult( def bind_io( self, function: Callable[[_ValueType_co], IO[_NewValueType]], - ) -> 'FutureResult[_NewValueType, _ErrorType_co]': + ) -> "FutureResult[_NewValueType, _ErrorType_co]": """ Binds a function returning ``IO[a]`` container. @@ -998,7 +1005,7 @@ def bind_io( def bind_future( self, function: Callable[[_ValueType_co], Future[_NewValueType]], - ) -> 'FutureResult[_NewValueType, _ErrorType_co]': + ) -> "FutureResult[_NewValueType, _ErrorType_co]": """ Binds a function returning ``Future[a]`` container. @@ -1028,8 +1035,10 @@ def bind_future( def bind_async_future( self, - function: Callable[[_ValueType_co], Awaitable['Future[_NewValueType]']], - ) -> 'FutureResult[_NewValueType, _ErrorType_co]': + function: Callable[ + [_ValueType_co], Awaitable["Future[_NewValueType]"] + ], + ) -> "FutureResult[_NewValueType, _ErrorType_co]": """ Composes a container and ``async`` function returning ``Future``. @@ -1065,7 +1074,7 @@ def bind_async_future( def alt( self, function: Callable[[_ErrorType_co], _NewErrorType], - ) -> 'FutureResult[_ValueType_co, _NewErrorType]': + ) -> "FutureResult[_ValueType_co, _NewErrorType]": """ Composes failed container with a pure function to modify failure. @@ -1097,9 +1106,9 @@ def lash( self, function: Callable[ [_ErrorType_co], - Kind2['FutureResult', _ValueType_co, _NewErrorType], + Kind2["FutureResult", _ValueType_co, _NewErrorType], ], - ) -> 'FutureResult[_ValueType_co, _NewErrorType]': + ) -> "FutureResult[_ValueType_co, _NewErrorType]": """ Composes failed container with a function that returns a container. @@ -1131,9 +1140,9 @@ def compose_result( self, function: Callable[ [Result[_ValueType_co, _ErrorType_co]], - Kind2['FutureResult', _NewValueType, _ErrorType_co], + Kind2["FutureResult", _NewValueType, _ErrorType_co], ], - ) -> 'FutureResult[_NewValueType, _ErrorType_co]': + ) -> "FutureResult[_NewValueType, _ErrorType_co]": """ Composes inner ``Result`` with ``FutureResult`` returning function. @@ -1179,7 +1188,7 @@ async def factory() -> AsyncGenerator[_ValueType_co, None]: def do( cls, expr: AsyncGenerator[_NewValueType, None], - ) -> 'FutureResult[_NewValueType, _NewErrorType]': + ) -> "FutureResult[_NewValueType, _NewErrorType]": """ Allows working with unwrapped values of containers in a safe way. @@ -1223,7 +1232,7 @@ async def factory() -> Result[_NewValueType, _NewErrorType]: def from_typecast( cls, inner_value: Future[Result[_NewValueType, _NewErrorType]], - ) -> 'FutureResult[_NewValueType, _NewErrorType]': + ) -> "FutureResult[_NewValueType, _NewErrorType]": """ Creates ``FutureResult[a, b]`` from ``Future[Result[a, b]]``. @@ -1251,7 +1260,7 @@ def from_typecast( def from_future( cls, inner_value: Future[_NewValueType], - ) -> 'FutureResult[_NewValueType, Any]': + ) -> "FutureResult[_NewValueType, Any]": """ Creates ``FutureResult`` from successful ``Future`` value. @@ -1275,7 +1284,7 @@ def from_future( def from_failed_future( cls, inner_value: Future[_NewErrorType], - ) -> 'FutureResult[Any, _NewErrorType]': + ) -> "FutureResult[Any, _NewErrorType]": """ Creates ``FutureResult`` from failed ``Future`` value. @@ -1298,8 +1307,8 @@ def from_failed_future( @classmethod def from_future_result( cls, - inner_value: 'FutureResult[_NewValueType, _NewErrorType]', - ) -> 'FutureResult[_NewValueType, _NewErrorType]': + inner_value: "FutureResult[_NewValueType, _NewErrorType]", + ) -> "FutureResult[_NewValueType, _NewErrorType]": """ Creates new ``FutureResult`` from existing one. @@ -1324,7 +1333,7 @@ def from_future_result( def from_io( cls, inner_value: IO[_NewValueType], - ) -> 'FutureResult[_NewValueType, Any]': + ) -> "FutureResult[_NewValueType, Any]": """ Creates ``FutureResult`` from successful ``IO`` value. @@ -1348,7 +1357,7 @@ def from_io( def from_failed_io( cls, inner_value: IO[_NewErrorType], - ) -> 'FutureResult[Any, _NewErrorType]': + ) -> "FutureResult[Any, _NewErrorType]": """ Creates ``FutureResult`` from failed ``IO`` value. @@ -1372,7 +1381,7 @@ def from_failed_io( def from_ioresult( cls, inner_value: IOResult[_NewValueType, _NewErrorType], - ) -> 'FutureResult[_NewValueType, _NewErrorType]': + ) -> "FutureResult[_NewValueType, _NewErrorType]": """ Creates ``FutureResult`` from ``IOResult`` value. @@ -1399,7 +1408,7 @@ def from_ioresult( def from_result( cls, inner_value: Result[_NewValueType, _NewErrorType], - ) -> 'FutureResult[_NewValueType, _NewErrorType]': + ) -> "FutureResult[_NewValueType, _NewErrorType]": """ Creates ``FutureResult`` from ``Result`` value. @@ -1427,7 +1436,7 @@ def from_result( def from_value( cls, inner_value: _NewValueType, - ) -> 'FutureResult[_NewValueType, Any]': + ) -> "FutureResult[_NewValueType, Any]": """ Creates ``FutureResult`` from successful value. @@ -1451,7 +1460,7 @@ def from_value( def from_failure( cls, inner_value: _NewErrorType, - ) -> 'FutureResult[Any, _NewErrorType]': + ) -> "FutureResult[Any, _NewErrorType]": """ Creates ``FutureResult`` from failed value. @@ -1518,7 +1527,7 @@ def FutureFailure( # noqa: N802 FutureResultE: TypeAlias = FutureResult[_ValueType_co, Exception] -_ExceptionType = TypeVar('_ExceptionType', bound=Exception) +_ExceptionType = TypeVar("_ExceptionType", bound=Exception) # Decorators: @@ -1549,6 +1558,7 @@ def future_safe( ]: ... +# add_note_on_failure is optional for backwards compatibility. def future_safe( # noqa: WPS212, WPS234, exceptions: ( Callable[ diff --git a/returns/io.py b/returns/io.py index dd49f60bc..632a16712 100644 --- a/returns/io.py +++ b/returns/io.py @@ -8,7 +8,10 @@ from returns.interfaces.specific import io, ioresult from returns.primitives.container import BaseContainer, container_equality -from returns.primitives.exceptions import UnwrapFailedError +from returns.primitives.exceptions import ( + UnwrapFailedError, + add_note_to_exception, +) from returns.primitives.hkt import ( Kind1, Kind2, @@ -16,21 +19,21 @@ SupportsKind2, dekind, ) -from returns.result import Failure, Result, Success, add_note_to_exception +from returns.result import Failure, Result, Success -_ValueType_co = TypeVar('_ValueType_co', covariant=True) -_NewValueType = TypeVar('_NewValueType') +_ValueType_co = TypeVar("_ValueType_co", covariant=True) +_NewValueType = TypeVar("_NewValueType") -_FuncParams = ParamSpec('_FuncParams') +_FuncParams = ParamSpec("_FuncParams") # Result related: -_ErrorType_co = TypeVar('_ErrorType_co', covariant=True) -_NewErrorType = TypeVar('_NewErrorType') +_ErrorType_co = TypeVar("_ErrorType_co", covariant=True) +_NewErrorType = TypeVar("_NewErrorType") class IO( # type: ignore[type-var] BaseContainer, - SupportsKind1['IO', _ValueType_co], + SupportsKind1["IO", _ValueType_co], io.IOLike1[_ValueType_co], ): """ @@ -80,7 +83,7 @@ def __init__(self, inner_value: _ValueType_co) -> None: def map( self, function: Callable[[_ValueType_co], _NewValueType], - ) -> 'IO[_NewValueType]': + ) -> "IO[_NewValueType]": """ Applies function to the inner value. @@ -101,8 +104,8 @@ def map( def apply( self, - container: Kind1['IO', Callable[[_ValueType_co], _NewValueType]], - ) -> 'IO[_NewValueType]': + container: Kind1["IO", Callable[[_ValueType_co], _NewValueType]], + ) -> "IO[_NewValueType]": """ Calls a wrapped function in a container on this container. @@ -129,8 +132,8 @@ def apply( def bind( self, - function: Callable[[_ValueType_co], Kind1['IO', _NewValueType]], - ) -> 'IO[_NewValueType]': + function: Callable[[_ValueType_co], Kind1["IO", _NewValueType]], + ) -> "IO[_NewValueType]": """ Applies 'function' to the result of a previous calculation. @@ -158,7 +161,7 @@ def __iter__(self) -> Iterator[_ValueType_co]: def do( cls, expr: Generator[_NewValueType, None, None], - ) -> 'IO[_NewValueType]': + ) -> "IO[_NewValueType]": """ Allows working with unwrapped values of containers in a safe way. @@ -177,7 +180,7 @@ def do( return IO(next(expr)) @classmethod - def from_value(cls, inner_value: _NewValueType) -> 'IO[_NewValueType]': + def from_value(cls, inner_value: _NewValueType) -> "IO[_NewValueType]": """ Unit function to construct new ``IO`` values. @@ -194,7 +197,7 @@ def from_value(cls, inner_value: _NewValueType) -> 'IO[_NewValueType]': return IO(inner_value) @classmethod - def from_io(cls, inner_value: 'IO[_NewValueType]') -> 'IO[_NewValueType]': + def from_io(cls, inner_value: "IO[_NewValueType]") -> "IO[_NewValueType]": """ Unit function to construct new ``IO`` values from existing ``IO``. @@ -211,8 +214,8 @@ def from_io(cls, inner_value: 'IO[_NewValueType]') -> 'IO[_NewValueType]': @classmethod def from_ioresult( cls, - inner_value: 'IOResult[_NewValueType, _NewErrorType]', - ) -> 'IO[Result[_NewValueType, _NewErrorType]]': + inner_value: "IOResult[_NewValueType, _NewErrorType]", + ) -> "IO[Result[_NewValueType, _NewErrorType]]": """ Converts ``IOResult[a, b]`` back to ``IO[Result[a, b]]``. @@ -270,7 +273,7 @@ def decorator( class IOResult( # type: ignore[type-var] BaseContainer, - SupportsKind2['IOResult', _ValueType_co, _ErrorType_co], + SupportsKind2["IOResult", _ValueType_co, _ErrorType_co], ioresult.IOResultBased2[_ValueType_co, _ErrorType_co], ABC, ): @@ -332,7 +335,7 @@ class IOResult( # type: ignore[type-var] __slots__ = () _inner_value: Result[_ValueType_co, _ErrorType_co] - __match_args__ = ('_inner_value',) + __match_args__ = ("_inner_value",) #: Typesafe equality comparison with other `IOResult` objects. equals = container_equality @@ -361,14 +364,14 @@ def __repr__(self) -> str: '>' """ - return f'' + return f"" @property def trace(self) -> list[FrameInfo] | None: """Returns a stack trace when :func:`~IOFailure` was called.""" return self._inner_value.trace - def swap(self) -> 'IOResult[_ErrorType_co, _ValueType_co]': + def swap(self) -> "IOResult[_ErrorType_co, _ValueType_co]": """ Swaps value and error types. @@ -389,7 +392,7 @@ def swap(self) -> 'IOResult[_ErrorType_co, _ValueType_co]': def map( self, function: Callable[[_ValueType_co], _NewValueType], - ) -> 'IOResult[_NewValueType, _ErrorType_co]': + ) -> "IOResult[_NewValueType, _ErrorType_co]": """ Composes successful container with a pure function. @@ -404,11 +407,11 @@ def map( def apply( self, container: Kind2[ - 'IOResult', + "IOResult", Callable[[_ValueType_co], _NewValueType], _ErrorType_co, ], - ) -> 'IOResult[_NewValueType, _ErrorType_co]': + ) -> "IOResult[_NewValueType, _ErrorType_co]": """ Calls a wrapped function in a container on this container. @@ -444,9 +447,9 @@ def bind( self, function: Callable[ [_ValueType_co], - Kind2['IOResult', _NewValueType, _ErrorType_co], + Kind2["IOResult", _NewValueType, _ErrorType_co], ], - ) -> 'IOResult[_NewValueType, _ErrorType_co]': + ) -> "IOResult[_NewValueType, _ErrorType_co]": """ Composes successful container with a function that returns a container. @@ -473,7 +476,7 @@ def bind_result( [_ValueType_co], Result[_NewValueType, _ErrorType_co], ], - ) -> 'IOResult[_NewValueType, _ErrorType_co]': + ) -> "IOResult[_NewValueType, _ErrorType_co]": """ Composes successful container with a function that returns a container. @@ -500,7 +503,7 @@ def bind_result( def bind_io( self, function: Callable[[_ValueType_co], IO[_NewValueType]], - ) -> 'IOResult[_NewValueType, _ErrorType_co]': + ) -> "IOResult[_NewValueType, _ErrorType_co]": """ Composes successful container with a function that returns a container. @@ -522,7 +525,7 @@ def bind_io( def alt( self, function: Callable[[_ErrorType_co], _NewErrorType], - ) -> 'IOResult[_ValueType_co, _NewErrorType]': + ) -> "IOResult[_ValueType_co, _NewErrorType]": """ Composes failed container with a pure function to modify failure. @@ -538,9 +541,9 @@ def lash( self, function: Callable[ [_ErrorType_co], - Kind2['IOResult', _ValueType_co, _NewErrorType], + Kind2["IOResult", _ValueType_co, _NewErrorType], ], - ) -> 'IOResult[_ValueType_co, _NewErrorType]': + ) -> "IOResult[_ValueType_co, _NewErrorType]": """ Composes failed container with a function that returns a container. @@ -614,9 +617,9 @@ def compose_result( self, function: Callable[ [Result[_ValueType_co, _ErrorType_co]], - Kind2['IOResult', _NewValueType, _ErrorType_co], + Kind2["IOResult", _NewValueType, _ErrorType_co], ], - ) -> 'IOResult[_NewValueType, _ErrorType_co]': + ) -> "IOResult[_NewValueType, _ErrorType_co]": """ Composes inner ``Result`` with ``IOResult`` returning function. @@ -647,7 +650,7 @@ def __iter__(self) -> Iterator[_ValueType_co]: def do( cls, expr: Generator[_NewValueType, None, None], - ) -> 'IOResult[_NewValueType, _NewErrorType]': + ) -> "IOResult[_NewValueType, _NewErrorType]": """ Allows working with unwrapped values of containers in a safe way. @@ -680,7 +683,7 @@ def do( def from_typecast( cls, inner_value: IO[Result[_NewValueType, _NewErrorType]], - ) -> 'IOResult[_NewValueType, _NewErrorType]': + ) -> "IOResult[_NewValueType, _NewErrorType]": """ Converts ``IO[Result[_ValueType_co, _ErrorType_co]]`` to ``IOResult``. @@ -703,7 +706,7 @@ def from_typecast( def from_failed_io( cls, inner_value: IO[_NewErrorType], - ) -> 'IOResult[Any, _NewErrorType]': + ) -> "IOResult[Any, _NewErrorType]": """ Creates new ``IOResult`` from "failed" ``IO`` container. @@ -720,7 +723,7 @@ def from_failed_io( def from_io( cls, inner_value: IO[_NewValueType], - ) -> 'IOResult[_NewValueType, Any]': + ) -> "IOResult[_NewValueType, Any]": """ Creates new ``IOResult`` from "successful" ``IO`` container. @@ -737,7 +740,7 @@ def from_io( def from_result( cls, inner_value: Result[_NewValueType, _NewErrorType], - ) -> 'IOResult[_NewValueType, _NewErrorType]': + ) -> "IOResult[_NewValueType, _NewErrorType]": """ Creates ``IOResult`` from ``Result`` value. @@ -757,8 +760,8 @@ def from_result( @classmethod def from_ioresult( cls, - inner_value: 'IOResult[_NewValueType, _NewErrorType]', - ) -> 'IOResult[_NewValueType, _NewErrorType]': + inner_value: "IOResult[_NewValueType, _NewErrorType]", + ) -> "IOResult[_NewValueType, _NewErrorType]": """ Creates ``IOResult`` from existing ``IOResult`` value. @@ -776,7 +779,7 @@ def from_ioresult( def from_value( cls, inner_value: _NewValueType, - ) -> 'IOResult[_NewValueType, Any]': + ) -> "IOResult[_NewValueType, Any]": """ One more value to create success unit values. @@ -797,7 +800,7 @@ def from_value( def from_failure( cls, inner_value: _NewErrorType, - ) -> 'IOResult[Any, _NewErrorType]': + ) -> "IOResult[Any, _NewErrorType]": """ One more value to create failure unit values. @@ -892,7 +895,7 @@ def lash(self, function): # impure_safe decorator: -_ExceptionType = TypeVar('_ExceptionType', bound=Exception) +_ExceptionType = TypeVar("_ExceptionType", bound=Exception) @overload @@ -912,6 +915,7 @@ def impure_safe( ]: ... +# add_note_on_failure is optional for backwards compatibility. def impure_safe( # noqa: WPS234 exceptions: ( Callable[_FuncParams, _NewValueType] | tuple[type[_ExceptionType], ...] diff --git a/returns/result.py b/returns/result.py index a8709badf..7cfd37a90 100644 --- a/returns/result.py +++ b/returns/result.py @@ -8,22 +8,25 @@ from returns.interfaces.specific import result from returns.primitives.container import BaseContainer, container_equality -from returns.primitives.exceptions import UnwrapFailedError +from returns.primitives.exceptions import ( + UnwrapFailedError, + add_note_to_exception, +) from returns.primitives.hkt import Kind2, SupportsKind2 # Definitions: -_ValueType_co = TypeVar('_ValueType_co', covariant=True) -_NewValueType = TypeVar('_NewValueType') -_ErrorType_co = TypeVar('_ErrorType_co', covariant=True) -_NewErrorType = TypeVar('_NewErrorType') +_ValueType_co = TypeVar("_ValueType_co", covariant=True) +_NewValueType = TypeVar("_NewValueType") +_ErrorType_co = TypeVar("_ErrorType_co", covariant=True) +_NewErrorType = TypeVar("_NewErrorType") -_FirstType = TypeVar('_FirstType') -_FuncParams = ParamSpec('_FuncParams') +_FirstType = TypeVar("_FirstType") +_FuncParams = ParamSpec("_FuncParams") class Result( # type: ignore[type-var] BaseContainer, - SupportsKind2['Result', _ValueType_co, _ErrorType_co], + SupportsKind2["Result", _ValueType_co, _ErrorType_co], result.ResultBased2[_ValueType_co, _ErrorType_co], ABC, ): @@ -39,8 +42,8 @@ class Result( # type: ignore[type-var] """ - __slots__ = ('_trace',) - __match_args__ = ('_inner_value',) + __slots__ = ("_trace",) + __match_args__ = ("_inner_value",) _inner_value: _ValueType_co | _ErrorType_co _trace: list[FrameInfo] | None @@ -53,7 +56,7 @@ def trace(self) -> list[FrameInfo] | None: """Returns a list with stack trace when :func:`~Failure` was called.""" return self._trace - def swap(self) -> 'Result[_ErrorType_co, _ValueType_co]': + def swap(self) -> "Result[_ErrorType_co, _ValueType_co]": """ Swaps value and error types. @@ -73,7 +76,7 @@ def swap(self) -> 'Result[_ErrorType_co, _ValueType_co]': def map( self, function: Callable[[_ValueType_co], _NewValueType], - ) -> 'Result[_NewValueType, _ErrorType_co]': + ) -> "Result[_NewValueType, _ErrorType_co]": """ Composes successful container with a pure function. @@ -92,11 +95,11 @@ def map( def apply( self, container: Kind2[ - 'Result', + "Result", Callable[[_ValueType_co], _NewValueType], _ErrorType_co, ], - ) -> 'Result[_NewValueType, _ErrorType_co]': + ) -> "Result[_NewValueType, _ErrorType_co]": """ Calls a wrapped function in a container on this container. @@ -119,9 +122,9 @@ def bind( self, function: Callable[ [_ValueType_co], - Kind2['Result', _NewValueType, _ErrorType_co], + Kind2["Result", _NewValueType, _ErrorType_co], ], - ) -> 'Result[_NewValueType, _ErrorType_co]': + ) -> "Result[_NewValueType, _ErrorType_co]": """ Composes successful container with a function that returns a container. @@ -146,7 +149,7 @@ def bind( def alt( self, function: Callable[[_ErrorType_co], _NewErrorType], - ) -> 'Result[_ValueType_co, _NewErrorType]': + ) -> "Result[_ValueType_co, _NewErrorType]": """ Composes failed container with a pure function to modify failure. @@ -166,9 +169,9 @@ def lash( self, function: Callable[ [_ErrorType_co], - Kind2['Result', _ValueType_co, _NewErrorType], + Kind2["Result", _ValueType_co, _NewErrorType], ], - ) -> 'Result[_ValueType_co, _NewErrorType]': + ) -> "Result[_ValueType_co, _NewErrorType]": """ Composes failed container with a function that returns a container. @@ -195,7 +198,7 @@ def __iter__(self) -> Iterator[_ValueType_co]: def do( cls, expr: Generator[_NewValueType, None, None], - ) -> 'Result[_NewValueType, _NewErrorType]': + ) -> "Result[_NewValueType, _NewErrorType]": """ Allows working with unwrapped values of containers in a safe way. @@ -277,7 +280,7 @@ def failure(self) -> _ErrorType_co: def from_value( cls, inner_value: _NewValueType, - ) -> 'Result[_NewValueType, Any]': + ) -> "Result[_NewValueType, Any]": """ One more value to create success unit values. @@ -298,7 +301,7 @@ def from_value( def from_failure( cls, inner_value: _NewErrorType, - ) -> 'Result[Any, _NewErrorType]': + ) -> "Result[Any, _NewErrorType]": """ One more value to create failure unit values. @@ -318,8 +321,8 @@ def from_failure( @classmethod def from_result( cls, - inner_value: 'Result[_NewValueType, _NewErrorType]', - ) -> 'Result[_NewValueType, _NewErrorType]': + inner_value: "Result[_NewValueType, _NewErrorType]", + ) -> "Result[_NewValueType, _NewErrorType]": """ Creates a new ``Result`` instance from existing ``Result`` instance. @@ -350,7 +353,7 @@ class Failure(Result[Any, _ErrorType_co]): # noqa: WPS338 def __init__(self, inner_value: _ErrorType_co) -> None: """Failure constructor.""" super().__init__(inner_value) - object.__setattr__(self, '_trace', self._get_trace()) + object.__setattr__(self, "_trace", self._get_trace()) if not TYPE_CHECKING: # noqa: WPS604 # pragma: no branch @@ -466,38 +469,7 @@ def failure(self) -> Never: # Decorators: - -_ExceptionType = TypeVar('_ExceptionType', bound=Exception) - - -def add_note_to_exception( - exception: _ExceptionType, - message: bool | str, - function: Callable[_FuncParams, _ValueType_co], -) -> _ExceptionType: - """ - A utility function to add a generic note with file name, line number, and - function name to the exception. If a custom message is provided, it will be - added as an additional note to the exception. - """ - if message: - # If the user provides a custom message, add it as a note - # to the exception. Otherwise just add a generic note. - if isinstance(message, str): - exception.add_note(message) - - # Add the generic note. - exc_traceback = exception.__traceback__ - if exc_traceback is not None: - filename = exc_traceback.tb_next.tb_frame.f_code.co_filename - line_number = exc_traceback.tb_next.tb_lineno - exception.add_note( - f'Exception occurred in {function.__name__} ' - f'of {filename} ' - f'at line number {line_number}.' - ) - - return exception +_ExceptionType = TypeVar("_ExceptionType", bound=Exception) @overload From 31e396119c5ab5521cfa4d112ec368d2d0dbac2a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 12 Apr 2025 11:25:45 +0000 Subject: [PATCH 8/8] [pre-commit.ci] auto fixes from pre-commit.com hooks --- returns/future.py | 126 +++++++++++++++---------------- returns/io.py | 84 ++++++++++----------- returns/primitives/exceptions.py | 19 ++--- returns/result.py | 50 ++++++------ 4 files changed, 137 insertions(+), 142 deletions(-) diff --git a/returns/future.py b/returns/future.py index 5cc137ad8..2e95946b8 100644 --- a/returns/future.py +++ b/returns/future.py @@ -31,16 +31,16 @@ from returns.result import Failure, Result, Success # Definitions: -_ValueType_co = TypeVar("_ValueType_co", covariant=True) -_NewValueType = TypeVar("_NewValueType") -_ErrorType_co = TypeVar("_ErrorType_co", covariant=True) -_NewErrorType = TypeVar("_NewErrorType") +_ValueType_co = TypeVar('_ValueType_co', covariant=True) +_NewValueType = TypeVar('_NewValueType') +_ErrorType_co = TypeVar('_ErrorType_co', covariant=True) +_NewErrorType = TypeVar('_NewErrorType') -_FuncParams = ParamSpec("_FuncParams") +_FuncParams = ParamSpec('_FuncParams') # Aliases: -_FirstType = TypeVar("_FirstType") -_SecondType = TypeVar("_SecondType") +_FirstType = TypeVar('_FirstType') +_SecondType = TypeVar('_SecondType') # Public composition helpers: @@ -70,7 +70,7 @@ async def async_identity(instance: _FirstType) -> _FirstType: # noqa: RUF029 @final class Future( # type: ignore[type-var] BaseContainer, - SupportsKind1["Future", _ValueType_co], + SupportsKind1['Future', _ValueType_co], FutureBased1[_ValueType_co], ): """ @@ -175,7 +175,7 @@ async def awaitable(self) -> IO[_ValueType_co]: def map( self, function: Callable[[_ValueType_co], _NewValueType], - ) -> "Future[_NewValueType]": + ) -> 'Future[_NewValueType]': """ Applies function to the inner value. @@ -202,8 +202,8 @@ def map( def apply( self, - container: Kind1["Future", Callable[[_ValueType_co], _NewValueType]], - ) -> "Future[_NewValueType]": + container: Kind1['Future', Callable[[_ValueType_co], _NewValueType]], + ) -> 'Future[_NewValueType]': """ Calls a wrapped function in a container on this container. @@ -222,14 +222,12 @@ def apply( ... ) == IO('1b') """ - return Future( - _future.async_apply(dekind(container), self._inner_value) - ) + return Future(_future.async_apply(dekind(container), self._inner_value)) def bind( self, - function: Callable[[_ValueType_co], Kind1["Future", _NewValueType]], - ) -> "Future[_NewValueType]": + function: Callable[[_ValueType_co], Kind1['Future', _NewValueType]], + ) -> 'Future[_NewValueType]': """ Applies 'function' to the result of a previous calculation. @@ -259,9 +257,9 @@ def bind_async( self, function: Callable[ [_ValueType_co], - Awaitable[Kind1["Future", _NewValueType]], + Awaitable[Kind1['Future', _NewValueType]], ], - ) -> "Future[_NewValueType]": + ) -> 'Future[_NewValueType]': """ Compose a container and ``async`` function returning a container. @@ -290,8 +288,8 @@ def bind_async( def bind_awaitable( self, - function: Callable[[_ValueType_co], "Awaitable[_NewValueType]"], - ) -> "Future[_NewValueType]": + function: Callable[[_ValueType_co], 'Awaitable[_NewValueType]'], + ) -> 'Future[_NewValueType]': """ Allows to compose a container and a regular ``async`` function. @@ -323,7 +321,7 @@ def bind_awaitable( def bind_io( self, function: Callable[[_ValueType_co], IO[_NewValueType]], - ) -> "Future[_NewValueType]": + ) -> 'Future[_NewValueType]': """ Applies 'function' to the result of a previous calculation. @@ -358,7 +356,7 @@ async def factory() -> AsyncGenerator[_ValueType_co, None]: def do( cls, expr: AsyncGenerator[_NewValueType, None], - ) -> "Future[_NewValueType]": + ) -> 'Future[_NewValueType]': """ Allows working with unwrapped values of containers in a safe way. @@ -387,7 +385,7 @@ async def factory() -> _NewValueType: return Future(factory()) @classmethod - def from_value(cls, inner_value: _NewValueType) -> "Future[_NewValueType]": + def from_value(cls, inner_value: _NewValueType) -> 'Future[_NewValueType]': """ Allows to create a ``Future`` from a plain value. @@ -411,8 +409,8 @@ def from_value(cls, inner_value: _NewValueType) -> "Future[_NewValueType]": @classmethod def from_future( cls, - inner_value: "Future[_NewValueType]", - ) -> "Future[_NewValueType]": + inner_value: 'Future[_NewValueType]', + ) -> 'Future[_NewValueType]': """ Creates a new ``Future`` from the existing one. @@ -430,9 +428,7 @@ def from_future( return inner_value @classmethod - def from_io( - cls, inner_value: IO[_NewValueType] - ) -> "Future[_NewValueType]": + def from_io(cls, inner_value: IO[_NewValueType]) -> 'Future[_NewValueType]': """ Allows to create a ``Future`` from ``IO`` container. @@ -453,8 +449,8 @@ def from_io( @classmethod def from_future_result( cls, - inner_value: "FutureResult[_NewValueType, _NewErrorType]", - ) -> "Future[Result[_NewValueType, _NewErrorType]]": + inner_value: 'FutureResult[_NewValueType, _NewErrorType]', + ) -> 'Future[Result[_NewValueType, _NewErrorType]]': """ Creates ``Future[Result[a, b]]`` instance from ``FutureResult[a, b]``. @@ -562,7 +558,7 @@ async def decorator( # noqa: RUF029 @final class FutureResult( # type: ignore[type-var] BaseContainer, - SupportsKind2["FutureResult", _ValueType_co, _ErrorType_co], + SupportsKind2['FutureResult', _ValueType_co, _ErrorType_co], FutureResultBased2[_ValueType_co, _ErrorType_co], ): """ @@ -678,7 +674,7 @@ async def awaitable(self) -> IOResult[_ValueType_co, _ErrorType_co]: """ return IOResult.from_result(await self._inner_value) - def swap(self) -> "FutureResult[_ErrorType_co, _ValueType_co]": + def swap(self) -> 'FutureResult[_ErrorType_co, _ValueType_co]': """ Swaps value and error types. @@ -703,7 +699,7 @@ def swap(self) -> "FutureResult[_ErrorType_co, _ValueType_co]": def map( self, function: Callable[[_ValueType_co], _NewValueType], - ) -> "FutureResult[_NewValueType, _ErrorType_co]": + ) -> 'FutureResult[_NewValueType, _ErrorType_co]': """ Applies function to the inner value. @@ -739,11 +735,11 @@ def map( def apply( self, container: Kind2[ - "FutureResult", + 'FutureResult', Callable[[_ValueType_co], _NewValueType], _ErrorType_co, ], - ) -> "FutureResult[_NewValueType, _ErrorType_co]": + ) -> 'FutureResult[_NewValueType, _ErrorType_co]': """ Calls a wrapped function in a container on this container. @@ -790,9 +786,9 @@ def bind( self, function: Callable[ [_ValueType_co], - Kind2["FutureResult", _NewValueType, _ErrorType_co], + Kind2['FutureResult', _NewValueType, _ErrorType_co], ], - ) -> "FutureResult[_NewValueType, _ErrorType_co]": + ) -> 'FutureResult[_NewValueType, _ErrorType_co]': """ Applies 'function' to the result of a previous calculation. @@ -831,9 +827,9 @@ def bind_async( self, function: Callable[ [_ValueType_co], - Awaitable[Kind2["FutureResult", _NewValueType, _ErrorType_co]], + Awaitable[Kind2['FutureResult', _NewValueType, _ErrorType_co]], ], - ) -> "FutureResult[_NewValueType, _ErrorType_co]": + ) -> 'FutureResult[_NewValueType, _ErrorType_co]': """ Composes a container and ``async`` function returning container. @@ -872,7 +868,7 @@ def bind_async( def bind_awaitable( self, function: Callable[[_ValueType_co], Awaitable[_NewValueType]], - ) -> "FutureResult[_NewValueType, _ErrorType_co]": + ) -> 'FutureResult[_NewValueType, _ErrorType_co]': """ Allows to compose a container and a regular ``async`` function. @@ -909,7 +905,7 @@ def bind_result( function: Callable[ [_ValueType_co], Result[_NewValueType, _ErrorType_co] ], - ) -> "FutureResult[_NewValueType, _ErrorType_co]": + ) -> 'FutureResult[_NewValueType, _ErrorType_co]': """ Binds a function returning ``Result[a, b]`` container. @@ -943,7 +939,7 @@ def bind_ioresult( function: Callable[ [_ValueType_co], IOResult[_NewValueType, _ErrorType_co] ], - ) -> "FutureResult[_NewValueType, _ErrorType_co]": + ) -> 'FutureResult[_NewValueType, _ErrorType_co]': """ Binds a function returning ``IOResult[a, b]`` container. @@ -974,7 +970,7 @@ def bind_ioresult( def bind_io( self, function: Callable[[_ValueType_co], IO[_NewValueType]], - ) -> "FutureResult[_NewValueType, _ErrorType_co]": + ) -> 'FutureResult[_NewValueType, _ErrorType_co]': """ Binds a function returning ``IO[a]`` container. @@ -1005,7 +1001,7 @@ def bind_io( def bind_future( self, function: Callable[[_ValueType_co], Future[_NewValueType]], - ) -> "FutureResult[_NewValueType, _ErrorType_co]": + ) -> 'FutureResult[_NewValueType, _ErrorType_co]': """ Binds a function returning ``Future[a]`` container. @@ -1035,10 +1031,8 @@ def bind_future( def bind_async_future( self, - function: Callable[ - [_ValueType_co], Awaitable["Future[_NewValueType]"] - ], - ) -> "FutureResult[_NewValueType, _ErrorType_co]": + function: Callable[[_ValueType_co], Awaitable['Future[_NewValueType]']], + ) -> 'FutureResult[_NewValueType, _ErrorType_co]': """ Composes a container and ``async`` function returning ``Future``. @@ -1074,7 +1068,7 @@ def bind_async_future( def alt( self, function: Callable[[_ErrorType_co], _NewErrorType], - ) -> "FutureResult[_ValueType_co, _NewErrorType]": + ) -> 'FutureResult[_ValueType_co, _NewErrorType]': """ Composes failed container with a pure function to modify failure. @@ -1106,9 +1100,9 @@ def lash( self, function: Callable[ [_ErrorType_co], - Kind2["FutureResult", _ValueType_co, _NewErrorType], + Kind2['FutureResult', _ValueType_co, _NewErrorType], ], - ) -> "FutureResult[_ValueType_co, _NewErrorType]": + ) -> 'FutureResult[_ValueType_co, _NewErrorType]': """ Composes failed container with a function that returns a container. @@ -1140,9 +1134,9 @@ def compose_result( self, function: Callable[ [Result[_ValueType_co, _ErrorType_co]], - Kind2["FutureResult", _NewValueType, _ErrorType_co], + Kind2['FutureResult', _NewValueType, _ErrorType_co], ], - ) -> "FutureResult[_NewValueType, _ErrorType_co]": + ) -> 'FutureResult[_NewValueType, _ErrorType_co]': """ Composes inner ``Result`` with ``FutureResult`` returning function. @@ -1188,7 +1182,7 @@ async def factory() -> AsyncGenerator[_ValueType_co, None]: def do( cls, expr: AsyncGenerator[_NewValueType, None], - ) -> "FutureResult[_NewValueType, _NewErrorType]": + ) -> 'FutureResult[_NewValueType, _NewErrorType]': """ Allows working with unwrapped values of containers in a safe way. @@ -1232,7 +1226,7 @@ async def factory() -> Result[_NewValueType, _NewErrorType]: def from_typecast( cls, inner_value: Future[Result[_NewValueType, _NewErrorType]], - ) -> "FutureResult[_NewValueType, _NewErrorType]": + ) -> 'FutureResult[_NewValueType, _NewErrorType]': """ Creates ``FutureResult[a, b]`` from ``Future[Result[a, b]]``. @@ -1260,7 +1254,7 @@ def from_typecast( def from_future( cls, inner_value: Future[_NewValueType], - ) -> "FutureResult[_NewValueType, Any]": + ) -> 'FutureResult[_NewValueType, Any]': """ Creates ``FutureResult`` from successful ``Future`` value. @@ -1284,7 +1278,7 @@ def from_future( def from_failed_future( cls, inner_value: Future[_NewErrorType], - ) -> "FutureResult[Any, _NewErrorType]": + ) -> 'FutureResult[Any, _NewErrorType]': """ Creates ``FutureResult`` from failed ``Future`` value. @@ -1307,8 +1301,8 @@ def from_failed_future( @classmethod def from_future_result( cls, - inner_value: "FutureResult[_NewValueType, _NewErrorType]", - ) -> "FutureResult[_NewValueType, _NewErrorType]": + inner_value: 'FutureResult[_NewValueType, _NewErrorType]', + ) -> 'FutureResult[_NewValueType, _NewErrorType]': """ Creates new ``FutureResult`` from existing one. @@ -1333,7 +1327,7 @@ def from_future_result( def from_io( cls, inner_value: IO[_NewValueType], - ) -> "FutureResult[_NewValueType, Any]": + ) -> 'FutureResult[_NewValueType, Any]': """ Creates ``FutureResult`` from successful ``IO`` value. @@ -1357,7 +1351,7 @@ def from_io( def from_failed_io( cls, inner_value: IO[_NewErrorType], - ) -> "FutureResult[Any, _NewErrorType]": + ) -> 'FutureResult[Any, _NewErrorType]': """ Creates ``FutureResult`` from failed ``IO`` value. @@ -1381,7 +1375,7 @@ def from_failed_io( def from_ioresult( cls, inner_value: IOResult[_NewValueType, _NewErrorType], - ) -> "FutureResult[_NewValueType, _NewErrorType]": + ) -> 'FutureResult[_NewValueType, _NewErrorType]': """ Creates ``FutureResult`` from ``IOResult`` value. @@ -1408,7 +1402,7 @@ def from_ioresult( def from_result( cls, inner_value: Result[_NewValueType, _NewErrorType], - ) -> "FutureResult[_NewValueType, _NewErrorType]": + ) -> 'FutureResult[_NewValueType, _NewErrorType]': """ Creates ``FutureResult`` from ``Result`` value. @@ -1436,7 +1430,7 @@ def from_result( def from_value( cls, inner_value: _NewValueType, - ) -> "FutureResult[_NewValueType, Any]": + ) -> 'FutureResult[_NewValueType, Any]': """ Creates ``FutureResult`` from successful value. @@ -1460,7 +1454,7 @@ def from_value( def from_failure( cls, inner_value: _NewErrorType, - ) -> "FutureResult[Any, _NewErrorType]": + ) -> 'FutureResult[Any, _NewErrorType]': """ Creates ``FutureResult`` from failed value. @@ -1527,7 +1521,7 @@ def FutureFailure( # noqa: N802 FutureResultE: TypeAlias = FutureResult[_ValueType_co, Exception] -_ExceptionType = TypeVar("_ExceptionType", bound=Exception) +_ExceptionType = TypeVar('_ExceptionType', bound=Exception) # Decorators: diff --git a/returns/io.py b/returns/io.py index 632a16712..09d7c5df1 100644 --- a/returns/io.py +++ b/returns/io.py @@ -21,19 +21,19 @@ ) from returns.result import Failure, Result, Success -_ValueType_co = TypeVar("_ValueType_co", covariant=True) -_NewValueType = TypeVar("_NewValueType") +_ValueType_co = TypeVar('_ValueType_co', covariant=True) +_NewValueType = TypeVar('_NewValueType') -_FuncParams = ParamSpec("_FuncParams") +_FuncParams = ParamSpec('_FuncParams') # Result related: -_ErrorType_co = TypeVar("_ErrorType_co", covariant=True) -_NewErrorType = TypeVar("_NewErrorType") +_ErrorType_co = TypeVar('_ErrorType_co', covariant=True) +_NewErrorType = TypeVar('_NewErrorType') class IO( # type: ignore[type-var] BaseContainer, - SupportsKind1["IO", _ValueType_co], + SupportsKind1['IO', _ValueType_co], io.IOLike1[_ValueType_co], ): """ @@ -83,7 +83,7 @@ def __init__(self, inner_value: _ValueType_co) -> None: def map( self, function: Callable[[_ValueType_co], _NewValueType], - ) -> "IO[_NewValueType]": + ) -> 'IO[_NewValueType]': """ Applies function to the inner value. @@ -104,8 +104,8 @@ def map( def apply( self, - container: Kind1["IO", Callable[[_ValueType_co], _NewValueType]], - ) -> "IO[_NewValueType]": + container: Kind1['IO', Callable[[_ValueType_co], _NewValueType]], + ) -> 'IO[_NewValueType]': """ Calls a wrapped function in a container on this container. @@ -132,8 +132,8 @@ def apply( def bind( self, - function: Callable[[_ValueType_co], Kind1["IO", _NewValueType]], - ) -> "IO[_NewValueType]": + function: Callable[[_ValueType_co], Kind1['IO', _NewValueType]], + ) -> 'IO[_NewValueType]': """ Applies 'function' to the result of a previous calculation. @@ -161,7 +161,7 @@ def __iter__(self) -> Iterator[_ValueType_co]: def do( cls, expr: Generator[_NewValueType, None, None], - ) -> "IO[_NewValueType]": + ) -> 'IO[_NewValueType]': """ Allows working with unwrapped values of containers in a safe way. @@ -180,7 +180,7 @@ def do( return IO(next(expr)) @classmethod - def from_value(cls, inner_value: _NewValueType) -> "IO[_NewValueType]": + def from_value(cls, inner_value: _NewValueType) -> 'IO[_NewValueType]': """ Unit function to construct new ``IO`` values. @@ -197,7 +197,7 @@ def from_value(cls, inner_value: _NewValueType) -> "IO[_NewValueType]": return IO(inner_value) @classmethod - def from_io(cls, inner_value: "IO[_NewValueType]") -> "IO[_NewValueType]": + def from_io(cls, inner_value: 'IO[_NewValueType]') -> 'IO[_NewValueType]': """ Unit function to construct new ``IO`` values from existing ``IO``. @@ -214,8 +214,8 @@ def from_io(cls, inner_value: "IO[_NewValueType]") -> "IO[_NewValueType]": @classmethod def from_ioresult( cls, - inner_value: "IOResult[_NewValueType, _NewErrorType]", - ) -> "IO[Result[_NewValueType, _NewErrorType]]": + inner_value: 'IOResult[_NewValueType, _NewErrorType]', + ) -> 'IO[Result[_NewValueType, _NewErrorType]]': """ Converts ``IOResult[a, b]`` back to ``IO[Result[a, b]]``. @@ -273,7 +273,7 @@ def decorator( class IOResult( # type: ignore[type-var] BaseContainer, - SupportsKind2["IOResult", _ValueType_co, _ErrorType_co], + SupportsKind2['IOResult', _ValueType_co, _ErrorType_co], ioresult.IOResultBased2[_ValueType_co, _ErrorType_co], ABC, ): @@ -335,7 +335,7 @@ class IOResult( # type: ignore[type-var] __slots__ = () _inner_value: Result[_ValueType_co, _ErrorType_co] - __match_args__ = ("_inner_value",) + __match_args__ = ('_inner_value',) #: Typesafe equality comparison with other `IOResult` objects. equals = container_equality @@ -364,14 +364,14 @@ def __repr__(self) -> str: '>' """ - return f"" + return f'' @property def trace(self) -> list[FrameInfo] | None: """Returns a stack trace when :func:`~IOFailure` was called.""" return self._inner_value.trace - def swap(self) -> "IOResult[_ErrorType_co, _ValueType_co]": + def swap(self) -> 'IOResult[_ErrorType_co, _ValueType_co]': """ Swaps value and error types. @@ -392,7 +392,7 @@ def swap(self) -> "IOResult[_ErrorType_co, _ValueType_co]": def map( self, function: Callable[[_ValueType_co], _NewValueType], - ) -> "IOResult[_NewValueType, _ErrorType_co]": + ) -> 'IOResult[_NewValueType, _ErrorType_co]': """ Composes successful container with a pure function. @@ -407,11 +407,11 @@ def map( def apply( self, container: Kind2[ - "IOResult", + 'IOResult', Callable[[_ValueType_co], _NewValueType], _ErrorType_co, ], - ) -> "IOResult[_NewValueType, _ErrorType_co]": + ) -> 'IOResult[_NewValueType, _ErrorType_co]': """ Calls a wrapped function in a container on this container. @@ -447,9 +447,9 @@ def bind( self, function: Callable[ [_ValueType_co], - Kind2["IOResult", _NewValueType, _ErrorType_co], + Kind2['IOResult', _NewValueType, _ErrorType_co], ], - ) -> "IOResult[_NewValueType, _ErrorType_co]": + ) -> 'IOResult[_NewValueType, _ErrorType_co]': """ Composes successful container with a function that returns a container. @@ -476,7 +476,7 @@ def bind_result( [_ValueType_co], Result[_NewValueType, _ErrorType_co], ], - ) -> "IOResult[_NewValueType, _ErrorType_co]": + ) -> 'IOResult[_NewValueType, _ErrorType_co]': """ Composes successful container with a function that returns a container. @@ -503,7 +503,7 @@ def bind_result( def bind_io( self, function: Callable[[_ValueType_co], IO[_NewValueType]], - ) -> "IOResult[_NewValueType, _ErrorType_co]": + ) -> 'IOResult[_NewValueType, _ErrorType_co]': """ Composes successful container with a function that returns a container. @@ -525,7 +525,7 @@ def bind_io( def alt( self, function: Callable[[_ErrorType_co], _NewErrorType], - ) -> "IOResult[_ValueType_co, _NewErrorType]": + ) -> 'IOResult[_ValueType_co, _NewErrorType]': """ Composes failed container with a pure function to modify failure. @@ -541,9 +541,9 @@ def lash( self, function: Callable[ [_ErrorType_co], - Kind2["IOResult", _ValueType_co, _NewErrorType], + Kind2['IOResult', _ValueType_co, _NewErrorType], ], - ) -> "IOResult[_ValueType_co, _NewErrorType]": + ) -> 'IOResult[_ValueType_co, _NewErrorType]': """ Composes failed container with a function that returns a container. @@ -617,9 +617,9 @@ def compose_result( self, function: Callable[ [Result[_ValueType_co, _ErrorType_co]], - Kind2["IOResult", _NewValueType, _ErrorType_co], + Kind2['IOResult', _NewValueType, _ErrorType_co], ], - ) -> "IOResult[_NewValueType, _ErrorType_co]": + ) -> 'IOResult[_NewValueType, _ErrorType_co]': """ Composes inner ``Result`` with ``IOResult`` returning function. @@ -650,7 +650,7 @@ def __iter__(self) -> Iterator[_ValueType_co]: def do( cls, expr: Generator[_NewValueType, None, None], - ) -> "IOResult[_NewValueType, _NewErrorType]": + ) -> 'IOResult[_NewValueType, _NewErrorType]': """ Allows working with unwrapped values of containers in a safe way. @@ -683,7 +683,7 @@ def do( def from_typecast( cls, inner_value: IO[Result[_NewValueType, _NewErrorType]], - ) -> "IOResult[_NewValueType, _NewErrorType]": + ) -> 'IOResult[_NewValueType, _NewErrorType]': """ Converts ``IO[Result[_ValueType_co, _ErrorType_co]]`` to ``IOResult``. @@ -706,7 +706,7 @@ def from_typecast( def from_failed_io( cls, inner_value: IO[_NewErrorType], - ) -> "IOResult[Any, _NewErrorType]": + ) -> 'IOResult[Any, _NewErrorType]': """ Creates new ``IOResult`` from "failed" ``IO`` container. @@ -723,7 +723,7 @@ def from_failed_io( def from_io( cls, inner_value: IO[_NewValueType], - ) -> "IOResult[_NewValueType, Any]": + ) -> 'IOResult[_NewValueType, Any]': """ Creates new ``IOResult`` from "successful" ``IO`` container. @@ -740,7 +740,7 @@ def from_io( def from_result( cls, inner_value: Result[_NewValueType, _NewErrorType], - ) -> "IOResult[_NewValueType, _NewErrorType]": + ) -> 'IOResult[_NewValueType, _NewErrorType]': """ Creates ``IOResult`` from ``Result`` value. @@ -760,8 +760,8 @@ def from_result( @classmethod def from_ioresult( cls, - inner_value: "IOResult[_NewValueType, _NewErrorType]", - ) -> "IOResult[_NewValueType, _NewErrorType]": + inner_value: 'IOResult[_NewValueType, _NewErrorType]', + ) -> 'IOResult[_NewValueType, _NewErrorType]': """ Creates ``IOResult`` from existing ``IOResult`` value. @@ -779,7 +779,7 @@ def from_ioresult( def from_value( cls, inner_value: _NewValueType, - ) -> "IOResult[_NewValueType, Any]": + ) -> 'IOResult[_NewValueType, Any]': """ One more value to create success unit values. @@ -800,7 +800,7 @@ def from_value( def from_failure( cls, inner_value: _NewErrorType, - ) -> "IOResult[Any, _NewErrorType]": + ) -> 'IOResult[Any, _NewErrorType]': """ One more value to create failure unit values. @@ -895,7 +895,7 @@ def lash(self, function): # impure_safe decorator: -_ExceptionType = TypeVar("_ExceptionType", bound=Exception) +_ExceptionType = TypeVar('_ExceptionType', bound=Exception) @overload diff --git a/returns/primitives/exceptions.py b/returns/primitives/exceptions.py index 01eaab38b..2648cb471 100644 --- a/returns/primitives/exceptions.py +++ b/returns/primitives/exceptions.py @@ -1,21 +1,23 @@ from __future__ import annotations -from typing import TYPE_CHECKING, TypeVar, Callable +from collections.abc import Callable +from typing import TYPE_CHECKING, TypeVar + from typing_extensions import ParamSpec if TYPE_CHECKING: from returns.interfaces.unwrappable import Unwrappable # noqa: WPS433 -_ValueType_co = TypeVar("_ValueType_co", covariant=True) -_FuncParams = ParamSpec("_FuncParams") -_ExceptionType = TypeVar("_ExceptionType", bound=Exception) +_ValueType_co = TypeVar('_ValueType_co', covariant=True) +_FuncParams = ParamSpec('_FuncParams') +_ExceptionType = TypeVar('_ExceptionType', bound=Exception) class UnwrapFailedError(Exception): """Raised when a container can not be unwrapped into a meaningful value.""" - __slots__ = ("halted_container",) + __slots__ = ('halted_container',) def __init__(self, container: Unwrappable) -> None: """ @@ -63,7 +65,6 @@ def add_note_to_exception( function name to the exception. If a custom message is provided, it will be added as an additional note to the exception. """ - if not message: return exception @@ -83,9 +84,9 @@ def add_note_to_exception( filename = exc_traceback.tb_next.tb_frame.f_code.co_filename line_number = exc_traceback.tb_next.tb_lineno exception.add_note( - f"Exception occurred in {function.__name__} " - f"of {filename} " - f"at line number {line_number}." + f'Exception occurred in {function.__name__} ' + f'of {filename} ' + f'at line number {line_number}.' ) return exception diff --git a/returns/result.py b/returns/result.py index 7cfd37a90..57c70c135 100644 --- a/returns/result.py +++ b/returns/result.py @@ -15,18 +15,18 @@ from returns.primitives.hkt import Kind2, SupportsKind2 # Definitions: -_ValueType_co = TypeVar("_ValueType_co", covariant=True) -_NewValueType = TypeVar("_NewValueType") -_ErrorType_co = TypeVar("_ErrorType_co", covariant=True) -_NewErrorType = TypeVar("_NewErrorType") +_ValueType_co = TypeVar('_ValueType_co', covariant=True) +_NewValueType = TypeVar('_NewValueType') +_ErrorType_co = TypeVar('_ErrorType_co', covariant=True) +_NewErrorType = TypeVar('_NewErrorType') -_FirstType = TypeVar("_FirstType") -_FuncParams = ParamSpec("_FuncParams") +_FirstType = TypeVar('_FirstType') +_FuncParams = ParamSpec('_FuncParams') class Result( # type: ignore[type-var] BaseContainer, - SupportsKind2["Result", _ValueType_co, _ErrorType_co], + SupportsKind2['Result', _ValueType_co, _ErrorType_co], result.ResultBased2[_ValueType_co, _ErrorType_co], ABC, ): @@ -42,8 +42,8 @@ class Result( # type: ignore[type-var] """ - __slots__ = ("_trace",) - __match_args__ = ("_inner_value",) + __slots__ = ('_trace',) + __match_args__ = ('_inner_value',) _inner_value: _ValueType_co | _ErrorType_co _trace: list[FrameInfo] | None @@ -56,7 +56,7 @@ def trace(self) -> list[FrameInfo] | None: """Returns a list with stack trace when :func:`~Failure` was called.""" return self._trace - def swap(self) -> "Result[_ErrorType_co, _ValueType_co]": + def swap(self) -> 'Result[_ErrorType_co, _ValueType_co]': """ Swaps value and error types. @@ -76,7 +76,7 @@ def swap(self) -> "Result[_ErrorType_co, _ValueType_co]": def map( self, function: Callable[[_ValueType_co], _NewValueType], - ) -> "Result[_NewValueType, _ErrorType_co]": + ) -> 'Result[_NewValueType, _ErrorType_co]': """ Composes successful container with a pure function. @@ -95,11 +95,11 @@ def map( def apply( self, container: Kind2[ - "Result", + 'Result', Callable[[_ValueType_co], _NewValueType], _ErrorType_co, ], - ) -> "Result[_NewValueType, _ErrorType_co]": + ) -> 'Result[_NewValueType, _ErrorType_co]': """ Calls a wrapped function in a container on this container. @@ -122,9 +122,9 @@ def bind( self, function: Callable[ [_ValueType_co], - Kind2["Result", _NewValueType, _ErrorType_co], + Kind2['Result', _NewValueType, _ErrorType_co], ], - ) -> "Result[_NewValueType, _ErrorType_co]": + ) -> 'Result[_NewValueType, _ErrorType_co]': """ Composes successful container with a function that returns a container. @@ -149,7 +149,7 @@ def bind( def alt( self, function: Callable[[_ErrorType_co], _NewErrorType], - ) -> "Result[_ValueType_co, _NewErrorType]": + ) -> 'Result[_ValueType_co, _NewErrorType]': """ Composes failed container with a pure function to modify failure. @@ -169,9 +169,9 @@ def lash( self, function: Callable[ [_ErrorType_co], - Kind2["Result", _ValueType_co, _NewErrorType], + Kind2['Result', _ValueType_co, _NewErrorType], ], - ) -> "Result[_ValueType_co, _NewErrorType]": + ) -> 'Result[_ValueType_co, _NewErrorType]': """ Composes failed container with a function that returns a container. @@ -198,7 +198,7 @@ def __iter__(self) -> Iterator[_ValueType_co]: def do( cls, expr: Generator[_NewValueType, None, None], - ) -> "Result[_NewValueType, _NewErrorType]": + ) -> 'Result[_NewValueType, _NewErrorType]': """ Allows working with unwrapped values of containers in a safe way. @@ -280,7 +280,7 @@ def failure(self) -> _ErrorType_co: def from_value( cls, inner_value: _NewValueType, - ) -> "Result[_NewValueType, Any]": + ) -> 'Result[_NewValueType, Any]': """ One more value to create success unit values. @@ -301,7 +301,7 @@ def from_value( def from_failure( cls, inner_value: _NewErrorType, - ) -> "Result[Any, _NewErrorType]": + ) -> 'Result[Any, _NewErrorType]': """ One more value to create failure unit values. @@ -321,8 +321,8 @@ def from_failure( @classmethod def from_result( cls, - inner_value: "Result[_NewValueType, _NewErrorType]", - ) -> "Result[_NewValueType, _NewErrorType]": + inner_value: 'Result[_NewValueType, _NewErrorType]', + ) -> 'Result[_NewValueType, _NewErrorType]': """ Creates a new ``Result`` instance from existing ``Result`` instance. @@ -353,7 +353,7 @@ class Failure(Result[Any, _ErrorType_co]): # noqa: WPS338 def __init__(self, inner_value: _ErrorType_co) -> None: """Failure constructor.""" super().__init__(inner_value) - object.__setattr__(self, "_trace", self._get_trace()) + object.__setattr__(self, '_trace', self._get_trace()) if not TYPE_CHECKING: # noqa: WPS604 # pragma: no branch @@ -469,7 +469,7 @@ def failure(self) -> Never: # Decorators: -_ExceptionType = TypeVar("_ExceptionType", bound=Exception) +_ExceptionType = TypeVar('_ExceptionType', bound=Exception) @overload