diff --git a/src/journal_bot/queue.py b/src/journal_bot/queue.py index 4d75c0f..02a3e0c 100644 --- a/src/journal_bot/queue.py +++ b/src/journal_bot/queue.py @@ -1,7 +1,6 @@ -import json from pathlib import Path from typing import Optional -from pydantic import BaseModel, Field +from pydantic import BaseModel class QueueItem(BaseModel): @@ -34,7 +33,7 @@ class Queue: def enqueue(self, item: QueueItem) -> None: target = self._path(self.pending, item.update_id) tmp = target.with_suffix(".tmp") - tmp.write_text(item.model_dump_json()) + tmp.write_text(item.model_dump_json(), encoding="utf-8") tmp.replace(target) def claim_next(self) -> Optional[QueueItem]: @@ -44,7 +43,7 @@ class Queue: src.rename(dst) except FileNotFoundError: continue - return QueueItem.model_validate_json(dst.read_text()) + return QueueItem.model_validate_json(dst.read_text(encoding="utf-8")) return None def complete(self, item: QueueItem) -> None: @@ -59,8 +58,11 @@ class Queue: dst = self._path(self.failed, item.update_id) else: dst = self._path(self.pending, item.update_id) - tmp = src.with_suffix(".tmp") - tmp.write_text(item.model_dump_json()) + # Write tmp inside destination dir, then atomically replace into dst. + # If we crash before unlinking src, dst is already in place; the + # working/ entry becomes an orphan but no item is ever missing. + tmp = dst.with_suffix(".tmp") + tmp.write_text(item.model_dump_json(), encoding="utf-8") tmp.replace(dst) if src.exists(): src.unlink()