failed NO_PATCH NO_PATCH UNSUBMITTED empty_patch(budget) · 10 tool calls · 313 s · fastapi/fastapi
✨ 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 | Arguments | Result |
|---|---|---|---|
| 1 | run_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 | run_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: |
| 3 | run_command | {
"command": "grep -n \"Cannot specify `Depends` for type Response\" fastapi/dependencies/utils.py"
} | {"status": "error", "error_type": "CommandError", "error_message": "/bin/bash: Depends: command not found\n", "details": {"stdout": "", "stderr": "/bin/bash: Defull{"status": "error", "error_type": "CommandError", "error_message": "/bin/bash: Depends: command not found\n", "details": {"stdout": "", "stderr": "/bin/bash: Depends: command not found\n", "exit_code": 1}} |
| 4 | run_command | {
"command": "grep -rn \"Cannot specify `Depends` for type Response\" fastapi"
} | {"status": "error", "error_type": "CommandError", "error_message": "/bin/bash: Depends: command not found\n", "details": {"stdout": "", "stderr": "/bin/bash: Defull{"status": "error", "error_type": "CommandError", "error_message": "/bin/bash: Depends: command not found\n", "details": {"stdout": "", "stderr": "/bin/bash: Depends: command not found\n", "exit_code": 1}} |
| 5 | run_command | {
"command": "grep -rn \"Cannot specify `Depends` for type\" fastapi"
} | {"status": "error", "error_type": "CommandError", "error_message": "/bin/bash: Depends: command not found\n", "details": {"stdout": "", "stderr": "/bin/bash: Defull{"status": "error", "error_type": "CommandError", "error_message": "/bin/bash: Depends: command not found\n", "details": {"stdout": "", "stderr": "/bin/bash: Depends: command not found\n", "exit_code": 1}} |
| 6 | run_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} |
| 7 | run_command | {
"command": "grep -rn \"Cannot specify `Depends`\" fastapi"
} | {"status": "ok", "stdout": "fastapi/dependencies/utils.py:320: f\"Cannot specify multiple FastAPI annotations for {param_name!r}\"\nfastapi/dependencies/utils.pfull{"status": "ok", "stdout": "fastapi/dependencies/utils.py:320: f\"Cannot specify multiple FastAPI annotations for {param_name!r}\"\nfastapi/dependencies/utils.py:427: \"Cannot specify `Depends` in `Annotated` and default value\"\nfastapi/dependencies/utils.py:431: \"Cannot specify a FastAPI annotation in `Annotated` and `Depends` as a\"\nfastapi/dependencies/utils.py:438: \"Cannot specify FastAPI annotations in `Annotated` and default value\"\nfastapi/dependencies/utils.py:463: assert depends is None, f\"Cannot specify `Depends` for type {type_annotation!r}\"\nfastapi/dependencies/utils.py:465: f\"Cannot specify FastAPI annotation for type {type_annotation!r}\"\n", "stderr": "/bin/bash: Depends: command not found\n", "exit_code": 0} |
| 8 | run_command | {
"command": "sed -n '450,480p' 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 HTTPCofull{"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 # 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} |
| 9 | run_command | {
"command": "python3 - <<'PY'\nfrom fastapi import FastAPI, Response, Depends\nfrom typing import Annotated\nfrom fastapi.dependencies.utils import analyze_param\nimport inspect\n\ndef modify_response():\n return Response(content=\"modified\")\n\n# Case 1: Annotated[Response, Depends(modify_response)]\ntry:\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\")\nexcept AssertionError as e:\n print(f\"Case 1 failed: {e}\")\n\n# Case 2: response: Response = Depends(modify_response)\ntry:\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\")\nexcept AssertionError as e:\n print(f\"Case 2 failed: {e}\")\nPY\n"
} | {"status": "ok", "stdout": "Case 1 failed: Cannot specify `Depends` for type <class 'starlette.responses.Response'>\nCase 2 failed: Cannot specify `Depends` forfull{"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} |
| 10 | run_command | {
"command": "cd /workspace && python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/dependencies/utils.py\")\ns = 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 field_info is None and lenient_issubclass(\n type_annotation,\n (\n Request,\n WebSocket,\n HTTPConnection,\n Response,\n StarletteBackgroundTasks,\n SecurityScopes,\n ),\n ):\n # This part is actually not needed if we just check depends is None and field_info is None\n # but we want to keep the logic that if it IS one of these types and NOT a dependency,\n # it's handled here.\n pass\n elif depends is not None and lenient_issubclass(\n type_annotation,\n (\n Request,\n WebSocket,\n HTTPConnection,\n Response,\n StarletteBackgroundTasks,\n SecurityScopes,\n ),\n ):\n # If it is a dependency, we allow it even if it's one of these types.\n # We just need to make sure it doesn't fall into the 'elif field_info is None and depends is None' block.\n pass\n elif field_info is None and depends is None:\n # ... (rest of the logic)\n pass\n\"\"\"\n# Wait, the logic in the original code was:\n# if is_special_type:\n# assert depends is None\n# assert field_info is None\n# elif field_info is None and depends is None:\n# ...\n\n# If we want to allow depends for special types, we should change it to:\n# if depends is None and field_info is None and is_special_type:\n# # handle special type without depends/field_info\n# elif depends is not None and is_special_type:\n# # allow depends for special type\n# elif field_info is None and depends is None:\n# # handle normal type without depends/field_info\nPY\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |