python / cpython

The Python programming language
https://www.python.org
Other
62.15k stars 29.86k forks source link

`typing_extensions.deprecated` and `warnings.deprecated` remove coroutine property; How to deprecate async functions? #122088

Closed Kakadus closed 1 month ago

Kakadus commented 1 month ago

When I annotate an async function with @deprecated(...), inspect.iscoroutinefunction no longer returns True. This is not what I expect to happen, as with partial from functools this does not happen.

Consider the following code:

import inspect

from functools import partial
from typing_extensions import deprecated

async def coroutinefunction_a():
    pass

@deprecated("deprecated coroutine")
async def coroutinefunction_b():
    pass

assert inspect.iscoroutinefunction(coroutinefunction_a) is True  # correct
assert inspect.iscoroutinefunction(partial(coroutinefunction_a)) is True  # correct
assert inspect.iscoroutinefunction(coroutinefunction_b) is True  # fail

With typing_extensions and python3.13, the last assertion fails.

This is especially annoying since e.g. libraries use inspect.iscoroutinefunction to detect the type of function.

I am not sure this is the correct place for this bug report, as this both happens with python 3.13 and typing_extensions on older python versions.

Linked PRs

srittau commented 1 month ago

inspect.iscoroutinefunction() has special handling for partial(). The ultimate culprit here is @functools.wraps, which @deprecated uses:

import inspect

from functools import wraps

async def coroutinefunction_a():
    pass

def wrapper(f):
    @wraps(f)
    def x():
        return f()

    return x

@wrapper
async def coroutinefunction_b():
    pass

assert inspect.iscoroutinefunction(coroutinefunction_a) is True  # correct
assert inspect.iscoroutinefunction(coroutinefunction_b) is True  # fail

@wraps (or rather update_wrapper) should probably copy the coroutine marker (_is_coroutine_marker) that iscoroutinefunction checks for.

AlexWaygood commented 1 month ago

@wraps (or rather update_wrapper) should probably copy the coroutine marker (_is_coroutine_marker) that iscoroutinefunction checks for.

Previously discussed in:

srittau commented 1 month ago

In that case, I definitely see the point of copying the flag in @deprecated. (Maybe update_wrapper/@wraps should get a flag to copy it, but that's off-topic for this issue?)

AlexWaygood commented 1 month ago

In that case, I definitely see the point of copying the flag in @deprecated. (Maybe update_wrapper/@wraps should get a flag to copy it, but that's off-topic for this issue?)

Yeah, that sounds good to me. Even if the flag is added to update_wrapper/@wraps, it won't be added to the stdlib before Python 3.14, so we'll need an intermediate solution here.

srittau commented 1 month ago

@AlexWaygood @JelleZijlstra Could one of you move this issue over to the cpython repository? This should probably be fixed in CPython first.