← eval4_submission

fastapi_14794

resolved RESOLVED LOOP PASS · 22 tool calls · 150 s · fastapi/fastapi

Task input

✨ Allow `Response` type hint as dependency annotation

## Summary
Fixes #10127

Using `Response` as a type hint with `Depends` would fail with an assertion error:
```python
def endpoint(response: Annotated[Response, Depends(modify_response)]):
    # AssertionError: Cannot specify \`Depends\` for type Response
```

## Changes
Modified the type annotation handling in `analyze_param()` to check for `Depends` before applying special type handling. When a `Depends` is specified, the dependency is called normally and its return value used.

## Usage
Now supports:
- `response: Annotated[Response, Depends(modify_response)]`
- `response: Response = Depends(modify_response)`

While still supporting regular injection:
- `response: Response`

## Testing
Added comprehensive tests in `tests/test_response_dependency.py`

Tool calls (23)

#ToolArgumentsResult
1run_command
{
 "command": "grep -rn \"analyze_param\" fastapi | head -20"
}
{"status": "ok", "stdout": "fastapi/dependencies/utils.py:281: param_details = analyze_param(\nfastapi/dependencies/utils.py:362:def analyze_param(\n", "exit_co
full
{"status": "ok", "stdout": "fastapi/dependencies/utils.py:281:        param_details = analyze_param(\nfastapi/dependencies/utils.py:362:def analyze_param(\n", "exit_code": 0}
2read_file
{
 "filepath": "fastapi/dependencies/utils.py",
 "start_line": 362
}
{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": "def analyze_param(\n *,\n param_name: str,\n annotation: Any,\n value: Any,\n is_path_
full
{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": "def analyze_param(\n    *,\n    param_name: str,\n    annotation: Any,\n    value: Any,\n    is_path_param: bool,\n) -> ParamDetails:\n    field_info = None\n    depends = None\n    type_annotation: Any = Any\n    use_annotation: Any = Any\n    if annotation is not inspect.Signature.empty:\n        use_annotation = annotation\n        type_annotation = annotation\n    # Extract Annotated info\n    if get_origin(use_annotation) is Annotated:\n        annotated_args = get_args(annotation)\n        type_annotation = annotated_args[0]\n        fastapi_annotations = [\n            arg\n            for arg in annotated_args[1:]\n            if isinstance(arg, (FieldInfo, params.Depends))\n        ]\n        fastapi_specific_annotations = [\n            arg\n            for arg in fastapi_annotations\n            if isinstance(\n                arg,\n                (\n                    params.Param,\n                    params.Body,\n                    params.Depends,\n                ),\n            )\n        ]\n        if fastapi_specific_annotations:\n            fastapi_annotation: Union[FieldInfo, params.Depends, None] = (\n                fastapi_specific_annotations[-1]\n            )\n        else:\n            fastapi_annotation = None\n        # Set default for Annotated FieldInfo\n        if isinstance(fastapi_annotation, FieldInfo):\n            # Copy `field_info` because we mutate `field_info.default` below.\n            field_info = copy_field_info(\n                field_info=fastapi_annotation,\n                annotation=use_annotation,\n            )\n            assert (\n                field_info.default == Undefined or field_info.default == RequiredParam\n            ), (\n                f\"`{field_info.__class__.__name__}` default value cannot be set in\"\n                f\" `Annotated` for {param_name!r}. Set the default value with `=` instead.\"\n            )\n      
3run_command
{
 "command": "python3 -c \"from fastapi import FastAPI, Response, Depends; from typing import Annotated; app = FastAPI(); def modify_response(): return Response(); @app.get('/') def endpoint(response: Annotated[Response, Depends(modify_response)]): return response; print('Success')\""
}
{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": " File \"<string>\", l
full
{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": "  File \"<string>\", line 1\n    from fastapi import FastAPI, Response, Depends; from typing import Annotated; app = FastAPI(); def modify_response(): return Response(); @app.get('/') def endpoint(response: Annotated[Response, Depends(modify_response)]): return response; print('Success')\n                                                                                                   ^^^\nSyntaxError: invalid syntax\n", "exit_code": 1}}
4write_file
{
 "content": "from fastapi import FastAPI, Response, Depends\nfrom typing import Annotated\n\napp = FastAPI()\n\ndef modify_response():\n    return Response(content=\"modified\")\n\n@app.get(\"/\")\ndef endpoint(response: Annotated[Response, Depends(modify_response)]):\n    return response\n\nif __name__ == \"__main__\":\n    from fastapi.testclient import TestClient\n    client = TestClient(app)\n    response = client.get(\"/\")\n    print(f\"Status: {response.status_code}, Body: {response.text}\")\n    print(\"Success\")\n",
 "filepath": "/tmp/test_response_dep.py"
}
{"status": "ok", "filepath": "/tmp/test_response_dep.py", "size": 486}
full
{"status": "ok", "filepath": "/tmp/test_response_dep.py", "size": 486}
5run_command
{
 "command": "python3 /tmp/test_response_dep.py"
}
{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 2", "details": {"stdout": "", "stderr": "python3: can't open f
full
{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 2", "details": {"stdout": "", "stderr": "python3: can't open file '/tmp/swe_work/eval4_submission/fastapi_14794/a/tmp/test_response_dep.py': [Errno 2] No such file or directory\n", "exit_code": 2}}
6run_command
{
 "command": "python3 /workspace/tmp/test_response_dep.py"
}
{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 2", "details": {"stdout": "", "stderr": "python3: can't open f
full
{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 2", "details": {"stdout": "", "stderr": "python3: can't open file '/tmp/swe_work/eval4_submission/fastapi_14794/a/workspace/tmp/test_response_dep.py': [Errno 2] No such file or directory\n", "exit_code": 2}}
7run_command
{
 "command": "mkdir -p /tmp && write_file --content \"from fastapi import FastAPI, Response, Depends\nfrom typing import Annotated\n\napp = FastAPI()\n\ndef modify_response():\n    return Response(content='modified')\n\n@app.get('/')\ndef endpoint(response: Annotated[Response, Depends(modify_response)]):\n    return response\n\nif __name__ == '__main__':\n    from fastapi.testclient import TestClient\n    client = TestClient(app)\n    response = client.get('/')\n    print(f'Status: {response.status_code}, Body: {response.text}')\n    print('Success')\" /tmp/test_response_dep.py && python3 /tmp/test_response_dep.py"
}
{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 127", "details": {"stdout": "", "stderr": "/bin/bash: line 1:
full
{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 127", "details": {"stdout": "", "stderr": "/bin/bash: line 1: write_file: command not found\n", "exit_code": 127}}
8write_file
{
 "content": "from fastapi import FastAPI, Response, Depends\nfrom typing import Annotated\n\napp = FastAPI()\n\ndef modify_response():\n    return Response(content=\"modified\")\n\n@app.get(\"/\")\ndef endpoint(response: Annotated[Response, Depends(modify_response)]):\n    return response\n\nif __name__ == \"__main__\":\n    from fastapi.testclient import TestClient\n    client = TestClient(app)\n    response = client.get(\"/\")\n    print(f\"Status: {response.status_code}, Body: {response.text}\")\n    print(\"Success\")\n",
 "filepath": "test_response_dep.py"
}
{"status": "ok", "filepath": "test_response_dep.py", "size": 486}
full
{"status": "ok", "filepath": "test_response_dep.py", "size": 486}
9run_command
{
 "command": "python3 test_response_dep.py"
}
{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": "Traceback (most recen
full
{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": "Traceback (most recent call last):\n  File \"/workspace/test_response_dep.py\", line 9, in <module>\n    @app.get(\"/\")\n     ~~~~~~~^^^^^\n  File \"/workspace/fastapi/routing.py\", line 1063, in decorator\n    self.add_api_route(\n    ~~~~~~~~~~~~~~~~~~^\n        path,\n        ^^^^^\n    ...<23 lines>...\n        generate_unique_id_function=generate_unique_id_function,\n        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n    )\n    ^\n  File \"/workspace/fastapi/routing.py\", line 1002, in add_api_route\n    route = route_class(\n        self.prefix + path,\n    ...<24 lines>...\n        generate_unique_id_function=current_generate_unique_id,\n    )\n  File \"/workspace/fastapi/routing.py\", line 621, in __init__\n    self.dependant = get_dependant(\n                     ~~~~~~~~~~~~~^\n        path=self.path_format, call=self.endpoint, scope=\"function\"\n        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n    )\n    ^\n  File \"/workspace/fastapi/dependencies/utils.py\", line 281, in get_dependant\n    param_details = analyze_param(\n        param_name=param_name,\n    ...<2 lines>...\n        is_path_param=is_path_param,\n    )\n  File \"/workspace/fastapi/dependencies/utils.py\", line 463, in analyze_param\n    assert depends is None, f\"Cannot specify `Depends` for type {type_annotation!r}\"\n           ^^^^^^^^^^^^^^^\nAssertionError: Cannot specify `Depends` for type <class 'starlette.responses.Response'>\n", "exit_code": 1}}
10read_file
{
 "filepath": "fastapi/dependencies/utils.py",
 "start_line": 455,
 "end_line": 475
}
{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": " Request,\n WebSocket,\n HTTPConnection,\n Response,\n StarletteBackgroundTasks,\n Sec
full
{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": "            Request,\n            WebSocket,\n            HTTPConnection,\n            Response,\n            StarletteBackgroundTasks,\n            SecurityScopes,\n        ),\n    ):\n        assert depends is None, f\"Cannot specify `Depends` for type {type_annotation!r}\"\n        assert field_info is None, (\n            f\"Cannot specify FastAPI annotation for type {type_annotation!r}\"\n        )\n    # Handle default assignations, neither field_info nor depends was not found in Annotated nor default value\n    elif field_info is None and depends is None:\n        default_value = value if value is not inspect.Signature.empty else RequiredParam\n        if is_path_param:\n            # We might check here that `default_value is RequiredParam`, but the fact is that the same\n            # parameter might sometimes be a path parameter and sometimes not. See\n            # `tests/test_infer_param_optionality.py` for an example.\n            field_info = params.Path(annotation=use_annotation)\n        elif is_uploadfile_or_nonable_uploadfile_annotation(\n", "start_line": 455, "end_line": 475, "total_lines": 1026, "is_truncated": false}
11edit_file
{
 "filepath": "fastapi/dependencies/utils.py",
 "new_string": "    # Handle non-param type annotations like Request\n    if lenient_issubclass(\n        type_annotation,\n        (\n            Request,\n            WebSocket,\n            HTTPConnection,\n            Response,\n            StarletteBackgroundTasks,\n            SecurityScopes,\n        ),\n    ) and depends is None:\n        assert field_info is None, (\n            f\"Cannot specify FastAPI annotation for type {type_annotation!r}\"\n        )\n    # Handle default assignations, neither field_info nor depends was not found in Annotated nor default value\n    elif field_info is None and depends is None:",
 "old_string": "    # Handle non-param type annotations like Request\n    if lenient_issubclass(\n        type_annotation,\n        (\n            Request,\n            WebSocket,\n            HTTPConnection,\n            Response,\n            StarletteBackgroundTasks,\n            SecurityScopes,\n        ),\n    ):\n        assert depends is None, f\"Cannot specify `Depends` for type {type_annotation!r}\"\n        assert field_info is None, (\n            f\"Cannot specify FastAPI annotation for type {type_annotation!r}\"\n        )\n    # Handle default assignations, neither field_info nor depends was not found in Annotated nor default value\n    elif field_info is None and depends is None:"
}
{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/dependencies/utils.py\n+++ b/fastapi
full
{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/dependencies/utils.py\n+++ b/fastapi/dependencies/utils.py\n@@ -459,8 +459,7 @@\n             StarletteBackgroundTasks,\n             SecurityScopes,\n         ),\n-    ):\n-        assert depends is None, f\"Cannot specify `Depends` for type {type_annotation!r}\"\n+    ) and depends is None:\n         assert field_info is None, (\n             f\"Cannot specify FastAPI annotation for type {type_annotation!r}\"\n         )\n", "is_truncated": false}
12run_command
{
 "command": "python3 test_response_dep.py"
}
{"status": "ok", "stdout": "Status: 200, Body: modified\nSuccess\n", "exit_code": 0}
full
{"status": "ok", "stdout": "Status: 200, Body: modified\nSuccess\n", "exit_code": 0}
13run_command
{
 "command": "python3 -c \"from fastapi import FastAPI, Response, Depends; app = FastAPI(); def modify_response(): return Response(content='modified'); @app.get('/') def endpoint(response: Response = Depends(modify_response)): return response; from fastapi.testclient import TestClient; client = TestClient(app); print(client.get('/').text)\""
}
{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": " File \"<string>\", l
full
{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": "  File \"<string>\", line 1\n    from fastapi import FastAPI, Response, Depends; app = FastAPI(); def modify_response(): return Response(content='modified'); @app.get('/') def endpoint(response: Response = Depends(modify_response)): return response; from fastapi.testclient import TestClient; client = TestClient(app); print(client.get('/').text)\n                                                                     ^^^\nSyntaxError: invalid syntax\n", "exit_code": 1}}
14write_file
{
 "content": "from fastapi import FastAPI, Response, Depends\napp = FastAPI()\ndef modify_response():\n    return Response(content=\"modified\")\n@app.get(\"/\")\ndef endpoint(response: Response = Depends(modify_response)):\n    return response\nif __name__ == \"__main__\":\n    from fastapi.testclient import TestClient\n    client = TestClient(app)\n    print(client.get(\"/\").text)\n",
 "filepath": "test_response_dep_simple.py"
}
{"status": "ok", "filepath": "test_response_dep_simple.py", "size": 355}
full
{"status": "ok", "filepath": "test_response_dep_simple.py", "size": 355}
15run_command
{
 "command": "python3 test_response_dep_simple.py"
}
{"status": "ok", "stdout": "modified\n", "exit_code": 0}
full
{"status": "ok", "stdout": "modified\n", "exit_code": 0}
16run_command
{
 "command": "python3 -c \"from fastapi import FastAPI, Response; app = FastAPI(); @app.get('/') def endpoint(response: Response): return response; from fastapi.testclient import TestClient; client = TestClient(app); print(client.get('/').status_code)\""
}
{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": " File \"<string>\", l
full
{"status": "error", "error_type": "CommandError", "error_message": "Command failed with exit code 1", "details": {"stdout": "", "stderr": "  File \"<string>\", line 1\n    from fastapi import FastAPI, Response; app = FastAPI(); @app.get('/') def endpoint(response: Response): return response; from fastapi.testclient import TestClient; client = TestClient(app); print(client.get('/').status_code)\n                                                            ^\nSyntaxError: invalid syntax\n", "exit_code": 1}}
17write_file
{
 "content": "from fastapi import FastAPI, Response\napp = FastAPI()\n@app.get(\"/\")\ndef endpoint(response: Response):\n    return response\nif __name__ == \"__main__\":\n    from fastapi.testclient import TestClient\n    client = TestClient(app)\n    print(client.get(\"/\").status_code)\n",
 "filepath": "test_response_regular.py"
}
{"status": "ok", "filepath": "test_response_regular.py", "size": 263}
full
{"status": "ok", "filepath": "test_response_regular.py", "size": 263}
18-21 ×4run_command
{
 "command": "python3 test_response_regular.py"
}
{"status": "ok", "stdout": "None\n", "exit_code": 0}
full
{"status": "ok", "stdout": "None\n", "exit_code": 0}
22run_command
{
 "command": "rm test_response_dep.py test_response_dep_simple.py test_response_regular.py"
}
{"status": "ok", "stdout": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "exit_code": 0}
23submit_patch
[]
{"status": "ok", "patch_size": 1229, "files_changed": 2}
full
{"status": "ok", "patch_size": 1229, "files_changed": 2}

Patch

diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py
index fc5dfed8..1c3ca95f 100644
--- a/fastapi/dependencies/utils.py
+++ b/fastapi/dependencies/utils.py
@@ -459,8 +459,7 @@ def analyze_param(
             StarletteBackgroundTasks,
             SecurityScopes,
         ),
-    ):
-        assert depends is None, f"Cannot specify `Depends` for type {type_annotation!r}"
+    ) and depends is None:
         assert field_info is None, (
             f"Cannot specify FastAPI annotation for type {type_annotation!r}"
         )
diff --git a/tmp/test_response_dep.py b/tmp/test_response_dep.py
new file mode 100644
index 00000000..57355c9d
--- /dev/null
+++ b/tmp/test_response_dep.py
@@ -0,0 +1,18 @@
+from fastapi import FastAPI, Response, Depends
+from typing import Annotated
+
+app = FastAPI()
+
+def modify_response():
+    return Response(content="modified")
+
+@app.get("/")
+def endpoint(response: Annotated[Response, Depends(modify_response)]):
+    return response
+
+if __name__ == "__main__":
+    from fastapi.testclient import TestClient
+    client = TestClient(app)
+    response = client.get("/")
+    print(f"Status: {response.status_code}, Body: {response.text}")
+    print("Success")

Test output

show
.......                                                                  [100%]
=============================== 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
7 passed, 1 warning in 0.69s