resolved RESOLVED PASS · 13 tool calls · 49 s · fastapi/fastapi
🐛 Fix `Depends(func, scope='function')` for top level (parameterless) dependencies **Summary** This PR enables proper support for `scope="function"` on `APIRoute` dependencies by passing the dependency’s scope through to `get_dependant`. Previously, parameter-less dependencies created via `get_parameterless_sub_dependant` ignored `depends.scope`, which led to unexpected behavior for function-scoped dependencies. **Motivation** Users expect `scope="function"` to work as defined, even when registered at the route level. Because `depends.scope` wasn’t forwarded, function scope behaved like the default(request). This change aligns behavior with user expectations and documentation around dependency scopes. (Related: #14296)
| # | Tool | Arguments | Result |
|---|---|---|---|
| 1 | run_command | {
"command": "grep -rn \"get_parameterless_sub_dependant\" ."
} | {"status": "ok", "stdout": "./fastapi/dependencies/utils.py:124:def get_parameterless_sub_dependant(*, depends: params.Depends, path: str) -> Dependant:\n./fastfull{"status": "ok", "stdout": "./fastapi/dependencies/utils.py:124:def get_parameterless_sub_dependant(*, depends: params.Depends, path: str) -> Dependant:\n./fastapi/routing.py:44: get_parameterless_sub_dependant,\n./fastapi/routing.py:489: get_parameterless_sub_dependant(depends=depends, path=self.path_format),\n./fastapi/routing.py:642: get_parameterless_sub_dependant(depends=depends, path=self.path_format),\n", "stderr": "", "exit_code": 0} |
| 2 | read_file | {
"filepath": "fastapi/dependencies/utils.py",
"start_line": "124"
} | {"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'"} |
| 3 | read_file | {
"filepath": "fastapi/dependencies/utils.py"
} | {"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": "import inspect\nfrom contextlib import AsyncExitStack, contextmanager\nfrom copy imporfull{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": "import inspect\nfrom contextlib import AsyncExitStack, contextmanager\nfrom copy import copy, deepcopy\nfrom dataclasses import dataclass\nfrom typing import (\n Any,\n Callable,\n Coroutine,\n Dict,\n ForwardRef,\n List,\n Mapping,\n Optional,\n Sequence,\n Tuple,\n Type,\n Union,\n cast,\n)\n\nimport anyio\nfrom fastapi import params\nfrom fastapi._compat import (\n PYDANTIC_V2,\n ModelField,\n RequiredParam,\n Undefined,\n _is_error_wrapper,\n _is_model_class,\n copy_field_info,\n create_body_model,\n evaluate_forwardref,\n field_annotation_is_scalar,\n get_annotation_from_field_info,\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 may_v1,\n sequence_types,\n serialize_sequence_value,\n value_is_sequence,\n)\nfrom fastapi._compat.shared import annotation_is_pydantic_v1\nfrom fastapi.background import BackgroundTasks\nfrom fastapi.concurrency import (\n asynccontextmanager,\n contextmanager_in_threadpool,\n)\nfrom fastapi.dependencies.models import Dependant, SecurityRequirement\nfrom fastapi.exceptions import DependencyScopeError\nfrom fastapi.logger import logger\nfrom fastapi.security.base import SecurityBase\nfrom fastapi.security.oauth2 import OAuth2, SecurityScopes\nfrom fastapi.security.open_id_connect_url import OpenIdConnect\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\nfro |
| 4 | read_file | {
"filepath": "fastapi/dependencies/utils.py",
"start_line": "124",
"end_line": "135"
} | {"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'"} |
| 5 | read_file | {
"filepath": "fastapi/dependencies/utils.py"
} | {"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": "import inspect\nfrom contextlib import AsyncExitStack, contextmanager\nfrom copy imporfull{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": "import inspect\nfrom contextlib import AsyncExitStack, contextmanager\nfrom copy import copy, deepcopy\nfrom dataclasses import dataclass\nfrom typing import (\n Any,\n Callable,\n Coroutine,\n Dict,\n ForwardRef,\n List,\n Mapping,\n Optional,\n Sequence,\n Tuple,\n Type,\n Union,\n cast,\n)\n\nimport anyio\nfrom fastapi import params\nfrom fastapi._compat import (\n PYDANTIC_V2,\n ModelField,\n RequiredParam,\n Undefined,\n _is_error_wrapper,\n _is_model_class,\n copy_field_info,\n create_body_model,\n evaluate_forwardref,\n field_annotation_is_scalar,\n get_annotation_from_field_info,\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 may_v1,\n sequence_types,\n serialize_sequence_value,\n value_is_sequence,\n)\nfrom fastapi._compat.shared import annotation_is_pydantic_v1\nfrom fastapi.background import BackgroundTasks\nfrom fastapi.concurrency import (\n asynccontextmanager,\n contextmanager_in_threadpool,\n)\nfrom fastapi.dependencies.models import Dependant, SecurityRequirement\nfrom fastapi.exceptions import DependencyScopeError\nfrom fastapi.logger import logger\nfrom fastapi.security.base import SecurityBase\nfrom fastapi.security.oauth2 import OAuth2, SecurityScopes\nfrom fastapi.security.open_id_connect_url import OpenIdConnect\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\nfro |
| 6 | run_command | {
"command": "grep -n \"def get_dependant\" fastapi/dependencies/utils.py"
} | {"status": "ok", "stdout": "226:def get_dependant(\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "226:def get_dependant(\n", "stderr": "", "exit_code": 0} |
| 7 | read_file | {
"end_line": "240",
"filepath": "fastapi/dependencies/utils.py",
"start_line": "226"
} | {"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'"} |
| 8 | run_command | {
"command": "grep -n \"def get_dependant\" fastapi/dependencies/utils.py"
} | {"status": "ok", "stdout": "226:def get_dependant(\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "226:def get_dependant(\n", "stderr": "", "exit_code": 0} |
| 9 | read_file | {
"filepath": "fastapi/dependencies/utils.py"
} | {"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": "import inspect\nfrom contextlib import AsyncExitStack, contextmanager\nfrom copy imporfull{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": "import inspect\nfrom contextlib import AsyncExitStack, contextmanager\nfrom copy import copy, deepcopy\nfrom dataclasses import dataclass\nfrom typing import (\n Any,\n Callable,\n Coroutine,\n Dict,\n ForwardRef,\n List,\n Mapping,\n Optional,\n Sequence,\n Tuple,\n Type,\n Union,\n cast,\n)\n\nimport anyio\nfrom fastapi import params\nfrom fastapi._compat import (\n PYDANTIC_V2,\n ModelField,\n RequiredParam,\n Undefined,\n _is_error_wrapper,\n _is_model_class,\n copy_field_info,\n create_body_model,\n evaluate_forwardref,\n field_annotation_is_scalar,\n get_annotation_from_field_info,\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 may_v1,\n sequence_types,\n serialize_sequence_value,\n value_is_sequence,\n)\nfrom fastapi._compat.shared import annotation_is_pydantic_v1\nfrom fastapi.background import BackgroundTasks\nfrom fastapi.concurrency import (\n asynccontextmanager,\n contextmanager_in_threadpool,\n)\nfrom fastapi.dependencies.models import Dependant, SecurityRequirement\nfrom fastapi.exceptions import DependencyScopeError\nfrom fastapi.logger import logger\nfrom fastapi.security.base import SecurityBase\nfrom fastapi.security.oauth2 import OAuth2, SecurityScopes\nfrom fastapi.security.open_id_connect_url import OpenIdConnect\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\nfro |
| 10 | run_command | {
"command": "grep -n \"def get_dependant\" fastapi/dependencies/utils.py"
} | {"status": "ok", "stdout": "226:def get_dependant(\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "226:def get_dependant(\n", "stderr": "", "exit_code": 0} |
| 11 | run_command | {
"command": "sed -n '226,260p' fastapi/dependencies/utils.py"
} | {"status": "ok", "stdout": "def get_dependant(\n *,\n path: str,\n call: Callable[..., Any],\n name: Optional[str] = None,\n security_scopes: Optional[List[str]full{"status": "ok", "stdout": "def get_dependant(\n *,\n path: str,\n call: Callable[..., Any],\n name: Optional[str] = None,\n security_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 security_scopes=security_scopes,\n use_cache=use_cache,\n scope=scope,\n )\n path_param_names = get_path_param_names(path)\n endpoint_signature = get_typed_signature(call)\n signature_params = endpoint_signature.parameters\n if isinstance(call, SecurityBase):\n use_scopes: List[str] = []\n if isinstance(call, (OAuth2, OpenIdConnect)):\n use_scopes = security_scopes or use_scopes\n security_requirement = SecurityRequirement(\n security_scheme=call, scopes=use_scopes\n )\n dependant.security_requirements.append(security_requirement)\n for param_name, param in signature_params.items():\n 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", "stderr": "", "exit_code": 0} |
| 12 | run_command | {
"command": "sed -n '124,135p' fastapi/dependencies/utils.py"
} | {"status": "ok", "stdout": "def get_parameterless_sub_dependant(*, depends: params.Depends, path: str) -> Dependant:\n assert callable(depends.dependency), (\n full{"status": "ok", "stdout": "def get_parameterless_sub_dependant(*, depends: params.Depends, path: str) -> Dependant:\n assert callable(depends.dependency), (\n \"A parameter-less dependency must have a callable dependency\"\n )\n use_security_scopes: List[str] = []\n if isinstance(depends, params.Security) and depends.scopes:\n use_security_scopes.extend(depends.scopes)\n return get_dependant(\n path=path, call=depends.dependency, security_scopes=use_security_scopes\n )\n\n\n", "stderr": "", "exit_code": 0} |
| 13 | edit_file | {
"filepath": "fastapi/dependencies/utils.py",
"new_string": " return get_dependant(\n path=path,\n call=depends.dependency,\n security_scopes=use_security_scopes,\n scope=depends.scope,\n )",
"old_string": " return get_dependant(\n path=path, call=depends.dependency, security_scopes=use_security_scopes\n )"
} | {"status": "ok", "filepath": "fastapi/dependencies/utils.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/dependencies/utils.py\n+++ b/fastapifull{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "occurrences": 1, "strategy": "exact", "diff": "--- a/fastapi/dependencies/utils.py\n+++ b/fastapi/dependencies/utils.py\n@@ -129,7 +129,10 @@\n if isinstance(depends, params.Security) and depends.scopes:\n use_security_scopes.extend(depends.scopes)\n return get_dependant(\n- path=path, call=depends.dependency, security_scopes=use_security_scopes\n+ path=path,\n+ call=depends.dependency,\n+ security_scopes=use_security_scopes,\n+ scope=depends.scope,\n )\n \n \n", "is_truncated": false} |
| 14 | submit_patch | {} | {"status": "ok", "patch_size": 647, "files_changed": 1}full{"status": "ok", "patch_size": 647, "files_changed": 1} |
diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py
index c5c6b69b..1981e9e1 100644
--- a/fastapi/dependencies/utils.py
+++ b/fastapi/dependencies/utils.py
@@ -129,7 +129,10 @@ def get_parameterless_sub_dependant(*, depends: params.Depends, path: str) -> De
if isinstance(depends, params.Security) and depends.scopes:
use_security_scopes.extend(depends.scopes)
return get_dependant(
- path=path, call=depends.dependency, security_scopes=use_security_scopes
+ path=path,
+ call=depends.dependency,
+ security_scopes=use_security_scopes,
+ scope=depends.scope,
)
........... [100%]
=============================== warnings summary ===============================
../../../../../../../Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.49.3-py3-none-any/starlette/testclient.py:51
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-0.49.3-py3-none-any/starlette/testclient.py:51: 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
11 passed, 1 warning in 0.45s