resolved RESOLVED PASS · 11 tool calls · 32 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\" . | head -20"
} | {"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-3 ×2 | read_file | {
"filepath": "fastapi/dependencies/utils.py",
"start_line": "120",
"end_line": "150"
} | {"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'"} |
| 4 | 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 |
| 5 | read_file | {
"filepath": "fastapi/dependencies/utils.py",
"start_line": "120",
"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'"} |
| 6 | 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 |
| 7 | 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} |
| 8 | read_file | {
"end_line": "250",
"filepath": "fastapi/dependencies/utils.py",
"start_line": "220"
} | {"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'"} |
| 9 | run_command | {
"command": "sed -n '120,140p' fastapi/dependencies/utils.py"
} | {"status": "ok", "stdout": " logger.error(multipart_not_installed_error)\n raise RuntimeError(multipart_not_installed_error) from None\n\n\ndef get_parameterlesfull{"status": "ok", "stdout": " logger.error(multipart_not_installed_error)\n raise RuntimeError(multipart_not_installed_error) from None\n\n\ndef 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\ndef get_flat_dependant(\n dependant: Dependant,\n *,\n skip_repeats: bool = False,\n visited: Optional[List[DependencyCacheKey]] = None,\n", "stderr": "", "exit_code": 0} |
| 10 | run_command | {
"command": "sed -n '220,250p' fastapi/dependencies/utils.py"
} | {"status": "ok", "stdout": " return None\n\n globalns = getattr(call, \"__globals__\", {})\n return get_typed_annotation(annotation, globalns)\n\n\ndef get_depefull{"status": "ok", "stdout": " return None\n\n globalns = getattr(call, \"__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 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", "stderr": "", "exit_code": 0} |
| 11 | edit_file | {
"filepath": "fastapi/dependencies/utils.py",
"new_string": " return get_dependant(\n path=path, call=depends.dependency, security_scopes=use_security_scopes, 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,7 @@\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, call=depends.dependency, security_scopes=use_security_scopes, scope=depends.scope\n )\n \n \n", "is_truncated": false} |
| 12 | submit_patch | {} | {"status": "ok", "patch_size": 618, "files_changed": 1}full{"status": "ok", "patch_size": 618, "files_changed": 1} |
diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py
index c5c6b69b..f71e84ee 100644
--- a/fastapi/dependencies/utils.py
+++ b/fastapi/dependencies/utils.py
@@ -129,7 +129,7 @@ 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 ===============================
../../../../../../kaggle/tmp/envs/overlays/starlette-0.49.3-py3-none-any/starlette/testclient.py:51
/kaggle/tmp/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.76s