Replace log dict toggles with logdiff kwarg and kanta.transaction.diff logger

Per-transaction logdiff=False skips building and printing the diff body,
logging only the header. Globally, configure_logging(diff=False) disables
the kanta.transaction.diff child logger, which now carries all diff lines,
so applications can route or silence diffs separately from headers.
This commit is contained in:
Leo Vasanko
2026-08-07 01:26:50 +00:00
parent 9e19a1bf21
commit e42f81f44f
7 changed files with 132 additions and 110 deletions
+37 -57
View File
@@ -1,15 +1,5 @@
"""Kanta feature demo.
Run from the project root: python demo/main.py
Demonstrates bootstrap, colored transaction diffs, logfmt value formatting,
logging toggles, rollback, migrations, and a custom clock.
The database is recreated with fixed timestamps on every run; everything else
lives in this file.
"""
#!/usr/bin/env -S uv run
import asyncio
import logging
import sys
from datetime import UTC, datetime, timedelta
from pathlib import Path
@@ -38,34 +28,24 @@ kanta_v0 = Kanta(filename, Data())
@kanta_v0.bootstrap
def bootstrap(data: Data) -> None:
"""Create the initial admin user."""
data.users["u1"] = {"name": "Alice", "role": "admin"}
data.users["userid001"] = {"name": "Alice", "role": "admin"}
# Redefinition to simulate new version with counter renamed to total
# Redefinition to simulate new version
class Data(msgspec.Struct):
users: dict[str, dict] = {}
total: int = 0
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.pop("counter")
d["total"] = d["counter"]
kanta_v1 = Kanta(filename, Data(), migrations=sys.modules[__name__])
_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
@kanta_v0.logfmt
@kanta_v1.logfmt
def resolve_user(
@@ -82,61 +62,61 @@ def resolve_user(
return None
def section(title: str) -> None:
print(f"\n# {title}", flush=True)
async def main() -> None:
filename.unlink(missing_ok=True)
section("Database creation with v0 schema and basic access")
print("# Database creation with v0 schema and basic ops, pretty logs", flush=True)
# Open and close automatically; you can also `await kanta.open()` instead
async with kanta_v0 as kanta:
with kanta.transaction(action="create", user="u2") as data:
data.users["u2"] = {"name": "Bob", "role": "user"}
with kanta.transaction(action="create", user="userid001") as data:
data.users["userid002"] = {"name": "Bob", "role": "user"}
with kanta.transaction(action="update", user="u1") as data:
data.users["u2"]["role"] = "editor"
with kanta.transaction(action="update", user="userid001") as data:
data.users["userid002"]["role"] = "editor"
data.counter = 1
with kanta.transaction(action="delete", user="u1") as data:
del data.users["u2"]
with kanta.transaction(action="delete", user="userid002") as data:
del data.users["userid001"]
# Display-only extra string, appended after the action.
with kanta.transaction(action="export", user="u1", extra=filename.name) as data:
with kanta.transaction(
action="export", user="userid002", extra="extra info"
) as data:
data.counter = 2
# Compact logging, diff only: a system fix stamped by the clock, but the
# modification time (m) is not updated.
with kanta.transaction(
action="repair", mtime=False, 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:
with kanta.transaction(action="reset") as data:
data.counter = 99
raise ValueError("simulated failure")
except ValueError:
pass
print(f"# Reading does not need transaction: {data.counter=}", flush=True)
# Compact logging, header only.
with kanta.transaction(
action="import", user="u1", log={"header": True, "diff": False}
) as data:
with kanta.transaction(action="import", logdiff=False) as data:
data.counter = 3
section("A later version of our application with new data model and migrations")
print(
"\n# A later version of our application with new data model and migrations",
flush=True,
)
async with kanta_v1 as kanta:
with kanta.transaction(action="update", user="u1", extra=filename.name) as data:
with kanta.transaction(
action="update", user="userid002", extra=filename.name
) as data:
data.total = 4
with kanta.transaction(action="create", user="u3", extra="Dave (u4)") as data:
data.users["u4"] = {"name": "Dave", "role": "user"}
# 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()
logging.getLogger("kanta").setLevel(logging.DEBUG) # show migration diffs
asyncio.run(main())