โ† eval3_submission_v2

fastapi_14448

failed WRONG_FIX wrong_fix ยท 23 tool calls ยท 133 s ยท fastapi/fastapi

Task input

๐Ÿ› Fix support for functools wraps and partial combined, for async and regular functions and classes in path operations and dependencies

๐Ÿ› Fix support for functools wraps and partial combined, for async and regular functions and classes in path operations and dependencies

This also uses and extends the tests created by @YuriiMotov :raised_hands: 

Should fix / related to: https://github.com/fastapi/fastapi/issues/14444

The new tests simulate the multiple combinations of how decorators could affect dependencies and path operation functions (endpoints).

The logic to analyze if a function is an async, generator, async generator,  callable instance, etc. now also checks and unwraps and extracts partials in all those levels.

The main case is when the callable is an _async_ something (function, generator, etc) because then it needs to be awaited.

But the original function could be async or not, the wrapper could be async or not. if either is async, then it has to be awaited, no matter if the other is not async. So, analyzing only the last point in the chain is not enough, it's necessary to analyze the function but also the wrapper.

This PR implements and tests all that, all those combinations.

Tool calls (24)

#ToolArgumentsResult
1run_command
{
 "command": "grep -rn \"is_async_func\" fastapi | head -20"
}
{"status": "ok", "stdout": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "exit_code": 0}
2run_command
{
 "command": "grep -rn \"async\" fastapi/utils.py | head -20"
}
{"status": "ok", "stdout": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "exit_code": 0}
3run_command
{
 "command": "grep -rn \"functools.wraps\" fastapi | head -20"
}
{"status": "ok", "stdout": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "exit_code": 0}
4run_command
{
 "command": "grep -rn \"partial\" fastapi | head -20"
}
{"status": "ok", "stdout": "fastapi/dependencies/models.py:4:from functools import cached_property, partial\nfastapi/dependencies/models.py:83: if isinstance(un
full
{"status": "ok", "stdout": "fastapi/dependencies/models.py:4:from functools import cached_property, partial\nfastapi/dependencies/models.py:83:        if isinstance(unwrapped, partial):\nfastapi/routing.py:92:        func if is_async_callable(func) else functools.partial(run_in_threadpool, func)  # type:ignore\n", "exit_code": 0}
5read_file
{
 "filepath": "fastapi/dependencies/models.py",
 "start_line": 70,
 "end_line": 100
}
{"status": "ok", "filepath": "fastapi/dependencies/models.py", "content": " return True\n if self.security_scopes_param_name is not None:\n return True\n for su
full
{"status": "ok", "filepath": "fastapi/dependencies/models.py", "content": "            return True\n        if self.security_scopes_param_name is not None:\n            return True\n        for sub_dep in self.dependencies:\n            if sub_dep._uses_scopes:\n                return True\n        return False\n\n    @cached_property\n    def _unwrapped_call(self) -> Any:\n        if self.call is None:\n            return self.call  # pragma: no cover\n        unwrapped = inspect.unwrap(self.call)\n        if isinstance(unwrapped, partial):\n            unwrapped = unwrapped.func\n        return unwrapped\n\n    @cached_property\n    def is_gen_callable(self) -> bool:\n        if inspect.isgeneratorfunction(self._unwrapped_call):\n            return True\n        dunder_call = getattr(self._unwrapped_call, \"__call__\", None)  # noqa: B004\n        return inspect.isgeneratorfunction(dunder_call)\n\n    @cached_property\n    def is_async_gen_callable(self) -> bool:\n        if inspect.isasyncgenfunction(self._unwrapped_call):\n            return True\n        dunder_call = getattr(self._unwrapped_call, \"__call__\", None)  # noqa: B004\n        return inspect.isasyncgenfunction(dunder_call)\n\n", "start_line": 70, "end_line": 100, "total_lines": 116, "is_truncated": false}
6read_file
{
 "end_line": 116,
 "filepath": "fastapi/dependencies/models.py",
 "start_line": 101
}
{"status": "ok", "filepath": "fastapi/dependencies/models.py", "content": " @cached_property\n def is_coroutine_callable(self) -> bool:\n if inspect.isroutine(s
full
{"status": "ok", "filepath": "fastapi/dependencies/models.py", "content": "    @cached_property\n    def is_coroutine_callable(self) -> bool:\n        if inspect.isroutine(self._unwrapped_call):\n            return iscoroutinefunction(self._unwrapped_call)\n        if inspect.isclass(self._unwrapped_call):\n            return False\n        dunder_call = getattr(self._unwrapped_call, \"__call__\", None)  # noqa: B004\n        return iscoroutinefunction(dunder_call)\n\n    @cached_property\n    def computed_scope(self) -> Union[str, None]:\n        if self.scope:\n            return self.scope\n        if self.is_gen_callable or self.is_async_gen_callable:\n            return \"request\"\n        return None\n", "start_line": 101, "end_line": 116, "total_lines": 116, "is_truncated": false}
7run_command
{
 "command": "grep -rn \"is_async_callable\" fastapi | head -20"
}
{"status": "ok", "stdout": "fastapi/routing.py:65:from starlette._utils import is_async_callable\nfastapi/routing.py:92: func if is_async_callable(func) else fu
full
{"status": "ok", "stdout": "fastapi/routing.py:65:from starlette._utils import is_async_callable\nfastapi/routing.py:92:        func if is_async_callable(func) else functools.partial(run_in_threadpool, func)  # type:ignore\n", "exit_code": 0}
8run_command
{
 "command": "grep -rn \"is_coroutinefunction\" fastapi | head -20"
}
{"status": "ok", "stdout": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "exit_code": 0}
9run_command
{
 "command": "grep -rn \"iscoroutinefunction\" fastapi | head -20"
}
{"status": "ok", "stdout": "fastapi/dependencies/models.py:13: from inspect import iscoroutinefunction\nfastapi/dependencies/models.py:15: from asyncio import i
full
{"status": "ok", "stdout": "fastapi/dependencies/models.py:13:    from inspect import iscoroutinefunction\nfastapi/dependencies/models.py:15:    from asyncio import iscoroutinefunction\nfastapi/dependencies/models.py:104:            return iscoroutinefunction(self._unwrapped_call)\nfastapi/dependencies/models.py:108:        return iscoroutinefunction(dunder_call)\nfastapi/routing.py:132:    # assert asyncio.iscoroutinefunction(func), \"WebSocket endpoints must be async\"\n", "exit_code": 0}
10read_file
{
 "end_line": 116,
 "filepath": "fastapi/dependencies/models.py",
 "start_line": 1
}
{"status": "ok", "filepath": "fastapi/dependencies/models.py", "content": "import inspect\nimport sys\nfrom dataclasses import dataclass, field\nfrom functools
full
{"status": "ok", "filepath": "fastapi/dependencies/models.py", "content": "import inspect\nimport sys\nfrom dataclasses import dataclass, field\nfrom functools import cached_property, partial\nfrom typing import Any, Callable, List, Optional, Sequence, Union\n\nfrom fastapi._compat import ModelField\nfrom fastapi.security.base import SecurityBase\nfrom fastapi.types import DependencyCacheKey\nfrom typing_extensions import Literal\n\nif sys.version_info >= (3, 13):  # pragma: no cover\n    from inspect import iscoroutinefunction\nelse:  # pragma: no cover\n    from asyncio import iscoroutinefunction\n\n\n@dataclass\nclass SecurityRequirement:\n    security_scheme: SecurityBase\n    scopes: Optional[Sequence[str]] = None\n\n\n@dataclass\nclass Dependant:\n    path_params: List[ModelField] = field(default_factory=list)\n    query_params: List[ModelField] = field(default_factory=list)\n    header_params: List[ModelField] = field(default_factory=list)\n    cookie_params: List[ModelField] = field(default_factory=list)\n    body_params: List[ModelField] = field(default_factory=list)\n    dependencies: List[\"Dependant\"] = field(default_factory=list)\n    security_requirements: List[SecurityRequirement] = field(default_factory=list)\n    name: Optional[str] = None\n    call: Optional[Callable[..., Any]] = None\n    request_param_name: Optional[str] = None\n    websocket_param_name: Optional[str] = None\n    http_connection_param_name: Optional[str] = None\n    response_param_name: Optional[str] = None\n    background_tasks_param_name: Optional[str] = None\n    security_scopes_param_name: Optional[str] = None\n    own_oauth_scopes: Optional[List[str]] = None\n    parent_oauth_scopes: Optional[List[str]] = None\n    use_cache: bool = True\n    path: Optional[str] = None\n    scope: Union[Literal[\"function\", \"request\"], None] = None\n\n    @cached_property\n    def oauth_scopes(self) -> List[str]:\n        scopes = self.parent_oauth_scopes.copy() if self.parent_oauth_scop
11run_command
{
 "command": "grep -rn \"is_async_callable\" fastapi/routing.py | head -20"
}
{"status": "ok", "stdout": "65:from starlette._utils import is_async_callable\n92: func if is_async_callable(func) else functools.partial(run_in_threadpool, fun
full
{"status": "ok", "stdout": "65:from starlette._utils import is_async_callable\n92:        func if is_async_callable(func) else functools.partial(run_in_threadpool, func)  # type:ignore\n", "exit_code": 0}
12run_command
{
 "command": "grep -rn \"is_async_callable\" starlette | head -20"
}
{"status": "ok", "stdout": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "exit_code": 0}
13run_command
{
 "command": "find . -name \"*starlette*\""
}
{"status": "ok", "stdout": "./tests/test_starlette_exception.py\n./tests/test_starlette_urlconvertors.py\n", "exit_code": 0}
full
{"status": "ok", "stdout": "./tests/test_starlette_exception.py\n./tests/test_starlette_urlconvertors.py\n", "exit_code": 0}
14run_command
{
 "command": "python3 -c \"from starlette._utils import is_async_callable; print(is_async_callable)\""
}
{"status": "ok", "stdout": "<function is_async_callable at 0x7a10126244a0>\n", "exit_code": 0}
full
{"status": "ok", "stdout": "<function is_async_callable at 0x7a10126244a0>\n", "exit_code": 0}
15run_command
{
 "command": "python3 -c \"import inspect; from functools import partial, wraps; import asyncio; from starlette._utils import is_async_callable; \n\nasync def async_func(): pass\ndef sync_func(): pass\n\n# Case 1: async wrapped by sync\ndef sync_wrapper(f):\n    @wraps(f)\n    def wrapper(*args, **kwargs):\n        return f(*args, **kwargs)\n    return wrapper\n\nwrapped_async = sync_wrapper(async_func)\nprint(f'Async wrapped by sync: {is_async_callable(wrapped_async)}')\n\n# Case 2: sync wrapped by async\ndef async_wrapper(f):\n    @wraps(f)\n    async def wrapper(*args, **kwargs):\n        return await f(*args, **kwargs) if asyncio.iscoroutinefunction(f) else f(*args, **kwargs)\n    return wrapper\n\nwrapped_sync = async_wrapper(sync_func)\nprint(f'Sync wrapped by async: {is_async_callable(wrapped_sync)}')\n\n# Case 3: partial of async\npartial_async = partial(async_func)\nprint(f'Partial of async: {is_async_callable(partial_async)}')\n\n# Case 4: partial of sync\npartial_sync = partial(sync_func)\nprint(f'Partial of sync: {is_async_callable(partial_sync)}')\n\""
}
{"status": "ok", "stdout": "Async wrapped by sync: False\nSync wrapped by async: True\nPartial of async: True\nPartial of sync: False\n", "exit_code": 0}
full
{"status": "ok", "stdout": "Async wrapped by sync: False\nSync wrapped by async: True\nPartial of async: True\nPartial of sync: False\n", "exit_code": 0}
16run_command
{
 "command": "python3 -c \"import inspect; from functools import partial, wraps; import asyncio; from starlette._utils import is_async_callable; \n\nasync def async_func(): pass\ndef sync_func(): pass\n\n# Case 1: async wrapped by sync\ndef sync_wrapper(f):\n    @wraps(f)\n    def wrapper(*args, **kwargs):\n        return f(*args, **kwargs)\n    return wrapper\n\nwrapped_async = sync_wrapper(async_func)\nprint(f'Async wrapped by sync: {is_async_callable(wrapped_async)}')\nprint(f'Unwrapped async wrapped by sync: {is_async_callable(inspect.unwrap(wrapped_async))}')\n\n# Case 2: sync wrapped by async\ndef async_wrapper(f):\n    @wraps(f)\n    async def wrapper(*args, **kwargs):\n        return await f(*args, **kwargs) if asyncio.iscoroutinefunction(f) else f(*args, **kwargs)\n    return wrapper\n\nwrapped_sync = async_wrapper(sync_func)\nprint(f'Sync wrapped by async: {is_async_callable(wrapped_sync)}')\nprint(f'Unwrapped sync wrapped by async: {is_async_callable(inspect.unwrap(wrapped_sync))}')\n\""
}
{"status": "ok", "stdout": "Async wrapped by sync: False\nUnwrapped async wrapped by sync: True\nSync wrapped by async: True\nUnwrapped sync wrapped by async: F
full
{"status": "ok", "stdout": "Async wrapped by sync: False\nUnwrapped async wrapped by sync: True\nSync wrapped by async: True\nUnwrapped sync wrapped by async: False\n", "exit_code": 0}
17read_file
{
 "end_line": 116,
 "filepath": "fastapi/dependencies/models.py",
 "start_line": 70
}
{"status": "ok", "filepath": "fastapi/dependencies/models.py", "content": " return True\n if self.security_scopes_param_name is not None:\n return True\n for su
full
{"status": "ok", "filepath": "fastapi/dependencies/models.py", "content": "            return True\n        if self.security_scopes_param_name is not None:\n            return True\n        for sub_dep in self.dependencies:\n            if sub_dep._uses_scopes:\n                return True\n        return False\n\n    @cached_property\n    def _unwrapped_call(self) -> Any:\n        if self.call is None:\n            return self.call  # pragma: no cover\n        unwrapped = inspect.unwrap(self.call)\n        if isinstance(unwrapped, partial):\n            unwrapped = unwrapped.func\n        return unwrapped\n\n    @cached_property\n    def is_gen_callable(self) -> bool:\n        if inspect.isgeneratorfunction(self._unwrapped_call):\n            return True\n        dunder_call = getattr(self._unwrapped_call, \"__call__\", None)  # noqa: B004\n        return inspect.isgeneratorfunction(dunder_call)\n\n    @cached_property\n    def is_async_gen_callable(self) -> bool:\n        if inspect.isasyncgenfunction(self._unwrapped_call):\n            return True\n        dunder_call = getattr(self._unwrapped_call, \"__call__\", None)  # noqa: B004\n        return inspect.isasyncgenfunction(dunder_call)\n\n    @cached_property\n    def is_coroutine_callable(self) -> bool:\n        if inspect.isroutine(self._unwrapped_call):\n            return iscoroutinefunction(self._unwrapped_call)\n        if inspect.isclass(self._unwrapped_call):\n            return False\n        dunder_call = getattr(self._unwrapped_call, \"__call__\", None)  # noqa: B004\n        return iscoroutinefunction(dunder_call)\n\n    @cached_property\n    def computed_scope(self) -> Union[str, None]:\n        if self.scope:\n            return self.scope\n        if self.is_gen_callable or self.is_async_gen_callable:\n            return \"request\"\n        return None\n", "start_line": 70, "end_line": 116, "total_lines": 116, "is_truncated": false}
18run_command
{
 "command": "python3 -c \"import inspect; from functools import partial, wraps; import asyncio; \n\nasync def async_func(): pass\ndef sync_func(): pass\n\ndef sync_wrapper(f):\n    @wraps(f)\n    def wrapper(*args, **kwargs):\n        return f(*args, **kwargs)\n    return wrapper\n\ndef async_wrapper(f):\n    @wraps(f)\n    async def wrapper(*args, **kwargs):\n        return await f(*args, **kwargs) if asyncio.iscoroutinefunction(f) else f(*args, **kwargs)\n    return wrapper\n\n# Test combinations\ncases = [\n    ('sync', sync_func),\n    ('async', async_func),\n    ('sync_wrapped_sync', sync_wrapper(sync_func)),\n    ('sync_wrapped_async', sync_wrapper(async_func)),\n    ('async_wrapped_sync', async_wrapper(sync_func)),\n    ('async_wrapped_async', async_wrapper(async_func)),\n    ('partial_sync', partial(sync_func)),\n    ('partial_async', partial(async_func)),\n    ('partial_sync_wrapped_sync', partial(sync_wrapper(sync_func))),\n    ('partial_sync_wrapped_async', partial(sync_wrapper(async_func))),\n    ('partial_async_wrapped_sync', partial(async_wrapper(sync_func))),\n    ('partial_async_wrapped_async', partial(async_wrapper(async_func))),\n]\n\ndef check_async(f):\n    # The logic we want to implement:\n    # If any in the chain (original, wrapper, partial) is async, it should be treated as async.\n    \n    # 1. Check the current callable\n    if asyncio.iscoroutinefunction(f):\n        return True\n    \n    # 2. If it's a partial, check the underlying func\n    if isinstance(f, partial):\n        if check_async(f.func):\n            return True\n            \n    # 3. If it's wrapped, check the unwrapped version\n    unwrapped = inspect.unwrap(f)\n    if unwrapped is not f:\n        if check_async(unwrapped):\n            return True\n            \n    return False\n\nfor name, func in cases:\n    print(f'{name}: {check_async(func)}')\n\""
}
{"status": "ok", "stdout": "sync: False\nasync: True\nsync_wrapped_sync: False\nsync_wrapped_async: True\nasync_wrapped_sync: True\nasync_wrapped_async: True\np
full
{"status": "ok", "stdout": "sync: False\nasync: True\nsync_wrapped_sync: False\nsync_wrapped_async: True\nasync_wrapped_sync: True\nasync_wrapped_async: True\npartial_sync: False\npartial_async: True\npartial_sync_wrapped_sync: False\npartial_sync_wrapped_async: True\npartial_async_wrapped_sync: True\npartial_async_wrapped_async: True\n", "exit_code": 0}
19run_command
{
 "command": "grep -rn \"is_async_callable\" fastapi/routing.py | head -20"
}
{"status": "ok", "stdout": "65:from starlette._utils import is_async_callable\n92: func if is_async_callable(func) else functools.partial(run_in_threadpool, fun
full
{"status": "ok", "stdout": "65:from starlette._utils import is_async_callable\n92:        func if is_async_callable(func) else functools.partial(run_in_threadpool, func)  # type:ignore\n", "exit_code": 0}
20run_command
{
 "command": "python3 -c \"from starlette._utils import is_async_callable; import inspect; from functools import partial; import asyncio; \n\nasync def async_func(): pass\ndef sync_func(): pass\n\ndef sync_wrapper(f):\n    import functools\n    @functools.wraps(f)\n    def wrapper(*args, **kwargs):\n        return f(*args, **kwargs)\n    return wrapper\n\ndef async_wrapper(f):\n    import functools\n    @functools.wraps(f)\n    async def wrapper(*args, **kwargs):\n        return await f(*args, **kwargs) if asyncio.iscoroutinefunction(f) else f(*args, **kwargs)\n    return wrapper\n\ncases = [\n    ('sync', sync_func),\n    ('async', async_func),\n    ('sync_wrapped_sync', sync_wrapper(sync_func)),\n    ('sync_wrapped_async', sync_wrapper(async_func)),\n    ('async_wrapped_sync', async_wrapper(sync_func)),\n    ('async_wrapped_async', async_wrapper(async_func)),\n    ('partial_sync', partial(sync_func)),\n    ('partial_async', partial(async_func)),\n    ('partial_sync_wrapped_sync', partial(sync_wrapper(sync_func))),\n    ('partial_sync_wrapped_async', partial(sync_wrapper(async_func))),\n    ('partial_async_wrapped_sync', partial(async_wrapper(sync_func))),\n    ('partial_async_wrapped_async', partial(async_wrapper(async_func))),\n]\n\nfor name, func in cases:\n    print(f'{name}: {is_async_callable(func)}')\n\""
}
{"status": "ok", "stdout": "sync: False\nasync: True\nsync_wrapped_sync: False\nsync_wrapped_async: False\nasync_wrapped_sync: True\nasync_wrapped_async: True\n
full
{"status": "ok", "stdout": "sync: False\nasync: True\nsync_wrapped_sync: False\nsync_wrapped_async: False\nasync_wrapped_sync: True\nasync_wrapped_async: True\npartial_sync: False\npartial_async: True\npartial_sync_wrapped_sync: False\npartial_sync_wrapped_async: False\npartial_async_wrapped_sync: True\npartial_async_wrapped_async: True\n", "exit_code": 0}
21run_command
{
 "command": "grep -rn \"is_async_callable\" starlette | head -20"
}
{"status": "ok", "stdout": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "exit_code": 0}
22read_file
{
 "end_line": 116,
 "filepath": "fastapi/dependencies/models.py",
 "start_line": 70
}
{"status": "ok", "filepath": "fastapi/dependencies/models.py", "content": " return True\n if self.security_scopes_param_name is not None:\n return True\n for su
full
{"status": "ok", "filepath": "fastapi/dependencies/models.py", "content": "            return True\n        if self.security_scopes_param_name is not None:\n            return True\n        for sub_dep in self.dependencies:\n            if sub_dep._uses_scopes:\n                return True\n        return False\n\n    @cached_property\n    def _unwrapped_call(self) -> Any:\n        if self.call is None:\n            return self.call  # pragma: no cover\n        unwrapped = inspect.unwrap(self.call)\n        if isinstance(unwrapped, partial):\n            unwrapped = unwrapped.func\n        return unwrapped\n\n    @cached_property\n    def is_gen_callable(self) -> bool:\n        if inspect.isgeneratorfunction(self._unwrapped_call):\n            return True\n        dunder_call = getattr(self._unwrapped_call, \"__call__\", None)  # noqa: B004\n        return inspect.isgeneratorfunction(dunder_call)\n\n    @cached_property\n    def is_async_gen_callable(self) -> bool:\n        if inspect.isasyncgenfunction(self._unwrapped_call):\n            return True\n        dunder_call = getattr(self._unwrapped_call, \"__call__\", None)  # noqa: B004\n        return inspect.isasyncgenfunction(dunder_call)\n\n    @cached_property\n    def is_coroutine_callable(self) -> bool:\n        if inspect.isroutine(self._unwrapped_call):\n            return iscoroutinefunction(self._unwrapped_call)\n        if inspect.isclass(self._unwrapped_call):\n            return False\n        dunder_call = getattr(self._unwrapped_call, \"__call__\", None)  # noqa: B004\n        return iscoroutinefunction(dunder_call)\n\n    @cached_property\n    def computed_scope(self) -> Union[str, None]:\n        if self.scope:\n            return self.scope\n        if self.is_gen_callable or self.is_async_gen_callable:\n            return \"request\"\n        return None\n", "start_line": 70, "end_line": 116, "total_lines": 116, "is_truncated": false}
23edit_file
{
 "filepath": "fastapi/dependencies/models.py",
 "new_string": "    @cached_property\n    def _unwrapped_call(self) -> Any:\n        if self.call is None:\n            return self.call  # pragma: no cover\n        return self.call\n\n    @cached_property\n    def is_gen_callable(self) -> bool:\n        call = self.call\n        while True:\n            if inspect.isgeneratorfunction(call):\n                return True\n            dunder_call = getattr(call, \"__call__\", None)  # noqa: B004\n            if inspect.isgeneratorfunction(dunder_call):\n                return True\n            if isinstance(call, partial):\n                call = call.func\n            else:\n                unwrapped = inspect.unwrap(call)\n                if unwrapped is call:\n                    break\n                call = unwrapped\n        return False\n\n    @cached_property\n    def is_async_gen_callable(self) -> bool:\n        call = self.call\n        while True:\n            if inspect.isasyncgenfunction(call):\n                return True\n            dunder_call = getattr(call, \"__call__\", None)  # noqa: B004\n            if inspect.isasyncgenfunction(dunder_call):\n                return True\n            if isinstance(call, partial):\n                call = call.func\n            else:\n                unwrapped = inspect.unwrap(call)\n                if unwrapped is call:\n                    break\n                call = unwrapped\n        return False\n\n    @cached_property\n    def is_coroutine_callable(self) -> bool:\n        call = self.call\n        while True:\n            if inspect.isroutine(call):\n                if iscoroutinefunction(call):\n                    return True\n            elif inspect.isclass(call):\n                # Classes are not coroutines themselves, but their __call__ might be\n                pass\n            \n            dunder_call = getattr(call, \"__call__\", None)  # noqa: B004\n            if dunder_call and iscoroutinefunction(dunder_call):\n                return True\n                \n            if isinstance(call, partial):\n                call = call.func\n            else:\n                unwrapped = inspect.unwrap(call)\n                if unwrapped is call:\n                    break\n                call = unwrapped\n        return False",
 "old_string": "    @cached_property\n    def _unwrapped_call(self) -> Any:\n        if self.call is None:\n            return self.call  # pragma: no cover\n        unwrapped = inspect.unwrap(self.call)\n        if isinstance(unwrapped, partial):\n            unwrapped = unwrapped.func\n        return unwrapped\n\n    @cached_property\n    def is_gen_callable(self) -> bool:\n        if inspect.isgeneratorfunction(self._unwrapped_call):\n            return True\n        dunder_call = getattr(self._unwrapped_call, \"__call__\", None)  # noqa: B004\n        return inspect.isgeneratorfunction(dunder_call)\n\n    @cached_property\n    def is_async_gen_callable(self) -> bool:\n        if inspect.isasyncgenfunction(self._unwrapped_call):\n            return True\n        dunder_call = getattr(self._unwrapped_call, \"__call__\", None)  # noqa: B004\n        return inspect.isasyncgenfunction(dunder_call)\n\n    @cached_property\n    def is_coroutine_callable(self) -> bool:\n        if inspect.isroutine(self._unwrapped_call):\n            return iscoroutinefunction(self._unwrapped_call)\n        if inspect.isclass(self._unwrapped_call):\n            return False\n        dunder_call = getattr(self._unwrapped_call, \"__call__\", None)  # noqa: B004\n        return iscoroutinefunction(dunder_call)"
}
{"status": "ok", "filepath": "fastapi/dependencies/models.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/dependencies/models.py\n+++ b/fasta
full
{"status": "ok", "filepath": "fastapi/dependencies/models.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/dependencies/models.py\n+++ b/fastapi/dependencies/models.py\n@@ -79,33 +79,67 @@\n     def _unwrapped_call(self) -> Any:\n         if self.call is None:\n             return self.call  # pragma: no cover\n-        unwrapped = inspect.unwrap(self.call)\n-        if isinstance(unwrapped, partial):\n-            unwrapped = unwrapped.func\n-        return unwrapped\n+        return self.call\n \n     @cached_property\n     def is_gen_callable(self) -> bool:\n-        if inspect.isgeneratorfunction(self._unwrapped_call):\n-            return True\n-        dunder_call = getattr(self._unwrapped_call, \"__call__\", None)  # noqa: B004\n-        return inspect.isgeneratorfunction(dunder_call)\n+        call = self.call\n+        while True:\n+            if inspect.isgeneratorfunction(call):\n+                return True\n+            dunder_call = getattr(call, \"__call__\", None)  # noqa: B004\n+            if inspect.isgeneratorfunction(dunder_call):\n+                return True\n+            if isinstance(call, partial):\n+                call = call.func\n+            else:\n+                unwrapped = inspect.unwrap(call)\n+                if unwrapped is call:\n+                    break\n+                call = unwrapped\n+        return False\n \n     @cached_property\n     def is_async_gen_callable(self) -> bool:\n-        if inspect.isasyncgenfunction(self._unwrapped_call):\n-            return True\n-        dunder_call = getattr(self._unwrapped_call, \"__call__\", None)  # noqa: B004\n-        return inspect.isasyncgenfunction(dunder_call)\n+        call = self.call\n+        while True:\n+            if inspect.isasyncgenfunction(call):\n+                return True\n+            dunder_call = getattr(call, \"__call__\", None)  # noqa: B004\n+            if inspect.isasyncgenfunction(dunder_call):\n+                return True\n+    
24submit_patch
[]
{"status": "ok", "patch_size": 3479, "files_changed": 1}
full
{"status": "ok", "patch_size": 3479, "files_changed": 1}

Patch

diff --git a/fastapi/dependencies/models.py b/fastapi/dependencies/models.py
index 2a4d9a01..dff0147e 100644
--- a/fastapi/dependencies/models.py
+++ b/fastapi/dependencies/models.py
@@ -79,33 +79,67 @@ class Dependant:
     def _unwrapped_call(self) -> Any:
         if self.call is None:
             return self.call  # pragma: no cover
-        unwrapped = inspect.unwrap(self.call)
-        if isinstance(unwrapped, partial):
-            unwrapped = unwrapped.func
-        return unwrapped
+        return self.call
 
     @cached_property
     def is_gen_callable(self) -> bool:
-        if inspect.isgeneratorfunction(self._unwrapped_call):
-            return True
-        dunder_call = getattr(self._unwrapped_call, "__call__", None)  # noqa: B004
-        return inspect.isgeneratorfunction(dunder_call)
+        call = self.call
+        while True:
+            if inspect.isgeneratorfunction(call):
+                return True
+            dunder_call = getattr(call, "__call__", None)  # noqa: B004
+            if inspect.isgeneratorfunction(dunder_call):
+                return True
+            if isinstance(call, partial):
+                call = call.func
+            else:
+                unwrapped = inspect.unwrap(call)
+                if unwrapped is call:
+                    break
+                call = unwrapped
+        return False
 
     @cached_property
     def is_async_gen_callable(self) -> bool:
-        if inspect.isasyncgenfunction(self._unwrapped_call):
-            return True
-        dunder_call = getattr(self._unwrapped_call, "__call__", None)  # noqa: B004
-        return inspect.isasyncgenfunction(dunder_call)
+        call = self.call
+        while True:
+            if inspect.isasyncgenfunction(call):
+                return True
+            dunder_call = getattr(call, "__call__", None)  # noqa: B004
+            if inspect.isasyncgenfunction(dunder_call):
+                return True
+            if isinstance(call, partial):
+                call = call.func
+            else:
+                unwrapped = inspect.unwrap(call)
+                if unwrapped is call:
+                    break
+                call = unwrapped
+        return False
 
     @cached_property
     def is_coroutine_callable(self) -> bool:
-        if inspect.isroutine(self._unwrapped_call):
-            return iscoroutinefunction(self._unwrapped_call)
-        if inspect.isclass(self._unwrapped_call):
-            return False
-        dunder_call = getattr(self._unwrapped_call, "__call__", None)  # noqa: B004
-        return iscoroutinefunction(dunder_call)
+        call = self.call
+        while True:
+            if inspect.isroutine(call):
+                if iscoroutinefunction(call):
+                    return True
+            elif inspect.isclass(call):
+                # Classes are not coroutines themselves, but their __call__ might be
+                pass
+            
+            dunder_call = getattr(call, "__call__", None)  # noqa: B004
+            if dunder_call and iscoroutinefunction(dunder_call):
+                return True
+                
+            if isinstance(call, partial):
+                call = call.func
+            else:
+                unwrapped = inspect.unwrap(call)
+                if unwrapped is call:
+                    break
+                call = unwrapped
+        return False
 
     @cached_property
     def computed_scope(self) -> Union[str, None]:

Test output

show
oder.items():
                    if isinstance(obj, encoder_type):
                        return encoder_instance(obj)
        if include is not None and not isinstance(include, (set, dict)):
            include = set(include)
        if exclude is not None and not isinstance(exclude, (set, dict)):
            exclude = set(exclude)
        if isinstance(obj, (BaseModel, may_v1.BaseModel)):
            # TODO: remove when deprecating Pydantic v1
            encoders: Dict[Any, Any] = {}
            if isinstance(obj, may_v1.BaseModel):
                encoders = getattr(obj.__config__, "json_encoders", {})  # type: ignore[attr-defined]
                if custom_encoder:
                    encoders = {**encoders, **custom_encoder}
            obj_dict = _model_dump(
                obj,
                mode="json",
                include=include,
                exclude=exclude,
                by_alias=by_alias,
                exclude_unset=exclude_unset,
                exclude_none=exclude_none,
                exclude_defaults=exclude_defaults,
            )
            if "__root__" in obj_dict:
                obj_dict = obj_dict["__root__"]
            return jsonable_encoder(
                obj_dict,
                exclude_none=exclude_none,
                exclude_defaults=exclude_defaults,
                # TODO: remove when deprecating Pydantic v1
                custom_encoder=encoders,
                sqlalchemy_safe=sqlalchemy_safe,
            )
        if dataclasses.is_dataclass(obj):
            assert not isinstance(obj, type)
            obj_dict = dataclasses.asdict(obj)
            return jsonable_encoder(
                obj_dict,
                include=include,
                exclude=exclude,
                by_alias=by_alias,
                exclude_unset=exclude_unset,
                exclude_defaults=exclude_defaults,
                exclude_none=exclude_none,
                custom_encoder=custom_encoder,
                sqlalchemy_safe=sqlalchemy_safe,
            )
        if isinstance(obj, Enum):
            return obj.value
        if isinstance(obj, PurePath):
            return str(obj)
        if isinstance(obj, (str, int, float, type(None))):
            return obj
        if _is_undefined(obj):
            return None
        if isinstance(obj, dict):
            encoded_dict = {}
            allowed_keys = set(obj.keys())
            if include is not None:
                allowed_keys &= set(include)
            if exclude is not None:
                allowed_keys -= set(exclude)
            for key, value in obj.items():
                if (
                    (
                        not sqlalchemy_safe
                        or (not isinstance(key, str))
                        or (not key.startswith("_sa"))
                    )
                    and (value is not None or not exclude_none)
                    and key in allowed_keys
                ):
                    encoded_key = jsonable_encoder(
                        key,
                        by_alias=by_alias,
                        exclude_unset=exclude_unset,
                        exclude_none=exclude_none,
                        custom_encoder=custom_encoder,
                        sqlalchemy_safe=sqlalchemy_safe,
                    )
                    encoded_value = jsonable_encoder(
                        value,
                        by_alias=by_alias,
                        exclude_unset=exclude_unset,
                        exclude_none=exclude_none,
                        custom_encoder=custom_encoder,
                        sqlalchemy_safe=sqlalchemy_safe,
                    )
                    encoded_dict[encoded_key] = encoded_value
            return encoded_dict
        if isinstance(obj, (list, set, frozenset, GeneratorType, tuple, deque)):
            encoded_list = []
            for item in obj:
                encoded_list.append(
                    jsonable_encoder(
                        item,
                        include=include,
                        exclude=exclude,
                        by_alias=by_alias,
                        exclude_unset=exclude_unset,
                        exclude_defaults=exclude_defaults,
                        exclude_none=exclude_none,
                        custom_encoder=custom_encoder,
                        sqlalchemy_safe=sqlalchemy_safe,
                    )
                )
            return encoded_list
    
        if type(obj) in ENCODERS_BY_TYPE:
            return ENCODERS_BY_TYPE[type(obj)](obj)
        for encoder, classes_tuple in encoders_by_class_tuples.items():
            if isinstance(obj, classes_tuple):
                return encoder(obj)
    
        try:
            data = dict(obj)
        except Exception as e:
            errors: List[Exception] = []
            errors.append(e)
            try:
                data = vars(obj)
            except Exception as e:
                errors.append(e)
>               raise ValueError(errors) from e
E               ValueError: [TypeError("'coroutine' object is not iterable"), TypeError('vars() argument must have __dict__ attribute')]

fastapi/encoders.py:345: ValueError
=============================== warnings summary ===============================
../../../../../../kaggle/tmp/envs/overlays/starlette-0.50.0-py3-none-any/starlette/testclient.py:45
  /kaggle/tmp/envs/overlays/starlette-0.50.0-py3-none-any/starlette/testclient.py:45: DeprecationWarning: The anyio.abc.BlockingPortal alias is deprecated, use anyio.from_thread.BlockingPortal instead.
    _PortalFactoryType = Callable[[], AbstractContextManager[anyio.abc.BlockingPortal]]

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 10 passed, 1 warning in 0.97s
<sys>:0: RuntimeWarning: coroutine 'ClassInstanceAsyncWrappedDep.__call__' was never awaited