[`flake8-use-pathlib`] Add fixes for `PTH102` and `PTH103` (#19514)

## Summary

Part of https://github.com/astral-sh/ruff/issues/2331

## Test Plan

<!-- How was it tested? -->
`cargo nextest run flake8_use_pathlib`
This commit is contained in:
chiri 2025-08-20 21:36:07 +03:00 committed by GitHub
parent 39ee71c2a5
commit d04dcd991b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 745 additions and 123 deletions

View File

@ -106,4 +106,22 @@ os.replace("src", "dst", src_dir_fd=1)
os.replace("src", "dst", dst_dir_fd=2)
os.getcwd()
os.getcwdb()
os.getcwdb()
os.mkdir(path="directory")
os.mkdir(
# comment 1
"directory",
mode=0o777
)
os.mkdir("directory", mode=0o777, dir_fd=1)
os.makedirs("name", 0o777, exist_ok=False)
os.makedirs("name", 0o777, False)
os.makedirs(name="name", mode=0o777, exist_ok=False)
os.makedirs("name", unknown_kwarg=True)

View File

@ -1039,8 +1039,6 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
flake8_simplify::rules::zip_dict_keys_and_values(checker, call);
}
if checker.any_rule_enabled(&[
Rule::OsMkdir,
Rule::OsMakedirs,
Rule::OsStat,
Rule::OsPathJoin,
Rule::OsPathSplitext,
@ -1120,6 +1118,12 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
if checker.is_rule_enabled(Rule::OsPathSamefile) {
flake8_use_pathlib::rules::os_path_samefile(checker, call, segments);
}
if checker.is_rule_enabled(Rule::OsMkdir) {
flake8_use_pathlib::rules::os_mkdir(checker, call, segments);
}
if checker.is_rule_enabled(Rule::OsMakedirs) {
flake8_use_pathlib::rules::os_makedirs(checker, call, segments);
}
if checker.is_rule_enabled(Rule::PathConstructorCurrentDirectory) {
flake8_use_pathlib::rules::path_constructor_current_directory(
checker, call, segments,

View File

@ -921,8 +921,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
// flake8-use-pathlib
(Flake8UsePathlib, "100") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsPathAbspath),
(Flake8UsePathlib, "101") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsChmod),
(Flake8UsePathlib, "102") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsMkdir),
(Flake8UsePathlib, "103") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsMakedirs),
(Flake8UsePathlib, "102") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsMkdir),
(Flake8UsePathlib, "103") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsMakedirs),
(Flake8UsePathlib, "104") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsRename),
(Flake8UsePathlib, "105") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsReplace),
(Flake8UsePathlib, "106") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsRmdir),

View File

@ -159,6 +159,16 @@ pub(crate) const fn is_fix_os_getcwd_enabled(settings: &LinterSettings) -> bool
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/19514
pub(crate) const fn is_fix_os_mkdir_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/19514
pub(crate) const fn is_fix_os_makedirs_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/11436
// https://github.com/astral-sh/ruff/pull/11168
pub(crate) const fn is_dunder_init_fix_unused_import_enabled(settings: &LinterSettings) -> bool {

View File

@ -1,10 +1,11 @@
use crate::checkers::ast::Checker;
use crate::importer::ImportRequest;
use crate::{Applicability, Edit, Fix, Violation};
use ruff_python_ast::{self as ast, Expr, ExprCall};
use ruff_python_semantic::{SemanticModel, analyze::typing};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::importer::ImportRequest;
use crate::{Applicability, Edit, Fix, Violation};
pub(crate) fn is_keyword_only_argument_non_default(arguments: &ast::Arguments, name: &str) -> bool {
arguments
.find_keyword(name)
@ -183,3 +184,17 @@ pub(crate) fn check_os_pathlib_two_arg_calls(
});
}
}
pub(crate) fn has_unknown_keywords_or_starred_expr(
arguments: &ast::Arguments,
allowed: &[&str],
) -> bool {
if arguments.args.iter().any(Expr::is_starred_expr) {
return true;
}
arguments.keywords.iter().any(|kw| match &kw.arg {
Some(arg) => !allowed.contains(&arg.as_str()),
None => true,
})
}

View File

@ -2,6 +2,8 @@ pub(crate) use glob_rule::*;
pub(crate) use invalid_pathlib_with_suffix::*;
pub(crate) use os_chmod::*;
pub(crate) use os_getcwd::*;
pub(crate) use os_makedirs::*;
pub(crate) use os_mkdir::*;
pub(crate) use os_path_abspath::*;
pub(crate) use os_path_basename::*;
pub(crate) use os_path_dirname::*;
@ -30,6 +32,8 @@ mod glob_rule;
mod invalid_pathlib_with_suffix;
mod os_chmod;
mod os_getcwd;
mod os_makedirs;
mod os_mkdir;
mod os_path_abspath;
mod os_path_basename;
mod os_path_dirname;

View File

@ -0,0 +1,152 @@
use ruff_diagnostics::{Applicability, Edit, Fix};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::{ArgOrKeyword, ExprCall};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::importer::ImportRequest;
use crate::preview::is_fix_os_makedirs_enabled;
use crate::rules::flake8_use_pathlib::helpers::{
has_unknown_keywords_or_starred_expr, is_pathlib_path_call,
};
use crate::{FixAvailability, Violation};
/// ## What it does
/// Checks for uses of `os.makedirs`.
///
/// ## Why is this bad?
/// `pathlib` offers a high-level API for path manipulation, as compared to
/// the lower-level API offered by `os`. When possible, using `Path` object
/// methods such as `Path.mkdir(parents=True)` can improve readability over the
/// `os` module's counterparts (e.g., `os.makedirs()`.
///
/// ## Examples
/// ```python
/// import os
///
/// os.makedirs("./nested/directory/")
/// ```
///
/// Use instead:
/// ```python
/// from pathlib import Path
///
/// Path("./nested/directory/").mkdir(parents=True)
/// ```
///
/// ## Known issues
/// While using `pathlib` can improve the readability and type safety of your code,
/// it can be less performant than the lower-level alternatives that work directly with strings,
/// especially on older versions of Python.
///
/// ## Fix Safety
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
///
/// ## References
/// - [Python documentation: `Path.mkdir`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.mkdir)
/// - [Python documentation: `os.makedirs`](https://docs.python.org/3/library/os.html#os.makedirs)
/// - [PEP 428 The pathlib module object-oriented filesystem paths](https://peps.python.org/pep-0428/)
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
#[derive(ViolationMetadata)]
pub(crate) struct OsMakedirs;
impl Violation for OsMakedirs {
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
"`os.makedirs()` should be replaced by `Path.mkdir(parents=True)`".to_string()
}
fn fix_title(&self) -> Option<String> {
Some("Replace with `Path(...).mkdir(parents=True)`".to_string())
}
}
/// PTH103
pub(crate) fn os_makedirs(checker: &Checker, call: &ExprCall, segments: &[&str]) {
if segments != ["os", "makedirs"] {
return;
}
let range = call.range();
let mut diagnostic = checker.report_diagnostic(OsMakedirs, call.func.range());
let Some(name) = call.arguments.find_argument_value("name", 0) else {
return;
};
if !is_fix_os_makedirs_enabled(checker.settings()) {
return;
}
// Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.makedirs)
// ```text
// 0 1 2
// os.makedirs(name, mode=0o777, exist_ok=False)
// ```
// We should not offer autofixes if there are more arguments
// than in the original signature
if call.arguments.len() > 3 {
return;
}
// We should not offer autofixes if there are keyword arguments
// that don't match the original function signature
if has_unknown_keywords_or_starred_expr(&call.arguments, &["name", "mode", "exist_ok"]) {
return;
}
diagnostic.try_set_fix(|| {
let (import_edit, binding) = checker.importer().get_or_import_symbol(
&ImportRequest::import("pathlib", "Path"),
call.start(),
checker.semantic(),
)?;
let applicability = if checker.comment_ranges().intersects(range) {
Applicability::Unsafe
} else {
Applicability::Safe
};
let locator = checker.locator();
let name_code = locator.slice(name.range());
let mode = call.arguments.find_argument("mode", 1);
let exist_ok = call.arguments.find_argument("exist_ok", 2);
let mkdir_args = match (mode, exist_ok) {
// Default to a keyword argument when alone.
(None, None) => "parents=True".to_string(),
// If either argument is missing, it's safe to add `parents` at the end.
(None, Some(arg)) | (Some(arg), None) => {
format!("{}, parents=True", locator.slice(arg))
}
// If they're all positional, `parents` has to be positional too.
(Some(ArgOrKeyword::Arg(mode)), Some(ArgOrKeyword::Arg(exist_ok))) => {
format!("{}, True, {}", locator.slice(mode), locator.slice(exist_ok))
}
// If either argument is a keyword, we can put `parents` at the end again.
(Some(mode), Some(exist_ok)) => format!(
"{}, {}, parents=True",
locator.slice(mode),
locator.slice(exist_ok)
),
};
let replacement = if is_pathlib_path_call(checker, name) {
format!("{name_code}.mkdir({mkdir_args})")
} else {
format!("{binding}({name_code}).mkdir({mkdir_args})")
};
Ok(Fix::applicable_edits(
Edit::range_replacement(replacement, range),
[import_edit],
applicability,
))
});
}

View File

@ -0,0 +1,136 @@
use ruff_diagnostics::{Applicability, Edit, Fix};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::ExprCall;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::importer::ImportRequest;
use crate::preview::is_fix_os_mkdir_enabled;
use crate::rules::flake8_use_pathlib::helpers::{
has_unknown_keywords_or_starred_expr, is_keyword_only_argument_non_default,
is_pathlib_path_call,
};
use crate::{FixAvailability, Violation};
/// ## What it does
/// Checks for uses of `os.mkdir`.
///
/// ## Why is this bad?
/// `pathlib` offers a high-level API for path manipulation, as compared to
/// the lower-level API offered by `os`. When possible, using `Path` object
/// methods such as `Path.mkdir()` can improve readability over the `os`
/// module's counterparts (e.g., `os.mkdir()`).
///
/// ## Examples
/// ```python
/// import os
///
/// os.mkdir("./directory/")
/// ```
///
/// Use instead:
/// ```python
/// from pathlib import Path
///
/// Path("./directory/").mkdir()
/// ```
///
/// ## Known issues
/// While using `pathlib` can improve the readability and type safety of your code,
/// it can be less performant than the lower-level alternatives that work directly with strings,
/// especially on older versions of Python.
///
/// ## Fix Safety
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
///
/// ## References
/// - [Python documentation: `Path.mkdir`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.mkdir)
/// - [Python documentation: `os.mkdir`](https://docs.python.org/3/library/os.html#os.mkdir)
/// - [PEP 428 The pathlib module object-oriented filesystem paths](https://peps.python.org/pep-0428/)
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
#[derive(ViolationMetadata)]
pub(crate) struct OsMkdir;
impl Violation for OsMkdir {
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
"`os.mkdir()` should be replaced by `Path.mkdir()`".to_string()
}
fn fix_title(&self) -> Option<String> {
Some("Replace with `Path(...).mkdir()`".to_string())
}
}
/// PTH102
pub(crate) fn os_mkdir(checker: &Checker, call: &ExprCall, segments: &[&str]) {
if segments != ["os", "mkdir"] {
return;
}
// `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
// Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.mkdir)
// ```text
// 0 1 2
// os.mkdir(path, mode=0o777, *, dir_fd=None)
// ```
if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") {
return;
}
let range = call.range();
let mut diagnostic = checker.report_diagnostic(OsMkdir, call.func.range());
let Some(path) = call.arguments.find_argument_value("path", 0) else {
return;
};
if !is_fix_os_mkdir_enabled(checker.settings()) {
return;
}
if call.arguments.len() > 2 {
return;
}
if has_unknown_keywords_or_starred_expr(&call.arguments, &["path", "mode"]) {
return;
}
diagnostic.try_set_fix(|| {
let (import_edit, binding) = checker.importer().get_or_import_symbol(
&ImportRequest::import("pathlib", "Path"),
call.start(),
checker.semantic(),
)?;
let applicability = if checker.comment_ranges().intersects(range) {
Applicability::Unsafe
} else {
Applicability::Safe
};
let path_code = checker.locator().slice(path.range());
let mkdir_args = call
.arguments
.find_argument_value("mode", 1)
.map(|expr| format!("mode={}", checker.locator().slice(expr.range())))
.unwrap_or_default();
let replacement = if is_pathlib_path_call(checker, path) {
format!("{path_code}.mkdir({mkdir_args})")
} else {
format!("{binding}({path_code}).mkdir({mkdir_args})")
};
Ok(Fix::applicable_edits(
Edit::range_replacement(replacement, range),
[import_edit],
applicability,
))
});
}

View File

@ -8,8 +8,7 @@ use crate::rules::flake8_use_pathlib::helpers::{
use crate::rules::flake8_use_pathlib::{
rules::Glob,
violations::{
BuiltinOpen, Joiner, OsListdir, OsMakedirs, OsMkdir, OsPathJoin, OsPathSplitext, OsStat,
OsSymlink, PyPath,
BuiltinOpen, Joiner, OsListdir, OsPathJoin, OsPathSplitext, OsStat, OsSymlink, PyPath,
},
};
@ -20,21 +19,6 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
let range = call.func.range();
match qualified_name.segments() {
// PTH102
["os", "makedirs"] => checker.report_diagnostic_if_enabled(OsMakedirs, range),
// PTH103
["os", "mkdir"] => {
// `dir_fd` is not supported by pathlib, so check if it's set to non-default values.
// Signature as of Python 3.13 (https://docs.python.org/3/library/os.html#os.mkdir)
// ```text
// 0 1 2
// os.mkdir(path, mode=0o777, *, dir_fd=None)
// ```
if is_keyword_only_argument_non_default(&call.arguments, "dir_fd") {
return;
}
checker.report_diagnostic_if_enabled(OsMkdir, range)
}
// PTH116
["os", "stat"] => {
// `dir_fd` is not supported by pathlib, so check if it's set to non-default values.

View File

@ -34,6 +34,7 @@ PTH102 `os.mkdir()` should be replaced by `Path.mkdir()`
10 | os.makedirs(p)
11 | os.rename(p)
|
help: Replace with `Path(...).mkdir()`
PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
--> full_name.py:10:1
@ -45,6 +46,7 @@ PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
11 | os.rename(p)
12 | os.replace(p)
|
help: Replace with `Path(...).mkdir(parents=True)`
PTH104 `os.rename()` should be replaced by `Path.rename()`
--> full_name.py:11:1
@ -419,5 +421,77 @@ PTH109 `os.getcwd()` should be replaced by `Path.cwd()`
108 | os.getcwd()
109 | os.getcwdb()
| ^^^^^^^^^^
110 |
111 | os.mkdir(path="directory")
|
help: Replace with `Path.cwd()`
PTH102 `os.mkdir()` should be replaced by `Path.mkdir()`
--> full_name.py:111:1
|
109 | os.getcwdb()
110 |
111 | os.mkdir(path="directory")
| ^^^^^^^^
112 |
113 | os.mkdir(
|
help: Replace with `Path(...).mkdir()`
PTH102 `os.mkdir()` should be replaced by `Path.mkdir()`
--> full_name.py:113:1
|
111 | os.mkdir(path="directory")
112 |
113 | os.mkdir(
| ^^^^^^^^
114 | # comment 1
115 | "directory",
|
help: Replace with `Path(...).mkdir()`
PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
--> full_name.py:121:1
|
119 | os.mkdir("directory", mode=0o777, dir_fd=1)
120 |
121 | os.makedirs("name", 0o777, exist_ok=False)
| ^^^^^^^^^^^
122 |
123 | os.makedirs("name", 0o777, False)
|
help: Replace with `Path(...).mkdir(parents=True)`
PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
--> full_name.py:123:1
|
121 | os.makedirs("name", 0o777, exist_ok=False)
122 |
123 | os.makedirs("name", 0o777, False)
| ^^^^^^^^^^^
124 |
125 | os.makedirs(name="name", mode=0o777, exist_ok=False)
|
help: Replace with `Path(...).mkdir(parents=True)`
PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
--> full_name.py:125:1
|
123 | os.makedirs("name", 0o777, False)
124 |
125 | os.makedirs(name="name", mode=0o777, exist_ok=False)
| ^^^^^^^^^^^
126 |
127 | os.makedirs("name", unknown_kwarg=True)
|
help: Replace with `Path(...).mkdir(parents=True)`
PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
--> full_name.py:127:1
|
125 | os.makedirs(name="name", mode=0o777, exist_ok=False)
126 |
127 | os.makedirs("name", unknown_kwarg=True)
| ^^^^^^^^^^^
|
help: Replace with `Path(...).mkdir(parents=True)`

View File

@ -34,6 +34,7 @@ PTH102 `os.mkdir()` should be replaced by `Path.mkdir()`
10 | foo.makedirs(p)
11 | foo.rename(p)
|
help: Replace with `Path(...).mkdir()`
PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
--> import_as.py:10:1
@ -45,6 +46,7 @@ PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
11 | foo.rename(p)
12 | foo.replace(p)
|
help: Replace with `Path(...).mkdir(parents=True)`
PTH104 `os.rename()` should be replaced by `Path.rename()`
--> import_as.py:11:1

View File

@ -34,6 +34,7 @@ PTH102 `os.mkdir()` should be replaced by `Path.mkdir()`
12 | makedirs(p)
13 | rename(p)
|
help: Replace with `Path(...).mkdir()`
PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
--> import_from.py:12:1
@ -45,6 +46,7 @@ PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
13 | rename(p)
14 | replace(p)
|
help: Replace with `Path(...).mkdir(parents=True)`
PTH104 `os.rename()` should be replaced by `Path.rename()`
--> import_from.py:13:1

View File

@ -34,6 +34,7 @@ PTH102 `os.mkdir()` should be replaced by `Path.mkdir()`
17 | xmakedirs(p)
18 | xrename(p)
|
help: Replace with `Path(...).mkdir()`
PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
--> import_from_as.py:17:1
@ -45,6 +46,7 @@ PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
18 | xrename(p)
19 | xreplace(p)
|
help: Replace with `Path(...).mkdir(parents=True)`
PTH104 `os.rename()` should be replaced by `Path.rename()`
--> import_from_as.py:18:1

View File

@ -38,7 +38,7 @@ PTH101 `os.chmod()` should be replaced by `Path.chmod()`
|
help: Replace with `Path(...).chmod(...)`
PTH102 `os.mkdir()` should be replaced by `Path.mkdir()`
PTH102 [*] `os.mkdir()` should be replaced by `Path.mkdir()`
--> full_name.py:9:7
|
7 | a = os.path.abspath(p)
@ -48,8 +48,25 @@ PTH102 `os.mkdir()` should be replaced by `Path.mkdir()`
10 | os.makedirs(p)
11 | os.rename(p)
|
help: Replace with `Path(...).mkdir()`
PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
Safe fix
1 1 | import os
2 2 | import os.path
3 |+import pathlib
3 4 |
4 5 | p = "/foo"
5 6 | q = "bar"
6 7 |
7 8 | a = os.path.abspath(p)
8 9 | aa = os.chmod(p)
9 |-aaa = os.mkdir(p)
10 |+aaa = pathlib.Path(p).mkdir()
10 11 | os.makedirs(p)
11 12 | os.rename(p)
12 13 | os.replace(p)
PTH103 [*] `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
--> full_name.py:10:1
|
8 | aa = os.chmod(p)
@ -59,6 +76,24 @@ PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
11 | os.rename(p)
12 | os.replace(p)
|
help: Replace with `Path(...).mkdir(parents=True)`
Safe fix
1 1 | import os
2 2 | import os.path
3 |+import pathlib
3 4 |
4 5 | p = "/foo"
5 6 | q = "bar"
--------------------------------------------------------------------------------
7 8 | a = os.path.abspath(p)
8 9 | aa = os.chmod(p)
9 10 | aaa = os.mkdir(p)
10 |-os.makedirs(p)
11 |+pathlib.Path(p).mkdir(parents=True)
11 12 | os.rename(p)
12 13 | os.replace(p)
13 14 | os.rmdir(p)
PTH104 `os.rename()` should be replaced by `Path.rename()`
--> full_name.py:11:1
@ -645,6 +680,8 @@ help: Replace with `Path.cwd()`
108 |-os.getcwd()
109 |+pathlib.Path.cwd()
109 110 | os.getcwdb()
110 111 |
111 112 | os.mkdir(path="directory")
PTH109 [*] `os.getcwd()` should be replaced by `Path.cwd()`
--> full_name.py:109:1
@ -652,6 +689,8 @@ PTH109 [*] `os.getcwd()` should be replaced by `Path.cwd()`
108 | os.getcwd()
109 | os.getcwdb()
| ^^^^^^^^^^
110 |
111 | os.mkdir(path="directory")
|
help: Replace with `Path.cwd()`
@ -668,3 +707,164 @@ help: Replace with `Path.cwd()`
108 109 | os.getcwd()
109 |-os.getcwdb()
110 |+pathlib.Path.cwd()
110 111 |
111 112 | os.mkdir(path="directory")
112 113 |
PTH102 [*] `os.mkdir()` should be replaced by `Path.mkdir()`
--> full_name.py:111:1
|
109 | os.getcwdb()
110 |
111 | os.mkdir(path="directory")
| ^^^^^^^^
112 |
113 | os.mkdir(
|
help: Replace with `Path(...).mkdir()`
Safe fix
1 1 | import os
2 2 | import os.path
3 |+import pathlib
3 4 |
4 5 | p = "/foo"
5 6 | q = "bar"
--------------------------------------------------------------------------------
108 109 | os.getcwd()
109 110 | os.getcwdb()
110 111 |
111 |-os.mkdir(path="directory")
112 |+pathlib.Path("directory").mkdir()
112 113 |
113 114 | os.mkdir(
114 115 | # comment 1
PTH102 [*] `os.mkdir()` should be replaced by `Path.mkdir()`
--> full_name.py:113:1
|
111 | os.mkdir(path="directory")
112 |
113 | os.mkdir(
| ^^^^^^^^
114 | # comment 1
115 | "directory",
|
help: Replace with `Path(...).mkdir()`
Unsafe fix
1 1 | import os
2 2 | import os.path
3 |+import pathlib
3 4 |
4 5 | p = "/foo"
5 6 | q = "bar"
--------------------------------------------------------------------------------
110 111 |
111 112 | os.mkdir(path="directory")
112 113 |
113 |-os.mkdir(
114 |- # comment 1
115 |- "directory",
116 |- mode=0o777
117 |-)
114 |+pathlib.Path("directory").mkdir(mode=0o777)
118 115 |
119 116 | os.mkdir("directory", mode=0o777, dir_fd=1)
120 117 |
PTH103 [*] `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
--> full_name.py:121:1
|
119 | os.mkdir("directory", mode=0o777, dir_fd=1)
120 |
121 | os.makedirs("name", 0o777, exist_ok=False)
| ^^^^^^^^^^^
122 |
123 | os.makedirs("name", 0o777, False)
|
help: Replace with `Path(...).mkdir(parents=True)`
Safe fix
1 1 | import os
2 2 | import os.path
3 |+import pathlib
3 4 |
4 5 | p = "/foo"
5 6 | q = "bar"
--------------------------------------------------------------------------------
118 119 |
119 120 | os.mkdir("directory", mode=0o777, dir_fd=1)
120 121 |
121 |-os.makedirs("name", 0o777, exist_ok=False)
122 |+pathlib.Path("name").mkdir(0o777, exist_ok=False, parents=True)
122 123 |
123 124 | os.makedirs("name", 0o777, False)
124 125 |
PTH103 [*] `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
--> full_name.py:123:1
|
121 | os.makedirs("name", 0o777, exist_ok=False)
122 |
123 | os.makedirs("name", 0o777, False)
| ^^^^^^^^^^^
124 |
125 | os.makedirs(name="name", mode=0o777, exist_ok=False)
|
help: Replace with `Path(...).mkdir(parents=True)`
Safe fix
1 1 | import os
2 2 | import os.path
3 |+import pathlib
3 4 |
4 5 | p = "/foo"
5 6 | q = "bar"
--------------------------------------------------------------------------------
120 121 |
121 122 | os.makedirs("name", 0o777, exist_ok=False)
122 123 |
123 |-os.makedirs("name", 0o777, False)
124 |+pathlib.Path("name").mkdir(0o777, True, False)
124 125 |
125 126 | os.makedirs(name="name", mode=0o777, exist_ok=False)
126 127 |
PTH103 [*] `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
--> full_name.py:125:1
|
123 | os.makedirs("name", 0o777, False)
124 |
125 | os.makedirs(name="name", mode=0o777, exist_ok=False)
| ^^^^^^^^^^^
126 |
127 | os.makedirs("name", unknown_kwarg=True)
|
help: Replace with `Path(...).mkdir(parents=True)`
Safe fix
1 1 | import os
2 2 | import os.path
3 |+import pathlib
3 4 |
4 5 | p = "/foo"
5 6 | q = "bar"
--------------------------------------------------------------------------------
122 123 |
123 124 | os.makedirs("name", 0o777, False)
124 125 |
125 |-os.makedirs(name="name", mode=0o777, exist_ok=False)
126 |+pathlib.Path("name").mkdir(mode=0o777, exist_ok=False, parents=True)
126 127 |
127 128 | os.makedirs("name", unknown_kwarg=True)
PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
--> full_name.py:127:1
|
125 | os.makedirs(name="name", mode=0o777, exist_ok=False)
126 |
127 | os.makedirs("name", unknown_kwarg=True)
| ^^^^^^^^^^^
|
help: Replace with `Path(...).mkdir(parents=True)`

View File

@ -38,7 +38,7 @@ PTH101 `os.chmod()` should be replaced by `Path.chmod()`
|
help: Replace with `Path(...).chmod(...)`
PTH102 `os.mkdir()` should be replaced by `Path.mkdir()`
PTH102 [*] `os.mkdir()` should be replaced by `Path.mkdir()`
--> import_as.py:9:7
|
7 | a = foo_p.abspath(p)
@ -48,8 +48,25 @@ PTH102 `os.mkdir()` should be replaced by `Path.mkdir()`
10 | foo.makedirs(p)
11 | foo.rename(p)
|
help: Replace with `Path(...).mkdir()`
PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
Safe fix
1 1 | import os as foo
2 2 | import os.path as foo_p
3 |+import pathlib
3 4 |
4 5 | p = "/foo"
5 6 | q = "bar"
6 7 |
7 8 | a = foo_p.abspath(p)
8 9 | aa = foo.chmod(p)
9 |-aaa = foo.mkdir(p)
10 |+aaa = pathlib.Path(p).mkdir()
10 11 | foo.makedirs(p)
11 12 | foo.rename(p)
12 13 | foo.replace(p)
PTH103 [*] `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
--> import_as.py:10:1
|
8 | aa = foo.chmod(p)
@ -59,6 +76,24 @@ PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
11 | foo.rename(p)
12 | foo.replace(p)
|
help: Replace with `Path(...).mkdir(parents=True)`
Safe fix
1 1 | import os as foo
2 2 | import os.path as foo_p
3 |+import pathlib
3 4 |
4 5 | p = "/foo"
5 6 | q = "bar"
--------------------------------------------------------------------------------
7 8 | a = foo_p.abspath(p)
8 9 | aa = foo.chmod(p)
9 10 | aaa = foo.mkdir(p)
10 |-foo.makedirs(p)
11 |+pathlib.Path(p).mkdir(parents=True)
11 12 | foo.rename(p)
12 13 | foo.replace(p)
13 14 | foo.rmdir(p)
PTH104 `os.rename()` should be replaced by `Path.rename()`
--> import_as.py:11:1

View File

@ -39,7 +39,7 @@ PTH101 `os.chmod()` should be replaced by `Path.chmod()`
|
help: Replace with `Path(...).chmod(...)`
PTH102 `os.mkdir()` should be replaced by `Path.mkdir()`
PTH102 [*] `os.mkdir()` should be replaced by `Path.mkdir()`
--> import_from.py:11:7
|
9 | a = abspath(p)
@ -49,8 +49,26 @@ PTH102 `os.mkdir()` should be replaced by `Path.mkdir()`
12 | makedirs(p)
13 | rename(p)
|
help: Replace with `Path(...).mkdir()`
PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
Safe fix
2 2 | from os import remove, unlink, getcwd, readlink, stat
3 3 | from os.path import abspath, exists, expanduser, isdir, isfile, islink
4 4 | from os.path import isabs, join, basename, dirname, samefile, splitext
5 |+import pathlib
5 6 |
6 7 | p = "/foo"
7 8 | q = "bar"
8 9 |
9 10 | a = abspath(p)
10 11 | aa = chmod(p)
11 |-aaa = mkdir(p)
12 |+aaa = pathlib.Path(p).mkdir()
12 13 | makedirs(p)
13 14 | rename(p)
14 15 | replace(p)
PTH103 [*] `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
--> import_from.py:12:1
|
10 | aa = chmod(p)
@ -60,6 +78,25 @@ PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
13 | rename(p)
14 | replace(p)
|
help: Replace with `Path(...).mkdir(parents=True)`
Safe fix
2 2 | from os import remove, unlink, getcwd, readlink, stat
3 3 | from os.path import abspath, exists, expanduser, isdir, isfile, islink
4 4 | from os.path import isabs, join, basename, dirname, samefile, splitext
5 |+import pathlib
5 6 |
6 7 | p = "/foo"
7 8 | q = "bar"
--------------------------------------------------------------------------------
9 10 | a = abspath(p)
10 11 | aa = chmod(p)
11 12 | aaa = mkdir(p)
12 |-makedirs(p)
13 |+pathlib.Path(p).mkdir(parents=True)
13 14 | rename(p)
14 15 | replace(p)
15 16 | rmdir(p)
PTH104 `os.rename()` should be replaced by `Path.rename()`
--> import_from.py:13:1

View File

@ -39,7 +39,7 @@ PTH101 `os.chmod()` should be replaced by `Path.chmod()`
|
help: Replace with `Path(...).chmod(...)`
PTH102 `os.mkdir()` should be replaced by `Path.mkdir()`
PTH102 [*] `os.mkdir()` should be replaced by `Path.mkdir()`
--> import_from_as.py:16:7
|
14 | a = xabspath(p)
@ -49,8 +49,26 @@ PTH102 `os.mkdir()` should be replaced by `Path.mkdir()`
17 | xmakedirs(p)
18 | xrename(p)
|
help: Replace with `Path(...).mkdir()`
PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
Safe fix
7 7 | from os.path import isfile as xisfile, islink as xislink, isabs as xisabs
8 8 | from os.path import join as xjoin, basename as xbasename, dirname as xdirname
9 9 | from os.path import samefile as xsamefile, splitext as xsplitext
10 |+import pathlib
10 11 |
11 12 | p = "/foo"
12 13 | q = "bar"
13 14 |
14 15 | a = xabspath(p)
15 16 | aa = xchmod(p)
16 |-aaa = xmkdir(p)
17 |+aaa = pathlib.Path(p).mkdir()
17 18 | xmakedirs(p)
18 19 | xrename(p)
19 20 | xreplace(p)
PTH103 [*] `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
--> import_from_as.py:17:1
|
15 | aa = xchmod(p)
@ -60,6 +78,25 @@ PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
18 | xrename(p)
19 | xreplace(p)
|
help: Replace with `Path(...).mkdir(parents=True)`
Safe fix
7 7 | from os.path import isfile as xisfile, islink as xislink, isabs as xisabs
8 8 | from os.path import join as xjoin, basename as xbasename, dirname as xdirname
9 9 | from os.path import samefile as xsamefile, splitext as xsplitext
10 |+import pathlib
10 11 |
11 12 | p = "/foo"
12 13 | q = "bar"
--------------------------------------------------------------------------------
14 15 | a = xabspath(p)
15 16 | aa = xchmod(p)
16 17 | aaa = xmkdir(p)
17 |-xmakedirs(p)
18 |+pathlib.Path(p).mkdir(parents=True)
18 19 | xrename(p)
19 20 | xreplace(p)
20 21 | xrmdir(p)
PTH104 `os.rename()` should be replaced by `Path.rename()`
--> import_from_as.py:18:1

View File

@ -2,96 +2,6 @@ use ruff_macros::{ViolationMetadata, derive_message_formats};
use crate::Violation;
/// ## What it does
/// Checks for uses of `os.makedirs`.
///
/// ## Why is this bad?
/// `pathlib` offers a high-level API for path manipulation, as compared to
/// the lower-level API offered by `os`. When possible, using `Path` object
/// methods such as `Path.mkdir(parents=True)` can improve readability over the
/// `os` module's counterparts (e.g., `os.makedirs()`.
///
/// ## Examples
/// ```python
/// import os
///
/// os.makedirs("./nested/directory/")
/// ```
///
/// Use instead:
/// ```python
/// from pathlib import Path
///
/// Path("./nested/directory/").mkdir(parents=True)
/// ```
///
/// ## Known issues
/// While using `pathlib` can improve the readability and type safety of your code,
/// it can be less performant than the lower-level alternatives that work directly with strings,
/// especially on older versions of Python.
///
/// ## References
/// - [Python documentation: `Path.mkdir`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.mkdir)
/// - [Python documentation: `os.makedirs`](https://docs.python.org/3/library/os.html#os.makedirs)
/// - [PEP 428 The pathlib module object-oriented filesystem paths](https://peps.python.org/pep-0428/)
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
#[derive(ViolationMetadata)]
pub(crate) struct OsMakedirs;
impl Violation for OsMakedirs {
#[derive_message_formats]
fn message(&self) -> String {
"`os.makedirs()` should be replaced by `Path.mkdir(parents=True)`".to_string()
}
}
/// ## What it does
/// Checks for uses of `os.mkdir`.
///
/// ## Why is this bad?
/// `pathlib` offers a high-level API for path manipulation, as compared to
/// the lower-level API offered by `os`. When possible, using `Path` object
/// methods such as `Path.mkdir()` can improve readability over the `os`
/// module's counterparts (e.g., `os.mkdir()`).
///
/// ## Examples
/// ```python
/// import os
///
/// os.mkdir("./directory/")
/// ```
///
/// Use instead:
/// ```python
/// from pathlib import Path
///
/// Path("./directory/").mkdir()
/// ```
///
/// ## Known issues
/// While using `pathlib` can improve the readability and type safety of your code,
/// it can be less performant than the lower-level alternatives that work directly with strings,
/// especially on older versions of Python.
///
/// ## References
/// - [Python documentation: `Path.mkdir`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.mkdir)
/// - [Python documentation: `os.mkdir`](https://docs.python.org/3/library/os.html#os.mkdir)
/// - [PEP 428 The pathlib module object-oriented filesystem paths](https://peps.python.org/pep-0428/)
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
#[derive(ViolationMetadata)]
pub(crate) struct OsMkdir;
impl Violation for OsMkdir {
#[derive_message_formats]
fn message(&self) -> String {
"`os.mkdir()` should be replaced by `Path.mkdir()`".to_string()
}
}
/// ## What it does
/// Checks for uses of `os.stat`.
///