From 16b75e212aa530103c4ff20a1b0be7f05d5182c5 Mon Sep 17 00:00:00 2001 From: beo3000 Date: Mon, 15 Jun 2026 15:08:53 +0200 Subject: [PATCH] feat(state): persist last_update_id and rolling processed_ids --- src/journal_bot/state.py | 46 ++++++++++++++++++++++++++++++++++++++++ tests/test_state.py | 34 +++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 src/journal_bot/state.py create mode 100644 tests/test_state.py diff --git a/src/journal_bot/state.py b/src/journal_bot/state.py new file mode 100644 index 0000000..c45b1ac --- /dev/null +++ b/src/journal_bot/state.py @@ -0,0 +1,46 @@ +import json +from collections import deque +from pathlib import Path + + +class State: + """Persistent state: last_update_id + rolling processed_ids.""" + + def __init__(self, state_dir: Path, processed_ids_max: int = 500): + self.state_dir = state_dir + self.state_dir.mkdir(parents=True, exist_ok=True) + self._last_path = state_dir / "last_update_id.json" + self._processed_path = state_dir / "processed_ids.json" + self._max = processed_ids_max + self._processed: deque[int] = deque(maxlen=processed_ids_max) + self._load() + + def _load(self) -> None: + if self._last_path.exists(): + self._last_update_id = json.loads(self._last_path.read_text())["value"] + else: + self._last_update_id = 0 + if self._processed_path.exists(): + ids = json.loads(self._processed_path.read_text())["ids"] + self._processed = deque(ids[-self._max:], maxlen=self._max) + + @property + def last_update_id(self) -> int: + return self._last_update_id + + def set_last_update_id(self, value: int) -> None: + self._last_update_id = value + tmp = self._last_path.with_suffix(".tmp") + tmp.write_text(json.dumps({"value": value})) + tmp.replace(self._last_path) + + def has_processed(self, update_id: int) -> bool: + return update_id in self._processed + + def mark_processed(self, update_id: int) -> None: + if update_id in self._processed: + return + self._processed.append(update_id) + tmp = self._processed_path.with_suffix(".tmp") + tmp.write_text(json.dumps({"ids": list(self._processed)})) + tmp.replace(self._processed_path) diff --git a/tests/test_state.py b/tests/test_state.py new file mode 100644 index 0000000..1a570eb --- /dev/null +++ b/tests/test_state.py @@ -0,0 +1,34 @@ +from pathlib import Path +from journal_bot.state import State + + +def test_state_initial_values(tmp_path): + s = State(tmp_path) + assert s.last_update_id == 0 + assert not s.has_processed(123) + + +def test_state_persists_last_update_id(tmp_path): + s = State(tmp_path) + s.set_last_update_id(456) + s2 = State(tmp_path) + assert s2.last_update_id == 456 + + +def test_state_processed_ids_rolling_window(tmp_path): + s = State(tmp_path, processed_ids_max=3) + s.mark_processed(1) + s.mark_processed(2) + s.mark_processed(3) + s.mark_processed(4) + assert not s.has_processed(1) # dropped + assert s.has_processed(2) + assert s.has_processed(3) + assert s.has_processed(4) + + +def test_state_processed_ids_persist(tmp_path): + s = State(tmp_path) + s.mark_processed(42) + s2 = State(tmp_path) + assert s2.has_processed(42)