Skip to content

Commit e2c7b1e

Browse files
authored
[TRIO] Add TRIO109 rule (#8534)
## Summary Adds TRIO109 from the [flake8-trio plugin](https://github.com/Zac-HD/flake8-trio). Relates to: #8451
1 parent 621e98f commit e2c7b1e

File tree

9 files changed

+98
-0
lines changed

9 files changed

+98
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
async def func():
2+
...
3+
4+
5+
async def func(timeout):
6+
...
7+
8+
9+
async def func(timeout=10):
10+
...

crates/ruff_linter/src/checkers/ast/analyze/statement.rs

+3
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
356356
flake8_builtins::rules::builtin_variable_shadowing(checker, name, name.range());
357357
}
358358
}
359+
if checker.enabled(Rule::TrioAsyncFunctionWithTimeout) {
360+
flake8_trio::rules::async_function_with_timeout(checker, function_def);
361+
}
359362
#[cfg(feature = "unreachable-code")]
360363
if checker.enabled(Rule::UnreachableCode) {
361364
checker

crates/ruff_linter/src/codes.rs

+1
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
293293
// flake8-trio
294294
(Flake8Trio, "100") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioTimeoutWithoutAwait),
295295
(Flake8Trio, "105") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioSyncCall),
296+
(Flake8Trio, "109") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioAsyncFunctionWithTimeout),
296297
(Flake8Trio, "110") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioUnneededSleep),
297298
(Flake8Trio, "115") => (RuleGroup::Preview, rules::flake8_trio::rules::TrioZeroSleepCall),
298299

crates/ruff_linter/src/rules/flake8_trio/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ mod tests {
1616

1717
#[test_case(Rule::TrioTimeoutWithoutAwait, Path::new("TRIO100.py"))]
1818
#[test_case(Rule::TrioSyncCall, Path::new("TRIO105.py"))]
19+
#[test_case(Rule::TrioAsyncFunctionWithTimeout, Path::new("TRIO109.py"))]
1920
#[test_case(Rule::TrioUnneededSleep, Path::new("TRIO110.py"))]
2021
#[test_case(Rule::TrioZeroSleepCall, Path::new("TRIO115.py"))]
2122
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
use ruff_diagnostics::{Diagnostic, Violation};
2+
use ruff_macros::{derive_message_formats, violation};
3+
use ruff_python_ast as ast;
4+
use ruff_text_size::Ranged;
5+
6+
use crate::checkers::ast::Checker;
7+
8+
/// ## What it does
9+
/// Checks for `async` functions with a `timeout` argument.
10+
///
11+
/// ## Why is this bad?
12+
/// Rather than implementing asynchronous timeout behavior manually, prefer
13+
/// trio's built-in timeout functionality, available as `trio.fail_after`,
14+
/// `trio.move_on_after`, `trio.fail_at`, and `trio.move_on_at`.
15+
///
16+
/// ## Example
17+
/// ```python
18+
/// async def func():
19+
/// await long_running_task(timeout=2)
20+
/// ```
21+
///
22+
/// Use instead:
23+
/// ```python
24+
/// async def func():
25+
/// with trio.fail_after(2):
26+
/// await long_running_task()
27+
/// ```
28+
#[violation]
29+
pub struct TrioAsyncFunctionWithTimeout;
30+
31+
impl Violation for TrioAsyncFunctionWithTimeout {
32+
#[derive_message_formats]
33+
fn message(&self) -> String {
34+
format!("Prefer `trio.fail_after` and `trio.move_on_after` over manual `async` timeout behavior")
35+
}
36+
}
37+
38+
/// TRIO109
39+
pub(crate) fn async_function_with_timeout(
40+
checker: &mut Checker,
41+
function_def: &ast::StmtFunctionDef,
42+
) {
43+
if !function_def.is_async {
44+
return;
45+
}
46+
let Some(timeout) = function_def.parameters.find("timeout") else {
47+
return;
48+
};
49+
checker.diagnostics.push(Diagnostic::new(
50+
TrioAsyncFunctionWithTimeout,
51+
timeout.range(),
52+
));
53+
}

crates/ruff_linter/src/rules/flake8_trio/rules/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
pub(crate) use async_function_with_timeout::*;
12
pub(crate) use sync_call::*;
23
pub(crate) use timeout_without_await::*;
34
pub(crate) use unneeded_sleep::*;
45
pub(crate) use zero_sleep_call::*;
56

7+
mod async_function_with_timeout;
68
mod sync_call;
79
mod timeout_without_await;
810
mod unneeded_sleep;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
source: crates/ruff_linter/src/rules/flake8_trio/mod.rs
3+
---
4+
TRIO109.py:5:16: TRIO109 Prefer `trio.fail_after` and `trio.move_on_after` over manual `async` timeout behavior
5+
|
6+
5 | async def func(timeout):
7+
| ^^^^^^^ TRIO109
8+
6 | ...
9+
|
10+
11+
TRIO109.py:9:16: TRIO109 Prefer `trio.fail_after` and `trio.move_on_after` over manual `async` timeout behavior
12+
|
13+
9 | async def func(timeout=10):
14+
| ^^^^^^^^^^ TRIO109
15+
10 | ...
16+
|
17+
18+

crates/ruff_python_ast/src/nodes.rs

+9
Original file line numberDiff line numberDiff line change
@@ -2281,6 +2281,15 @@ pub struct Parameters {
22812281
}
22822282

22832283
impl Parameters {
2284+
/// Returns the [`ParameterWithDefault`] with the given name, or `None` if no such [`ParameterWithDefault`] exists.
2285+
pub fn find(&self, name: &str) -> Option<&ParameterWithDefault> {
2286+
self.posonlyargs
2287+
.iter()
2288+
.chain(&self.args)
2289+
.chain(&self.kwonlyargs)
2290+
.find(|arg| arg.parameter.name.as_str() == name)
2291+
}
2292+
22842293
/// Returns `true` if a parameter with the given name included in this [`Parameters`].
22852294
pub fn includes(&self, name: &str) -> bool {
22862295
if self

ruff.schema.json

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)