mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-07-04 14:43:01 +02:00
fix: correct mypy ignore comment for attr-defined error
This commit is contained in:
parent
60cf90a699
commit
cd4f4e3301
@ -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)
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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()
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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}"
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user