Allow matplotlib.use calls to intersperse imports (#9094)

This PR allows `matplotlib.use` calls to intersperse imports without
triggering `E402`. This is a pragmatic choice as it's common to require
`matplotlib.use` calls prior to importing from within `matplotlib`
itself.

Closes https://github.com/astral-sh/ruff/issues/9091.
This commit is contained in:
Charlie Marsh 2023-12-11 12:06:25 -05:00 committed by GitHub
parent 07380e0657
commit f452bf8cad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 77 additions and 32 deletions

View file

@ -24,21 +24,27 @@ sys.path.insert(0, "some/path")
import f import f
__some__magic = 1 import matplotlib
matplotlib.use("Agg")
import g import g
__some__magic = 1
import h
def foo() -> None: def foo() -> None:
import h
if __name__ == "__main__":
import i import i
import j; import k
if __name__ == "__main__":
import j
import k; import l
if __name__ == "__main__": if __name__ == "__main__":
import l; \ import m; \
import m import n

View file

@ -306,6 +306,7 @@ where
if !(self.semantic.seen_import_boundary() if !(self.semantic.seen_import_boundary()
|| helpers::is_assignment_to_a_dunder(stmt) || helpers::is_assignment_to_a_dunder(stmt)
|| helpers::in_nested_block(self.semantic.current_statements()) || helpers::in_nested_block(self.semantic.current_statements())
|| imports::is_matplotlib_activation(stmt, self.semantic())
|| self.settings.preview.is_enabled() || self.settings.preview.is_enabled()
&& imports::is_sys_path_modification(stmt, self.semantic())) && imports::is_sys_path_modification(stmt, self.semantic()))
{ {

View file

@ -8,30 +8,50 @@ E402.py:25:1: E402 Module level import not at top of file
25 | import f 25 | import f
| ^^^^^^^^ E402 | ^^^^^^^^ E402
26 | 26 |
27 | __some__magic = 1 27 | import matplotlib
| |
E402.py:29:1: E402 Module level import not at top of file E402.py:27:1: E402 Module level import not at top of file
| |
27 | __some__magic = 1 25 | import f
26 |
27 | import matplotlib
| ^^^^^^^^^^^^^^^^^ E402
28 | 28 |
29 | import g 29 | matplotlib.use("Agg")
|
E402.py:31:1: E402 Module level import not at top of file
|
29 | matplotlib.use("Agg")
30 |
31 | import g
| ^^^^^^^^ E402
32 |
33 | __some__magic = 1
|
E402.py:35:1: E402 Module level import not at top of file
|
33 | __some__magic = 1
34 |
35 | import h
| ^^^^^^^^ E402 | ^^^^^^^^ E402
| |
E402.py:39:1: E402 Module level import not at top of file E402.py:45:1: E402 Module level import not at top of file
| |
37 | import i 43 | import j
38 | 44 |
39 | import j; import k 45 | import k; import l
| ^^^^^^^^ E402 | ^^^^^^^^ E402
| |
E402.py:39:11: E402 Module level import not at top of file E402.py:45:11: E402 Module level import not at top of file
| |
37 | import i 43 | import j
38 | 44 |
39 | import j; import k 45 | import k; import l
| ^^^^^^^^ E402 | ^^^^^^^^ E402
| |

View file

@ -1,27 +1,27 @@
--- ---
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
--- ---
E402.py:29:1: E402 Module level import not at top of file E402.py:35:1: E402 Module level import not at top of file
| |
27 | __some__magic = 1 33 | __some__magic = 1
28 | 34 |
29 | import g 35 | import h
| ^^^^^^^^ E402 | ^^^^^^^^ E402
| |
E402.py:39:1: E402 Module level import not at top of file E402.py:45:1: E402 Module level import not at top of file
| |
37 | import i 43 | import j
38 | 44 |
39 | import j; import k 45 | import k; import l
| ^^^^^^^^ E402 | ^^^^^^^^ E402
| |
E402.py:39:11: E402 Module level import not at top of file E402.py:45:11: E402 Module level import not at top of file
| |
37 | import i 43 | import j
38 | 44 |
39 | import j; import k 45 | import k; import l
| ^^^^^^^^ E402 | ^^^^^^^^ E402
| |

View file

@ -35,3 +35,21 @@ pub fn is_sys_path_modification(stmt: &Stmt, semantic: &SemanticModel) -> bool {
) )
}) })
} }
/// Returns `true` if a [`Stmt`] is a `matplotlib.use` activation, as in:
/// ```python
/// import matplotlib
///
/// matplotlib.use("Agg")
/// ```
pub fn is_matplotlib_activation(stmt: &Stmt, semantic: &SemanticModel) -> bool {
let Stmt::Expr(ast::StmtExpr { value, range: _ }) = stmt else {
return false;
};
let Expr::Call(ast::ExprCall { func, .. }) = value.as_ref() else {
return false;
};
semantic
.resolve_call_path(func.as_ref())
.is_some_and(|call_path| matches!(call_path.as_slice(), ["matplotlib", "use"]))
}