Date: 2026-05-29
Time: 11:15
persistevent — Append-Only Disk Serializationpersistevent 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.
self.persistpath is a valid, writable file path (not None). Every caller gates on if self.persistpath: before calling this method — the method itself does not check.event is a fully constructed Event with all fields populated, including a datetime timestamp (not a string).self.persistpath. The file is closed (via with) and the write is handed off to the OS.self._events in insertion order. Every event appended to the in-memory list gets a corresponding line on disk (assuming persistence is enabled).| 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).
None. This is a fire-and-forget write. The caller (append / append_batch) does not check for success.
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.
self.persistpath on every call. The file is opened and closed per-event, which is safe but not high-throughput — each call incurs open/close overhead.write() and the OS flushing to disk loses the event. This is a deliberate simplicity trade-off — this is a reference implementation, not a production WAL.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.
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.
json (stdlib): Serialization.datetime.isoformat(): Timestamp formatting. The inverse (datetime.fromisoformat()) is used in loadfrom_file.self.persistpath: Set once in _init_, never modified.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.
persist-event-no-fsync — persistevent writes to the OS buffer but never calls fsync; data can be lost on crash between write and OS flushpersist-event-per-call-open — The persist file is opened and closed on every single event write, not held open across the store's lifetimepersist-before-notify — Disk persistence happens before subscriber notification: an event is written to disk before any LiveProjection sees itbatch-persist-non-atomic — appendbatch persists events one at a time via persist_event; a crash mid-batch leaves a partial batch on disk with no transactional boundarypersist-after-memory — The event is appended to self.events before persist_event is called; a write failure leaves the in-memory store ahead of disk with no rollback