From 4d9474036dc3429abfb766205f99c6174141eaef Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sun, 14 Jun 2026 02:24:21 +0100 Subject: [PATCH] Add Python CultMesh global document facade --- packages/cultcache-py/PARITY.md | 9 ++++-- packages/cultcache-py/src/cultmesh_py/node.py | 31 +++++++++++++++++++ packages/cultcache-py/tests/test_cultcache.py | 23 ++++++++++++++ 3 files changed, 60 insertions(+), 3 deletions(-) diff --git a/packages/cultcache-py/PARITY.md b/packages/cultcache-py/PARITY.md index 29954467..f5925e82 100644 --- a/packages/cultcache-py/PARITY.md +++ b/packages/cultcache-py/PARITY.md @@ -24,9 +24,12 @@ CultCache, CultNet, and CultMesh. It is evidence, not confetti. - Python CultMesh database facade: `node.database` owns the local typed document read/write/snapshot/sync surface so Python callers do not need to reach through the node composition wrapper. - It also exposes local `watch(...)`, `watch_record(...)`, `watch_by_name(...)`, - and `watch_by_index(...)` callbacks for database changes, including - authoritative shard-log reconciliation applied through the session facade. + It includes global document helpers that delegate through the same singleton + key invariant as `CultCache`. + It also exposes local `watch(...)`, `watch_record(...)`, `watch_global(...)`, + `watch_by_name(...)`, and `watch_by_index(...)` callbacks for database changes, + including authoritative shard-log reconciliation applied through the session + facade. Change notifications carry previous values when the database can observe the old record before local or replicated application. - Python CultMesh committed simulation facts: diff --git a/packages/cultcache-py/src/cultmesh_py/node.py b/packages/cultcache-py/src/cultmesh_py/node.py index e146e75d..f7f53e4e 100644 --- a/packages/cultcache-py/src/cultmesh_py/node.py +++ b/packages/cultcache-py/src/cultmesh_py/node.py @@ -44,6 +44,9 @@ class CultMeshNode: def put(self, document: DocumentDefinition[Any], key: str, value: Any) -> None: self.database.put(document, key, value) + def put_global(self, document: DocumentDefinition[Any], value: Any) -> None: + self.database.put_global(document, value) + def put_raw_message( self, document: DocumentDefinition[Any], @@ -69,9 +72,18 @@ class CultMeshNode: def get_required(self, document: DocumentDefinition[Any], key: str) -> Any: return self.database.get_required(document, key) + def get_global(self, document: DocumentDefinition[Any]) -> Any: + return self.database.get_global(document) + + def get_required_global(self, document: DocumentDefinition[Any]) -> Any: + return self.database.get_required_global(document) + def delete(self, document: DocumentDefinition[Any], key: str) -> None: self.database.delete(document, key) + def delete_global(self, document: DocumentDefinition[Any]) -> None: + self.database.delete_global(document) + def delete_raw_message( self, document: DocumentDefinition[Any], @@ -171,6 +183,13 @@ class CultMeshDatabase: ) -> Callable[[], None]: return self.watch(callback, document=document, key=key) + def watch_global( + self, + document: DocumentDefinition[Any], + callback: Callable[[CultMeshDatabaseChange], None], + ) -> Callable[[], None]: + return self.watch_record(document, self.cache.GLOBAL_KEY, callback) + def watch_by_name( self, document: DocumentDefinition[Any], @@ -224,6 +243,9 @@ class CultMeshDatabase: self.cache.put(document, key, value) self._publish_local_change(document, key, "added" if previous is None else "updated", value, previous) + def put_global(self, document: DocumentDefinition[Any], value: Any) -> None: + self.put(document, self.cache.GLOBAL_KEY, value) + def put_raw_message( self, document: DocumentDefinition[Any], @@ -262,12 +284,21 @@ class CultMeshDatabase: def get_required(self, document: DocumentDefinition[Any], key: str) -> Any: return self.cache.get_required(document, key) + def get_global(self, document: DocumentDefinition[Any]) -> Any: + return self.cache.get_global(document) + + def get_required_global(self, document: DocumentDefinition[Any]) -> Any: + return self.cache.get_required_global(document) + def delete(self, document: DocumentDefinition[Any], key: str) -> None: previous = self.cache.get(document, key) self.cache.delete(document, key) if previous is not None: self._publish_local_change(document, key, "removed", None, previous) + def delete_global(self, document: DocumentDefinition[Any]) -> None: + self.delete(document, self.cache.GLOBAL_KEY) + def delete_raw_message( self, document: DocumentDefinition[Any], diff --git a/packages/cultcache-py/tests/test_cultcache.py b/packages/cultcache-py/tests/test_cultcache.py index d603215f..25b0be30 100644 --- a/packages/cultcache-py/tests/test_cultcache.py +++ b/packages/cultcache-py/tests/test_cultcache.py @@ -917,6 +917,29 @@ class CultCacheTests(unittest.TestCase): with self.assertRaises(ValueError): node.database.register_document(conflicting) + def test_cultmesh_database_global_document_facade_uses_singleton_key_and_watchers(self) -> None: + document = define_database_entry_type( + "mesh.global_settings", + [("theme", 0)], + global_document=True, + ) + node = CultMesh.create_node(runtime_id="mesh-global") + node.database.register_document(document) + changes: list[CultMeshDatabaseChange] = [] + + node.database.watch_global(document, changes.append) + node.database.put_global(document, {"theme": "ash"}) + node.put_global(document, {"theme": "ember"}) + node.delete_global(document) + + self.assertIsNone(node.database.get_global(document)) + self.assertEqual([change.record_key for change in changes], [node.cache.GLOBAL_KEY] * 3) + self.assertEqual([change.change_kind for change in changes], ["added", "updated", "removed"]) + self.assertEqual(changes[1].previous_value, {"theme": "ash"}) + self.assertEqual(changes[2].previous_value, {"theme": "ember"}) + with self.assertRaises(Exception): + node.database.put(document, "settings:wrong", {"theme": "bad"}) + def test_cultmesh_database_watchers_observe_name_and_index_changes(self) -> None: document = define_database_entry_type( "mesh.named_watch",