Kanta colors log header parts: extra is a plain string, logheader callbacks receive pre-colored parts
This commit is contained in:
+66
-42
@@ -4,10 +4,21 @@ import pytest
|
||||
|
||||
from kanta import Kanta
|
||||
from kanta.callbacks import DictPost, DictPre
|
||||
from kanta.logging import _ACTION, _RESET, _TARGET, _USER, configure_logging
|
||||
|
||||
from .support import Data, make_kanta, read_changes
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _reset_kanta_loggers():
|
||||
yield
|
||||
for name in ("kanta", "kanta.transaction", "kanta.bootstrap", "kanta.migration"):
|
||||
logger = logging.getLogger(name)
|
||||
logger.setLevel(logging.NOTSET)
|
||||
logger.propagate = True
|
||||
logger.handlers.clear()
|
||||
|
||||
|
||||
def test_logheader_rejects_async(tmp_path, format_config):
|
||||
kanta = make_kanta(tmp_path / "test.db", Data, format_config)
|
||||
|
||||
@@ -38,59 +49,74 @@ def test_logheader_rejects_unknown_annotation(tmp_path, format_config):
|
||||
return action
|
||||
|
||||
|
||||
def test_logheader_rejects_dict_extra(tmp_path, format_config):
|
||||
kanta = make_kanta(tmp_path / "test.db", Data, format_config)
|
||||
|
||||
with pytest.raises(TypeError, match="unsupported annotation"):
|
||||
|
||||
@kanta.logheader
|
||||
def header(action: str, extra: dict) -> str:
|
||||
return action
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_logheader_replaces_default_header(tmp_path, format_config, caplog):
|
||||
caplog.set_level(logging.INFO, logger="kanta.transaction")
|
||||
kanta = make_kanta(tmp_path / "test.db", Data, format_config)
|
||||
|
||||
@kanta.logheader
|
||||
def header(action: str, user: str | None, extra: dict | None) -> str:
|
||||
return f"HDR {action} user={user} session={extra['session']}"
|
||||
def header(action: str, user: str, extra: str) -> str:
|
||||
return f"{user} {action} {extra}"
|
||||
|
||||
await kanta.open(log=False)
|
||||
with kanta.transaction(action="update", user="alice", extra={"session": 3}) as data:
|
||||
with kanta.transaction(action="update", user="alice", extra="tgt") as data:
|
||||
data.counter = 1
|
||||
await kanta.close()
|
||||
|
||||
assert "HDR update user=alice session=3" in caplog.text
|
||||
# caplog strips ANSI codes; the colors are verified via capsys below.
|
||||
assert "alice update tgt" in caplog.text
|
||||
# The diff body is still logged after the custom header.
|
||||
assert "counter" in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_logheader_default_target_is_filename(tmp_path, format_config, caplog):
|
||||
caplog.set_level(logging.INFO, logger="kanta.transaction")
|
||||
kanta = make_kanta(tmp_path / "mydb.db", Data, format_config)
|
||||
async def test_logheader_parts_are_colored_by_kanta(tmp_path, format_config, capsys):
|
||||
logging.getLogger("kanta").handlers.clear()
|
||||
configure_logging()
|
||||
kanta = make_kanta(tmp_path / "test.db", Data, format_config)
|
||||
|
||||
@kanta.logheader
|
||||
def header(action: str, extra: dict | None) -> str:
|
||||
return f"target={extra['target']}"
|
||||
def header(action: str, user: str, extra: str) -> str:
|
||||
return f"{user} {action} {extra}"
|
||||
|
||||
await kanta.open(log=False)
|
||||
with kanta.transaction(action="update", user="alice", extra="tgt") as data:
|
||||
data.counter = 1
|
||||
await kanta.close()
|
||||
|
||||
err = capsys.readouterr().err
|
||||
assert f"{_USER}alice{_RESET}" in err
|
||||
assert f"{_ACTION}update{_RESET}" in err
|
||||
assert f"{_TARGET}tgt{_RESET}" in err
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_logheader_missing_user_and_extra_are_empty(
|
||||
tmp_path, format_config, caplog
|
||||
):
|
||||
caplog.set_level(logging.INFO, logger="kanta.transaction")
|
||||
kanta = make_kanta(tmp_path / "test.db", Data, format_config)
|
||||
|
||||
@kanta.logheader
|
||||
def header(action: str, user: str, extra: str) -> str:
|
||||
return f"<{user}><{extra}>"
|
||||
|
||||
await kanta.open(log=False)
|
||||
with kanta.transaction(action="update") as data:
|
||||
data.counter = 1
|
||||
await kanta.close()
|
||||
|
||||
assert "target=mydb.db" in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_logheader_explicit_target_kept(tmp_path, format_config, caplog):
|
||||
caplog.set_level(logging.INFO, logger="kanta.transaction")
|
||||
kanta = make_kanta(tmp_path / "mydb.db", Data, format_config)
|
||||
|
||||
@kanta.logheader
|
||||
def header(action: str, extra: dict | None) -> str:
|
||||
return f"target={extra['target']}"
|
||||
|
||||
await kanta.open(log=False)
|
||||
with kanta.transaction(
|
||||
action="update", extra={"target": "Project X (abcd1234)"}
|
||||
) as data:
|
||||
data.counter = 1
|
||||
await kanta.close()
|
||||
|
||||
assert "target=Project X (abcd1234)" in caplog.text
|
||||
assert "<><>" in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -106,7 +132,7 @@ async def test_logheader_injects_states_and_kanta(tmp_path, format_config, caplo
|
||||
kanta: Kanta,
|
||||
) -> str:
|
||||
return (
|
||||
f"{action} counter {previous.get('counter')}"
|
||||
f"counter {previous.get('counter')}"
|
||||
f" -> {current.get('counter')} db={kanta.filename.name}"
|
||||
)
|
||||
|
||||
@@ -115,7 +141,7 @@ async def test_logheader_injects_states_and_kanta(tmp_path, format_config, caplo
|
||||
data.counter = 5
|
||||
await kanta.close()
|
||||
|
||||
assert "increment counter 0 -> 5 db=test.db" in caplog.text
|
||||
assert "counter 0 -> 5 db=test.db" in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -128,15 +154,15 @@ async def test_logheader_chain_first_non_none_wins(tmp_path, format_config, capl
|
||||
return None
|
||||
|
||||
@kanta.logheader
|
||||
def second(action: str) -> str:
|
||||
return f"SECOND {action}"
|
||||
def second(action: str, extra: str) -> str:
|
||||
return f"SECOND {extra}"
|
||||
|
||||
await kanta.open(log=False)
|
||||
with kanta.transaction(action="update") as data:
|
||||
with kanta.transaction(action="update", extra="marked") as data:
|
||||
data.counter = 1
|
||||
await kanta.close()
|
||||
|
||||
assert "SECOND update" in caplog.text
|
||||
assert "SECOND marked" in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -169,7 +195,7 @@ async def test_logheader_receives_formatted_user(tmp_path, format_config, caplog
|
||||
return "Alice"
|
||||
|
||||
@kanta.logheader
|
||||
def header(action: str, user: str | None) -> str:
|
||||
def header(action: str, user: str) -> str:
|
||||
return f"actor={user}"
|
||||
|
||||
await kanta.open(log=False)
|
||||
@@ -186,13 +212,13 @@ async def test_logheader_applies_to_bootstrap(tmp_path, format_config, caplog):
|
||||
kanta = make_kanta(tmp_path / "test.db", Data, format_config)
|
||||
|
||||
@kanta.logheader
|
||||
def header(action: str, extra: dict | None) -> str:
|
||||
return f"BOOT {action} target={extra['target']}"
|
||||
def header(action: str) -> str:
|
||||
return f"BOOT {action}"
|
||||
|
||||
await kanta.open()
|
||||
await kanta.close()
|
||||
|
||||
assert "BOOT bootstrap target=test.db" in caplog.text
|
||||
assert "BOOT bootstrap" in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -217,9 +243,7 @@ async def test_extra_is_not_persisted(tmp_path, format_config):
|
||||
kanta = make_kanta(path, Data, format_config)
|
||||
|
||||
await kanta.open(log=False)
|
||||
with kanta.transaction(
|
||||
action="update", user="alice", extra={"session": 3, "target": "X"}
|
||||
) as data:
|
||||
with kanta.transaction(action="update", user="alice", extra="session-3") as data:
|
||||
data.counter = 1
|
||||
await kanta.close()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user