Function: persistevent in event-sourcing-store/event_store.py

Date: 2026-05-29

Time: 11:15

persistevent — Append-Only Disk Serialization

Purpose

persistevent writes a single event to disk as a JSON line (newline-delimited JSON / NDJSON). It implements the durable half of the event store's append-only log — the in-memory list gives you fast reads, this method gives you crash recovery. Without it, the store is purely ephemeral.

Contract

Parameters

| Parameter | Type | Notes |

|-----------|------|-------|

| event | Event | A dataclass instance. The method reads six fields from it. metadata may be None, which serializes to JSON null. |

No edge cases on the parameter itself — the interesting edge cases are at the I/O boundary (see Error Handling).

Return Value

None. This is a fire-and-forget write. The caller (append / append_batch) does not check for success.

Algorithm

1. Open the persist file in append mode ("a"). This creates the file if it doesn't exist and positions the write pointer at the end.

2. Build a plain dict from the event's fields. Notably, timestamp is converted from datetime to ISO-8601 string via .isoformat() — this is the only field that needs serialization beyond what json.dumps handles natively.

3. Serialize the dict to a JSON string and append "\n" to form one NDJSON line.

4. Write that line to the file. The with block closes the file handle immediately after.

Side Effects

Error Handling

None. If the file can't be opened (permissions, disk full, invalid path), the open() call raises OSError/PermissionError and it propagates uncaught through append() to the caller. The event is already in self.events at that point (it was appended before persist_event is called), so a failure here leaves the store in an inconsistent state: in-memory has the event, disk doesn't. There is no rollback.

Similarly, json.dumps could theoretically fail if event.data contains non-serializable values (e.g., datetime objects nested in the data dict). This is not validated.

Usage Patterns

Never called directly — it's a private method called from exactly two places:

1. append() — called once per single-event append, after the event is added to self.events and self.streams.

2. append_batch() — called once per event in the batch, inside the loop. Each event is persisted individually; there's no atomic batch write.

The symmetrical counterpart is loadfromfile, which reads the NDJSON file back during init_ to reconstruct the in-memory state.

Dependencies

Unenforceable Assumptions

1. event.data is JSON-serializable. The type hint says dict, but nothing prevents values like {key: datetime.now()} which would blow up in json.dumps.

2. event.metadata is JSON-serializable (or None).

3. No concurrent writers. If two EventStore instances point at the same file, their appends will interleave unpredictably. There's no file locking.

4. persistpath directory exists. Append mode creates files, not directories.

Beliefs