[flake8-async] Update ASYNC100 to match upstream (#12221)

## Summary

Update the name of `ASYNC100` to match
[upstream](https://flake8-async.readthedocs.io/en/latest/rules.html).

Also update to the functionality to match upstream by supporting
additional context managers from asyncio and anyio. Matching this
[list](https://flake8-async.readthedocs.io/en/latest/glossary.html#timeout-context).

Part of #12039.

## Test Plan

Added the new context managers to the fixture.
This commit is contained in:
Auguste Lalande 2024-07-09 13:55:18 -04:00 committed by GitHub
parent 6fa4e32ad3
commit 88abc6aed8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 333 additions and 153 deletions

View file

@ -1,3 +1,5 @@
import anyio
import asyncio
import trio import trio
@ -25,3 +27,33 @@ async def func():
with trio.move_at(): with trio.move_at():
async with trio.open_nursery() as nursery: async with trio.open_nursery() as nursery:
... ...
async def func():
with anyio.move_on_after():
...
async def func():
with anyio.fail_after():
...
async def func():
with anyio.CancelScope():
...
async def func():
with anyio.CancelScope():
...
async def func():
with asyncio.timeout():
...
async def func():
with asyncio.timeout_at():
...

View file

@ -1313,8 +1313,8 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::UselessWithLock) { if checker.enabled(Rule::UselessWithLock) {
pylint::rules::useless_with_lock(checker, with_stmt); pylint::rules::useless_with_lock(checker, with_stmt);
} }
if checker.enabled(Rule::TrioTimeoutWithoutAwait) { if checker.enabled(Rule::CancelScopeNoCheckpoint) {
flake8_async::rules::timeout_without_await(checker, with_stmt, items); flake8_async::rules::cancel_scope_no_checkpoint(checker, with_stmt, items);
} }
} }
Stmt::While(while_stmt @ ast::StmtWhile { body, orelse, .. }) => { Stmt::While(while_stmt @ ast::StmtWhile { body, orelse, .. }) => {

View file

@ -293,7 +293,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pylint, "W3301") => (RuleGroup::Stable, rules::pylint::rules::NestedMinMax), (Pylint, "W3301") => (RuleGroup::Stable, rules::pylint::rules::NestedMinMax),
// flake8-async // flake8-async
(Flake8Async, "100") => (RuleGroup::Stable, rules::flake8_async::rules::TrioTimeoutWithoutAwait), (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::TrioUnneededSleep),

View file

@ -12,69 +12,124 @@ pub(super) enum AsyncModule {
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub(super) enum MethodName { pub(super) enum MethodName {
AcloseForcefully, AsyncIoTimeout,
CancelScope, AsyncIoTimeoutAt,
CancelShieldedCheckpoint, AnyIoMoveOnAfter,
Checkpoint, AnyIoFailAfter,
CheckpointIfCancelled, AnyIoCancelScope,
FailAfter, TrioAcloseForcefully,
FailAt, TrioCancelScope,
MoveOnAfter, TrioCancelShieldedCheckpoint,
MoveOnAt, TrioCheckpoint,
OpenFile, TrioCheckpointIfCancelled,
OpenProcess, TrioFailAfter,
OpenSslOverTcpListeners, TrioFailAt,
OpenSslOverTcpStream, TrioMoveOnAfter,
OpenTcpListeners, TrioMoveOnAt,
OpenTcpStream, TrioOpenFile,
OpenUnixSocket, TrioOpenProcess,
PermanentlyDetachCoroutineObject, TrioOpenSslOverTcpListeners,
ReattachDetachedCoroutineObject, TrioOpenSslOverTcpStream,
RunProcess, TrioOpenTcpListeners,
ServeListeners, TrioOpenTcpStream,
ServeSslOverTcp, TrioOpenUnixSocket,
ServeTcp, TrioPermanentlyDetachCoroutineObject,
Sleep, TrioReattachDetachedCoroutineObject,
SleepForever, TrioRunProcess,
TemporarilyDetachCoroutineObject, TrioServeListeners,
WaitReadable, TrioServeSslOverTcp,
WaitTaskRescheduled, TrioServeTcp,
WaitWritable, TrioSleep,
TrioSleepForever,
TrioTemporarilyDetachCoroutineObject,
TrioWaitReadable,
TrioWaitTaskRescheduled,
TrioWaitWritable,
} }
impl MethodName { impl MethodName {
/// Returns `true` if the method is async, `false` if it is sync. /// Returns `true` if the method is async, `false` if it is sync.
pub(super) fn is_async(self) -> bool { pub(super) fn is_async(self) -> bool {
match self { matches!(
MethodName::AcloseForcefully self,
| MethodName::CancelShieldedCheckpoint MethodName::TrioAcloseForcefully
| MethodName::Checkpoint | MethodName::TrioCancelShieldedCheckpoint
| MethodName::CheckpointIfCancelled | MethodName::TrioCheckpoint
| MethodName::OpenFile | MethodName::TrioCheckpointIfCancelled
| MethodName::OpenProcess | MethodName::TrioOpenFile
| MethodName::OpenSslOverTcpListeners | MethodName::TrioOpenProcess
| MethodName::OpenSslOverTcpStream | MethodName::TrioOpenSslOverTcpListeners
| MethodName::OpenTcpListeners | MethodName::TrioOpenSslOverTcpStream
| MethodName::OpenTcpStream | MethodName::TrioOpenTcpListeners
| MethodName::OpenUnixSocket | MethodName::TrioOpenTcpStream
| MethodName::PermanentlyDetachCoroutineObject | MethodName::TrioOpenUnixSocket
| MethodName::ReattachDetachedCoroutineObject | MethodName::TrioPermanentlyDetachCoroutineObject
| MethodName::RunProcess | MethodName::TrioReattachDetachedCoroutineObject
| MethodName::ServeListeners | MethodName::TrioRunProcess
| MethodName::ServeSslOverTcp | MethodName::TrioServeListeners
| MethodName::ServeTcp | MethodName::TrioServeSslOverTcp
| MethodName::Sleep | MethodName::TrioServeTcp
| MethodName::SleepForever | MethodName::TrioSleep
| MethodName::TemporarilyDetachCoroutineObject | MethodName::TrioSleepForever
| MethodName::WaitReadable | MethodName::TrioTemporarilyDetachCoroutineObject
| MethodName::WaitTaskRescheduled | MethodName::TrioWaitReadable
| MethodName::WaitWritable => true, | MethodName::TrioWaitTaskRescheduled
| MethodName::TrioWaitWritable
)
}
MethodName::MoveOnAfter /// Returns `true` if the method a timeout context manager.
| MethodName::MoveOnAt pub(super) fn is_timeout_context(self) -> bool {
| MethodName::FailAfter matches!(
| MethodName::FailAt self,
| MethodName::CancelScope => false, MethodName::AsyncIoTimeout
| MethodName::AsyncIoTimeoutAt
| MethodName::AnyIoMoveOnAfter
| MethodName::AnyIoFailAfter
| MethodName::AnyIoCancelScope
| MethodName::TrioMoveOnAfter
| MethodName::TrioMoveOnAt
| MethodName::TrioFailAfter
| MethodName::TrioFailAt
| MethodName::TrioCancelScope
)
}
/// Returns associated module
pub(super) fn module(self) -> AsyncModule {
match self {
MethodName::AsyncIoTimeout | MethodName::AsyncIoTimeoutAt => AsyncModule::AsyncIo,
MethodName::AnyIoMoveOnAfter
| MethodName::AnyIoFailAfter
| MethodName::AnyIoCancelScope => AsyncModule::AnyIo,
MethodName::TrioAcloseForcefully
| MethodName::TrioCancelScope
| MethodName::TrioCancelShieldedCheckpoint
| MethodName::TrioCheckpoint
| MethodName::TrioCheckpointIfCancelled
| MethodName::TrioFailAfter
| MethodName::TrioFailAt
| MethodName::TrioMoveOnAfter
| MethodName::TrioMoveOnAt
| MethodName::TrioOpenFile
| MethodName::TrioOpenProcess
| MethodName::TrioOpenSslOverTcpListeners
| MethodName::TrioOpenSslOverTcpStream
| MethodName::TrioOpenTcpListeners
| MethodName::TrioOpenTcpStream
| MethodName::TrioOpenUnixSocket
| MethodName::TrioPermanentlyDetachCoroutineObject
| MethodName::TrioReattachDetachedCoroutineObject
| MethodName::TrioRunProcess
| MethodName::TrioServeListeners
| MethodName::TrioServeSslOverTcp
| MethodName::TrioServeTcp
| MethodName::TrioSleep
| MethodName::TrioSleepForever
| MethodName::TrioTemporarilyDetachCoroutineObject
| MethodName::TrioWaitReadable
| MethodName::TrioWaitTaskRescheduled
| MethodName::TrioWaitWritable => AsyncModule::Trio,
} }
} }
} }
@ -82,42 +137,49 @@ impl MethodName {
impl MethodName { impl MethodName {
pub(super) fn try_from(qualified_name: &QualifiedName<'_>) -> Option<Self> { pub(super) fn try_from(qualified_name: &QualifiedName<'_>) -> Option<Self> {
match qualified_name.segments() { match qualified_name.segments() {
["trio", "CancelScope"] => Some(Self::CancelScope), ["asyncio", "timeout"] => Some(Self::AsyncIoTimeout),
["trio", "aclose_forcefully"] => Some(Self::AcloseForcefully), ["asyncio", "timeout_at"] => Some(Self::AsyncIoTimeoutAt),
["trio", "fail_after"] => Some(Self::FailAfter), ["anyio", "move_on_after"] => Some(Self::AnyIoMoveOnAfter),
["trio", "fail_at"] => Some(Self::FailAt), ["anyio", "fail_after"] => Some(Self::AnyIoFailAfter),
["anyio", "CancelScope"] => Some(Self::AnyIoCancelScope),
["trio", "CancelScope"] => Some(Self::TrioCancelScope),
["trio", "aclose_forcefully"] => Some(Self::TrioAcloseForcefully),
["trio", "fail_after"] => Some(Self::TrioFailAfter),
["trio", "fail_at"] => Some(Self::TrioFailAt),
["trio", "lowlevel", "cancel_shielded_checkpoint"] => { ["trio", "lowlevel", "cancel_shielded_checkpoint"] => {
Some(Self::CancelShieldedCheckpoint) Some(Self::TrioCancelShieldedCheckpoint)
} }
["trio", "lowlevel", "checkpoint"] => Some(Self::Checkpoint), ["trio", "lowlevel", "checkpoint"] => Some(Self::TrioCheckpoint),
["trio", "lowlevel", "checkpoint_if_cancelled"] => Some(Self::CheckpointIfCancelled), ["trio", "lowlevel", "checkpoint_if_cancelled"] => {
["trio", "lowlevel", "open_process"] => Some(Self::OpenProcess), Some(Self::TrioCheckpointIfCancelled)
}
["trio", "lowlevel", "open_process"] => Some(Self::TrioOpenProcess),
["trio", "lowlevel", "permanently_detach_coroutine_object"] => { ["trio", "lowlevel", "permanently_detach_coroutine_object"] => {
Some(Self::PermanentlyDetachCoroutineObject) Some(Self::TrioPermanentlyDetachCoroutineObject)
} }
["trio", "lowlevel", "reattach_detached_coroutine_object"] => { ["trio", "lowlevel", "reattach_detached_coroutine_object"] => {
Some(Self::ReattachDetachedCoroutineObject) Some(Self::TrioReattachDetachedCoroutineObject)
} }
["trio", "lowlevel", "temporarily_detach_coroutine_object"] => { ["trio", "lowlevel", "temporarily_detach_coroutine_object"] => {
Some(Self::TemporarilyDetachCoroutineObject) Some(Self::TrioTemporarilyDetachCoroutineObject)
} }
["trio", "lowlevel", "wait_readable"] => Some(Self::WaitReadable), ["trio", "lowlevel", "wait_readable"] => Some(Self::TrioWaitReadable),
["trio", "lowlevel", "wait_task_rescheduled"] => Some(Self::WaitTaskRescheduled), ["trio", "lowlevel", "wait_task_rescheduled"] => Some(Self::TrioWaitTaskRescheduled),
["trio", "lowlevel", "wait_writable"] => Some(Self::WaitWritable), ["trio", "lowlevel", "wait_writable"] => Some(Self::TrioWaitWritable),
["trio", "move_on_after"] => Some(Self::MoveOnAfter), ["trio", "move_on_after"] => Some(Self::TrioMoveOnAfter),
["trio", "move_on_at"] => Some(Self::MoveOnAt), ["trio", "move_on_at"] => Some(Self::TrioMoveOnAt),
["trio", "open_file"] => Some(Self::OpenFile), ["trio", "open_file"] => Some(Self::TrioOpenFile),
["trio", "open_ssl_over_tcp_listeners"] => Some(Self::OpenSslOverTcpListeners), ["trio", "open_ssl_over_tcp_listeners"] => Some(Self::TrioOpenSslOverTcpListeners),
["trio", "open_ssl_over_tcp_stream"] => Some(Self::OpenSslOverTcpStream), ["trio", "open_ssl_over_tcp_stream"] => Some(Self::TrioOpenSslOverTcpStream),
["trio", "open_tcp_listeners"] => Some(Self::OpenTcpListeners), ["trio", "open_tcp_listeners"] => Some(Self::TrioOpenTcpListeners),
["trio", "open_tcp_stream"] => Some(Self::OpenTcpStream), ["trio", "open_tcp_stream"] => Some(Self::TrioOpenTcpStream),
["trio", "open_unix_socket"] => Some(Self::OpenUnixSocket), ["trio", "open_unix_socket"] => Some(Self::TrioOpenUnixSocket),
["trio", "run_process"] => Some(Self::RunProcess), ["trio", "run_process"] => Some(Self::TrioRunProcess),
["trio", "serve_listeners"] => Some(Self::ServeListeners), ["trio", "serve_listeners"] => Some(Self::TrioServeListeners),
["trio", "serve_ssl_over_tcp"] => Some(Self::ServeSslOverTcp), ["trio", "serve_ssl_over_tcp"] => Some(Self::TrioServeSslOverTcp),
["trio", "serve_tcp"] => Some(Self::ServeTcp), ["trio", "serve_tcp"] => Some(Self::TrioServeTcp),
["trio", "sleep"] => Some(Self::Sleep), ["trio", "sleep"] => Some(Self::TrioSleep),
["trio", "sleep_forever"] => Some(Self::SleepForever), ["trio", "sleep_forever"] => Some(Self::TrioSleepForever),
_ => None, _ => None,
} }
} }
@ -126,42 +188,51 @@ impl MethodName {
impl std::fmt::Display for MethodName { impl std::fmt::Display for MethodName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
MethodName::AcloseForcefully => write!(f, "trio.aclose_forcefully"), MethodName::AsyncIoTimeout => write!(f, "asyncio.timeout"),
MethodName::CancelScope => write!(f, "trio.CancelScope"), MethodName::AsyncIoTimeoutAt => write!(f, "asyncio.timeout_at"),
MethodName::CancelShieldedCheckpoint => { MethodName::AnyIoMoveOnAfter => write!(f, "anyio.move_on_after"),
MethodName::AnyIoFailAfter => write!(f, "anyio.fail_after"),
MethodName::AnyIoCancelScope => write!(f, "anyio.CancelScope"),
MethodName::TrioAcloseForcefully => write!(f, "trio.aclose_forcefully"),
MethodName::TrioCancelScope => write!(f, "trio.CancelScope"),
MethodName::TrioCancelShieldedCheckpoint => {
write!(f, "trio.lowlevel.cancel_shielded_checkpoint") write!(f, "trio.lowlevel.cancel_shielded_checkpoint")
} }
MethodName::Checkpoint => write!(f, "trio.lowlevel.checkpoint"), MethodName::TrioCheckpoint => write!(f, "trio.lowlevel.checkpoint"),
MethodName::CheckpointIfCancelled => write!(f, "trio.lowlevel.checkpoint_if_cancelled"), MethodName::TrioCheckpointIfCancelled => {
MethodName::FailAfter => write!(f, "trio.fail_after"), write!(f, "trio.lowlevel.checkpoint_if_cancelled")
MethodName::FailAt => write!(f, "trio.fail_at"), }
MethodName::MoveOnAfter => write!(f, "trio.move_on_after"), MethodName::TrioFailAfter => write!(f, "trio.fail_after"),
MethodName::MoveOnAt => write!(f, "trio.move_on_at"), MethodName::TrioFailAt => write!(f, "trio.fail_at"),
MethodName::OpenFile => write!(f, "trio.open_file"), MethodName::TrioMoveOnAfter => write!(f, "trio.move_on_after"),
MethodName::OpenProcess => write!(f, "trio.lowlevel.open_process"), MethodName::TrioMoveOnAt => write!(f, "trio.move_on_at"),
MethodName::OpenSslOverTcpListeners => write!(f, "trio.open_ssl_over_tcp_listeners"), MethodName::TrioOpenFile => write!(f, "trio.open_file"),
MethodName::OpenSslOverTcpStream => write!(f, "trio.open_ssl_over_tcp_stream"), MethodName::TrioOpenProcess => write!(f, "trio.lowlevel.open_process"),
MethodName::OpenTcpListeners => write!(f, "trio.open_tcp_listeners"), MethodName::TrioOpenSslOverTcpListeners => {
MethodName::OpenTcpStream => write!(f, "trio.open_tcp_stream"), write!(f, "trio.open_ssl_over_tcp_listeners")
MethodName::OpenUnixSocket => write!(f, "trio.open_unix_socket"), }
MethodName::PermanentlyDetachCoroutineObject => { MethodName::TrioOpenSslOverTcpStream => write!(f, "trio.open_ssl_over_tcp_stream"),
MethodName::TrioOpenTcpListeners => write!(f, "trio.open_tcp_listeners"),
MethodName::TrioOpenTcpStream => write!(f, "trio.open_tcp_stream"),
MethodName::TrioOpenUnixSocket => write!(f, "trio.open_unix_socket"),
MethodName::TrioPermanentlyDetachCoroutineObject => {
write!(f, "trio.lowlevel.permanently_detach_coroutine_object") write!(f, "trio.lowlevel.permanently_detach_coroutine_object")
} }
MethodName::ReattachDetachedCoroutineObject => { MethodName::TrioReattachDetachedCoroutineObject => {
write!(f, "trio.lowlevel.reattach_detached_coroutine_object") write!(f, "trio.lowlevel.reattach_detached_coroutine_object")
} }
MethodName::RunProcess => write!(f, "trio.run_process"), MethodName::TrioRunProcess => write!(f, "trio.run_process"),
MethodName::ServeListeners => write!(f, "trio.serve_listeners"), MethodName::TrioServeListeners => write!(f, "trio.serve_listeners"),
MethodName::ServeSslOverTcp => write!(f, "trio.serve_ssl_over_tcp"), MethodName::TrioServeSslOverTcp => write!(f, "trio.serve_ssl_over_tcp"),
MethodName::ServeTcp => write!(f, "trio.serve_tcp"), MethodName::TrioServeTcp => write!(f, "trio.serve_tcp"),
MethodName::Sleep => write!(f, "trio.sleep"), MethodName::TrioSleep => write!(f, "trio.sleep"),
MethodName::SleepForever => write!(f, "trio.sleep_forever"), MethodName::TrioSleepForever => write!(f, "trio.sleep_forever"),
MethodName::TemporarilyDetachCoroutineObject => { MethodName::TrioTemporarilyDetachCoroutineObject => {
write!(f, "trio.lowlevel.temporarily_detach_coroutine_object") write!(f, "trio.lowlevel.temporarily_detach_coroutine_object")
} }
MethodName::WaitReadable => write!(f, "trio.lowlevel.wait_readable"), MethodName::TrioWaitReadable => write!(f, "trio.lowlevel.wait_readable"),
MethodName::WaitTaskRescheduled => write!(f, "trio.lowlevel.wait_task_rescheduled"), MethodName::TrioWaitTaskRescheduled => write!(f, "trio.lowlevel.wait_task_rescheduled"),
MethodName::WaitWritable => write!(f, "trio.lowlevel.wait_writable"), MethodName::TrioWaitWritable => write!(f, "trio.lowlevel.wait_writable"),
} }
} }
} }

View file

@ -15,7 +15,7 @@ mod tests {
use crate::test::test_path; use crate::test::test_path;
use crate::{assert_messages, settings}; use crate::{assert_messages, settings};
#[test_case(Rule::TrioTimeoutWithoutAwait, Path::new("ASYNC100.py"))] #[test_case(Rule::CancelScopeNoCheckpoint, Path::new("ASYNC100.py"))]
#[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"))]
@ -38,6 +38,7 @@ mod tests {
Ok(()) Ok(())
} }
#[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"))]
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {

View file

@ -3,40 +3,44 @@ use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::AwaitVisitor; use ruff_python_ast::helpers::AwaitVisitor;
use ruff_python_ast::visitor::Visitor; use ruff_python_ast::visitor::Visitor;
use ruff_python_ast::{StmtWith, WithItem}; use ruff_python_ast::{StmtWith, WithItem};
use ruff_python_semantic::Modules;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::rules::flake8_async::helpers::MethodName; use crate::rules::flake8_async::helpers::{AsyncModule, MethodName};
use crate::settings::types::PreviewMode;
/// ## What it does /// ## What it does
/// Checks for trio functions that should contain await but don't. /// Checks for timeout context managers which do not contain a checkpoint.
/// ///
/// ## Why is this bad? /// ## Why is this bad?
/// Some trio context managers, such as `trio.fail_after` and /// Some asynchronous context managers, such as `asyncio.timeout` and
/// `trio.move_on_after`, have no effect unless they contain an `await` /// `trio.move_on_after`, have no effect unless they contain an `await`
/// statement. The use of such functions without an `await` statement is /// statement. The use of such context managers without an `await` statement is
/// likely a mistake. /// likely a mistake.
/// ///
/// ## Example /// ## Example
/// ```python /// ```python
/// async def func(): /// async def func():
/// with trio.move_on_after(2): /// with asyncio.timeout(2):
/// do_something() /// do_something()
/// ``` /// ```
/// ///
/// Use instead: /// Use instead:
/// ```python /// ```python
/// async def func(): /// async def func():
/// with trio.move_on_after(2): /// with asyncio.timeout(2):
/// do_something() /// do_something()
/// await awaitable() /// await awaitable()
/// ``` /// ```
///
/// [`asyncio` timeouts]: https://docs.python.org/3/library/asyncio-task.html#timeouts
/// [`anyio` timeouts]: https://anyio.readthedocs.io/en/stable/cancellation.html
/// [`trio` timeouts]: https://trio.readthedocs.io/en/stable/reference-core.html#cancellation-and-timeouts
#[violation] #[violation]
pub struct TrioTimeoutWithoutAwait { pub struct CancelScopeNoCheckpoint {
method_name: MethodName, method_name: MethodName,
} }
impl Violation for TrioTimeoutWithoutAwait { impl Violation for CancelScopeNoCheckpoint {
#[derive_message_formats] #[derive_message_formats]
fn message(&self) -> String { fn message(&self) -> String {
let Self { method_name } = self; let Self { method_name } = self;
@ -45,15 +49,11 @@ impl Violation for TrioTimeoutWithoutAwait {
} }
/// ASYNC100 /// ASYNC100
pub(crate) fn timeout_without_await( pub(crate) fn cancel_scope_no_checkpoint(
checker: &mut Checker, checker: &mut Checker,
with_stmt: &StmtWith, with_stmt: &StmtWith,
with_items: &[WithItem], with_items: &[WithItem],
) { ) {
if !checker.semantic().seen_module(Modules::TRIO) {
return;
}
let Some(method_name) = with_items.iter().find_map(|item| { let Some(method_name) = with_items.iter().find_map(|item| {
let call = item.context_expr.as_call_expr()?; let call = item.context_expr.as_call_expr()?;
let qualified_name = checker let qualified_name = checker
@ -64,14 +64,7 @@ pub(crate) fn timeout_without_await(
return; return;
}; };
if !matches!( if !method_name.is_timeout_context() {
method_name,
MethodName::MoveOnAfter
| MethodName::MoveOnAt
| MethodName::FailAfter
| MethodName::FailAt
| MethodName::CancelScope
) {
return; return;
} }
@ -81,8 +74,17 @@ pub(crate) fn timeout_without_await(
return; return;
} }
checker.diagnostics.push(Diagnostic::new( if matches!(checker.settings.preview, PreviewMode::Disabled) {
TrioTimeoutWithoutAwait { method_name }, if matches!(method_name.module(), AsyncModule::Trio) {
with_stmt.range, checker.diagnostics.push(Diagnostic::new(
)); CancelScopeNoCheckpoint { method_name },
with_stmt.range,
));
}
} else {
checker.diagnostics.push(Diagnostic::new(
CancelScopeNoCheckpoint { method_name },
with_stmt.range,
));
}
} }

View file

@ -3,9 +3,9 @@ pub(crate) use blocking_http_call::*;
pub(crate) use blocking_open_call::*; pub(crate) use blocking_open_call::*;
pub(crate) use blocking_process_invocation::*; pub(crate) use blocking_process_invocation::*;
pub(crate) use blocking_sleep::*; pub(crate) use blocking_sleep::*;
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 timeout_without_await::*;
pub(crate) use unneeded_sleep::*; pub(crate) use unneeded_sleep::*;
pub(crate) use zero_sleep_call::*; pub(crate) use zero_sleep_call::*;
@ -14,8 +14,8 @@ mod blocking_http_call;
mod blocking_open_call; mod blocking_open_call;
mod blocking_process_invocation; mod blocking_process_invocation;
mod blocking_sleep; mod blocking_sleep;
mod cancel_scope_no_checkpoint;
mod sleep_forever_call; mod sleep_forever_call;
mod sync_call; mod sync_call;
mod timeout_without_await;
mod unneeded_sleep; mod unneeded_sleep;
mod zero_sleep_call; mod zero_sleep_call;

View file

@ -1,20 +1,20 @@
--- ---
source: crates/ruff_linter/src/rules/flake8_async/mod.rs source: crates/ruff_linter/src/rules/flake8_async/mod.rs
--- ---
ASYNC100.py:5:5: ASYNC100 A `with trio.fail_after(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint. ASYNC100.py:7:5: ASYNC100 A `with trio.fail_after(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint.
| |
4 | async def func(): 6 | async def func():
5 | with trio.fail_after(): 7 | with trio.fail_after():
| _____^ | _____^
6 | | ... 8 | | ...
| |___________^ ASYNC100 | |___________^ ASYNC100
| |
ASYNC100.py:15:5: ASYNC100 A `with trio.move_on_after(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint. ASYNC100.py:17:5: ASYNC100 A `with trio.move_on_after(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint.
| |
14 | async def func(): 16 | async def func():
15 | with trio.move_on_after(): 17 | with trio.move_on_after():
| _____^ | _____^
16 | | ... 18 | | ...
| |___________^ ASYNC100 | |___________^ ASYNC100
| |

View file

@ -0,0 +1,74 @@
---
source: crates/ruff_linter/src/rules/flake8_async/mod.rs
---
ASYNC100.py:7:5: ASYNC100 A `with trio.fail_after(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint.
|
6 | async def func():
7 | with trio.fail_after():
| _____^
8 | | ...
| |___________^ ASYNC100
|
ASYNC100.py:17:5: ASYNC100 A `with trio.move_on_after(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint.
|
16 | async def func():
17 | with trio.move_on_after():
| _____^
18 | | ...
| |___________^ ASYNC100
|
ASYNC100.py:33:5: ASYNC100 A `with anyio.move_on_after(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint.
|
32 | async def func():
33 | with anyio.move_on_after():
| _____^
34 | | ...
| |___________^ ASYNC100
|
ASYNC100.py:38:5: ASYNC100 A `with anyio.fail_after(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint.
|
37 | async def func():
38 | with anyio.fail_after():
| _____^
39 | | ...
| |___________^ ASYNC100
|
ASYNC100.py:43:5: ASYNC100 A `with anyio.CancelScope(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint.
|
42 | async def func():
43 | with anyio.CancelScope():
| _____^
44 | | ...
| |___________^ ASYNC100
|
ASYNC100.py:48:5: ASYNC100 A `with anyio.CancelScope(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint.
|
47 | async def func():
48 | with anyio.CancelScope():
| _____^
49 | | ...
| |___________^ ASYNC100
|
ASYNC100.py:53:5: ASYNC100 A `with asyncio.timeout(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint.
|
52 | async def func():
53 | with asyncio.timeout():
| _____^
54 | | ...
| |___________^ ASYNC100
|
ASYNC100.py:58:5: ASYNC100 A `with asyncio.timeout_at(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint.
|
57 | async def func():
58 | with asyncio.timeout_at():
| _____^
59 | | ...
| |___________^ ASYNC100
|