refactor: rename folders to fix N999, INP001, S311 linting rules

- Rename PYTHON/ to python_pkg/ (fix N999 uppercase folder)
- Rename camelCase folders to snake_case:
  - randomJPG -> random_jpg
  - tagDivider -> tag_divider
  - downloadCats -> download_cats
  - keyboardCoop -> keyboard_coop
  - extractLinks -> extract_links
  - scapeWebsite -> scrape_website
- Rename camelCase files:
  - generateJpeg.py -> generate_jpeg.py
  - tagDivider.py -> tag_divider.py
- Rename poker-modifier-app to poker_modifier_app (fix INP001)
- Add __init__.py to poker_modifier_app
- Replace random module with secrets.SystemRandom (fix S311)
- Fix S110 try-except-pass with contextlib.suppress
- Update all imports and config references
This commit is contained in:
Krzysztof kuhy Rudnicki 2025-11-30 21:20:17 +01:00
parent 6b62f6bea9
commit ec8861d01c
78 changed files with 96 additions and 107 deletions

View File

@ -4,15 +4,15 @@ on:
push:
branches: [main]
paths:
- "PYTHON/lichess_bot/**"
- "PYTHON/**"
- "python_pkg/lichess_bot/**"
- "python_pkg/**"
- "tests/**"
- "requirements.txt"
pull_request:
branches: [main]
paths:
- "PYTHON/lichess_bot/**"
- "PYTHON/**"
- "python_pkg/lichess_bot/**"
- "python_pkg/**"
- "tests/**"
- "requirements.txt"

View File

@ -0,0 +1 @@
"""Poker modifier application package."""

View File

@ -1,12 +1,15 @@
"""Texas Hold'em poker game modifier application."""
import logging
import random
import secrets
import tkinter as tk
from tkinter import ttk
logging.basicConfig(level=logging.INFO)
# Use cryptographically secure random number generator
_rng = secrets.SystemRandom()
class PokerModifierApp:
"""GUI application for poker game modifiers."""
@ -850,7 +853,7 @@ class PokerModifierApp:
modifier_chance = self.prob_var.get()
# Determine if modifier should be applied
random_value = random.random() * 100
random_value = _rng.random() * 100
should_apply_modifier = random_value < modifier_chance
if should_apply_modifier:
@ -886,7 +889,7 @@ class PokerModifierApp:
bg_color = "#2d4a2d" # Green for normal
# Select random modifier from appropriate pool
selected_modifier = random.choice(modifier_pool).copy()
selected_modifier = _rng.choice(modifier_pool).copy()
# Special handling for Steel Cards - randomize the rank
if selected_modifier["name"] == "Steel Cards":
@ -905,7 +908,7 @@ class PokerModifierApp:
"King",
"Ace",
]
steel_rank = random.choice(ranks)
steel_rank = _rng.choice(ranks)
selected_modifier["description"] = selected_modifier["description"].format(
steel_rank=steel_rank
)

View File

@ -64,112 +64,96 @@ unfixable = []
"D103", # Allow missing function docstring
"PTH", # Allow os.path in conftest
]
# N999 ignores for PYTHON folder naming (uppercase folder)
"PYTHON/**/__init__.py" = [
"N999", # Invalid module name due to PYTHON folder naming
]
"PYTHON/randomJPG/generateJpeg.py" = [
"N999", # camelCase filename preserved for compatibility
"S311", # Random for image generation, not crypto
"python_pkg/random_jpg/generate_jpeg.py" = [
"PTH", # os.path patterns in existing code
"LOG015", # Root logger in script
"G004", # f-strings in logging
]
"PYTHON/tagDivider/tagDivider.py" = [
"N999", # camelCase filename preserved for compatibility
"python_pkg/tag_divider/tag_divider.py" = [
"PTH", # os.path patterns in existing code
"LOG015", # Root logger in script
]
"poker-modifier-app/*.py" = [
"INP001", # Folder has hyphen, can't be a valid Python package
"S311", # Random for game mechanics, not crypto
"poker_modifier_app/*.py" = [
"LOG015", # Root logger in script
"G004", # f-strings in logging
]
"poker-modifier-app/poker_modifier_app.py" = [
"INP001", # Folder has hyphen, can't be a valid Python package
"poker_modifier_app/poker_modifier_app.py" = [
"FBT003", # Boolean positional values in tkinter API calls
"S311", # Random for game mechanics, not crypto
"LOG015", # Root logger in app
"G004", # f-strings in logging
]
"PYTHON/downloadCats/generate_cats.py" = [
"python_pkg/download_cats/generate_cats.py" = [
"PERF203", # Try-except needed for download resilience in loop
"PTH", # os.path patterns in existing code
"LOG015", # Root logger in script
"G004", # f-strings in logging
]
"PYTHON/lichess_bot/main.py" = [
"python_pkg/lichess_bot/main.py" = [
"C901", # Complex functions handling game lifecycle (run_bot, handle_game)
"PERF203", # Try-except needed for stream/move error handling in loops
"PLR0912", # Complex nested game event handling with many branches
"PLR0915", # Long function handling complete game lifecycle
"BLE001", # Blind except for resilient bot operation
"S110", # Try-except-pass for non-critical error handling
"S603", # Subprocess call for analysis script
"PTH", # os.path patterns in existing code
"LOG015", # Root logger in bot
"G004", # f-strings in logging
"TRY301", # Raise in try block for null checks on subprocess streams
]
"PYTHON/lichess_bot/engine.py" = [
"python_pkg/lichess_bot/engine.py" = [
"BLE001", # Blind except for engine error handling
"S110", # Try-except-pass for optional features
"S603", # Subprocess for engine communication
"PTH", # os.path patterns
"LOG015", # Root logger for debug messages
]
"PYTHON/lichess_bot/lichess_api.py" = [
"python_pkg/lichess_bot/lichess_api.py" = [
"BLE001", # Blind except for API resilience
"LOG015", # Root logger in API client
"G004", # f-strings in logging
]
"PYTHON/lichess_bot/utils.py" = [
"python_pkg/lichess_bot/utils.py" = [
"BLE001", # Blind except for file operations
"PTH", # os.path patterns
"LOG015", # Root logger
"G004", # f-strings in logging
]
"PYTHON/lichess_bot/tools/generate_blunder_tests.py" = [
"python_pkg/lichess_bot/tools/generate_blunder_tests.py" = [
"BLE001", # Blind except for test generation
"PTH", # os.path patterns in tool
"LOG015", # Root logger in tool
"G004", # f-strings in logging
]
"PYTHON/stockfish_analysis/analyze_chess_game.py" = [
"python_pkg/stockfish_analysis/analyze_chess_game.py" = [
"C901", # Complex main() with many argument combinations and analysis modes
"PLR0912", # Complex main() with many argument combinations and analysis modes
"PLR0915", # Long main() handling complete analysis workflow
"BLE001", # Blind except for engine configuration
"S110", # Try-except-pass for optional engine features
"PTH", # os.path patterns
"LOG015", # Root logger in analysis tool
"G004", # f-strings in logging
]
"PYTHON/randomize_numbers/random_digits.py" = [
"python_pkg/randomize_numbers/random_digits.py" = [
"PERF203", # Try-except needed for parsing user input in loop
"S311", # Random for number randomization, not crypto
"LOG015", # Root logger in script
"G004", # f-strings in logging
"TRY301", # Raise in try block for input validation
]
"PYTHON/keyboardCoop/main.py" = [
"python_pkg/keyboard_coop/main.py" = [
"FBT003", # Boolean positional values in pygame API calls (e.g., font.render)
"PTH", # os.path patterns
"LOG015", # Root logger in script
]
"PYTHON/screen_locker/screen_lock.py" = [
"python_pkg/screen_locker/screen_lock.py" = [
"FBT003", # Boolean positional values in tkinter API calls
"PTH", # os.path patterns
"LOG015", # Root logger in app
"G004", # f-strings in logging
]
"PYTHON/scapeWebsite/scrape_comics.py" = [
"python_pkg/scrape_website/scrape_comics.py" = [
"BLE001", # Blind except for web scraping resilience
"PTH", # os.path patterns
"LOG015", # Root logger in script
"G004", # f-strings in logging
]
"PYTHON/extractLinks/main.py" = [
"python_pkg/extract_links/main.py" = [
"PTH", # os.path patterns
"LOG015", # Root logger in script
"G004", # f-strings in logging
@ -181,7 +165,7 @@ convention = "google" # Use Google docstring convention
[tool.ruff.lint.isort]
force-single-line = false
force-sort-within-sections = true
known-first-party = ["PYTHON"]
known-first-party = ["python_pkg"]
[tool.ruff.lint.flake8-quotes]
docstring-quotes = "double"

View File

@ -6,13 +6,16 @@ Players take turns selecting adjacent keys to form valid English words.
import json
import logging
import os
import random
import secrets
import sys
import pygame
logging.basicConfig(level=logging.INFO)
# Use cryptographically secure random number generator
_rng = secrets.SystemRandom()
# Initialize Pygame
pygame.init()
@ -202,7 +205,7 @@ class KeyboardCoopGame:
"""Generate a random keyboard layout and calculate adjacencies."""
# All 26 letters
all_letters = list("abcdefghijklmnopqrstuvwxyz")
random.shuffle(all_letters)
_rng.shuffle(all_letters)
# Create random layout with same structure as QWERTY (10-9-7)
self.keyboard_layout = [

View File

@ -1,6 +1,8 @@
"""Chess engine wrapper for the C-based random/scoring engine."""
import contextlib
import json
import logging
import os
import subprocess
@ -144,12 +146,10 @@ class RandomEngine:
# best move
chosen = data.get("chosen_move")
if isinstance(chosen, str):
try:
with contextlib.suppress(Exception):
bm = chess.Move.from_uci(chosen)
if bm in board.legal_moves:
best_move = bm
except Exception:
best_move = None
# Store compact explanations for debugging
cand_expl = json.dumps(analyze, ensure_ascii=False)
best_expl = json.dumps(
@ -160,7 +160,6 @@ class RandomEngine:
ensure_ascii=False,
)
except Exception:
# Leave defaults with raw output text
pass
logging.debug("Failed to parse engine JSON output")
return cand_score, cand_expl, best_move, best_expl

View File

@ -1,6 +1,7 @@
"""Main entry point for the Lichess bot."""
import argparse
import contextlib
import datetime
import json
import logging
@ -12,9 +13,9 @@ import threading
import chess
import chess.pgn
from PYTHON.lichess_bot.engine import RandomEngine
from PYTHON.lichess_bot.lichess_api import LichessAPI
from PYTHON.lichess_bot.utils import backoff_sleep, get_and_increment_version
from python_pkg.lichess_bot.engine import RandomEngine
from python_pkg.lichess_bot.lichess_api import LichessAPI
from python_pkg.lichess_bot.utils import backoff_sleep, get_and_increment_version
def run_bot(log_level: str = "INFO", *, decline_correspondence: bool = False) -> None:
@ -91,7 +92,7 @@ def run_bot(log_level: str = "INFO", *, decline_correspondence: bool = False) ->
white_name = event["white"].get("name") or white_id or "?"
black_name = event["black"].get("name") or black_id or "?"
# Set site and date if available
try:
with contextlib.suppress(Exception):
# Lichess event may include 'createdAt' ms epoch
created_ms = event.get("createdAt") or event.get(
"createdAtDate"
@ -100,8 +101,6 @@ def run_bot(log_level: str = "INFO", *, decline_correspondence: bool = False) ->
game_date_iso = datetime.datetime.fromtimestamp(
int(created_ms) / 1000, tz=datetime.timezone.utc
).strftime("%Y.%m.%d")
except Exception:
pass
site_url = f"https://lichess.org/{game_id}"
me = api.get_my_user_id()
if me == white_id:
@ -241,7 +240,7 @@ def run_bot(log_level: str = "INFO", *, decline_correspondence: bool = False) ->
if game_log_path:
game = chess.pgn.Game.from_board(board)
# Record the bot version in the PGN headers
try:
with contextlib.suppress(Exception):
game.headers["BotVersion"] = f"v{bot_version}"
if site_url:
game.headers["Site"] = site_url
@ -251,8 +250,6 @@ def run_bot(log_level: str = "INFO", *, decline_correspondence: bool = False) ->
game.headers["White"] = white_name
if black_name:
game.headers["Black"] = black_name
except Exception:
pass
with open(game_log_path, "a") as lf:
lf.write("\nPGN:\n")
exporter = chess.pgn.StringExporter(
@ -293,9 +290,9 @@ def run_bot(log_level: str = "INFO", *, decline_correspondence: bool = False) ->
lines: list[str] = []
ply_line_re = __import__("re").compile(r"^\s*(\d+)\s")
# Read stdout line by line
if proc.stdout is None:
msg = "subprocess stdout is None"
raise RuntimeError(msg)
# stdout/stderr are guaranteed non-None with PIPE
assert proc.stdout is not None # noqa: S101
assert proc.stderr is not None # noqa: S101
for line in proc.stdout:
lines.append(line)
m = ply_line_re.match(line)
@ -321,9 +318,6 @@ def run_bot(log_level: str = "INFO", *, decline_correspondence: bool = False) ->
)
# Capture any remaining stderr and ensure process ends
if proc.stderr is None:
msg = "subprocess stderr is None"
raise RuntimeError(msg)
stderr_text = proc.stderr.read() or ""
ret = proc.wait()
analysis_text = "".join(lines)

View File

@ -4,7 +4,7 @@ import sys
import pytest
# Add repository root to sys.path so 'import PYTHON.*' works when running
# 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__), "../../.."))
if ROOT not in sys.path:

View File

@ -6,7 +6,7 @@ import os
import chess
import pytest
from PYTHON.lichess_bot.engine import RandomEngine
from python_pkg.lichess_bot.engine import RandomEngine
def _load_top_puzzles(csv_path: str, limit: int = 8) -> list[tuple[str, str]]:

View File

@ -2,7 +2,7 @@
import pytest
from PYTHON.lichess_bot.utils import backoff_sleep
from python_pkg.lichess_bot.utils import backoff_sleep
def test_backoff_sleep_increments_and_caps(monkeypatch: pytest.MonkeyPatch) -> None:

View File

@ -4,7 +4,7 @@ from pathlib import Path
import pytest
from PYTHON.lichess_bot.utils import get_and_increment_version
from python_pkg.lichess_bot.utils import get_and_increment_version
def test_version_file_increments_and_persists(

View File

@ -16,17 +16,17 @@ Where logs are loaded from:
Usage examples:
# Process all logs in tools/past_games
python PYTHON/lichess_bot/tools/generate_blunder_tests.py
python python_pkg/lichess_bot/tools/generate_blunder_tests.py
# Process a specific game by id from tools/past_games
python PYTHON/lichess_bot/tools/generate_blunder_tests.py OVmR29MI
python python_pkg/lichess_bot/tools/generate_blunder_tests.py OVmR29MI
# Process an explicit file path
python PYTHON/lichess_bot/tools/generate_blunder_tests.py \
python python_pkg/lichess_bot/tools/generate_blunder_tests.py \
/path/to/lichess_bot_game_xxxxx.log
It will create files like:
PYTHON/lichess_bot/tests/test_blunders_<gameid>.py
python_pkg/lichess_bot/tests/test_blunders_<gameid>.py
Dependencies: python-chess, pytest (already in requirements.txt)
"""
@ -203,7 +203,7 @@ REPO_ROOT = os.path.dirname(
if REPO_ROOT not in sys.path:
sys.path.insert(0, REPO_ROOT)
from PYTHON.lichess_bot.engine import RandomEngine # noqa: E402
from python_pkg.lichess_bot.engine import RandomEngine # noqa: E402
BLUNDER_CASES = [
]

View File

@ -5,12 +5,15 @@ from dataclasses import dataclass
from datetime import datetime, timezone
import logging
import os
import random
import secrets
from PIL import Image
logging.basicConfig(level=logging.INFO)
# Use cryptographically secure random number generator
_rng = secrets.SystemRandom()
MAX_IMAGE_SIZE = 1000
@ -57,7 +60,7 @@ def generate_bloated_jpeg(config: ImageConfig, image_index: int, folder: str) ->
# of random colors from the list
for y in range(0, config.size, config.block_size):
for x in range(0, config.size, config.block_size):
color = random.choice(rgb_colors)
color = _rng.choice(rgb_colors)
for i in range(config.block_size):
for j in range(config.block_size):
pixels[x + i, y + j] = color

View File

@ -2,12 +2,15 @@
import contextlib
import logging
import random
import re
import secrets
import sys
logging.basicConfig(level=logging.INFO)
# Use cryptographically secure random number generator
_rng = secrets.SystemRandom()
DEFAULT_MIN_PERCENTAGE = 1
DEFAULT_MAX_PERCENTAGE = 20
@ -20,8 +23,8 @@ def randomize_numbers(
"""Apply random percentage variation to a list of numbers."""
randomized_numbers = []
for number in numbers:
percentage = random.uniform(min_percentage, max_percentage) / 100
if random.choice([True, False]):
percentage = _rng.uniform(min_percentage, max_percentage) / 100
if _rng.choice([True, False]):
new_number = number + (number * percentage)
else:
new_number = number - (number * percentage)
@ -60,16 +63,17 @@ if __name__ == "__main__":
)
sys.exit(1)
input_string = " ".join(sys.argv[1:])
numbers, decimal_counts = parse_input(input_string)
if len(numbers) == 0:
logging.error("No valid numbers provided.")
sys.exit(1)
min_percentage = DEFAULT_MIN_PERCENTAGE
max_percentage = DEFAULT_MAX_PERCENTAGE
try:
input_string = " ".join(sys.argv[1:])
numbers, decimal_counts = parse_input(input_string)
min_percentage = DEFAULT_MIN_PERCENTAGE
max_percentage = DEFAULT_MAX_PERCENTAGE
if len(numbers) == 0:
msg = "No valid numbers provided."
raise ValueError(msg)
if len(sys.argv) > len(numbers) + 1:
with contextlib.suppress(ValueError):
min_percentage = float(sys.argv[len(numbers) + 1])

View File

@ -2,7 +2,7 @@
"""Analyze a chess game's moves using a local Stockfish engine and rate each move.
Usage:
python3 PYTHON/analyze_chess_game.py <path-to-file>
python3 python_pkg/analyze_chess_game.py <path-to-file>
[--engine stockfish]
[--time 0.5 | --depth 20]
[--threads auto|N]
@ -11,7 +11,7 @@ Usage:
[--last-move-only]
Notes:
- Requires python-chess. Install from PYTHON/stockfish_analysis/requirements.txt
- Requires python-chess. Install from python_pkg/stockfish_analysis/requirements.txt
- The input file can be a pure PGN or a log file containing a PGN section.
- The script tries to locate the PGN by looking for a 'PGN:' marker,
PGN tags '[...]', or a move list starting with '1.'.
@ -41,7 +41,7 @@ try:
import chess.pgn
except Exception: # pragma: no cover
logging.exception("Missing dependency. Please install python-chess:")
logging.exception(" pip install -r PYTHON/stockfish_analysis/requirements.txt")
logging.exception(" pip install -r python_pkg/stockfish_analysis/requirements.txt")
raise
# Memory configuration constants
@ -177,22 +177,20 @@ def _parse_hash_mb(value: str) -> int | None:
def _detect_total_mem_mb() -> int | None:
# Prefer psutil if available
if psutil is not None:
try:
with contextlib.suppress(Exception):
return int(psutil.virtual_memory().total // (1024 * 1024))
except Exception:
pass
# Fallback approach for Linux systems using proc meminfo.
try:
with open("/proc/meminfo", encoding="utf-8", errors="ignore") as f:
for line in f:
if line.startswith("MemTotal:"):
parts = line.split()
if len(parts) >= MEMINFO_PARTS_MIN and parts[1].isdigit():
# Value is in kB
kb = int(parts[1])
return kb // 1024
except Exception:
pass
with (
contextlib.suppress(Exception),
open("/proc/meminfo", encoding="utf-8", errors="ignore") as f,
):
for line in f:
if line.startswith("MemTotal:"):
parts = line.split()
if len(parts) >= MEMINFO_PARTS_MIN and parts[1].isdigit():
# Value is in kB
kb = int(parts[1])
return kb // 1024
return None
@ -320,7 +318,7 @@ def main() -> None:
wanted_threads = max(wanted_threads, min_thr)
engine.configure({"Threads": int(wanted_threads)})
except Exception:
pass
logging.debug("Failed to configure Threads option")
# Configure hash table size in MB.
if "Hash" in options:
@ -338,7 +336,7 @@ def main() -> None:
target_hash = max(target_hash, min_hash)
engine.configure({"Hash": int(target_hash)})
except Exception:
pass
logging.debug("Failed to configure Hash option")
# MultiPV
effective_mpv = max(1, int(args.multipv))
@ -349,7 +347,7 @@ def main() -> None:
effective_mpv = min(effective_mpv, max_mpv)
engine.configure({"MultiPV": int(effective_mpv)})
except Exception:
pass
logging.debug("Failed to configure MultiPV option")
# Enable NNUE if the option exists
for nnue_key in ("Use NNUE", "UseNNUE"):