Demo: resolve user names from database state, kanta instances at top with stacked decorators

This commit is contained in:
Leo Vasanko
2026-08-07 00:06:54 +00:00
parent 5d66d423da
commit 72944c9410
+33 -36
View File
@@ -17,24 +17,12 @@ from types import ModuleType
import msgspec import msgspec
from kanta import Kanta from kanta import Kanta
from kanta.callbacks import DictPost, DictPre
from kanta.logging import _ACTION, _ACTOR, _RESET, _SESSION, _TARGET from kanta.logging import _ACTION, _ACTOR, _RESET, _SESSION, _TARGET
from kanta.logging import configure_logging from kanta.logging import configure_logging
DB = Path(__file__).with_name("demo.kantadb") DB = Path(__file__).with_name("demo.kantadb")
# Fake directory: user id -> display name, resolved by the logfmt callbacks.
USERS = {"u1": "Alice", "u2": "Bob", "u3": "Carol"}
_now = datetime(2026, 8, 6, tzinfo=UTC)
def fake_now() -> datetime:
"""Deterministic clock: starts at midnight, +1h on every read."""
global _now
ts = _now
_now += timedelta(hours=1)
return ts
class DataV1(msgspec.Struct): class DataV1(msgspec.Struct):
"""Original schema (version 0).""" """Original schema (version 0)."""
@@ -59,19 +47,41 @@ def migrate_v1(d: dict) -> None:
migrations = ModuleType("demo_migrations") migrations = ModuleType("demo_migrations")
migrations.migrate_v1 = migrate_v1 migrations.migrate_v1 = migrate_v1
# Phase 1 instance: default logging, original schema.
kanta_v0 = Kanta(DB, DataV1())
# Phase 2 instance: migrations and a custom log header.
kanta_v1 = Kanta(DB, Data(), migrations=migrations)
def resolve_actor(value: str) -> str | None: _now = datetime(2026, 8, 6, tzinfo=UTC)
"""Resolve the transaction user id to a display name."""
return USERS.get(value)
def resolve_user_key(value: str, path: str) -> str | None: @kanta_v0.clock
"""Resolve user ids in diff paths to display names.""" @kanta_v1.clock
if path.startswith("users."): def fake_now() -> datetime:
return USERS.get(value) """Deterministic clock: starts at midnight, +1h on every read."""
global _now
ts = _now
_now += timedelta(hours=1)
return ts
@kanta_v0.logfmt
@kanta_v1.logfmt
def resolve_user(
value: str, path: str, previous: DictPre, current: DictPost
) -> str | None:
"""Resolve user ids to names from the database state itself."""
if path != "$user" and not path.startswith("users."):
return None
# Post-change state first, then pre-change (deleted users are only there).
for state in (current, previous):
name = state.get("users", {}).get(value, {}).get("name")
if name:
return name
return None return None
@kanta_v1.logheader
def header(action: str, user: str | None, extra: dict | None) -> str: def header(action: str, user: str | None, extra: dict | None) -> str:
"""Aligned rich header: actor, session id, action, target.""" """Aligned rich header: actor, session id, action, target."""
actor = f"{_ACTOR}{user or '-':<8}{_RESET}" actor = f"{_ACTOR}{user or '-':<8}{_RESET}"
@@ -80,25 +90,12 @@ def header(action: str, user: str | None, extra: dict | None) -> str:
return f"{actor} {session} {_ACTION}{action}{_RESET} {target}" return f"{actor} {session} {_ACTION}{action}{_RESET} {target}"
@kanta_v0.bootstrap
def seed(data: DataV1) -> None: def seed(data: DataV1) -> None:
"""Bootstrap: create the initial admin user.""" """Create the initial admin user."""
data.users["u1"] = {"name": "Alice", "role": "admin"} data.users["u1"] = {"name": "Alice", "role": "admin"}
# Phase 1 instance: default logging, original schema.
kanta_v0 = Kanta(DB, DataV1())
# Phase 2 instance: migrations and a custom log header.
kanta_v1 = Kanta(DB, Data(), migrations=migrations)
for k in (kanta_v0, kanta_v1):
k.clock(fake_now)
k.logfmt(resolve_user_key)
k.logfmt(resolve_actor, path="$user")
kanta_v0.bootstrap(seed)
kanta_v1.logheader(header)
def section(title: str) -> None: def section(title: str) -> None:
print(f"\n# {title}", flush=True) print(f"\n# {title}", flush=True)
@@ -157,7 +154,7 @@ async def main() -> None:
with kanta_v1.transaction( with kanta_v1.transaction(
action="update", action="update",
user="u2", user="u3",
extra={"session_id": 7, "target": "settings (demo)"}, extra={"session_id": 7, "target": "settings (demo)"},
) as data: ) as data:
data.settings["lang"] = "en" data.settings["lang"] = "en"