Forbid passing str to Security `scopes` parameter. Add descriptive error message

This commit is contained in:
Yurii Motov 2025-11-19 22:01:06 +01:00
parent 02e108d166
commit 81e4a639af
3 changed files with 79 additions and 4 deletions

View File

@ -1,8 +1,19 @@
from typing import Any, Callable, Dict, List, Optional, Sequence, Union
from typing import (
Any,
Callable,
Dict,
FrozenSet,
List,
Optional,
Set,
Tuple,
Union,
)
from annotated_doc import Doc
from fastapi import params
from fastapi._compat import Undefined
from fastapi.exceptions import FastAPIError
from fastapi.openapi.models import Example
from typing_extensions import Annotated, Literal, deprecated
@ -2312,7 +2323,14 @@ def Security( # noqa: N802
] = None,
*,
scopes: Annotated[
Optional[Sequence[str]],
Optional[
Union[
List[str],
Tuple[str, ...],
Set[str],
FrozenSet[str],
]
],
Doc(
"""
OAuth2 scopes required for the *path operation* that uses this Security
@ -2378,4 +2396,18 @@ def Security( # noqa: N802
return [{"item_id": "Foo", "owner": current_user.username}]
```
"""
if isinstance(scopes, str):
if scopes in ("function", "request"):
raise FastAPIError(
"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."
)
raise FastAPIError(
"Invalid value for `scopes` 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'."
)
return params.Security(dependency=dependency, scopes=scopes, use_cache=use_cache)

View File

@ -1,7 +1,17 @@
import warnings
from dataclasses import dataclass
from enum import Enum
from typing import Any, Callable, Dict, List, Optional, Sequence, Union
from typing import (
Any,
Callable,
Dict,
FrozenSet,
List,
Optional,
Set,
Tuple,
Union,
)
from fastapi.openapi.models import Example
from pydantic.fields import FieldInfo
@ -771,4 +781,11 @@ class Depends:
@dataclass
class Security(Depends):
scopes: Optional[Sequence[str]] = None
scopes: Optional[
Union[
List[str],
Tuple[str, ...],
Set[str],
FrozenSet[str],
]
] = None

View File

@ -0,0 +1,26 @@
import pytest
from fastapi import Security
from fastapi.exceptions import FastAPIError
def test_pass_single_str():
with pytest.raises(FastAPIError) as exc_info:
Security(dependency=lambda: None, scopes="admin")
assert str(exc_info.value) == (
"Invalid value for `scopes` 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'."
)
@pytest.mark.parametrize("value", ["function", "request"])
def test_pass_scope_instead_of_scopes(value: str):
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."
)