Click: Asyncio integration

Created on 16 May 2014  路  8Comments  路  Source: pallets/click

It would be nice if I could do:

@click.command()
@ascyncio.coroutine
def command():
    yield from whatever

I think the only place that should be changed to make that work would be Context.invoke (check if function is coroutine, do loop.run_until_complete()). But it's hard to subclass context in current click.

Would you mind making this functionality built-in?

Most helpful comment

This is an updated version making use of asyncio.run() from Python 3.7:

import asyncio
from functools import wraps

def coro(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        return asyncio.run(f(*args, **kwargs))

    return wrapper

Usage:

@click.command()
@coro
async def command():
    await asyncio.sleep(1)
    click.echo("Delayed hello")

All 8 comments

Why does that have to be in click? Just have a custom decorator that does asyncio.coroutine + whatever wrapper logic necessary. Eg:

import asyncio
from functools import update_wrapper

def coro(f):
    f = asyncio.coroutine(f)
    def wrapper(*args, **kwargs):
        loop = asyncio.get_event_loop()
        return loop.run_until_complete(f(*args, **kwargs))
    return update_wrapper(wrapper, f)

Would be used like this then:

@click.command()
@coro
def command():
    yield from whatever

For more complex cases you need to accept the loop anyways or get it from somewhere.

Generally I don't want to put builtin support for asyncio into click. It really does not belong there and by pure user count someone will come and ask for twisted or whatever support next :)

Ah, Thanks. I don't know why I didn't find this solution before :)

Just curious if this is still the recommended practice now that async/await is a first-class language feature.

I feel a little dirty having to redundantly decorate all my commands with a @coroutine wrapper as well as a async def. Or maybe theirs a neater way that I've not thought of where I don't need to decorate each command.

Thanks - minor thing I know.

Just curious if this is still the recommended practice now that async/await is a first-class language feature.

I feel a little dirty having to redundantly decorate all my commands with a @coroutine wrapper as well as a async def. Or maybe theirs a neater way that I've not thought of where I don't need to decorate each command.

Thanks - minor thing I know.

Any update on this? Is it possible to pass an async generator into a progressbar as iterable for example?

Can this issue be re-opened and considered?

This is an updated version making use of asyncio.run() from Python 3.7:

import asyncio
from functools import wraps

def coro(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        return asyncio.run(f(*args, **kwargs))

    return wrapper

Usage:

@click.command()
@coro
async def command():
    await asyncio.sleep(1)
    click.echo("Delayed hello")

BTW, there is the fork of click, which allows to use it with asyncio https://github.com/python-trio/trio-click

New click user here. Thanks for this great tool.

I naively tried decorating a coroutine with @click.command() and received the cryptic error:

sys:1: RuntimeWarning: coroutine 'main' was never awaited

I believe it would be reasonable for @click.command() to examine the function it is decorating and, if a coroutine is found, automatically wrap it with the @coro decorator suggested above. In the meantime, the workaround worked great.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mahmoudimus picture mahmoudimus  路  25Comments

Diaoul picture Diaoul  路  27Comments

ivankravets picture ivankravets  路  27Comments

mchwalisz picture mchwalisz  路  14Comments

cyeb picture cyeb  路  12Comments