test_ui2 / skip_persistence.py
Fagan Valiyev
initial
d7efa84
"""Skip tracking persistence layer."""
import os
import csv
import tempfile
import logging
from datetime import datetime
from pathlib import Path
from filelock import FileLock, Timeout
logger = logging.getLogger(__name__)
SKIP_CSV_COLUMNS = ["labeler", "source", "reason", "timestamp"]
def save_skip(labeler: str, source: str, reason: str, csv_path: str) -> None:
"""Save a skip record to the shared skipped_audios.csv.
Uses filelock + temp-file-then-rename for atomicity.
Args:
labeler: Username of the labeler who skipped.
source: Audio filename.
reason: Reason for skipping.
csv_path: Path to the shared skipped_audios.csv.
Raises:
IOError: If the write operation fails.
"""
lock_path = csv_path + ".lock"
lock = FileLock(lock_path, timeout=10)
try:
with lock:
rows: list[dict] = []
path = Path(csv_path)
if path.exists():
with open(path, "r", encoding="utf-8", newline="") as f:
reader = csv.DictReader(f)
rows = [row for row in reader]
new_row = {
"labeler": labeler,
"source": source,
"reason": reason,
"timestamp": datetime.now().isoformat(),
}
# Upsert: overwrite if same labeler+source already skipped
updated = False
for i, row in enumerate(rows):
if row["labeler"] == labeler and row["source"] == source:
rows[i] = new_row
updated = True
break
if not updated:
rows.append(new_row)
# Atomic write
dir_name = os.path.dirname(csv_path) or "."
os.makedirs(dir_name, exist_ok=True)
fd, tmp_path = tempfile.mkstemp(dir=dir_name, suffix=".tmp")
try:
with os.fdopen(fd, "w", encoding="utf-8", newline="") as f:
writer = csv.DictWriter(f, fieldnames=SKIP_CSV_COLUMNS)
writer.writeheader()
writer.writerows(rows)
os.replace(tmp_path, csv_path)
logger.info(f"Saved skip for '{source}' by '{labeler}': {reason}")
except Exception:
if os.path.exists(tmp_path):
os.unlink(tmp_path)
raise
except Timeout:
logger.error(f"Lock timeout for skip CSV: {csv_path}")
raise IOError("Failed to save skip record. Please try again.")
except IOError:
raise
except Exception as e:
logger.error(f"Failed to save skip for {source}: {e}")
raise IOError("Failed to save skip record. Please try again.") from e