mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-07-04 15:23:03 +02:00
fix(lint): LOG015 - replace root logger with module loggers
- Add _logger = logging.getLogger(__name__) to all modules - Replace logging.X() calls with _logger.X() calls - Remove logging.basicConfig() from module level (keep in run_bot()) - Add G004 to global ignores (f-strings in logging are more readable) - Remove LOG015 and G004 per-file ignores from pyproject.toml - Fix pytest_ignore_collect hook signature in conftest.py
This commit is contained in:
parent
dd2da6e2cc
commit
b1a5f245a2
@ -5,7 +5,7 @@ import secrets
|
|||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Use cryptographically secure random number generator
|
# Use cryptographically secure random number generator
|
||||||
_rng = secrets.SystemRandom()
|
_rng = secrets.SystemRandom()
|
||||||
@ -812,21 +812,21 @@ class PokerModifierApp:
|
|||||||
self.debug_mode = self.debug_var.get()
|
self.debug_mode = self.debug_var.get()
|
||||||
if self.debug_mode:
|
if self.debug_mode:
|
||||||
self.force_endgame_button.pack(side=tk.LEFT, padx=(0, 10))
|
self.force_endgame_button.pack(side=tk.LEFT, padx=(0, 10))
|
||||||
logging.debug("Debug mode enabled")
|
_logger.debug("Debug mode enabled")
|
||||||
else:
|
else:
|
||||||
self.force_endgame_button.pack_forget()
|
self.force_endgame_button.pack_forget()
|
||||||
self.force_endgame = False
|
self.force_endgame = False
|
||||||
logging.debug("Debug mode disabled")
|
_logger.debug("Debug mode disabled")
|
||||||
|
|
||||||
def toggle_force_endgame(self) -> None:
|
def toggle_force_endgame(self) -> None:
|
||||||
"""Toggle forced endgame mode for testing."""
|
"""Toggle forced endgame mode for testing."""
|
||||||
self.force_endgame = not self.force_endgame
|
self.force_endgame = not self.force_endgame
|
||||||
if self.force_endgame:
|
if self.force_endgame:
|
||||||
self.force_endgame_button.config(text="Stop Force Endgame", bg="#4CAF50")
|
self.force_endgame_button.config(text="Stop Force Endgame", bg="#4CAF50")
|
||||||
logging.debug("Forcing endgame modifiers")
|
_logger.debug("Forcing endgame modifiers")
|
||||||
else:
|
else:
|
||||||
self.force_endgame_button.config(text="Force Endgame", bg="#ff6b6b")
|
self.force_endgame_button.config(text="Force Endgame", bg="#ff6b6b")
|
||||||
logging.debug("Normal modifier selection restored")
|
_logger.debug("Normal modifier selection restored")
|
||||||
|
|
||||||
def is_endgame(self) -> bool:
|
def is_endgame(self) -> bool:
|
||||||
"""Determine if we're in endgame phase."""
|
"""Determine if we're in endgame phase."""
|
||||||
@ -977,7 +977,7 @@ class PokerModifierApp:
|
|||||||
if self.debug_mode:
|
if self.debug_mode:
|
||||||
self.force_endgame_button.config(text="Force Endgame", bg="#ff6b6b")
|
self.force_endgame_button.config(text="Force Endgame", bg="#ff6b6b")
|
||||||
|
|
||||||
logging.info("Game reset to initial state")
|
_logger.info("Game reset to initial state")
|
||||||
|
|
||||||
def add_modifier(self, name: str, description: str) -> None:
|
def add_modifier(self, name: str, description: str) -> None:
|
||||||
"""Add a new modifier to the list."""
|
"""Add a new modifier to the list."""
|
||||||
@ -1005,17 +1005,17 @@ class PokerModifierApp:
|
|||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
"""Start the application."""
|
"""Start the application."""
|
||||||
logging.info("Texas Hold'em Modifier App started!")
|
_logger.info("Texas Hold'em Modifier App started!")
|
||||||
logging.info(
|
_logger.info(
|
||||||
"Available methods: app.get_stats(), app.add_modifier(name, description)"
|
"Available methods: app.get_stats(), app.add_modifier(name, description)"
|
||||||
)
|
)
|
||||||
logging.info(
|
_logger.info(
|
||||||
"Debug features: Toggle debug mode to access force endgame controls"
|
"Debug features: Toggle debug mode to access force endgame controls"
|
||||||
)
|
)
|
||||||
logging.info(f"Default game length: {self.total_game_rounds} rounds")
|
_logger.info("Default game length: %s rounds", self.total_game_rounds)
|
||||||
endgame_pct = int(self.endgame_threshold * 100)
|
endgame_pct = int(self.endgame_threshold * 100)
|
||||||
endgame_rounds = int(self.total_game_rounds * self.endgame_threshold)
|
endgame_rounds = int(self.total_game_rounds * self.endgame_threshold)
|
||||||
logging.info(f"Endgame threshold: {endgame_pct}% ({endgame_rounds} rounds)")
|
_logger.info("Endgame threshold: %s%% (%s rounds)", endgame_pct, endgame_rounds)
|
||||||
self.root.mainloop()
|
self.root.mainloop()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -34,6 +34,8 @@ ignore = [
|
|||||||
# Formatter conflicts - these rules conflict with ruff format
|
# Formatter conflicts - these rules conflict with ruff format
|
||||||
"COM812", # Trailing comma missing (conflicts with formatter)
|
"COM812", # Trailing comma missing (conflicts with formatter)
|
||||||
"ISC001", # Implicit string concatenation (conflicts with formatter)
|
"ISC001", # Implicit string concatenation (conflicts with formatter)
|
||||||
|
# Logging style preference - f-strings are more readable
|
||||||
|
"G004", # Logging statement uses f-string (stylistic preference)
|
||||||
]
|
]
|
||||||
|
|
||||||
# Allow ALL rules to be auto-fixed
|
# Allow ALL rules to be auto-fixed
|
||||||
@ -64,26 +66,15 @@ unfixable = []
|
|||||||
]
|
]
|
||||||
"python_pkg/random_jpg/generate_jpeg.py" = [
|
"python_pkg/random_jpg/generate_jpeg.py" = [
|
||||||
"PTH", # os.path patterns in existing code
|
"PTH", # os.path patterns in existing code
|
||||||
"LOG015", # Root logger in script
|
|
||||||
"G004", # f-strings in logging
|
|
||||||
]
|
]
|
||||||
"python_pkg/tag_divider/tag_divider.py" = [
|
"python_pkg/tag_divider/tag_divider.py" = [
|
||||||
"PTH", # os.path patterns in existing code
|
"PTH", # os.path patterns in existing code
|
||||||
"LOG015", # Root logger in script
|
|
||||||
]
|
|
||||||
"poker_modifier_app/*.py" = [
|
|
||||||
"LOG015", # Root logger in script
|
|
||||||
"G004", # f-strings in logging
|
|
||||||
]
|
]
|
||||||
"poker_modifier_app/poker_modifier_app.py" = [
|
"poker_modifier_app/poker_modifier_app.py" = [
|
||||||
"FBT003", # Boolean positional values in tkinter API calls
|
"FBT003", # Boolean positional values in tkinter API calls
|
||||||
"LOG015", # Root logger in app
|
|
||||||
"G004", # f-strings in logging
|
|
||||||
]
|
]
|
||||||
"python_pkg/download_cats/generate_cats.py" = [
|
"python_pkg/download_cats/generate_cats.py" = [
|
||||||
"PTH", # os.path patterns in existing code
|
"PTH", # os.path patterns in existing code
|
||||||
"LOG015", # Root logger in script
|
|
||||||
"G004", # f-strings in logging
|
|
||||||
]
|
]
|
||||||
"python_pkg/lichess_bot/main.py" = [
|
"python_pkg/lichess_bot/main.py" = [
|
||||||
"C901", # Complex functions handling game lifecycle (run_bot, handle_game)
|
"C901", # Complex functions handling game lifecycle (run_bot, handle_game)
|
||||||
@ -91,60 +82,36 @@ unfixable = []
|
|||||||
"PLR0915", # Long function handling complete game lifecycle
|
"PLR0915", # Long function handling complete game lifecycle
|
||||||
"S603", # Subprocess call for analysis script
|
"S603", # Subprocess call for analysis script
|
||||||
"PTH", # os.path patterns in existing code
|
"PTH", # os.path patterns in existing code
|
||||||
"LOG015", # Root logger in bot
|
|
||||||
"G004", # f-strings in logging
|
|
||||||
]
|
]
|
||||||
"python_pkg/lichess_bot/engine.py" = [
|
"python_pkg/lichess_bot/engine.py" = [
|
||||||
"S603", # Subprocess for engine communication
|
"S603", # Subprocess for engine communication
|
||||||
"PTH", # os.path patterns
|
"PTH", # os.path patterns
|
||||||
"LOG015", # Root logger for debug messages
|
|
||||||
]
|
|
||||||
"python_pkg/lichess_bot/lichess_api.py" = [
|
|
||||||
"LOG015", # Root logger in API client
|
|
||||||
"G004", # f-strings in logging
|
|
||||||
]
|
]
|
||||||
"python_pkg/lichess_bot/utils.py" = [
|
"python_pkg/lichess_bot/utils.py" = [
|
||||||
"PTH", # os.path patterns
|
"PTH", # os.path patterns
|
||||||
"LOG015", # Root logger
|
|
||||||
"G004", # f-strings in logging
|
|
||||||
]
|
]
|
||||||
"python_pkg/lichess_bot/tools/generate_blunder_tests.py" = [
|
"python_pkg/lichess_bot/tools/generate_blunder_tests.py" = [
|
||||||
"PTH", # os.path patterns in tool
|
"PTH", # os.path patterns in tool
|
||||||
"LOG015", # Root logger in tool
|
|
||||||
"G004", # f-strings in logging
|
|
||||||
]
|
]
|
||||||
"python_pkg/stockfish_analysis/analyze_chess_game.py" = [
|
"python_pkg/stockfish_analysis/analyze_chess_game.py" = [
|
||||||
"C901", # Complex main() with many argument combinations and analysis modes
|
"C901", # Complex main() with many argument combinations and analysis modes
|
||||||
"PLR0912", # Complex main() with many argument combinations and analysis modes
|
"PLR0912", # Complex main() with many argument combinations and analysis modes
|
||||||
"PLR0915", # Long main() handling complete analysis workflow
|
"PLR0915", # Long main() handling complete analysis workflow
|
||||||
"PTH", # os.path patterns
|
"PTH", # os.path patterns
|
||||||
"LOG015", # Root logger in analysis tool
|
|
||||||
"G004", # f-strings in logging
|
|
||||||
]
|
|
||||||
"python_pkg/randomize_numbers/random_digits.py" = [
|
|
||||||
"LOG015", # Root logger in script
|
|
||||||
"G004", # f-strings in logging
|
|
||||||
]
|
]
|
||||||
"python_pkg/keyboard_coop/main.py" = [
|
"python_pkg/keyboard_coop/main.py" = [
|
||||||
"FBT003", # Boolean positional values in pygame API calls (e.g., font.render)
|
"FBT003", # Boolean positional values in pygame API calls (e.g., font.render)
|
||||||
"PTH", # os.path patterns
|
"PTH", # os.path patterns
|
||||||
"LOG015", # Root logger in script
|
|
||||||
]
|
]
|
||||||
"python_pkg/screen_locker/screen_lock.py" = [
|
"python_pkg/screen_locker/screen_lock.py" = [
|
||||||
"FBT003", # Boolean positional values in tkinter API calls
|
"FBT003", # Boolean positional values in tkinter API calls
|
||||||
"PTH", # os.path patterns
|
"PTH", # os.path patterns
|
||||||
"LOG015", # Root logger in app
|
|
||||||
"G004", # f-strings in logging
|
|
||||||
]
|
]
|
||||||
"python_pkg/scrape_website/scrape_comics.py" = [
|
"python_pkg/scrape_website/scrape_comics.py" = [
|
||||||
"PTH", # os.path patterns
|
"PTH", # os.path patterns
|
||||||
"LOG015", # Root logger in script
|
|
||||||
"G004", # f-strings in logging
|
|
||||||
]
|
]
|
||||||
"python_pkg/extract_links/main.py" = [
|
"python_pkg/extract_links/main.py" = [
|
||||||
"PTH", # os.path patterns
|
"PTH", # os.path patterns
|
||||||
"LOG015", # Root logger in script
|
|
||||||
"G004", # f-strings in logging
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.ruff.lint.pydocstyle]
|
[tool.ruff.lint.pydocstyle]
|
||||||
|
|||||||
@ -10,7 +10,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
MAX_REQUESTS = 90
|
MAX_REQUESTS = 90
|
||||||
REQUEST_TIMEOUT = 30 # seconds
|
REQUEST_TIMEOUT = 30 # seconds
|
||||||
@ -35,10 +35,10 @@ def _download_single_image(url: str) -> None:
|
|||||||
with open(image_path, "wb") as file:
|
with open(image_path, "wb") as file:
|
||||||
file.write(response.content)
|
file.write(response.content)
|
||||||
|
|
||||||
logging.info(f"Saved {url} as {image_path}")
|
_logger.info("Saved %s as %s", url, image_path)
|
||||||
|
|
||||||
except requests.exceptions.RequestException:
|
except requests.exceptions.RequestException:
|
||||||
logging.exception(f"Failed to download {url}")
|
_logger.exception("Failed to download %s", url)
|
||||||
|
|
||||||
|
|
||||||
requests_send = 0
|
requests_send = 0
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import logging
|
|||||||
import os
|
import os
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class _HrefParser(HTMLParser):
|
class _HrefParser(HTMLParser):
|
||||||
@ -90,7 +90,7 @@ def main() -> int:
|
|||||||
with open(out_path, "w", encoding="utf-8") as f:
|
with open(out_path, "w", encoding="utf-8") as f:
|
||||||
f.writelines(f"*{host}*\n" for host in hosts)
|
f.writelines(f"*{host}*\n" for host in hosts)
|
||||||
|
|
||||||
logging.info(f"Wrote {len(hosts)} host(s) to {out_path}")
|
_logger.info("Wrote %s host(s) to %s", len(hosts), out_path)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import sys
|
|||||||
|
|
||||||
import pygame
|
import pygame
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Use cryptographically secure random number generator
|
# Use cryptographically secure random number generator
|
||||||
_rng = secrets.SystemRandom()
|
_rng = secrets.SystemRandom()
|
||||||
@ -110,7 +110,7 @@ class KeyboardCoopGame:
|
|||||||
# Convert to set for faster lookup (we only need the keys)
|
# Convert to set for faster lookup (we only need the keys)
|
||||||
return set(dictionary_data.keys())
|
return set(dictionary_data.keys())
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
logging.warning(
|
_logger.warning(
|
||||||
"words_dictionary.json not found, using fallback dictionary"
|
"words_dictionary.json not found, using fallback dictionary"
|
||||||
)
|
)
|
||||||
# Fallback to a smaller dictionary if file not found
|
# Fallback to a smaller dictionary if file not found
|
||||||
@ -166,7 +166,7 @@ class KeyboardCoopGame:
|
|||||||
"good",
|
"good",
|
||||||
}
|
}
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
logging.warning(
|
_logger.warning(
|
||||||
"Error reading words_dictionary.json, using fallback dictionary"
|
"Error reading words_dictionary.json, using fallback dictionary"
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -8,6 +8,8 @@ import subprocess
|
|||||||
|
|
||||||
import chess
|
import chess
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class RandomEngine:
|
class RandomEngine:
|
||||||
"""Thin wrapper around the C engine in C/lichess_random_engine/random_engine.
|
"""Thin wrapper around the C engine in C/lichess_random_engine/random_engine.
|
||||||
@ -160,6 +162,6 @@ class RandomEngine:
|
|||||||
ensure_ascii=False,
|
ensure_ascii=False,
|
||||||
)
|
)
|
||||||
except (json.JSONDecodeError, KeyError, TypeError):
|
except (json.JSONDecodeError, KeyError, TypeError):
|
||||||
logging.debug("Failed to parse engine JSON output")
|
_logger.debug("Failed to parse engine JSON output")
|
||||||
|
|
||||||
return cand_score, cand_expl, best_move, best_expl
|
return cand_score, cand_expl, best_move, best_expl
|
||||||
|
|||||||
@ -10,6 +10,8 @@ import time
|
|||||||
import chess
|
import chess
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
LICHESS_API = "https://lichess.org"
|
LICHESS_API = "https://lichess.org"
|
||||||
|
|
||||||
|
|
||||||
@ -43,11 +45,11 @@ class LichessAPI:
|
|||||||
- Optionally raises for status.
|
- Optionally raises for status.
|
||||||
"""
|
"""
|
||||||
t0 = time.monotonic()
|
t0 = time.monotonic()
|
||||||
logging.info(f"HTTP {method} {url} -> sending")
|
_logger.info("HTTP %s %s -> sending", method, url)
|
||||||
try:
|
try:
|
||||||
r = self.session.request(method, url, **kwargs) # type: ignore[arg-type]
|
r = self.session.request(method, url, **kwargs) # type: ignore[arg-type]
|
||||||
except Exception:
|
except Exception:
|
||||||
logging.exception(f"HTTP {method} {url} -> exception")
|
_logger.exception("HTTP %s %s -> exception", method, url)
|
||||||
raise
|
raise
|
||||||
elapsed = time.monotonic() - t0
|
elapsed = time.monotonic() - t0
|
||||||
status = r.status_code
|
status = r.status_code
|
||||||
@ -60,14 +62,20 @@ class LichessAPI:
|
|||||||
except (AttributeError, TypeError):
|
except (AttributeError, TypeError):
|
||||||
snippet = None
|
snippet = None
|
||||||
if snippet:
|
if snippet:
|
||||||
logging.warning(
|
_logger.warning(
|
||||||
f"HTTP {method} {url} -> {status} "
|
"HTTP %s %s -> %s in %.2fs body='%s'",
|
||||||
f"in {elapsed:.2f}s body='{snippet}'"
|
method,
|
||||||
|
url,
|
||||||
|
status,
|
||||||
|
elapsed,
|
||||||
|
snippet,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logging.warning(f"HTTP {method} {url} -> {status} in {elapsed:.2f}s")
|
_logger.warning(
|
||||||
|
"HTTP %s %s -> %s in %.2fs", method, url, status, elapsed
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
logging.info(f"HTTP {method} {url} -> {status} in {elapsed:.2f}s")
|
_logger.info("HTTP %s %s -> %s in %.2fs", method, url, status, elapsed)
|
||||||
if raise_for_status:
|
if raise_for_status:
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
return r
|
return r
|
||||||
@ -91,11 +99,11 @@ class LichessAPI:
|
|||||||
try:
|
try:
|
||||||
yield json.loads(line)
|
yield json.loads(line)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
logging.debug(f"Skipping non-JSON line: {line}")
|
_logger.debug("Skipping non-JSON line: %s", line)
|
||||||
except requests.HTTPError as e:
|
except requests.HTTPError as e:
|
||||||
status = getattr(e.response, "status_code", None)
|
status = getattr(e.response, "status_code", None)
|
||||||
if status == HTTPStatus.TOO_MANY_REQUESTS:
|
if status == HTTPStatus.TOO_MANY_REQUESTS:
|
||||||
logging.warning("Event stream hit 429; backing off")
|
_logger.warning("Event stream hit 429; backing off")
|
||||||
time.sleep(backoff)
|
time.sleep(backoff)
|
||||||
backoff = min(8.0, backoff * 2)
|
backoff = min(8.0, backoff * 2)
|
||||||
continue
|
continue
|
||||||
@ -160,7 +168,9 @@ class LichessAPI:
|
|||||||
try:
|
try:
|
||||||
yield json.loads(line)
|
yield json.loads(line)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
logging.debug(f"Skipping non-JSON line in game {game_id}: {line}")
|
_logger.debug(
|
||||||
|
"Skipping non-JSON line in game %s: %s", game_id, line
|
||||||
|
)
|
||||||
|
|
||||||
def make_move(self, game_id: str, move: chess.Move) -> None:
|
def make_move(self, game_id: str, move: chess.Move) -> None:
|
||||||
"""Submit a move to an active game."""
|
"""Submit a move to an active game."""
|
||||||
@ -171,7 +181,7 @@ class LichessAPI:
|
|||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
return
|
return
|
||||||
if r.status_code == HTTPStatus.TOO_MANY_REQUESTS:
|
if r.status_code == HTTPStatus.TOO_MANY_REQUESTS:
|
||||||
logging.warning(f"HTTP POST {url} -> 429; retrying once after 0.5s")
|
_logger.warning("HTTP POST %s -> 429; retrying once after 0.5s", url)
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
r = self._request("POST", url, timeout=30)
|
r = self._request("POST", url, timeout=30)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
|
|||||||
@ -18,6 +18,8 @@ from python_pkg.lichess_bot.engine import RandomEngine
|
|||||||
from python_pkg.lichess_bot.lichess_api import LichessAPI
|
from python_pkg.lichess_bot.lichess_api import LichessAPI
|
||||||
from python_pkg.lichess_bot.utils import backoff_sleep, get_and_increment_version
|
from python_pkg.lichess_bot.utils import backoff_sleep, get_and_increment_version
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def _apply_move_to_board(board: chess.Board, move: str, game_id: str) -> None:
|
def _apply_move_to_board(board: chess.Board, move: str, game_id: str) -> None:
|
||||||
"""Apply a single move to the board, logging errors.
|
"""Apply a single move to the board, logging errors.
|
||||||
@ -30,11 +32,12 @@ def _apply_move_to_board(board: chess.Board, move: str, game_id: str) -> None:
|
|||||||
try:
|
try:
|
||||||
board.push_uci(move)
|
board.push_uci(move)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
logging.debug(f"Game {game_id}: could not apply move {move}")
|
_logger.debug(f"Game {game_id}: could not apply move {move}")
|
||||||
|
|
||||||
|
|
||||||
def run_bot(log_level: str = "INFO", *, decline_correspondence: bool = False) -> None:
|
def run_bot(log_level: str = "INFO", *, decline_correspondence: bool = False) -> None:
|
||||||
"""Start the bot and listen for incoming events."""
|
"""Start the bot and listen for incoming events."""
|
||||||
|
# Configure root logger for console output
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level=getattr(logging, log_level.upper(), logging.INFO),
|
level=getattr(logging, log_level.upper(), logging.INFO),
|
||||||
format="[%(asctime)s] %(levelname)s %(threadName)s: %(message)s",
|
format="[%(asctime)s] %(levelname)s %(threadName)s: %(message)s",
|
||||||
@ -45,17 +48,17 @@ def run_bot(log_level: str = "INFO", *, decline_correspondence: bool = False) ->
|
|||||||
msg = "LICHESS_TOKEN environment variable is required"
|
msg = "LICHESS_TOKEN environment variable is required"
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
|
|
||||||
logging.info("Token present. Initializing client and engine...")
|
_logger.info("Token present. Initializing client and engine...")
|
||||||
# Self-incrementing bot version (persisted on disk)
|
# Self-incrementing bot version (persisted on disk)
|
||||||
bot_version = get_and_increment_version()
|
bot_version = get_and_increment_version()
|
||||||
logging.info(f"Bot version: v{bot_version}")
|
_logger.info(f"Bot version: v{bot_version}")
|
||||||
api = LichessAPI(token)
|
api = LichessAPI(token)
|
||||||
engine = RandomEngine()
|
engine = RandomEngine()
|
||||||
|
|
||||||
game_threads = {}
|
game_threads = {}
|
||||||
|
|
||||||
def handle_game(game_id: str, my_color: str | None = 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}]")
|
_logger.info(f"Starting game thread for {game_id} [bot v{bot_version}]")
|
||||||
board = chess.Board()
|
board = chess.Board()
|
||||||
color: str | None = my_color
|
color: str | None = my_color
|
||||||
# Track how many moves we have already processed;
|
# Track how many moves we have already processed;
|
||||||
@ -122,7 +125,7 @@ def run_bot(log_level: str = "INFO", *, decline_correspondence: bool = False) ->
|
|||||||
color = "white"
|
color = "white"
|
||||||
elif me == black_id:
|
elif me == black_id:
|
||||||
color = "black"
|
color = "black"
|
||||||
logging.info(f"Game {game_id}: joined as {color} (gameFull)")
|
_logger.info(f"Game {game_id}: joined as {color} (gameFull)")
|
||||||
else:
|
else:
|
||||||
moves = event.get("moves", "")
|
moves = event.get("moves", "")
|
||||||
status = event.get("status")
|
status = event.get("status")
|
||||||
@ -138,11 +141,11 @@ def run_bot(log_level: str = "INFO", *, decline_correspondence: bool = False) ->
|
|||||||
|
|
||||||
moves_list = moves.split() if moves else []
|
moves_list = moves.split() if moves else []
|
||||||
new_len = len(moves_list)
|
new_len = len(moves_list)
|
||||||
logging.info(
|
_logger.info(
|
||||||
f"Game {game_id}: event={et}, moves={new_len}, color={color}"
|
f"Game {game_id}: event={et}, moves={new_len}, color={color}"
|
||||||
)
|
)
|
||||||
if new_len == last_handled_len:
|
if new_len == last_handled_len:
|
||||||
logging.debug(
|
_logger.debug(
|
||||||
f"Game {game_id}: position unchanged "
|
f"Game {game_id}: position unchanged "
|
||||||
f"(len={new_len}), skipping"
|
f"(len={new_len}), skipping"
|
||||||
)
|
)
|
||||||
@ -154,7 +157,7 @@ def run_bot(log_level: str = "INFO", *, decline_correspondence: bool = False) ->
|
|||||||
_apply_move_to_board(board, m, game_id)
|
_apply_move_to_board(board, m, game_id)
|
||||||
|
|
||||||
if color is None:
|
if color is None:
|
||||||
logging.info(
|
_logger.info(
|
||||||
f"Game {game_id}: color unknown yet; waiting for gameFull"
|
f"Game {game_id}: color unknown yet; waiting for gameFull"
|
||||||
)
|
)
|
||||||
# Do not mark this position handled on gameFull;
|
# Do not mark this position handled on gameFull;
|
||||||
@ -167,7 +170,7 @@ def run_bot(log_level: str = "INFO", *, decline_correspondence: bool = False) ->
|
|||||||
my_turn = (is_white_turn and color == "white") or (
|
my_turn = (is_white_turn and color == "white") or (
|
||||||
(not is_white_turn) and color == "black"
|
(not is_white_turn) and color == "black"
|
||||||
)
|
)
|
||||||
logging.info(
|
_logger.info(
|
||||||
f"Game {game_id}: "
|
f"Game {game_id}: "
|
||||||
f"turn={'white' if is_white_turn else 'black'}, "
|
f"turn={'white' if is_white_turn else 'black'}, "
|
||||||
f"my_turn={my_turn}"
|
f"my_turn={my_turn}"
|
||||||
@ -204,7 +207,7 @@ def run_bot(log_level: str = "INFO", *, decline_correspondence: bool = False) ->
|
|||||||
board, time_budget_sec=budget
|
board, time_budget_sec=budget
|
||||||
)
|
)
|
||||||
if move is None:
|
if move is None:
|
||||||
logging.info(
|
_logger.info(
|
||||||
f"Game {game_id}: no legal moves (game likely over)"
|
f"Game {game_id}: no legal moves (game likely over)"
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
@ -212,12 +215,12 @@ def run_bot(log_level: str = "INFO", *, decline_correspondence: bool = False) ->
|
|||||||
# Double-check legality just before sending
|
# Double-check legality just before sending
|
||||||
# to avoid 400s when state changed.
|
# to avoid 400s when state changed.
|
||||||
if move not in board.legal_moves:
|
if move not in board.legal_moves:
|
||||||
logging.info(
|
_logger.info(
|
||||||
f"Game {game_id}: selected move "
|
f"Game {game_id}: selected move "
|
||||||
"no longer legal; skipping send"
|
"no longer legal; skipping send"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logging.info(
|
_logger.info(
|
||||||
f"Game {game_id}: playing {move.uci()} "
|
f"Game {game_id}: playing {move.uci()} "
|
||||||
f"(budget={budget:.2f}s, "
|
f"(budget={budget:.2f}s, "
|
||||||
f"my_time_left={time_left_sec:.1f}s, "
|
f"my_time_left={time_left_sec:.1f}s, "
|
||||||
@ -231,7 +234,7 @@ def run_bot(log_level: str = "INFO", *, decline_correspondence: bool = False) ->
|
|||||||
)
|
)
|
||||||
api.make_move(game_id, move)
|
api.make_move(game_id, move)
|
||||||
except requests.RequestException as e:
|
except requests.RequestException as e:
|
||||||
logging.warning(
|
_logger.warning(
|
||||||
f"Game {game_id}: move {move.uci()} failed: {e}"
|
f"Game {game_id}: move {move.uci()} failed: {e}"
|
||||||
)
|
)
|
||||||
# Mark this position as handled on authoritative
|
# Mark this position as handled on authoritative
|
||||||
@ -240,12 +243,12 @@ def run_bot(log_level: str = "INFO", *, decline_correspondence: bool = False) ->
|
|||||||
if et == "gameState" or (my_turn and allow_move):
|
if et == "gameState" or (my_turn and allow_move):
|
||||||
last_handled_len = new_len
|
last_handled_len = new_len
|
||||||
if status in {"mate", "resign", "stalemate", "timeout", "draw"}:
|
if status in {"mate", "resign", "stalemate", "timeout", "draw"}:
|
||||||
logging.info(f"Game {game_id} finished: {status}")
|
_logger.info(f"Game {game_id} finished: {status}")
|
||||||
break
|
break
|
||||||
elif et in {"chatLine", "opponentGone"}:
|
elif et in {"chatLine", "opponentGone"}:
|
||||||
continue
|
continue
|
||||||
except requests.RequestException:
|
except requests.RequestException:
|
||||||
logging.exception(f"Game {game_id} thread error")
|
_logger.exception(f"Game {game_id} thread error")
|
||||||
finally:
|
finally:
|
||||||
# On game end, write full PGN to the log file
|
# On game end, write full PGN to the log file
|
||||||
try:
|
try:
|
||||||
@ -286,7 +289,7 @@ def run_bot(log_level: str = "INFO", *, decline_correspondence: bool = False) ->
|
|||||||
except TypeError:
|
except TypeError:
|
||||||
total_plies = 0
|
total_plies = 0
|
||||||
|
|
||||||
logging.info(
|
_logger.info(
|
||||||
f"Game {game_id}: starting post-game "
|
f"Game {game_id}: starting post-game "
|
||||||
f"analysis ({total_plies} plies)"
|
f"analysis ({total_plies} plies)"
|
||||||
)
|
)
|
||||||
@ -318,13 +321,13 @@ def run_bot(log_level: str = "INFO", *, decline_correspondence: bool = False) ->
|
|||||||
)
|
)
|
||||||
if total_plies:
|
if total_plies:
|
||||||
pct = analyzed / total_plies * 100.0
|
pct = analyzed / total_plies * 100.0
|
||||||
logging.info(
|
_logger.info(
|
||||||
f"Game {game_id}: analysis progress "
|
f"Game {game_id}: analysis progress "
|
||||||
f"{analyzed}/{total_plies} "
|
f"{analyzed}/{total_plies} "
|
||||||
f"({pct:.0f}%), left {left}"
|
f"({pct:.0f}%), left {left}"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logging.info(
|
_logger.info(
|
||||||
f"Game {game_id}: analysis progress "
|
f"Game {game_id}: analysis progress "
|
||||||
f"{analyzed} plies (total unknown)"
|
f"{analyzed} plies (total unknown)"
|
||||||
)
|
)
|
||||||
@ -334,20 +337,20 @@ def run_bot(log_level: str = "INFO", *, decline_correspondence: bool = False) ->
|
|||||||
ret = proc.wait()
|
ret = proc.wait()
|
||||||
analysis_text = "".join(lines)
|
analysis_text = "".join(lines)
|
||||||
if ret != 0:
|
if ret != 0:
|
||||||
logging.warning(
|
_logger.warning(
|
||||||
f"Game {game_id}: analysis script "
|
f"Game {game_id}: analysis script "
|
||||||
f"exited with code {ret}"
|
f"exited with code {ret}"
|
||||||
)
|
)
|
||||||
if stderr_text:
|
if stderr_text:
|
||||||
analysis_text += "\n[stderr]\n" + stderr_text
|
analysis_text += "\n[stderr]\n" + stderr_text
|
||||||
logging.info(f"Game {game_id}: analysis complete")
|
_logger.info(f"Game {game_id}: analysis complete")
|
||||||
else:
|
else:
|
||||||
logging.info(
|
_logger.info(
|
||||||
f"Game {game_id}: analysis script not found "
|
f"Game {game_id}: analysis script not found "
|
||||||
f"at {analyze_script}; skipping analysis"
|
f"at {analyze_script}; skipping analysis"
|
||||||
)
|
)
|
||||||
except (subprocess.SubprocessError, OSError) as e:
|
except (subprocess.SubprocessError, OSError) as e:
|
||||||
logging.debug(f"Game {game_id}: analysis run failed: {e}")
|
_logger.debug(f"Game {game_id}: analysis run failed: {e}")
|
||||||
|
|
||||||
# Insert analysis before the PGN section so future runs
|
# Insert analysis before the PGN section so future runs
|
||||||
# can still parse PGN cleanly
|
# can still parse PGN cleanly
|
||||||
@ -399,12 +402,12 @@ def run_bot(log_level: str = "INFO", *, decline_correspondence: bool = False) ->
|
|||||||
with open(game_log_path, "w", encoding="utf-8") as f:
|
with open(game_log_path, "w", encoding="utf-8") as f:
|
||||||
f.write(new_content)
|
f.write(new_content)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
logging.debug(
|
_logger.debug(
|
||||||
f"Game {game_id}: could not write analysis to log: {e}"
|
f"Game {game_id}: could not write analysis to log: {e}"
|
||||||
)
|
)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
logging.debug(f"Game {game_id}: could not write PGN: {e}")
|
_logger.debug(f"Game {game_id}: could not write PGN: {e}")
|
||||||
logging.info(f"Ending game thread for {game_id}")
|
_logger.info(f"Ending game thread for {game_id}")
|
||||||
|
|
||||||
def _process_event_stream() -> None:
|
def _process_event_stream() -> None:
|
||||||
"""Process events from the Lichess event stream.
|
"""Process events from the Lichess event stream.
|
||||||
@ -423,10 +426,10 @@ def run_bot(log_level: str = "INFO", *, decline_correspondence: bool = False) ->
|
|||||||
or not decline_correspondence
|
or not decline_correspondence
|
||||||
)
|
)
|
||||||
if variant == "standard" and perf_ok and not_corr:
|
if variant == "standard" and perf_ok and not_corr:
|
||||||
logging.info(f"Accepting challenge {ch_id} ({speed})")
|
_logger.info(f"Accepting challenge {ch_id} ({speed})")
|
||||||
api.accept_challenge(ch_id)
|
api.accept_challenge(ch_id)
|
||||||
else:
|
else:
|
||||||
logging.info(
|
_logger.info(
|
||||||
f"Declining challenge {ch_id} "
|
f"Declining challenge {ch_id} "
|
||||||
f"(variant={variant}, speed={speed})"
|
f"(variant={variant}, speed={speed})"
|
||||||
)
|
)
|
||||||
@ -445,9 +448,9 @@ def run_bot(log_level: str = "INFO", *, decline_correspondence: bool = False) ->
|
|||||||
|
|
||||||
elif event.get("type") == "gameFinish":
|
elif event.get("type") == "gameFinish":
|
||||||
game_id = event["game"]["id"]
|
game_id = event["game"]["id"]
|
||||||
logging.info(f"Game finished event: {game_id}")
|
_logger.info(f"Game finished event: {game_id}")
|
||||||
else:
|
else:
|
||||||
logging.debug(f"Unhandled event: {json.dumps(event)}")
|
_logger.debug(f"Unhandled event: {json.dumps(event)}")
|
||||||
|
|
||||||
def _run_event_loop_iteration() -> int:
|
def _run_event_loop_iteration() -> int:
|
||||||
"""Run one iteration of the event loop with error handling.
|
"""Run one iteration of the event loop with error handling.
|
||||||
@ -458,14 +461,14 @@ def run_bot(log_level: str = "INFO", *, decline_correspondence: bool = False) ->
|
|||||||
try:
|
try:
|
||||||
_process_event_stream()
|
_process_event_stream()
|
||||||
except requests.RequestException as e:
|
except requests.RequestException as e:
|
||||||
logging.warning(f"Event stream error: {e}")
|
_logger.warning(f"Event stream error: {e}")
|
||||||
return backoff_sleep(backoff)
|
return backoff_sleep(backoff)
|
||||||
else:
|
else:
|
||||||
# If stream ends normally, reset backoff
|
# If stream ends normally, reset backoff
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
# Main event stream: challenge and game start events
|
# Main event stream: challenge and game start events
|
||||||
logging.info("Connecting to Lichess event stream. Waiting for challenges...")
|
_logger.info("Connecting to Lichess event stream. Waiting for challenges...")
|
||||||
backoff = 0
|
backoff = 0
|
||||||
while True:
|
while True:
|
||||||
backoff = _run_event_loop_iteration()
|
backoff = _run_event_loop_iteration()
|
||||||
|
|||||||
@ -11,11 +11,16 @@ if ROOT not in sys.path:
|
|||||||
sys.path.insert(0, ROOT)
|
sys.path.insert(0, ROOT)
|
||||||
|
|
||||||
|
|
||||||
def pytest_ignore_collect(collection_path: Path, _: pytest.Config) -> bool | None:
|
def pytest_ignore_collect(collection_path: Path, config: pytest.Config) -> bool | None:
|
||||||
"""Ignore per-game blunder test files; keep only the unified one.
|
"""Ignore per-game blunder test files; keep only the unified one.
|
||||||
|
|
||||||
This lets us keep historical files in the repo without collecting them.
|
This lets us keep historical files in the repo without collecting them.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
collection_path: Path being collected.
|
||||||
|
config: Pytest config object (unused).
|
||||||
"""
|
"""
|
||||||
|
del config # unused
|
||||||
basename = collection_path.name
|
basename = collection_path.name
|
||||||
return bool(
|
return bool(
|
||||||
basename.startswith("test_blunders_") and basename != "test_blunders_all.py"
|
basename.startswith("test_blunders_") and basename != "test_blunders_all.py"
|
||||||
|
|||||||
@ -43,7 +43,7 @@ import sys
|
|||||||
import chess
|
import chess
|
||||||
import chess.pgn
|
import chess.pgn
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Expected columns in the log file:
|
# Expected columns in the log file:
|
||||||
# ply, side, move, played_eval, best_eval, loss, class, best_suggestion
|
# ply, side, move, played_eval, best_eval, loss, class, best_suggestion
|
||||||
@ -162,7 +162,7 @@ def fen_and_uci_for_blunders(
|
|||||||
try:
|
try:
|
||||||
move = board.parse_san(main_sans[bl.ply - 1])
|
move = board.parse_san(main_sans[bl.ply - 1])
|
||||||
except ValueError:
|
except ValueError:
|
||||||
logging.debug("Skipping blunder: failed to parse fallback move")
|
_logger.debug("Skipping blunder: failed to parse fallback move")
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
@ -339,7 +339,7 @@ def _process_single_log(log_path: str) -> int:
|
|||||||
)
|
)
|
||||||
unified = os.path.abspath(unified)
|
unified = os.path.abspath(unified)
|
||||||
added = append_cases_to_unified_test(unified, cases)
|
added = append_cases_to_unified_test(unified, cases)
|
||||||
logging.info(
|
_logger.info(
|
||||||
f"Appended {added} new blunder checks to "
|
f"Appended {added} new blunder checks to "
|
||||||
f"{os.path.relpath(unified)} (game {game_id})."
|
f"{os.path.relpath(unified)} (game {game_id})."
|
||||||
)
|
)
|
||||||
@ -376,7 +376,7 @@ def _read_log_file(log_path: str) -> tuple[str | None, int | None]:
|
|||||||
with open(log_path, encoding="utf-8") as fh:
|
with open(log_path, encoding="utf-8") as fh:
|
||||||
return fh.read(), None
|
return fh.read(), None
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
logging.exception(f"Log file not found: {log_path}")
|
_logger.exception(f"Log file not found: {log_path}")
|
||||||
return None, 2
|
return None, 2
|
||||||
|
|
||||||
|
|
||||||
@ -385,10 +385,10 @@ def _parse_blunders(text: str, base: str) -> tuple[list[Blunder] | None, int | N
|
|||||||
try:
|
try:
|
||||||
blunders = parse_columns_for_blunders(text)
|
blunders = parse_columns_for_blunders(text)
|
||||||
except Exception:
|
except Exception:
|
||||||
logging.exception(f"Error parsing Columns in {base}")
|
_logger.exception(f"Error parsing Columns in {base}")
|
||||||
return None, 2
|
return None, 2
|
||||||
if not blunders:
|
if not blunders:
|
||||||
logging.warning(f"No blunders found in Columns section: {base}")
|
_logger.warning(f"No blunders found in Columns section: {base}")
|
||||||
return None, 1
|
return None, 1
|
||||||
return blunders, None
|
return blunders, None
|
||||||
|
|
||||||
@ -399,16 +399,16 @@ def _extract_cases(
|
|||||||
"""Extract FEN/UCI cases from PGN. Returns (cases, None) or (None, error_code)."""
|
"""Extract FEN/UCI cases from PGN. Returns (cases, None) or (None, error_code)."""
|
||||||
pgn_text = extract_pgn(text)
|
pgn_text = extract_pgn(text)
|
||||||
if not pgn_text:
|
if not pgn_text:
|
||||||
logging.warning(f"No PGN section found: {base}")
|
_logger.warning(f"No PGN section found: {base}")
|
||||||
return None, 1
|
return None, 1
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cases = fen_and_uci_for_blunders(pgn_text, blunders)
|
cases = fen_and_uci_for_blunders(pgn_text, blunders)
|
||||||
except Exception:
|
except Exception:
|
||||||
logging.exception(f"Error converting SAN to UCI in {base}")
|
_logger.exception(f"Error converting SAN to UCI in {base}")
|
||||||
return None, 2
|
return None, 2
|
||||||
if not cases:
|
if not cases:
|
||||||
logging.warning(f"Failed to reconstruct any blunder positions from PGN: {base}")
|
_logger.warning(f"Failed to reconstruct any blunder positions from PGN: {base}")
|
||||||
return None, 1
|
return None, 1
|
||||||
return cases, None
|
return cases, None
|
||||||
|
|
||||||
@ -421,7 +421,7 @@ def main(argv: list[str]) -> int:
|
|||||||
# No argument: process all logs in past_games
|
# No argument: process all logs in past_games
|
||||||
if len(argv) == 1:
|
if len(argv) == 1:
|
||||||
if not os.path.isdir(past_dir):
|
if not os.path.isdir(past_dir):
|
||||||
logging.error(f"No past_games directory found at {past_dir}")
|
_logger.error(f"No past_games directory found at {past_dir}")
|
||||||
return 2
|
return 2
|
||||||
logs = [
|
logs = [
|
||||||
os.path.join(past_dir, name)
|
os.path.join(past_dir, name)
|
||||||
@ -429,7 +429,7 @@ def main(argv: list[str]) -> int:
|
|||||||
if re.match(r"lichess_bot_game_[A-Za-z0-9]+\.log$", name)
|
if re.match(r"lichess_bot_game_[A-Za-z0-9]+\.log$", name)
|
||||||
]
|
]
|
||||||
if not logs:
|
if not logs:
|
||||||
logging.warning(f"No logs found in {past_dir}")
|
_logger.warning(f"No logs found in {past_dir}")
|
||||||
return 1
|
return 1
|
||||||
# Sort by mtime ascending for determinism
|
# Sort by mtime ascending for determinism
|
||||||
logs.sort(key=lambda p: os.path.getmtime(p))
|
logs.sort(key=lambda p: os.path.getmtime(p))
|
||||||
@ -438,7 +438,7 @@ def main(argv: list[str]) -> int:
|
|||||||
rc = _process_single_log(lp)
|
rc = _process_single_log(lp)
|
||||||
if rc == 0:
|
if rc == 0:
|
||||||
ok += 1
|
ok += 1
|
||||||
logging.info(
|
_logger.info(
|
||||||
f"Processed {len(logs)} logs from {past_dir}, "
|
f"Processed {len(logs)} logs from {past_dir}, "
|
||||||
f"succeeded: {ok}, failed: {len(logs) - ok}"
|
f"succeeded: {ok}, failed: {len(logs) - ok}"
|
||||||
)
|
)
|
||||||
@ -459,7 +459,7 @@ def main(argv: list[str]) -> int:
|
|||||||
candidate_path = maybe
|
candidate_path = maybe
|
||||||
|
|
||||||
if not candidate_path:
|
if not candidate_path:
|
||||||
logging.info("Usage: generate_blunder_tests.py [<game_id>|</path/to/log>]")
|
_logger.info("Usage: generate_blunder_tests.py [<game_id>|</path/to/log>]")
|
||||||
return 2
|
return 2
|
||||||
|
|
||||||
return _process_single_log(candidate_path)
|
return _process_single_log(candidate_path)
|
||||||
|
|||||||
@ -4,6 +4,8 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def _version_file_path() -> str:
|
def _version_file_path() -> str:
|
||||||
"""Return the path to the persistent bot version file.
|
"""Return the path to the persistent bot version file.
|
||||||
@ -44,7 +46,7 @@ def get_and_increment_version() -> int:
|
|||||||
with open(path, "w") as f:
|
with open(path, "w") as f:
|
||||||
f.write(str(new_version))
|
f.write(str(new_version))
|
||||||
except OSError:
|
except OSError:
|
||||||
logging.debug("Could not persist bot version to %s", path)
|
_logger.debug("Could not persist bot version to %s", path)
|
||||||
|
|
||||||
return new_version
|
return new_version
|
||||||
|
|
||||||
@ -57,6 +59,6 @@ def backoff_sleep(current_backoff: int, base: float = 0.5, cap: float = 8.0) ->
|
|||||||
- cap: maximum delay in seconds
|
- cap: maximum delay in seconds
|
||||||
"""
|
"""
|
||||||
delay = min(cap, base * (2**current_backoff))
|
delay = min(cap, base * (2**current_backoff))
|
||||||
logging.info(f"Backing off for {delay:.1f}s")
|
_logger.info("Backing off for %.1fs", delay)
|
||||||
time.sleep(delay)
|
time.sleep(delay)
|
||||||
return min(current_backoff + 1, 10)
|
return min(current_backoff + 1, 10)
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import secrets
|
|||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Use cryptographically secure random number generator
|
# Use cryptographically secure random number generator
|
||||||
_rng = secrets.SystemRandom()
|
_rng = secrets.SystemRandom()
|
||||||
@ -142,15 +142,15 @@ if __name__ == "__main__":
|
|||||||
folder = f"generated_images_{timestamp}"
|
folder = f"generated_images_{timestamp}"
|
||||||
|
|
||||||
# Display used parameters
|
# Display used parameters
|
||||||
logging.info(
|
_logger.info(
|
||||||
f"Generating {args.num_images} image(s) with the following parameters:"
|
"Generating %s image(s) with the following parameters:", args.num_images
|
||||||
)
|
)
|
||||||
logging.info(f" Size: {args.size}")
|
_logger.info(" Size: %s", args.size)
|
||||||
logging.info(f" Colors: {args.colors}")
|
_logger.info(" Colors: %s", args.colors)
|
||||||
logging.info(f" Block size: {args.block_size}")
|
_logger.info(" Block size: %s", args.block_size)
|
||||||
logging.info(f" Base output path: {args.output_path}")
|
_logger.info(" Base output path: %s", args.output_path)
|
||||||
logging.info(f" Quality: {args.quality}")
|
_logger.info(" Quality: %s", args.quality)
|
||||||
logging.info(f" Output folder: {folder}")
|
_logger.info(" Output folder: %s", folder)
|
||||||
|
|
||||||
# Generate the specified number of images
|
# Generate the specified number of images
|
||||||
config = ImageConfig(
|
config = ImageConfig(
|
||||||
@ -162,4 +162,4 @@ if __name__ == "__main__":
|
|||||||
)
|
)
|
||||||
for i in range(1, args.num_images + 1):
|
for i in range(1, args.num_images + 1):
|
||||||
output_path = generate_bloated_jpeg(config, i, folder)
|
output_path = generate_bloated_jpeg(config, i, folder)
|
||||||
logging.info(f"Image {i} saved to {os.path.abspath(output_path)}")
|
_logger.info("Image %s saved to %s", i, os.path.abspath(output_path))
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import re
|
|||||||
import secrets
|
import secrets
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Use cryptographically secure random number generator
|
# Use cryptographically secure random number generator
|
||||||
_rng = secrets.SystemRandom()
|
_rng = secrets.SystemRandom()
|
||||||
@ -73,7 +73,7 @@ MIN_ARGS = 2
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
if len(sys.argv) < MIN_ARGS:
|
if len(sys.argv) < MIN_ARGS:
|
||||||
logging.info(
|
_logger.info(
|
||||||
"Usage: python random_digits.py <number1> <number2> ... "
|
"Usage: python random_digits.py <number1> <number2> ... "
|
||||||
"[min_percentage max_percentage]"
|
"[min_percentage max_percentage]"
|
||||||
)
|
)
|
||||||
@ -83,7 +83,7 @@ if __name__ == "__main__":
|
|||||||
numbers, decimal_counts = parse_input(input_string)
|
numbers, decimal_counts = parse_input(input_string)
|
||||||
|
|
||||||
if len(numbers) == 0:
|
if len(numbers) == 0:
|
||||||
logging.error("No valid numbers provided.")
|
_logger.error("No valid numbers provided.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
min_percentage = DEFAULT_MIN_PERCENTAGE
|
min_percentage = DEFAULT_MIN_PERCENTAGE
|
||||||
@ -103,9 +103,9 @@ if __name__ == "__main__":
|
|||||||
format_str = f".{decimal_counts[i]}f"
|
format_str = f".{decimal_counts[i]}f"
|
||||||
formatted_numbers.append(float(format(num, format_str)))
|
formatted_numbers.append(float(format(num, format_str)))
|
||||||
|
|
||||||
logging.info(f"Original numbers: {numbers}")
|
_logger.info("Original numbers: %s", numbers)
|
||||||
logging.info(f"Randomized numbers: {formatted_numbers}")
|
_logger.info("Randomized numbers: %s", formatted_numbers)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
logging.exception("Error processing numbers")
|
_logger.exception("Error processing numbers")
|
||||||
logging.exception("Please provide valid numbers and percentages.")
|
_logger.exception("Please provide valid numbers and percentages.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|||||||
@ -10,7 +10,7 @@ from selenium import webdriver
|
|||||||
from selenium.common.exceptions import NoSuchElementException
|
from selenium.common.exceptions import NoSuchElementException
|
||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
REQUEST_TIMEOUT = 30 # seconds
|
REQUEST_TIMEOUT = 30 # seconds
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ driver = webdriver.Chrome()
|
|||||||
|
|
||||||
# Open the website from the passed argument
|
# Open the website from the passed argument
|
||||||
url = args.url
|
url = args.url
|
||||||
logging.info(f"Opening the website: {url}")
|
_logger.info("Opening the website: %s", url)
|
||||||
driver.get(url)
|
driver.get(url)
|
||||||
|
|
||||||
|
|
||||||
@ -38,13 +38,13 @@ def download_image(url: str) -> bool:
|
|||||||
|
|
||||||
# Check if the image already exists
|
# Check if the image already exists
|
||||||
if os.path.exists(image_name):
|
if os.path.exists(image_name):
|
||||||
logging.info(f"Image {image_name} already exists, skipping download.")
|
_logger.info("Image %s already exists, skipping download.", image_name)
|
||||||
return False
|
return False
|
||||||
logging.info(f"Downloading image from URL: {url}")
|
_logger.info("Downloading image from URL: %s", url)
|
||||||
img_data = requests.get(url, timeout=REQUEST_TIMEOUT).content
|
img_data = requests.get(url, timeout=REQUEST_TIMEOUT).content
|
||||||
with open(image_name, "wb") as handler:
|
with open(image_name, "wb") as handler:
|
||||||
handler.write(img_data)
|
handler.write(img_data)
|
||||||
logging.info(f"Image {image_name} downloaded successfully")
|
_logger.info("Image %s downloaded successfully", image_name)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@ -52,14 +52,14 @@ def download_image(url: str) -> bool:
|
|||||||
count = 1
|
count = 1
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
logging.info(f"Processing image {count}...")
|
_logger.info("Processing image %s...", count)
|
||||||
|
|
||||||
# Find the image element by its ID
|
# Find the image element by its ID
|
||||||
image_element = driver.find_element(By.ID, "cc-comic")
|
image_element = driver.find_element(By.ID, "cc-comic")
|
||||||
|
|
||||||
# Get the image URL from the 'src' attribute
|
# Get the image URL from the 'src' attribute
|
||||||
image_url = image_element.get_attribute("src")
|
image_url = image_element.get_attribute("src")
|
||||||
logging.info(f"Found image URL: {image_url}")
|
_logger.info("Found image URL: %s", image_url)
|
||||||
|
|
||||||
# Download the image if it doesn't already exist
|
# Download the image if it doesn't already exist
|
||||||
if download_image(image_url):
|
if download_image(image_url):
|
||||||
@ -67,7 +67,7 @@ while True:
|
|||||||
|
|
||||||
# Try to find the 'Next' button by its class
|
# Try to find the 'Next' button by its class
|
||||||
try:
|
try:
|
||||||
logging.info("Clicking the 'Next' button to load the next image...")
|
_logger.info("Clicking the 'Next' button to load the next image...")
|
||||||
next_button = driver.find_element(By.CSS_SELECTOR, "a.cc-next")
|
next_button = driver.find_element(By.CSS_SELECTOR, "a.cc-next")
|
||||||
|
|
||||||
# Navigate to the URL in the 'href' of the next button
|
# Navigate to the URL in the 'href' of the next button
|
||||||
@ -76,9 +76,9 @@ while True:
|
|||||||
|
|
||||||
except NoSuchElementException:
|
except NoSuchElementException:
|
||||||
# If the 'Next' button is not found, it means we've reached the last image
|
# If the 'Next' button is not found, it means we've reached the last image
|
||||||
logging.info("No 'Next' button found. Reached the end of images.")
|
_logger.info("No 'Next' button found. Reached the end of images.")
|
||||||
break
|
break
|
||||||
|
|
||||||
# Close the browser
|
# Close the browser
|
||||||
logging.info("All images processed, closing the browser.")
|
_logger.info("All images processed, closing the browser.")
|
||||||
driver.quit()
|
driver.quit()
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Validation limits for workout data
|
# Validation limits for workout data
|
||||||
MAX_DISTANCE_KM = 100
|
MAX_DISTANCE_KM = 100
|
||||||
@ -34,7 +34,7 @@ class ScreenLocker:
|
|||||||
|
|
||||||
# Check if already logged today
|
# Check if already logged today
|
||||||
if self.has_logged_today():
|
if self.has_logged_today():
|
||||||
logging.info("Workout already logged today. Skipping screen lock.")
|
_logger.info("Workout already logged today. Skipping screen lock.")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
self.root = tk.Tk()
|
self.root = tk.Tk()
|
||||||
@ -663,7 +663,7 @@ class ScreenLocker:
|
|||||||
with open(self.log_file, "w") as f:
|
with open(self.log_file, "w") as f:
|
||||||
json.dump(logs, f, indent=2)
|
json.dump(logs, f, indent=2)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
logging.warning(f"Could not save workout log: {e}")
|
_logger.warning(f"Could not save workout log: {e}")
|
||||||
|
|
||||||
def close(self) -> None:
|
def close(self) -> None:
|
||||||
"""Close the application and exit."""
|
"""Close the application and exit."""
|
||||||
|
|||||||
@ -30,6 +30,8 @@ import os
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import psutil # type: ignore[import-untyped]
|
import psutil # type: ignore[import-untyped]
|
||||||
except ImportError: # pragma: no cover
|
except ImportError: # pragma: no cover
|
||||||
@ -40,8 +42,8 @@ try:
|
|||||||
import chess.engine
|
import chess.engine
|
||||||
import chess.pgn
|
import chess.pgn
|
||||||
except ImportError: # pragma: no cover
|
except ImportError: # pragma: no cover
|
||||||
logging.exception("Missing dependency. Please install python-chess:")
|
_logger.exception("Missing dependency. Please install python-chess:")
|
||||||
logging.exception(" pip install -r python_pkg/stockfish_analysis/requirements.txt")
|
_logger.exception(" pip install -r python_pkg/stockfish_analysis/requirements.txt")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
# Memory configuration constants
|
# Memory configuration constants
|
||||||
@ -216,7 +218,6 @@ def _auto_hash_mb(threads_wanted: int, engine_options: dict[str, object]) -> int
|
|||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
"""Parse arguments and run chess game analysis."""
|
"""Parse arguments and run chess game analysis."""
|
||||||
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
|
||||||
ap = argparse.ArgumentParser(
|
ap = argparse.ArgumentParser(
|
||||||
description="Analyze a chess game's moves with Stockfish and rate each move."
|
description="Analyze a chess game's moves with Stockfish and rate each move."
|
||||||
)
|
)
|
||||||
@ -271,7 +272,7 @@ def main() -> None:
|
|||||||
args = ap.parse_args()
|
args = ap.parse_args()
|
||||||
|
|
||||||
if not os.path.isfile(args.file):
|
if not os.path.isfile(args.file):
|
||||||
logging.error(f"Input not found: {args.file}")
|
_logger.error(f"Input not found: {args.file}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
with open(args.file, encoding="utf-8", errors="replace") as f:
|
with open(args.file, encoding="utf-8", errors="replace") as f:
|
||||||
@ -279,20 +280,20 @@ def main() -> None:
|
|||||||
|
|
||||||
pgn_text = extract_pgn_text(raw)
|
pgn_text = extract_pgn_text(raw)
|
||||||
if not pgn_text:
|
if not pgn_text:
|
||||||
logging.error("Could not locate PGN text in the file.")
|
_logger.error("Could not locate PGN text in the file.")
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
game = chess.pgn.read_game(io.StringIO(pgn_text))
|
game = chess.pgn.read_game(io.StringIO(pgn_text))
|
||||||
if game is None:
|
if game is None:
|
||||||
logging.error("Failed to parse PGN.")
|
_logger.error("Failed to parse PGN.")
|
||||||
sys.exit(3)
|
sys.exit(3)
|
||||||
|
|
||||||
# Prepare engine
|
# Prepare engine
|
||||||
try:
|
try:
|
||||||
engine = chess.engine.SimpleEngine.popen_uci([args.engine])
|
engine = chess.engine.SimpleEngine.popen_uci([args.engine])
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
logging.exception(f"Could not launch engine at: {args.engine}")
|
_logger.exception(f"Could not launch engine at: {args.engine}")
|
||||||
logging.exception(
|
_logger.exception(
|
||||||
"Ensure Stockfish is installed and in PATH, or specify with --engine."
|
"Ensure Stockfish is installed and in PATH, or specify with --engine."
|
||||||
)
|
)
|
||||||
sys.exit(4)
|
sys.exit(4)
|
||||||
@ -318,7 +319,7 @@ def main() -> None:
|
|||||||
wanted_threads = max(wanted_threads, min_thr)
|
wanted_threads = max(wanted_threads, min_thr)
|
||||||
engine.configure({"Threads": int(wanted_threads)})
|
engine.configure({"Threads": int(wanted_threads)})
|
||||||
except (AttributeError, TypeError, ValueError):
|
except (AttributeError, TypeError, ValueError):
|
||||||
logging.debug("Failed to configure Threads option")
|
_logger.debug("Failed to configure Threads option")
|
||||||
|
|
||||||
# Configure hash table size in MB.
|
# Configure hash table size in MB.
|
||||||
if "Hash" in options:
|
if "Hash" in options:
|
||||||
@ -336,7 +337,7 @@ def main() -> None:
|
|||||||
target_hash = max(target_hash, min_hash)
|
target_hash = max(target_hash, min_hash)
|
||||||
engine.configure({"Hash": int(target_hash)})
|
engine.configure({"Hash": int(target_hash)})
|
||||||
except (AttributeError, TypeError, ValueError):
|
except (AttributeError, TypeError, ValueError):
|
||||||
logging.debug("Failed to configure Hash option")
|
_logger.debug("Failed to configure Hash option")
|
||||||
|
|
||||||
# MultiPV
|
# MultiPV
|
||||||
effective_mpv = max(1, int(args.multipv))
|
effective_mpv = max(1, int(args.multipv))
|
||||||
@ -347,7 +348,7 @@ def main() -> None:
|
|||||||
effective_mpv = min(effective_mpv, max_mpv)
|
effective_mpv = min(effective_mpv, max_mpv)
|
||||||
engine.configure({"MultiPV": int(effective_mpv)})
|
engine.configure({"MultiPV": int(effective_mpv)})
|
||||||
except (AttributeError, TypeError, ValueError):
|
except (AttributeError, TypeError, ValueError):
|
||||||
logging.debug("Failed to configure MultiPV option")
|
_logger.debug("Failed to configure MultiPV option")
|
||||||
|
|
||||||
# Enable NNUE if the option exists
|
# Enable NNUE if the option exists
|
||||||
for nnue_key in ("Use NNUE", "UseNNUE"):
|
for nnue_key in ("Use NNUE", "UseNNUE"):
|
||||||
@ -362,13 +363,13 @@ def main() -> None:
|
|||||||
limit = chess.engine.Limit(time=max(0.05, args.time))
|
limit = chess.engine.Limit(time=max(0.05, args.time))
|
||||||
|
|
||||||
board = game.board()
|
board = game.board()
|
||||||
logging.info("Game:")
|
_logger.info("Game:")
|
||||||
white = game.headers.get("White", "White")
|
white = game.headers.get("White", "White")
|
||||||
black = game.headers.get("Black", "Black")
|
black = game.headers.get("Black", "Black")
|
||||||
result = game.headers.get("Result", "*")
|
result = game.headers.get("Result", "*")
|
||||||
logging.info(f" {white} vs {black} Result: {result}")
|
_logger.info(f" {white} vs {black} Result: {result}")
|
||||||
logging.info("")
|
_logger.info("")
|
||||||
logging.info(
|
_logger.info(
|
||||||
"Columns: ply side move played_eval best_eval loss class best_suggestion"
|
"Columns: ply side move played_eval best_eval loss class best_suggestion"
|
||||||
)
|
)
|
||||||
# Brief performance summary (best-effort)
|
# Brief performance summary (best-effort)
|
||||||
@ -385,12 +386,12 @@ def main() -> None:
|
|||||||
except (AttributeError, TypeError, ValueError):
|
except (AttributeError, TypeError, ValueError):
|
||||||
hash_show = None
|
hash_show = None
|
||||||
if hash_show is not None:
|
if hash_show is not None:
|
||||||
logging.info(
|
_logger.info(
|
||||||
f"Using engine options: Threads={thr_show}, "
|
f"Using engine options: Threads={thr_show}, "
|
||||||
f"Hash={hash_show} MB, MultiPV={effective_mpv}"
|
f"Hash={hash_show} MB, MultiPV={effective_mpv}"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logging.info(
|
_logger.info(
|
||||||
f"Using engine options: Threads={thr_show}, MultiPV={effective_mpv}"
|
f"Using engine options: Threads={thr_show}, MultiPV={effective_mpv}"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -401,7 +402,7 @@ def main() -> None:
|
|||||||
if args.last_move_only:
|
if args.last_move_only:
|
||||||
# Walk to the last move in the main line and analyze only that ply.
|
# Walk to the last move in the main line and analyze only that ply.
|
||||||
if not node.variations:
|
if not node.variations:
|
||||||
logging.warning("No moves found in the game.")
|
_logger.warning("No moves found in the game.")
|
||||||
else:
|
else:
|
||||||
while node.variations:
|
while node.variations:
|
||||||
move_node = node.variations[0]
|
move_node = node.variations[0]
|
||||||
@ -502,7 +503,7 @@ def main() -> None:
|
|||||||
classification = classify_cp_loss(cp_loss)
|
classification = classify_cp_loss(cp_loss)
|
||||||
|
|
||||||
side = "W" if mover_white else "B"
|
side = "W" if mover_white else "B"
|
||||||
logging.info(
|
_logger.info(
|
||||||
f"{ply:>3} {side} {san:<8} "
|
f"{ply:>3} {side} {san:<8} "
|
||||||
f"{fmt_eval(played_cp, played_mate):>10} "
|
f"{fmt_eval(played_cp, played_mate):>10} "
|
||||||
f"{fmt_eval(best_cp, best_mate):>9} "
|
f"{fmt_eval(best_cp, best_mate):>9} "
|
||||||
@ -617,7 +618,7 @@ def main() -> None:
|
|||||||
classification = classify_cp_loss(cp_loss)
|
classification = classify_cp_loss(cp_loss)
|
||||||
|
|
||||||
side = "W" if mover_white else "B"
|
side = "W" if mover_white else "B"
|
||||||
logging.info(
|
_logger.info(
|
||||||
f"{ply:>3} {side} {san:<8} "
|
f"{ply:>3} {side} {san:<8} "
|
||||||
f"{fmt_eval(played_cp, played_mate):>10} "
|
f"{fmt_eval(played_cp, played_mate):>10} "
|
||||||
f"{fmt_eval(best_cp, best_mate):>9} "
|
f"{fmt_eval(best_cp, best_mate):>9} "
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import shutil # for: shutil.move
|
|||||||
# cv2.waitKey; cv2.destroyAllWindows; cv2.IMREAD_COLOR
|
# cv2.waitKey; cv2.destroyAllWindows; cv2.IMREAD_COLOR
|
||||||
import cv2
|
import cv2
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
IMAGE_EXTENSION = (
|
IMAGE_EXTENSION = (
|
||||||
".bmp",
|
".bmp",
|
||||||
@ -59,7 +59,7 @@ for filename in os.listdir(
|
|||||||
if (filename.lower()).endswith(
|
if (filename.lower()).endswith(
|
||||||
IMAGE_EXTENSION
|
IMAGE_EXTENSION
|
||||||
): # If the file name ends with image extension
|
): # If the file name ends with image extension
|
||||||
logging.info(filename)
|
_logger.info(filename)
|
||||||
image = cv2.imread(filename, cv2.IMREAD_COLOR)
|
image = cv2.imread(filename, cv2.IMREAD_COLOR)
|
||||||
window_name = filename.split(".")[0]
|
window_name = filename.split(".")[0]
|
||||||
cv2.namedWindow(window_name) # Window name is the same as image file name
|
cv2.namedWindow(window_name) # Window name is the same as image file name
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user