Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Output coroutine functions #41

Closed
Victor-Savu opened this issue May 30, 2017 · 11 comments
Closed

Output coroutine functions #41

Victor-Savu opened this issue May 30, 2017 · 11 comments

Comments

@Victor-Savu
Copy link

It would be great if @decorator could be used to create decorators which produce coroutine functions. Is there already such functionality and I just didn't find it? If not, would this be feasible? If so, are there any plans for this? Would you accept PRs?

Example:

from inspect import iscoroutinefunction
from decorator import decorator

@decorator
async def ensure_coro(function, *args, **kwargs): 
    ret = function(*args, **kwargs)
    if iscoroutinefunction(function):
        ret = await ret
    return ret

@ensure_coro
def foo():
    return 42

assert iscoroutinefunction(foo)
@micheles
Copy link
Owner

The goal of the decorator module is to provide a framework on top of which you can build useful decorators. It is not meant to be a library of decorators, so I cannot accept this kind of contributions. Still, if somebody wants to build a library of decorators on top of my module I am pretty happy with that ;-)

@Victor-Savu
Copy link
Author

Victor-Savu commented May 31, 2017

Thanks for the insights! I had a look at the code, mainly at the workhorse class that creates functions and I am not sure it can be used as is for generating coroutine functions, especially since the regular expression for verifying the function expects it to begin with def and not async def. So I am wondering if it would even be possible to leverage your library for creating such a decorator.

@micheles
Copy link
Owner

micheles commented May 31, 2017

You raise an interesting point. I think that the decorator module should be made able to decorate async functions. I have not done that until now since I still need to support Python 2. But it is something I should think about and work on, when I will have the time. I am reopening the issue.

@micheles micheles reopened this May 31, 2017
@Victor-Savu
Copy link
Author

Thanks! Just to clarify: async functions are decorated more-or-less correctly since calling them produces an awaitable object. The problem is that in the process an important part of their signature is gone: the fact that they are coroutine functions.
I know that Python 2 compatibility requires a lot of jumping through hoops in order to provide support for new Python 3 features, so kudos on your awesome results!

@micheles
Copy link
Owner

micheles commented Jun 3, 2017

I have just pushed in master a change that make decorators preserving the iscoroutinefunction property. Can you test it? It seems to be working with the async def syntax, but not with @asyncio.coroutine.
I do not have code using coroutines, but if you report that it works well I could make a new release 4.1.0
with this new feature.

@Victor-Savu
Copy link
Author

Victor-Savu commented Jun 6, 2017

Thanks for taking this up! The decorated function is now a coroutine function, but it returns another coroutine function, so two awaits are necessary:

from decorator import decorator
from asyncio import get_event_loop
from inspect import iscoroutinefunction

def awt(coro):
    """ A synchronous version for await """
    return get_event_loop().run_until_complete(coro)
 
@decorator
async def foo(coro, *args, **kwargs):
    return "<before>" + (await coro(*args, **kwargs)) + "<after>")

@foo
async def baba(ba):
    return ba

async def main():
    print(iscoroutinefunction(baba))  # prints: True

    print(await baba("banana"))  # prints: <coroutine object foo at 0x7f1dc04fc308>
                                 # and issues: RuntimeWarning: coroutine 'foo' was never awaited

    print(await (await baba("banana"))) # prints: <before>banana<after>

awt(main())

Before the change, one await was enough.

Regarding the @asyncio.coroutine decorator, it is perfectly acceptable since the decorated function would not satisfy iscoroutinefunction anyway:

In [9]: @asyncio.coroutine
   ...: def foo():
   ...:     a = yield from bar()
   ...:     return a
   ...: 

In [10]: iscoroutinefunction(foo)
Out[10]: False

@micheles
Copy link
Owner

micheles commented Jun 6, 2017

I have fixed this in the current master. Let me know if you find other unexpected behaviors, I guess there are corner cases.

@Victor-Savu
Copy link
Author

Thanks! I'll only be able to do a thorough review in the beginning of next week. Would that be OK?

@Victor-Savu
Copy link
Author

I am sorry it took so long! I've had three rather busy weeks. However, I wanted to say that I think the implementation works great and I couldn't find any other problems with it. I am very happy with the solution and I want to thank you very much for your continued work!

@micheles
Copy link
Owner

micheles commented Jul 15, 2017 via email

@Victor-Savu
Copy link
Author

Thanks! I hope you had a nice time at EuroPython! I'll keep an eye open for the recorded sessions on YouTube.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants