107 lines
3.0 KiB
Python
107 lines
3.0 KiB
Python
import sys
|
|
from datetime import UTC, datetime
|
|
from pathlib import Path
|
|
from types import ModuleType
|
|
from uuid import UUID
|
|
|
|
import msgspec
|
|
|
|
from kanta.kanta import Kanta
|
|
from kanta.structs import ChangeRecord, Snapshot
|
|
|
|
|
|
class User(msgspec.Struct):
|
|
name: str = ""
|
|
age: int = 0
|
|
|
|
|
|
class Data(msgspec.Struct):
|
|
users: dict[str, User] = {}
|
|
counter: int = 0
|
|
|
|
|
|
class ExoticData(msgspec.Struct, omit_defaults=False):
|
|
uuid_values: dict[str, UUID] = {}
|
|
uuid_keys: dict[UUID, int] = {}
|
|
datetime_values: dict[str, datetime] = {}
|
|
datetime_keys: dict[datetime, int] = {}
|
|
bytes_values: dict[str, bytes] = {}
|
|
bytes_keys: dict[bytes, int] = {}
|
|
|
|
|
|
class EvolvableDataV1(msgspec.Struct, omit_defaults=False):
|
|
counter: int = 0
|
|
|
|
|
|
class EvolvableDataV2(msgspec.Struct, omit_defaults=False):
|
|
counter: int = 0
|
|
enabled: bool = True
|
|
|
|
|
|
def make_kanta(path: Path, data_or_type, format_config, **kwargs):
|
|
_, serializer_cls = format_config
|
|
root = data_or_type() if isinstance(data_or_type, type) else data_or_type
|
|
return Kanta(
|
|
str(path),
|
|
root,
|
|
serializer=serializer_cls(),
|
|
**kwargs,
|
|
)
|
|
|
|
|
|
def seed_single_change(path: Path, change: ChangeRecord, format_config) -> None:
|
|
_, serializer_cls = format_config
|
|
serializer = serializer_cls()
|
|
framer = serializer.framer_cls()
|
|
payload = serializer.encode(change)
|
|
path.write_bytes(framer.frame_change(payload, record_offset=0))
|
|
|
|
|
|
def change_actions(path: Path, format_config) -> list[str]:
|
|
_, serializer_cls = format_config
|
|
serializer = serializer_cls()
|
|
framer = serializer.framer_cls()
|
|
actions: list[str] = []
|
|
for is_snapshot, payload, _, _ in framer.iter_records(path.read_bytes(), 0):
|
|
if is_snapshot:
|
|
continue
|
|
rec = serializer.decode(payload, type=ChangeRecord)
|
|
actions.append(rec.a)
|
|
return actions
|
|
|
|
|
|
def read_changes(path: Path, format_config) -> list[ChangeRecord]:
|
|
_, serializer_cls = format_config
|
|
serializer = serializer_cls()
|
|
framer = serializer.framer_cls()
|
|
records: list[ChangeRecord] = []
|
|
for is_snapshot, payload, _, _ in framer.iter_records(path.read_bytes(), 0):
|
|
if is_snapshot:
|
|
continue
|
|
records.append(serializer.decode(payload, type=ChangeRecord))
|
|
return records
|
|
|
|
|
|
def make_migrations_module(name: str, fn_name: str, fn):
|
|
mod = ModuleType(name)
|
|
mod.__dict__[fn_name] = fn
|
|
sys.modules[name] = mod
|
|
return mod
|
|
|
|
|
|
def read_last_snapshot(path: Path, format_config) -> Snapshot | None:
|
|
_, serializer_cls = format_config
|
|
serializer = serializer_cls()
|
|
framer = serializer.framer_cls()
|
|
data = path.read_bytes()
|
|
payload, _, _ = framer.scan_last_snapshot(data)
|
|
if payload is None:
|
|
return None
|
|
return serializer.decode(payload, type=Snapshot)
|
|
|
|
|
|
def fixed_change(action: str, diff: dict, *, version: int = 0) -> ChangeRecord:
|
|
return ChangeRecord(
|
|
ts=datetime(2026, 1, 1, tzinfo=UTC), a=action, v=version, diff=diff
|
|
)
|