fix: correct mypy ignore comment for attr-defined error

This commit is contained in:
Krzysztof kuhy Rudnicki 2025-11-30 15:49:40 +01:00
parent 4e178be185
commit 97edab318f
20 changed files with 150 additions and 118 deletions

View File

@ -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)

View File

@ -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"

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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(

View File

@ -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.

View File

@ -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)

View File

@ -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)

View File

@ -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))

View File

@ -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:

View File

@ -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)

View File

@ -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)

View File

@ -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()

View File

@ -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)

View File

@ -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(

View File

@ -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

View File

@ -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}"

View File

@ -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(

View File

@ -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