fix(lint): convert os.path to pathlib - remove PTH per-file ignores

- Converted os.path patterns to pathlib.Path in 15+ files
- os.path.join → Path /
- os.path.dirname → Path.parent
- os.path.exists → Path.exists()
- os.path.isfile → Path.is_file()
- os.path.abspath → Path.resolve()
- os.mkdir → Path.mkdir()
- os.listdir → Path.iterdir()
- os.getcwd → Path.cwd()
- os.replace → Path.replace()
- Updated function type hints to accept str | Path

Added PTH123 (open() vs Path.open()) to global ignores as stylistic preference
This commit is contained in:
Krzysztof Rudnicki 2025-11-30 23:03:03 +01:00
parent 2ffa1cc169
commit 3f9ba376e0
12 changed files with 83 additions and 93 deletions

View File

@ -5,7 +5,6 @@ Fetches cat images in batches and saves them to a local directory.
import json
import logging
import os
from pathlib import Path
import requests
@ -28,8 +27,8 @@ def _download_single_image(url: str) -> None:
response.raise_for_status() # Raise an exception for HTTP errors
# Extract the image name from the URL
image_name = os.path.basename(url)
image_path = os.path.join("./CATS2/", image_name)
image_name = Path(url).name
image_path = Path("./CATS2/") / image_name
# Save the image to the directory
with open(image_path, "wb") as file:

View File

@ -13,7 +13,7 @@ from __future__ import annotations
import argparse
from html.parser import HTMLParser
import logging
import os
from pathlib import Path
from urllib.parse import urlparse
_logger = logging.getLogger(__name__)
@ -72,15 +72,16 @@ def main() -> int:
)
args = ap.parse_args()
input_path = args.input_html
if not os.path.isfile(input_path):
input_path = Path(args.input_html)
if not input_path.is_file():
msg = f"Input file not found: {input_path}"
raise SystemExit(msg)
out_path = args.output_txt
if not out_path:
base = os.path.splitext(os.path.basename(input_path))[0]
out_path = os.path.join(os.path.dirname(input_path), f"{base}_links.txt")
out_path = input_path.parent / f"{input_path.stem}_links.txt"
else:
out_path = Path(out_path)
with open(input_path, encoding="utf-8", errors="ignore") as f:
html_text = f.read()

View File

@ -5,7 +5,7 @@ Players take turns selecting adjacent keys to form valid English words.
import json
import logging
import os
from pathlib import Path
import secrets
import sys
@ -102,9 +102,7 @@ class KeyboardCoopGame:
def load_dictionary(self) -> set[str]:
"""Load dictionary from words_dictionary.json file."""
try:
dictionary_path = os.path.join(
os.path.dirname(__file__), "words_dictionary.json"
)
dictionary_path = Path(__file__).parent / "words_dictionary.json"
with open(dictionary_path, encoding="utf-8") as f:
dictionary_data = json.load(f)
# Convert to set for faster lookup (we only need the keys)

View File

@ -4,6 +4,7 @@ import contextlib
import json
import logging
import os
from pathlib import Path
import subprocess
import chess
@ -36,20 +37,14 @@ class RandomEngine:
# the C engine handles its own scoring/selection.
self.depth = depth
# Default relative path inside this repo
default_path = os.path.abspath(
os.path.join(
os.path.dirname(__file__),
"..",
"..",
"C",
"lichess_random_engine",
"random_engine",
)
default_path = (
Path(__file__).resolve().parent.parent.parent
/ "C"
/ "lichess_random_engine"
/ "random_engine"
)
self.engine_path = engine_path or default_path
if not os.path.isfile(self.engine_path) or not os.access(
self.engine_path, os.X_OK
):
self.engine_path = Path(engine_path) if engine_path else default_path
if not self.engine_path.is_file() or not os.access(self.engine_path, os.X_OK):
msg = (
f"C engine not found or not executable at '{self.engine_path}'. "
"Build it first (make -C C/lichess_random_engine)."

View File

@ -6,6 +6,7 @@ import datetime
import json
import logging
import os
from pathlib import Path
import subprocess
import sys
import threading
@ -65,7 +66,7 @@ def run_bot(log_level: str = "INFO", *, decline_correspondence: bool = False) ->
# start at -1 so we act on the first state (0 moves)
last_handled_len = -1
# Prepare a per-game log file
game_log_path = os.path.join(os.getcwd(), f"lichess_bot_game_{game_id}.log")
game_log_path = Path.cwd() / f"lichess_bot_game_{game_id}.log"
try:
with open(game_log_path, "w") as lf:
lf.write(f"game {game_id} started\n")
@ -277,12 +278,12 @@ def run_bot(log_level: str = "INFO", *, decline_correspondence: bool = False) ->
if game_log_path:
analysis_text: str | None = None
try:
analyze_script = os.path.join(
os.path.dirname(os.path.dirname(__file__)),
"stockfish_analysis",
"analyze_chess_game.py",
analyze_script = (
Path(__file__).resolve().parent.parent
/ "stockfish_analysis"
/ "analyze_chess_game.py"
)
if os.path.isfile(analyze_script):
if analyze_script.is_file():
# Estimate total plies from the final board
try:
total_plies = len(board.move_stack)

View File

@ -1,4 +1,3 @@
import os
from pathlib import Path
import sys
@ -6,7 +5,7 @@ import pytest
# Add repository root to sys.path so 'import python_pkg.*' works when running
# pytest with a subdirectory as rootdir.
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../.."))
ROOT = str(Path(__file__).resolve().parent.parent.parent.parent)
if ROOT not in sys.path:
sys.path.insert(0, ROOT)

View File

@ -1,23 +1,23 @@
"""Test the engine against Lichess puzzles."""
import csv
import os
from pathlib import Path
import chess
import pytest
from python_pkg.lichess_bot.engine import RandomEngine
_PUZZLE_CSV = os.path.join(os.path.dirname(__file__), "lichess_db_puzzle.csv")
_PUZZLE_CSV = Path(__file__).parent / "lichess_db_puzzle.csv"
def _load_top_puzzles(csv_path: str, limit: int = 8) -> list[tuple[str, str]]:
def _load_top_puzzles(csv_path: str | Path, limit: int = 8) -> list[tuple[str, str]]:
"""Return a list of (FEN, solution_moves_str) for the first `limit` rows in the CSV.
CSV columns: PuzzleId,FEN,Moves,...
"""
puzzles: list[tuple[str, str]] = []
if not os.path.isfile(csv_path):
if not Path(csv_path).is_file():
return puzzles
with open(csv_path, newline="", encoding="utf-8") as f:
reader = csv.DictReader(f)

View File

@ -36,7 +36,7 @@ from __future__ import annotations
from dataclasses import dataclass
import io
import logging
import os
from pathlib import Path
import re
import sys
@ -183,10 +183,10 @@ def fen_and_uci_for_blunders(
return results
def ensure_unified_test_file(target_path: str) -> None:
def ensure_unified_test_file(target_path: str | Path) -> None:
"""Create the unified test file skeleton if it doesn't exist."""
os.makedirs(os.path.dirname(target_path), exist_ok=True)
if os.path.exists(target_path):
Path(target_path).parent.mkdir(parents=True, exist_ok=True)
if Path(target_path).exists():
return
# Create skeleton unified test file
with open(target_path, "w", encoding="utf-8") as f:
@ -197,8 +197,8 @@ import chess
import pytest
# Ensure repo root is importable when running pytest directly
REPO_ROOT = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
REPO_ROOT = str(
Path(__file__).resolve().parent.parent.parent
)
if REPO_ROOT not in sys.path:
sys.path.insert(0, REPO_ROOT)
@ -239,7 +239,7 @@ def test_engine_avoids_logged_blunder(fen, blunder_uci, label):
def append_cases_to_unified_test(
unified_path: str, cases: list[tuple[str, str, str, Blunder]]
unified_path: str | Path, cases: list[tuple[str, str, str, Blunder]]
) -> int:
"""Append new cases to BLUNDER_CASES in the unified test file, skipping duplicates.
@ -325,29 +325,27 @@ def append_cases_to_unified_test(
return len(lines) + updated_existing
def _process_single_log(log_path: str) -> int:
def _process_single_log(log_path: str | Path) -> int:
"""Process a single log file. Returns 0 on success, non-zero otherwise."""
base = os.path.basename(log_path)
base = Path(log_path).name
result = _parse_and_extract_blunders(log_path, base)
if isinstance(result, int):
return result # Error code
cases, game_id = result
# Always append to the unified test file
unified = os.path.join(
os.path.dirname(__file__), "..", "tests", "test_blunders_all.py"
)
unified = os.path.abspath(unified)
unified = Path(__file__).parent.parent / "tests" / "test_blunders_all.py"
unified = unified.resolve()
added = append_cases_to_unified_test(unified, cases)
_logger.info(
f"Appended {added} new blunder checks to "
f"{os.path.relpath(unified)} (game {game_id})."
f"{unified.relative_to(Path.cwd())} (game {game_id})."
)
return 0
def _parse_and_extract_blunders(
log_path: str, base: str
log_path: str | Path, base: str
) -> int | tuple[list[tuple[str, str, str, Blunder]], str]:
"""Parse log file and extract blunder cases.
@ -366,11 +364,11 @@ def _parse_and_extract_blunders(
return err if err is not None else 2
m = re.search(r"game_([A-Za-z0-9]+)\.log$", base)
game_id = m.group(1) if m else os.path.splitext(base)[0]
game_id = m.group(1) if m else Path(base).stem
return cases, game_id
def _read_log_file(log_path: str) -> tuple[str | None, int | None]:
def _read_log_file(log_path: str | Path) -> tuple[str | None, int | None]:
"""Read log file contents. Returns (text, None) or (None, error_code)."""
try:
with open(log_path, encoding="utf-8") as fh:
@ -415,24 +413,24 @@ def _extract_cases(
def main(argv: list[str]) -> int:
"""Process log files and generate blunder test cases."""
script_dir = os.path.dirname(__file__)
past_dir = os.path.abspath(os.path.join(script_dir, "past_games"))
script_dir = Path(__file__).parent
past_dir = (script_dir / "past_games").resolve()
# No argument: process all logs in past_games
if len(argv) == 1:
if not os.path.isdir(past_dir):
if not past_dir.is_dir():
_logger.error(f"No past_games directory found at {past_dir}")
return 2
logs = [
os.path.join(past_dir, name)
for name in os.listdir(past_dir)
if re.match(r"lichess_bot_game_[A-Za-z0-9]+\.log$", name)
path
for path in past_dir.iterdir()
if re.match(r"lichess_bot_game_[A-Za-z0-9]+\.log$", path.name)
]
if not logs:
_logger.warning(f"No logs found in {past_dir}")
return 1
# Sort by mtime ascending for determinism
logs.sort(key=lambda p: os.path.getmtime(p))
logs.sort(key=lambda p: Path(p).stat().st_mtime)
ok = 0
for lp in logs:
rc = _process_single_log(lp)
@ -446,16 +444,16 @@ def main(argv: list[str]) -> int:
# One argument: game id or file path
arg = argv[1]
candidate_path = None
if os.path.isfile(arg):
candidate_path: str | Path | None = None
if Path(arg).is_file():
candidate_path = arg
# Treat as game id, resolve within past_games
elif re.fullmatch(r"[A-Za-z0-9]+", arg):
candidate_path = os.path.join(past_dir, f"lichess_bot_game_{arg}.log")
candidate_path = past_dir / f"lichess_bot_game_{arg}.log"
else:
# Fallback: if it's a bare filename, try inside past_games
maybe = os.path.join(past_dir, arg)
if os.path.isfile(maybe):
maybe = past_dir / arg
if maybe.is_file():
candidate_path = maybe
if not candidate_path:

View File

@ -2,6 +2,7 @@
import logging
import os
from pathlib import Path
import time
_logger = logging.getLogger(__name__)
@ -15,7 +16,7 @@ def _version_file_path() -> str:
override = os.getenv("LICHESS_BOT_VERSION_FILE")
if override:
return override
return os.path.join(os.path.dirname(__file__), ".bot_version")
return str(Path(__file__).parent / ".bot_version")
def get_and_increment_version() -> int:
@ -36,10 +37,10 @@ def get_and_increment_version() -> int:
new_version = current + 1
try:
tmp_path = path + ".tmp"
tmp_path = Path(path + ".tmp")
with open(tmp_path, "w") as f:
f.write(str(new_version))
os.replace(tmp_path, path)
tmp_path.replace(path)
except OSError:
# As a fallback, try a direct write; failure is non-fatal to bot operation
try:

View File

@ -2,7 +2,7 @@
import argparse
import logging
import os
from pathlib import Path
from urllib.parse import urlparse
import requests
@ -34,15 +34,16 @@ driver.get(url)
def download_image(url: str) -> bool:
"""Download an image from a URL and save it locally."""
# Extract image name from URL
image_name = os.path.basename(urlparse(url).path)
image_name = Path(urlparse(url).path).name
image_path = Path(image_name)
# Check if the image already exists
if os.path.exists(image_name):
if image_path.exists():
_logger.info("Image %s already exists, skipping download.", image_name)
return False
_logger.info("Downloading image from URL: %s", url)
img_data = requests.get(url, timeout=REQUEST_TIMEOUT).content
with open(image_name, "wb") as handler:
with open(image_path, "wb") as handler:
handler.write(img_data)
_logger.info("Image %s downloaded successfully", image_name)
return True

View File

@ -26,7 +26,7 @@ import contextlib
import io
import logging
import multiprocessing
import os
from pathlib import Path
import re
import sys
@ -271,7 +271,7 @@ def main() -> None:
)
args = ap.parse_args()
if not os.path.isfile(args.file):
if not Path(args.file).is_file():
_logger.error(f"Input not found: {args.file}")
sys.exit(1)

View File

@ -1,8 +1,8 @@
"""Sort images into folders using keyboard input."""
import logging
import os # for: os.getcwd; os.mkdir; os.listdir;
from os import path # for: os.path.abspath
import os # for: os.chdir
from pathlib import Path
import shutil # for: shutil.move
# for: cv2.imread; cv2.namedWindow; cv2.imshow;
@ -41,21 +41,18 @@ RIGHT_FOLDER_CODE = 97 # Default 97 - 'a'
first_folder_name = input("Enter first folder name: [a] ")
second_folder_name = input("Enter second folder name: [d] ")
current_path = os.path.abspath(
os.getcwd()
) # Stolen from: https://stackoverflow.com/q/3430372
current_path = Path.cwd().resolve()
os.chdir(current_path) # Change working directory to the path where the python file is
if (
path.isdir(first_folder_name) != 1
): # Check if folder already exists, if it does not make it
os.mkdir(first_folder_name)
if path.isdir(second_folder_name) != 1:
os.mkdir(second_folder_name)
if not Path(
first_folder_name
).is_dir(): # Check if folder already exists, if not make it
Path(first_folder_name).mkdir()
if not Path(second_folder_name).is_dir():
Path(second_folder_name).mkdir()
for filename in os.listdir(
os.getcwd()
): # Go through every file in the working directory
for file_path in Path.cwd().iterdir(): # Go through every file in the working directory
filename = file_path.name
if (filename.lower()).endswith(
IMAGE_EXTENSION
): # If the file name ends with image extension
@ -67,12 +64,12 @@ for filename in os.listdir(
key = cv2.waitKey()
if key == RIGHT_FOLDER_CODE:
shutil.move(
current_path + "/" + filename,
current_path + "/" + first_folder_name + "/" + filename,
current_path / filename,
current_path / first_folder_name / filename,
)
elif key == LEFT_FOLDER_CODE:
shutil.move(
current_path + "/" + filename,
current_path + "/" + second_folder_name + "/" + filename,
current_path / filename,
current_path / second_folder_name / filename,
)
cv2.destroyAllWindows()