Using a coroutine as decorator

Question:

in this scenario:

async def foo(f):
    async def wrapper(*args, **kwargs):
        return f(*args, **kwargs)
    return wrapper

@foo
async def boo(*args, **kwargs):
    pass

is the call to foo as a decorator for boo decorator an async call?

–First Edit:
Also how does one handle calling chain of coroutines as decorators?

Asked By: Juggernaut

||

Answers:

Thanks to @blacknght’s comment, considering

def foo():
    def wrapper(func):
        @functools.wraps(func)
        async def wrapped(*args):
             # Some fancy foo stuff
            return await func(*args)
        return wrapped
    return wrapper

and

def boo():
    def wrapper(func):
        @functools.wraps(func)
        async def wrapped(*args):
            # Some fancy boo stuff
            return await func(*args)
        return wrapped
    return wrapper

as two decorators, and

@foo()
@boo()
async def work(*args):
    pass

As the foo is wrapping the work coroutine, the key is to await the func(*arg) in both decorators.

Answered By: Juggernaut

Here is an alternate approach using the decorator library (i.e. pip install decorator first):

import asyncio

import decorator


@decorator.decorator
async def decorate_coro(coro, *args, **kwargs):
    try:
        res = await coro(*args, **kwargs)
    except Exception as e:
        print(e)
    else:
        print(res)


@decorate_coro
async def f():
    return 42


@decorate_coro
async def g():
    return 1 / 0


async def main():
    return await asyncio.gather(f(), g())

if __name__ == '__main__':
    asyncio.run(main())

Output:

42
division by zero
Answered By: Kevin Lyons
def foo(f):
    async def wrapper(*args, **kwargs):
        return await f(*args, **kwargs)
    return wrapper

@foo
async def boo(*args, **kwargs):
    pass

Your decorator needs to be a normal function and it will work fine.

When a decorator is evaluated python executes the method with the function as the argument.

@foo
async def boo():
    pass

Evaluates to:

__main__.boo = foo(boo)

If foo is an async function type(main.boo) will be a coroutine object, not a function object. But if foo is a regular synch function it will evaluate right away and main.boo will be the wrapper returned.

Answered By: user11919393
async def foo(f):
  def wrapper(*args, **kwargs):
    # wrapper pre-function stuff
    result = await f(*args, **kwargs) # key is to await function's result
    # wrapper post-function stuff
    return result 
  wrapper.__name__ = f.__name__ # for some reason, async wrappers don't do this
  # do it to avoid an error if you use the wrapper on multiple functions
  return wrapper

The two key changes are to await the function you are wrapping, as it is an async function, and to change the name of the wrapper function so your program isn’t trying to name multiple functions the same thing.

Answered By: Mike Smith
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.