failed WRONG_FIX wrong_fix ยท 31 tool calls ยท 174 s ยท fastapi/fastapi
๐ 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 | Arguments | Result |
|---|---|---|---|
| 1 | run_command | {
"command": "grep -rn \"async\" fastapi/utils.py | head -20"
} | {"status": "ok", "stdout": "", "exit_code": 0}full{"status": "ok", "stdout": "", "exit_code": 0} |
| 2 | run_command | {
"command": "grep -rn \"is_async_func\" fastapi | head -20"
} | {"status": "ok", "stdout": "", "exit_code": 0}full{"status": "ok", "stdout": "", "exit_code": 0} |
| 3 | run_command | {
"command": "grep -rn \"is_coroutine_function\" fastapi | head -20"
} | {"status": "ok", "stdout": "", "exit_code": 0}full{"status": "ok", "stdout": "", "exit_code": 0} |
| 4 | run_command | {
"command": "grep -rn \"inspect.iscoroutinefunction\" fastapi | head -20"
} | {"status": "ok", "stdout": "", "exit_code": 0}full{"status": "ok", "stdout": "", "exit_code": 0} |
| 5 | run_command | {
"command": "grep -rn \"functools.wraps\" fastapi | head -20"
} | {"status": "ok", "stdout": "", "exit_code": 0}full{"status": "ok", "stdout": "", "exit_code": 0} |
| 6 | run_command | {
"command": "grep -rn \"functools.partial\" fastapi | head -20"
} | {"status": "ok", "stdout": "fastapi/routing.py:92: func if is_async_callable(func) else functools.partial(run_in_threadpool, func) # type:ignore\n", "exit_code"full{"status": "ok", "stdout": "fastapi/routing.py:92: func if is_async_callable(func) else functools.partial(run_in_threadpool, func) # type:ignore\n", "exit_code": 0} |
| 7 | read_file | {
"filepath": "fastapi/routing.py",
"start_line": 80,
"end_line": 110
} | {"status": "ok", "filepath": "fastapi/routing.py", "content": "\n\n# Copy of starlette.routing.request_response modified to include the\n# dependencies' AsyncExfull{"status": "ok", "filepath": "fastapi/routing.py", "content": "\n\n# Copy of starlette.routing.request_response modified to include the\n# dependencies' AsyncExitStack\ndef request_response(\n func: Callable[[Request], Union[Awaitable[Response], Response]],\n) -> ASGIApp:\n \"\"\"\n Takes a function or coroutine `func(request) -> response`,\n and returns an ASGI application.\n \"\"\"\n f: Callable[[Request], Awaitable[Response]] = (\n func if is_async_callable(func) else functools.partial(run_in_threadpool, func) # type:ignore\n )\n\n async def app(scope: Scope, receive: Receive, send: Send) -> None:\n request = Request(scope, receive, send)\n\n async def app(scope: Scope, receive: Receive, send: Send) -> None:\n # Starts customization\n response_awaited = False\n async with AsyncExitStack() as request_stack:\n scope[\"fastapi_inner_astack\"] = request_stack\n async with AsyncExitStack() as function_stack:\n scope[\"fastapi_function_astack\"] = function_stack\n response = await f(request)\n await response(scope, receive, send)\n # Continues customization\n response_awaited = True\n if not response_awaited:\n raise FastAPIError(\n", "start_line": 80, "end_line": 110, "total_lines": 4517, "is_truncated": false} |
| 8 | run_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 fufull{"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} |
| 9 | run_command | {
"command": "grep -rn \"get_async_func\" fastapi | head -20"
} | {"status": "ok", "stdout": "", "exit_code": 0}full{"status": "ok", "stdout": "", "exit_code": 0} |
| 10 | run_command | {
"command": "grep -rn \"async\" fastapi/dependencies/utils.py | head -20"
} | {"status": "ok", "stdout": "54: asynccontextmanager,\n270: (dependant.is_gen_callable or dependant.is_async_gen_callable)\n547:async def _solve_generator(\n553:full{"status": "ok", "stdout": "54: asynccontextmanager,\n270: (dependant.is_gen_callable or dependant.is_async_gen_callable)\n547:async def _solve_generator(\n553: elif dependant.is_async_gen_callable:\n554: cm = asynccontextmanager(dependant.call)(**sub_values)\n555: return await stack.enter_async_context(cm)\n567:async def solve_dependencies(\n578: async_exit_stack: AsyncExitStack,\n626: async_exit_stack=async_exit_stack,\n636: use_sub_dependant.is_gen_callable or use_sub_dependant.is_async_gen_callable\n877:async def _extract_form_body(\n901: async def process_fn(\n907: async with anyio.create_task_group() as tg:\n924:async def request_body_to_args(\n", "exit_code": 0} |
| 11 | read_file | {
"end_line": 300,
"filepath": "fastapi/dependencies/utils.py",
"start_line": 200
} | {"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": " kind=param.kind,\n default=param.default,\n annotation=get_typed_annotation(param.annfull{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": " kind=param.kind,\n default=param.default,\n annotation=get_typed_annotation(param.annotation, globalns),\n )\n for param in signature.parameters.values()\n ]\n typed_signature = inspect.Signature(typed_params)\n return typed_signature\n\n\ndef get_typed_annotation(annotation: Any, globalns: Dict[str, Any]) -> Any:\n if isinstance(annotation, str):\n annotation = ForwardRef(annotation)\n annotation = evaluate_forwardref(annotation, globalns, globalns)\n if annotation is type(None):\n return None\n return annotation\n\n\ndef get_typed_return_annotation(call: Callable[..., Any]) -> Any:\n signature = inspect.signature(call)\n unwrapped = inspect.unwrap(call)\n annotation = signature.return_annotation\n\n if annotation is inspect.Signature.empty:\n return None\n\n globalns = getattr(unwrapped, \"__globals__\", {})\n return get_typed_annotation(annotation, globalns)\n\n\ndef get_dependant(\n *,\n path: str,\n call: Callable[..., Any],\n 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 scope: Union[Literal[\"function\", \"request\"], None] = None,\n) -> Dependant:\n dependant = Dependant(\n call=call,\n name=name,\n path=path,\n use_cache=use_cache,\n scope=scope,\n own_oauth_scopes=own_oauth_scopes,\n parent_oauth_scopes=parent_oauth_scopes,\n )\n current_scopes = (parent_oauth_scopes or []) + (own_oauth_scopes or [])\n path_param_names = get_path_param_names(path)\n endpoint_signature = get_typed_signature(call)\n signature_params = endpoint_signature.parameters\n if isinstance(call, SecurityBase):\n security_requirement = SecurityRequirement(\n security_scheme=call |
| 12 | run_command | {
"command": "grep -rn \"class Dependant\" fastapi/dependencies/models.py"
} | {"status": "ok", "stdout": "25:class Dependant:\n", "exit_code": 0}full{"status": "ok", "stdout": "25:class Dependant:\n", "exit_code": 0} |
| 13 | read_file | {
"end_line": 100,
"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 |
| 14 | read_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(sfull{"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} |
| 15 | run_command | {
"command": "grep -rn \"is_async_callable\" fastapi/routing.py"
} | {"status": "ok", "stdout": "65:from starlette._utils import is_async_callable\n92: func if is_async_callable(func) else functools.partial(run_in_threadpool, funfull{"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} |
| 16 | run_command | {
"command": "cat << 'EOF' > /tmp/test_async_callable.py\nimport asyncio\nimport functools\nimport inspect\nfrom starlette._utils import is_async_callable\n\nasync def async_func():\n pass\n\ndef sync_func():\n pass\n\ndef wrap_async(f):\n @functools.wraps(f)\n async def wrapper(*args, **kwargs):\n return await f(*args, **kwargs)\n return wrapper\n\ndef wrap_sync(f):\n @functools.wraps(f)\n def wrapper(*args, **kwargs):\n return f(*args, **kwargs)\n return wrapper\n\n# Test 1: Simple async\nprint(f\"Simple async: {is_async_callable(async_func)}\") # Expected: True\n# Test 2: Simple sync\nprint(f\"Simple sync: {is_async_callable(sync_func)}\") # Expected: False\n# Test 3: wrapped async with async wrapper\nwrapped_async_async = wrap_async(async_func)\nprint(f\"Wrapped async (async wrapper): {is_async_callable(wrapped_async_async)}\") # Expected: True\n# Test 4: wrapped async with sync wrapper\nwrapped_async_sync = wrap_sync(async_func)\nprint(f\"Wrapped async (sync wrapper): {is_async_callable(wrapped_async_sync)}\") # Current: False? Task says it should be True if either is async.\n# Test 5: wrapped sync with async wrapper\nwrapped_sync_async = wrap_async(sync_func)\nprint(f\"Wrapped sync (async wrapper): {is_async_callable(wrapped_sync_async)}\") # Expected: True\n# Test 6: partial of async\npartial_async = functools.partial(async_func)\nprint(f\"Partial async: {is_async_callable(partial_async)}\") # Expected: True\n# Test 7: partial of wrapped async\npartial_wrapped_async = functools.partial(wrapped_async_sync)\nprint(f\"Partial wrapped async: {is_async_callable(partial_wrapped_async)}\")\n\nEOF\npython3 /tmp/test_async_callable.py"
} | {"status": "ok", "stdout": "Simple async: True\nSimple sync: False\nWrapped async (async wrapper): True\nWrapped async (sync wrapper): False\nWrapped sync (asynfull{"status": "ok", "stdout": "Simple async: True\nSimple sync: False\nWrapped async (async wrapper): True\nWrapped async (sync wrapper): False\nWrapped sync (async wrapper): True\nPartial async: True\nPartial wrapped async: False\n", "exit_code": 0} |
| 17 | read_file | {
"filepath": "fastapi/dependencies/models.py",
"start_line": 68,
"end_line": 116
} | {"status": "ok", "filepath": "fastapi/dependencies/models.py", "content": " def _uses_scopes(self) -> bool:\n if self.own_oauth_scopes:\n return True\n if self.full{"status": "ok", "filepath": "fastapi/dependencies/models.py", "content": " def _uses_scopes(self) -> bool:\n if self.own_oauth_scopes:\n 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": 68, "end_line": 116, "total_lines": 116, "is_truncated" |
| 18 | run_command | {
"command": "grep -rn \"is_async_callable\" fastapi/routing.py"
} | {"status": "ok", "stdout": "65:from starlette._utils import is_async_callable\n92: func if is_async_callable(func) else functools.partial(run_in_threadpool, funfull{"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} |
| 19 | run_command | {
"command": "grep -rn \"is_async_callable\" ."
} | {"status": "ok", "stdout": "./fastapi/routing.py:65:from starlette._utils import is_async_callable\n./fastapi/routing.py:92: func if is_async_callable(func) elsfull{"status": "ok", "stdout": "./fastapi/routing.py:65:from starlette._utils import is_async_callable\n./fastapi/routing.py:92: func if is_async_callable(func) else functools.partial(run_in_threadpool, func) # type:ignore\n", "exit_code": 0} |
| 20 | run_command | {
"command": "grep -rn \"run_in_threadpool\" fastapi | head -20"
} | {"status": "ok", "stdout": "fastapi/dependencies/utils.py:67:from starlette.concurrency import run_in_threadpool\nfastapi/dependencies/utils.py:649: solved = awfull{"status": "ok", "stdout": "fastapi/dependencies/utils.py:67:from starlette.concurrency import run_in_threadpool\nfastapi/dependencies/utils.py:649: solved = await run_in_threadpool(call, **solved_result.values)\nfastapi/routing.py:66:from starlette.concurrency import run_in_threadpool\nfastapi/routing.py:92: func if is_async_callable(func) else functools.partial(run_in_threadpool, func) # type:ignore\nfastapi/routing.py:240: value, errors_ = await run_in_threadpool(\nfastapi/routing.py:286: return await run_in_threadpool(dependant.call, **values)\nfastapi/concurrency.py:7:from starlette.concurrency import run_in_threadpool as run_in_threadpool # noqa\nfastapi/concurrency.py:27: yield await run_in_threadpool(cm.__enter__)\n", "exit_code": 0} |
| 21-22 ร2 | read_file | {
"end_line": 650,
"filepath": "fastapi/dependencies/utils.py",
"start_line": 600
} | {"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": " use_sub_dependant = sub_dependant\n if (\n dependency_overrides_provider\n and dependfull{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": " use_sub_dependant = sub_dependant\n if (\n dependency_overrides_provider\n and dependency_overrides_provider.dependency_overrides\n ):\n original_call = sub_dependant.call\n call = getattr(\n dependency_overrides_provider, \"dependency_overrides\", {}\n ).get(original_call, original_call)\n use_path: str = sub_dependant.path # type: ignore\n use_sub_dependant = get_dependant(\n path=use_path,\n call=call,\n name=sub_dependant.name,\n parent_oauth_scopes=sub_dependant.oauth_scopes,\n scope=sub_dependant.scope,\n )\n\n solved_result = await solve_dependencies(\n request=request,\n dependant=use_sub_dependant,\n body=body,\n background_tasks=background_tasks,\n response=response,\n dependency_overrides_provider=dependency_overrides_provider,\n dependency_cache=dependency_cache,\n async_exit_stack=async_exit_stack,\n embed_body_fields=embed_body_fields,\n )\n background_tasks = solved_result.background_tasks\n if solved_result.errors:\n errors.extend(solved_result.errors)\n continue\n if sub_dependant.use_cache and sub_dependant.cache_key in dependency_cache:\n solved = dependency_cache[sub_dependant.cache_key]\n elif (\n use_sub_dependant.is_gen_callable or use_sub_dependant.is_async_gen_callable\n ):\n use_astack = request_astack\n if sub_dependant.scope == \"function\":\n use_astack = function_astack\n solved = await _solve_generator(\n dependant=use_sub_dependant,\n stack=use_astack,\n sub_values=solved_result.values |
| 23 | read_file | {
"end_line": 116,
"filepath": "fastapi/dependencies/models.py",
"start_line": 68
} | {"status": "ok", "filepath": "fastapi/dependencies/models.py", "content": " def _uses_scopes(self) -> bool:\n if self.own_oauth_scopes:\n return True\n if self.full{"status": "ok", "filepath": "fastapi/dependencies/models.py", "content": " def _uses_scopes(self) -> bool:\n if self.own_oauth_scopes:\n 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": 68, "end_line": 116, "total_lines": 116, "is_truncated" |
| 24 | edit_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 unwrapped = self.call\n while True:\n if isinstance(unwrapped, partial):\n unwrapped = unwrapped.func\n else:\n unwrapped = inspect.unwrap(unwrapped)\n if unwrapped == inspect.unwrap(unwrapped) and not isinstance(unwrapped, partial):\n break\n return unwrapped",
"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"
} | {"status": "ok", "filepath": "fastapi/dependencies/models.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/dependencies/models.py\n+++ b/fastafull{"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,9 +79,14 @@\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+ unwrapped = self.call\n+ while True:\n+ if isinstance(unwrapped, partial):\n+ unwrapped = unwrapped.func\n+ else:\n+ unwrapped = inspect.unwrap(unwrapped)\n+ if unwrapped == inspect.unwrap(unwrapped) and not isinstance(unwrapped, partial):\n+ break\n return unwrapped\n \n @cached_property\n", "is_truncated": false} |
| 25 | edit_file | {
"filepath": "fastapi/dependencies/models.py",
"new_string": " @cached_property\n def is_coroutine_callable(self) -> bool:\n # Check both the original call and the unwrapped call to see if either is async.\n # This handles the case where a sync wrapper wraps an async function.\n if self.is_coroutine_callable_direct(self.call) or self.is_coroutine_callable_direct(self._unwrapped_call):\n return True\n return False\n\n def is_coroutine_callable_direct(self, call: Any) -> bool:\n if call is None:\n return False\n if inspect.isroutine(call):\n return iscoroutinefunction(call)\n if inspect.isclass(call):\n return False\n dunder_call = getattr(call, \"__call__\", None) # noqa: B004\n return iscoroutinefunction(dunder_call)",
"old_string": " @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/fastafull{"status": "ok", "filepath": "fastapi/dependencies/models.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/dependencies/models.py\n+++ b/fastapi/dependencies/models.py\n@@ -105,11 +105,20 @@\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+ # Check both the original call and the unwrapped call to see if either is async.\n+ # This handles the case where a sync wrapper wraps an async function.\n+ if self.is_coroutine_callable_direct(self.call) or self.is_coroutine_callable_direct(self._unwrapped_call):\n+ return True\n+ return False\n+\n+ def is_coroutine_callable_direct(self, call: Any) -> bool:\n+ if call is None:\n return False\n- dunder_call = getattr(self._unwrapped_call, \"__call__\", None) # noqa: B004\n+ if inspect.isroutine(call):\n+ return iscoroutinefunction(call)\n+ if inspect.isclass(call):\n+ return False\n+ dunder_call = getattr(call, \"__call__\", None) # noqa: B004\n return iscoroutinefunction(dunder_call)\n \n @cached_property\n", "is_truncated": false} |
| 26 | run_command | {
"command": "cat << 'EOF' > /tmp/test_dependant.py\nimport asyncio\nimport functools\nimport inspect\nfrom fastapi.dependencies.models import Dependant\n\nasync def async_func():\n pass\n\ndef sync_func():\n pass\n\ndef wrap_async(f):\n @functools.wraps(f)\n async def wrapper(*args, **kwargs):\n return await f(*args, **kwargs)\n return wrapper\n\ndef wrap_sync(f):\n @functools.wraps(f)\n def wrapper(*args, **kwargs):\n return f(*args, **kwargs)\n return wrapper\n\n# Test 1: Simple async\nd1 = Dependant(call=async_func)\nprint(f\"Simple async: {d1.is_coroutine_callable}\") # Expected: True\n# Test 2: Simple sync\nd2 = Dependant(call=sync_func)\nprint(f\"Simple sync: {d2.is_coroutine_callable}\") # Expected: False\n# Test 3: wrapped async with async wrapper\nwrapped_async_async = wrap_async(async_func)\nd3 = Dependant(call=wrapped_async_async)\nprint(f\"Wrapped async (async wrapper): {d3.is_coroutine_callable}\") # Expected: True\n# Test 4: wrapped async with sync wrapper\nwrapped_async_sync = wrap_sync(async_func)\nd4 = Dependant(call=wrapped_async_sync)\nprint(f\"Wrapped async (sync wrapper): {d4.is_coroutine_callable}\") # Expected: True\n# Test 5: wrapped sync with async wrapper\nwrapped_sync_async = wrap_async(sync_func)\nd5 = Dependant(call=wrapped_sync_async)\nprint(f\"Wrapped sync (async wrapper): {d5.is_coroutine_callable}\") # Expected: True\n# Test 6: partial of async\npartial_async = functools.partial(async_func)\nd6 = Dependant(call=partial_async)\nprint(f\"Partial async: {d6.is_coroutine_callable}\") # Expected: True\n# Test 7: partial of wrapped async\npartial_wrapped_async = functools.partial(wrapped_async_sync)\nd7 = Dependant(call=partial_wrapped_async)\nprint(f\"Partial wrapped async: {d7.is_coroutine_callable}\") # Expected: True\nEOF\npython3 /tmp/test_dependant.py"
} | {"status": "ok", "stdout": "Simple async: True\nSimple sync: False\nWrapped async (async wrapper): True\nWrapped async (sync wrapper): True\nWrapped sync (asyncfull{"status": "ok", "stdout": "Simple async: True\nSimple sync: False\nWrapped async (async wrapper): True\nWrapped async (sync wrapper): True\nWrapped sync (async wrapper): True\nPartial async: True\nPartial wrapped async: True\n", "exit_code": 0} |
| 27 | run_command | {
"command": "grep -rn \"is_async_callable\" fastapi/routing.py"
} | {"status": "ok", "stdout": "65:from starlette._utils import is_async_callable\n92: func if is_async_callable(func) else functools.partial(run_in_threadpool, funfull{"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} |
| 28 | run_command | {
"command": "cat << 'EOF' > /tmp/test_starlette_async_callable.py\nimport asyncio\nimport functools\nimport inspect\nfrom starlette._utils import is_async_callable\n\nasync def async_func():\n pass\n\ndef sync_func():\n pass\n\ndef wrap_async(f):\n @functools.wraps(f)\n async def wrapper(*args, **kwargs):\n return await f(*args, **kwargs)\n return wrapper\n\ndef wrap_sync(f):\n @functools.wraps(f)\n def wrapper(*args, **kwargs):\n return f(*args, **kwargs)\n return wrapper\n\n# Test 1: Simple async\nprint(f\"Simple async: {is_async_callable(async_func)}\") \n# Test 2: Simple sync\nprint(f\"Simple sync: {is_async_callable(sync_func)}\") \n# Test 3: wrapped async with async wrapper\nwrapped_async_async = wrap_async(async_func)\nprint(f\"Wrapped async (async wrapper): {is_async_callable(wrapped_async_async)}\") \n# Test 4: wrapped async with sync wrapper\nwrapped_async_sync = wrap_sync(async_func)\nprint(f\"Wrapped async (sync wrapper): {is_async_callable(wrapped_async_sync)}\") \n# Test 5: wrapped sync with async wrapper\nwrapped_sync_async = wrap_async(sync_func)\nprint(f\"Wrapped sync (async wrapper): {is_async_callable(wrapped_sync_async)}\") \n# Test 6: partial of async\npartial_async = functools.partial(async_func)\nprint(f\"Partial async: {is_async_callable(partial_async)}\") \n# Test 7: partial of wrapped async\npartial_wrapped_async = functools.partial(wrapped_async_sync)\nprint(f\"Partial wrapped async: {is_async_callable(partial_wrapped_async)}\") \nEOF\npython3 /tmp/test_starlette_async_callable.py"
} | {"status": "ok", "stdout": "Simple async: True\nSimple sync: False\nWrapped async (async wrapper): True\nWrapped async (sync wrapper): False\nWrapped sync (asynfull{"status": "ok", "stdout": "Simple async: True\nSimple sync: False\nWrapped async (async wrapper): True\nWrapped async (sync wrapper): False\nWrapped sync (async wrapper): True\nPartial async: True\nPartial wrapped async: False\n", "exit_code": 0} |
| 29 | run_command | {
"command": "grep -rn \"is_async_callable\" fastapi/routing.py"
} | {"status": "ok", "stdout": "65:from starlette._utils import is_async_callable\n92: func if is_async_callable(func) else functools.partial(run_in_threadpool, funfull{"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} |
| 30 | edit_file | {
"filepath": "fastapi/routing.py",
"new_string": "def is_async_callable(callable_):\n \"\"\"\n Check if a callable is async.\n \"\"\"\n if callable_ is None:\n return False\n \n # Unwrapping the callable to handle functools.wraps and functools.partial\n unwrapped = callable_\n while True:\n if isinstance(unwrapped, functools.partial):\n unwrapped = unwrapped.func\n else:\n unwrapped = inspect.unwrap(unwrapped)\n if unwrapped == inspect.unwrap(unwrapped) and not isinstance(unwrapped, functools.partial):\n break\n \n # The function is async if either the original or the unwrapped version is async.\n from asyncio import iscoroutinefunction\n \n def check_async(func):\n if inspect.isroutine(func):\n return iscoroutinefunction(func)\n if inspect.isclass(func):\n return False\n dunder_call = getattr(func, \"__call__\", None)\n return iscoroutinefunction(dunder_call)\n\n return check_async(callable_) or check_async(unwrapped)",
"old_string": "from starlette._utils import is_async_callable"
} | {"status": "ok", "filepath": "fastapi/routing.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/routing.py\n+++ b/fastapi/routing.py\n@@ -62,7 full{"status": "ok", "filepath": "fastapi/routing.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/routing.py\n+++ b/fastapi/routing.py\n@@ -62,7 +62,35 @@\n from pydantic import BaseModel\n from starlette import routing\n from starlette._exception_handler import wrap_app_handling_exceptions\n-from starlette._utils import is_async_callable\n+def is_async_callable(callable_):\n+ \"\"\"\n+ Check if a callable is async.\n+ \"\"\"\n+ if callable_ is None:\n+ return False\n+ \n+ # Unwrapping the callable to handle functools.wraps and functools.partial\n+ unwrapped = callable_\n+ while True:\n+ if isinstance(unwrapped, functools.partial):\n+ unwrapped = unwrapped.func\n+ else:\n+ unwrapped = inspect.unwrap(unwrapped)\n+ if unwrapped == inspect.unwrap(unwrapped) and not isinstance(unwrapped, functools.partial):\n+ break\n+ \n+ # The function is async if either the original or the unwrapped version is async.\n+ from asyncio import iscoroutinefunction\n+ \n+ def check_async(func):\n+ if inspect.isroutine(func):\n+ return iscoroutinefunction(func)\n+ if inspect.isclass(func):\n+ return False\n+ dunder_call = getattr(func, \"__call__\", None)\n+ return iscoroutinefunction(dunder_call)\n+\n+ return check_async(callable_) or check_async(unwrapped)\n from starlette.concurrency import run_in_threadpool\n from starlette.exceptions import HTTPException\n from starlette.requests import Request\n", "is_truncated": false} |
| 31 | run_command | {
"command": "cat << 'EOF' > /tmp/test_routing_async_callable.py\nimport asyncio\nimport functools\nimport inspect\nfrom fastapi.routing import is_async_callable\n\nasync def async_func():\n pass\n\ndef sync_func():\n pass\n\ndef wrap_async(f):\n @functools.wraps(f)\n async def wrapper(*args, **kwargs):\n return await f(*args, **kwargs)\n return wrapper\n\ndef wrap_sync(f):\n @functools.wraps(f)\n def wrapper(*args, **kwargs):\n return f(*args, **kwargs)\n return wrapper\n\n# Test 1: Simple async\nprint(f\"Simple async: {is_async_callable(async_func)}\") \n# Test 2: Simple sync\nprint(f\"Simple sync: {is_async_callable(sync_func)}\") \n# Test 3: wrapped async with async wrapper\nwrapped_async_async = wrap_async(async_func)\nprint(f\"Wrapped async (async wrapper): {is_async_callable(wrapped_async_async)}\") \n# Test 4: wrapped async with sync wrapper\nwrapped_async_sync = wrap_sync(async_func)\nprint(f\"Wrapped async (sync wrapper): {is_async_callable(wrapped_async_sync)}\") \n# Test 5: wrapped sync with async wrapper\nwrapped_sync_async = wrap_async(sync_func)\nprint(f\"Wrapped sync (async wrapper): {is_async_callable(wrapped_sync_async)}\") \n# Test 6: partial of async\npartial_async = functools.partial(async_func)\nprint(f\"Partial async: {is_async_callable(partial_async)}\") \n# Test 7: partial of wrapped async\npartial_wrapped_async = functools.partial(wrapped_async_sync)\nprint(f\"Partial wrapped async: {is_async_callable(partial_wrapped_async)}\") \nEOF\npython3 /tmp/test_routing_async_callable.py"
} | {"status": "ok", "stdout": "Simple async: True\nSimple sync: False\nWrapped async (async wrapper): True\nWrapped async (sync wrapper): True\nWrapped sync (asyncfull{"status": "ok", "stdout": "Simple async: True\nSimple sync: False\nWrapped async (async wrapper): True\nWrapped async (sync wrapper): True\nWrapped sync (async wrapper): True\nPartial async: True\nPartial wrapped async: True\n", "exit_code": 0} |
| 32 | submit_patch | [] | {"status": "ok", "patch_size": 3495, "files_changed": 2}full{"status": "ok", "patch_size": 3495, "files_changed": 2} |
diff --git a/fastapi/dependencies/models.py b/fastapi/dependencies/models.py
index 2a4d9a01..0e99d73a 100644
--- a/fastapi/dependencies/models.py
+++ b/fastapi/dependencies/models.py
@@ -79,9 +79,14 @@ 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
+ unwrapped = self.call
+ while True:
+ if isinstance(unwrapped, partial):
+ unwrapped = unwrapped.func
+ else:
+ unwrapped = inspect.unwrap(unwrapped)
+ if unwrapped == inspect.unwrap(unwrapped) and not isinstance(unwrapped, partial):
+ break
return unwrapped
@cached_property
@@ -100,11 +105,20 @@ class Dependant:
@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):
+ # Check both the original call and the unwrapped call to see if either is async.
+ # This handles the case where a sync wrapper wraps an async function.
+ if self.is_coroutine_callable_direct(self.call) or self.is_coroutine_callable_direct(self._unwrapped_call):
+ return True
+ return False
+
+ def is_coroutine_callable_direct(self, call: Any) -> bool:
+ if call is None:
return False
- dunder_call = getattr(self._unwrapped_call, "__call__", None) # noqa: B004
+ if inspect.isroutine(call):
+ return iscoroutinefunction(call)
+ if inspect.isclass(call):
+ return False
+ dunder_call = getattr(call, "__call__", None) # noqa: B004
return iscoroutinefunction(dunder_call)
@cached_property
diff --git a/fastapi/routing.py b/fastapi/routing.py
index c10175b1..51f10dc3 100644
--- a/fastapi/routing.py
+++ b/fastapi/routing.py
@@ -62,7 +62,35 @@ from fastapi.utils import (
from pydantic import BaseModel
from starlette import routing
from starlette._exception_handler import wrap_app_handling_exceptions
-from starlette._utils import is_async_callable
+def is_async_callable(callable_):
+ """
+ Check if a callable is async.
+ """
+ if callable_ is None:
+ return False
+
+ # Unwrapping the callable to handle functools.wraps and functools.partial
+ unwrapped = callable_
+ while True:
+ if isinstance(unwrapped, functools.partial):
+ unwrapped = unwrapped.func
+ else:
+ unwrapped = inspect.unwrap(unwrapped)
+ if unwrapped == inspect.unwrap(unwrapped) and not isinstance(unwrapped, functools.partial):
+ break
+
+ # The function is async if either the original or the unwrapped version is async.
+ from asyncio import iscoroutinefunction
+
+ def check_async(func):
+ if inspect.isroutine(func):
+ return iscoroutinefunction(func)
+ if inspect.isclass(func):
+ return False
+ dunder_call = getattr(func, "__call__", None)
+ return iscoroutinefunction(dunder_call)
+
+ return check_async(callable_) or check_async(unwrapped)
from starlette.concurrency import run_in_threadpool
from starlette.exceptions import HTTPException
from starlette.requests import Request
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.91s
<sys>:0: RuntimeWarning: coroutine 'ClassInstanceAsyncWrappedDep.__call__' was never awaited