← eval5_submission_v1s

fastapi_14794

resolved RESOLVED PASS · 19 tool calls · 97 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 (20)

#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}
2-3 ×2read_file
{
 "filepath": "fastapi/dependencies/utils.py",
 "start_line": "362"
}
{"status": "error", "error_type": "FileReadError", "error_message": "'>' not supported between instances of 'int' and 'str'"}
full
{"status": "error", "error_type": "FileReadError", "error_message": "'>' not supported between instances of 'int' and 'str'"}
4read_file
{
 "filepath": "fastapi/dependencies/utils.py"
}
{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": "import dataclasses\nimport inspect\nimport sys\nfrom collections.abc import Coroutine,
full
{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": "import dataclasses\nimport inspect\nimport sys\nfrom collections.abc import Coroutine, Mapping, Sequence\nfrom contextlib import AsyncExitStack, contextmanager\nfrom copy import copy, deepcopy\nfrom dataclasses import dataclass\nfrom typing import (\n    Annotated,\n    Any,\n    Callable,\n    ForwardRef,\n    Optional,\n    Union,\n    cast,\n)\n\nimport anyio\nfrom fastapi import params\nfrom fastapi._compat import (\n    ModelField,\n    RequiredParam,\n    Undefined,\n    _regenerate_error_with_loc,\n    copy_field_info,\n    create_body_model,\n    evaluate_forwardref,\n    field_annotation_is_scalar,\n    get_cached_model_fields,\n    get_missing_field_error,\n    is_bytes_field,\n    is_bytes_sequence_field,\n    is_scalar_field,\n    is_scalar_sequence_field,\n    is_sequence_field,\n    is_uploadfile_or_nonable_uploadfile_annotation,\n    is_uploadfile_sequence_annotation,\n    lenient_issubclass,\n    sequence_types,\n    serialize_sequence_value,\n    value_is_sequence,\n)\nfrom fastapi.background import BackgroundTasks\nfrom fastapi.concurrency import (\n    asynccontextmanager,\n    contextmanager_in_threadpool,\n)\nfrom fastapi.dependencies.models import Dependant\nfrom fastapi.exceptions import DependencyScopeError\nfrom fastapi.logger import logger\nfrom fastapi.security.oauth2 import SecurityScopes\nfrom fastapi.types import DependencyCacheKey\nfrom fastapi.utils import create_model_field, get_path_param_names\nfrom pydantic import BaseModel\nfrom pydantic.fields import FieldInfo\nfrom starlette.background import BackgroundTasks as StarletteBackgroundTasks\nfrom starlette.concurrency import run_in_threadpool\nfrom starlette.datastructures import (\n    FormData,\n    Headers,\n    ImmutableMultiDict,\n    QueryParams,\n    UploadFile,\n)\nfrom starlette.requests import HTTPConnection, Request\nfrom starlette.responses import Response\nfrom starlette.websockets import WebSocke
5read_file
{
 "filepath": "fastapi/dependencies/utils.py",
 "start_line": "362"
}
{"status": "error", "error_type": "FileReadError", "error_message": "'>' not supported between instances of 'int' and 'str'"}
full
{"status": "error", "error_type": "FileReadError", "error_message": "'>' not supported between instances of 'int' and 'str'"}
6run_command
{
 "command": "grep -n \"def analyze_param\" fastapi/dependencies/utils.py"
}
{"status": "ok", "stdout": "362:def analyze_param(\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "362:def analyze_param(\n", "stderr": "", "exit_code": 0}
7read_file
{
 "filepath": "fastapi/dependencies/utils.py",
 "start_line": "362",
 "end_line": "500"
}
{"status": "error", "error_type": "FileReadError", "error_message": "'>' not supported between instances of 'int' and 'str'"}
full
{"status": "error", "error_type": "FileReadError", "error_message": "'>' not supported between instances of 'int' and 'str'"}
8run_command
{
 "command": "sed -n '362,500p' fastapi/dependencies/utils.py"
}
{"status": "ok", "stdout": "def analyze_param(\n *,\n param_name: str,\n annotation: Any,\n value: Any,\n is_path_param: bool,\n) -> ParamDetails:\n field_info
full
{"status": "ok", "stdout": "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            if value is not inspect.Signature.empty:
9run_command
{
 "command": "cat <<EOF > /tmp/repro.py\nfrom fastapi import FastAPI, Depends, Response\nfrom typing import Annotated\n\napp = FastAPI()\n\ndef modify_response(response: Response):\n    response.headers[\"X-Custom-Header\"] = \"CustomValue\"\n    return response\n\n@app.get(\"/\")\ndef endpoint(response: Annotated[Response, Depends(modify_response)]):\n    return {\"message\": \"Hello World\"}\n\nif __name__ == \"__main__\":\n    from fastapi.testclient import TestClient\n    client = TestClient(app)\n    try:\n        response = client.get(\"/\")\n        print(f\"Status: {response.status_code}\")\n        print(f\"Headers: {response.headers}\")\n    except Exception as e:\n        print(f\"Error: {e}\")\nEOF\npython3 /tmp/repro.py"
}
{"status": "error", "error_type": "CommandError", "error_message": "Traceback (most recent call last):\n File \"/tmp/repro.py\", line 10, in <module>\n @app.get
full
{"status": "error", "error_type": "CommandError", "error_message": "Traceback (most recent call last):\n  File \"/tmp/repro.py\", line 10, 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", "details": {"stdout": "", "stderr": "Traceback (most recent call last):\n  File \"/tmp/repro.py\", line 10, 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    ^\
10edit_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."}
11run_command
{
 "command": "sed -n '460,475p' fastapi/dependencies/utils.py"
}
{"status": "ok", "stdout": " SecurityScopes,\n ),\n ):\n assert depends is None, f\"Cannot specify `Depends` for type {type_annotation!r}\"\n assert field_info
full
{"status": "ok", "stdout": "            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", "stderr": "", "exit_code": 0}
12read_file
{
 "end_line": "480",
 "filepath": "fastapi/dependencies/utils.py",
 "start_line": "450"
}
{"status": "error", "error_type": "FileReadError", "error_message": "'>' not supported between instances of 'int' and 'str'"}
full
{"status": "error", "error_type": "FileReadError", "error_message": "'>' not supported between instances of 'int' and 'str'"}
13run_command
{
 "command": "sed -n '440,480p' fastapi/dependencies/utils.py"
}
{"status": "ok", "stdout": " )\n field_info = value\n if isinstance(field_info, FieldInfo):\n field_info.annotation = type_annotation\n\n # Get Depends from typ
full
{"status": "ok", "stdout": "        )\n        field_info = value\n        if isinstance(field_info, FieldInfo):\n            field_info.annotation = type_annotation\n\n    # 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,\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            type_annotation\n        ) or is_uploadfile_sequence_annotation(type_annotation):\n            field_info = params.File(annotation=use_annotation, default=default_value)\n        elif not field_annotation_is_scalar(annotation=type_annotation):\n            field_info = params.Body(annotation=use_annotation, default=default_value)\n", "stderr": "", "exit_code": 0}
14edit_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."}
15run_command
{
 "command": "cat -A fastapi/dependencies/utils.py | sed -n '455,475p'"
}
{"status": "ok", "stdout": " Request,$\n WebSocket,$\n HTTPConnection,$\n Response,$\n StarletteBackgroundTasks,$\n SecurityScopes,$\n ),$\n ):$\n assert depend
full
{"status": "ok", "stdout": "            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", "stderr": "", "exit_code": 0}
16edit_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}
17run_command
{
 "command": "python3 /tmp/repro.py"
}
{"status": "ok", "stdout": "Status: 200\nHeaders: Headers({'content-length': '25', 'content-type': 'application/json', 'x-custom-header': 'CustomValue'})\n", "s
full
{"status": "ok", "stdout": "Status: 200\nHeaders: Headers({'content-length': '25', 'content-type': 'application/json', 'x-custom-header': 'CustomValue'})\n", "stderr": "", "exit_code": 0}
18run_command
{
 "command": "cat <<EOF > /tmp/repro2.py\nfrom fastapi import FastAPI, Depends, Response\nfrom typing import Annotated\n\napp = FastAPI()\n\ndef modify_response(response: Response):\n    response.headers[\"X-Custom-Header\"] = \"CustomValue\"\n    return response\n\n@app.get(\"/\")\ndef endpoint(response: Response = Depends(modify_response)):\n    return {\"message\": \"Hello World\"}\n\nif __name__ == \"__main__\":\n    from fastapi.testclient import TestClient\n    client = TestClient(app)\n    try:\n        response = client.get(\"/\")\n        print(f\"Status: {response.status_code}\")\n        print(f\"Headers: {response.headers}\")\n    except Exception as e:\n        print(f\"Error: {e}\")\nEOF\npython3 /tmp/repro2.py"
}
{"status": "ok", "stdout": "Status: 200\nHeaders: Headers({'content-length': '25', 'content-type': 'application/json', 'x-custom-header': 'CustomValue'})\n", "s
full
{"status": "ok", "stdout": "Status: 200\nHeaders: Headers({'content-length': '25', 'content-type': 'application/json', 'x-custom-header': 'CustomValue'})\n", "stderr": "", "exit_code": 0}
19run_command
{
 "command": "cat <<EOF > /tmp/repro3.py\nfrom fastapi import FastAPI, Response\n\napp = FastAPI()\n\n@app.get(\"/\")\ndef endpoint(response: Response):\n    return {\"message\": \"Hello World\"}\n\nif __name__ == \"__main__\":\n    from fastapi.testclient import TestClient\n    client = TestClient(app)\n    try:\n        response = client.get(\"/\")\n        print(f\"Status: {response.status_code}\")\n    except Exception as e:\n        print(f\"Error: {e}\")\nEOF\npython3 /tmp/repro3.py"
}
{"status": "ok", "stdout": "Status: 200\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "Status: 200\n", "stderr": "", "exit_code": 0}
20submit_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 ===============================
../../../../../../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.70s