Add Python CultMesh database facade
This commit is contained in:
parent
bd50ecd15b
commit
c125b0fb20
6 changed files with 133 additions and 13 deletions
|
|
@ -21,6 +21,9 @@ CultCache, CultNet, and CultMesh. It is evidence, not confetti.
|
|||
Exposes C#-matching client entrypoints for Verse discovery, peer exchange,
|
||||
raw CultNet client construction, local nodes, catalogs, sessions, streams,
|
||||
authority leases, and simulation fact commits.
|
||||
- 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.
|
||||
- Python CultMesh committed simulation facts:
|
||||
The live interop harness sends Python a simulation observation, receives a
|
||||
consensus candidate, then requests the committed `gamecult.mesh.simulation_fact`
|
||||
|
|
|
|||
|
|
@ -223,11 +223,11 @@ from cultmesh_py import CultMesh, CultMeshGameSessionOptions, peer_exchange_requ
|
|||
|
||||
note_doc = define_database_entry_type("mesh.note", [("body", 0)])
|
||||
node = CultMesh.start_node("mesh.cc", runtime_id="python-runtime")
|
||||
node.register_document(note_doc)
|
||||
node.put(note_doc, "note:1", {"body": "hello"})
|
||||
live_note = node.get_required(note_doc, "note:1")
|
||||
put_message = node.put_raw_message(note_doc, "note:2", {"body": "wire me"}, shard_id="interop", shard_epoch=1)
|
||||
delete_message = node.delete_raw_message(note_doc, "note:2", shard_id="interop", shard_epoch=1)
|
||||
node.database.register_document(note_doc)
|
||||
node.database.put(note_doc, "note:1", {"body": "hello"})
|
||||
live_note = node.database.get_required(note_doc, "note:1")
|
||||
put_message = node.database.put_raw_message(note_doc, "note:2", {"body": "wire me"}, shard_id="interop", shard_epoch=1)
|
||||
delete_message = node.database.delete_raw_message(note_doc, "note:2", shard_id="interop", shard_epoch=1)
|
||||
|
||||
peers = CultMesh.create_peer_catalog()
|
||||
response = peers.create_response(peer_exchange_request("pex-1", verse_id="local"))
|
||||
|
|
@ -238,8 +238,8 @@ mesh_peers = client.fetch_peers(verse_id="python-interop", roles=["read-replica"
|
|||
client.sync_peer_catalog(peers, verse_id="python-interop", roles=["read-replica"])
|
||||
|
||||
raw_client = CultNetRawClient("127.0.0.1", 3075)
|
||||
node.sync_snapshot(raw_client, schema_ids=[note_doc.catalog_entry().schema_id])
|
||||
node.sync_shard_log(raw_client, shard_id="interop", shard_epoch=1)
|
||||
node.database.sync_snapshot(raw_client, schema_ids=[note_doc.catalog_entry().schema_id])
|
||||
node.database.sync_shard_log(raw_client, shard_id="interop", shard_epoch=1)
|
||||
|
||||
streams = CultMesh.create_stream_catalog()
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ EXPECTED_EXPORTS: dict[str, tuple[str, ...]] = {
|
|||
),
|
||||
"cultmesh_py": (
|
||||
"CultMesh",
|
||||
"CultMeshDatabase",
|
||||
"CultMeshGameSession",
|
||||
"CultMeshPeerExchangeClient",
|
||||
"CultMeshSimulationFact",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from .client import CultMeshDiscoveryClient, CultMeshPeerExchangeClient, CultMeshVerseDiscoveryClient
|
||||
from .facade import CultMesh
|
||||
from .node import CultMeshNode, create_node
|
||||
from .node import CultMeshDatabase, CultMeshNode, create_node
|
||||
from .session import (
|
||||
CultMeshGameSession,
|
||||
CultMeshGameSessionOptions,
|
||||
|
|
@ -40,6 +40,7 @@ from .wire import (
|
|||
|
||||
__all__ = [
|
||||
"CultMeshNode",
|
||||
"CultMeshDatabase",
|
||||
"CultMesh",
|
||||
"CultMeshGameSession",
|
||||
"CultMeshGameSessionOptions",
|
||||
|
|
|
|||
|
|
@ -22,6 +22,118 @@ class CultMeshNode:
|
|||
cache: CultCache = field(default_factory=CultCache)
|
||||
runtime_id: str = "python-runtime"
|
||||
documents: list[DocumentDefinition[Any]] = field(default_factory=list)
|
||||
database: "CultMeshDatabase" = field(init=False)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
self.database = CultMeshDatabase(self)
|
||||
|
||||
def register_document(self, document: DocumentDefinition[Any]) -> None:
|
||||
self.database.register_document(document)
|
||||
|
||||
def put(self, document: DocumentDefinition[Any], key: str, value: Any) -> None:
|
||||
self.database.put(document, key, value)
|
||||
|
||||
def put_raw_message(
|
||||
self,
|
||||
document: DocumentDefinition[Any],
|
||||
key: str,
|
||||
value: Any,
|
||||
*,
|
||||
message_id: str = "",
|
||||
shard_id: str | None = None,
|
||||
shard_epoch: int | None = None,
|
||||
) -> CultNetMessage:
|
||||
return self.database.put_raw_message(
|
||||
document,
|
||||
key,
|
||||
value,
|
||||
message_id=message_id,
|
||||
shard_id=shard_id,
|
||||
shard_epoch=shard_epoch,
|
||||
)
|
||||
|
||||
def get(self, document: DocumentDefinition[Any], key: str) -> Any:
|
||||
return self.database.get(document, key)
|
||||
|
||||
def get_required(self, document: DocumentDefinition[Any], key: str) -> Any:
|
||||
return self.database.get_required(document, key)
|
||||
|
||||
def delete(self, document: DocumentDefinition[Any], key: str) -> None:
|
||||
self.database.delete(document, key)
|
||||
|
||||
def delete_raw_message(
|
||||
self,
|
||||
document: DocumentDefinition[Any],
|
||||
key: str,
|
||||
*,
|
||||
message_id: str = "",
|
||||
shard_id: str | None = None,
|
||||
shard_epoch: int | None = None,
|
||||
) -> CultNetMessage:
|
||||
return self.database.delete_raw_message(
|
||||
document,
|
||||
key,
|
||||
message_id=message_id,
|
||||
shard_id=shard_id,
|
||||
shard_epoch=shard_epoch,
|
||||
)
|
||||
|
||||
def snapshot(self) -> dict[str, dict[str, Any]]:
|
||||
return self.database.snapshot()
|
||||
|
||||
def pull(self) -> None:
|
||||
self.database.pull()
|
||||
|
||||
def sync_snapshot(
|
||||
self,
|
||||
client: CultNetRawClient,
|
||||
*,
|
||||
schema_ids: list[str] | None = None,
|
||||
record_keys: list[str] | None = None,
|
||||
shard_id: str | None = None,
|
||||
shard_epoch: int | None = None,
|
||||
) -> list[CultNetAppliedRecord]:
|
||||
return self.database.sync_snapshot(
|
||||
client,
|
||||
schema_ids=schema_ids,
|
||||
record_keys=record_keys,
|
||||
shard_id=shard_id,
|
||||
shard_epoch=shard_epoch,
|
||||
)
|
||||
|
||||
def sync_shard_log(
|
||||
self,
|
||||
client: CultNetRawClient,
|
||||
*,
|
||||
shard_id: str,
|
||||
shard_epoch: int | None = None,
|
||||
after_sequence: int = 0,
|
||||
limit: int | None = None,
|
||||
) -> list[CultNetAppliedRecord]:
|
||||
return self.database.sync_shard_log(
|
||||
client,
|
||||
shard_id=shard_id,
|
||||
shard_epoch=shard_epoch,
|
||||
after_sequence=after_sequence,
|
||||
limit=limit,
|
||||
)
|
||||
|
||||
|
||||
class CultMeshDatabase:
|
||||
def __init__(self, node: CultMeshNode) -> None:
|
||||
self._node = node
|
||||
|
||||
@property
|
||||
def cache(self) -> CultCache:
|
||||
return self._node.cache
|
||||
|
||||
@property
|
||||
def documents(self) -> list[DocumentDefinition[Any]]:
|
||||
return self._node.documents
|
||||
|
||||
@property
|
||||
def runtime_id(self) -> str:
|
||||
return self._node.runtime_id
|
||||
|
||||
def register_document(self, document: DocumentDefinition[Any]) -> None:
|
||||
self.cache.register_document_type(document)
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ from cultnet_py.interop_peer import wire_message_schema_descriptors
|
|||
from cultmesh_py import create_node
|
||||
from cultmesh_py import (
|
||||
CultMesh,
|
||||
CultMeshDatabase,
|
||||
CultMeshGameSessionOptions,
|
||||
CultMeshPeerCard,
|
||||
CultMeshPeerCatalog,
|
||||
|
|
@ -867,13 +868,15 @@ class CultCacheTests(unittest.TestCase):
|
|||
document = define_database_entry_type("mesh.note", [("body", 0)])
|
||||
store_path = Path(tmp) / "mesh.cc"
|
||||
node = create_node(store_path, runtime_id="mesh-test")
|
||||
node.register_document(document)
|
||||
node.put(document, "note:1", {"body": "hello"})
|
||||
self.assertIsInstance(node.database, CultMeshDatabase)
|
||||
node.database.register_document(document)
|
||||
node.database.put(document, "note:1", {"body": "hello"})
|
||||
self.assertEqual(node.get_required(document, "note:1")["body"], "hello")
|
||||
|
||||
reopened = create_node(store_path, runtime_id="mesh-test")
|
||||
reopened.register_document(document)
|
||||
reopened.pull()
|
||||
self.assertEqual(reopened.get(document, "note:1")["body"], "hello")
|
||||
reopened.database.register_document(document)
|
||||
reopened.database.pull()
|
||||
self.assertEqual(reopened.database.get(document, "note:1")["body"], "hello")
|
||||
|
||||
def test_cultmesh_facade_matches_peer_runtime_entrypoints(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
|
|
|
|||
Loading…
Reference in a new issue