From c440c61f56c2f6922a1cdd3151e7a5b024704982 Mon Sep 17 00:00:00 2001 From: Vincent MAILLOL Date: Wed, 26 Nov 2025 16:29:43 +0100 Subject: [PATCH] Fix pass callable object to sync_to_async --- asgiref/sync.py | 4 +++- tests/test_sync_contextvars.py | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/asgiref/sync.py b/asgiref/sync.py index 54e417d3..e733c8e5 100644 --- a/asgiref/sync.py +++ b/asgiref/sync.py @@ -432,9 +432,11 @@ def __init__( or iscoroutinefunction(getattr(func, "__call__", func)) ): raise TypeError("sync_to_async can only be applied to sync functions.") + + functools.update_wrapper(self, func) self.func = func self.context = context - functools.update_wrapper(self, func) + self._thread_sensitive = thread_sensitive markcoroutinefunction(self) if thread_sensitive and executor is not None: diff --git a/tests/test_sync_contextvars.py b/tests/test_sync_contextvars.py index 1611399f..c24fdaa3 100644 --- a/tests/test_sync_contextvars.py +++ b/tests/test_sync_contextvars.py @@ -138,3 +138,24 @@ async def async_function(): sync_function = async_to_sync(async_function) assert sync_function() == 42 assert foo.get() == "baz" + + +@pytest.mark.asyncio +async def test_sync_to_async_contextvars_with_callable_with_context_attribute(): + """ + Tests that a callable object with a `context` attribute + can be wrapped with `sync_to_async` without overwriting the `context` attribute + and still returns the expected result. + """ + # Define sync Callable + class SyncCallable: + def __init__(self): + # Should not be copied to the SyncToAsync wrapper. + self.context = ... + + def __call__(self): + return 42 + + async_function = sync_to_async(SyncCallable()) + assert async_function.context is None + assert await async_function() == 42