holoviz / param

Param: Make your Python code clearer and more reliable by declaring Parameters
https://param.holoviz.org
BSD 3-Clause "New" or "Revised" License
427 stars 73 forks source link

Unpack partial in iscoroutinefunction #894

Closed maximlt closed 8 months ago

maximlt commented 9 months ago

We've observed that inspect.iscoroutinefunction doesn't return the same output of different versions of Python 3.10 when the function is a functools.partial. We make sure that param's iscoroutinefunction (that covers async generators too) unpacks partials before checking if they wrap a coroutine/async generator or not.

maximlt commented 9 months ago
import inspect
from functools import partial
import sys

print(sys.version)

class Foo:
    async def foo(self, x=None):
        return

cb = partial(Foo().foo, x=1)
print(inspect.iscoroutinefunction(cb))

# 3.10.5 | packaged by conda-forge | (main, Jun 14 2022, 07:05:37) [Clang 13.0.1 ]
# False

# 3.10.13 | packaged by conda-forge | (main, Oct 26 2023, 18:09:17) [Clang 16.0.6 ]
# True

I'm pretty sure this change occurred in https://github.com/python/cpython/pull/94050. In our case f is not a method but a partial, that is unwrapped to a method, and a method is not a function so this used to return False. In the newer version with _signature_is_functionlike(f) that evaluates to True in our case the returned value is True. Anyway, what matters is that this has been backported to CPython 3.10 and 3.11, precisely to CPython 3.10.6 and 3.11.0 (backported before the actual release). So we could get rid of the partial unwrapping starting from Python 3.11.0.

def _has_code_flag(f, flag):
     while ismethod(f):
         f = f.__func__
     f = functools._unwrap_partial(f)
-    if not isfunction(f):
+    if not (isfunction(f) or _signature_is_functionlike(f)):
         return False
     return bool(f.__code__.co_flags & flag)