mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-01 14:21:53 +00:00
[flake8-async
] Update ASYNC110
to match upstream (#12261)
## Summary Update the name of `ASYNC110` to match [upstream](https://flake8-async.readthedocs.io/en/latest/rules.html). Also update to the functionality to match upstream by adding support for `asyncio` and `anyio` (gated behind preview). Part of https://github.com/astral-sh/ruff/issues/12039. ## Test Plan Added tests for `asyncio` and `anyio`
This commit is contained in:
parent
88abc6aed8
commit
855d62cdde
10 changed files with 210 additions and 87 deletions
|
@ -1,4 +1,6 @@
|
||||||
import trio
|
import trio
|
||||||
|
import anyio
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
|
||||||
async def func():
|
async def func():
|
||||||
|
@ -14,3 +16,33 @@ async def func():
|
||||||
async def func():
|
async def func():
|
||||||
while True:
|
while True:
|
||||||
trio.sleep(10)
|
trio.sleep(10)
|
||||||
|
|
||||||
|
|
||||||
|
async def func():
|
||||||
|
while True:
|
||||||
|
await anyio.sleep(10)
|
||||||
|
|
||||||
|
|
||||||
|
async def func():
|
||||||
|
while True:
|
||||||
|
await anyio.sleep_until(10)
|
||||||
|
|
||||||
|
|
||||||
|
async def func():
|
||||||
|
while True:
|
||||||
|
anyio.sleep(10)
|
||||||
|
|
||||||
|
|
||||||
|
async def func():
|
||||||
|
while True:
|
||||||
|
await asyncio.sleep(10)
|
||||||
|
|
||||||
|
|
||||||
|
async def func():
|
||||||
|
while True:
|
||||||
|
await asyncio.sleep_until(10)
|
||||||
|
|
||||||
|
|
||||||
|
async def func():
|
||||||
|
while True:
|
||||||
|
asyncio.sleep(10)
|
||||||
|
|
|
@ -1330,8 +1330,8 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||||
if checker.enabled(Rule::TryExceptInLoop) {
|
if checker.enabled(Rule::TryExceptInLoop) {
|
||||||
perflint::rules::try_except_in_loop(checker, body);
|
perflint::rules::try_except_in_loop(checker, body);
|
||||||
}
|
}
|
||||||
if checker.enabled(Rule::TrioUnneededSleep) {
|
if checker.enabled(Rule::AsyncBusyWait) {
|
||||||
flake8_async::rules::unneeded_sleep(checker, while_stmt);
|
flake8_async::rules::async_busy_wait(checker, while_stmt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Stmt::For(
|
Stmt::For(
|
||||||
|
|
|
@ -296,7 +296,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||||
(Flake8Async, "100") => (RuleGroup::Stable, rules::flake8_async::rules::CancelScopeNoCheckpoint),
|
(Flake8Async, "100") => (RuleGroup::Stable, rules::flake8_async::rules::CancelScopeNoCheckpoint),
|
||||||
(Flake8Async, "105") => (RuleGroup::Stable, rules::flake8_async::rules::TrioSyncCall),
|
(Flake8Async, "105") => (RuleGroup::Stable, rules::flake8_async::rules::TrioSyncCall),
|
||||||
(Flake8Async, "109") => (RuleGroup::Stable, rules::flake8_async::rules::AsyncFunctionWithTimeout),
|
(Flake8Async, "109") => (RuleGroup::Stable, rules::flake8_async::rules::AsyncFunctionWithTimeout),
|
||||||
(Flake8Async, "110") => (RuleGroup::Stable, rules::flake8_async::rules::TrioUnneededSleep),
|
(Flake8Async, "110") => (RuleGroup::Stable, rules::flake8_async::rules::AsyncBusyWait),
|
||||||
(Flake8Async, "115") => (RuleGroup::Stable, rules::flake8_async::rules::TrioZeroSleepCall),
|
(Flake8Async, "115") => (RuleGroup::Stable, rules::flake8_async::rules::TrioZeroSleepCall),
|
||||||
(Flake8Async, "116") => (RuleGroup::Preview, rules::flake8_async::rules::SleepForeverCall),
|
(Flake8Async, "116") => (RuleGroup::Preview, rules::flake8_async::rules::SleepForeverCall),
|
||||||
(Flake8Async, "210") => (RuleGroup::Stable, rules::flake8_async::rules::BlockingHttpCallInAsyncFunction),
|
(Flake8Async, "210") => (RuleGroup::Stable, rules::flake8_async::rules::BlockingHttpCallInAsyncFunction),
|
||||||
|
|
|
@ -10,6 +10,27 @@ pub(super) enum AsyncModule {
|
||||||
Trio,
|
Trio,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AsyncModule {
|
||||||
|
pub(super) fn try_from(qualified_name: &QualifiedName<'_>) -> Option<Self> {
|
||||||
|
match qualified_name.segments() {
|
||||||
|
["asyncio", ..] => Some(Self::AsyncIo),
|
||||||
|
["anyio", ..] => Some(Self::AnyIo),
|
||||||
|
["trio", ..] => Some(Self::Trio),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for AsyncModule {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
AsyncModule::AnyIo => write!(f, "asyncio"),
|
||||||
|
AsyncModule::AsyncIo => write!(f, "anyio"),
|
||||||
|
AsyncModule::Trio => write!(f, "trio"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
pub(super) enum MethodName {
|
pub(super) enum MethodName {
|
||||||
AsyncIoTimeout,
|
AsyncIoTimeout,
|
||||||
|
|
|
@ -19,7 +19,7 @@ mod tests {
|
||||||
#[test_case(Rule::TrioSyncCall, Path::new("ASYNC105.py"))]
|
#[test_case(Rule::TrioSyncCall, Path::new("ASYNC105.py"))]
|
||||||
#[test_case(Rule::AsyncFunctionWithTimeout, Path::new("ASYNC109_0.py"))]
|
#[test_case(Rule::AsyncFunctionWithTimeout, Path::new("ASYNC109_0.py"))]
|
||||||
#[test_case(Rule::AsyncFunctionWithTimeout, Path::new("ASYNC109_1.py"))]
|
#[test_case(Rule::AsyncFunctionWithTimeout, Path::new("ASYNC109_1.py"))]
|
||||||
#[test_case(Rule::TrioUnneededSleep, Path::new("ASYNC110.py"))]
|
#[test_case(Rule::AsyncBusyWait, Path::new("ASYNC110.py"))]
|
||||||
#[test_case(Rule::TrioZeroSleepCall, Path::new("ASYNC115.py"))]
|
#[test_case(Rule::TrioZeroSleepCall, Path::new("ASYNC115.py"))]
|
||||||
#[test_case(Rule::SleepForeverCall, Path::new("ASYNC116.py"))]
|
#[test_case(Rule::SleepForeverCall, Path::new("ASYNC116.py"))]
|
||||||
#[test_case(Rule::BlockingHttpCallInAsyncFunction, Path::new("ASYNC210.py"))]
|
#[test_case(Rule::BlockingHttpCallInAsyncFunction, Path::new("ASYNC210.py"))]
|
||||||
|
@ -41,6 +41,7 @@ mod tests {
|
||||||
#[test_case(Rule::CancelScopeNoCheckpoint, Path::new("ASYNC100.py"))]
|
#[test_case(Rule::CancelScopeNoCheckpoint, Path::new("ASYNC100.py"))]
|
||||||
#[test_case(Rule::AsyncFunctionWithTimeout, Path::new("ASYNC109_0.py"))]
|
#[test_case(Rule::AsyncFunctionWithTimeout, Path::new("ASYNC109_0.py"))]
|
||||||
#[test_case(Rule::AsyncFunctionWithTimeout, Path::new("ASYNC109_1.py"))]
|
#[test_case(Rule::AsyncFunctionWithTimeout, Path::new("ASYNC109_1.py"))]
|
||||||
|
#[test_case(Rule::AsyncBusyWait, Path::new("ASYNC110.py"))]
|
||||||
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||||
let snapshot = format!(
|
let snapshot = format!(
|
||||||
"preview__{}_{}",
|
"preview__{}_{}",
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
use ruff_python_ast::{self as ast, Expr, Stmt};
|
||||||
|
use ruff_text_size::Ranged;
|
||||||
|
|
||||||
|
use crate::checkers::ast::Checker;
|
||||||
|
use crate::rules::flake8_async::helpers::AsyncModule;
|
||||||
|
use crate::settings::types::PreviewMode;
|
||||||
|
|
||||||
|
/// ## What it does
|
||||||
|
/// Checks for the use of an async sleep function in a `while` loop.
|
||||||
|
///
|
||||||
|
/// ## Why is this bad?
|
||||||
|
/// Instead of sleeping in a `while` loop, and waiting for a condition
|
||||||
|
/// to become true, it's preferable to `await` on an `Event` object such
|
||||||
|
/// as: `asyncio.Event`, `trio.Event`, or `anyio.Event`.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```python
|
||||||
|
/// DONE = False
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// async def func():
|
||||||
|
/// while not DONE:
|
||||||
|
/// await asyncio.sleep(1)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Use instead:
|
||||||
|
/// ```python
|
||||||
|
/// DONE = asyncio.Event()
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// async def func():
|
||||||
|
/// await DONE.wait()
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [`asyncio` events]: https://docs.python.org/3/library/asyncio-sync.html#asyncio.Event
|
||||||
|
/// [`anyio` events]: https://trio.readthedocs.io/en/latest/reference-core.html#trio.Event
|
||||||
|
/// [`trio` events]: https://anyio.readthedocs.io/en/latest/api.html#anyio.Event
|
||||||
|
#[violation]
|
||||||
|
pub struct AsyncBusyWait {
|
||||||
|
module: AsyncModule,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Violation for AsyncBusyWait {
|
||||||
|
#[derive_message_formats]
|
||||||
|
fn message(&self) -> String {
|
||||||
|
let Self { module } = self;
|
||||||
|
format!("Use `{module}.Event` instead of awaiting `{module}.sleep` in a `while` loop")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ASYNC110
|
||||||
|
pub(crate) fn async_busy_wait(checker: &mut Checker, while_stmt: &ast::StmtWhile) {
|
||||||
|
// The body should be a single `await` call.
|
||||||
|
let [stmt] = while_stmt.body.as_slice() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Stmt::Expr(ast::StmtExpr { value, .. }) = stmt else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Expr::Await(ast::ExprAwait { value, .. }) = value.as_ref() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Expr::Call(ast::ExprCall { func, .. }) = value.as_ref() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(qualified_name) = checker.semantic().resolve_qualified_name(func.as_ref()) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if matches!(checker.settings.preview, PreviewMode::Disabled) {
|
||||||
|
if matches!(qualified_name.segments(), ["trio", "sleep" | "sleep_until"]) {
|
||||||
|
checker.diagnostics.push(Diagnostic::new(
|
||||||
|
AsyncBusyWait {
|
||||||
|
module: AsyncModule::Trio,
|
||||||
|
},
|
||||||
|
while_stmt.range(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if matches!(
|
||||||
|
qualified_name.segments(),
|
||||||
|
["trio" | "anyio", "sleep" | "sleep_until"] | ["asyncio", "sleep"]
|
||||||
|
) {
|
||||||
|
checker.diagnostics.push(Diagnostic::new(
|
||||||
|
AsyncBusyWait {
|
||||||
|
module: AsyncModule::try_from(&qualified_name).unwrap(),
|
||||||
|
},
|
||||||
|
while_stmt.range(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
pub(crate) use async_busy_wait::*;
|
||||||
pub(crate) use async_function_with_timeout::*;
|
pub(crate) use async_function_with_timeout::*;
|
||||||
pub(crate) use blocking_http_call::*;
|
pub(crate) use blocking_http_call::*;
|
||||||
pub(crate) use blocking_open_call::*;
|
pub(crate) use blocking_open_call::*;
|
||||||
|
@ -6,9 +7,9 @@ pub(crate) use blocking_sleep::*;
|
||||||
pub(crate) use cancel_scope_no_checkpoint::*;
|
pub(crate) use cancel_scope_no_checkpoint::*;
|
||||||
pub(crate) use sleep_forever_call::*;
|
pub(crate) use sleep_forever_call::*;
|
||||||
pub(crate) use sync_call::*;
|
pub(crate) use sync_call::*;
|
||||||
pub(crate) use unneeded_sleep::*;
|
|
||||||
pub(crate) use zero_sleep_call::*;
|
pub(crate) use zero_sleep_call::*;
|
||||||
|
|
||||||
|
mod async_busy_wait;
|
||||||
mod async_function_with_timeout;
|
mod async_function_with_timeout;
|
||||||
mod blocking_http_call;
|
mod blocking_http_call;
|
||||||
mod blocking_open_call;
|
mod blocking_open_call;
|
||||||
|
@ -17,5 +18,4 @@ mod blocking_sleep;
|
||||||
mod cancel_scope_no_checkpoint;
|
mod cancel_scope_no_checkpoint;
|
||||||
mod sleep_forever_call;
|
mod sleep_forever_call;
|
||||||
mod sync_call;
|
mod sync_call;
|
||||||
mod unneeded_sleep;
|
|
||||||
mod zero_sleep_call;
|
mod zero_sleep_call;
|
||||||
|
|
|
@ -1,73 +0,0 @@
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
|
||||||
use ruff_python_ast::{self as ast, Expr, Stmt};
|
|
||||||
use ruff_python_semantic::Modules;
|
|
||||||
use ruff_text_size::Ranged;
|
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
|
||||||
|
|
||||||
/// ## What it does
|
|
||||||
/// Checks for the use of `trio.sleep` in a `while` loop.
|
|
||||||
///
|
|
||||||
/// ## Why is this bad?
|
|
||||||
/// Instead of sleeping in a `while` loop, and waiting for a condition
|
|
||||||
/// to become true, it's preferable to `wait()` on a `trio.Event`.
|
|
||||||
///
|
|
||||||
/// ## Example
|
|
||||||
/// ```python
|
|
||||||
/// DONE = False
|
|
||||||
///
|
|
||||||
///
|
|
||||||
/// async def func():
|
|
||||||
/// while not DONE:
|
|
||||||
/// await trio.sleep(1)
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Use instead:
|
|
||||||
/// ```python
|
|
||||||
/// DONE = trio.Event()
|
|
||||||
///
|
|
||||||
///
|
|
||||||
/// async def func():
|
|
||||||
/// await DONE.wait()
|
|
||||||
/// ```
|
|
||||||
#[violation]
|
|
||||||
pub struct TrioUnneededSleep;
|
|
||||||
|
|
||||||
impl Violation for TrioUnneededSleep {
|
|
||||||
#[derive_message_formats]
|
|
||||||
fn message(&self) -> String {
|
|
||||||
format!("Use `trio.Event` instead of awaiting `trio.sleep` in a `while` loop")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ASYNC110
|
|
||||||
pub(crate) fn unneeded_sleep(checker: &mut Checker, while_stmt: &ast::StmtWhile) {
|
|
||||||
if !checker.semantic().seen_module(Modules::TRIO) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The body should be a single `await` call.
|
|
||||||
let [stmt] = while_stmt.body.as_slice() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let Stmt::Expr(ast::StmtExpr { value, .. }) = stmt else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let Expr::Await(ast::ExprAwait { value, .. }) = value.as_ref() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let Expr::Call(ast::ExprCall { func, .. }) = value.as_ref() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
if checker
|
|
||||||
.semantic()
|
|
||||||
.resolve_qualified_name(func.as_ref())
|
|
||||||
.is_some_and(|path| matches!(path.segments(), ["trio", "sleep" | "sleep_until"]))
|
|
||||||
{
|
|
||||||
checker
|
|
||||||
.diagnostics
|
|
||||||
.push(Diagnostic::new(TrioUnneededSleep, while_stmt.range()));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +1,20 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff_linter/src/rules/flake8_async/mod.rs
|
source: crates/ruff_linter/src/rules/flake8_async/mod.rs
|
||||||
---
|
---
|
||||||
ASYNC110.py:5:5: ASYNC110 Use `trio.Event` instead of awaiting `trio.sleep` in a `while` loop
|
ASYNC110.py:7:5: ASYNC110 Use `trio.Event` instead of awaiting `trio.sleep` in a `while` loop
|
||||||
|
|
|
|
||||||
4 | async def func():
|
6 | async def func():
|
||||||
5 | while True:
|
7 | while True:
|
||||||
| _____^
|
| _____^
|
||||||
6 | | await trio.sleep(10)
|
8 | | await trio.sleep(10)
|
||||||
| |____________________________^ ASYNC110
|
| |____________________________^ ASYNC110
|
||||||
|
|
|
|
||||||
|
|
||||||
ASYNC110.py:10:5: ASYNC110 Use `trio.Event` instead of awaiting `trio.sleep` in a `while` loop
|
ASYNC110.py:12:5: ASYNC110 Use `trio.Event` instead of awaiting `trio.sleep` in a `while` loop
|
||||||
|
|
|
|
||||||
9 | async def func():
|
11 | async def func():
|
||||||
10 | while True:
|
12 | while True:
|
||||||
| _____^
|
| _____^
|
||||||
11 | | await trio.sleep_until(10)
|
13 | | await trio.sleep_until(10)
|
||||||
| |__________________________________^ ASYNC110
|
| |__________________________________^ ASYNC110
|
||||||
|
|
|
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff_linter/src/rules/flake8_async/mod.rs
|
||||||
|
---
|
||||||
|
ASYNC110.py:7:5: ASYNC110 Use `trio.Event` instead of awaiting `trio.sleep` in a `while` loop
|
||||||
|
|
|
||||||
|
6 | async def func():
|
||||||
|
7 | while True:
|
||||||
|
| _____^
|
||||||
|
8 | | await trio.sleep(10)
|
||||||
|
| |____________________________^ ASYNC110
|
||||||
|
|
|
||||||
|
|
||||||
|
ASYNC110.py:12:5: ASYNC110 Use `trio.Event` instead of awaiting `trio.sleep` in a `while` loop
|
||||||
|
|
|
||||||
|
11 | async def func():
|
||||||
|
12 | while True:
|
||||||
|
| _____^
|
||||||
|
13 | | await trio.sleep_until(10)
|
||||||
|
| |__________________________________^ ASYNC110
|
||||||
|
|
|
||||||
|
|
||||||
|
ASYNC110.py:22:5: ASYNC110 Use `asyncio.Event` instead of awaiting `asyncio.sleep` in a `while` loop
|
||||||
|
|
|
||||||
|
21 | async def func():
|
||||||
|
22 | while True:
|
||||||
|
| _____^
|
||||||
|
23 | | await anyio.sleep(10)
|
||||||
|
| |_____________________________^ ASYNC110
|
||||||
|
|
|
||||||
|
|
||||||
|
ASYNC110.py:27:5: ASYNC110 Use `asyncio.Event` instead of awaiting `asyncio.sleep` in a `while` loop
|
||||||
|
|
|
||||||
|
26 | async def func():
|
||||||
|
27 | while True:
|
||||||
|
| _____^
|
||||||
|
28 | | await anyio.sleep_until(10)
|
||||||
|
| |___________________________________^ ASYNC110
|
||||||
|
|
|
||||||
|
|
||||||
|
ASYNC110.py:37:5: ASYNC110 Use `anyio.Event` instead of awaiting `anyio.sleep` in a `while` loop
|
||||||
|
|
|
||||||
|
36 | async def func():
|
||||||
|
37 | while True:
|
||||||
|
| _____^
|
||||||
|
38 | | await asyncio.sleep(10)
|
||||||
|
| |_______________________________^ ASYNC110
|
||||||
|
|
|
Loading…
Add table
Add a link
Reference in a new issue