diff --git a/instructor/patch.py b/instructor/patch.py index fe4c1f6be..63e85dfb5 100644 --- a/instructor/patch.py +++ b/instructor/patch.py @@ -467,9 +467,11 @@ def retry_sync( def is_async(func: Callable) -> bool: """Returns true if the callable is async, accounting for wrapped callables""" - return inspect.iscoroutinefunction(func) or ( - hasattr(func, "__wrapped__") and inspect.iscoroutinefunction(func.__wrapped__) - ) + is_coroutine = inspect.iscoroutinefunction(func) + while hasattr(func, "__wrapped__"): + func = func.__wrapped__ + is_coroutine = is_coroutine or inspect.iscoroutinefunction(func) + return is_coroutine OVERRIDE_DOCS = """ diff --git a/tests/test_patch.py b/tests/test_patch.py index 0418a1e14..b0e72e58a 100644 --- a/tests/test_patch.py +++ b/tests/test_patch.py @@ -39,6 +39,40 @@ def wrapped_function(): assert is_async(wrapped_function) is True +def test_is_async_returns_true_if_double_wrapped_function_is_async(): + async def async_function(): + pass + + @functools.wraps(async_function) + def wrapped_function(): + pass + + @functools.wraps(wrapped_function) + def double_wrapped_function(): + pass + + assert is_async(double_wrapped_function) is True + + +def test_is_async_returns_true_if_triple_wrapped_function_is_async(): + async def async_function(): + pass + + @functools.wraps(async_function) + def wrapped_function(): + pass + + @functools.wraps(wrapped_function) + def double_wrapped_function(): + pass + + @functools.wraps(double_wrapped_function) + def triple_wrapped_function(): + pass + + assert is_async(triple_wrapped_function) is True + + def test_override_docs(): assert ( "response_model" in OVERRIDE_DOCS