diff --git a/PYTHON/downloadCats/generate_cats.py b/PYTHON/downloadCats/generate_cats.py index 8af095a..b6a59b9 100644 --- a/PYTHON/downloadCats/generate_cats.py +++ b/PYTHON/downloadCats/generate_cats.py @@ -1,3 +1,8 @@ +"""Download cat images from TheCatAPI. + +Fetches cat images in batches and saves them to a local directory. +""" + import json import logging import os diff --git a/PYTHON/extractLinks/main.py b/PYTHON/extractLinks/main.py index 6923b73..f659b90 100755 --- a/PYTHON/extractLinks/main.py +++ b/PYTHON/extractLinks/main.py @@ -55,6 +55,7 @@ def extract_hosts_from_html(html_text: str) -> list[str]: def main() -> int: + """Parse command-line arguments and extract hosts from an HTML file.""" ap = argparse.ArgumentParser( description="Extract hosts from hrefs in an HTML file." ) diff --git a/PYTHON/extractLinks/tests/test_main.py b/PYTHON/extractLinks/tests/test_main.py index e3cf4a4..ba26a00 100644 --- a/PYTHON/extractLinks/tests/test_main.py +++ b/PYTHON/extractLinks/tests/test_main.py @@ -1,3 +1,5 @@ +"""Unit tests for link extraction functionality.""" + from pathlib import Path import subprocess import sys @@ -10,10 +12,12 @@ SCRIPT = ROOT / "main.py" def read_lines(p: Path): + """Read lines from a file, stripping newlines.""" return [l.rstrip("\n") for l in p.read_text(encoding="utf-8").splitlines()] def test_extract_hosts_function(): + """Test extract_hosts_from_html extracts unique hosts in order.""" from main import extract_hosts_from_html html = ( @@ -28,6 +32,7 @@ def test_extract_hosts_function(): def test_cli_writes_expected_output(tmp_path: Path): + """Test CLI writes correctly formatted output file.""" # copy sample1.html to tmpdir and run the script sample = ROOT / "tests" / "sample1.html" html_copy = tmp_path / "sample1.html" @@ -49,6 +54,7 @@ def test_cli_writes_expected_output(tmp_path: Path): def test_cli_default_output_name(tmp_path: Path): + """Test CLI generates default output filename from input.""" sample = ROOT / "tests" / "sample2.html" html_copy = tmp_path / "sample2.html" html_copy.write_text(sample.read_text(encoding="utf-8"), encoding="utf-8") diff --git a/PYTHON/keyboardCoop/main.py b/PYTHON/keyboardCoop/main.py index c12a9c9..623d3fa 100644 --- a/PYTHON/keyboardCoop/main.py +++ b/PYTHON/keyboardCoop/main.py @@ -1,3 +1,8 @@ +"""Keyboard cooperative word game using Pygame. + +Players take turns selecting adjacent keys to form valid English words. +""" + import json import logging import os @@ -62,7 +67,10 @@ KEY_ADJACENCY = { class KeyboardCoopGame: + """Main game class for the keyboard cooperative word game.""" + def __init__(self): + """Initialize the game window, fonts, and game state.""" self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) pygame.display.set_caption("Keyboard Coop Game") self.clock = pygame.time.Clock() diff --git a/PYTHON/lichess_bot/engine.py b/PYTHON/lichess_bot/engine.py index 6ab4d8c..4008224 100644 --- a/PYTHON/lichess_bot/engine.py +++ b/PYTHON/lichess_bot/engine.py @@ -1,3 +1,5 @@ +"""Chess engine wrapper for the C-based random/scoring engine.""" + import os import subprocess @@ -23,6 +25,7 @@ class RandomEngine: max_time_sec: float = 2.0, depth: int | None = None, ): + """Initialize the engine wrapper with path and time settings.""" self.max_time_sec = max_time_sec # depth is accepted for compatibility with existing callers but is unused; # the C engine handles its own scoring/selection. @@ -67,6 +70,7 @@ class RandomEngine: return (proc.stdout or "").strip() def choose_move(self, board: chess.Board) -> chess.Move: + """Choose a move for the given board position.""" mv, _ = self.choose_move_with_explanation( board, time_budget_sec=self.max_time_sec ) @@ -75,6 +79,7 @@ class RandomEngine: def choose_move_with_explanation( self, board: chess.Board, *, time_budget_sec: float ) -> tuple[chess.Move | None, str]: + """Choose a move and return explanation for the decision.""" # Collect legal moves and send to engine as plain UCI tokens. legal = list(board.legal_moves) if not legal: @@ -135,7 +140,7 @@ class RandomEngine: # candidate score if provided analyze = data.get("analyze") or {} cs = analyze.get("candidate_score") - if isinstance(cs, (int, float)): + if isinstance(cs, int | float): cand_score = float(cs) # best move chosen = data.get("chosen_move") diff --git a/PYTHON/lichess_bot/lichess_api.py b/PYTHON/lichess_bot/lichess_api.py index fa87744..15121e8 100644 --- a/PYTHON/lichess_bot/lichess_api.py +++ b/PYTHON/lichess_bot/lichess_api.py @@ -1,3 +1,5 @@ +"""Lichess API client for bot interactions.""" + from collections.abc import Generator import contextlib import json @@ -11,7 +13,10 @@ LICHESS_API = "https://lichess.org" class LichessAPI: + """Client for interacting with the Lichess Bot API.""" + def __init__(self, token: str, session: requests.Session | None = None): + """Initialize the API client with authentication token.""" self.token = token self.session = session or requests.Session() self.session.headers.update( @@ -62,6 +67,7 @@ class LichessAPI: return r def stream_events(self) -> Generator[dict, None, None]: + """Stream incoming events (challenges, game starts, etc.).""" url = f"{LICHESS_API}/api/stream/event" backoff = 0.5 while True: @@ -90,10 +96,12 @@ class LichessAPI: raise def accept_challenge(self, challenge_id: str) -> None: + """Accept a challenge by its ID.""" url = f"{LICHESS_API}/api/challenge/{challenge_id}/accept" self._request("POST", url, timeout=30, raise_for_status=True) def decline_challenge(self, challenge_id: str, reason: str = "generic") -> None: + """Decline a challenge with an optional reason.""" url = f"{LICHESS_API}/api/challenge/{challenge_id}/decline" data = {"reason": reason} self._request("POST", url, data=data, timeout=30, raise_for_status=True) @@ -135,6 +143,7 @@ class LichessAPI: return board, color def stream_game_events(self, game_id: str) -> Generator[dict, None, None]: + """Stream game state events for a specific game.""" url = f"{LICHESS_API}/api/board/game/stream/{game_id}" headers = {"Accept": "application/x-ndjson"} with self._request("GET", url, headers=headers, stream=True, timeout=None) as r: @@ -148,6 +157,7 @@ class LichessAPI: logging.debug(f"Skipping non-JSON line in game {game_id}: {line}") def make_move(self, game_id: str, move: chess.Move) -> None: + """Submit a move to an active game.""" url = f"{LICHESS_API}/api/board/game/{game_id}/move/{move.uci()}" r = self._request("POST", url, timeout=30) if r.status_code in (400, 409): @@ -165,6 +175,7 @@ class LichessAPI: return None def get_my_user_id(self) -> str | None: + """Fetch the authenticated user's ID.""" url = f"{LICHESS_API}/api/account" r = self._request("GET", url, timeout=30) if r.status_code == 200: diff --git a/PYTHON/lichess_bot/main.py b/PYTHON/lichess_bot/main.py index 11266b3..77cadd0 100644 --- a/PYTHON/lichess_bot/main.py +++ b/PYTHON/lichess_bot/main.py @@ -1,3 +1,5 @@ +"""Main entry point for the Lichess bot.""" + import argparse import json import logging @@ -15,6 +17,7 @@ from PYTHON.lichess_bot.utils import backoff_sleep, get_and_increment_version def run_bot(log_level: str = "INFO", decline_correspondence: bool = False) -> None: + """Start the bot and listen for incoming events.""" logging.basicConfig( level=getattr(logging, log_level.upper(), logging.INFO), format="[%(asctime)s] %(levelname)s %(threadName)s: %(message)s", @@ -448,6 +451,7 @@ def run_bot(log_level: str = "INFO", decline_correspondence: bool = False) -> No def main(): + """Parse arguments and run the Lichess bot.""" parser = argparse.ArgumentParser(description="Run a minimal Lichess bot") parser.add_argument( "--log-level", default="INFO", help="Logging level (default: INFO)" diff --git a/PYTHON/lichess_bot/tests/test_puzzles.py b/PYTHON/lichess_bot/tests/test_puzzles.py index e5e69e0..f4dc3e8 100644 --- a/PYTHON/lichess_bot/tests/test_puzzles.py +++ b/PYTHON/lichess_bot/tests/test_puzzles.py @@ -1,3 +1,5 @@ +"""Test the engine against Lichess puzzles.""" + import csv import os @@ -9,6 +11,7 @@ from PYTHON.lichess_bot.engine import RandomEngine def _load_top_puzzles(csv_path: str, 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]] = [] @@ -31,6 +34,7 @@ def _load_top_puzzles(csv_path: str, limit: int = 8) -> list[tuple[str, str]]: ), ) def test_puzzle_engine_follow_solution(fen: str, moves_str: str): + """Verify the engine follows puzzle solutions correctly.""" board = chess.Board(fen) eng = RandomEngine(max_time_sec=1.0) diff --git a/PYTHON/lichess_bot/tests/test_utils.py b/PYTHON/lichess_bot/tests/test_utils.py index 12eed48..503372b 100644 --- a/PYTHON/lichess_bot/tests/test_utils.py +++ b/PYTHON/lichess_bot/tests/test_utils.py @@ -1,7 +1,10 @@ +"""Tests for utility functions.""" + from PYTHON.lichess_bot.utils import backoff_sleep def test_backoff_sleep_increments_and_caps(monkeypatch): + """Test that backoff sleep increments and respects the cap.""" slept = [] def fake_sleep(sec): diff --git a/PYTHON/lichess_bot/tests/test_versioning.py b/PYTHON/lichess_bot/tests/test_versioning.py index a41f30c..b81e02c 100644 --- a/PYTHON/lichess_bot/tests/test_versioning.py +++ b/PYTHON/lichess_bot/tests/test_versioning.py @@ -1,7 +1,10 @@ +"""Tests for bot version management.""" + from PYTHON.lichess_bot.utils import get_and_increment_version def test_version_file_increments_and_persists(tmp_path, monkeypatch): + """Test that version increments and persists to file.""" version_file = tmp_path / "version.txt" monkeypatch.setenv("LICHESS_BOT_VERSION_FILE", str(version_file)) diff --git a/PYTHON/lichess_bot/tools/generate_blunder_tests.py b/PYTHON/lichess_bot/tools/generate_blunder_tests.py index 044fdf9..73e9164 100755 --- a/PYTHON/lichess_bot/tools/generate_blunder_tests.py +++ b/PYTHON/lichess_bot/tools/generate_blunder_tests.py @@ -48,6 +48,8 @@ logging.basicConfig(level=logging.INFO) @dataclass class Blunder: + """Data class representing a blunder move from analysis.""" + ply: int side: str # 'W' or 'B' san: str # SAN of the played blunder @@ -55,6 +57,7 @@ class Blunder: def parse_columns_for_blunders(text: str) -> list[Blunder]: + """Parse the Columns section of a log file to extract blunders.""" lines = text.splitlines() # Find start of "Columns:" block try: @@ -107,6 +110,7 @@ def parse_columns_for_blunders(text: str) -> list[Blunder]: def extract_pgn(text: str) -> str | None: + """Extract the PGN block from a log file.""" # Extract the PGN block after a line that is exactly 'PGN:' or starts with it m = re.search(r"^PGN:\s*$", text, flags=re.MULTILINE) if not m: @@ -117,6 +121,7 @@ def extract_pgn(text: str) -> str | None: def san_list_from_game(game: chess.pgn.Game) -> list[str]: + """Extract the list of SAN moves from a PGN game.""" san_moves: list[str] = [] node = game while node.variations: @@ -128,6 +133,7 @@ def san_list_from_game(game: chess.pgn.Game) -> list[str]: def fen_and_uci_for_blunders( pgn_text: str, blunders: list[Blunder] ) -> list[tuple[str, str, str, Blunder]]: + """Convert blunders to (FEN, UCI, best_UCI, Blunder) tuples.""" game = chess.pgn.read_game(io.StringIO(pgn_text)) if game is None: msg = "Failed to parse PGN from log" @@ -173,6 +179,7 @@ def fen_and_uci_for_blunders( def ensure_unified_test_file(target_path: str) -> 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): return @@ -370,6 +377,7 @@ def _process_single_log(log_path: str) -> int: 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")) diff --git a/PYTHON/lichess_bot/utils.py b/PYTHON/lichess_bot/utils.py index 6567b82..258d51d 100644 --- a/PYTHON/lichess_bot/utils.py +++ b/PYTHON/lichess_bot/utils.py @@ -1,3 +1,5 @@ +"""Utility functions for the Lichess bot.""" + import logging import os import time diff --git a/PYTHON/mock_server/mock_server.py b/PYTHON/mock_server/mock_server.py index 3f0e8bc..767118b 100644 --- a/PYTHON/mock_server/mock_server.py +++ b/PYTHON/mock_server/mock_server.py @@ -1,7 +1,10 @@ +"""Mitmproxy addon to simulate connection failures.""" + from mitmproxy import http def request(flow: http.HTTPFlow) -> None: + """Intercept requests and simulate failures for specific hosts.""" # Only intercept traffic to example.com if "example.com" in flow.request.host: flow.response = http.Response.make( diff --git a/PYTHON/randomJPG/generateJpeg.py b/PYTHON/randomJPG/generateJpeg.py index 6a62abc..e639c2b 100644 --- a/PYTHON/randomJPG/generateJpeg.py +++ b/PYTHON/randomJPG/generateJpeg.py @@ -1,3 +1,5 @@ +"""Generate random colorful JPEG images with configurable parameters.""" + import argparse from datetime import datetime import logging diff --git a/PYTHON/randomize_numbers/random_digits.py b/PYTHON/randomize_numbers/random_digits.py index bf1727b..ac549b9 100644 --- a/PYTHON/randomize_numbers/random_digits.py +++ b/PYTHON/randomize_numbers/random_digits.py @@ -1,3 +1,5 @@ +"""Randomize numbers by applying a random percentage variation.""" + import contextlib import logging import random @@ -8,6 +10,7 @@ logging.basicConfig(level=logging.INFO) def randomize_numbers(numbers, min_percentage=1, max_percentage=20): + """Apply random percentage variation to a list of numbers.""" randomized_numbers = [] for number in numbers: percentage = random.uniform(min_percentage, max_percentage) / 100 @@ -20,6 +23,7 @@ def randomize_numbers(numbers, min_percentage=1, max_percentage=20): def parse_input(input_string): + """Parse a string of numbers and return floats with decimal counts.""" # Replace commas with dots and remove non-numeric characters # except dots, commas, and digits cleaned_input = re.sub(r"[^\d.,\s]", "", input_string).replace(",", ".") diff --git a/PYTHON/scapeWebsite/scrape_comics.py b/PYTHON/scapeWebsite/scrape_comics.py index 5031911..d071be8 100644 --- a/PYTHON/scapeWebsite/scrape_comics.py +++ b/PYTHON/scapeWebsite/scrape_comics.py @@ -1,3 +1,5 @@ +"""Download comic images from a website using Selenium.""" + import argparse import logging import os @@ -27,6 +29,7 @@ driver.get(url) # A function to download images by URL def download_image(url): + """Download an image from a URL and save it locally.""" # Extract image name from URL image_name = os.path.basename(urlparse(url).path) diff --git a/PYTHON/screen_locker/screen_lock.py b/PYTHON/screen_locker/screen_lock.py index d2d4043..aa56ca8 100755 --- a/PYTHON/screen_locker/screen_lock.py +++ b/PYTHON/screen_locker/screen_lock.py @@ -14,7 +14,10 @@ logging.basicConfig(level=logging.INFO) class ScreenLocker: + """Screen locker that requires workout logging to unlock.""" + def __init__(self, demo_mode=True): + """Initialize screen locker with optional demo mode.""" # Set up log file path script_dir = os.path.dirname(os.path.abspath(__file__)) self.log_file = os.path.join(script_dir, "workout_log.json") @@ -74,10 +77,12 @@ class ScreenLocker: self.root.grab_set_global() def clear_container(self): + """Remove all widgets from the main container.""" for widget in self.container.winfo_children(): widget.destroy() def ask_workout_done(self): + """Display the initial workout question dialog.""" self.clear_container() question = tk.Label( @@ -117,6 +122,7 @@ class ScreenLocker: no_btn.pack(side="left", padx=20) def lockout(self): + """Display lockout screen with countdown timer.""" self.clear_container() self.lockout_label = tk.Label( @@ -141,6 +147,7 @@ class ScreenLocker: self.update_lockout_countdown() def update_lockout_countdown(self): + """Update the lockout countdown timer display.""" if self.remaining_time > 0: self.countdown_label.config(text=str(self.remaining_time)) self.remaining_time -= 1 @@ -149,6 +156,7 @@ class ScreenLocker: self.ask_workout_done() def ask_workout_type(self): + """Display workout type selection dialog.""" self.clear_container() question = tk.Label( @@ -188,6 +196,7 @@ class ScreenLocker: strength_btn.pack(side="left", padx=20) def ask_running_details(self): + """Display running workout input form.""" self.clear_container() self.workout_data["type"] = "running" @@ -277,6 +286,7 @@ class ScreenLocker: self.update_submit_timer() def verify_running_data(self): + """Validate running workout data and unlock if valid.""" try: distance = float(self.distance_entry.get()) time_mins = float(self.time_entry.get()) @@ -314,6 +324,7 @@ class ScreenLocker: self.show_error("Please enter valid numbers") def ask_strength_details(self): + """Display strength training input form.""" self.clear_container() self.workout_data["type"] = "strength" @@ -435,6 +446,7 @@ class ScreenLocker: self.update_submit_timer() def verify_strength_data(self): + """Validate strength workout data and unlock if valid.""" try: exercises = [e.strip() for e in self.exercises_entry.get().split(",")] sets = [int(s.strip()) for s in self.sets_entry.get().split(",")] @@ -539,6 +551,7 @@ class ScreenLocker: pass def show_error(self, message): + """Display error message with retry option.""" self.clear_container() error_label = tk.Label( @@ -573,6 +586,7 @@ class ScreenLocker: retry_btn.pack(pady=30) def unlock_screen(self): + """Save workout log and display success message.""" # Save workout data to log self.save_workout_log() @@ -638,10 +652,12 @@ class ScreenLocker: logging.warning(f"Could not save workout log: {e}") def close(self): + """Close the application and exit.""" self.root.destroy() sys.exit(0) def run(self): + """Start the Tkinter main event loop.""" self.root.mainloop() diff --git a/PYTHON/split/split_x_into_n_symmetrically.py b/PYTHON/split/split_x_into_n_symmetrically.py index ff4147f..14ff041 100644 --- a/PYTHON/split/split_x_into_n_symmetrically.py +++ b/PYTHON/split/split_x_into_n_symmetrically.py @@ -1,3 +1,6 @@ +"""Distribute values symmetrically across N parts.""" + + def calculate_symmetric_weights(N, middle_weight, factors=None): """Calculate symmetric weights for both even and odd N. diff --git a/PYTHON/stockfish_analysis/analyze_chess_game.py b/PYTHON/stockfish_analysis/analyze_chess_game.py index 8894790..5d32291 100755 --- a/PYTHON/stockfish_analysis/analyze_chess_game.py +++ b/PYTHON/stockfish_analysis/analyze_chess_game.py @@ -125,6 +125,7 @@ def classify_cp_loss(cp_loss: int | None) -> str: def fmt_eval(cp: int | None, mate_in: int | None) -> str: + """Format evaluation score as human-readable string.""" if mate_in is not None: sign = "+" if mate_in > 0 else "" return f"M{sign}{mate_in}" @@ -201,6 +202,7 @@ def _auto_hash_mb(threads_wanted: int, engine_options) -> int: def main(): + """Parse arguments and run chess game analysis.""" logging.basicConfig(level=logging.INFO, format="%(message)s") ap = argparse.ArgumentParser( description="Analyze a chess game's moves with Stockfish and rate each move." diff --git a/PYTHON/tagDivider/tagDivider.py b/PYTHON/tagDivider/tagDivider.py index f098628..740777a 100644 --- a/PYTHON/tagDivider/tagDivider.py +++ b/PYTHON/tagDivider/tagDivider.py @@ -1,3 +1,5 @@ +"""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 diff --git a/articles/test_server_api.py b/articles/test_server_api.py index 8c7ce6f..3e16366 100644 --- a/articles/test_server_api.py +++ b/articles/test_server_api.py @@ -1,3 +1,5 @@ +"""Integration tests for the articles C server API.""" + import json import os from pathlib import Path @@ -9,7 +11,8 @@ import urllib.request def _req(url, method="GET", data=None): - if data is not None and not isinstance(data, (bytes, bytearray)): + """Send an HTTP request and return status code and body.""" + if data is not None and not isinstance(data, bytes | bytearray): data = json.dumps(data).encode("utf-8") req = urllib.request.Request(url, data=data, method=method) req.add_header("Content-Type", "application/json") @@ -19,6 +22,7 @@ def _req(url, method="GET", data=None): def test_crud_roundtrip(tmp_path): + """Test full CRUD lifecycle for articles API.""" # Build C server here = Path(__file__).resolve().parent subprocess.run(["make", "-s", "server_c"], check=True, cwd=str(here)) diff --git a/articles/test_site_size.py b/articles/test_site_size.py index 46928ea..8982ae5 100644 --- a/articles/test_site_size.py +++ b/articles/test_site_size.py @@ -1,3 +1,5 @@ +"""Tests to ensure website stays within size budget.""" + import os # Budget for the entire website (single file) in bytes @@ -8,9 +10,11 @@ SITE_FILE = os.path.join(HERE, "index.html") def test_site_file_exists(): + """Verify the main site HTML file exists.""" assert os.path.exists(SITE_FILE), f"Missing site file: {SITE_FILE}" def test_site_size_under_budget(): + """Verify site size is under the defined budget.""" size = os.path.getsize(SITE_FILE) assert size <= BUDGET, f"Site size {size} bytes exceeds budget {BUDGET}" diff --git a/poker-modifier-app/poker_modifier_app.py b/poker-modifier-app/poker_modifier_app.py index 5f155e4..ed1f9f0 100644 --- a/poker-modifier-app/poker_modifier_app.py +++ b/poker-modifier-app/poker_modifier_app.py @@ -1,3 +1,5 @@ +"""Texas Hold'em poker game modifier application.""" + import logging import random import tkinter as tk @@ -7,7 +9,10 @@ logging.basicConfig(level=logging.INFO) class PokerModifierApp: + """GUI application for poker game modifiers.""" + def __init__(self): + """Initialize the poker modifier app with default settings.""" self.modifiers = [ # Hand Bonus Modifiers (Balatro-inspired) { @@ -515,6 +520,7 @@ class PokerModifierApp: self.setup_gui() def setup_gui(self): + """Create and configure the main GUI window.""" # Create main window self.root = tk.Tk() self.root.title("🃏 Texas Hold'em Modifier") diff --git a/pyproject.toml b/pyproject.toml index 5fbc6fa..d042e7c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,15 +33,7 @@ ignore = [ "D213", # Multi-line docstring summary should start at second line (conflicts with D212) "COM812", # Trailing comma missing (conflicts with formatter) "ISC001", # Implicit string concatenation (conflicts with formatter) - "ANN101", # Missing type annotation for self (deprecated) - "ANN102", # Missing type annotation for cls (deprecated) # Relaxed for script-heavy repository - "D100", # Missing docstring in public module - scripts don't need module docstrings - "D101", # Missing docstring in public class - relaxed - "D102", # Missing docstring in public method - relaxed - "D103", # Missing docstring in public function - relaxed - "D104", # Missing docstring in public package - relaxed - "D107", # Missing docstring in __init__ - relaxed "D205", # Missing blank line after summary - relaxed "D415", # Missing terminal punctuation - relaxed for scripts "INP001", # Implicit namespace package - this is a scripts repo @@ -98,11 +90,9 @@ ignore = [ "B023", # function-uses-loop-variable - common pattern with closures "B904", # raise-without-from - style preference "DTZ005", # datetime.now without tz - acceptable for local scripts - "UP038", # isinstance union type - py3.10+ style, not required "E741", # ambiguous-variable-name - sometimes intentional (e.g. l for list) "DTZ004", # datetime.utcfromtimestamp - acceptable "E722", # bare-except - will be fixed where critical - "E741", # ambiguous-variable-name - sometimes intentional "PT017", # pytest-assert-in-except - acceptable pattern ]