From 8440f3ea9f8c6823ab88d8ccaaddca07413150e6 Mon Sep 17 00:00:00 2001 From: Harutaka Kawamura Date: Mon, 23 Dec 2024 19:02:42 +0900 Subject: [PATCH] [`fastapi`] Update `FAST002` to check keyword-only arguments (#15119) ## Summary Close #15117. Update `FAST002` to check keyword-only arguments. ## Test Plan New test case --- .../test/fixtures/fastapi/FAST002.py | 3 ++ .../rules/fastapi_non_annotated_dependency.rs | 10 ++++- ...i-non-annotated-dependency_FAST002.py.snap | 42 +++++++++++++++---- ...-annotated-dependency_FAST002.py_py38.snap | 42 +++++++++++++++---- 4 files changed, 81 insertions(+), 16 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/fastapi/FAST002.py b/crates/ruff_linter/resources/test/fixtures/fastapi/FAST002.py index 4f1d19463b..78f05d0a56 100644 --- a/crates/ruff_linter/resources/test/fixtures/fastapi/FAST002.py +++ b/crates/ruff_linter/resources/test/fixtures/fastapi/FAST002.py @@ -57,6 +57,9 @@ def get_users( pass +@app.get("/items/{item_id}") +async def read_items(*, item_id: int = Path(title="The ID of the item to get"), q: str): + pass # Non fixable errors diff --git a/crates/ruff_linter/src/rules/fastapi/rules/fastapi_non_annotated_dependency.rs b/crates/ruff_linter/src/rules/fastapi/rules/fastapi_non_annotated_dependency.rs index 14148c56ac..42f1ddc99e 100644 --- a/crates/ruff_linter/src/rules/fastapi/rules/fastapi_non_annotated_dependency.rs +++ b/crates/ruff_linter/src/rules/fastapi/rules/fastapi_non_annotated_dependency.rs @@ -99,9 +99,15 @@ pub(crate) fn fastapi_non_annotated_dependency( let mut updatable_count = 0; let mut has_non_updatable_default = false; - let total_params = function_def.parameters.args.len(); + let total_params = + function_def.parameters.args.len() + function_def.parameters.kwonlyargs.len(); - for parameter in &function_def.parameters.args { + for parameter in function_def + .parameters + .args + .iter() + .chain(&function_def.parameters.kwonlyargs) + { let needs_update = matches!( (¶meter.parameter.annotation, ¶meter.default), (Some(_annotation), Some(default)) if is_fastapi_dependency(checker, default) diff --git a/crates/ruff_linter/src/rules/fastapi/snapshots/ruff_linter__rules__fastapi__tests__fast-api-non-annotated-dependency_FAST002.py.snap b/crates/ruff_linter/src/rules/fastapi/snapshots/ruff_linter__rules__fastapi__tests__fast-api-non-annotated-dependency_FAST002.py.snap index e05eaf7195..58ec83f578 100644 --- a/crates/ruff_linter/src/rules/fastapi/snapshots/ruff_linter__rules__fastapi__tests__fast-api-non-annotated-dependency_FAST002.py.snap +++ b/crates/ruff_linter/src/rules/fastapi/snapshots/ruff_linter__rules__fastapi__tests__fast-api-non-annotated-dependency_FAST002.py.snap @@ -1,5 +1,6 @@ --- source: crates/ruff_linter/src/rules/fastapi/mod.rs +snapshot_kind: text --- FAST002.py:24:5: FAST002 [*] FastAPI dependency without `Annotated` | @@ -320,13 +321,40 @@ FAST002.py:53:5: FAST002 [*] FastAPI dependency without `Annotated` 55 56 | limit: int = 10, 56 57 | ): -FAST002.py:67:5: FAST002 FastAPI dependency without `Annotated` +FAST002.py:61:25: FAST002 [*] FastAPI dependency without `Annotated` | -65 | skip: int = 0, -66 | limit: int = 10, -67 | current_user: User = Depends(get_current_user), - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FAST002 -68 | ): -69 | pass +60 | @app.get("/items/{item_id}") +61 | async def read_items(*, item_id: int = Path(title="The ID of the item to get"), q: str): + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FAST002 +62 | pass + | + = help: Replace with `typing.Annotated` + +ℹ Unsafe fix +12 12 | Security, +13 13 | ) +14 14 | from pydantic import BaseModel + 15 |+from typing import Annotated +15 16 | +16 17 | app = FastAPI() +17 18 | router = APIRouter() +-------------------------------------------------------------------------------- +58 59 | +59 60 | +60 61 | @app.get("/items/{item_id}") +61 |-async def read_items(*, item_id: int = Path(title="The ID of the item to get"), q: str): + 62 |+async def read_items(*, item_id: Annotated[int, Path(title="The ID of the item to get")], q: str): +62 63 | pass +63 64 | +64 65 | # Non fixable errors + +FAST002.py:70:5: FAST002 FastAPI dependency without `Annotated` + | +68 | skip: int = 0, +69 | limit: int = 10, +70 | current_user: User = Depends(get_current_user), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FAST002 +71 | ): +72 | pass | = help: Replace with `typing.Annotated` diff --git a/crates/ruff_linter/src/rules/fastapi/snapshots/ruff_linter__rules__fastapi__tests__fast-api-non-annotated-dependency_FAST002.py_py38.snap b/crates/ruff_linter/src/rules/fastapi/snapshots/ruff_linter__rules__fastapi__tests__fast-api-non-annotated-dependency_FAST002.py_py38.snap index 0d6342b9ad..1348dcf8b2 100644 --- a/crates/ruff_linter/src/rules/fastapi/snapshots/ruff_linter__rules__fastapi__tests__fast-api-non-annotated-dependency_FAST002.py_py38.snap +++ b/crates/ruff_linter/src/rules/fastapi/snapshots/ruff_linter__rules__fastapi__tests__fast-api-non-annotated-dependency_FAST002.py_py38.snap @@ -1,5 +1,6 @@ --- source: crates/ruff_linter/src/rules/fastapi/mod.rs +snapshot_kind: text --- FAST002.py:24:5: FAST002 [*] FastAPI dependency without `Annotated` | @@ -320,13 +321,40 @@ FAST002.py:53:5: FAST002 [*] FastAPI dependency without `Annotated` 55 56 | limit: int = 10, 56 57 | ): -FAST002.py:67:5: FAST002 FastAPI dependency without `Annotated` +FAST002.py:61:25: FAST002 [*] FastAPI dependency without `Annotated` | -65 | skip: int = 0, -66 | limit: int = 10, -67 | current_user: User = Depends(get_current_user), - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FAST002 -68 | ): -69 | pass +60 | @app.get("/items/{item_id}") +61 | async def read_items(*, item_id: int = Path(title="The ID of the item to get"), q: str): + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FAST002 +62 | pass + | + = help: Replace with `typing_extensions.Annotated` + +ℹ Unsafe fix +12 12 | Security, +13 13 | ) +14 14 | from pydantic import BaseModel + 15 |+from typing_extensions import Annotated +15 16 | +16 17 | app = FastAPI() +17 18 | router = APIRouter() +-------------------------------------------------------------------------------- +58 59 | +59 60 | +60 61 | @app.get("/items/{item_id}") +61 |-async def read_items(*, item_id: int = Path(title="The ID of the item to get"), q: str): + 62 |+async def read_items(*, item_id: Annotated[int, Path(title="The ID of the item to get")], q: str): +62 63 | pass +63 64 | +64 65 | # Non fixable errors + +FAST002.py:70:5: FAST002 FastAPI dependency without `Annotated` + | +68 | skip: int = 0, +69 | limit: int = 10, +70 | current_user: User = Depends(get_current_user), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FAST002 +71 | ): +72 | pass | = help: Replace with `typing_extensions.Annotated`