wrapt icon indicating copy to clipboard operation
wrapt copied to clipboard

Can we propagate the name from a coroutine function to the resulting coroutine?

Open Tinche opened this issue 3 years ago • 3 comments

Hello!

I've come up against this while working on an asyncio job library. Maybe it's best if I paste a code example.

from wrapt import decorator


@decorator
async def dec(wrapped, instance, args, kwargs):
    return await wrapped(*args, **kwargs)


@dec
async def coroutine_func():
    pass

coroutine_func.__name__  # is 'coroutine_func' as expected
coroutine_func().__name__  # is 'dec' instead of 'coroutine_func'

Can anything be done about this in wrapt, or is this too lost a case?

Tinche avatar Aug 17 '22 23:08 Tinche

What do you get for:

type(coroutine_func())

GrahamDumpleton avatar Aug 17 '22 23:08 GrahamDumpleton

Am inclined to think this is a shortcoming in Python implementation of coroutines in that it doesn't cope well with decorated functions, or certainly not ones where the decorated function is actual using a decorator which is a descriptor.

If you use older asyncio.coroutine decorator you get:

>>> @asyncio.coroutine
... @dec
... def my_func(): pass
...
>>> my_func().__name__
'my_func'
>>> type(my_func())
<class 'generator'>

Although if switch that around you get:

>>> @dec
... @asyncio.coroutine
... def my_func(): pass
...
>>> my_func().__name__
'dec'
>>> type(my_func())
<class 'coroutine'>

showing wrong name, with result being a different type as well, which is strange.

So right now not really sure. I can't really work out how the async keyword magic happens.

It might be interesting to do some test with decorator coroutines where decorator uses normal function closure and functools.wraps. If it works with that probably shows that a decorator implemented as a descriptor is the issue.

GrahamDumpleton avatar Aug 18 '22 07:08 GrahamDumpleton

Yep, looks like functools.wraps will do the trick.

from functools import wraps


def dec(fn):
    @wraps(fn)
    def wrapper(*args, **kwargs):
        return fn(*args, **kwargs)

    return wrapper


@dec
async def coroutine_func():
    pass


print(coroutine_func.__name__)  # is 'coroutine_func' as expected
print(coroutine_func().__name__)  # is 'coroutine_func' as expected

prints outs:

coroutine_func
coroutine_func

(and a warning that's not relevant)

Should we open up a CPython issue? Could you do it since I think you have a much better grasp of what's going on?

Tinche avatar Aug 21 '22 22:08 Tinche