diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF029.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF029.py index 3993a506538cc..47651079fa321 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/RUF029.py +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF029.py @@ -78,3 +78,13 @@ async def test(): async def test() -> str: vals = [str(val) for val in await async_func(1)] return ",".join(vals) + + +from fastapi import FastAPI + +app = FastAPI() + + +@app.post("/count") +async def fastapi_route(): # Ok: FastApi routes can be async without actually using await + return 1 diff --git a/crates/ruff_linter/src/rules/ruff/rules/unused_async.rs b/crates/ruff_linter/src/rules/ruff/rules/unused_async.rs index f0bc106b98ebc..b3b78c2755447 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/unused_async.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/unused_async.rs @@ -4,8 +4,10 @@ use ruff_python_ast::identifier::Identifier; use ruff_python_ast::visitor::source_order; use ruff_python_ast::{self as ast, AnyNodeRef, Expr, Stmt}; use ruff_python_semantic::analyze::function_type::is_stub; +use ruff_python_semantic::Modules; use crate::checkers::ast::Checker; +use crate::rules::fastapi::rules::is_fastapi_route; /// ## What it does /// Checks for functions declared `async` that do not await or otherwise use features requiring the @@ -173,6 +175,12 @@ pub(crate) fn unused_async( return; } + if checker.semantic().seen_module(Modules::FASTAPI) + && is_fastapi_route(function_def, checker.semantic()) + { + return; + } + let found_await_or_async = { let mut visitor = AsyncExprVisitor::default(); source_order::walk_body(&mut visitor, body);