[store] Add StoreBundle.from_dict() for symmetric serialization#164
[store] Add StoreBundle.from_dict() for symmetric serialization#164
Conversation
… symmetric serialization Agent-Logs-Url: https://github.com/dgenio/contextweaver/sessions/899c9875-0eb9-4fca-937a-024fae9c064b Co-authored-by: dgenio <12731907+dgenio@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds symmetric JSON-compatible deserialization for the store layer by introducing from_dict() round-tripping for StoreBundle and InMemoryArtifactStore (metadata-only for artifacts), with accompanying unit tests.
Changes:
- Add
InMemoryArtifactStore.from_dict()to restore the artifact metadata index (ArtifactRefs) from serialized data. - Add
StoreBundle.from_dict()to reconstruct non-Nonestores fromStoreBundle.to_dict()output. - Add/extend tests to validate round-trip behavior and the “metadata-only” artifact contract.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/contextweaver/store/artifacts.py |
Implements InMemoryArtifactStore.from_dict() restoring refs metadata only. |
src/contextweaver/store/__init__.py |
Adds StoreBundle.from_dict() to rebuild bundles from serialized dicts. |
tests/test_store_artifacts.py |
Adds tests for artifact metadata restoration and metadata-only round-trip semantics. |
tests/test_store_bundle.py |
New tests covering StoreBundle round-tripping across all/partial/none store configurations. |
| @classmethod | ||
| def from_dict(cls, data: dict[str, Any]) -> StoreBundle: | ||
| """Deserialise from a JSON-compatible dict produced by :meth:`to_dict`.""" | ||
| raw_artifact = data.get("artifact_store") |
There was a problem hiding this comment.
This PR introduces new public from_dict() APIs (on StoreBundle and InMemoryArtifactStore). Per the project workflow, public API additions should be recorded under CHANGELOG.md → ## [Unreleased] so downstream users can discover the new serialization support.
| @classmethod | ||
| def from_dict(cls, data: dict[str, Any]) -> StoreBundle: | ||
| """Deserialise from a JSON-compatible dict produced by :meth:`to_dict`.""" | ||
| raw_artifact = data.get("artifact_store") | ||
| raw_event_log = data.get("event_log") | ||
| raw_episodic = data.get("episodic_store") | ||
| raw_fact = data.get("fact_store") | ||
| return cls( | ||
| artifact_store=InMemoryArtifactStore.from_dict(raw_artifact) | ||
| if raw_artifact is not None | ||
| else None, | ||
| event_log=InMemoryEventLog.from_dict(raw_event_log) | ||
| if raw_event_log is not None | ||
| else None, | ||
| episodic_store=InMemoryEpisodicStore.from_dict(raw_episodic) | ||
| if raw_episodic is not None | ||
| else None, | ||
| fact_store=InMemoryFactStore.from_dict(raw_fact) | ||
| if raw_fact is not None | ||
| else None, | ||
| ) |
There was a problem hiding this comment.
StoreBundle.from_dict() adds additional implementation logic to src/contextweaver/store/__init__.py, but the repo hard rule is that __init__.py files should contain re-exports only (no business logic). Consider moving StoreBundle (and its to_dict/from_dict) into a dedicated module (e.g., store/bundle.py) and re-exporting it from store/__init__.py to comply with that rule and avoid import-side effects growing over time.
Summary
Closes #66
StoreBundlehadto_dict()but nofrom_dict(), making it the only serializable type in the codebase without a round-trip.InMemoryArtifactStorehad the same gap.Changes
InMemoryArtifactStore.from_dict()— restores the metadata index (refs) from a serialized dict. Raw bytes are not JSON-serializable and are explicitly documented as unavailable after round-tripping; onlyArtifactRefmetadata (handle, media_type, size_bytes, label) is restored.StoreBundle.from_dict()— delegates to each store'sfrom_dict()for non-Noneentries; absent stores remainNone.tests/test_store_artifacts.py— adds 3 tests: metadata restore, empty input, and the metadata-only round-trip contract.tests/test_store_bundle.py(new) — 4 tests: all-None, all-stores, partial-stores, and dict structure.Checklist
make cipasses locally (fmt + lint + type + test + example + demo)CHANGELOG.mdupdated under## [Unreleased]Notes for reviewers
InMemoryArtifactStore.from_dict()only restores the metadata index — the_datadict (raw bytes) is intentionally left empty. This matches whatto_dict()serializes (refs only) and is documented in the docstring. Callers that need byte contents must repopulate viaput()after loading.The
typingnote from the issue (protocol types forStoreBundlefields) is deferred pending #40.