datasette/tests/test_utils_check_callable.py
makiaveli1 aafac649cb
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.
2026-09-30 15:26:24 +01:00

63 lines
1.5 KiB
Python

import functools
import pytest
from datasette.utils.check_callable import check_callable
class AsyncClass:
async def __call__(self):
pass
class NotAsyncClass:
def __call__(self):
pass
class ClassNoCall:
pass
class MethodHolder:
async def async_method(self):
pass
def sync_method(self):
pass
async def async_func():
pass
def non_async_func():
pass
@pytest.mark.parametrize(
"obj,expected_is_callable,expected_is_async_callable",
(
(async_func, True, True),
(non_async_func, True, False),
(AsyncClass(), True, True),
(NotAsyncClass(), True, False),
(ClassNoCall(), False, False),
(AsyncClass, True, False),
(NotAsyncClass, True, False),
(ClassNoCall, True, False),
("", 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):
status = check_callable(obj)
assert status.is_callable == expected_is_callable
assert status.is_async_callable == expected_is_async_callable