clean up veilid-python

This commit is contained in:
Christien Rioux 2025-06-19 10:41:02 -04:00
parent 9d4976b243
commit 9f0495cacb
5 changed files with 145 additions and 43 deletions

View file

@ -135,6 +135,8 @@ async def test_open_writer_dht_value(api_connection: veilid.VeilidAPI):
key = rec.key
owner = rec.owner
secret = rec.owner_secret
assert secret is not None
#print(f"key:{key}")
cs = await api_connection.get_crypto_system(rec.key.kind())
@ -151,6 +153,7 @@ async def test_open_writer_dht_value(api_connection: veilid.VeilidAPI):
assert vdtemp is None
vdtemp = await rc.get_dht_value(key, ValueSubkey(1), False)
assert vdtemp is not None
assert vdtemp.data == va
assert vdtemp.seq == 0
assert vdtemp.writer == owner
@ -164,9 +167,11 @@ async def test_open_writer_dht_value(api_connection: veilid.VeilidAPI):
await sync(rc, [rec])
vdtemp = await rc.get_dht_value(key, ValueSubkey(0), True)
assert vdtemp is not None
assert vdtemp.data == vb
vdtemp = await rc.get_dht_value(key, ValueSubkey(1), True)
assert vdtemp is not None
assert vdtemp.data == va
# Equal value should not trigger sequence number update
@ -191,7 +196,7 @@ async def test_open_writer_dht_value(api_connection: veilid.VeilidAPI):
assert rec.key == key
assert rec.owner == owner
assert rec.owner_secret == secret
assert rec.schema.kind == veilid.DHTSchemaKind.DFLT
assert isinstance(rec.schema, veilid.DHTSchemaDFLT)
assert rec.schema.o_cnt == 2
# Verify subkey 1 can be set before it is get but newer is available online
@ -225,7 +230,7 @@ async def test_open_writer_dht_value(api_connection: veilid.VeilidAPI):
assert rec.key == key
assert rec.owner == owner
assert rec.owner_secret is None
assert rec.schema.kind == veilid.DHTSchemaKind.DFLT
assert isinstance(rec.schema, veilid.DHTSchemaDFLT)
assert rec.schema.o_cnt == 2
# Verify subkey 1 can NOT be set because we have the wrong writer
@ -261,6 +266,7 @@ async def test_open_writer_dht_value_no_offline(api_connection: veilid.VeilidAPI
key = rec.key
owner = rec.owner
secret = rec.owner_secret
assert secret is not None
#print(f"key:{key}")
cs = await api_connection.get_crypto_system(rec.key.kind())
@ -277,6 +283,7 @@ async def test_open_writer_dht_value_no_offline(api_connection: veilid.VeilidAPI
assert vdtemp is None
vdtemp = await rc.get_dht_value(key, ValueSubkey(1), False)
assert vdtemp is not None
assert vdtemp.data == va
assert vdtemp.seq == 0
assert vdtemp.writer == owner
@ -287,12 +294,12 @@ async def test_open_writer_dht_value_no_offline(api_connection: veilid.VeilidAPI
vdtemp = await rc.set_dht_value(key, ValueSubkey(0), vb, veilid.SetDHTValueOptions(None, False))
assert vdtemp is None
await sync(rc, [rec])
vdtemp = await rc.get_dht_value(key, ValueSubkey(0), True)
assert vdtemp is not None
assert vdtemp.data == vb
vdtemp = await rc.get_dht_value(key, ValueSubkey(1), True)
assert vdtemp is not None
assert vdtemp.data == va
# Equal value should not trigger sequence number update
@ -316,7 +323,7 @@ async def test_open_writer_dht_value_no_offline(api_connection: veilid.VeilidAPI
assert rec.key == key
assert rec.owner == owner
assert rec.owner_secret == secret
assert rec.schema.kind == veilid.DHTSchemaKind.DFLT
assert isinstance(rec.schema, veilid.DHTSchemaDFLT)
assert rec.schema.o_cnt == 2
# Verify subkey 1 can be set before it is get but newer is available online
@ -348,7 +355,7 @@ async def test_open_writer_dht_value_no_offline(api_connection: veilid.VeilidAPI
assert rec.key == key
assert rec.owner == owner
assert rec.owner_secret is None
assert rec.schema.kind == veilid.DHTSchemaKind.DFLT
assert isinstance(rec.schema, veilid.DHTSchemaDFLT)
assert rec.schema.o_cnt == 2
# Verify subkey 1 can NOT be set because we have the wrong writer
@ -457,9 +464,11 @@ async def test_watch_dht_values():
upd = await asyncio.wait_for(value_change_queue.get(), timeout=10)
# Server 0: Verify the update came back with the first changed subkey's data
assert isinstance(upd.detail, veilid.VeilidValueChange)
assert upd.detail.key == rec0.key
assert upd.detail.count == 0xFFFFFFFE
assert upd.detail.subkeys == [(3, 3)]
assert upd.detail.value is not None
assert upd.detail.value.data == b"BLAH BLAH"
# Server 1: Now set subkey and trigger an update
@ -471,9 +480,11 @@ async def test_watch_dht_values():
upd = await asyncio.wait_for(value_change_queue.get(), timeout=10)
# Server 0: Verify the update came back with the first changed subkey's data
assert isinstance(upd.detail, veilid.VeilidValueChange)
assert upd.detail.key == rec0.key
assert upd.detail.count == 0xFFFFFFFD
assert upd.detail.subkeys == [(4, 4)]
assert upd.detail.value is not None
assert upd.detail.value.data == b"BZORT"
# Server 0: Cancel some subkeys we don't care about
@ -489,9 +500,11 @@ async def test_watch_dht_values():
upd = await asyncio.wait_for(value_change_queue.get(), timeout=10)
# Server 0: Verify only one update came back
assert isinstance(upd.detail, veilid.VeilidValueChange)
assert upd.detail.key == rec0.key
assert upd.detail.count == 0xFFFFFFFC
assert upd.detail.subkeys == [(4, 4)]
assert upd.detail.value is not None
assert upd.detail.value.data == b"BZORT BZORT"
# Server 0: Now we should NOT get any other update
@ -512,6 +525,7 @@ async def test_watch_dht_values():
upd = await asyncio.wait_for(value_change_queue.get(), timeout=10)
# Server 0: Verify only one update came back
assert isinstance(upd.detail, veilid.VeilidValueChange)
assert upd.detail.key == rec0.key
assert upd.detail.count == 0
assert upd.detail.subkeys == []
@ -618,6 +632,7 @@ async def test_watch_many_dht_values():
# Server 0: Wait for the update
try:
upd = await asyncio.wait_for(value_change_queue.get(), timeout=10)
assert isinstance(upd.detail, veilid.VeilidValueChange)
missing_records.remove(upd.detail.key)
except:
# Dump which records didn't get updates
@ -689,6 +704,7 @@ async def _run_test_schema_limit(api_connection: veilid.VeilidAPI, open_record:
desc1 = await rc.open_dht_record(desc.key)
for n in range(count):
vd0 = await rc.get_dht_value(desc1.key, ValueSubkey(n))
assert vd0 is not None
assert vd0.data == test_data
print(f' {n}')
@ -728,7 +744,7 @@ async def test_schema_limit_dflt(api_connection: veilid.VeilidAPI):
@pytest.mark.asyncio
async def test_schema_limit_smpl(api_connection: veilid.VeilidAPI):
async def open_record(rc: veilid.RoutingContext, count: int) -> tuple[veilid.TypedKey, veilid.PublicKey, veilid.SecretKey]:
async def open_record(rc: veilid.RoutingContext, count: int) -> tuple[veilid.DHTRecordDescriptor, veilid.KeyPair]:
cs = await api_connection.best_crypto_system()
async with cs:
writer_keypair = await cs.generate_key_pair()
@ -817,6 +833,7 @@ async def test_dht_integration_writer_reader():
desc1 = await rc1.open_dht_record(desc.key)
vd1 = await rc1.get_dht_value(desc1.key, ValueSubkey(0))
assert vd1 is not None
assert vd1.data == TEST_DATA
await rc1.close_dht_record(desc1.key)
@ -877,9 +894,11 @@ async def test_dht_write_read_local():
desc1 = await rc0.open_dht_record(desc0.key)
vd0 = await rc0.get_dht_value(desc1.key, ValueSubkey(0), force_refresh=True)
assert vd0 is not None
assert vd0.data == TEST_DATA
vd1 = await rc0.get_dht_value(desc1.key, ValueSubkey(1), force_refresh=True)
assert vd1 is not None
assert vd1.data == TEST_DATA2
await rc0.close_dht_record(desc1.key)
@ -1082,8 +1101,8 @@ async def sync_win(
max(0, int(cur_cols/2) - int(WIDTH/2)))
win.border(0,0,0,0)
win.addstr(1, 1, "syncing records to the network", curses.color_pair(0))
for n, rr in enumerate(records):
key = rr.key
for n, rd in enumerate(records):
key = rd.key
win.addstr(n+2, GRAPHWIDTH+1, key, curses.color_pair(0))
if key in donerecords:

View file

@ -49,7 +49,7 @@ class RoutingContext(ABC):
pass
@abstractmethod
async def app_call(self, target: types.TypedKey | types.RouteId, request: bytes) -> bytes:
async def app_call(self, target: types.TypedKey | types.RouteId, message: bytes) -> bytes:
pass
@abstractmethod
@ -132,7 +132,7 @@ class TableDbTransaction(ABC):
async def __aexit__(self, *excinfo):
self.ref_count -= 1
if self.ref_count == 0 and not self.is_done():
await self.release()
await self.rollback()
@abstractmethod
def is_done(self) -> bool:

View file

@ -522,7 +522,7 @@ class VeilidAppCall:
message: bytes
call_id: OperationId
def __init__(self, sender: Optional[TypedKey], route_id: Optional[TypedKey], message: bytes, call_id: OperationId):
def __init__(self, sender: Optional[TypedKey], route_id: Optional[RouteId], message: bytes, call_id: OperationId):
self.sender = sender
self.route_id = route_id
self.message = message

View file

@ -1,8 +1,9 @@
import base64
import json
from abc import ABC, abstractmethod
from enum import StrEnum
from functools import total_ordering
from typing import Any, Optional, Self
from typing import Any, Optional, Self, final
####################################################################
@ -267,9 +268,9 @@ class VeilidVersion:
def __eq__(self, other):
return (
isinstance(other, VeilidVersion)
and self.data == other.data
and self.seq == other.seq
and self.writer == other.writer
and self._major == other._major
and self._minor == other._minor
and self._patch == other._patch
)
@property
@ -323,26 +324,19 @@ class DHTSchemaSMPLMember:
return self.__dict__
class DHTSchema:
class DHTSchema(ABC):
kind: DHTSchemaKind
def __init__(self, kind: DHTSchemaKind, **kwargs):
def __init__(self, kind: DHTSchemaKind):
self.kind = kind
for k, v in kwargs.items():
setattr(self, k, v)
@classmethod
def dflt(cls, o_cnt: int) -> Self:
assert isinstance(o_cnt, int)
return cls(DHTSchemaKind.DFLT, o_cnt=o_cnt)
return DHTSchemaDFLT(o_cnt=o_cnt) # type: ignore
@classmethod
def smpl(cls, o_cnt: int, members: list[DHTSchemaSMPLMember]) -> Self:
assert isinstance(o_cnt, int)
assert isinstance(members, list)
for m in members:
assert isinstance(m, DHTSchemaSMPLMember)
return cls(DHTSchemaKind.SMPL, o_cnt=o_cnt, members=members)
return DHTSchemaSMPL(o_cnt=o_cnt, members=members) # type: ignore
@classmethod
def from_json(cls, j: dict) -> Self:
@ -358,6 +352,53 @@ class DHTSchema:
def to_json(self) -> dict:
return self.__dict__
@final
class DHTSchemaDFLT(DHTSchema):
o_cnt: int
def __init__(
self,
o_cnt: int
):
super().__init__(DHTSchemaKind.DFLT)
assert isinstance(o_cnt, int)
self.o_cnt = o_cnt
@classmethod
def from_json(cls, j: dict) -> Self:
if DHTSchemaKind(j["kind"]) == DHTSchemaKind.DFLT:
return cls(j["o_cnt"])
raise Exception("Invalid DHTSchemaDFLT")
@final
class DHTSchemaSMPL(DHTSchema):
o_cnt: int
members: list[DHTSchemaSMPLMember]
def __init__(
self,
o_cnt: int,
members: list[DHTSchemaSMPLMember]
):
super().__init__(DHTSchemaKind.SMPL)
assert isinstance(o_cnt, int)
assert isinstance(members, list)
for m in members:
assert isinstance(m, DHTSchemaSMPLMember)
self.o_cnt = o_cnt
self.members = members
@classmethod
def from_json(cls, j: dict) -> Self:
if DHTSchemaKind(j["kind"]) == DHTSchemaKind.SMPL:
return cls(j["o_cnt"],
[DHTSchemaSMPLMember.from_json(member) for member in j["members"]])
raise Exception("Invalid DHTSchemaSMPL")
class DHTRecordDescriptor:
key: TypedKey
@ -381,6 +422,8 @@ class DHTRecordDescriptor:
return f"<{self.__class__.__name__}(key={self.key!r}, owner={self.owner!r}, owner_secret={self.owner_secret!r}, schema={self.schema!r})>"
def owner_key_pair(self) -> Optional[KeyPair]:
if self.owner_secret is None:
return None
return KeyPair.from_parts(self.owner, self.owner_secret)
@classmethod
@ -435,7 +478,7 @@ class SetDHTValueOptions:
writer: Optional[KeyPair]
allow_offline: Optional[bool]
def __init__(self, writer: Optional[KeyPair], allow_offline: Optional[bool] = None):
def __init__(self, writer: Optional[KeyPair] = None, allow_offline: Optional[bool] = None):
self.writer = writer
self.allow_offline = allow_offline
@ -534,22 +577,20 @@ class SafetySpec:
def to_json(self) -> dict:
return self.__dict__
class SafetySelection(ABC):
class SafetySelection:
kind: SafetySelectionKind
def __init__(self, kind: SafetySelectionKind, **kwargs):
self.kind = kind
for k, v in kwargs.items():
setattr(self, k, v)
@property
@abstractmethod
def kind(self) -> SafetySelectionKind:
pass
@classmethod
def unsafe(cls, sequencing: Sequencing = Sequencing.PREFER_ORDERED) -> Self:
return cls(SafetySelectionKind.UNSAFE, sequencing=sequencing)
return SafetySelectionUnsafe(sequencing=sequencing) # type: ignore
@classmethod
def safe(cls, safety_spec: SafetySpec) -> Self:
return cls(SafetySelectionKind.SAFE, safety_spec=safety_spec)
return SafetySelectionSafe(safety_spec=safety_spec) # type: ignore
@classmethod
def from_json(cls, j: dict) -> Self:
@ -559,10 +600,48 @@ class SafetySelection:
return cls.unsafe(Sequencing(j["Unsafe"]))
raise Exception("Invalid SafetySelection")
@abstractmethod
def to_json(self) -> dict:
if self.kind == SafetySelectionKind.UNSAFE:
return {"Unsafe": self.sequencing}
elif self.kind == SafetySelectionKind.SAFE:
return {"Safe": self.safety_spec.to_json()}
else:
raise Exception("Invalid SafetySelection")
pass
@final
class SafetySelectionUnsafe(SafetySelection):
sequencing: Sequencing
def __init__(self, sequencing: Sequencing = Sequencing.PREFER_ORDERED):
assert isinstance(sequencing, Sequencing)
self.sequencing = sequencing
@property
def kind(self):
return SafetySelectionKind.UNSAFE
@classmethod
def from_json(cls, j: dict) -> Self:
if "Unsafe" in j:
return cls(Sequencing(j["Unsafe"]))
raise Exception("Invalid SafetySelectionUnsafe")
def to_json(self) -> dict:
return {"Unsafe": self.sequencing}
@final
class SafetySelectionSafe(SafetySelection):
safety_spec: SafetySpec
def __init__(self, safety_spec: SafetySpec):
assert isinstance(safety_spec, SafetySpec)
self.safety_spec = safety_spec
@property
def kind(self):
return SafetySelectionKind.SAFE
@classmethod
def from_json(cls, j: dict) -> Self:
if "Safe" in j:
return cls(SafetySpec.from_json(j["Safe"]))
raise Exception("Invalid SafetySelectionUnsafe")
def to_json(self) -> dict:
return {"Safe": self.safety_spec.to_json()}