From ed8040aa98a33a3295e51dffb1d61339b9d7ffd6 Mon Sep 17 00:00:00 2001 From: Yurii Motov Date: Tue, 25 Nov 2025 20:44:37 +0100 Subject: [PATCH] Handle mistakes when wrong value is passed to `scopes` or `oauth_scopes` --- fastapi/param_functions.py | 21 +++++++++++++++++++ tests/test_security_scopes_parameter.py | 27 +++++++++++++++++-------- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/fastapi/param_functions.py b/fastapi/param_functions.py index 2ec498d0f..f9389e0cd 100644 --- a/fastapi/param_functions.py +++ b/fastapi/param_functions.py @@ -2453,8 +2453,29 @@ def Security( # noqa: N802 stacklevel=2, ) + # Handle case when `scopes="function"` is mistakenly used instead of `scope="function"` + if isinstance(scopes, str) and (scopes in ("function", "request")): + raise FastAPIError( + "Invalid value for the 'scopes' parameter in Security(). " + "Expected a sequence of strings (e.g., ['admin', 'user']), but received " + "a single string. " + f'Did you mean to use scope="{scopes}" to specify when the exit code ' + "of dependencies with yield should run? " + ) + + oauth_scopes_param = "oauth_scopes" if (oauth_scopes is not None) else "scopes" oauth_scopes = oauth_scopes or scopes + # Handle case when single string is passed to `scopes` or `oauth_scopes` instead of + # a list of strings + if isinstance(oauth_scopes, str): + raise FastAPIError( + f"Invalid value for the '{oauth_scopes_param}' parameter in Security(). " + "Expected a sequence of strings (e.g., ['admin', 'user']), but received a " + "single string. Wrap it in a list: oauth_scopes=['your_scope'] instead of " + "oauth_scopes='your_scope'." + ) + return params.Security( dependency=dependency, oauth_scopes=oauth_scopes, diff --git a/tests/test_security_scopes_parameter.py b/tests/test_security_scopes_parameter.py index cd043bcd1..d893bbbf2 100644 --- a/tests/test_security_scopes_parameter.py +++ b/tests/test_security_scopes_parameter.py @@ -3,24 +3,35 @@ from fastapi import Security from fastapi.exceptions import FastAPIError -def test_pass_single_str(): +@pytest.mark.parametrize("parameter_name", ["scopes", "oauth_scopes"]) +@pytest.mark.filterwarnings("ignore::DeprecationWarning") +def test_pass_single_str(parameter_name: str): + """ + Test passing single string instead of list of strings to `scopes` or `oauth_scopes`. + """ + with pytest.raises(FastAPIError) as exc_info: - Security(dependency=lambda: None, scopes="admin") + Security(dependency=lambda: None, **{parameter_name: "admin"}) assert str(exc_info.value) == ( - "Invalid value for `scopes` parameter in Security(). " + f"Invalid value for the '{parameter_name}' parameter in Security(). " "Expected a sequence of strings (e.g., ['admin', 'user']), but received a single string. " - "Wrap it in a list: scopes=['your_scope'] instead of scopes='your_scope'." + "Wrap it in a list: oauth_scopes=['your_scope'] instead of oauth_scopes='your_scope'." ) @pytest.mark.parametrize("value", ["function", "request"]) -def test_pass_scope_instead_of_scopes(value: str): +@pytest.mark.filterwarnings("ignore::DeprecationWarning") +def test_pass_scope_as_scopes(value: str): + """ + Test passing `scopes="function"` instead of `scope="function"` to `Security`. + """ + with pytest.raises(FastAPIError) as exc_info: Security(dependency=lambda: None, scopes=value) assert str(exc_info.value) == ( - "Invalid value for `scopes` parameter in Security(). " - "You probably meant to use the `scope` parameter instead of `scopes`. " - "Expected a sequence of strings (e.g., ['admin', 'user']), but received a single string." + "Invalid value for the 'scopes' parameter in Security(). " + "Expected a sequence of strings (e.g., ['admin', 'user']), but received a single string. " + f'Did you mean to use scope="{value}" to specify when the exit code of dependencies with yield should run? ' )