From 1fd9d63c0ad9e431ca1f6902e40399ad1cf72e69 Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Thu, 9 Nov 2023 11:45:57 -0500 Subject: [PATCH 1/4] ruff_linter: tweak detection logic for TimeoutErrorAlias Previously, this lint had its alias detection logic a little backwards. That is, for Python 3.11+, it would *only* detect asyncio.TimeoutError as an alias, but it should have also detected socket.timeout as an alias. And in Python <3.11, it would falsely detect asyncio.TimeoutError as an alias where it should have only detected socket.timeout as an alias. We fix it so that both asyncio.TimeoutError and socket.timeout are detected as aliases in Python 3.11+, and only socket.timeout is detected as an alias in Python 3.10. --- .../pyupgrade/rules/timeout_error_alias.rs | 6 +-- ...er__rules__pyupgrade__tests__UP041.py.snap | 44 ++++++++++++++++++- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/timeout_error_alias.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/timeout_error_alias.rs index 1bb999f96d6516..899c9922fdbbd8 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/timeout_error_alias.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/timeout_error_alias.rs @@ -61,12 +61,12 @@ impl AlwaysFixableViolation for TimeoutErrorAlias { fn is_alias(expr: &Expr, semantic: &SemanticModel, target_version: PythonVersion) -> bool { semantic.resolve_call_path(expr).is_some_and(|call_path| { if target_version >= PythonVersion::Py311 { - matches!(call_path.as_slice(), [""] | ["asyncio", "TimeoutError"]) - } else { matches!( call_path.as_slice(), - [""] | ["asyncio", "TimeoutError"] | ["socket", "timeout"] + [""] | ["socket", "timeout"] | ["asyncio", "TimeoutError"] ) + } else { + matches!(call_path.as_slice(), [""] | ["socket", "timeout"]) } }) } diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP041.py.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP041.py.snap index 8b0c7b90e2298f..68f66dc6de38fd 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP041.py.snap +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP041.py.snap @@ -21,6 +21,26 @@ UP041.py:5:8: UP041 [*] Replace aliased errors with `TimeoutError` 7 7 | 8 8 | try: +UP041.py:10:8: UP041 [*] Replace aliased errors with `TimeoutError` + | + 8 | try: + 9 | pass +10 | except socket.timeout: + | ^^^^^^^^^^^^^^ UP041 +11 | pass + | + = help: Replace `socket.timeout` with builtin `TimeoutError` + +ℹ Safe fix +7 7 | +8 8 | try: +9 9 | pass +10 |-except socket.timeout: + 10 |+except TimeoutError: +11 11 | pass +12 12 | +13 13 | # Should NOT be in parentheses when replaced + UP041.py:17:8: UP041 [*] Replace aliased errors with `TimeoutError` | 15 | try: @@ -41,6 +61,26 @@ UP041.py:17:8: UP041 [*] Replace aliased errors with `TimeoutError` 19 19 | 20 20 | try: +UP041.py:22:8: UP041 [*] Replace aliased errors with `TimeoutError` + | +20 | try: +21 | pass +22 | except (socket.timeout,): + | ^^^^^^^^^^^^^^^^^ UP041 +23 | pass + | + = help: Replace with builtin `TimeoutError` + +ℹ Safe fix +19 19 | +20 20 | try: +21 21 | pass +22 |-except (socket.timeout,): + 22 |+except TimeoutError: +23 23 | pass +24 24 | +25 25 | try: + UP041.py:27:8: UP041 [*] Replace aliased errors with `TimeoutError` | 25 | try: @@ -56,7 +96,7 @@ UP041.py:27:8: UP041 [*] Replace aliased errors with `TimeoutError` 25 25 | try: 26 26 | pass 27 |-except (asyncio.TimeoutError, socket.timeout,): - 27 |+except (TimeoutError, socket.timeout): + 27 |+except TimeoutError: 28 28 | pass 29 29 | 30 30 | # Should be kept in parentheses (because multiple) @@ -76,7 +116,7 @@ UP041.py:34:8: UP041 [*] Replace aliased errors with `TimeoutError` 32 32 | try: 33 33 | pass 34 |-except (asyncio.TimeoutError, socket.timeout, KeyError, TimeoutError): - 34 |+except (socket.timeout, KeyError, TimeoutError): + 34 |+except (KeyError, TimeoutError): 35 35 | pass 36 36 | 37 37 | # First should change, second should not From ea06087f5afced238c5f9be1483b9f194be2aef0 Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Thu, 9 Nov 2023 11:55:36 -0500 Subject: [PATCH 2/4] ruff_linter: assert that TimeoutErrorAlias only runs on 3.10+ Without this assert, the lint could falsely trigger a safe fix that is actually unsafe if the higher level logic calling the lint changes. That is, right now, the lint is only invoked for 3.10+ so everything is fine, but nothing is stopping that from changing. So we make the assumption clear: if it changes, then we should get a pretty loud panic. --- .../src/rules/pyupgrade/rules/timeout_error_alias.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/timeout_error_alias.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/timeout_error_alias.rs index 899c9922fdbbd8..796e7ea67e04ee 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/timeout_error_alias.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/timeout_error_alias.rs @@ -66,6 +66,14 @@ fn is_alias(expr: &Expr, semantic: &SemanticModel, target_version: PythonVersion [""] | ["socket", "timeout"] | ["asyncio", "TimeoutError"] ) } else { + // N.B. This lint is only invoked for Python 3.10+. We assume + // as much here since otherwise socket.timeout would be an unsafe + // fix in Python <3.10. We add an assert to make this assumption + // explicit. + assert!( + target_version >= PythonVersion::Py310, + "lint should only be used for Python 3.10+", + ); matches!(call_path.as_slice(), [""] | ["socket", "timeout"]) } }) From 70438469e11b56c30d2152da0870075eae597c08 Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Thu, 9 Nov 2023 11:57:02 -0500 Subject: [PATCH 3/4] ruff_linter: add asyncio.TimeoutError regression test for Python 3.10+ This new test checks that safe fixes are not suggested for replacing asyncio.TimeoutError with TimeoutError in Python <3.11. Namely, before 3.11, these were distinct types. So switching from asyncio.TimeoutError to TimeoutError could result in a change of semantics. --- crates/ruff_linter/src/rules/pyupgrade/mod.rs | 13 +++ ...timeout_error_alias_not_applied_py310.snap | 84 +++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__async_timeout_error_alias_not_applied_py310.snap diff --git a/crates/ruff_linter/src/rules/pyupgrade/mod.rs b/crates/ruff_linter/src/rules/pyupgrade/mod.rs index 987e82608e03b5..194bcef73fa3f9 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/mod.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/mod.rs @@ -96,6 +96,19 @@ mod tests { Ok(()) } + #[test] + fn async_timeout_error_alias_not_applied_py310() -> Result<()> { + let diagnostics = test_path( + Path::new("pyupgrade/UP041.py"), + &settings::LinterSettings { + target_version: PythonVersion::Py310, + ..settings::LinterSettings::for_rule(Rule::TimeoutErrorAlias) + }, + )?; + assert_messages!(diagnostics); + Ok(()) + } + #[test] fn non_pep695_type_alias_not_applied_py311() -> Result<()> { let diagnostics = test_path( diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__async_timeout_error_alias_not_applied_py310.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__async_timeout_error_alias_not_applied_py310.snap new file mode 100644 index 00000000000000..3197391b53919f --- /dev/null +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__async_timeout_error_alias_not_applied_py310.snap @@ -0,0 +1,84 @@ +--- +source: crates/ruff_linter/src/rules/pyupgrade/mod.rs +--- +UP041.py:10:8: UP041 [*] Replace aliased errors with `TimeoutError` + | + 8 | try: + 9 | pass +10 | except socket.timeout: + | ^^^^^^^^^^^^^^ UP041 +11 | pass + | + = help: Replace `socket.timeout` with builtin `TimeoutError` + +ℹ Safe fix +7 7 | +8 8 | try: +9 9 | pass +10 |-except socket.timeout: + 10 |+except TimeoutError: +11 11 | pass +12 12 | +13 13 | # Should NOT be in parentheses when replaced + +UP041.py:22:8: UP041 [*] Replace aliased errors with `TimeoutError` + | +20 | try: +21 | pass +22 | except (socket.timeout,): + | ^^^^^^^^^^^^^^^^^ UP041 +23 | pass + | + = help: Replace with builtin `TimeoutError` + +ℹ Safe fix +19 19 | +20 20 | try: +21 21 | pass +22 |-except (socket.timeout,): + 22 |+except TimeoutError: +23 23 | pass +24 24 | +25 25 | try: + +UP041.py:27:8: UP041 [*] Replace aliased errors with `TimeoutError` + | +25 | try: +26 | pass +27 | except (asyncio.TimeoutError, socket.timeout,): + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP041 +28 | pass + | + = help: Replace with builtin `TimeoutError` + +ℹ Safe fix +24 24 | +25 25 | try: +26 26 | pass +27 |-except (asyncio.TimeoutError, socket.timeout,): + 27 |+except (TimeoutError, asyncio.TimeoutError): +28 28 | pass +29 29 | +30 30 | # Should be kept in parentheses (because multiple) + +UP041.py:34:8: UP041 [*] Replace aliased errors with `TimeoutError` + | +32 | try: +33 | pass +34 | except (asyncio.TimeoutError, socket.timeout, KeyError, TimeoutError): + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP041 +35 | pass + | + = help: Replace with builtin `TimeoutError` + +ℹ Safe fix +31 31 | +32 32 | try: +33 33 | pass +34 |-except (asyncio.TimeoutError, socket.timeout, KeyError, TimeoutError): + 34 |+except (asyncio.TimeoutError, KeyError, TimeoutError): +35 35 | pass +36 36 | +37 37 | # First should change, second should not + + From ba22f95c62933fa695f400ea54f8342347737508 Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Thu, 9 Nov 2023 12:38:48 -0500 Subject: [PATCH 4/4] ruff_linter: permit asyncio.TimeoutError as an unsafe fix for <3.11 This commit tweaks the TimeoutErrorAlias lint to suggest asyncio.TimeoutError in Python <3.11, but only as an unsafe fix. Namely, in <3.11, asyncio.TimeoutError is not an alias and thus the transformation could change the semantics of the program. In the case where there is a tuple containing both safe and unsafe fixes, I decided to keep it as a single suggested fix and made it overall unsafe. So for example, if Ruff sees this code in Python 3.10: try: pass except (asyncio.TimeoutError, socket.timeout): pass Then it will suggest this as an unsafe fix: try: pass except TimeoutError: pass It could, though, suggest this as a safe fix: try: pass except (asyncio.TimeoutError, TimeoutError): pass since socket.timeout became an alias of TimeoutError in Python 3.10. I opted not to go this route because it wasn't obvious to me that it was worth it. Fixes #8565 --- crates/ruff_diagnostics/src/fix.rs | 17 +++ .../pyupgrade/rules/timeout_error_alias.rs | 103 +++++++++++------- ...timeout_error_alias_not_applied_py310.snap | 68 +++++++++++- 3 files changed, 142 insertions(+), 46 deletions(-) diff --git a/crates/ruff_diagnostics/src/fix.rs b/crates/ruff_diagnostics/src/fix.rs index 751258508f7183..81bab65cf12a78 100644 --- a/crates/ruff_diagnostics/src/fix.rs +++ b/crates/ruff_diagnostics/src/fix.rs @@ -23,6 +23,23 @@ pub enum Applicability { Safe, } +impl Applicability { + /// Returns the "less safe" of the two applicabilities. + /// + /// This is useful in contexts where there are multiple transformations in + /// a single fix, and the fix should be labeled as the least safe of the + /// bunch. + #[must_use] + pub fn degrade(self, other: Applicability) -> Applicability { + match (self, other) { + (Applicability::DisplayOnly, _) => self, + (Applicability::Unsafe, Applicability::DisplayOnly) => other, + (Applicability::Unsafe, _) => self, + (Applicability::Safe, _) => other, + } + } +} + /// Indicates the level of isolation required to apply a fix. #[derive(Default, Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/timeout_error_alias.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/timeout_error_alias.rs index 796e7ea67e04ee..938cddb77846b1 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/timeout_error_alias.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/timeout_error_alias.rs @@ -2,7 +2,7 @@ use ruff_python_ast::{self as ast, ExceptHandler, Expr, ExprContext}; use ruff_text_size::{Ranged, TextRange}; use crate::fix::edits::pad; -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::call_path::compose_call_path; use ruff_python_semantic::SemanticModel; @@ -57,26 +57,34 @@ impl AlwaysFixableViolation for TimeoutErrorAlias { } } -/// Return `true` if an [`Expr`] is an alias of `TimeoutError`. -fn is_alias(expr: &Expr, semantic: &SemanticModel, target_version: PythonVersion) -> bool { - semantic.resolve_call_path(expr).is_some_and(|call_path| { - if target_version >= PythonVersion::Py311 { - matches!( - call_path.as_slice(), - [""] | ["socket", "timeout"] | ["asyncio", "TimeoutError"] - ) +/// Return an appropriate `Applicability` if an [`Expr`] is an alias of +/// `TimeoutError`. +fn alias_applicability(checker: &mut Checker, expr: &Expr) -> Option { + // N.B. This lint is only invoked for Python 3.10+. We assume + // as much here since otherwise socket.timeout would be an unsafe + // fix in Python <3.10. We add an assert to make this assumption + // explicit. + assert!( + checker.settings.target_version >= PythonVersion::Py310, + "lint should only be used for Python 3.10+", + ); + + let call_path = checker.semantic().resolve_call_path(expr)?; + if matches!(call_path.as_slice(), [""] | ["socket", "timeout"]) { + // Since we assume 3.10+ here and socket.timeout is an alias + // of TimeoutError in 3.10+, it's always safe to change it to + // TimeoutError. + return Some(Applicability::Safe); + } + if matches!(call_path.as_slice(), [""] | ["asyncio", "TimeoutError"]) { + // asyncio.TimeoutError only became an alias of TimeoutError in 3.11+. + return if checker.settings.target_version >= PythonVersion::Py311 { + Some(Applicability::Safe) } else { - // N.B. This lint is only invoked for Python 3.10+. We assume - // as much here since otherwise socket.timeout would be an unsafe - // fix in Python <3.10. We add an assert to make this assumption - // explicit. - assert!( - target_version >= PythonVersion::Py310, - "lint should only be used for Python 3.10+", - ); - matches!(call_path.as_slice(), [""] | ["socket", "timeout"]) - } - }) + Some(Applicability::Unsafe) + }; + } + None } /// Return `true` if an [`Expr`] is `TimeoutError`. @@ -87,7 +95,7 @@ fn is_timeout_error(expr: &Expr, semantic: &SemanticModel) -> bool { } /// Create a [`Diagnostic`] for a single target, like an [`Expr::Name`]. -fn atom_diagnostic(checker: &mut Checker, target: &Expr) { +fn atom_diagnostic(checker: &mut Checker, app: Applicability, target: &Expr) { let mut diagnostic = Diagnostic::new( TimeoutErrorAlias { name: compose_call_path(target), @@ -95,16 +103,19 @@ fn atom_diagnostic(checker: &mut Checker, target: &Expr) { target.range(), ); if checker.semantic().is_builtin("TimeoutError") { - diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( - "TimeoutError".to_string(), - target.range(), - ))); + let edit = Edit::range_replacement("TimeoutError".to_string(), target.range()); + diagnostic.set_fix(Fix::applicable_edit(edit, app)); } checker.diagnostics.push(diagnostic); } /// Create a [`Diagnostic`] for a tuple of expressions. -fn tuple_diagnostic(checker: &mut Checker, tuple: &ast::ExprTuple, aliases: &[&Expr]) { +fn tuple_diagnostic( + checker: &mut Checker, + app: Applicability, + tuple: &ast::ExprTuple, + aliases: &[&Expr], +) { let mut diagnostic = Diagnostic::new(TimeoutErrorAlias { name: None }, tuple.range()); if checker.semantic().is_builtin("TimeoutError") { // Filter out any `TimeoutErrors` aliases. @@ -145,10 +156,11 @@ fn tuple_diagnostic(checker: &mut Checker, tuple: &ast::ExprTuple, aliases: &[&E format!("({})", checker.generator().expr(&node.into())) }; - diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( + let edit = Edit::range_replacement( pad(content, tuple.range(), checker.locator()), tuple.range(), - ))); + ); + diagnostic.set_fix(Fix::applicable_edit(edit, app)); } checker.diagnostics.push(diagnostic); } @@ -162,20 +174,24 @@ pub(crate) fn timeout_error_alias_handlers(checker: &mut Checker, handlers: &[Ex }; match expr.as_ref() { Expr::Name(_) | Expr::Attribute(_) => { - if is_alias(expr, checker.semantic(), checker.settings.target_version) { - atom_diagnostic(checker, expr); - } + let Some(app) = alias_applicability(checker, expr) else { + continue; + }; + atom_diagnostic(checker, app, expr); } Expr::Tuple(tuple) => { // List of aliases to replace with `TimeoutError`. let mut aliases: Vec<&Expr> = vec![]; + let mut app = Applicability::Safe; for elt in &tuple.elts { - if is_alias(elt, checker.semantic(), checker.settings.target_version) { - aliases.push(elt); - } + let Some(eltapp) = alias_applicability(checker, elt) else { + continue; + }; + aliases.push(elt); + app = app.degrade(eltapp); } if !aliases.is_empty() { - tuple_diagnostic(checker, tuple, &aliases); + tuple_diagnostic(checker, app, tuple, &aliases); } } _ => {} @@ -185,16 +201,19 @@ pub(crate) fn timeout_error_alias_handlers(checker: &mut Checker, handlers: &[Ex /// UP041 pub(crate) fn timeout_error_alias_call(checker: &mut Checker, func: &Expr) { - if is_alias(func, checker.semantic(), checker.settings.target_version) { - atom_diagnostic(checker, func); - } + let Some(app) = alias_applicability(checker, func) else { + return; + }; + atom_diagnostic(checker, app, func); } /// UP041 pub(crate) fn timeout_error_alias_raise(checker: &mut Checker, expr: &Expr) { - if matches!(expr, Expr::Name(_) | Expr::Attribute(_)) { - if is_alias(expr, checker.semantic(), checker.settings.target_version) { - atom_diagnostic(checker, expr); - } + if !matches!(expr, Expr::Name(_) | Expr::Attribute(_)) { + return; } + let Some(app) = alias_applicability(checker, expr) else { + return; + }; + atom_diagnostic(checker, app, expr); } diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__async_timeout_error_alias_not_applied_py310.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__async_timeout_error_alias_not_applied_py310.snap index 3197391b53919f..14d06aeb3df00e 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__async_timeout_error_alias_not_applied_py310.snap +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__async_timeout_error_alias_not_applied_py310.snap @@ -1,6 +1,26 @@ --- source: crates/ruff_linter/src/rules/pyupgrade/mod.rs --- +UP041.py:5:8: UP041 [*] Replace aliased errors with `TimeoutError` + | +3 | try: +4 | pass +5 | except asyncio.TimeoutError: + | ^^^^^^^^^^^^^^^^^^^^ UP041 +6 | pass + | + = help: Replace `asyncio.TimeoutError` with builtin `TimeoutError` + +ℹ Unsafe fix +2 2 | # These should be fixed +3 3 | try: +4 4 | pass +5 |-except asyncio.TimeoutError: + 5 |+except TimeoutError: +6 6 | pass +7 7 | +8 8 | try: + UP041.py:10:8: UP041 [*] Replace aliased errors with `TimeoutError` | 8 | try: @@ -21,6 +41,26 @@ UP041.py:10:8: UP041 [*] Replace aliased errors with `TimeoutError` 12 12 | 13 13 | # Should NOT be in parentheses when replaced +UP041.py:17:8: UP041 [*] Replace aliased errors with `TimeoutError` + | +15 | try: +16 | pass +17 | except (asyncio.TimeoutError,): + | ^^^^^^^^^^^^^^^^^^^^^^^ UP041 +18 | pass + | + = help: Replace with builtin `TimeoutError` + +ℹ Unsafe fix +14 14 | +15 15 | try: +16 16 | pass +17 |-except (asyncio.TimeoutError,): + 17 |+except TimeoutError: +18 18 | pass +19 19 | +20 20 | try: + UP041.py:22:8: UP041 [*] Replace aliased errors with `TimeoutError` | 20 | try: @@ -51,12 +91,12 @@ UP041.py:27:8: UP041 [*] Replace aliased errors with `TimeoutError` | = help: Replace with builtin `TimeoutError` -ℹ Safe fix +ℹ Unsafe fix 24 24 | 25 25 | try: 26 26 | pass 27 |-except (asyncio.TimeoutError, socket.timeout,): - 27 |+except (TimeoutError, asyncio.TimeoutError): + 27 |+except TimeoutError: 28 28 | pass 29 29 | 30 30 | # Should be kept in parentheses (because multiple) @@ -71,14 +111,34 @@ UP041.py:34:8: UP041 [*] Replace aliased errors with `TimeoutError` | = help: Replace with builtin `TimeoutError` -ℹ Safe fix +ℹ Unsafe fix 31 31 | 32 32 | try: 33 33 | pass 34 |-except (asyncio.TimeoutError, socket.timeout, KeyError, TimeoutError): - 34 |+except (asyncio.TimeoutError, KeyError, TimeoutError): + 34 |+except (KeyError, TimeoutError): 35 35 | pass 36 36 | 37 37 | # First should change, second should not +UP041.py:42:8: UP041 [*] Replace aliased errors with `TimeoutError` + | +40 | try: +41 | pass +42 | except (asyncio.TimeoutError, error): + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP041 +43 | pass + | + = help: Replace with builtin `TimeoutError` + +ℹ Unsafe fix +39 39 | from .mmap import error +40 40 | try: +41 41 | pass +42 |-except (asyncio.TimeoutError, error): + 42 |+except (TimeoutError, error): +43 43 | pass +44 44 | +45 45 | # These should not change +