From 286e931a7fa8c467537ccc5dadb600e6c3691431 Mon Sep 17 00:00:00 2001 From: Goffi Date: Wed, 1 Oct 2025 17:57:47 +0200 Subject: [PATCH] tools (utils): Add `maybe_async` function: `maybe_async` can be used to call a function which may be or not `async`. It doesn't use `defer.ensureDeferred` contrary to `ad_defered`, so the call stack is not lost, and it's easier to debug when an Exception is raised. --- libervia/backend/tools/utils.py | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) 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