← eval5_submission_v1s

fastapi_14301

resolved RESOLVED PASS · 11 tool calls · 128 s · fastapi/fastapi

Task input

🐛 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 calls (12)

#ToolArgumentsResult
1run_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./fast
full
{"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}
2read_file
{
 "filepath": "fastapi/dependencies/utils.py",
 "start_line": "120",
 "end_line": "140"
}
{"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'"}
3read_file
{
 "filepath": "fastapi/dependencies/utils.py"
}
{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": "import inspect\nfrom contextlib import AsyncExitStack, contextmanager\nfrom copy impor
full
{"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
4read_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'"}
5run_command
{
 "command": "grep -n \"def get_parameterless_sub_dependant\" fastapi/dependencies/utils.py"
}
{"status": "ok", "stdout": "124:def get_parameterless_sub_dependant(*, depends: params.Depends, path: str) -> Dependant:\n", "stderr": "", "exit_code": 0}
full
{"status": "ok", "stdout": "124:def get_parameterless_sub_dependant(*, depends: params.Depends, path: str) -> Dependant:\n", "stderr": "", "exit_code": 0}
6read_file
{
 "filepath": "fastapi/dependencies/utils.py"
}
{"status": "ok", "filepath": "fastapi/dependencies/utils.py", "content": "import inspect\nfrom contextlib import AsyncExitStack, contextmanager\nfrom copy impor
full
{"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
7run_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}
8read_file
{
 "end_line": "240",
 "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'"}
9run_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_parameterles
full
{"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}
10run_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_depe
full
{"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}
11edit_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/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@@ -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}
12submit_patch
{}
{"status": "ok", "patch_size": 618, "files_changed": 1}
full
{"status": "ok", "patch_size": 618, "files_changed": 1}

Patch

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
     )
 
 

Test output

show
...........                                                              [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.73s