Source code for nb2slurm.done
"""Idempotent 'already done?' bookkeeping, as an object.
Generalised from cci.py: a single CSV records which subjects have completed, so
re-submitting the whole set skips finished work. A file lock makes it safe when
many jobs write concurrently on a shared filesystem.
done = nb2slurm.Done("done/done.csv")
if done.is_done(key):
...
done.mark(key)
"""
from __future__ import annotations
import csv
from pathlib import Path
from filelock import FileLock
LOCK_TIMEOUT = 60 * 3
[docs]
class Done:
"""A CSV ledger of completed subjects, with concurrency-safe writes."""
def __init__(self, csv_file: str | Path):
self.csv_file = Path(csv_file)
@property
def _lock(self) -> str:
return str(self.csv_file) + ".lock"
[docs]
def is_done(self, key: str) -> bool:
"""Return True if ``key`` is already recorded."""
if not self.csv_file.exists():
return False
with FileLock(self._lock, timeout=LOCK_TIMEOUT):
with open(self.csv_file, newline="") as f:
reader = csv.reader(f)
next(reader, None) # header
for row in reader:
if row and row[0] == str(key):
return True
return False
[docs]
def mark(self, key: str) -> None:
"""Record ``key`` as done (no-op if already present)."""
self.csv_file.parent.mkdir(parents=True, exist_ok=True)
if not self.csv_file.exists():
with open(self.csv_file, "w", newline="") as f:
csv.writer(f).writerow(["key"])
with FileLock(self._lock, timeout=LOCK_TIMEOUT):
existing = set()
with open(self.csv_file, newline="") as f:
reader = csv.reader(f)
next(reader, None)
for row in reader:
if row:
existing.add(row[0])
if str(key) not in existing:
with open(self.csv_file, "a", newline="") as f:
csv.writer(f).writerow([key])
print(f"Marked {key} as done.")
else:
print(f"{key} already recorded as done.")
[docs]
def clear(self) -> None:
"""Delete the ledger so every key is treated as not-done again.
No-op if the file doesn't exist. It's just a CSV, so removing individual
rows by hand also works if you only want to rerun a subset.
"""
with FileLock(self._lock, timeout=LOCK_TIMEOUT):
if self.csv_file.exists():
self.csv_file.unlink()