test_ui2 / skip_persistence.py
Fagan Valiyev
initial
d7efa84
Raw History Blame Contribute Delete
2.79 kB
"""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