[fastapi] Handle ellipsis defaults in FAST002 autofix (FAST002) (#20810)

## Summary

Implement handling of ellipsis (`...`) defaults in the `FAST002` autofix
to correctly differentiate between required and optional parameters in
FastAPI route definitions.

Previously, the autofix did not properly handle cases where parameters
used `...` as a default value (to indicate required parameters). This
could lead to incorrect transformations when applying the autofix.

This change updates the `FAST002` autofix logic to:
- Correctly recognize `...` as a valid FastAPI required default.
- Preserve the semantics of required parameters while still applying
other autofix improvements.
- Avoid incorrectly substituting or removing ellipsis defaults.

Fixes https://github.com/astral-sh/ruff/issues/20800

## Test Plan

Added a new test fixture at:
```crates/ruff_linter/resources/test/fixtures/fastapi/FAST002_2.py```
This commit is contained in:
Hengky Kurniawan 2025-10-21 09:47:13 +07:00 committed by GitHub
parent 692b7d7f0c
commit a802d7a0ea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 725 additions and 7 deletions

View file

@ -0,0 +1,87 @@
"""Test FAST002 ellipsis handling."""
from fastapi import Body, Cookie, FastAPI, Header, Query
app = FastAPI()
# Cases that should be fixed - ellipsis should be removed
@app.get("/test1")
async def test_ellipsis_query(
# This should become: param: Annotated[str, Query(description="Test param")]
param: str = Query(..., description="Test param"),
) -> str:
return param
@app.get("/test2")
async def test_ellipsis_header(
# This should become: auth: Annotated[str, Header(description="Auth header")]
auth: str = Header(..., description="Auth header"),
) -> str:
return auth
@app.post("/test3")
async def test_ellipsis_body(
# This should become: data: Annotated[dict, Body(description="Request body")]
data: dict = Body(..., description="Request body"),
) -> dict:
return data
@app.get("/test4")
async def test_ellipsis_cookie(
# This should become: session: Annotated[str, Cookie(description="Session ID")]
session: str = Cookie(..., description="Session ID"),
) -> str:
return session
@app.get("/test5")
async def test_simple_ellipsis(
# This should become: id: Annotated[str, Query()]
id: str = Query(...),
) -> str:
return id
@app.get("/test6")
async def test_multiple_kwargs_with_ellipsis(
# This should become: param: Annotated[str, Query(description="Test", min_length=1, max_length=10)]
param: str = Query(..., description="Test", min_length=1, max_length=10),
) -> str:
return param
# Cases with actual default values - these should preserve the default
@app.get("/test7")
async def test_with_default_value(
# This should become: param: Annotated[str, Query(description="Test")] = "default"
param: str = Query("default", description="Test"),
) -> str:
return param
@app.get("/test8")
async def test_with_default_none(
# This should become: param: Annotated[str | None, Query(description="Test")] = None
param: str | None = Query(None, description="Test"),
) -> str:
return param or "empty"
@app.get("/test9")
async def test_mixed_parameters(
# First param should be fixed with default preserved
optional_param: str = Query("default", description="Optional"),
# Second param should not be fixed because of the preceding default
required_param: str = Query(..., description="Required"),
# Third param should be fixed with default preserved
another_optional_param: int = Query(42, description="Another optional"),
) -> str:
return f"{required_param}-{optional_param}-{another_optional_param}"

View file

@ -18,6 +18,7 @@ mod tests {
#[test_case(Rule::FastApiRedundantResponseModel, Path::new("FAST001.py"))]
#[test_case(Rule::FastApiNonAnnotatedDependency, Path::new("FAST002_0.py"))]
#[test_case(Rule::FastApiNonAnnotatedDependency, Path::new("FAST002_1.py"))]
#[test_case(Rule::FastApiNonAnnotatedDependency, Path::new("FAST002_2.py"))]
#[test_case(Rule::FastApiUnusedPathParameter, Path::new("FAST003.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.name(), path.to_string_lossy());
@ -56,6 +57,7 @@ mod tests {
// since `typing.Annotated` was added in Python 3.9
#[test_case(Rule::FastApiNonAnnotatedDependency, Path::new("FAST002_0.py"))]
#[test_case(Rule::FastApiNonAnnotatedDependency, Path::new("FAST002_1.py"))]
#[test_case(Rule::FastApiNonAnnotatedDependency, Path::new("FAST002_2.py"))]
fn rules_py38(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}_py38", rule_code.name(), path.to_string_lossy());
let diagnostics = test_path(

View file

@ -275,19 +275,42 @@ fn create_diagnostic(
.collect::<Vec<_>>()
.join(", ");
seen_default = true;
format!(
"{parameter_name}: {binding}[{annotation}, {default_}({kwarg_list})] \
= {default_value}",
// Check if the default argument is ellipsis (...), which in FastAPI means "required"
let is_default_argument_ellipsis = matches!(
dependency_call.default_argument.value(),
ast::Expr::EllipsisLiteral(_)
);
if is_default_argument_ellipsis && seen_default {
// For ellipsis after a parameter with default, can't remove the default
return Ok(None);
}
if !is_default_argument_ellipsis {
// For actual default values, mark that we've seen a default
seen_default = true;
}
let base_format = format!(
"{parameter_name}: {binding}[{annotation}, {default_}({kwarg_list})]",
parameter_name = parameter.name,
annotation = checker.locator().slice(parameter.annotation.range()),
default_ = checker
.locator()
.slice(map_callable(parameter.default).range()),
default_value = checker
);
if is_default_argument_ellipsis {
// For ellipsis, don't add a default value since the parameter
// should remain required after conversion to Annotated
base_format
} else {
// For actual default values, preserve them
let default_value = checker
.locator()
.slice(dependency_call.default_argument.value().range()),
)
.slice(dependency_call.default_argument.value().range());
format!("{base_format} = {default_value}")
}
}
_ => {
if seen_default {

View file

@ -0,0 +1,303 @@
---
source: crates/ruff_linter/src/rules/fastapi/mod.rs
---
FAST002 [*] FastAPI dependency without `Annotated`
--> FAST002_2.py:14:5
|
12 | async def test_ellipsis_query(
13 | # This should become: param: Annotated[str, Query(description="Test param")]
14 | param: str = Query(..., description="Test param"),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
15 | ) -> str:
16 | return param
|
help: Replace with `typing.Annotated`
1 | """Test FAST002 ellipsis handling."""
2 |
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
4 + from typing import Annotated
5 |
6 | app = FastAPI()
7 |
--------------------------------------------------------------------------------
12 | @app.get("/test1")
13 | async def test_ellipsis_query(
14 | # This should become: param: Annotated[str, Query(description="Test param")]
- param: str = Query(..., description="Test param"),
15 + param: Annotated[str, Query(description="Test param")],
16 | ) -> str:
17 | return param
18 |
note: This is an unsafe fix and may change runtime behavior
FAST002 [*] FastAPI dependency without `Annotated`
--> FAST002_2.py:22:5
|
20 | async def test_ellipsis_header(
21 | # This should become: auth: Annotated[str, Header(description="Auth header")]
22 | auth: str = Header(..., description="Auth header"),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
23 | ) -> str:
24 | return auth
|
help: Replace with `typing.Annotated`
1 | """Test FAST002 ellipsis handling."""
2 |
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
4 + from typing import Annotated
5 |
6 | app = FastAPI()
7 |
--------------------------------------------------------------------------------
20 | @app.get("/test2")
21 | async def test_ellipsis_header(
22 | # This should become: auth: Annotated[str, Header(description="Auth header")]
- auth: str = Header(..., description="Auth header"),
23 + auth: Annotated[str, Header(description="Auth header")],
24 | ) -> str:
25 | return auth
26 |
note: This is an unsafe fix and may change runtime behavior
FAST002 [*] FastAPI dependency without `Annotated`
--> FAST002_2.py:30:5
|
28 | async def test_ellipsis_body(
29 | # This should become: data: Annotated[dict, Body(description="Request body")]
30 | data: dict = Body(..., description="Request body"),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
31 | ) -> dict:
32 | return data
|
help: Replace with `typing.Annotated`
1 | """Test FAST002 ellipsis handling."""
2 |
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
4 + from typing import Annotated
5 |
6 | app = FastAPI()
7 |
--------------------------------------------------------------------------------
28 | @app.post("/test3")
29 | async def test_ellipsis_body(
30 | # This should become: data: Annotated[dict, Body(description="Request body")]
- data: dict = Body(..., description="Request body"),
31 + data: Annotated[dict, Body(description="Request body")],
32 | ) -> dict:
33 | return data
34 |
note: This is an unsafe fix and may change runtime behavior
FAST002 [*] FastAPI dependency without `Annotated`
--> FAST002_2.py:38:5
|
36 | async def test_ellipsis_cookie(
37 | # This should become: session: Annotated[str, Cookie(description="Session ID")]
38 | session: str = Cookie(..., description="Session ID"),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
39 | ) -> str:
40 | return session
|
help: Replace with `typing.Annotated`
1 | """Test FAST002 ellipsis handling."""
2 |
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
4 + from typing import Annotated
5 |
6 | app = FastAPI()
7 |
--------------------------------------------------------------------------------
36 | @app.get("/test4")
37 | async def test_ellipsis_cookie(
38 | # This should become: session: Annotated[str, Cookie(description="Session ID")]
- session: str = Cookie(..., description="Session ID"),
39 + session: Annotated[str, Cookie(description="Session ID")],
40 | ) -> str:
41 | return session
42 |
note: This is an unsafe fix and may change runtime behavior
FAST002 [*] FastAPI dependency without `Annotated`
--> FAST002_2.py:46:5
|
44 | async def test_simple_ellipsis(
45 | # This should become: id: Annotated[str, Query()]
46 | id: str = Query(...),
| ^^^^^^^^^^^^^^^^^^^^
47 | ) -> str:
48 | return id
|
help: Replace with `typing.Annotated`
1 | """Test FAST002 ellipsis handling."""
2 |
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
4 + from typing import Annotated
5 |
6 | app = FastAPI()
7 |
--------------------------------------------------------------------------------
44 | @app.get("/test5")
45 | async def test_simple_ellipsis(
46 | # This should become: id: Annotated[str, Query()]
- id: str = Query(...),
47 + id: Annotated[str, Query()],
48 | ) -> str:
49 | return id
50 |
note: This is an unsafe fix and may change runtime behavior
FAST002 [*] FastAPI dependency without `Annotated`
--> FAST002_2.py:54:5
|
52 | async def test_multiple_kwargs_with_ellipsis(
53 | # This should become: param: Annotated[str, Query(description="Test", min_length=1, max_length=10)]
54 | param: str = Query(..., description="Test", min_length=1, max_length=10),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
55 | ) -> str:
56 | return param
|
help: Replace with `typing.Annotated`
1 | """Test FAST002 ellipsis handling."""
2 |
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
4 + from typing import Annotated
5 |
6 | app = FastAPI()
7 |
--------------------------------------------------------------------------------
52 | @app.get("/test6")
53 | async def test_multiple_kwargs_with_ellipsis(
54 | # This should become: param: Annotated[str, Query(description="Test", min_length=1, max_length=10)]
- param: str = Query(..., description="Test", min_length=1, max_length=10),
55 + param: Annotated[str, Query(description="Test", min_length=1, max_length=10)],
56 | ) -> str:
57 | return param
58 |
note: This is an unsafe fix and may change runtime behavior
FAST002 [*] FastAPI dependency without `Annotated`
--> FAST002_2.py:65:5
|
63 | async def test_with_default_value(
64 | # This should become: param: Annotated[str, Query(description="Test")] = "default"
65 | param: str = Query("default", description="Test"),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
66 | ) -> str:
67 | return param
|
help: Replace with `typing.Annotated`
1 | """Test FAST002 ellipsis handling."""
2 |
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
4 + from typing import Annotated
5 |
6 | app = FastAPI()
7 |
--------------------------------------------------------------------------------
63 | @app.get("/test7")
64 | async def test_with_default_value(
65 | # This should become: param: Annotated[str, Query(description="Test")] = "default"
- param: str = Query("default", description="Test"),
66 + param: Annotated[str, Query(description="Test")] = "default",
67 | ) -> str:
68 | return param
69 |
note: This is an unsafe fix and may change runtime behavior
FAST002 [*] FastAPI dependency without `Annotated`
--> FAST002_2.py:73:5
|
71 | async def test_with_default_none(
72 | # This should become: param: Annotated[str | None, Query(description="Test")] = None
73 | param: str | None = Query(None, description="Test"),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
74 | ) -> str:
75 | return param or "empty"
|
help: Replace with `typing.Annotated`
1 | """Test FAST002 ellipsis handling."""
2 |
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
4 + from typing import Annotated
5 |
6 | app = FastAPI()
7 |
--------------------------------------------------------------------------------
71 | @app.get("/test8")
72 | async def test_with_default_none(
73 | # This should become: param: Annotated[str | None, Query(description="Test")] = None
- param: str | None = Query(None, description="Test"),
74 + param: Annotated[str | None, Query(description="Test")] = None,
75 | ) -> str:
76 | return param or "empty"
77 |
note: This is an unsafe fix and may change runtime behavior
FAST002 [*] FastAPI dependency without `Annotated`
--> FAST002_2.py:81:5
|
79 | async def test_mixed_parameters(
80 | # First param should be fixed with default preserved
81 | optional_param: str = Query("default", description="Optional"),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
82 | # Second param should not be fixed because of the preceding default
83 | required_param: str = Query(..., description="Required"),
|
help: Replace with `typing.Annotated`
1 | """Test FAST002 ellipsis handling."""
2 |
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
4 + from typing import Annotated
5 |
6 | app = FastAPI()
7 |
--------------------------------------------------------------------------------
79 | @app.get("/test9")
80 | async def test_mixed_parameters(
81 | # First param should be fixed with default preserved
- optional_param: str = Query("default", description="Optional"),
82 + optional_param: Annotated[str, Query(description="Optional")] = "default",
83 | # Second param should not be fixed because of the preceding default
84 | required_param: str = Query(..., description="Required"),
85 | # Third param should be fixed with default preserved
note: This is an unsafe fix and may change runtime behavior
FAST002 FastAPI dependency without `Annotated`
--> FAST002_2.py:83:5
|
81 | optional_param: str = Query("default", description="Optional"),
82 | # Second param should not be fixed because of the preceding default
83 | required_param: str = Query(..., description="Required"),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
84 | # Third param should be fixed with default preserved
85 | another_optional_param: int = Query(42, description="Another optional"),
|
help: Replace with `typing.Annotated`
FAST002 [*] FastAPI dependency without `Annotated`
--> FAST002_2.py:85:5
|
83 | required_param: str = Query(..., description="Required"),
84 | # Third param should be fixed with default preserved
85 | another_optional_param: int = Query(42, description="Another optional"),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
86 | ) -> str:
87 | return f"{required_param}-{optional_param}-{another_optional_param}"
|
help: Replace with `typing.Annotated`
1 | """Test FAST002 ellipsis handling."""
2 |
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
4 + from typing import Annotated
5 |
6 | app = FastAPI()
7 |
--------------------------------------------------------------------------------
83 | # Second param should not be fixed because of the preceding default
84 | required_param: str = Query(..., description="Required"),
85 | # Third param should be fixed with default preserved
- another_optional_param: int = Query(42, description="Another optional"),
86 + another_optional_param: Annotated[int, Query(description="Another optional")] = 42,
87 | ) -> str:
88 | return f"{required_param}-{optional_param}-{another_optional_param}"
note: This is an unsafe fix and may change runtime behavior

View file

@ -0,0 +1,303 @@
---
source: crates/ruff_linter/src/rules/fastapi/mod.rs
---
FAST002 [*] FastAPI dependency without `Annotated`
--> FAST002_2.py:14:5
|
12 | async def test_ellipsis_query(
13 | # This should become: param: Annotated[str, Query(description="Test param")]
14 | param: str = Query(..., description="Test param"),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
15 | ) -> str:
16 | return param
|
help: Replace with `typing_extensions.Annotated`
1 | """Test FAST002 ellipsis handling."""
2 |
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
4 + from typing_extensions import Annotated
5 |
6 | app = FastAPI()
7 |
--------------------------------------------------------------------------------
12 | @app.get("/test1")
13 | async def test_ellipsis_query(
14 | # This should become: param: Annotated[str, Query(description="Test param")]
- param: str = Query(..., description="Test param"),
15 + param: Annotated[str, Query(description="Test param")],
16 | ) -> str:
17 | return param
18 |
note: This is an unsafe fix and may change runtime behavior
FAST002 [*] FastAPI dependency without `Annotated`
--> FAST002_2.py:22:5
|
20 | async def test_ellipsis_header(
21 | # This should become: auth: Annotated[str, Header(description="Auth header")]
22 | auth: str = Header(..., description="Auth header"),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
23 | ) -> str:
24 | return auth
|
help: Replace with `typing_extensions.Annotated`
1 | """Test FAST002 ellipsis handling."""
2 |
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
4 + from typing_extensions import Annotated
5 |
6 | app = FastAPI()
7 |
--------------------------------------------------------------------------------
20 | @app.get("/test2")
21 | async def test_ellipsis_header(
22 | # This should become: auth: Annotated[str, Header(description="Auth header")]
- auth: str = Header(..., description="Auth header"),
23 + auth: Annotated[str, Header(description="Auth header")],
24 | ) -> str:
25 | return auth
26 |
note: This is an unsafe fix and may change runtime behavior
FAST002 [*] FastAPI dependency without `Annotated`
--> FAST002_2.py:30:5
|
28 | async def test_ellipsis_body(
29 | # This should become: data: Annotated[dict, Body(description="Request body")]
30 | data: dict = Body(..., description="Request body"),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
31 | ) -> dict:
32 | return data
|
help: Replace with `typing_extensions.Annotated`
1 | """Test FAST002 ellipsis handling."""
2 |
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
4 + from typing_extensions import Annotated
5 |
6 | app = FastAPI()
7 |
--------------------------------------------------------------------------------
28 | @app.post("/test3")
29 | async def test_ellipsis_body(
30 | # This should become: data: Annotated[dict, Body(description="Request body")]
- data: dict = Body(..., description="Request body"),
31 + data: Annotated[dict, Body(description="Request body")],
32 | ) -> dict:
33 | return data
34 |
note: This is an unsafe fix and may change runtime behavior
FAST002 [*] FastAPI dependency without `Annotated`
--> FAST002_2.py:38:5
|
36 | async def test_ellipsis_cookie(
37 | # This should become: session: Annotated[str, Cookie(description="Session ID")]
38 | session: str = Cookie(..., description="Session ID"),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
39 | ) -> str:
40 | return session
|
help: Replace with `typing_extensions.Annotated`
1 | """Test FAST002 ellipsis handling."""
2 |
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
4 + from typing_extensions import Annotated
5 |
6 | app = FastAPI()
7 |
--------------------------------------------------------------------------------
36 | @app.get("/test4")
37 | async def test_ellipsis_cookie(
38 | # This should become: session: Annotated[str, Cookie(description="Session ID")]
- session: str = Cookie(..., description="Session ID"),
39 + session: Annotated[str, Cookie(description="Session ID")],
40 | ) -> str:
41 | return session
42 |
note: This is an unsafe fix and may change runtime behavior
FAST002 [*] FastAPI dependency without `Annotated`
--> FAST002_2.py:46:5
|
44 | async def test_simple_ellipsis(
45 | # This should become: id: Annotated[str, Query()]
46 | id: str = Query(...),
| ^^^^^^^^^^^^^^^^^^^^
47 | ) -> str:
48 | return id
|
help: Replace with `typing_extensions.Annotated`
1 | """Test FAST002 ellipsis handling."""
2 |
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
4 + from typing_extensions import Annotated
5 |
6 | app = FastAPI()
7 |
--------------------------------------------------------------------------------
44 | @app.get("/test5")
45 | async def test_simple_ellipsis(
46 | # This should become: id: Annotated[str, Query()]
- id: str = Query(...),
47 + id: Annotated[str, Query()],
48 | ) -> str:
49 | return id
50 |
note: This is an unsafe fix and may change runtime behavior
FAST002 [*] FastAPI dependency without `Annotated`
--> FAST002_2.py:54:5
|
52 | async def test_multiple_kwargs_with_ellipsis(
53 | # This should become: param: Annotated[str, Query(description="Test", min_length=1, max_length=10)]
54 | param: str = Query(..., description="Test", min_length=1, max_length=10),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
55 | ) -> str:
56 | return param
|
help: Replace with `typing_extensions.Annotated`
1 | """Test FAST002 ellipsis handling."""
2 |
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
4 + from typing_extensions import Annotated
5 |
6 | app = FastAPI()
7 |
--------------------------------------------------------------------------------
52 | @app.get("/test6")
53 | async def test_multiple_kwargs_with_ellipsis(
54 | # This should become: param: Annotated[str, Query(description="Test", min_length=1, max_length=10)]
- param: str = Query(..., description="Test", min_length=1, max_length=10),
55 + param: Annotated[str, Query(description="Test", min_length=1, max_length=10)],
56 | ) -> str:
57 | return param
58 |
note: This is an unsafe fix and may change runtime behavior
FAST002 [*] FastAPI dependency without `Annotated`
--> FAST002_2.py:65:5
|
63 | async def test_with_default_value(
64 | # This should become: param: Annotated[str, Query(description="Test")] = "default"
65 | param: str = Query("default", description="Test"),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
66 | ) -> str:
67 | return param
|
help: Replace with `typing_extensions.Annotated`
1 | """Test FAST002 ellipsis handling."""
2 |
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
4 + from typing_extensions import Annotated
5 |
6 | app = FastAPI()
7 |
--------------------------------------------------------------------------------
63 | @app.get("/test7")
64 | async def test_with_default_value(
65 | # This should become: param: Annotated[str, Query(description="Test")] = "default"
- param: str = Query("default", description="Test"),
66 + param: Annotated[str, Query(description="Test")] = "default",
67 | ) -> str:
68 | return param
69 |
note: This is an unsafe fix and may change runtime behavior
FAST002 [*] FastAPI dependency without `Annotated`
--> FAST002_2.py:73:5
|
71 | async def test_with_default_none(
72 | # This should become: param: Annotated[str | None, Query(description="Test")] = None
73 | param: str | None = Query(None, description="Test"),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
74 | ) -> str:
75 | return param or "empty"
|
help: Replace with `typing_extensions.Annotated`
1 | """Test FAST002 ellipsis handling."""
2 |
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
4 + from typing_extensions import Annotated
5 |
6 | app = FastAPI()
7 |
--------------------------------------------------------------------------------
71 | @app.get("/test8")
72 | async def test_with_default_none(
73 | # This should become: param: Annotated[str | None, Query(description="Test")] = None
- param: str | None = Query(None, description="Test"),
74 + param: Annotated[str | None, Query(description="Test")] = None,
75 | ) -> str:
76 | return param or "empty"
77 |
note: This is an unsafe fix and may change runtime behavior
FAST002 [*] FastAPI dependency without `Annotated`
--> FAST002_2.py:81:5
|
79 | async def test_mixed_parameters(
80 | # First param should be fixed with default preserved
81 | optional_param: str = Query("default", description="Optional"),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
82 | # Second param should not be fixed because of the preceding default
83 | required_param: str = Query(..., description="Required"),
|
help: Replace with `typing_extensions.Annotated`
1 | """Test FAST002 ellipsis handling."""
2 |
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
4 + from typing_extensions import Annotated
5 |
6 | app = FastAPI()
7 |
--------------------------------------------------------------------------------
79 | @app.get("/test9")
80 | async def test_mixed_parameters(
81 | # First param should be fixed with default preserved
- optional_param: str = Query("default", description="Optional"),
82 + optional_param: Annotated[str, Query(description="Optional")] = "default",
83 | # Second param should not be fixed because of the preceding default
84 | required_param: str = Query(..., description="Required"),
85 | # Third param should be fixed with default preserved
note: This is an unsafe fix and may change runtime behavior
FAST002 FastAPI dependency without `Annotated`
--> FAST002_2.py:83:5
|
81 | optional_param: str = Query("default", description="Optional"),
82 | # Second param should not be fixed because of the preceding default
83 | required_param: str = Query(..., description="Required"),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
84 | # Third param should be fixed with default preserved
85 | another_optional_param: int = Query(42, description="Another optional"),
|
help: Replace with `typing_extensions.Annotated`
FAST002 [*] FastAPI dependency without `Annotated`
--> FAST002_2.py:85:5
|
83 | required_param: str = Query(..., description="Required"),
84 | # Third param should be fixed with default preserved
85 | another_optional_param: int = Query(42, description="Another optional"),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
86 | ) -> str:
87 | return f"{required_param}-{optional_param}-{another_optional_param}"
|
help: Replace with `typing_extensions.Annotated`
1 | """Test FAST002 ellipsis handling."""
2 |
3 | from fastapi import Body, Cookie, FastAPI, Header, Query
4 + from typing_extensions import Annotated
5 |
6 | app = FastAPI()
7 |
--------------------------------------------------------------------------------
83 | # Second param should not be fixed because of the preceding default
84 | required_param: str = Query(..., description="Required"),
85 | # Third param should be fixed with default preserved
- another_optional_param: int = Query(42, description="Another optional"),
86 + another_optional_param: Annotated[int, Query(description="Another optional")] = 42,
87 | ) -> str:
88 | return f"{required_param}-{optional_param}-{another_optional_param}"
note: This is an unsafe fix and may change runtime behavior