diff --git a/asgiref/sync.py b/asgiref/sync.py index a70dac18..d02bd4aa 100644 --- a/asgiref/sync.py +++ b/asgiref/sync.py @@ -107,7 +107,12 @@ class AsyncToSync: loop_thread_executors: "Dict[asyncio.AbstractEventLoop, CurrentThreadExecutor]" = {} def __init__(self, awaitable, force_new_loop=False): - if not callable(awaitable) or not _iscoroutinefunction_or_partial(awaitable): + if not callable(awaitable) or ( + not _iscoroutinefunction_or_partial(awaitable) + and not _iscoroutinefunction_or_partial( + getattr(awaitable, "__call__", awaitable) + ) + ): # Python does not have very reliable detection of async functions # (lots of false negatives) so this is just a warning. warnings.warn( diff --git a/tests/test_sync.py b/tests/test_sync.py index 2837423b..c5677fcb 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -3,6 +3,7 @@ import multiprocessing import threading import time +import warnings from concurrent.futures import ThreadPoolExecutor from functools import wraps from unittest import TestCase @@ -365,6 +366,29 @@ async def inner_async_function(*args): assert result["worked"] +def test_async_to_sync_on_callable_object(): + """ + Tests async_to_sync on a callable class instance + """ + + result = {} + + class CallableClass: + async def __call__(self, value): + await asyncio.sleep(0) + result["worked"] = True + return value + + # Run it (without warnings) + with warnings.catch_warnings(): + warnings.simplefilter("error") + sync_function = async_to_sync(CallableClass()) + out = sync_function(42) + + assert out == 42 + assert result["worked"] is True + + def test_async_to_sync_method_self_attribute(): """ Tests async_to_sync on a method copies __self__.