failed WRONG_FIX UNSUBMITTED wrong_fix_unsubmitted(budget) ยท 26 tool calls ยท 309 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\" --include=\"*.py\" fastapi/ | grep \"is_async_gen\" -A 5 | head -20 || echo '<IMPORTANT> NO MATCH FOUND. DO NOT TRY SAME COMMAND.'"
} | {"status": "ok", "stdout": "fastapi/dependencies/models.py:95: def is_async_gen_callable(self) -> bool:\nfastapi/dependencies/models.py:96: if inspect.isasyncgefull{"status": "ok", "stdout": "fastapi/dependencies/models.py:95: def is_async_gen_callable(self) -> bool:\nfastapi/dependencies/models.py:96: if inspect.isasyncgenfunction(self._unwrapped_call):\nfastapi/dependencies/models.py:99: return inspect.isasyncgenfunction(dunder_call)\nfastapi/dependencies/models.py:114: if self.is_gen_callable or self.is_async_gen_callable:\nfastapi/dependencies/utils.py:54: asynccontextmanager,\nfastapi/dependencies/utils.py:270: (dependant.is_gen_callable or dependant.is_async_gen_callable)\nfastapi/dependencies/utils.py:547:async def _solve_generator(\nfastapi/dependencies/utils.py:553: elif dependant.is_async_gen_callable:\nfastapi/dependencies/utils.py:554: cm = asynccontextmanager(dependant.call)(**sub_values)\nfastapi/dependencies/utils.py:555: return await stack.enter_async_context(cm)\nfastapi/dependencies/utils.py:567:async def solve_dependencies(\nfastapi/dependencies/utils.py:578: async_exit_stack: AsyncExitStack,\nfastapi/dependencies/utils.py:626: async_exit_stack=async_exit_stack,\nfastapi/dependencies/utils.py:636: use_sub_dependant.is_gen_callable or use_sub_dependant.is_async_gen_callable\nfastapi/dependencies/utils.py:877:async def _extract_form_body(\nfastapi/dependencies/utils.py:901: async def process_fn(\nfastapi/dependencies/utils.py:907: async with anyio.create_task_group() as tg:\nfastapi/dependencies/utils.py:924:async def request_body_to_args(\nfastapi/routing.py:6:from contextlib import AsyncExitStack, asynccontextmanager\n", "stderr": "", "exit_code": 0} |
| 2 | run_command | {
"command": "sed -n '80,120p' fastapi/dependencies/models.py"
} | {"status": "ok", "stdout": " if self.call is None:\n return self.call # pragma: no cover\n unwrapped = inspect.unwrap(self.call)\n if isinstance(unwrapped, partfull{"status": "ok", "stdout": " 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", "stderr": "", "exit_code": 0} |
| 3 | run_command | {
"command": "sed -n '60,80p' fastapi/dependencies/models.py"
} | {"status": "ok", "stdout": " )\n return (\n self.call,\n scopes_for_cache,\n self.computed_scope or \"\",\n )\n\n @cached_property\n def _uses_scopes(self) -> bfull{"status": "ok", "stdout": " )\n return (\n self.call,\n scopes_for_cache,\n self.computed_scope or \"\",\n )\n\n @cached_property\n 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", "stderr": "", "exit_code": 0} |
| 4 | run_command | {
"command": "grep -rn \"inspect.unwrap\" fastapi/ | head -20 || echo '<IMPORTANT> NO MATCH FOUND. DO NOT TRY SAME COMMAND.'"
} | {"status": "ok", "stdout": "fastapi/dependencies/models.py:82: unwrapped = inspect.unwrap(self.call)\nfastapi/dependencies/utils.py:195: unwrapped = inspect.unwfull{"status": "ok", "stdout": "fastapi/dependencies/models.py:82: unwrapped = inspect.unwrap(self.call)\nfastapi/dependencies/utils.py:195: unwrapped = inspect.unwrap(call)\nfastapi/dependencies/utils.py:221: unwrapped = inspect.unwrap(call)\n", "stderr": "", "exit_code": 0} |
| 5 | run_command | {
"command": "sed -n '190,230p' fastapi/dependencies/utils.py"
} | {"status": "ok", "stdout": " return path_params + query_params + header_params + cookie_params\n\n\ndef get_typed_signature(call: Callable[..., Any]) -> inspectfull{"status": "ok", "stdout": " return path_params + query_params + header_params + cookie_params\n\n\ndef get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:\n signature = inspect.signature(call)\n unwrapped = inspect.unwrap(call)\n globalns = getattr(unwrapped, \"__globals__\", {})\n typed_params = [\n inspect.Parameter(\n name=param.name,\n 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\n", "stderr": "", "exit_code": 0} |
| 6 | run_command | {
"command": "grep -rn \"inspect.iscoroutinefunction\" fastapi/ | head -20 || echo '<IMPORTANT> NO MATCH FOUND. DO NOT TRY SAME COMMAND.'"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 7 | run_command | {
"command": "grep -rn \"iscoroutinefunction\" fastapi/ | head -20 || echo '<IMPORTANT> NO MATCH FOUND. DO NOT TRY SAME COMMAND.'"
} | {"status": "ok", "stdout": "fastapi/dependencies/models.py:13: from inspect import iscoroutinefunction\nfastapi/dependencies/models.py:15: from asyncio import ifull{"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", "stderr": "", "exit_code": 0} |
| 8 | run_command | {
"command": "grep -rn \"asyncio.iscoroutinefunction\" fastapi/ | head -20 || echo '<IMPORTANT> NO MATCH FOUND. DO NOT TRY SAME COMMAND.'"
} | {"status": "ok", "stdout": "fastapi/routing.py:132: # assert asyncio.iscoroutinefunction(func), \"WebSocket endpoints must be async\"\n", "stderr": "", "exit_cofull{"status": "ok", "stdout": "fastapi/routing.py:132: # assert asyncio.iscoroutinefunction(func), \"WebSocket endpoints must be async\"\n", "stderr": "", "exit_code": 0} |
| 9 | run_command | {
"command": "grep -rn \"is_async_callable\" fastapi/ | head -20 || echo '<IMPORTANT> NO MATCH FOUND. DO NOT TRY SAME COMMAND.'"
} | {"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", "stderr": "", "exit_code": 0} |
| 10 | run_command | {
"command": "sed -n '80,100p' fastapi/routing.py"
} | {"status": "ok", "stdout": "\n\n# Copy of starlette.routing.request_response modified to include the\n# dependencies' AsyncExitStack\ndef request_response(\n fufull{"status": "ok", "stdout": "\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", "stderr": "", "exit_code": 0} |
| 11 | run_command | {
"command": "grep -rn \"is_async_callable\" starlette/ | head -20 || echo '<IMPORTANT> NO MATCH FOUND. DO NOT TRY SAME COMMAND.'"
} | {"status": "ok", "stdout": "", "stderr": "grep: starlette/: No such file or directory\n", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "grep: starlette/: No such file or directory\n", "exit_code": 0} |
| 12 | run_command | {
"command": "# I need to find where is_async_callable is defined. It's imported from starlette._utils.\n# Since starlette is not in /workspace, I cannot check its source.\n# However, the problem statement says we need to fix support for functools.wraps and partial combined.\n# The current implementation of _unwrapped_call in fastapi/dependencies/models.py is:\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# This only unwraps once. If it's a partial of a wrapped function, or a wrapped partial, it might not be enough.\n# Let's see if we need a loop.\n# Also, if either the wrapper or the original function is async, it should be considered async.\n\n# Let's check fastapi/dependencies/models.py again.\nsed -n '70,110p' fastapi/dependencies/models.py"
} | {"status": "ok", "stdout": " 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_full{"status": "ok", "stdout": " 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", "stderr": "", "exit_code": 0} |
| 13 | run_command | {
"command": "cat > /tmp/repro.py <<'PY'\nimport asyncio\nimport functools\nimport inspect\nfrom typing import Any, Callable\nfrom fastapi.dependencies.models import Dependant\nfrom fastapi.dependencies.utils import solve_dependencies\nfrom fastapi.routing import APIRoute\nfrom fastapi import Request\nfrom starlette.testclient import TestClient\nfrom fastapi import FastAPI\n\ndef is_coroutine_function(func):\n return asyncio.iscoroutinefunction(func)\n\n# Case 1: regular function\ndef regular_fn():\n return \"regular\"\n\n# Case 2: async function\nasync def async_fn():\n return \"async\"\n\n# Case 3: partial of regular\npartial_regular = functools.partial(regular_fn)\n\n# Case 4: partial of async\npartial_async = functools.partial(async_fn)\n\ndef wrap_sync(f):\n @functools.wraps(f)\n def wrapper(*args, **kwargs):\n return f(*args, **kwargs)\n return wrapper\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\n# Case 5: wrapped regular\nwrapped_regular = wrap_sync(regular_fn)\n\n# Case 6: wrapped async\nwrapped_async = wrap_async(async_fn)\n\n# Case 7: partial of wrapped regular\npartial_wrapped_regular = functools.partial(wrapped_regular)\n\n# Case 8: partial of wrapped async\npartial_wrapped_async = functools.partial(wrapped_async)\n\n# Case 9: wrapped partial regular\nwrapped_partial_regular = wrap_sync(partial_regular)\n\n# Case 10: wrapped partial async\nwrapped_partial_async = wrap_async(partial_async)\n\ntest_cases = [\n (\"regular\", regular_fn),\n (\"partial_regular\", partial_regular),\n (\"wrapped_regular\", wrapped_regular),\n (\"partial_wrapped_regular\", partial_wrapped_regular),\n (\"wrapped_partial_regular\", wrapped_partial_regular),\n (\"async\", async_fn),\n (\"partial_async\", partial_async),\n (\"wrapped_async\", wrapped_async),\n (\"partial_wrapped_async\", partial_wrapped_async),\n (\"wrapped_partial_async\", wrapped_partial_async),\n]\n\nfor name, fn in test_cases:\n # Use a dummy Dependant to test _unwrapped_call and the callable properties\n dep = Dependant(call=fn)\n unwrapped = dep._unwrapped_call\n is_async = dep.is_coroutine_callable\n is_async_gen = dep.is_async_gen_callable\n print(f\"{name}: unwrapped={unwrapped}, is_async={is_async}, is_async_gen={is_async_gen}\")\n\nPY\npython3 /tmp/repro.py\n"
} | {"status": "ok", "stdout": "regular: unwrapped=<function regular_fn at 0x103d45f80>, is_async=False, is_async_gen=False\npartial_regular: unwrapped=<function refull{"status": "ok", "stdout": "regular: unwrapped=<function regular_fn at 0x103d45f80>, is_async=False, is_async_gen=False\npartial_regular: unwrapped=<function regular_fn at 0x103d45f80>, is_async=False, is_async_gen=False\nwrapped_regular: unwrapped=<function regular_fn at 0x103d45f80>, is_async=False, is_async_gen=False\npartial_wrapped_regular: unwrapped=<function regular_fn at 0x1053c8fe0>, is_async=False, is_async_gen=False\nwrapped_partial_regular: unwrapped=<function regular_fn at 0x103d45f80>, is_async=False, is_async_gen=False\nasync: unwrapped=<function async_fn at 0x104f014e0>, is_async=True, is_async_gen=False\npartial_async: unwrapped=<function async_fn at 0x104f014e0>, is_async=True, is_async_gen=False\nwrapped_async: unwrapped=<function async_fn at 0x104f014e0>, is_async=True, is_async_gen=False\npartial_wrapped_async: unwrapped=<function async_fn at 0x1053c9080>, is_async=True, is_async_gen=False\nwrapped_partial_async: unwrapped=<function async_fn at 0x104f014e0>, is_async=True, is_async_gen=False\n", "stderr": "", "exit_code": 0} |
| 14 | run_command | {
"command": "cat > /tmp/repro_async_check.py <<'PY'\nimport asyncio\nimport functools\nimport inspect\nfrom fastapi.dependencies.models import Dependant\n\ndef wrap_sync(f):\n @functools.wraps(f)\n def wrapper(*args, **kwargs):\n return f(*args, **kwargs)\n return wrapper\n\nasync def wrap_async(f):\n # This is not a decorator in the usual sense, but let's see.\n # The goal is to test if a wrapper is async but the original is sync.\n pass\n\ndef sync_fn():\n return \"sync\"\n\nasync def async_fn():\n return \"async\"\n\n# Case: sync function wrapped by async wrapper\ndef async_wrapper_for_sync(f):\n @functools.wraps(f)\n async def wrapper(*args, **kwargs):\n return f(*args, **kwargs)\n return wrapper\n\n# Case: async function wrapped by sync wrapper\ndef sync_wrapper_for_async(f):\n @functools.wraps(f)\n def wrapper(*args, **kwargs):\n return asyncio.run(f(*args, **kwargs)) # simplified\n return wrapper\n\nwrapped_sync_in_async = async_wrapper_for_sync(sync_fn)\nwrapped_async_in_sync = sync_wrapper_for_async(async_fn)\n\nprint(f\"wrapped_sync_in_async: is_async={Dependant(call=wrapped_sync_in_async).is_coroutine_callable}\")\nprint(f\"wrapped_async_in_sync: is_async={Dependant(call=wrapped_async_in_sync).is_coroutine_callable}\")\nPY\npython3 /tmp/repro_async_check.py\n"
} | {"status": "ok", "stdout": "wrapped_sync_in_async: is_async=False\nwrapped_async_in_sync: is_async=True\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "wrapped_sync_in_async: is_async=False\nwrapped_async_in_sync: is_async=True\n", "stderr": "", "exit_code": 0} |
| 15 | run_command | {
"command": "cat > /tmp/repro_async_check.py <<'PY'\nimport asyncio\nimport functools\nimport inspect\nfrom fastapi.dependencies.models import Dependant\n\ndef wrap_sync(f):\n @functools.wraps(f)\n def wrapper(*args, **kwargs):\n return f(*args, **kwargs)\n return wrapper\n\ndef sync_fn():\n return \"sync\"\n\nasync def async_fn():\n return \"async\"\n\ndef async_wrapper_for_sync(f):\n @functools.wraps(f)\n async def wrapper(*args, **kwargs):\n return f(*args, **kwargs)\n return wrapper\n\ndef sync_wrapper_for_async(f):\n @functools.wraps(f)\n def wrapper(*args, **kwargs):\n # Note: this is just for testing the property\n return asyncio.run(f(*args, **kwargs)) \n return wrapper\n\nwrapped_sync_in_async = async_wrapper_for_sync(sync_fn)\nwrapped_async_in_sync = sync_wrapper_for_async(async_fn)\n\ndep_sync_in_async = Dependant(call=wrapped_sync_in_async)\nprint(f\"wrapped_sync_in_async: unwrapped={dep_sync_in_async._unwrapped_call}, is_async={dep_sync_in_async.is_coroutine_callable}\")\n\ndep_async_in_sync = Dependant(call=wrapped_async_in_sync)\nprint(f\"wrapped_async_in_sync: unwrapped={dep_async_in_sync._unwrapped_call}, is_async={dep_async_in_sync.is_coroutine_callable}\")\nPY\npython3 /tmp/repro_async_check.py\n"
} | {"status": "ok", "stdout": "wrapped_sync_in_async: unwrapped=<function sync_fn at 0x10563fa60>, is_async=False\nwrapped_async_in_sync: unwrapped=<function asyncfull{"status": "ok", "stdout": "wrapped_sync_in_async: unwrapped=<function sync_fn at 0x10563fa60>, is_async=False\nwrapped_async_in_sync: unwrapped=<function async_fn at 0x105b01f80>, is_async=True\n", "stderr": "", "exit_code": 0} |
| 16 | run_command | {
"command": "cat > /tmp/repro_complex.py <<'PY'\nimport asyncio\nimport functools\nimport inspect\nfrom fastapi.dependencies.models import Dependant\n\ndef sync_fn():\n return \"sync\"\n\nasync def async_fn():\n return \"async\"\n\ndef sync_wrapper(f):\n @functools.wraps(f)\n def wrapper(*args, **kwargs):\n return f(*args, **kwargs)\n return wrapper\n\ndef async_wrapper(f):\n @functools.wraps(f)\n async def wrapper(*args, **kwargs):\n res = f(*args, **kwargs)\n if inspect.isawaitable(res):\n await res\n return res\n return wrapper\n\n# 1. Partial(sync_fn) -> Wrapped by sync_wrapper\n# Wrapper is sync, original is sync. Result: sync.\nc1 = sync_wrapper(functools.partial(sync_fn))\n\n# 2. Partial(async_fn) -> Wrapped by sync_wrapper\n# Wrapper is sync, original is async. Result: SHOULD BE async (since it returns a coroutine)\nc2 = sync_wrapper(functools.partial(async_fn))\n\n# 3. Partial(sync_fn) -> Wrapped by async_wrapper\n# Wrapper is async, original is sync. Result: SHOULD BE async.\nc3 = async_wrapper(functools.partial(sync_fn))\n\n# 4. Partial(async_fn) -> Wrapped by async_wrapper\n# Wrapper is async, original is async. Result: SHOULD BE async.\nc4 = async_wrapper(functools.partial(async_fn))\n\ncases = [\n (\"PartialSync-SyncWrap\", c1),\n (\"PartialAsync-SyncWrap\", c2),\n (\"PartialSync-AsyncWrap\", c3),\n (\"PartialAsync-AsyncWrap\", c4),\n]\n\nfor name, fn in cases:\n dep = Dependant(call=fn)\n print(f\"{name}: is_async={dep.is_coroutine_callable}\")\nPY\npython3 /tmp/repro_complex.py\n"
} | {"status": "ok", "stdout": "PartialSync-SyncWrap: is_async=False\nPartialAsync-SyncWrap: is_async=True\nPartialSync-AsyncWrap: is_async=False\nPartialAsync-Asynfull{"status": "ok", "stdout": "PartialSync-SyncWrap: is_async=False\nPartialAsync-SyncWrap: is_async=True\nPartialSync-AsyncWrap: is_async=False\nPartialAsync-AsyncWrap: is_async=True\n", "stderr": "", "exit_code": 0} |
| 17 | run_command | {
"command": "cat > /tmp/repro_complex_2.py <<'PY'\nimport asyncio\nimport functools\nimport inspect\nfrom fastapi.dependencies.models import Dependant\n\ndef sync_fn():\n return \"sync\"\n\nasync def async_fn():\n return \"async\"\n\ndef sync_wrapper(f):\n @functools.wraps(f)\n def wrapper(*args, **kwargs):\n return f(*args, **kwargs)\n return wrapper\n\ndef async_wrapper(f):\n @functools.wraps(f)\n async def wrapper(*args, **kwargs):\n res = f(*args, **kwargs)\n if inspect.isawaitable(res):\n await res\n return res\n return wrapper\n\n# 5. sync_fn -> Wrapped by sync_wrapper -> Wrapped by async_wrapper\nc5 = async_wrapper(sync_wrapper(sync_fn))\n\n# 6. async_fn -> Wrapped by sync_wrapper -> Wrapped by async_wrapper\nc6 = async_wrapper(sync_wrapper(async_fn))\n\n# 7. sync_fn -> Wrapped by async_wrapper -> Wrapped by sync_wrapper\nc7 = sync_wrapper(async_wrapper(sync_fn))\n\n# 8. async_fn -> Wrapped by async_wrapper -> Wrapped by sync_wrapper\nc8 = sync_wrapper(async_wrapper(async_fn))\n\ncases = [\n (\"Sync-SyncWrap-AsyncWrap\", c5),\n (\"Async-SyncWrap-AsyncWrap\", c6),\n (\"Sync-AsyncWrap-SyncWrap\", c7),\n (\"Async-AsyncWrap-SyncWrap\", c8),\n]\n\nfor name, fn in cases:\n dep = Dependant(call=fn)\n print(f\"{name}: is_async={dep.is_coroutine_callable}\")\nPY\npython3 /tmp/repro_complex_2.py\n"
} | {"status": "ok", "stdout": "Sync-SyncWrap-AsyncWrap: is_async=False\nAsync-SyncWrap-AsyncWrap: is_async=True\nSync-AsyncWrap-SyncWrap: is_async=False\nAsync-Asyfull{"status": "ok", "stdout": "Sync-SyncWrap-AsyncWrap: is_async=False\nAsync-SyncWrap-AsyncWrap: is_async=True\nSync-AsyncWrap-SyncWrap: is_async=False\nAsync-AsyncWrap-SyncWrap: is_async=True\n", "stderr": "", "exit_code": 0} |
| 18 | run_command | {
"command": "# The issue is that if the wrapper is async, but the original function is sync,\n# it is NOT being detected as async.\n# This is because _unwrapped_call uses inspect.unwrap(), which goes straight to the original function.\n# Then is_coroutine_callable checks _unwrapped_call.\n\n# Let's check the current implementation of is_coroutine_callable:\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# It ONLY checks the unwrapped call. It does NOT check the wrapper.\n# But if the wrapper is async, the whole thing should be treated as async.\n\n# So we need to check if EITHER the original call or any of the wrappers are async.\n# However, inspect.unwrap() only gives the final unwrapped function.\n\n# We should probably check the original `self.call` as well.\n\n# Let's try this:\n# 1. Start with self.call.\n# 2. Check if it is async.\n# 3. Unwrap it and check if the unwrapped is async.\n# 4. Also handle partials.\n\n# Wait, if we have a chain: WrapperAsync -> WrapperSync -> OriginalSync\n# self.call is WrapperAsync. WrapperAsync is async. Result should be async.\n# current: _unwrapped_call is OriginalSync. OriginalSync is sync. Result: sync. (WRONG)\n\n# If we have: WrapperSync -> WrapperAsync -> OriginalSync\n# self.call is WrapperSync. WrapperSync is sync.\n# _unwrapped_call is OriginalSync. OriginalSync is sync. Result: sync. (WRONG)\n\n# So we need to iterate through all wrappers.\n\n# Let's see how to iterate through wrappers.\n# `inspect.unwrap` just goes to the end.\n# But we can use `__wrapped__` if it's set by `functools.wraps`.\n\n# Let's refine _unwrapped_call or add a helper to check if any in the chain is async.\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 19 | run_command | {
"command": "# Let's check how to properly detect if any wrapper is async.\ncat > /tmp/check_wrappers.py <<'PY'\nimport asyncio\nimport functools\nimport inspect\n\ndef sync_fn():\n return \"sync\"\n\ndef sync_wrapper(f):\n @functools.wraps(f)\n def wrapper(*args, **kwargs):\n return f(*args, **kwargs)\n return wrapper\n\ndef async_wrapper(f):\n @functools.wraps(f)\n async def wrapper(*args, **kwargs):\n return f(*args, **kwargs)\n return wrapper\n\n# WrapperSync -> WrapperAsync -> OriginalSync\nc = sync_wrapper(async_wrapper(sync_fn))\n\ndef is_any_async(f):\n curr = f\n while True:\n if asyncio.iscoroutinefunction(curr):\n return True\n if isinstance(curr, functools.partial):\n # For partial, we must check the underlying func, \n # but partial itself is not a coroutine function.\n # However, a partial can wrap an async function.\n # This is tricky. \n # If we have partial(async_fn), asyncio.iscoroutinefunction(partial(async_fn)) is False.\n # So we need to handle partial separately.\n pass\n if hasattr(curr, \"__wrapped__\"):\n curr = curr.__wrapped__\n else:\n break\n return False\n\nprint(f\"c: is_any_async={is_any_async(c)}\")\nPY\npython3 /tmp/check_wrappers.py\n"
} | {"status": "ok", "stdout": "c: is_any_async=True\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "c: is_any_async=True\n", "stderr": "", "exit_code": 0} |
| 20 | run_command | {
"command": "cat > /tmp/check_wrappers_partial.py <<'PY'\nimport asyncio\nimport functools\nimport inspect\n\nasync def async_fn():\n return \"async\"\n\np = functools.partial(async_fn)\nprint(f\"asyncio.iscoroutinefunction(p): {asyncio.iscoroutinefunction(p)}\")\n\ndef is_any_async(f):\n curr = f\n while True:\n # Handle partials first: if it's a partial, we want to check its func\n if isinstance(curr, functools.partial):\n if asyncio.iscoroutinefunction(curr.func):\n return True\n curr = curr.func\n continue\n \n if asyncio.iscoroutinefunction(curr):\n return True\n \n if hasattr(curr, \"__wrapped__\"):\n curr = curr.__wrapped__\n else:\n break\n return False\n\nprint(f\"is_any_async(p): {is_any_async(p)}\")\nPY\npython3 /tmp/check_wrappers_partial.py\n"
} | {"status": "ok", "stdout": "asyncio.iscoroutinefunction(p): True\nis_any_async(p): True\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "asyncio.iscoroutinefunction(p): True\nis_any_async(p): True\n", "stderr": "", "exit_code": 0} |
| 21 | run_command | {
"command": "# Wait, asyncio.iscoroutinefunction(functools.partial(async_fn)) is True?\n# Let me re-check.\ncat > /tmp/check_partial.py <<'PY'\nimport asyncio\nimport functools\n\nasync def async_fn():\n return \"async\"\n\np = functools.partial(async_fn)\nprint(f\"asyncio.iscoroutinefunction(p): {asyncio.iscoroutinefunction(p)}\")\nPY\npython3 /tmp/check_partial.py\n"
} | {"status": "ok", "stdout": "asyncio.iscoroutinefunction(p): True\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "asyncio.iscoroutinefunction(p): True\n", "stderr": "", "exit_code": 0} |
| 22 | run_command | {
"command": "# If asyncio.iscoroutinefunction handles partials, then we just need to iterate through __wrapped__.\n# Let's test a combined case: Wrapped(Partial(Async)) and Partial(Wrapped(Async))\n\ncat > /tmp/check_combined.py <<'PY'\nimport asyncio\nimport functools\nimport inspect\n\nasync def async_fn():\n return \"async\"\n\ndef sync_fn():\n return \"sync\"\n\ndef sync_wrapper(f):\n @functools.wraps(f)\n def wrapper(*args, **kwargs):\n return f(*args, **kwargs)\n return wrapper\n\ndef async_wrapper(f):\n @functools.wraps(f)\n async def wrapper(*args, **kwargs):\n return f(*args, **kwargs)\n return wrapper\n\n# Case A: Partial(Async) -> Wrapped by sync_wrapper\ncase_a = sync_wrapper(functools.partial(async_fn))\n\n# Case B: Wrapped(Async) -> Partial\ncase_b = functools.partial(async_wrapper(async_fn))\n\n# Case C: Partial(Sync) -> Wrapped by async_wrapper\ncase_c = async_wrapper(functools.partial(sync_fn))\n\n# Case D: Wrapped(Sync) -> Partial\ncase_d = functools.partial(sync_wrapper(sync_fn))\n\ndef is_any_async(f):\n curr = f\n while True:\n if asyncio.iscoroutinefunction(curr):\n return True\n if hasattr(curr, \"__wrapped__\"):\n curr = curr.__wrapped__\n elif isinstance(curr, functools.partial):\n curr = curr.func\n else:\n break\n return False\n\nprint(f\"Case A (SyncWrap(Partial(Async))): {is_any_async(case_a)}\")\nprint(f\"Case B (Partial(AsyncWrap(Async))): {is_any_async(case_b)}\")\nprint(f\"Case C (AsyncWrap(Partial(Sync))): {is_any_async(case_c)}\")\nprint(f\"Case D (Partial(SyncWrap(Sync))): {is_any_async(case_d)}\")\nPY\npython3 /tmp/check_combined.py\n"
} | {"status": "ok", "stdout": "Case A (SyncWrap(Partial(Async))): True\nCase B (Partial(AsyncWrap(Async))): True\nCase C (AsyncWrap(Partial(Sync))): True\nCase D (full{"status": "ok", "stdout": "Case A (SyncWrap(Partial(Async))): True\nCase B (Partial(AsyncWrap(Async))): True\nCase C (AsyncWrap(Partial(Sync))): True\nCase D (Partial(SyncWrap(Sync))): False\n", "stderr": "", "exit_code": 0} |
| 23 | run_command | {
"command": "# Now let's see how to integrate this into fastapi/dependencies/models.py.\n# We need to update is_coroutine_callable, is_gen_callable, and is_async_gen_callable.\n\n# Currently they all depend on _unwrapped_call.\n# _unwrapped_call is:\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# If we change the logic to iterate through all levels, we can ensure that if ANY level is async, it returns True.\n\n# Let's check the original code again.\nsed -n '80,120p' fastapi/dependencies/models.py"
} | {"status": "ok", "stdout": " if self.call is None:\n return self.call # pragma: no cover\n unwrapped = inspect.unwrap(self.call)\n if isinstance(unwrapped, partfull{"status": "ok", "stdout": " 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", "stderr": "", "exit_code": 0} |
| 24 | run_command | {
"command": "# I will implement a helper method to check if any level in the wrapping chain matches a predicate.\n# Or just rewrite the properties to iterate.\n\n# Wait, is_coroutinefunction, isgeneratorfunction, isasyncgenfunction are the predicates.\n\n# Let's try to define a helper in Dependant.\n\n# But first, I should check if there is any other place that uses _unwrapped_call.\ngrep -rn \"_unwrapped_call\" fastapi/ | head -20 || echo '<IMPORTANT> NO MATCH FOUND. DO NOT TRY SAME COMMAND.'"
} | {"status": "ok", "stdout": "fastapi/dependencies/models.py:79: def _unwrapped_call(self) -> Any:\nfastapi/dependencies/models.py:89: if inspect.isgeneratorfunctfull{"status": "ok", "stdout": "fastapi/dependencies/models.py:79: def _unwrapped_call(self) -> Any:\nfastapi/dependencies/models.py:89: if inspect.isgeneratorfunction(self._unwrapped_call):\nfastapi/dependencies/models.py:91: dunder_call = getattr(self._unwrapped_call, \"__call__\", None) # noqa: B004\nfastapi/dependencies/models.py:96: if inspect.isasyncgenfunction(self._unwrapped_call):\nfastapi/dependencies/models.py:98: dunder_call = getattr(self._unwrapped_call, \"__call__\", None) # noqa: B004\nfastapi/dependencies/models.py:103: if inspect.isroutine(self._unwrapped_call):\nfastapi/dependencies/models.py:104: return iscoroutinefunction(self._unwrapped_call)\nfastapi/dependencies/models.py:105: if inspect.isclass(self._unwrapped_call):\nfastapi/dependencies/models.py:107: dunder_call = getattr(self._unwrapped_call, \"__call__\", None) # noqa: B004\n", "stderr": "", "exit_code": 0} |
| 25 | run_command | {
"command": "# Okay, _unwrapped_call is only used by these 3 properties in Dependant.\n# So I can change them.\n\n# Let's define a helper to check any wrapper.\n\n# Wait, if I change _unwrapped_call to return the whole chain, that might break other things if they expect only one function.\n# But they don't seem to expect that, they just use it to check properties.\n\n# Actually, the properties check if the call is async/gen etc.\n# If ANY level is async, it should be True.\n\n# Let's modify the properties in fastapi/dependencies/models.py.\n# I will use a loop to check all wrappers.\n\n# I'll start by adding the necessary import for partial if it's not there.\n# It is already there (it's used in _unwrapped_call).\n# Let's check imports.\nsed -n '1,30p' fastapi/dependencies/models.py"
} | {"status": "ok", "stdout": "import inspect\nimport sys\nfrom dataclasses import dataclass, field\nfrom functools import cached_property, partial\nfrom typing imfull{"status": "ok", "stdout": "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", "stderr": "", "exit_code": 0} |
| 26 | run_command | {
"command": "# Now I will apply the changes.\n# I will replace the property implementations.\n\npython3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/dependencies/models.py\")\ns = p.read_text()\n\n# Define new implementations for the properties\n# We need to check the call itself and all its unwrapped versions.\n\nnew_is_gen = \"\"\" @cached_property\n def is_gen_callable(self) -> bool:\n curr = self.call\n while True:\n if inspect.isgeneratorfunction(curr):\n return True\n dunder_call = getattr(curr, \"__call__\", None) # noqa: B004\n if inspect.isgeneratorfunction(dunder_call):\n return True\n if hasattr(curr, \"__wrapped__\"):\n curr = curr.__wrapped__\n elif isinstance(curr, partial):\n curr = curr.func\n else:\n break\n return False\"\"\"\n\nnew_is_async_gen = \"\"\" @cached_property\n def is_async_gen_callable(self) -> bool:\n curr = self.call\n while True:\n if inspect.isasyncgenfunction(curr):\n return True\n dunder_call = getattr(curr, \"__call__\", None) # noqa: B004\n if inspect.isasyncgenfunction(dunder_call):\n return True\n if hasattr(curr, \"__wrapped__\"):\n curr = curr.__wrapped__\n elif isinstance(curr, partial):\n curr = curr.func\n else:\n break\n return False\"\"\"\n\nnew_is_coroutine = \"\"\" @cached_property\n def is_coroutine_callable(self) -> bool:\n curr = self.call\n while True:\n if inspect.isroutine(curr):\n if iscoroutinefunction(curr):\n return True\n elif not inspect.isclass(curr):\n dunder_call = getattr(curr, \"__call__\", None) # noqa: B004\n if iscoroutinefunction(dunder_call):\n return True\n \n if hasattr(curr, \"__wrapped__\"):\n curr = curr.__wrapped__\n elif isinstance(curr, partial):\n curr = curr.func\n else:\n break\n return False\"\"\"\n\n# Let's find the old ones and replace them.\n# We should replace the old properties.\n\nold_is_gen = \"\"\" @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\nold_is_async_gen = \"\"\" @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\nold_is_coroutine = \"\"\" @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\ns = s.replace(old_is_gen, new_is_gen)\ns = s.replace(old_is_async_gen, new_is_async_gen)\ns = s.replace(old_is_coroutine, new_is_coroutine)\n\np.write_text(s)\nPY\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
diff --git a/fastapi/dependencies/models.py b/fastapi/dependencies/models.py
index 2a4d9a01..92bf5290 100644
--- a/fastapi/dependencies/models.py
+++ b/fastapi/dependencies/models.py
@@ -86,26 +86,57 @@ class Dependant:
@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)
+ curr = self.call
+ while True:
+ if inspect.isgeneratorfunction(curr):
+ return True
+ dunder_call = getattr(curr, "__call__", None) # noqa: B004
+ if inspect.isgeneratorfunction(dunder_call):
+ return True
+ if hasattr(curr, "__wrapped__"):
+ curr = curr.__wrapped__
+ elif isinstance(curr, partial):
+ curr = curr.func
+ else:
+ break
+ 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)
+ curr = self.call
+ while True:
+ if inspect.isasyncgenfunction(curr):
+ return True
+ dunder_call = getattr(curr, "__call__", None) # noqa: B004
+ if inspect.isasyncgenfunction(dunder_call):
+ return True
+ if hasattr(curr, "__wrapped__"):
+ curr = curr.__wrapped__
+ elif isinstance(curr, partial):
+ curr = curr.func
+ else:
+ break
+ 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)
+ curr = self.call
+ while True:
+ if inspect.isroutine(curr):
+ if iscoroutinefunction(curr):
+ return True
+ elif not inspect.isclass(curr):
+ dunder_call = getattr(curr, "__call__", None) # noqa: B004
+ if iscoroutinefunction(dunder_call):
+ return True
+
+ if hasattr(curr, "__wrapped__"):
+ curr = curr.__wrapped__
+ elif isinstance(curr, partial):
+ curr = curr.func
+ else:
+ break
+ return False
@cached_property
def computed_scope(self) -> Union[str, None]:
urn 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 ===============================
../../../../../../../Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.50.0-py3-none-any/starlette/testclient.py:45
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.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.67s
<sys>:0: RuntimeWarning: coroutine 'ClassInstanceAsyncWrappedDep.__call__' was never awaited