Files
kanta/demo/main.py
T

111 lines
3.1 KiB
Python

#!/usr/bin/env -S uv run
import asyncio
import sys
from datetime import UTC, datetime, timedelta
from pathlib import Path
import msgspec
from kanta import Kanta
from kanta.callbacks import DictPre
from kanta.logging import configure_logging
filename = Path(__file__).with_name("demo.kantadb")
# For demonstration purposes, we use "original v0" and "modified v1" in this same script
# Normally your app would only have the latest supported data model
class Data(msgspec.Struct): # type: ignore - intentionally redefined later
users: dict[str, dict] = {}
counter: int = 0
kanta_v0 = Kanta(filename, Data())
@kanta_v0.bootstrap
def bootstrap(data: Data) -> None:
"""Create the initial admin user."""
data.users["userid001"] = {"name": "Alice", "role": "admin"}
# Redefinition to simulate new version
class Data(msgspec.Struct):
users: dict[str, dict] = {}
total: int = 0 # Replaces old counter field
lang: str = "en" # New field
def migrate_v1(d: dict) -> None:
"""Rename counter to total"""
d["total"] = d["counter"]
kanta_v1 = Kanta(filename, Data(), migrations=sys.modules[__name__])
@kanta_v1.logfmt
def resolve_user(value: str, path: str, previous: DictPre) -> str | None:
"""Resolve user ids to names from the database state itself."""
if path != "$user" and not path.startswith("users."):
return None
return previous.get("users", {}).get(value, {}).get("name")
async def main() -> None:
filename.unlink(missing_ok=True)
print("# Database creation with v0 schema and basic ops, pretty logs")
# Open and close automatically; you can also `await kanta.open()` instead
async with kanta_v0 as kanta:
with kanta.transaction(action="create", user="userid001") as data:
data.users["userid002"] = {"name": "Bob", "role": "user"}
with kanta.transaction(action="update", user="userid001") as data:
data.users["userid002"]["role"] = "editor"
data.counter = 1
# Display-only extra string, appended after the action.
with kanta.transaction(
action="export", user="userid002", extra="extra info"
) as data:
data.counter = 2
try:
with kanta.transaction(action="reset") as data:
data.counter = 99
raise ValueError("simulated failure")
except ValueError:
print(f"# Reading does not need transaction: {data.counter=}", flush=True)
print(
"\n# A later version of our application with new data model, migrations and logfmt"
)
async with kanta_v1 as kanta:
with kanta.transaction(
action="update", user="userid002", extra=filename.name
) as data:
data.total += 1
with kanta.transaction(action="delete", user="userid002") as data:
del data.users["userid001"]
# Fake clock for deterministic timestamps
_now = datetime(2027, 1, 1, tzinfo=UTC)
@kanta_v0.clock
@kanta_v1.clock
def fake_clock() -> datetime:
global _now
_now += timedelta(hours=1)
return _now
if __name__ == "__main__":
configure_logging()
asyncio.run(main())