mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-07-04 15:03:01 +02:00
Enable PLR2004: replace magic values with named constants
- Added constants for HTTP status codes (using http.HTTPStatus) - Added validation limit constants in screen_locker - Added centipawn loss threshold constants in chess analysis - Added various other domain-specific constants across 9 files
This commit is contained in:
parent
91a4532772
commit
14612a6434
@ -12,8 +12,10 @@ import requests
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
MAX_REQUESTS = 90
|
||||
|
||||
requests_send = 0
|
||||
while requests_send < 90:
|
||||
while requests_send < MAX_REQUESTS:
|
||||
res = requests.get("https://api.thecatapi.com/v1/images/search?limit=100&api_key=")
|
||||
requests_send += 1
|
||||
response = json.loads(res.text)
|
||||
|
||||
@ -27,6 +27,7 @@ KEY_SELECTED_COLOR = (150, 150, 200)
|
||||
KEY_AVAILABLE_COLOR = (100, 150, 100)
|
||||
TEXT_COLOR = (255, 255, 255)
|
||||
PLAYER_COLORS = [(255, 100, 100), (100, 100, 255)]
|
||||
MIN_WORD_LENGTH = 3
|
||||
|
||||
# Keyboard layout
|
||||
KEYBOARD_LAYOUT = [
|
||||
@ -286,7 +287,7 @@ class KeyboardCoopGame:
|
||||
|
||||
def calculate_score(self, word_length):
|
||||
"""Calculate score exponentially based on word length."""
|
||||
if word_length < 3:
|
||||
if word_length < MIN_WORD_LENGTH:
|
||||
return 0
|
||||
return 2 ** (word_length - 2)
|
||||
|
||||
@ -317,7 +318,9 @@ class KeyboardCoopGame:
|
||||
|
||||
def submit_word(self):
|
||||
"""Submit the current word and check if it's valid."""
|
||||
if len(self.current_word) >= 3 and self.is_valid_word(self.current_word):
|
||||
if len(self.current_word) >= MIN_WORD_LENGTH and self.is_valid_word(
|
||||
self.current_word
|
||||
):
|
||||
points = self.calculate_score(len(self.current_word))
|
||||
self.score += points
|
||||
self.message = (
|
||||
@ -329,8 +332,11 @@ class KeyboardCoopGame:
|
||||
self.generate_random_keyboard()
|
||||
self.key_positions = self.calculate_key_positions()
|
||||
|
||||
elif len(self.current_word) < 3:
|
||||
self.message = f"'{self.current_word}' is too short! (minimum 3 letters)"
|
||||
elif len(self.current_word) < MIN_WORD_LENGTH:
|
||||
self.message = (
|
||||
f"'{self.current_word}' is too short! "
|
||||
f"(minimum {MIN_WORD_LENGTH} letters)"
|
||||
)
|
||||
else:
|
||||
self.message = f"'{self.current_word}' is not a valid word!"
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
from collections.abc import Generator
|
||||
import contextlib
|
||||
from http import HTTPStatus
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
@ -45,7 +46,7 @@ class LichessAPI:
|
||||
raise
|
||||
elapsed = time.monotonic() - t0
|
||||
status = r.status_code
|
||||
if status >= 400:
|
||||
if status >= HTTPStatus.BAD_REQUEST:
|
||||
# Log a brief error body snippet if available
|
||||
snippet = None
|
||||
try:
|
||||
@ -88,7 +89,7 @@ class LichessAPI:
|
||||
logging.debug(f"Skipping non-JSON line: {line}")
|
||||
except requests.HTTPError as e:
|
||||
status = getattr(e.response, "status_code", None)
|
||||
if status == 429:
|
||||
if status == HTTPStatus.TOO_MANY_REQUESTS:
|
||||
logging.warning("Event stream hit 429; backing off")
|
||||
time.sleep(backoff)
|
||||
backoff = min(8.0, backoff * 2)
|
||||
@ -160,11 +161,11 @@ class LichessAPI:
|
||||
"""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):
|
||||
if r.status_code in (HTTPStatus.BAD_REQUEST, HTTPStatus.CONFLICT):
|
||||
# Likely not our turn or move already played; do not retry to avoid spam
|
||||
r.raise_for_status()
|
||||
return
|
||||
if r.status_code == 429:
|
||||
if r.status_code == HTTPStatus.TOO_MANY_REQUESTS:
|
||||
logging.warning(f"HTTP POST {url} -> 429; retrying once after 0.5s")
|
||||
time.sleep(0.5)
|
||||
r = self._request("POST", url, timeout=30)
|
||||
@ -178,6 +179,6 @@ class LichessAPI:
|
||||
"""Fetch the authenticated user's ID."""
|
||||
url = f"{LICHESS_API}/api/account"
|
||||
r = self._request("GET", url, timeout=30)
|
||||
if r.status_code == 200:
|
||||
if r.status_code == HTTPStatus.OK:
|
||||
return r.json().get("id")
|
||||
return None
|
||||
|
||||
@ -45,6 +45,10 @@ import chess.pgn
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
# Expected columns in the log file:
|
||||
# ply, side, move, played_eval, best_eval, loss, class, best_suggestion
|
||||
EXPECTED_COLUMNS = 8
|
||||
|
||||
|
||||
@dataclass
|
||||
class Blunder:
|
||||
@ -79,7 +83,7 @@ def parse_columns_for_blunders(text: str) -> list[Blunder]:
|
||||
parts = re.split(r"\s{2,}", ln.strip())
|
||||
# Expected columns:
|
||||
# ply, side, move, played_eval, best_eval, loss, class, best_suggestion
|
||||
if len(parts) < 8:
|
||||
if len(parts) < EXPECTED_COLUMNS:
|
||||
continue
|
||||
try:
|
||||
ply = int(parts[0])
|
||||
|
||||
@ -10,6 +10,8 @@ from PIL import Image
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
MAX_IMAGE_SIZE = 1000
|
||||
|
||||
|
||||
def generate_bloated_jpeg(
|
||||
size, color_list, block_size, output_path, quality, image_index, folder
|
||||
@ -26,9 +28,11 @@ def generate_bloated_jpeg(
|
||||
image_index (int): Index of the image for unique naming.
|
||||
folder (str): Folder to save the image.
|
||||
"""
|
||||
# Ensure size is divisible by block_size and does not exceed 1000 pixels
|
||||
if size > 1000 or size % block_size != 0:
|
||||
msg = "Size must be 1000 pixels or less and divisible by block_size"
|
||||
# Ensure size is divisible by block_size and does not exceed MAX_IMAGE_SIZE
|
||||
if size > MAX_IMAGE_SIZE or size % block_size != 0:
|
||||
msg = (
|
||||
f"Size must be {MAX_IMAGE_SIZE} pixels or less and divisible by block_size"
|
||||
)
|
||||
raise ValueError(msg)
|
||||
|
||||
# Create a new image
|
||||
|
||||
@ -8,8 +8,15 @@ import sys
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
DEFAULT_MIN_PERCENTAGE = 1
|
||||
DEFAULT_MAX_PERCENTAGE = 20
|
||||
|
||||
def randomize_numbers(numbers, min_percentage=1, max_percentage=20):
|
||||
|
||||
def randomize_numbers(
|
||||
numbers,
|
||||
min_percentage=DEFAULT_MIN_PERCENTAGE,
|
||||
max_percentage=DEFAULT_MAX_PERCENTAGE,
|
||||
):
|
||||
"""Apply random percentage variation to a list of numbers."""
|
||||
randomized_numbers = []
|
||||
for number in numbers:
|
||||
@ -43,8 +50,10 @@ def parse_input(input_string):
|
||||
return numbers, decimal_counts
|
||||
|
||||
|
||||
MIN_ARGS = 2
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
if len(sys.argv) < MIN_ARGS:
|
||||
logging.info(
|
||||
"Usage: python random_digits.py <number1> <number2> ... "
|
||||
"[min_percentage max_percentage]"
|
||||
@ -54,8 +63,8 @@ if __name__ == "__main__":
|
||||
try:
|
||||
input_string = " ".join(sys.argv[1:])
|
||||
numbers, decimal_counts = parse_input(input_string)
|
||||
min_percentage = 1
|
||||
max_percentage = 20
|
||||
min_percentage = DEFAULT_MIN_PERCENTAGE
|
||||
max_percentage = DEFAULT_MAX_PERCENTAGE
|
||||
|
||||
if len(numbers) == 0:
|
||||
msg = "No valid numbers provided."
|
||||
|
||||
@ -13,6 +13,15 @@ import tkinter as tk
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
# Validation limits for workout data
|
||||
MAX_DISTANCE_KM = 100
|
||||
MAX_TIME_MINUTES = 600
|
||||
MAX_PACE_MIN_PER_KM = 20
|
||||
MIN_EXERCISE_NAME_LEN = 3
|
||||
MAX_SETS = 20
|
||||
MAX_REPS = 100
|
||||
MAX_WEIGHT_KG = 500
|
||||
|
||||
|
||||
class ScreenLocker:
|
||||
"""Screen locker that requires workout logging to unlock."""
|
||||
@ -294,16 +303,20 @@ class ScreenLocker:
|
||||
pace = float(self.pace_entry.get())
|
||||
|
||||
# Sanity checks
|
||||
if distance <= 0 or distance > 100:
|
||||
self.show_error("Distance seems unrealistic (0-100 km)")
|
||||
if distance <= 0 or distance > MAX_DISTANCE_KM:
|
||||
self.show_error(f"Distance seems unrealistic (0-{MAX_DISTANCE_KM} km)")
|
||||
return
|
||||
|
||||
if time_mins <= 0 or time_mins > 600:
|
||||
self.show_error("Time seems unrealistic (0-600 minutes)")
|
||||
if time_mins <= 0 or time_mins > MAX_TIME_MINUTES:
|
||||
self.show_error(
|
||||
f"Time seems unrealistic (0-{MAX_TIME_MINUTES} minutes)"
|
||||
)
|
||||
return
|
||||
|
||||
if pace <= 0 or pace > 20:
|
||||
self.show_error("Pace seems unrealistic (0-20 min/km)")
|
||||
if pace <= 0 or pace > MAX_PACE_MIN_PER_KM:
|
||||
self.show_error(
|
||||
f"Pace seems unrealistic (0-{MAX_PACE_MIN_PER_KM} min/km)"
|
||||
)
|
||||
return
|
||||
|
||||
# Calculate expected pace and check if close enough
|
||||
@ -463,21 +476,21 @@ class ScreenLocker:
|
||||
return
|
||||
|
||||
# Check for empty or lazy entries
|
||||
if any(len(ex) < 3 for ex in exercises):
|
||||
if any(len(ex) < MIN_EXERCISE_NAME_LEN for ex in exercises):
|
||||
self.show_error("Exercise names too short - be specific")
|
||||
return
|
||||
|
||||
# Sanity checks
|
||||
if any(s < 1 or s > 20 for s in sets):
|
||||
self.show_error("Sets should be between 1-20")
|
||||
if any(s < 1 or s > MAX_SETS for s in sets):
|
||||
self.show_error(f"Sets should be between 1-{MAX_SETS}")
|
||||
return
|
||||
|
||||
if any(r < 1 or r > 100 for r in reps):
|
||||
self.show_error("Reps should be between 1-100")
|
||||
if any(r < 1 or r > MAX_REPS for r in reps):
|
||||
self.show_error(f"Reps should be between 1-{MAX_REPS}")
|
||||
return
|
||||
|
||||
if any(w < 0 or w > 500 for w in weights):
|
||||
self.show_error("Weights should be between 0-500 kg")
|
||||
if any(w < 0 or w > MAX_WEIGHT_KG for w in weights):
|
||||
self.show_error(f"Weights should be between 0-{MAX_WEIGHT_KG} kg")
|
||||
return
|
||||
|
||||
# Calculate expected total weight
|
||||
|
||||
@ -44,6 +44,10 @@ except Exception: # pragma: no cover
|
||||
logging.exception(" pip install -r PYTHON/stockfish_analysis/requirements.txt")
|
||||
raise
|
||||
|
||||
# Memory configuration constants
|
||||
MEMINFO_PARTS_MIN = 2
|
||||
HIGH_THREAD_COUNT = 16
|
||||
|
||||
|
||||
def extract_pgn_text(raw: str) -> str | None:
|
||||
"""Try to extract a PGN block from a possibly noisy file.
|
||||
@ -97,6 +101,14 @@ def score_to_cp(
|
||||
return s.score(mate_score=None), None
|
||||
|
||||
|
||||
# Centipawn loss thresholds for move quality classification (Lichess-like bands)
|
||||
CP_LOSS_BEST = 10
|
||||
CP_LOSS_EXCELLENT = 20
|
||||
CP_LOSS_GOOD = 50
|
||||
CP_LOSS_INACCURACY = 99
|
||||
CP_LOSS_MISTAKE = 299
|
||||
|
||||
|
||||
def classify_cp_loss(cp_loss: int | None) -> str:
|
||||
"""Classify move quality using Lichess-like centipawn loss bands.
|
||||
|
||||
@ -111,15 +123,15 @@ def classify_cp_loss(cp_loss: int | None) -> str:
|
||||
"""
|
||||
if cp_loss is None:
|
||||
return "Unknown"
|
||||
if cp_loss <= 10:
|
||||
if cp_loss <= CP_LOSS_BEST:
|
||||
return "Best"
|
||||
if cp_loss <= 20:
|
||||
if cp_loss <= CP_LOSS_EXCELLENT:
|
||||
return "Excellent"
|
||||
if cp_loss <= 50:
|
||||
if cp_loss <= CP_LOSS_GOOD:
|
||||
return "Good"
|
||||
if cp_loss <= 99:
|
||||
if cp_loss <= CP_LOSS_INACCURACY:
|
||||
return "Inaccuracy"
|
||||
if cp_loss <= 299:
|
||||
if cp_loss <= CP_LOSS_MISTAKE:
|
||||
return "Mistake"
|
||||
return "Blunder"
|
||||
|
||||
@ -172,7 +184,7 @@ def _detect_total_mem_mb() -> int | None:
|
||||
for line in f:
|
||||
if line.startswith("MemTotal:"):
|
||||
parts = line.split()
|
||||
if len(parts) >= 2 and parts[1].isdigit():
|
||||
if len(parts) >= MEMINFO_PARTS_MIN and parts[1].isdigit():
|
||||
# Value is in kB
|
||||
kb = int(parts[1])
|
||||
return kb // 1024
|
||||
@ -196,7 +208,7 @@ def _auto_hash_mb(threads_wanted: int, engine_options) -> int:
|
||||
if isinstance(max_allowed, int):
|
||||
target = min(target, max_allowed)
|
||||
# Some rough scaling: if very many threads, give a bit more (but not huge)
|
||||
if threads_wanted >= 16:
|
||||
if threads_wanted >= HIGH_THREAD_COUNT:
|
||||
target = min(target + 1024, (total_mb * 3) // 4)
|
||||
return max(64, int(target))
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
"""Integration tests for the articles C server API."""
|
||||
|
||||
from http import HTTPStatus
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
@ -62,19 +63,19 @@ def test_crud_roundtrip(tmp_path):
|
||||
"thumb": "data:image/png;base64,xyz",
|
||||
},
|
||||
)
|
||||
assert code == 201
|
||||
assert code == HTTPStatus.CREATED
|
||||
created = json.loads(body)
|
||||
art_id = created["id"]
|
||||
|
||||
# List
|
||||
code, body = _req(base + "/api/articles")
|
||||
assert code == 200
|
||||
assert code == HTTPStatus.OK
|
||||
items = json.loads(body)
|
||||
assert any(a["id"] == art_id for a in items)
|
||||
|
||||
# Get one
|
||||
code, body = _req(base + f"/api/articles/{art_id}")
|
||||
assert code == 200
|
||||
assert code == HTTPStatus.OK
|
||||
got = json.loads(body)
|
||||
assert got["title"] == "T1"
|
||||
|
||||
@ -82,13 +83,13 @@ def test_crud_roundtrip(tmp_path):
|
||||
code, body = _req(
|
||||
base + f"/api/articles/{art_id}", method="PUT", data={"title": "T2"}
|
||||
)
|
||||
assert code == 200
|
||||
assert code == HTTPStatus.OK
|
||||
updated = json.loads(body)
|
||||
assert updated["title"] == "T2"
|
||||
|
||||
# Delete
|
||||
code, _ = _req(base + f"/api/articles/{art_id}", method="DELETE")
|
||||
assert code == 204
|
||||
assert code == HTTPStatus.NO_CONTENT
|
||||
|
||||
# Ensure gone
|
||||
try:
|
||||
@ -96,7 +97,7 @@ def test_crud_roundtrip(tmp_path):
|
||||
msg = "Expected 404"
|
||||
raise AssertionError(msg)
|
||||
except urllib.error.HTTPError as e:
|
||||
assert e.code == 404
|
||||
assert e.code == HTTPStatus.NOT_FOUND
|
||||
|
||||
finally:
|
||||
srv.terminate()
|
||||
|
||||
@ -34,7 +34,6 @@ ignore = [
|
||||
"COM812", # Trailing comma missing (conflicts with formatter)
|
||||
"ISC001", # Implicit string concatenation (conflicts with formatter)
|
||||
# Relaxed for script-heavy repository
|
||||
"PLR2004", # Magic value comparison - common in scripts
|
||||
"S101", # Use of assert - acceptable in this codebase
|
||||
"ANN001", # Missing type annotation for function argument
|
||||
"ANN002", # Missing type annotation for *args
|
||||
|
||||
Loading…
Reference in New Issue
Block a user