← pod_r3_v1e

fastapi_14794

resolved RESOLVED PASS · 21 tool calls · 76 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 (22)

#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}
2run_command
{
 "command": "sed -n '362,450p' 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:
3run_command
{
 "command": "grep -rn \"Cannot specify `Depends` for type Response\" fastapi | head -20"
}
{"status": "ok", "stdout": "", "stderr": "/bin/bash: Depends: command not found\n", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "/bin/bash: Depends: command not found\n", "exit_code": 0}
4run_command
{
 "command": "grep -rn \"Cannot specify `Depends` for type\" fastapi | head -20"
}
{"status": "ok", "stdout": "", "stderr": "/bin/bash: Depends: command not found\n", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "/bin/bash: Depends: command not found\n", "exit_code": 0}
5run_command
{
 "command": "grep -rn \"Cannot specify `Depends` for type\" . | head -20"
}
{"status": "ok", "stdout": "", "stderr": "/bin/bash: Depends: command not found\n", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "/bin/bash: Depends: command not found\n", "exit_code": 0}
6run_command
{
 "command": "grep -rn \"Response\" fastapi/dependencies/utils.py | head -20"
}
{"status": "ok", "stdout": "fastapi/dependencies/utils.py:66:from starlette.responses import Response\nfastapi/dependencies/utils.py:343: elif lenient_issubclas
full
{"status": "ok", "stdout": "fastapi/dependencies/utils.py:66:from starlette.responses import Response\nfastapi/dependencies/utils.py:343:    elif lenient_issubclass(type_annotation, Response):\nfastapi/dependencies/utils.py:458:            Response,\nfastapi/dependencies/utils.py:564:    response: Response\nfastapi/dependencies/utils.py:574:    response: Optional[Response] = None,\nfastapi/dependencies/utils.py:593:        response = Response()\n", "stderr": "", "exit_code": 0}
7run_command
{
 "command": "sed -n '340,350p' fastapi/dependencies/utils.py"
}
{"status": "ok", "stdout": " elif lenient_issubclass(type_annotation, HTTPConnection):\n dependant.http_connection_param_name = param_name\n return True\n elif
full
{"status": "ok", "stdout": "    elif lenient_issubclass(type_annotation, HTTPConnection):\n        dependant.http_connection_param_name = param_name\n        return True\n    elif lenient_issubclass(type_annotation, Response):\n        dependant.response_param_name = param_name\n        return True\n    elif lenient_issubclass(type_annotation, StarletteBackgroundTasks):\n        dependant.background_tasks_param_name = param_name\n        return True\n    elif lenient_issubclass(type_annotation, SecurityScopes):\n        dependant.security_scopes_param_name = param_name\n", "stderr": "", "exit_code": 0}
8run_command
{
 "command": "sed -n '330,360p' fastapi/dependencies/utils.py"
}
{"status": "ok", "stdout": "\ndef add_non_field_param_to_dependency(\n *, param_name: str, type_annotation: Any, dependant: Dependant\n) -> Optional[bool]:\n if
full
{"status": "ok", "stdout": "\ndef add_non_field_param_to_dependency(\n    *, param_name: str, type_annotation: Any, dependant: Dependant\n) -> Optional[bool]:\n    if lenient_issubclass(type_annotation, Request):\n        dependant.request_param_name = param_name\n        return True\n    elif lenient_issubclass(type_annotation, WebSocket):\n        dependant.websocket_param_name = param_name\n        return True\n    elif lenient_issubclass(type_annotation, HTTPConnection):\n        dependant.http_connection_param_name = param_name\n        return True\n    elif lenient_issubclass(type_annotation, Response):\n        dependant.response_param_name = param_name\n        return True\n    elif lenient_issubclass(type_annotation, StarletteBackgroundTasks):\n        dependant.background_tasks_param_name = param_name\n        return True\n    elif lenient_issubclass(type_annotation, SecurityScopes):\n        dependant.security_scopes_param_name = param_name\n        return True\n    return None\n\n\n@dataclass\nclass ParamDetails:\n    type_annotation: Any\n    depends: Optional[params.Depends]\n    field: Optional[ModelField]\n\n", "stderr": "", "exit_code": 0}
9run_command
{
 "command": "sed -n '280,300p' fastapi/dependencies/utils.py"
}
{"status": "ok", "stdout": " is_path_param = param_name in path_param_names\n param_details = analyze_param(\n param_name=param_name,\n annotation=param.annotat
full
{"status": "ok", "stdout": "        is_path_param = param_name in path_param_names\n        param_details = analyze_param(\n            param_name=param_name,\n            annotation=param.annotation,\n            value=param.default,\n            is_path_param=is_path_param,\n        )\n        if param_details.depends is not None:\n            assert param_details.depends.dependency\n            if (\n                (dependant.is_gen_callable or dependant.is_async_gen_callable)\n                and dependant.computed_scope == \"request\"\n                and param_details.depends.scope == \"function\"\n            ):\n                assert dependant.call\n                raise DependencyScopeError(\n                    f'The dependency \"{dependant.call.__name__}\" has a scope of '\n                    '\"request\", it cannot depend on dependencies with scope \"function\".'\n                )\n            sub_own_oauth_scopes: list[str] = []\n            if isinstance(param_details.depends, params.Security):\n", "stderr": "", "exit_code": 0}
10run_command
{
 "command": "sed -n '260,280p' fastapi/dependencies/utils.py"
}
{"status": "ok", "stdout": " name: Optional[str] = None,\n own_oauth_scopes: Optional[list[str]] = None,\n parent_oauth_scopes: Optional[list[str]] = None,\n us
full
{"status": "ok", "stdout": "    name: Optional[str] = None,\n    own_oauth_scopes: Optional[list[str]] = None,\n    parent_oauth_scopes: Optional[list[str]] = None,\n    use_cache: bool = True,\n    scope: Union[Literal[\"function\", \"request\"], None] = None,\n) -> Dependant:\n    dependant = Dependant(\n        call=call,\n        name=name,\n        path=path,\n        use_cache=use_cache,\n        scope=scope,\n        own_oauth_scopes=own_oauth_scopes,\n        parent_oauth_scopes=parent_oauth_scopes,\n    )\n    current_scopes = (parent_oauth_scopes or []) + (own_oauth_scopes or [])\n    path_param_names = get_path_param_names(path)\n    endpoint_signature = get_typed_signature(call)\n    signature_params = endpoint_signature.parameters\n    for param_name, param in signature_params.items():\n        is_path_param = param_name in path_param_names\n", "stderr": "", "exit_code": 0}
11run_command
{
 "command": "sed -n '240,260p' fastapi/dependencies/utils.py"
}
{"status": "ok", "stdout": " return None\n return annotation\n\n\ndef get_typed_return_annotation(call: Callable[..., Any]) -> Any:\n signature = _get_signature
full
{"status": "ok", "stdout": "            return None\n    return annotation\n\n\ndef get_typed_return_annotation(call: Callable[..., Any]) -> Any:\n    signature = _get_signature(call)\n    unwrapped = inspect.unwrap(call)\n    annotation = signature.return_annotation\n\n    if annotation is inspect.Signature.empty:\n        return None\n\n    globalns = getattr(unwrapped, \"__globals__\", {})\n    return get_typed_annotation(annotation, globalns)\n\n\ndef get_dependant(\n    *,\n    path: str,\n    call: Callable[..., Any],\n    name: Optional[str] = None,\n", "stderr": "", "exit_code": 0}
12run_command
{
 "command": "sed -n '280,360p' fastapi/dependencies/utils.py"
}
{"status": "ok", "stdout": " is_path_param = param_name in path_param_names\n param_details = analyze_param(\n param_name=param_name,\n annotation=param.annotat
full
{"status": "ok", "stdout": "        is_path_param = param_name in path_param_names\n        param_details = analyze_param(\n            param_name=param_name,\n            annotation=param.annotation,\n            value=param.default,\n            is_path_param=is_path_param,\n        )\n        if param_details.depends is not None:\n            assert param_details.depends.dependency\n            if (\n                (dependant.is_gen_callable or dependant.is_async_gen_callable)\n                and dependant.computed_scope == \"request\"\n                and param_details.depends.scope == \"function\"\n            ):\n                assert dependant.call\n                raise DependencyScopeError(\n                    f'The dependency \"{dependant.call.__name__}\" has a scope of '\n                    '\"request\", it cannot depend on dependencies with scope \"function\".'\n                )\n            sub_own_oauth_scopes: list[str] = []\n            if isinstance(param_details.depends, params.Security):\n                if param_details.depends.scopes:\n                    sub_own_oauth_scopes = list(param_details.depends.scopes)\n            sub_dependant = get_dependant(\n                path=path,\n                call=param_details.depends.dependency,\n                name=param_name,\n                own_oauth_scopes=sub_own_oauth_scopes,\n                parent_oauth_scopes=current_scopes,\n                use_cache=param_details.depends.use_cache,\n                scope=param_details.depends.scope,\n            )\n            dependant.dependencies.append(sub_dependant)\n            continue\n        if add_non_field_param_to_dependency(\n            param_name=param_name,\n            type_annotation=param_details.type_annotation,\n            dependant=dependant,\n        ):\n            assert param_details.field is None, (\n                f\"Cannot specify multiple FastAPI annotations for {param_name!r}\"\n            )\n            continue\n  
13run_command
{
 "command": "sed -n '450,470p' fastapi/dependencies/utils.py"
}
{"status": "ok", "stdout": "\n # Handle non-param type annotations like Request\n if lenient_issubclass(\n type_annotation,\n (\n Request,\n WebSocket,\n HTTPCo
full
{"status": "ok", "stdout": "\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", "stderr": "", "exit_code": 0}
14run_command
{
 "command": "sed -n '450,465p' fastapi/dependencies/utils.py"
}
{"status": "ok", "stdout": "\n # Handle non-param type annotations like Request\n if lenient_issubclass(\n type_annotation,\n (\n Request,\n WebSocket,\n HTTPCo
full
{"status": "ok", "stdout": "\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", "stderr": "", "exit_code": 0}
15run_command
{
 "command": "python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/dependencies/utils.py\"); s = p.read_text()\nold = \"\"\"    # 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        )\"\"\"\nnew = \"\"\"    # 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        )\"\"\"\nassert s.count(old) == 1, (\"old text found this many times:\", s.count(old))\nt = s.replace(old, new); compile(t, str(p), \"exec\"); p.write_text(t)\nPY\n"
}
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}
16run_command
{
 "command": "sed -n '450,465p' fastapi/dependencies/utils.py"
}
{"status": "ok", "stdout": "\n # Handle non-param type annotations like Request\n if depends is None and lenient_issubclass(\n type_annotation,\n (\n Request,\n
full
{"status": "ok", "stdout": "\n    # 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        )\n", "stderr": "", "exit_code": 0}
17run_command
{
 "command": "python3 - <<'PY'\nfrom fastapi import FastAPI, Depends, Response\nfrom fastapi.testclient import TestClient\nfrom typing import Annotated\n\ndef modify_response(response: Response):\n    response.headers[\"X-Custom-Header\"] = \"CustomValue\"\n    return response\n\napp = FastAPI()\n\n@app.get(\"/\")\ndef endpoint(response: Annotated[Response, Depends(modify_response)]):\n    return {\"message\": \"Hello World\"}\n\nclient = TestClient(app)\nresponse = client.get(\"/\")\nassert response.status_code == 200\nassert response.headers[\"X-Custom-Header\"] == \"CustomValue\"\nprint(\"Test passed!\")\nPY\n"
}
{"status": "ok", "stdout": "Test passed!\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "Test passed!\n", "stderr": "", "exit_code": 0}
18run_command
{
 "command": "python3 - <<'PY'\nfrom fastapi import FastAPI, Depends, Response\nfrom fastapi.testclient import TestClient\n\ndef modify_response(response: Response):\n    response.headers[\"X-Custom-Header\"] = \"CustomValue\"\n    return response\n\napp = FastAPI()\n\n@app.get(\"/\")\ndef endpoint(response: Response = Depends(modify_response)):\n    return {\"message\": \"Hello World\"}\n\nclient = TestClient(app)\nresponse = client.get(\"/\")\nassert response.status_code == 200\nassert response.headers[\"X-Custom-Header\"] == \"CustomValue\"\nprint(\"Test passed!\")\nPY\n"
}
{"status": "ok", "stdout": "Test passed!\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "Test passed!\n", "stderr": "", "exit_code": 0}
19run_command
{
 "command": "python3 - <<'PY'\nfrom fastapi import FastAPI, Response\nfrom fastapi.testclient import TestClient\n\napp = FastAPI()\n\n@app.get(\"/\")\ndef endpoint(response: Response):\n    response.headers[\"X-Custom-Header\"] = \"CustomValue\"\n    return {\"message\": \"Hello World\"}\n\nclient = TestClient(app)\nresponse = client.get(\"/\")\nassert response.status_code == 200\nassert response.headers[\"X-Custom-Header\"] == \"CustomValue\"\nprint(\"Test passed!\")\nPY\n"
}
{"status": "ok", "stdout": "Test passed!\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "Test passed!\n", "stderr": "", "exit_code": 0}
20run_command
{
 "command": "git diff"
}
{"status": "ok", "stdout": "diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py\nindex fc5dfed8..883f9b74 100644\n--- a/fastapi/dependenc
full
{"status": "ok", "stdout": "diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py\nindex fc5dfed8..883f9b74 100644\n--- a/fastapi/dependencies/utils.py\n+++ b/fastapi/dependencies/utils.py\n@@ -449,7 +449,7 @@ def analyze_param(\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 @@ def analyze_param(\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", "stderr": "", "exit_code": 0}
21run_command
{
 "command": "submit_patch"
}
{"status": "error", "error_type": "CommandError", "error_message": "/bin/bash: submit_patch: command not found\n", "details": {"stdout": "", "stderr": "/bin/bas
full
{"status": "error", "error_type": "CommandError", "error_message": "/bin/bash: submit_patch: command not found\n", "details": {"stdout": "", "stderr": "/bin/bash: submit_patch: command not found\n", "exit_code": 127}}
22submit_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.39s