[`flake8-use-pathlib`] Implement `glob` (`PTH207`) (#5939)

Discovered that the usage of `glob.glob` is
[widespread](https://grep.app/search?current=7&q=glob.glob%28&filter%5Blang%5D%5B0%5D=Python)
when working on the previous lints for `flake8-use-pathlib`.
This commit is contained in:
Simon Brugman 2023-07-27 01:15:05 +02:00 committed by GitHub
parent 132f07c27b
commit ffdd653c54
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 116 additions and 1 deletions

View File

@ -0,0 +1,11 @@
import os
import glob
from glob import glob as search
extensions_dir = "./extensions"
# PTH207
glob.glob(os.path.join(extensions_dir, "ops", "autograd", "*.cpp"))
list(glob.iglob(os.path.join(extensions_dir, "ops", "autograd", "*.cpp")))
search("*.png")

View File

@ -849,6 +849,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
Rule::OsPathGetatime, Rule::OsPathGetatime,
Rule::OsPathGetmtime, Rule::OsPathGetmtime,
Rule::OsPathGetctime, Rule::OsPathGetctime,
Rule::Glob,
]) { ]) {
flake8_use_pathlib::rules::replaceable_by_pathlib(checker, func); flake8_use_pathlib::rules::replaceable_by_pathlib(checker, func);
} }

View File

@ -760,6 +760,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8UsePathlib, "204") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::rules::OsPathGetmtime), (Flake8UsePathlib, "204") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::rules::OsPathGetmtime),
(Flake8UsePathlib, "205") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::rules::OsPathGetctime), (Flake8UsePathlib, "205") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::rules::OsPathGetctime),
(Flake8UsePathlib, "206") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::rules::OsSepSplit), (Flake8UsePathlib, "206") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::rules::OsSepSplit),
(Flake8UsePathlib, "207") => (RuleGroup::Unspecified, rules::flake8_use_pathlib::rules::Glob),
// flake8-logging-format // flake8-logging-format
(Flake8LoggingFormat, "001") => (RuleGroup::Unspecified, rules::flake8_logging_format::violations::LoggingStringFormat), (Flake8LoggingFormat, "001") => (RuleGroup::Unspecified, rules::flake8_logging_format::violations::LoggingStringFormat),

View File

@ -62,6 +62,7 @@ mod tests {
#[test_case(Rule::OsPathGetmtime, Path::new("PTH204.py"))] #[test_case(Rule::OsPathGetmtime, Path::new("PTH204.py"))]
#[test_case(Rule::OsPathGetctime, Path::new("PTH205.py"))] #[test_case(Rule::OsPathGetctime, Path::new("PTH205.py"))]
#[test_case(Rule::OsSepSplit, Path::new("PTH206.py"))] #[test_case(Rule::OsSepSplit, Path::new("PTH206.py"))]
#[test_case(Rule::Glob, Path::new("PTH207.py"))]
fn rules_pypath(rule_code: Rule, path: &Path) -> Result<()> { fn rules_pypath(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path( let diagnostics = test_path(

View File

@ -0,0 +1,55 @@
use ruff_diagnostics::Violation;
use ruff_macros::{derive_message_formats, violation};
/// ## What it does
/// Checks for the use of `glob` and `iglob`.
///
/// ## Why is this bad?
/// `pathlib` offers a high-level API for path manipulation, as compared to
/// the lower-level API offered by `os` and `glob`.
///
/// When possible, using `Path` object methods such as `Path.glob()` can
/// improve readability over their low-level counterparts (e.g.,
/// `glob.glob()`).
///
/// Note that `glob.glob` and `Path.glob` are not exact equivalents:
///
/// | | `glob` | `Path.glob` |
/// |-------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------|
/// | Hidden files | Excludes hidden files by default. From Python 3.11 onwards, the `include_hidden` keyword can used to include hidden directories. | Includes hidden files by default. |
/// | Iterator | `iglob` returns an iterator. Under the hood, `glob` simply converts the iterator to a list. | `Path.glob` returns an iterator. |
/// | Working directory | `glob` takes a `root_dir` keyword to set the current working directory. | `Path.rglob` can be used to return the relative path. |
/// | Globstar (`**`) | `glob` requires the `recursive` flag to be set to `True` for the `**` pattern to match any files and zero or more directories, subdirectories, and symbolic links. | The `**` pattern in `Path.glob` means "this directory and all subdirectories, recursively". In other words, it enables recursive globbing. |
///
/// ## Example
/// ```python
/// import glob
/// import os
///
/// glob.glob(os.path.join(path, "requirements*.txt"))
/// ```
///
/// Use instead:
/// ```python
/// from pathlib import Path
///
/// Path(path).glob("requirements*.txt")
/// ```
///
/// ## References
/// - [Python documentation: `Path.glob`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.glob)
/// - [Python documentation: `Path.rglob`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.rglob)
/// - [Python documentation: `glob.glob`](https://docs.python.org/3/library/glob.html#glob.glob)
/// - [Python documentation: `glob.iglob`](https://docs.python.org/3/library/glob.html#glob.iglob)
#[violation]
pub struct Glob {
pub function: String,
}
impl Violation for Glob {
#[derive_message_formats]
fn message(&self) -> String {
let Glob { function } = self;
format!("Replace `{function}` with `Path.glob` or `Path.rglob`")
}
}

View File

@ -1,3 +1,4 @@
pub(crate) use glob_rule::*;
pub(crate) use os_path_getatime::*; pub(crate) use os_path_getatime::*;
pub(crate) use os_path_getctime::*; pub(crate) use os_path_getctime::*;
pub(crate) use os_path_getmtime::*; pub(crate) use os_path_getmtime::*;
@ -6,6 +7,7 @@ pub(crate) use os_sep_split::*;
pub(crate) use path_constructor_current_directory::*; pub(crate) use path_constructor_current_directory::*;
pub(crate) use replaceable_by_pathlib::*; pub(crate) use replaceable_by_pathlib::*;
mod glob_rule;
mod os_path_getatime; mod os_path_getatime;
mod os_path_getctime; mod os_path_getctime;
mod os_path_getmtime; mod os_path_getmtime;

View File

@ -5,7 +5,7 @@ use ruff_diagnostics::{Diagnostic, DiagnosticKind};
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::AsRule; use crate::registry::AsRule;
use crate::rules::flake8_use_pathlib::rules::{ use crate::rules::flake8_use_pathlib::rules::{
OsPathGetatime, OsPathGetctime, OsPathGetmtime, OsPathGetsize, Glob, OsPathGetatime, OsPathGetctime, OsPathGetmtime, OsPathGetsize,
}; };
use crate::rules::flake8_use_pathlib::violations::{ use crate::rules::flake8_use_pathlib::violations::{
BuiltinOpen, OsChmod, OsGetcwd, OsMakedirs, OsMkdir, OsPathAbspath, OsPathBasename, BuiltinOpen, OsChmod, OsGetcwd, OsMakedirs, OsMkdir, OsPathAbspath, OsPathBasename,
@ -89,6 +89,19 @@ pub(crate) fn replaceable_by_pathlib(checker: &mut Checker, expr: &Expr) {
["" | "builtin", "open"] => Some(BuiltinOpen.into()), ["" | "builtin", "open"] => Some(BuiltinOpen.into()),
// PTH124 // PTH124
["py", "path", "local"] => Some(PyPath.into()), ["py", "path", "local"] => Some(PyPath.into()),
// PTH207
["glob", "glob"] => Some(
Glob {
function: "glob".to_string(),
}
.into(),
),
["glob", "iglob"] => Some(
Glob {
function: "iglob".to_string(),
}
.into(),
),
// PTH115 // PTH115
// Python 3.9+ // Python 3.9+
["os", "readlink"] if checker.settings.target_version >= PythonVersion::Py39 => { ["os", "readlink"] if checker.settings.target_version >= PythonVersion::Py39 => {

View File

@ -0,0 +1,30 @@
---
source: crates/ruff/src/rules/flake8_use_pathlib/mod.rs
---
PTH207.py:9:1: PTH207 Replace `glob` with `Path.glob` or `Path.rglob`
|
8 | # PTH207
9 | glob.glob(os.path.join(extensions_dir, "ops", "autograd", "*.cpp"))
| ^^^^^^^^^ PTH207
10 | list(glob.iglob(os.path.join(extensions_dir, "ops", "autograd", "*.cpp")))
11 | search("*.png")
|
PTH207.py:10:6: PTH207 Replace `iglob` with `Path.glob` or `Path.rglob`
|
8 | # PTH207
9 | glob.glob(os.path.join(extensions_dir, "ops", "autograd", "*.cpp"))
10 | list(glob.iglob(os.path.join(extensions_dir, "ops", "autograd", "*.cpp")))
| ^^^^^^^^^^ PTH207
11 | search("*.png")
|
PTH207.py:11:1: PTH207 Replace `glob` with `Path.glob` or `Path.rglob`
|
9 | glob.glob(os.path.join(extensions_dir, "ops", "autograd", "*.cpp"))
10 | list(glob.iglob(os.path.join(extensions_dir, "ops", "autograd", "*.cpp")))
11 | search("*.png")
| ^^^^^^ PTH207
|

1
ruff.schema.json generated
View File

@ -2346,6 +2346,7 @@
"PTH204", "PTH204",
"PTH205", "PTH205",
"PTH206", "PTH206",
"PTH207",
"PYI", "PYI",
"PYI0", "PYI0",
"PYI00", "PYI00",