← pod_r4_v1

fastapi_14794

resolved RESOLVED PASS · 8 tool calls · 53 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 (9)

#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", "stderr"
full
{"status": "ok", "stdout": "fastapi/dependencies/utils.py:281:        param_details = analyze_param(\nfastapi/dependencies/utils.py:362:def analyze_param(\n", "stderr": "", "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": "cat <<EOF > /tmp/repro.py\nfrom typing import Annotated\nfrom fastapi import FastAPI, Depends, Response\nfrom fastapi.dependencies.utils import analyze_param\nimport inspect\n\ndef modify_response():\n    return Response(content=\"modified\")\n\ndef test_response_dependency():\n    # Case 1: Annotated[Response, Depends(modify_response)]\n    try:\n        analyze_param(\n            param_name=\"response\",\n            annotation=Annotated[Response, Depends(modify_response)],\n            value=inspect.Signature.empty,\n            is_path_param=False,\n        )\n        print(\"Case 1 passed\")\n    except AssertionError as e:\n        print(f\"Case 1 failed: {e}\")\n\n    # Case 2: response: Response = Depends(modify_response)\n    try:\n        analyze_param(\n            param_name=\"response\",\n            annotation=Response,\n            value=Depends(modify_response),\n            is_path_param=False,\n        )\n        print(\"Case 2 passed\")\n    except AssertionError as e:\n        print(f\"Case 2 failed: {e}\")\n\nif __name__ == \"__main__\":\n    test_response_dependency()\nEOF\npython3 /tmp/repro.py\n"
}
{"status": "ok", "stdout": "Case 1 failed: Cannot specify `Depends` for type <class 'starlette.responses.Response'>\nCase 2 failed: Cannot specify `Depends` for
full
{"status": "ok", "stdout": "Case 1 failed: Cannot specify `Depends` for type <class 'starlette.responses.Response'>\nCase 2 failed: Cannot specify `Depends` for type <class 'starlette.responses.Response'>\n", "stderr": "", "exit_code": 0}
4read_file
{
 "filepath": "fastapi/dependencies/utils.py",
 "start_line": 445,
 "end_line": 460
}
{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": " # Get Depends from type annotation\n if depends is not None and depends.dependency is
full
{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": "    # Get Depends from type annotation\n    if depends is not None and depends.dependency is None:\n        # Copy `depends` before mutating it\n        depends = copy(depends)\n        depends = dataclasses.replace(depends, dependency=type_annotation)\n\n    # 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,", "start_line": 445, "end_line": 460, "total_lines": 1026, "is_truncated": false}
5edit_file
{
 "filepath": "fastapi/dependencies/utils.py",
 "new_string": "    # Handle non-param type annotations like Request\n    if depends is None and lenient_issubclass(\n        type_annotation,\n        (\n            Request,\n            WebSocket,\n            HTTPConnection,\n            Response,\n            StarletteBackgroundTasks,\n            SecurityScopes,\n        ),\n    ):\n        assert field_info is None, (\n            f\\\"Cannot specify FastAPI annotation for type {type_annotation!r}\\\"\n        )",
 "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        )"
}
{"status": "error", "error_type": "FileEditError", "error_message": "Failed to replace: old_string not found. Ensure you're not escaping content incorrectly and
full
{"status": "error", "error_type": "FileEditError", "error_message": "Failed to replace: old_string not found. Ensure you're not escaping content incorrectly and check whitespace, indentation, and context."}
6read_file
{
 "end_line": 465,
 "filepath": "fastapi/dependencies/utils.py",
 "start_line": 450
}
{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": "\n # Handle non-param type annotations like Request\n if lenient_issubclass(\n type_an
full
{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": "\n    # 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}\"", "start_line": 450, "end_line": 465, "total_lines": 1026, "is_truncated": false}
7edit_file
{
 "filepath": "fastapi/dependencies/utils.py",
 "new_string": "    # Handle non-param type annotations like Request\n    if depends is None and lenient_issubclass(\n        type_annotation,\n        (\n            Request,\n            WebSocket,\n            HTTPConnection,\n            Response,\n            StarletteBackgroundTasks,\n            SecurityScopes,\n        ),\n    ):\n        assert field_info is None, (\n            f\"Cannot specify FastAPI annotation for type {type_annotation!r}\"\n        )",
 "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        )"
}
{"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@@ -449,7 +449,7 @@\n         depends = dataclasses.replace(depends, dependency=type_annotation)\n \n     # Handle non-param type annotations like Request\n-    if lenient_issubclass(\n+    if depends is None and lenient_issubclass(\n         type_annotation,\n         (\n             Request,\n@@ -460,7 +460,6 @@\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", "is_truncated": false}
8run_command
{
 "command": "python3 /tmp/repro.py"
}
{"status": "ok", "stdout": "Case 1 passed\nCase 2 passed\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "Case 1 passed\nCase 2 passed\n", "stderr": "", "exit_code": 0}
9submit_patch
{}
{"status": "ok", "patch_size": 791, "files_changed": 1}
full
{"status": "ok", "patch_size": 791, "files_changed": 1}

Patch

diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py
index fc5dfed8..883f9b74 100644
--- a/fastapi/dependencies/utils.py
+++ b/fastapi/dependencies/utils.py
@@ -449,7 +449,7 @@ def analyze_param(
         depends = dataclasses.replace(depends, dependency=type_annotation)
 
     # Handle non-param type annotations like Request
-    if lenient_issubclass(
+    if depends is None and lenient_issubclass(
         type_annotation,
         (
             Request,
@@ -460,7 +460,6 @@ def analyze_param(
             SecurityScopes,
         ),
     ):
-        assert depends is None, f"Cannot specify `Depends` for type {type_annotation!r}"
         assert field_info is None, (
             f"Cannot specify FastAPI annotation for type {type_annotation!r}"
         )

Test output

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