The run ledger: which phases and chunks of a bulk or sync run are done, durably.
One JSON line per event, flushed and synced before the call returns, so after a
crash the ledger never claims more than happened. A chunk recorded as started
but not committed may or may not be in the target: it is written again, as a
replay. A restart forgets every chunk, for a target that lost them.
Attributes
LEDGER = 'ledger.jsonl'
module-attribute
Classes
ChunkState
Bases: StrEnum
Source code in graflo/hq/bulk/ledger.py
| class ChunkState(StrEnum):
NEW = "new"
UNCERTAIN = "uncertain"
COMMITTED = "committed"
|
Attributes
COMMITTED = 'committed'
class-attribute
instance-attribute
NEW = 'new'
class-attribute
instance-attribute
UNCERTAIN = 'uncertain'
class-attribute
instance-attribute
Phase
Bases: StrEnum
Phases of a bulk or sync run, in order.
Source code in graflo/hq/bulk/ledger.py
| class Phase(StrEnum):
"""Phases of a bulk or sync run, in order."""
OPENED = "opened"
STAGED = "staged"
FOLDED = "folded"
GROUPED = "grouped"
BEGINNING = "beginning"
BEGUN = "begun"
EDGES_PREPARED = "edges_prepared"
FINISHED = "finished"
|
Attributes
BEGINNING = 'beginning'
class-attribute
instance-attribute
BEGUN = 'begun'
class-attribute
instance-attribute
EDGES_PREPARED = 'edges_prepared'
class-attribute
instance-attribute
FINISHED = 'finished'
class-attribute
instance-attribute
FOLDED = 'folded'
class-attribute
instance-attribute
GROUPED = 'grouped'
class-attribute
instance-attribute
OPENED = 'opened'
class-attribute
instance-attribute
STAGED = 'staged'
class-attribute
instance-attribute
RunLedger
The ledger of the bulk run in staging_dir, read on open.
Source code in graflo/hq/bulk/ledger.py
| class RunLedger:
"""The ledger of the bulk run in *staging_dir*, read on open."""
def __init__(self, staging_dir: Path) -> None:
self.path = Path(staging_dir) / LEDGER
self._lock = threading.Lock()
self._phases: set[str] = set()
self._started: set[str] = set()
self._committed: set[str] = set()
self.mode: str | None = None
if self.path.exists():
for line in self.path.read_text().splitlines():
if not line.strip():
continue
event = json.loads(line)
if "mode" in event:
self.mode = event["mode"]
elif "phase" in event:
self._phases.add(event["phase"])
elif "restart" in event:
self._forget()
elif event.get("state") == "started":
self._started.add(event["chunk"])
elif event.get("state") == "committed":
self._committed.add(event["chunk"])
@property
def exists(self) -> bool:
return self.path.exists()
def has(self, phase: Phase) -> bool:
return phase.value in self._phases
def set_mode(self, mode: str) -> None:
"""Record the run's mode, ``bulk`` or ``sync``; first, in a new ledger."""
self._append({"mode": mode})
self.mode = mode
def mark(self, phase: Phase) -> None:
if phase.value not in self._phases:
self._append({"phase": phase.value})
self._phases.add(phase.value)
def chunk(self, chunk_id: str) -> ChunkState:
if chunk_id in self._committed:
return ChunkState.COMMITTED
if chunk_id in self._started:
return ChunkState.UNCERTAIN
return ChunkState.NEW
def started(self, chunk_id: str) -> None:
self._append({"chunk": chunk_id, "state": "started"})
self._started.add(chunk_id)
def committed(self, chunk_id: str) -> None:
self._append({"chunk": chunk_id, "state": "committed"})
self._committed.add(chunk_id)
def restart(self) -> None:
"""Forget every chunk and the phases after the load began."""
self._append({"restart": "chunks"})
self._forget()
def _forget(self) -> None:
self._started.clear()
self._committed.clear()
self._phases -= {Phase.EDGES_PREPARED.value, Phase.FINISHED.value}
def _append(self, event: dict[str, str]) -> None:
line = json.dumps(event, separators=(",", ":")) + "\n"
with self._lock:
self.path.parent.mkdir(parents=True, exist_ok=True)
with open(self.path, "a") as out:
out.write(line)
out.flush()
os.fsync(out.fileno())
|
Attributes
mode = None
instance-attribute
path = Path(staging_dir) / LEDGER
instance-attribute
Methods:
__init__(staging_dir)
Source code in graflo/hq/bulk/ledger.py
| def __init__(self, staging_dir: Path) -> None:
self.path = Path(staging_dir) / LEDGER
self._lock = threading.Lock()
self._phases: set[str] = set()
self._started: set[str] = set()
self._committed: set[str] = set()
self.mode: str | None = None
if self.path.exists():
for line in self.path.read_text().splitlines():
if not line.strip():
continue
event = json.loads(line)
if "mode" in event:
self.mode = event["mode"]
elif "phase" in event:
self._phases.add(event["phase"])
elif "restart" in event:
self._forget()
elif event.get("state") == "started":
self._started.add(event["chunk"])
elif event.get("state") == "committed":
self._committed.add(event["chunk"])
|
chunk(chunk_id)
Source code in graflo/hq/bulk/ledger.py
| def chunk(self, chunk_id: str) -> ChunkState:
if chunk_id in self._committed:
return ChunkState.COMMITTED
if chunk_id in self._started:
return ChunkState.UNCERTAIN
return ChunkState.NEW
|
committed(chunk_id)
Source code in graflo/hq/bulk/ledger.py
| def committed(self, chunk_id: str) -> None:
self._append({"chunk": chunk_id, "state": "committed"})
self._committed.add(chunk_id)
|
has(phase)
Source code in graflo/hq/bulk/ledger.py
| def has(self, phase: Phase) -> bool:
return phase.value in self._phases
|
mark(phase)
Source code in graflo/hq/bulk/ledger.py
| def mark(self, phase: Phase) -> None:
if phase.value not in self._phases:
self._append({"phase": phase.value})
self._phases.add(phase.value)
|
restart()
Forget every chunk and the phases after the load began.
Source code in graflo/hq/bulk/ledger.py
| def restart(self) -> None:
"""Forget every chunk and the phases after the load began."""
self._append({"restart": "chunks"})
self._forget()
|
set_mode(mode)
Record the run's mode, bulk or sync; first, in a new ledger.
Source code in graflo/hq/bulk/ledger.py
| def set_mode(self, mode: str) -> None:
"""Record the run's mode, ``bulk`` or ``sync``; first, in a new ledger."""
self._append({"mode": mode})
self.mode = mode
|
started(chunk_id)
Source code in graflo/hq/bulk/ledger.py
| def started(self, chunk_id: str) -> None:
self._append({"chunk": chunk_id, "state": "started"})
self._started.add(chunk_id)
|