Enable Ruff flake8-use-pathlib (PTH) (#13795)

Port existing code to pathlib
This commit is contained in:
Avasam
2025-05-05 12:59:43 -04:00
committed by GitHub
parent 0eb44e574c
commit 4265ee7c72
11 changed files with 101 additions and 113 deletions
+5 -6
View File
@@ -113,11 +113,10 @@ def check_test_cases() -> None:
def check_no_symlinks() -> None:
"""Check that there are no symlinks in the typeshed repository."""
files = [os.path.join(root, file) for root, _, files in os.walk(".") for file in files]
files = [Path(root, file) for root, _, files in os.walk(".") for file in files]
no_symlink = "You cannot use symlinks in typeshed, please copy {} to its link."
for file in files:
_, ext = os.path.splitext(file)
if ext == ".pyi" and os.path.islink(file):
if file.suffix == ".pyi" and file.is_symlink():
raise ValueError(no_symlink.format(file))
@@ -141,18 +140,18 @@ def _find_stdlib_modules() -> set[str]:
modules = set[str]()
for path, _, files in os.walk(STDLIB_PATH):
for filename in files:
base_module = ".".join(os.path.normpath(path).split(os.sep)[1:])
base_module = ".".join(Path(path).parts[1:])
if filename == "__init__.pyi":
modules.add(base_module)
elif filename.endswith(".pyi"):
mod, _ = os.path.splitext(filename)
mod = filename[:-4]
modules.add(f"{base_module}.{mod}" if base_module else mod)
return modules
def check_metadata() -> None:
"""Check that all METADATA.toml files are valid."""
for distribution in os.listdir("stubs"):
for distribution in os.listdir(STUBS_PATH):
# This function does various sanity checks for METADATA.toml files
read_metadata(distribution)
+7 -8
View File
@@ -150,6 +150,8 @@ def match(path: Path, args: TestConfig) -> bool:
def add_files(files: list[Path], module: Path, args: TestConfig) -> None:
"""Add all files in package or module represented by 'name' located in 'root'."""
if module.name.startswith("."):
return
if module.is_file() and module.suffix == ".pyi":
if match(module, args):
files.append(module)
@@ -244,10 +246,8 @@ def add_third_party_files(distribution: str, files: list[Path], args: TestConfig
seen_dists.add(distribution)
seen_dists.update(r.name for r in typeshed_reqs)
root = distribution_path(distribution)
for name in os.listdir(root):
if name.startswith("."):
continue
add_files(files, (root / name), args)
for path in root.iterdir():
add_files(files, path, args)
class TestResult(NamedTuple):
@@ -295,7 +295,7 @@ def test_third_party_distribution(
def test_stdlib(args: TestConfig) -> TestResult:
files: list[Path] = []
for file in STDLIB_PATH.iterdir():
if file.name in ("VERSIONS", TESTS_DIR) or file.name.startswith("."):
if file.name in ("VERSIONS", TESTS_DIR):
continue
add_files(files, file, args)
@@ -525,15 +525,14 @@ def test_third_party_stubs(args: TestConfig, tempdir: Path) -> TestSummary:
def test_typeshed(args: TestConfig, tempdir: Path) -> TestSummary:
print(f"*** Testing Python {args.version} on {args.platform}")
stdlib_dir, stubs_dir = Path("stdlib"), Path("stubs")
summary = TestSummary()
if stdlib_dir in args.filter or any(stdlib_dir in path.parents for path in args.filter):
if STDLIB_PATH in args.filter or any(STDLIB_PATH in path.parents for path in args.filter):
mypy_result, files_checked = test_stdlib(args)
summary.register_result(mypy_result, files_checked)
print()
if stubs_dir in args.filter or any(stubs_dir in path.parents for path in args.filter):
if STUBS_PATH in args.filter or any(STUBS_PATH in path.parents for path in args.filter):
tp_results = test_third_party_stubs(args, tempdir)
summary.merge(tp_results)
print()
+40 -45
View File
@@ -24,7 +24,6 @@ if sys.version_info >= (3, 13):
print("pytype does not support Python 3.13+ yet.", file=sys.stderr)
sys.exit(1)
import argparse
import importlib.metadata
import inspect
@@ -38,21 +37,22 @@ from pytype import config as pytype_config, load_pytd # type: ignore[import]
from pytype.imports import typeshed # type: ignore[import]
from ts_utils.metadata import read_dependencies
from ts_utils.paths import STDLIB_PATH, STUBS_PATH, TS_BASE_PATH
from ts_utils.utils import SupportedVersionsDict, parse_stdlib_versions_file, supported_versions_for_module
TYPESHED_SUBDIRS = ["stdlib", "stubs"]
TYPESHED_SUBDIRS = [STDLIB_PATH.absolute(), STUBS_PATH.absolute()]
TYPESHED_HOME = "TYPESHED_HOME"
EXCLUDE_LIST = TS_BASE_PATH / "tests" / "pytype_exclude_list.txt"
_LOADERS: dict[str, tuple[pytype_config.Options, load_pytd.Loader]] = {}
def main() -> None:
args = create_parser().parse_args()
typeshed_location = args.typeshed_location or os.getcwd()
subdir_paths = [os.path.join(typeshed_location, d) for d in TYPESHED_SUBDIRS]
check_subdirs_discoverable(subdir_paths)
typeshed_location = Path(args.typeshed_location) or Path.cwd()
check_subdirs_discoverable(TYPESHED_SUBDIRS)
old_typeshed_home = os.environ.get(TYPESHED_HOME)
os.environ[TYPESHED_HOME] = typeshed_location
files_to_test = determine_files_to_test(paths=args.files or subdir_paths)
os.environ[TYPESHED_HOME] = str(typeshed_location)
files_to_test = determine_files_to_test(paths=[Path(file) for file in args.files] or TYPESHED_SUBDIRS)
run_all_tests(files_to_test=files_to_test, print_stderr=args.print_stderr, dry_run=args.dry_run)
if old_typeshed_home is None:
del os.environ[TYPESHED_HOME]
@@ -75,12 +75,12 @@ def create_parser() -> argparse.ArgumentParser:
return parser
def run_pytype(*, filename: str, python_version: str, missing_modules: Iterable[str]) -> str | None:
def run_pytype(*, filename: StrPath, python_version: str, missing_modules: Iterable[str]) -> str | None:
"""Run pytype, returning the stderr if any."""
if python_version not in _LOADERS:
options = pytype_config.Options.create("", parse_pyi=True, python_version=python_version)
# For simplicity, pretends missing modules are part of the stdlib.
missing_modules = tuple(os.path.join("stdlib", m) for m in missing_modules)
missing_modules = tuple(str(STDLIB_PATH / m) for m in missing_modules)
loader = load_pytd.create_loader(options, missing_modules)
_LOADERS[python_version] = (options, loader)
options, loader = _LOADERS[python_version]
@@ -100,13 +100,13 @@ def _get_relative(filename: StrPath) -> Path:
filepath = Path(filename)
for d in TYPESHED_SUBDIRS:
try:
return filepath.absolute().relative_to(Path(d).absolute().parent)
return filepath.absolute().relative_to(d.parent)
except ValueError:
continue
raise ValueError(f"{filepath} not relative to {TYPESHED_SUBDIRS}")
def _get_module_name(filename: str) -> str:
def _get_module_name(filename: StrPath) -> str:
"""Convert a filename {subdir}/m.n/module/foo to module.foo."""
parts = _get_relative(filename).parts
if parts[0] == "stdlib":
@@ -117,13 +117,13 @@ def _get_module_name(filename: str) -> str:
return ".".join(module_parts).replace(".pyi", "").replace(".__init__", "")
def check_subdirs_discoverable(subdir_paths: list[str]) -> None:
def check_subdirs_discoverable(subdir_paths: Iterable[Path]) -> None:
for p in subdir_paths:
if not os.path.isdir(p):
if not p.is_dir():
raise SystemExit(f"Cannot find typeshed subdir at {p} (specify parent dir via --typeshed-location)")
def determine_files_to_test(*, paths: Sequence[str]) -> list[str]:
def determine_files_to_test(*, paths: Iterable[Path]) -> list[Path]:
"""Determine all files to test.
Checks for files in the pytype exclude list and for the stdlib VERSIONS file.
@@ -132,28 +132,25 @@ def determine_files_to_test(*, paths: Sequence[str]) -> list[str]:
ts = typeshed.Typeshed()
exclude_list = set(ts.read_blacklist())
stdlib_module_versions = parse_stdlib_versions_file()
files = []
for f in sorted(filenames):
if _get_relative(f).as_posix() in exclude_list:
continue
if not _is_supported_stdlib_version(stdlib_module_versions, f):
continue
files.append(f)
return files
return [
f
for f in sorted(filenames)
if _get_relative(f).as_posix() not in exclude_list and _is_supported_stdlib_version(stdlib_module_versions, f)
]
def find_stubs_in_paths(paths: Sequence[str]) -> list[str]:
filenames: list[str] = []
def find_stubs_in_paths(paths: Iterable[Path]) -> list[Path]:
filenames: list[Path] = []
for path in paths:
if os.path.isdir(path):
if path.is_dir():
for root, _, fns in os.walk(path):
filenames.extend(os.path.join(root, fn) for fn in fns if fn.endswith(".pyi"))
filenames.extend(Path(root, fn) for fn in fns if fn.endswith(".pyi"))
else:
filenames.append(path)
return filenames
def _is_supported_stdlib_version(module_versions: SupportedVersionsDict, filename: str) -> bool:
def _is_supported_stdlib_version(module_versions: SupportedVersionsDict, filename: StrPath) -> bool:
parts = _get_relative(filename).parts
if parts[0] != "stdlib":
return True
@@ -181,7 +178,7 @@ def _get_pkgs_associated_with_requirement(req_name: str) -> list[str]:
return sorted({package.removesuffix("-stubs") for package in packages})
def get_missing_modules(files_to_test: Sequence[str]) -> Iterable[str]:
def get_missing_modules(files_to_test: Iterable[Path]) -> Iterable[str]:
"""Get names of modules that should be treated as missing.
Some typeshed stubs depend on dependencies outside of typeshed. Since pytype
@@ -191,37 +188,35 @@ def get_missing_modules(files_to_test: Sequence[str]) -> Iterable[str]:
Similarly, pytype cannot parse files on its exclude list, so we also treat
those as missing.
"""
stub_distributions = set()
stub_distributions = set[str]()
for fi in files_to_test:
parts = fi.split(os.sep)
parts = fi.parts
try:
idx = parts.index("stubs")
except ValueError:
continue
stub_distributions.add(parts[idx + 1])
missing_modules = set()
for distribution in stub_distributions:
for external_req in read_dependencies(distribution).external_pkgs:
associated_packages = _get_pkgs_associated_with_requirement(external_req.name)
missing_modules.update(associated_packages)
missing_modules = {
associated_package
for distribution in stub_distributions
for external_req in read_dependencies(distribution).external_pkgs
for associated_package in _get_pkgs_associated_with_requirement(external_req.name)
}
test_dir = os.path.dirname(__file__)
exclude_list = os.path.join(test_dir, "pytype_exclude_list.txt")
with open(exclude_list) as f:
excluded_files = f.readlines()
for fi in excluded_files:
if not fi.startswith("stubs/"):
with EXCLUDE_LIST.open() as f:
for line in f:
if not line.startswith("stubs/"):
# Skips comments, empty lines, and stdlib files, which are in
# the exclude list because pytype has its own version.
continue
unused_stubs_prefix, unused_pkg, mod_path = fi.split("/", 2) # pyright: ignore[reportUnusedVariable]
missing_modules.add(os.path.splitext(mod_path)[0])
_ts_subdir, _distribution, module_path = line.strip().split("/", 2)
missing_modules.add(module_path.removesuffix(".pyi"))
return missing_modules
def run_all_tests(*, files_to_test: Sequence[str], print_stderr: bool, dry_run: bool) -> None:
bad = []
def run_all_tests(*, files_to_test: Sequence[Path], print_stderr: bool, dry_run: bool) -> None:
bad: list[tuple[StrPath, str, str]] = []
errors = 0
total_tests = len(files_to_test)
missing_modules = get_missing_modules(files_to_test)
+11 -14
View File
@@ -3,7 +3,6 @@ from __future__ import annotations
import argparse
import json
import os
import re
import subprocess
import sys
@@ -13,8 +12,8 @@ from pathlib import Path
from ts_utils.paths import TEST_CASES_DIR, test_cases_path
from ts_utils.utils import colored
_STRICTER_CONFIG_FILE = "pyrightconfig.stricter.json"
_TESTCASES_CONFIG_FILE = "pyrightconfig.testcases.json"
_STRICTER_CONFIG_FILE = Path("pyrightconfig.stricter.json")
_TESTCASES_CONFIG_FILE = Path("pyrightconfig.testcases.json")
_NPX_ERROR_PATTERN = r"error (runn|find)ing npx"
_NPX_ERROR_MESSAGE = colored("\nSkipping Pyright tests: npx is not installed or can't be run!", "yellow")
_SUCCESS = colored("Success", "green")
@@ -33,10 +32,9 @@ def _parse_jsonc(json_text: str) -> str:
return valid_json
def _get_strict_params(stub_path: str) -> list[str]:
with open(_STRICTER_CONFIG_FILE, encoding="UTF-8") as file:
data = json.loads(_parse_jsonc(file.read()))
lower_stub_path = stub_path.lower()
def _get_strict_params(stub_path: Path) -> list[str | Path]:
data = json.loads(_parse_jsonc(_STRICTER_CONFIG_FILE.read_text(encoding="UTF-8")))
lower_stub_path = stub_path.as_posix().lower()
if any(lower_stub_path == stub.lower() for stub in data["exclude"]):
return []
return ["-p", _STRICTER_CONFIG_FILE]
@@ -60,23 +58,22 @@ def main() -> None:
)
parser.add_argument("path", help="Path of the stub to test in format <folder>/<stub>, from the root of the project.")
args = parser.parse_args()
path: str = args.path
path = Path(args.path)
run_stubtest: bool = args.run_stubtest
python_version: str = args.python_version
path_tokens = Path(path).parts
if len(path_tokens) != 2:
if len(path.parts) != 2:
parser.error("'path' argument should be in format <folder>/<stub>.")
folder, stub = path_tokens
folder, stub = path.parts
if folder not in {"stdlib", "stubs"}:
parser.error("Only the 'stdlib' and 'stubs' folders are supported.")
if not os.path.exists(path):
if not path.exists():
parser.error(f"{path=} does not exist.")
stubtest_result: subprocess.CompletedProcess[bytes] | None = None
pytype_result: subprocess.CompletedProcess[bytes] | None = None
print("\nRunning pre-commit...")
pre_commit_result = subprocess.run(["pre-commit", "run", "--files", *Path(path).rglob("*")], check=False)
pre_commit_result = subprocess.run(["pre-commit", "run", "--files", *path.rglob("*")], check=False)
print("\nRunning check_typeshed_structure.py...")
check_structure_result = subprocess.run([sys.executable, "tests/check_typeshed_structure.py"], check=False)
@@ -141,7 +138,7 @@ def main() -> None:
regr_test_returncode = 0
else:
print(f"\nRunning Pyright regression tests for Python {python_version}...")
command = [
command: list[str | Path] = [
sys.executable,
"tests/pyright_test.py",
str(cases_path),