Skip to content

Commit 4265ee7

Browse files
authored
Enable Ruff flake8-use-pathlib (PTH) (#13795)
Port existing code to pathlib
1 parent 0eb44e5 commit 4265ee7

File tree

11 files changed

+102
-114
lines changed

11 files changed

+102
-114
lines changed

lib/ts_utils/paths.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
PYPROJECT_PATH: Final = TS_BASE_PATH / "pyproject.toml"
1313
REQUIREMENTS_PATH: Final = TS_BASE_PATH / "requirements-tests.txt"
14+
GITIGNORE_PATH: Final = TS_BASE_PATH / ".gitignore"
1415

1516
TESTS_DIR: Final = "@tests"
1617
TEST_CASES_DIR: Final = "test_cases"

lib/ts_utils/utils.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from __future__ import annotations
44

55
import functools
6-
import os
76
import re
87
import sys
98
import tempfile
@@ -16,7 +15,7 @@
1615
import pathspec
1716
from packaging.requirements import Requirement
1817

19-
from .paths import REQUIREMENTS_PATH, STDLIB_PATH, STUBS_PATH, TEST_CASES_DIR, allowlists_path, test_cases_path
18+
from .paths import GITIGNORE_PATH, REQUIREMENTS_PATH, STDLIB_PATH, STUBS_PATH, TEST_CASES_DIR, allowlists_path, test_cases_path
2019

2120
if TYPE_CHECKING:
2221
from _typeshed import OpenTextMode
@@ -215,7 +214,7 @@ def allowlists(distribution_name: str) -> list[str]:
215214
def NamedTemporaryFile(mode: OpenTextMode) -> TemporaryFileWrapper[str]: # noqa: N802
216215
def close(self: TemporaryFileWrapper[str]) -> None:
217216
TemporaryFileWrapper.close(self) # pyright: ignore[reportUnknownMemberType]
218-
os.remove(self.name)
217+
Path(self.name).unlink()
219218

220219
temp = tempfile.NamedTemporaryFile(mode, delete=False) # noqa: SIM115, TID251
221220
temp.close = MethodType(close, temp) # type: ignore[method-assign]
@@ -229,7 +228,7 @@ def close(self: TemporaryFileWrapper[str]) -> None:
229228

230229
@functools.cache
231230
def get_gitignore_spec() -> pathspec.PathSpec:
232-
with open(".gitignore", encoding="UTF-8") as f:
231+
with GITIGNORE_PATH.open(encoding="UTF-8") as f:
233232
return pathspec.GitIgnoreSpec.from_lines(f)
234233

235234

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ select = [
5454
"PGH", # pygrep-hooks
5555
"PIE", # flake8-pie
5656
"PL", # Pylint
57+
"PTH", # flake8-use-pathlib
5758
"RSE", # flake8-raise
5859
"RUF", # Ruff-specific and unused-noqa
5960
"SLOT", # flake8-slots
@@ -80,9 +81,6 @@ select = [
8081
"FURB187", # Use of assignment of `reversed` on list `{name}`
8182
# Used for lint.flake8-import-conventions.aliases
8283
"ICN001", # `{name}` should be imported as `{asname}`
83-
# Autofixable flake8-use-pathlib only
84-
"PTH201", # Do not pass the current directory explicitly to `Path`
85-
"PTH210", # Invalid suffix passed to `.with_suffix()`
8684
# PYI: only enable rules that have autofixes and that we always want to fix (even manually),
8785
# avoids duplicate # noqa with flake8-pyi
8886
"PYI009", # Empty body should contain `...`, not pass
@@ -167,6 +165,8 @@ ignore = [
167165
"PLR2004", # Magic value used in comparison, consider replacing `{value}` with a constant variable
168166
# Keep codeflow path separation explicit
169167
"PLR5501", # Use `elif` instead of `else` then `if`, to reduce indentation
168+
# Often just leads to redundant more verbose code when needing an actual str
169+
"PTH208", # Use `pathlib.Path.iterdir()` instead.
170170
# Allow FIXME
171171
"TD001", # Invalid TODO tag: `{tag}`
172172
# Git blame is sufficient

scripts/create_baseline_stubs.py

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,20 @@
1212

1313
import argparse
1414
import asyncio
15-
import glob
16-
import os.path
1715
import re
1816
import subprocess
1917
import sys
2018
import urllib.parse
2119
from http import HTTPStatus
2220
from importlib.metadata import distribution
21+
from pathlib import Path
2322

2423
import aiohttp
2524
import termcolor
2625

27-
PYRIGHT_CONFIG = "pyrightconfig.stricter.json"
26+
from ts_utils.paths import STDLIB_PATH, STUBS_PATH
27+
28+
PYRIGHT_CONFIG = Path("pyrightconfig.stricter.json")
2829

2930

3031
def search_pip_freeze_output(project: str, output: str) -> tuple[str, str] | None:
@@ -52,22 +53,22 @@ def get_installed_package_info(project: str) -> tuple[str, str] | None:
5253
return search_pip_freeze_output(project, r.stdout)
5354

5455

55-
def run_stubgen(package: str, output: str) -> None:
56+
def run_stubgen(package: str, output: Path) -> None:
5657
print(f"Running stubgen: stubgen -o {output} -p {package}")
5758
subprocess.run(["stubgen", "-o", output, "-p", package, "--export-less"], check=True)
5859

5960

60-
def run_stubdefaulter(stub_dir: str) -> None:
61+
def run_stubdefaulter(stub_dir: Path) -> None:
6162
print(f"Running stubdefaulter: stubdefaulter --packages {stub_dir}")
6263
subprocess.run(["stubdefaulter", "--packages", stub_dir], check=False)
6364

6465

65-
def run_black(stub_dir: str) -> None:
66+
def run_black(stub_dir: Path) -> None:
6667
print(f"Running Black: black {stub_dir}")
67-
subprocess.run(["pre-commit", "run", "black", "--files", *glob.iglob(f"{stub_dir}/**/*.pyi")], check=False)
68+
subprocess.run(["pre-commit", "run", "black", "--files", *stub_dir.rglob("*.pyi")], check=False)
6869

6970

70-
def run_ruff(stub_dir: str) -> None:
71+
def run_ruff(stub_dir: Path) -> None:
7172
print(f"Running Ruff: ruff check {stub_dir} --fix-only")
7273
subprocess.run([sys.executable, "-m", "ruff", "check", stub_dir, "--fix-only"], check=False)
7374

@@ -115,14 +116,14 @@ async def get_upstream_repo_url(project: str) -> str | None:
115116
return None
116117

117118

118-
def create_metadata(project: str, stub_dir: str, version: str) -> None:
119+
def create_metadata(project: str, stub_dir: Path, version: str) -> None:
119120
"""Create a METADATA.toml file."""
120121
match = re.match(r"[0-9]+.[0-9]+", version)
121122
if match is None:
122123
sys.exit(f"Error: Cannot parse version number: {version}")
123-
filename = os.path.join(stub_dir, "METADATA.toml")
124+
filename = stub_dir / "METADATA.toml"
124125
version = match.group(0)
125-
if os.path.exists(filename):
126+
if filename.exists():
126127
return
127128
metadata = f'version = "{version}.*"\n'
128129
upstream_repo_url = asyncio.run(get_upstream_repo_url(project))
@@ -135,13 +136,12 @@ def create_metadata(project: str, stub_dir: str, version: str) -> None:
135136
else:
136137
metadata += f'upstream_repository = "{upstream_repo_url}"\n'
137138
print(f"Writing {filename}")
138-
with open(filename, "w", encoding="UTF-8") as file:
139-
file.write(metadata)
139+
filename.write_text(metadata, encoding="UTF-8")
140140

141141

142-
def add_pyright_exclusion(stub_dir: str) -> None:
142+
def add_pyright_exclusion(stub_dir: Path) -> None:
143143
"""Exclude stub_dir from strict pyright checks."""
144-
with open(PYRIGHT_CONFIG, encoding="UTF-8") as f:
144+
with PYRIGHT_CONFIG.open(encoding="UTF-8") as f:
145145
lines = f.readlines()
146146
i = 0
147147
while i < len(lines) and not lines[i].strip().startswith('"exclude": ['):
@@ -167,7 +167,7 @@ def add_pyright_exclusion(stub_dir: str) -> None:
167167
third_party_excludes[-1] = last_line + "\n"
168168

169169
# Must use forward slash in the .json file
170-
line_to_add = f' "{stub_dir}",\n'.replace("\\", "/")
170+
line_to_add = f' "{stub_dir.as_posix()}",\n'
171171

172172
if line_to_add in third_party_excludes:
173173
print(f"{PYRIGHT_CONFIG} already up-to-date")
@@ -177,7 +177,7 @@ def add_pyright_exclusion(stub_dir: str) -> None:
177177
third_party_excludes.sort(key=str.lower)
178178

179179
print(f"Updating {PYRIGHT_CONFIG}")
180-
with open(PYRIGHT_CONFIG, "w", encoding="UTF-8") as f:
180+
with PYRIGHT_CONFIG.open("w", encoding="UTF-8") as f:
181181
f.writelines(before_third_party_excludes)
182182
f.writelines(third_party_excludes)
183183
f.writelines(after_third_party_excludes)
@@ -194,7 +194,7 @@ def main() -> None:
194194
parser.add_argument("--package", help="generate stubs for this Python package (default is autodetected)")
195195
args = parser.parse_args()
196196
project = args.project
197-
package = args.package
197+
package: str = args.package
198198

199199
if not re.match(r"[a-zA-Z0-9-_.]+$", project):
200200
sys.exit(f"Invalid character in project name: {project!r}")
@@ -214,7 +214,7 @@ def main() -> None:
214214
print(f'Using detected package "{package}" for project "{project}"', file=sys.stderr)
215215
print("Suggestion: Try again with --package argument if that's not what you wanted", file=sys.stderr)
216216

217-
if not os.path.isdir("stubs") or not os.path.isdir("stdlib"):
217+
if not STUBS_PATH.is_dir() or not STDLIB_PATH.is_dir():
218218
sys.exit("Error: Current working directory must be the root of typeshed repository")
219219

220220
# Get normalized project name and version of installed package.
@@ -226,9 +226,9 @@ def main() -> None:
226226
sys.exit(1)
227227
project, version = info
228228

229-
stub_dir = os.path.join("stubs", project)
230-
package_dir = os.path.join(stub_dir, package)
231-
if os.path.exists(package_dir):
229+
stub_dir = STUBS_PATH / project
230+
package_dir = stub_dir / package
231+
if package_dir.exists():
232232
sys.exit(f"Error: {package_dir} already exists (delete it first)")
233233

234234
run_stubgen(package, stub_dir)

scripts/sync_protobuf/_utils.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import sys
55
from collections.abc import Iterable
66
from http.client import HTTPResponse
7+
from pathlib import Path
78
from typing import TYPE_CHECKING
89
from urllib.request import urlopen
910
from zipfile import ZipFile
@@ -18,11 +19,11 @@
1819
MYPY_PROTOBUF_VERSION = mypy_protobuf__version__
1920

2021

21-
def download_file(url: str, destination: StrPath) -> None:
22+
def download_file(url: str, destination: Path) -> None:
2223
print(f"Downloading '{url}' to '{destination}'")
2324
resp: HTTPResponse
24-
with urlopen(url) as resp, open(destination, "wb") as file:
25-
file.write(resp.read())
25+
with urlopen(url) as resp:
26+
destination.write_bytes(resp.read())
2627

2728

2829
def extract_archive(archive_path: StrPath, destination: StrPath) -> None:

scripts/sync_protobuf/google_protobuf.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333

3434
def extract_python_version(file_path: Path) -> str:
3535
"""Extract the Python version from https://github.com/protocolbuffers/protobuf/blob/main/version.json ."""
36-
with open(file_path) as file:
36+
with file_path.open() as file:
3737
data: dict[str, Any] = json.load(file)
3838
# The root key will be the protobuf source code version
3939
version = next(iter(data.values()))["languages"]["python"]
@@ -47,7 +47,7 @@ def extract_proto_file_paths(temp_dir: Path) -> list[str]:
4747
as described in py_proto_library calls in
4848
https://github.com/protocolbuffers/protobuf/blob/main/python/dist/BUILD.bazel .
4949
"""
50-
with open(temp_dir / EXTRACTED_PACKAGE_DIR / "python" / "dist" / "BUILD.bazel") as file:
50+
with (temp_dir / EXTRACTED_PACKAGE_DIR / "python" / "dist" / "BUILD.bazel").open() as file:
5151
matched_lines = filter(None, (re.search(PROTO_FILE_PATTERN, line) for line in file))
5252
proto_files = [
5353
EXTRACTED_PACKAGE_DIR + "/src/google/protobuf/" + match.group(1).replace("compiler_", "compiler/") + ".proto"

scripts/sync_protobuf/tensorflow.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
from __future__ import annotations
88

9-
import os
109
import re
1110
import shutil
1211
import subprocess
@@ -72,21 +71,19 @@ def post_creation() -> None:
7271

7372
for path in STUBS_FOLDER.rglob("*_pb2.pyi"):
7473
print(f"Fixing imports in '{path}'")
75-
with open(path, encoding="utf-8") as file:
76-
filedata = file.read()
74+
filedata = path.read_text(encoding="utf-8")
7775

7876
# Replace the target string
7977
filedata = re.sub(TSL_IMPORT_PATTERN, "\\1tensorflow.tsl.", filedata)
8078
filedata = re.sub(XLA_IMPORT_PATTERN, "\\1tensorflow.compiler.xla.", filedata)
8179

8280
# Write the file out again
83-
with open(path, "w", encoding="utf-8") as file:
84-
file.write(filedata)
81+
path.write_text(filedata, encoding="utf-8")
8582

8683
print()
8784
for to_remove in PROTOS_TO_REMOVE:
8885
file_path = STUBS_FOLDER / "tensorflow" / to_remove
89-
os.remove(file_path)
86+
file_path.unlink()
9087
print(f"Removed '{file_path}'")
9188

9289

tests/check_typeshed_structure.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,11 +113,10 @@ def check_test_cases() -> None:
113113

114114
def check_no_symlinks() -> None:
115115
"""Check that there are no symlinks in the typeshed repository."""
116-
files = [os.path.join(root, file) for root, _, files in os.walk(".") for file in files]
116+
files = [Path(root, file) for root, _, files in os.walk(".") for file in files]
117117
no_symlink = "You cannot use symlinks in typeshed, please copy {} to its link."
118118
for file in files:
119-
_, ext = os.path.splitext(file)
120-
if ext == ".pyi" and os.path.islink(file):
119+
if file.suffix == ".pyi" and file.is_symlink():
121120
raise ValueError(no_symlink.format(file))
122121

123122

@@ -141,18 +140,18 @@ def _find_stdlib_modules() -> set[str]:
141140
modules = set[str]()
142141
for path, _, files in os.walk(STDLIB_PATH):
143142
for filename in files:
144-
base_module = ".".join(os.path.normpath(path).split(os.sep)[1:])
143+
base_module = ".".join(Path(path).parts[1:])
145144
if filename == "__init__.pyi":
146145
modules.add(base_module)
147146
elif filename.endswith(".pyi"):
148-
mod, _ = os.path.splitext(filename)
147+
mod = filename[:-4]
149148
modules.add(f"{base_module}.{mod}" if base_module else mod)
150149
return modules
151150

152151

153152
def check_metadata() -> None:
154153
"""Check that all METADATA.toml files are valid."""
155-
for distribution in os.listdir("stubs"):
154+
for distribution in os.listdir(STUBS_PATH):
156155
# This function does various sanity checks for METADATA.toml files
157156
read_metadata(distribution)
158157

tests/mypy_test.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ def match(path: Path, args: TestConfig) -> bool:
150150

151151
def add_files(files: list[Path], module: Path, args: TestConfig) -> None:
152152
"""Add all files in package or module represented by 'name' located in 'root'."""
153+
if module.name.startswith("."):
154+
return
153155
if module.is_file() and module.suffix == ".pyi":
154156
if match(module, args):
155157
files.append(module)
@@ -244,10 +246,8 @@ def add_third_party_files(distribution: str, files: list[Path], args: TestConfig
244246
seen_dists.add(distribution)
245247
seen_dists.update(r.name for r in typeshed_reqs)
246248
root = distribution_path(distribution)
247-
for name in os.listdir(root):
248-
if name.startswith("."):
249-
continue
250-
add_files(files, (root / name), args)
249+
for path in root.iterdir():
250+
add_files(files, path, args)
251251

252252

253253
class TestResult(NamedTuple):
@@ -295,7 +295,7 @@ def test_third_party_distribution(
295295
def test_stdlib(args: TestConfig) -> TestResult:
296296
files: list[Path] = []
297297
for file in STDLIB_PATH.iterdir():
298-
if file.name in ("VERSIONS", TESTS_DIR) or file.name.startswith("."):
298+
if file.name in ("VERSIONS", TESTS_DIR):
299299
continue
300300
add_files(files, file, args)
301301

@@ -525,15 +525,14 @@ def test_third_party_stubs(args: TestConfig, tempdir: Path) -> TestSummary:
525525

526526
def test_typeshed(args: TestConfig, tempdir: Path) -> TestSummary:
527527
print(f"*** Testing Python {args.version} on {args.platform}")
528-
stdlib_dir, stubs_dir = Path("stdlib"), Path("stubs")
529528
summary = TestSummary()
530529

531-
if stdlib_dir in args.filter or any(stdlib_dir in path.parents for path in args.filter):
530+
if STDLIB_PATH in args.filter or any(STDLIB_PATH in path.parents for path in args.filter):
532531
mypy_result, files_checked = test_stdlib(args)
533532
summary.register_result(mypy_result, files_checked)
534533
print()
535534

536-
if stubs_dir in args.filter or any(stubs_dir in path.parents for path in args.filter):
535+
if STUBS_PATH in args.filter or any(STUBS_PATH in path.parents for path in args.filter):
537536
tp_results = test_third_party_stubs(args, tempdir)
538537
summary.merge(tp_results)
539538
print()

0 commit comments

Comments
 (0)