From df861689b6ef37e1068e3404884ce5bb609a2cb3 Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Fri, 22 Aug 2025 20:22:06 +0200 Subject: [PATCH] feat: added bot versioning --- PYTHON/lichess_bot/.bot_version | 1 + PYTHON/lichess_bot/main.py | 13 +++++- PYTHON/lichess_bot/tests/test_versioning.py | 19 +++++++++ PYTHON/lichess_bot/utils.py | 45 +++++++++++++++++++++ 4 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 PYTHON/lichess_bot/.bot_version create mode 100644 PYTHON/lichess_bot/tests/test_versioning.py diff --git a/PYTHON/lichess_bot/.bot_version b/PYTHON/lichess_bot/.bot_version new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/PYTHON/lichess_bot/.bot_version @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/PYTHON/lichess_bot/main.py b/PYTHON/lichess_bot/main.py index 9220cf7..72a0b77 100644 --- a/PYTHON/lichess_bot/main.py +++ b/PYTHON/lichess_bot/main.py @@ -11,7 +11,7 @@ import chess.pgn from .engine import RandomEngine from .lichess_api import LichessAPI -from .utils import backoff_sleep +from .utils import backoff_sleep, get_and_increment_version def run_bot(log_level: str = "INFO", decline_correspondence: bool = False) -> None: @@ -25,13 +25,16 @@ def run_bot(log_level: str = "INFO", decline_correspondence: bool = False) -> No raise RuntimeError("LICHESS_TOKEN environment variable is required") logging.info("Token present. Initializing client and engine...") + # Self-incrementing bot version (persisted on disk) + bot_version = get_and_increment_version() + logging.info(f"Bot version: v{bot_version}") api = LichessAPI(token) engine = RandomEngine() game_threads = {} def handle_game(game_id: str, my_color: Optional[str] = None): - logging.info(f"Starting game thread for {game_id}") + logging.info(f"Starting game thread for {game_id} [bot v{bot_version}]") board = chess.Board() color: Optional[str] = my_color # Track how many moves we have already processed; start at -1 so we act on the first state (0 moves) @@ -41,6 +44,7 @@ def run_bot(log_level: str = "INFO", decline_correspondence: bool = False) -> No try: with open(game_log_path, "w") as lf: lf.write(f"game {game_id} started\n") + lf.write(f"bot_version v{bot_version}\n") except Exception: game_log_path = None # Simple time manager state @@ -149,6 +153,11 @@ def run_bot(log_level: str = "INFO", decline_correspondence: bool = False) -> No try: if game_log_path: game = chess.pgn.Game.from_board(board) + # Record the bot version in the PGN headers + try: + game.headers["BotVersion"] = f"v{bot_version}" + except Exception: + pass with open(game_log_path, "a") as lf: lf.write("\nPGN:\n") exporter = chess.pgn.StringExporter(headers=True, variations=False, comments=False) diff --git a/PYTHON/lichess_bot/tests/test_versioning.py b/PYTHON/lichess_bot/tests/test_versioning.py new file mode 100644 index 0000000..bf5026c --- /dev/null +++ b/PYTHON/lichess_bot/tests/test_versioning.py @@ -0,0 +1,19 @@ +import os +import tempfile + +from PYTHON.lichess_bot.utils import get_and_increment_version + + +def test_version_file_increments_and_persists(tmp_path, monkeypatch): + version_file = tmp_path / "version.txt" + monkeypatch.setenv("LICHESS_BOT_VERSION_FILE", str(version_file)) + + v1 = get_and_increment_version() + v2 = get_and_increment_version() + + assert v1 == 1 + assert v2 == 2 + + # Ensure it persisted + with open(version_file, "r") as f: + assert f.read().strip() == "2" diff --git a/PYTHON/lichess_bot/utils.py b/PYTHON/lichess_bot/utils.py index bb4ced4..c262c37 100644 --- a/PYTHON/lichess_bot/utils.py +++ b/PYTHON/lichess_bot/utils.py @@ -1,7 +1,52 @@ import logging +import os import time +def _version_file_path() -> str: + """Return the path to the persistent bot version file. + + Stored alongside this module as a simple text file containing an integer. + """ + override = os.getenv("LICHESS_BOT_VERSION_FILE") + if override: + return override + return os.path.join(os.path.dirname(__file__), ".bot_version") + + +def get_and_increment_version() -> int: + """Read the current bot version, increment it, persist, and return the new version. + + If the version file doesn't exist or is invalid, starts from 0, then sets to 1. + """ + path = _version_file_path() + current = 0 + try: + with open(path, "r") as f: + raw = f.read().strip() + if raw: + current = int(raw) + except Exception: + # Missing or unreadable file -> treat as version 0 + current = 0 + + new_version = current + 1 + try: + tmp_path = path + ".tmp" + with open(tmp_path, "w") as f: + f.write(str(new_version)) + os.replace(tmp_path, path) + except Exception: + # As a fallback, try a direct write; failure is non-fatal to bot operation + try: + with open(path, "w") as f: + f.write(str(new_version)) + except Exception: + logging.debug("Could not persist bot version to %s", path) + + return new_version + + def backoff_sleep(current_backoff: int, base: float = 0.5, cap: float = 8.0) -> int: """Sleep with exponential backoff. Returns the next backoff step.