From 1fca6c60c71f6c4dacb243e1f42fccc0f8a05c73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=20Garc=C3=ADa=20Salom=C3=B3n?= Date: Mon, 21 Oct 2024 13:29:10 +0200 Subject: [PATCH] feat: check that async_early_return is applied on async functions (#79) * feat: check that async_early_return is applied on async functions * chore: ignore lint error * chore: reorder checks --- meiga/decorators/async_decoration_error.py | 6 ++++++ meiga/decorators/async_early_return.py | 5 +++++ meiga/handlers.py | 6 ++++-- tests/unit/doc/example_with_meiga.py | 6 ++++-- tests/unit/doc/example_without_meiga.py | 6 ++++-- tests/unit/test_async_early_return.py | 13 +++++++++++++ 6 files changed, 36 insertions(+), 6 deletions(-) create mode 100644 meiga/decorators/async_decoration_error.py diff --git a/meiga/decorators/async_decoration_error.py b/meiga/decorators/async_decoration_error.py new file mode 100644 index 0000000..169e1c9 --- /dev/null +++ b/meiga/decorators/async_decoration_error.py @@ -0,0 +1,6 @@ +from meiga.error import Error + + +class AsyncDecorationError(Error): + def __init__(self) -> None: + self.message = "meiga async decorators must be declared on async functions" diff --git a/meiga/decorators/async_early_return.py b/meiga/decorators/async_early_return.py index b68a29d..882b034 100644 --- a/meiga/decorators/async_early_return.py +++ b/meiga/decorators/async_early_return.py @@ -1,7 +1,10 @@ +import inspect import sys from functools import wraps from typing import Any, Callable, Coroutine, TypeVar, cast +from meiga.decorators.async_decoration_error import AsyncDecorationError + if sys.version_info < (3, 10): # pragma: no cover from typing_extensions import ParamSpec else: @@ -29,6 +32,8 @@ async def _async_early_return(*args: P.args, **kwargs: P.kwargs) -> R: return Failure(UnexpectedDecorationOrderError()) # type: ignore elif isinstance(func, classmethod): return Failure(UnexpectedDecorationOrderError()) # type: ignore + elif not inspect.iscoroutinefunction(func): + return Failure(AsyncDecorationError()) # type: ignore else: return await func(*args, **kwargs) except WaitingForEarlyReturn as exc: diff --git a/meiga/handlers.py b/meiga/handlers.py index aca9c1e..0fd5b18 100644 --- a/meiga/handlers.py +++ b/meiga/handlers.py @@ -27,7 +27,9 @@ def execute(self, result: Result[TS, TF]) -> None: self.func(result.value) -class OnSuccessHandler(Handler): ... +class OnSuccessHandler(Handler): + ... -class OnFailureHandler(Handler): ... +class OnFailureHandler(Handler): + ... diff --git a/tests/unit/doc/example_with_meiga.py b/tests/unit/doc/example_with_meiga.py index 4d20922..2450a22 100644 --- a/tests/unit/doc/example_with_meiga.py +++ b/tests/unit/doc/example_with_meiga.py @@ -3,10 +3,12 @@ from meiga import Error, Failure, Result, Success -class NoSuchKey(Error): ... +class NoSuchKey(Error): + ... -class TypeMismatch(Error): ... +class TypeMismatch(Error): + ... def string_from_key(dictionary: dict, key: str) -> Result[str, NoSuchKey | TypeMismatch]: diff --git a/tests/unit/doc/example_without_meiga.py b/tests/unit/doc/example_without_meiga.py index d090837..c337b8b 100644 --- a/tests/unit/doc/example_without_meiga.py +++ b/tests/unit/doc/example_without_meiga.py @@ -1,7 +1,9 @@ -class NoSuchKey(Exception): ... +class NoSuchKey(Exception): + ... -class TypeMismatch(Exception): ... +class TypeMismatch(Exception): + ... # This return value masks the behavior of the unhappy path (Exceptions). 🥲 diff --git a/tests/unit/test_async_early_return.py b/tests/unit/test_async_early_return.py index 91e9b9b..c5b99d9 100644 --- a/tests/unit/test_async_early_return.py +++ b/tests/unit/test_async_early_return.py @@ -3,6 +3,7 @@ from meiga import BoolResult, Error, async_early_return, isFailure, isSuccess from meiga.assertions import assert_failure, assert_success from meiga.decorators import UnexpectedDecorationOrderError +from meiga.decorators.async_decoration_error import AsyncDecorationError @pytest.mark.unit @@ -107,3 +108,15 @@ async def decorated_method(cls) -> BoolResult: result = await MyClass.decorated_method() assert_failure(result, value_is_instance_of=UnexpectedDecorationOrderError) assert result.value.message == "meiga decorators must be declared after a @staticmethod, @classmethod" + + async def should_return_an_async_decorator_failure_result_with_async_early_return_on_normal_function( + self, + ): + class MyClass: + @async_early_return + def decorated_method(cls) -> BoolResult: + return isSuccess + + result = await MyClass.decorated_method() + assert_failure(result, value_is_instance_of=AsyncDecorationError) + assert result.value.message == "meiga async decorators must be declared on async functions"