Fix check_callable() reporting wrapped async callables as synchronous

check_callable() only inspected objects that are exactly types.FunctionType,
and otherwise looked at obj.__call__. For a bound async method or a
functools.partial wrapping an async function, __call__ is a method wrapper
rather than a coroutine function, so both were reported as synchronous even
though inspect.iscoroutinefunction() agrees they are async.

Inspect the object itself first, which covers functions, bound methods and
partial objects, and keep the __call__ check for callable instances.
This commit is contained in:
makiaveli1 2026-09-30 15:26:24 +01:00
commit aafac649cb
No known key found for this signature in database
2 changed files with 21 additions and 3 deletions

View file

@ -1,5 +1,4 @@
import inspect
import types
from typing import Any, NamedTuple
@ -16,8 +15,11 @@ def check_callable(obj: Any) -> CallableStatus:
# It's a class
return CallableStatus(True, False)
if isinstance(obj, types.FunctionType):
return CallableStatus(True, inspect.iscoroutinefunction(obj))
if inspect.iscoroutinefunction(obj):
# Inspect the object itself, so bound methods and functools.partial objects
# wrapping an async function are recognised - they are not types.FunctionType,
# and their __call__ is a method wrapper rather than a coroutine function.
return CallableStatus(True, True)
if callable(obj):
return CallableStatus(True, inspect.iscoroutinefunction(obj.__call__))

View file

@ -1,3 +1,5 @@
import functools
import pytest
from datasette.utils.check_callable import check_callable
@ -17,6 +19,14 @@ class ClassNoCall:
pass
class MethodHolder:
async def async_method(self):
pass
def sync_method(self):
pass
async def async_func():
pass
@ -39,6 +49,12 @@ def non_async_func():
("", False, False),
(1, False, False),
(str, True, False),
# Bound methods and functools.partial objects are not types.FunctionType,
# but they are still async when the function they wrap is
(MethodHolder().async_method, True, True),
(MethodHolder().sync_method, True, False),
(functools.partial(async_func), True, True),
(functools.partial(non_async_func), True, False),
),
)
def test_check_callable(obj, expected_is_callable, expected_is_async_callable):