diff --git a/libervia/backend/tools/utils.py b/libervia/backend/tools/utils.py index 57a39a89..1569bdd8 100644 --- a/libervia/backend/tools/utils.py +++ b/libervia/backend/tools/utils.py @@ -18,7 +18,7 @@ """various useful methods""" -from typing import Optional, Union +from typing import Any, Optional, Union import unicodedata import os.path import datetime @@ -92,12 +92,13 @@ def partial(func, *fixed_args, **fixed_kwargs): return method - -def as_deferred(func, *args, **kwargs): +def as_deferred(func, *args, **kwargs) -> defer.Deferred: """Call a method and return a Deferred the method can be a simple callable, a Deferred or a coroutine. - It is similar to defer.maybeDeferred, but also handles coroutines + It is similar to defer.maybeDeferred, but also handles coroutines. + Due to the use of ``defer.ensureDeferred``, the call stack may be lost, which can make + debugging harder in case of Exception, the function ``maybe_async`` below solves this. """ try: ret = func(*args, **kwargs) @@ -114,6 +115,26 @@ def as_deferred(func, *args, **kwargs): return defer.succeed(ret) +async def maybe_async(ret: Any) -> Any: + """Check if a return value is awaitable and await it if necessary. + + This function checks whether the return value of a method is awaitable. If it is, the + function awaits on it and returns the awaited value. Otherwise, it returns the value + as-is. + + Unlike ``as_deferred``, this function does not call ``defer.ensureDeferred``, so it + must be used within an already ``async`` context where ``defer.ensureDeferred`` has + already been called at a higher level. This makes debugging easier by preserving the + call stack in case of exceptions. + + @param ret: The return value of a function, which may be awaitable or not. + @return: The awaited value if the input was awaitable, otherwise the input as-is. + """ + if inspect.isawaitable(ret): + return await ret + return ret + + def aio(func): """Decorator to return a Deferred from asyncio coroutine