diff --git a/PYTHON/extractLinks/main.py b/PYTHON/extractLinks/main.py index f659b90..2007968 100755 --- a/PYTHON/extractLinks/main.py +++ b/PYTHON/extractLinks/main.py @@ -24,8 +24,10 @@ class _HrefParser(HTMLParser): super().__init__() self.hrefs: list[str] = [] - def handle_starttag(self, tag: str, attrs): # type: ignore[override] - # Collect any href attribute on any tag + def handle_starttag( # type: ignore[override] + self, tag: str, attrs: list[tuple[str, str | None]] + ) -> None: + """Collect href attributes from start tags.""" for k, v in attrs: if k.lower() == "href" and v is not None: self.hrefs.append(v) diff --git a/PYTHON/extractLinks/tests/test_main.py b/PYTHON/extractLinks/tests/test_main.py index e732adc..a6b3672 100644 --- a/PYTHON/extractLinks/tests/test_main.py +++ b/PYTHON/extractLinks/tests/test_main.py @@ -11,12 +11,12 @@ if str(ROOT) not in sys.path: SCRIPT = ROOT / "main.py" -def read_lines(p: Path): +def read_lines(p: Path) -> list[str]: """Read lines from a file, stripping newlines.""" return [line.rstrip("\n") for line in p.read_text(encoding="utf-8").splitlines()] -def test_extract_hosts_function(): +def test_extract_hosts_function() -> None: """Test extract_hosts_from_html extracts unique hosts in order.""" from main import extract_hosts_from_html @@ -31,7 +31,7 @@ def test_extract_hosts_function(): assert hosts == ["wiby.me", "example.com"], hosts -def test_cli_writes_expected_output(tmp_path: Path): +def test_cli_writes_expected_output(tmp_path: Path) -> None: """Test CLI writes correctly formatted output file.""" # copy sample1.html to tmpdir and run the script sample = ROOT / "tests" / "sample1.html" @@ -53,7 +53,7 @@ def test_cli_writes_expected_output(tmp_path: Path): assert lines == ["*wiby.me*", "*example.com*"], lines -def test_cli_default_output_name(tmp_path: Path): +def test_cli_default_output_name(tmp_path: Path) -> None: """Test CLI generates default output filename from input.""" sample = ROOT / "tests" / "sample2.html" html_copy = tmp_path / "sample2.html" diff --git a/PYTHON/keyboardCoop/main.py b/PYTHON/keyboardCoop/main.py index c5669e5..e3f5388 100644 --- a/PYTHON/keyboardCoop/main.py +++ b/PYTHON/keyboardCoop/main.py @@ -70,7 +70,7 @@ KEY_ADJACENCY = { class KeyboardCoopGame: """Main game class for the keyboard cooperative word game.""" - def __init__(self): + def __init__(self) -> None: """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") @@ -96,7 +96,7 @@ class KeyboardCoopGame: # Key positions self.key_positions = self.calculate_key_positions() - def load_dictionary(self): + def load_dictionary(self) -> set[str]: """Load dictionary from words_dictionary.json file.""" try: dictionary_path = os.path.join( @@ -198,7 +198,7 @@ class KeyboardCoopGame: "good", } - def generate_random_keyboard(self): + def generate_random_keyboard(self) -> None: """Generate a random keyboard layout and calculate adjacencies.""" # All 26 letters all_letters = list("abcdefghijklmnopqrstuvwxyz") @@ -217,7 +217,7 @@ class KeyboardCoopGame: # Calculate adjacencies based on new layout self.calculate_adjacencies() - def calculate_adjacencies(self): + def calculate_adjacencies(self) -> None: """Calculate adjacencies based on current keyboard layout.""" self.key_adjacency = {} @@ -248,9 +248,9 @@ class KeyboardCoopGame: self.key_adjacency[letter] = adjacents - def calculate_key_positions(self): + def calculate_key_positions(self) -> dict[str, pygame.Rect]: """Calculate the position of each key on screen.""" - positions = {} + positions: dict[str, pygame.Rect] = {} key_width = 60 key_height = 60 key_spacing = 8 @@ -266,14 +266,14 @@ class KeyboardCoopGame: return positions - def get_key_at_position(self, pos): + def get_key_at_position(self, pos: tuple[int, int]) -> str | None: """Get the key at the given mouse position.""" for key, rect in self.key_positions.items(): if rect.collidepoint(pos): return key return None - def is_valid_move(self, letter): + def is_valid_move(self, letter: str) -> bool: """Check if the letter is a valid move.""" if not self.selected_letters: return True # First move can be any letter @@ -281,17 +281,17 @@ class KeyboardCoopGame: last_letter = self.selected_letters[-1] return letter in self.key_adjacency[last_letter] - def is_valid_word(self, word): + def is_valid_word(self, word: str) -> bool: """Check if the word is in the dictionary.""" return word.lower() in self.dictionary - def calculate_score(self, word_length): + def calculate_score(self, word_length: int) -> int: """Calculate score exponentially based on word length.""" if word_length < MIN_WORD_LENGTH: return 0 return 2 ** (word_length - 2) - def handle_letter_click(self, letter): + def handle_letter_click(self, letter: str) -> None: """Handle clicking on a letter.""" if letter in self.available_letters and self.is_valid_move(letter): self.selected_letters.append(letter) @@ -316,7 +316,7 @@ class KeyboardCoopGame: if not self.available_letters: self.submit_word() - def submit_word(self): + def submit_word(self) -> None: """Submit the current word and check if it's valid.""" if len(self.current_word) >= MIN_WORD_LENGTH and self.is_valid_word( self.current_word @@ -345,7 +345,7 @@ class KeyboardCoopGame: self.selected_letters = [] self.current_player = 0 - def reset_game(self): + def reset_game(self) -> None: """Reset the game to initial state.""" self.current_player = 0 self.current_word = "" @@ -358,7 +358,7 @@ class KeyboardCoopGame: self.generate_random_keyboard() self.key_positions = self.calculate_key_positions() - def draw_keyboard(self): + def draw_keyboard(self) -> None: """Draw the virtual keyboard.""" mouse_pos = pygame.mouse.get_pos() @@ -382,7 +382,7 @@ class KeyboardCoopGame: text_rect = text.get_rect(center=rect.center) self.screen.blit(text, text_rect) - def draw_ui(self): + def draw_ui(self) -> tuple[pygame.Rect, pygame.Rect]: """Draw the user interface.""" # Title title = self.large_font.render("Keyboard Coop Game", True, TEXT_COLOR) @@ -444,7 +444,7 @@ class KeyboardCoopGame: return enter_rect, reset_rect - def handle_click(self, pos): + def handle_click(self, pos: tuple[int, int]) -> None: """Handle mouse clicks.""" # Check if clicked on a key key = self.get_key_at_position(pos) @@ -460,7 +460,7 @@ class KeyboardCoopGame: elif reset_rect.collidepoint(pos): self.reset_game() - def run(self): + def run(self) -> None: """Main game loop.""" running = True diff --git a/PYTHON/lichess_bot/engine.py b/PYTHON/lichess_bot/engine.py index 859fcbf..4376330 100644 --- a/PYTHON/lichess_bot/engine.py +++ b/PYTHON/lichess_bot/engine.py @@ -25,7 +25,7 @@ class RandomEngine: engine_path: str | None = None, max_time_sec: float = 2.0, depth: int | None = 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; diff --git a/PYTHON/lichess_bot/lichess_api.py b/PYTHON/lichess_bot/lichess_api.py index d7ad36c..2ecf503 100644 --- a/PYTHON/lichess_bot/lichess_api.py +++ b/PYTHON/lichess_bot/lichess_api.py @@ -16,7 +16,7 @@ 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): + def __init__(self, token: str, session: requests.Session | None = None) -> None: """Initialize the API client with authentication token.""" self.token = token self.session = session or requests.Session() @@ -29,7 +29,12 @@ class LichessAPI: ) def _request( - self, method: str, url: str, *, raise_for_status: bool = False, **kwargs + self, + method: str, + url: str, + *, + raise_for_status: bool = False, + **kwargs: object, ) -> requests.Response: """Wrapper around session.request that logs every request/response. @@ -40,7 +45,7 @@ class LichessAPI: t0 = time.monotonic() logging.info(f"HTTP {method} {url} -> sending") try: - r = self.session.request(method, url, **kwargs) + r = self.session.request(method, url, **kwargs) # type: ignore[arg-type] except Exception: logging.exception(f"HTTP {method} {url} -> exception") raise diff --git a/PYTHON/lichess_bot/main.py b/PYTHON/lichess_bot/main.py index 87bbb70..f12f0a9 100644 --- a/PYTHON/lichess_bot/main.py +++ b/PYTHON/lichess_bot/main.py @@ -38,7 +38,7 @@ def run_bot(log_level: str = "INFO", decline_correspondence: bool = False) -> No game_threads = {} - def handle_game(game_id: str, my_color: str | None = None): + def handle_game(game_id: str, my_color: str | None = None) -> None: logging.info(f"Starting game thread for {game_id} [bot v{bot_version}]") board = chess.Board() color: str | None = my_color @@ -453,7 +453,7 @@ def run_bot(log_level: str = "INFO", decline_correspondence: bool = False) -> No backoff = backoff_sleep(backoff) -def main(): +def main() -> None: """Parse arguments and run the Lichess bot.""" parser = argparse.ArgumentParser(description="Run a minimal Lichess bot") parser.add_argument( diff --git a/PYTHON/lichess_bot/tests/conftest.py b/PYTHON/lichess_bot/tests/conftest.py index 4c4dc4d..2cbfd23 100644 --- a/PYTHON/lichess_bot/tests/conftest.py +++ b/PYTHON/lichess_bot/tests/conftest.py @@ -2,6 +2,8 @@ import os from pathlib import Path import sys +import pytest + # Add repository root to sys.path so 'import PYTHON.*' works when running # pytest with a subdirectory as rootdir. ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../..")) @@ -9,7 +11,7 @@ if ROOT not in sys.path: sys.path.insert(0, ROOT) -def pytest_ignore_collect(collection_path: Path, config): +def pytest_ignore_collect(collection_path: Path, config: pytest.Config) -> bool | None: """Ignore per-game blunder test files; keep only the unified one. This lets us keep historical files in the repo without collecting them. diff --git a/PYTHON/lichess_bot/tests/test_puzzles.py b/PYTHON/lichess_bot/tests/test_puzzles.py index f4dc3e8..1009f1f 100644 --- a/PYTHON/lichess_bot/tests/test_puzzles.py +++ b/PYTHON/lichess_bot/tests/test_puzzles.py @@ -33,7 +33,7 @@ def _load_top_puzzles(csv_path: str, limit: int = 8) -> list[tuple[str, str]]: os.path.join(os.path.dirname(__file__), "lichess_db_puzzle.csv"), limit=8 ), ) -def test_puzzle_engine_follow_solution(fen: str, moves_str: str): +def test_puzzle_engine_follow_solution(fen: str, moves_str: str) -> None: """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 57e71a7..8afb8fc 100644 --- a/PYTHON/lichess_bot/tests/test_utils.py +++ b/PYTHON/lichess_bot/tests/test_utils.py @@ -1,13 +1,15 @@ """Tests for utility functions.""" +import pytest + from PYTHON.lichess_bot.utils import backoff_sleep -def test_backoff_sleep_increments_and_caps(monkeypatch): +def test_backoff_sleep_increments_and_caps(monkeypatch: pytest.MonkeyPatch) -> None: """Test that backoff sleep increments and respects the cap.""" - slept = [] + slept: list[float] = [] - def fake_sleep(sec): + def fake_sleep(sec: float) -> None: slept.append(sec) monkeypatch.setattr("time.sleep", fake_sleep) diff --git a/PYTHON/lichess_bot/tests/test_versioning.py b/PYTHON/lichess_bot/tests/test_versioning.py index b81e02c..e8a3ecc 100644 --- a/PYTHON/lichess_bot/tests/test_versioning.py +++ b/PYTHON/lichess_bot/tests/test_versioning.py @@ -1,9 +1,15 @@ """Tests for bot version management.""" +from pathlib import Path + +import pytest + from PYTHON.lichess_bot.utils import get_and_increment_version -def test_version_file_increments_and_persists(tmp_path, monkeypatch): +def test_version_file_increments_and_persists( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: """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/randomJPG/generateJpeg.py b/PYTHON/randomJPG/generateJpeg.py index 3fac021..74318b9 100644 --- a/PYTHON/randomJPG/generateJpeg.py +++ b/PYTHON/randomJPG/generateJpeg.py @@ -14,19 +14,28 @@ MAX_IMAGE_SIZE = 1000 def generate_bloated_jpeg( - size, color_list, block_size, output_path, quality, image_index, folder -): + size: int, + color_list: list[str], + block_size: int, + output_path: str, + quality: int, + image_index: int, + folder: str, +) -> str: """Generates a random JPEG image with given size, list of colors, and block size. Args: - size (int): Size of the image (both width and height, - must be divisible by block_size). - color_list (list of str): List of colors in hex format. - block_size (int): Size of the pixel blocks. - output_path (str): Output path for the JPEG image. - quality (int): Quality setting for the JPEG image (0-100). - image_index (int): Index of the image for unique naming. - folder (str): Folder to save the image. + size: Size of the image (both width and height, + must be divisible by block_size). + color_list: List of colors in hex format. + block_size: Size of the pixel blocks. + output_path: Output path for the JPEG image. + quality: Quality setting for the JPEG image (0-100). + image_index: Index of the image for unique naming. + folder: Folder to save the image. + + Returns: + Path to the generated image. """ # Ensure size is divisible by block_size and does not exceed MAX_IMAGE_SIZE if size > MAX_IMAGE_SIZE or size % block_size != 0: diff --git a/PYTHON/randomize_numbers/random_digits.py b/PYTHON/randomize_numbers/random_digits.py index 13266c5..0b20f6a 100644 --- a/PYTHON/randomize_numbers/random_digits.py +++ b/PYTHON/randomize_numbers/random_digits.py @@ -13,10 +13,10 @@ DEFAULT_MAX_PERCENTAGE = 20 def randomize_numbers( - numbers, - min_percentage=DEFAULT_MIN_PERCENTAGE, - max_percentage=DEFAULT_MAX_PERCENTAGE, -): + numbers: list[float], + min_percentage: float = DEFAULT_MIN_PERCENTAGE, + max_percentage: float = DEFAULT_MAX_PERCENTAGE, +) -> list[float]: """Apply random percentage variation to a list of numbers.""" randomized_numbers = [] for number in numbers: @@ -29,7 +29,7 @@ def randomize_numbers( return randomized_numbers -def parse_input(input_string): +def parse_input(input_string: str) -> tuple[list[float], list[int]]: """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 @@ -37,8 +37,8 @@ def parse_input(input_string): # Split the cleaned input into individual numbers number_strings = cleaned_input.split() # Convert the number strings to floats - numbers = [] - decimal_counts = [] + numbers: list[float] = [] + decimal_counts: list[int] = [] for num in number_strings: try: float_num = float(num) diff --git a/PYTHON/scapeWebsite/scrape_comics.py b/PYTHON/scapeWebsite/scrape_comics.py index e8add47..ca8d15a 100644 --- a/PYTHON/scapeWebsite/scrape_comics.py +++ b/PYTHON/scapeWebsite/scrape_comics.py @@ -30,7 +30,7 @@ driver.get(url) # A function to download images by URL -def download_image(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) diff --git a/PYTHON/screen_locker/screen_lock.py b/PYTHON/screen_locker/screen_lock.py index 4624b58..859775c 100755 --- a/PYTHON/screen_locker/screen_lock.py +++ b/PYTHON/screen_locker/screen_lock.py @@ -26,7 +26,7 @@ MAX_WEIGHT_KG = 500 class ScreenLocker: """Screen locker that requires workout logging to unlock.""" - def __init__(self, demo_mode=True): + def __init__(self, demo_mode: bool = True) -> None: """Initialize screen locker with optional demo mode.""" # Set up log file path script_dir = os.path.dirname(os.path.abspath(__file__)) @@ -86,12 +86,12 @@ class ScreenLocker: self.root.focus_force() self.root.grab_set_global() - def clear_container(self): + def clear_container(self) -> None: """Remove all widgets from the main container.""" for widget in self.container.winfo_children(): widget.destroy() - def ask_workout_done(self): + def ask_workout_done(self) -> None: """Display the initial workout question dialog.""" self.clear_container() @@ -131,7 +131,7 @@ class ScreenLocker: ) no_btn.pack(side="left", padx=20) - def lockout(self): + def lockout(self) -> None: """Display lockout screen with countdown timer.""" self.clear_container() @@ -156,7 +156,7 @@ class ScreenLocker: self.remaining_time = self.lockout_time self.update_lockout_countdown() - def update_lockout_countdown(self): + def update_lockout_countdown(self) -> None: """Update the lockout countdown timer display.""" if self.remaining_time > 0: self.countdown_label.config(text=str(self.remaining_time)) @@ -165,7 +165,7 @@ class ScreenLocker: else: self.ask_workout_done() - def ask_workout_type(self): + def ask_workout_type(self) -> None: """Display workout type selection dialog.""" self.clear_container() @@ -205,7 +205,7 @@ class ScreenLocker: ) strength_btn.pack(side="left", padx=20) - def ask_running_details(self): + def ask_running_details(self) -> None: """Display running workout input form.""" self.clear_container() self.workout_data["type"] = "running" @@ -295,7 +295,7 @@ class ScreenLocker: self.submit_command = self.verify_running_data self.update_submit_timer() - def verify_running_data(self): + def verify_running_data(self) -> None: """Validate running workout data and unlock if valid.""" try: distance = float(self.distance_entry.get()) @@ -337,7 +337,7 @@ class ScreenLocker: except ValueError: self.show_error("Please enter valid numbers") - def ask_strength_details(self): + def ask_strength_details(self) -> None: """Display strength training input form.""" self.clear_container() self.workout_data["type"] = "strength" @@ -459,7 +459,7 @@ class ScreenLocker: self.submit_command = self.verify_strength_data self.update_submit_timer() - def verify_strength_data(self): + def verify_strength_data(self) -> None: """Validate strength workout data and unlock if valid.""" try: exercises = [e.strip() for e in self.exercises_entry.get().split(",")] @@ -513,7 +513,7 @@ class ScreenLocker: except ValueError: self.show_error("Please enter valid data in correct format") - def update_submit_timer(self): + def update_submit_timer(self) -> None: """Update countdown timer and check if submit can be enabled.""" # Check if widgets still exist (user might have clicked back) try: @@ -544,7 +544,7 @@ class ScreenLocker: # Widgets were destroyed (user clicked back), stop the timer pass - def check_entries_filled(self): + def check_entries_filled(self) -> None: """Continuously check if entries are filled after timer expires.""" try: all_filled = all(entry.get().strip() for entry in self.entries_to_check) @@ -564,7 +564,7 @@ class ScreenLocker: # Widgets were destroyed (user clicked back), stop checking pass - def show_error(self, message): + def show_error(self, message: str) -> None: """Display error message with retry option.""" self.clear_container() @@ -599,7 +599,7 @@ class ScreenLocker: ) retry_btn.pack(pady=30) - def unlock_screen(self): + def unlock_screen(self) -> None: """Save workout log and display success message.""" # Save workout data to log self.save_workout_log() @@ -626,7 +626,7 @@ class ScreenLocker: self.root.after(1500, self.close) - def has_logged_today(self): + def has_logged_today(self) -> bool: """Check if workout has been logged today.""" if not os.path.exists(self.log_file): return False @@ -640,7 +640,7 @@ class ScreenLocker: except (OSError, json.JSONDecodeError): return False - def save_workout_log(self): + def save_workout_log(self) -> None: """Save workout data to log file.""" # Load existing logs logs = {} @@ -665,12 +665,12 @@ class ScreenLocker: except OSError as e: logging.warning(f"Could not save workout log: {e}") - def close(self): + def close(self) -> None: """Close the application and exit.""" self.root.destroy() sys.exit(0) - def run(self): + def run(self) -> None: """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 2663a83..d243fba 100644 --- a/PYTHON/split/split_x_into_n_symmetrically.py +++ b/PYTHON/split/split_x_into_n_symmetrically.py @@ -1,17 +1,22 @@ """Distribute values symmetrically across N parts.""" -def calculate_symmetric_weights(N, middle_weight, factors=None): +def calculate_symmetric_weights( + n: int, middle_weight: float, factors: list[float] | None = None +) -> list[float]: """Calculate symmetric weights for both even and odd N. - N: Number in which to split. - middle_weight: The middle value for symmetry. - factors: If provided, controls the difference in weights (used for the - `split_x_into_n_symmetrically` function). - Must have length N // 2 or N // 2 - 1 depending on N. + Args: + n: Number of parts to split into. + middle_weight: The middle value for symmetry. + factors: If provided, controls the difference in weights. + Must have length n // 2 or n // 2 - 1 depending on n. + + Returns: + List of symmetric weights. """ - half_n = N // 2 - weights_left = [middle_weight] + half_n = n // 2 + weights_left: list[float] = [middle_weight] if factors: for factor in factors: @@ -21,7 +26,7 @@ def calculate_symmetric_weights(N, middle_weight, factors=None): for i in range(half_n - 1): weights_left.append(middle_weight - (i + 1)) - if N % 2 == 0: + if n % 2 == 0: weights = weights_left[::-1] + weights_left else: weights = [*weights_left[::-1], middle_weight, *weights_left] @@ -29,24 +34,28 @@ def calculate_symmetric_weights(N, middle_weight, factors=None): return weights -def scale_to_total(X, weights): +def scale_to_total(x: float, weights: list[float]) -> list[float]: """Scale the weights so that their sum is proportional to X. - X: Total value to distribute. - weights: The list of weights to be scaled. + Args: + x: Total value to distribute. + weights: The list of weights to be scaled. + + Returns: + List of scaled values summing to x. """ total_weight = sum(weights) - base_unit = X / total_weight + base_unit = x / total_weight return [base_unit * weight for weight in weights] -def split_x_into_n_symmetrically(X, N, factors): +def split_x_into_n_symmetrically(x: float, n: int, factors: list[float]) -> list[float]: """Split X into N parts with symmetric weights controlled by factors.""" - weights = calculate_symmetric_weights(N, middle_weight=1, factors=factors) - return scale_to_total(X, weights) + weights = calculate_symmetric_weights(n, middle_weight=1, factors=factors) + return scale_to_total(x, weights) -def split_x_into_n_middle(X, N, middle_value): +def split_x_into_n_middle(x: float, n: int, middle_value: float) -> list[float]: """Split X into N parts with symmetric weights using middle_value as peak.""" - weights = calculate_symmetric_weights(N, middle_weight=middle_value) - return scale_to_total(X, weights) + weights = calculate_symmetric_weights(n, middle_weight=middle_value) + return scale_to_total(x, weights) diff --git a/PYTHON/stockfish_analysis/analyze_chess_game.py b/PYTHON/stockfish_analysis/analyze_chess_game.py index 31b5730..ff91aa4 100755 --- a/PYTHON/stockfish_analysis/analyze_chess_game.py +++ b/PYTHON/stockfish_analysis/analyze_chess_game.py @@ -193,7 +193,7 @@ def _detect_total_mem_mb() -> int | None: return None -def _auto_hash_mb(threads_wanted: int, engine_options) -> int: +def _auto_hash_mb(threads_wanted: int, engine_options: dict[str, object]) -> int: total_mb = _detect_total_mem_mb() or 2048 # Heuristic: cap at 4 GiB by default; keep at most half of RAM; ensure >= 64MB half_ram = max(64, total_mb // 2) @@ -202,7 +202,7 @@ def _auto_hash_mb(threads_wanted: int, engine_options) -> int: opt = engine_options.get("Hash") max_allowed = None try: - max_allowed = opt.max if opt is not None else None + max_allowed = opt.max if opt is not None else None # type: ignore[attr-defined] except Exception: max_allowed = None if isinstance(max_allowed, int): @@ -213,7 +213,7 @@ def _auto_hash_mb(threads_wanted: int, engine_options) -> int: return max(64, int(target)) -def main(): +def main() -> None: """Parse arguments and run chess game analysis.""" logging.basicConfig(level=logging.INFO, format="%(message)s") ap = argparse.ArgumentParser( diff --git a/articles/test_server_api.py b/articles/test_server_api.py index 4b99bc6..60a4719 100644 --- a/articles/test_server_api.py +++ b/articles/test_server_api.py @@ -7,13 +7,16 @@ from pathlib import Path import socket import subprocess import time +from typing import Any import urllib.error import urllib.request import pytest -def _req(url, method="GET", data=None): +def _req( + url: str, method: str = "GET", data: dict[str, Any] | bytes | None = None +) -> tuple[int, bytes]: """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") @@ -24,7 +27,7 @@ def _req(url, method="GET", data=None): return resp.getcode(), body -def test_crud_roundtrip(tmp_path): +def test_crud_roundtrip(tmp_path: Path) -> None: """Test full CRUD lifecycle for articles API.""" # Build C server here = Path(__file__).resolve().parent diff --git a/articles/test_site_size.py b/articles/test_site_size.py index 8982ae5..bbbf5ed 100644 --- a/articles/test_site_size.py +++ b/articles/test_site_size.py @@ -9,12 +9,12 @@ HERE = os.path.dirname(__file__) SITE_FILE = os.path.join(HERE, "index.html") -def test_site_file_exists(): +def test_site_file_exists() -> None: """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(): +def test_site_size_under_budget() -> None: """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 ed1f9f0..cb0c4f5 100644 --- a/poker-modifier-app/poker_modifier_app.py +++ b/poker-modifier-app/poker_modifier_app.py @@ -11,7 +11,7 @@ logging.basicConfig(level=logging.INFO) class PokerModifierApp: """GUI application for poker game modifiers.""" - def __init__(self): + def __init__(self) -> None: """Initialize the poker modifier app with default settings.""" self.modifiers = [ # Hand Bonus Modifiers (Balatro-inspired) @@ -519,7 +519,7 @@ class PokerModifierApp: self.setup_gui() - def setup_gui(self): + def setup_gui(self) -> None: """Create and configure the main GUI window.""" # Create main window self.root = tk.Tk() @@ -787,16 +787,16 @@ class PokerModifierApp: ) self.phase_label.pack(pady=10) - def update_prob_display(self, value): + def update_prob_display(self, value: str) -> None: """Update the probability percentage display.""" self.prob_label.config(text=f"{value}%") - def update_length_display(self, value): + def update_length_display(self, value: str) -> None: """Update the game length display.""" self.length_label.config(text=str(value)) self.total_game_rounds = int(value) - def toggle_debug_mode(self): + def toggle_debug_mode(self) -> None: """Toggle debug mode and show/hide debug controls.""" self.debug_mode = self.debug_var.get() if self.debug_mode: @@ -807,7 +807,7 @@ class PokerModifierApp: self.force_endgame = False logging.debug("Debug mode disabled") - def toggle_force_endgame(self): + def toggle_force_endgame(self) -> None: """Toggle forced endgame mode for testing.""" self.force_endgame = not self.force_endgame if self.force_endgame: @@ -817,7 +817,7 @@ class PokerModifierApp: self.force_endgame_button.config(text="Force Endgame", bg="#ff6b6b") logging.debug("Normal modifier selection restored") - def is_endgame(self): + def is_endgame(self) -> bool: """Determine if we're in endgame phase.""" if self.debug_mode and self.force_endgame: return True @@ -825,7 +825,7 @@ class PokerModifierApp: endgame_round = int(self.total_game_rounds * self.endgame_threshold) return self.rounds_played >= endgame_round - def start_round(self): + def start_round(self) -> None: """Start a new poker round and determine if modifier should be applied.""" # Button animation effect self.start_button.config(relief=tk.SUNKEN) @@ -850,7 +850,7 @@ class PokerModifierApp: else: self.show_no_modifier() - def update_phase_indicator(self): + def update_phase_indicator(self) -> None: """Update the game phase indicator based on current round.""" if self.is_endgame(): self.phase_label.config(text="Endgame", fg="#ff6b6b") @@ -861,7 +861,7 @@ class PokerModifierApp: else: self.phase_label.config(text="Early", fg="#4CAF50") - def apply_random_modifier(self): + def apply_random_modifier(self) -> None: """Apply a random modifier and update display.""" # Update modifier counter self.modifiers_applied += 1 @@ -925,7 +925,7 @@ class PokerModifierApp: text=modifier_text, fg="#ffd700", bg=bg_color, font=("Arial", 14, "bold") ) - def show_no_modifier(self): + def show_no_modifier(self) -> None: """Show no modifier message.""" # Update result frame styling for no modifier self.result_frame.config( @@ -940,7 +940,7 @@ class PokerModifierApp: font=("Arial", 14), ) - def reset_game(self): + def reset_game(self) -> None: """Reset the game to initial state.""" self.rounds_played = 0 self.modifiers_applied = 0 @@ -968,11 +968,11 @@ class PokerModifierApp: logging.info("Game reset to initial state") - def add_modifier(self, name, description): + def add_modifier(self, name: str, description: str) -> None: """Add a new modifier to the list.""" self.modifiers.append({"name": name, "description": description}) - def get_stats(self): + def get_stats(self) -> dict[str, int | float | bool]: """Get current statistics.""" modifier_rate = ( 0 @@ -992,7 +992,7 @@ class PokerModifierApp: "force_endgame": self.force_endgame, } - def run(self): + def run(self) -> None: """Start the application.""" logging.info("Texas Hold'em Modifier App started!") logging.info( diff --git a/pyproject.toml b/pyproject.toml index 0344d77..b02d915 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,12 +34,6 @@ ignore = [ "COM812", # Trailing comma missing (conflicts with formatter) "ISC001", # Implicit string concatenation (conflicts with formatter) # Relaxed for script-heavy repository - "ANN001", # Missing type annotation for function argument - "ANN002", # Missing type annotation for *args - "ANN003", # Missing type annotation for **kwargs - "ANN201", # Missing return type annotation for public function - "ANN202", # Missing return type annotation for private function - "ANN204", # Missing return type annotation for special method "PTH", # Use pathlib instead of os.path - too invasive for existing code "C901", # Function is too complex - many scripts have complex logic "PLR0912", # Too many branches - relaxed for scripts