Add feature demo app under demo/
This commit is contained in:
@@ -0,0 +1 @@
|
||||
demo.db
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
"""Kanta feature demo.
|
||||
|
||||
Run from the project root: python demo/main.py
|
||||
|
||||
Demonstrates bootstrap, colored transaction diffs, logfmt value formatting,
|
||||
custom log headers, logging toggles, rollback, and migrations. The database
|
||||
is recreated on every run; everything else lives in this file.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from types import ModuleType
|
||||
|
||||
import msgspec
|
||||
|
||||
from kanta import Kanta
|
||||
from kanta.logging import _ACTION, _ACTOR, _RESET, _SESSION, _TARGET
|
||||
from kanta.logging import configure_logging
|
||||
|
||||
DB = Path(__file__).with_name("demo.db")
|
||||
|
||||
# Fake directory: user id -> display name, resolved by the logfmt callbacks.
|
||||
USERS = {"u1": "Alice", "u2": "Bob", "u3": "Carol"}
|
||||
|
||||
|
||||
class DataV1(msgspec.Struct):
|
||||
"""Original schema (version 0)."""
|
||||
|
||||
users: dict[str, dict] = {}
|
||||
counter: int = 0
|
||||
|
||||
|
||||
class Data(msgspec.Struct):
|
||||
"""Current schema: migration v1 adds the settings section."""
|
||||
|
||||
users: dict[str, dict] = {}
|
||||
counter: int = 0
|
||||
settings: dict[str, str] = {}
|
||||
|
||||
|
||||
def migrate_v1(d: dict) -> None:
|
||||
"""Add settings section"""
|
||||
d["settings"] = {"theme": "dark"}
|
||||
|
||||
|
||||
migrations = ModuleType("demo_migrations")
|
||||
migrations.migrate_v1 = migrate_v1
|
||||
|
||||
|
||||
def add_logfmts(kanta: Kanta) -> None:
|
||||
"""Resolve user ids to display names in headers and diff paths."""
|
||||
|
||||
@kanta.logfmt(path="$user")
|
||||
def resolve_actor(value: str) -> str | None:
|
||||
return USERS.get(value)
|
||||
|
||||
@kanta.logfmt
|
||||
def resolve_user_key(value: str, path: str) -> str | None:
|
||||
if path.startswith("users."):
|
||||
return USERS.get(value)
|
||||
return None
|
||||
|
||||
|
||||
def add_header(kanta: Kanta) -> None:
|
||||
"""Aligned rich header: actor, session id, action, target."""
|
||||
|
||||
@kanta.logheader
|
||||
def header(action: str, user: str | None, extra: dict | None) -> str:
|
||||
actor = f"{_ACTOR}{user or '-':<8}{_RESET}"
|
||||
session = f"{_SESSION}{extra.get('session_id', '-'):>2}{_RESET}"
|
||||
target = f"{_TARGET}{extra['target']}{_RESET}"
|
||||
return f"{actor} {session} {_ACTION}{action}{_RESET} {target}"
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
DB.unlink(missing_ok=True)
|
||||
|
||||
print("=== Standard logging: bootstrap, diffs, toggles, rollback ===", flush=True)
|
||||
|
||||
kanta = Kanta(DB, DataV1())
|
||||
add_logfmts(kanta)
|
||||
|
||||
@kanta.bootstrap
|
||||
def seed(data: DataV1) -> None:
|
||||
data.users["u1"] = {"name": "Alice", "role": "admin"}
|
||||
|
||||
await kanta.open()
|
||||
|
||||
with kanta.transaction(action="create", user="u2") as data:
|
||||
data.users["u2"] = {"name": "Bob", "role": "user"}
|
||||
|
||||
with kanta.transaction(action="update", user="u1") as data:
|
||||
data.users["u2"]["role"] = "editor"
|
||||
data.counter = 1
|
||||
|
||||
with kanta.transaction(action="delete", user="u1") as data:
|
||||
del data.users["u2"]
|
||||
|
||||
# Display-only extra string, appended after the action.
|
||||
with kanta.transaction(action="export", user="u1", extra=DB.name) as data:
|
||||
data.counter = 2
|
||||
|
||||
# Compact logging: diff only (no header) ...
|
||||
with kanta.transaction(
|
||||
action="repair", log={"header": False, "diff": True}
|
||||
) as data:
|
||||
data.users["u3"] = {"name": "Carol", "role": "user"}
|
||||
|
||||
# A failing transaction rolls back and logs a warning.
|
||||
try:
|
||||
with kanta.transaction(action="reset", user="u1") as data:
|
||||
data.counter = 99
|
||||
raise ValueError("simulated failure")
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# ... and header only (no diff).
|
||||
with kanta.transaction(
|
||||
action="import", user="u1", log={"header": True, "diff": False}
|
||||
) as data:
|
||||
data.counter = 3
|
||||
|
||||
await kanta.close()
|
||||
|
||||
print("=== Reopen with migrations and a custom log header ===", flush=True)
|
||||
|
||||
kanta = Kanta(DB, Data(), migrations=migrations)
|
||||
add_logfmts(kanta)
|
||||
add_header(kanta)
|
||||
await kanta.open()
|
||||
|
||||
# No target given: defaults to the database filename.
|
||||
with kanta.transaction(action="update", user="u1", extra={"session_id": 3}) as data:
|
||||
data.settings["theme"] = "light"
|
||||
|
||||
with kanta.transaction(
|
||||
action="update",
|
||||
user="u2",
|
||||
extra={"session_id": 7, "target": "settings (demo)"},
|
||||
) as data:
|
||||
data.settings["lang"] = "en"
|
||||
|
||||
await kanta.close()
|
||||
|
||||
print(f"=== Database written to {DB} ===", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
configure_logging()
|
||||
logging.getLogger("kanta").setLevel(logging.DEBUG) # show migration diffs
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user