Log new database creation, bootstrap like a transaction.
This commit is contained in:
+7
-5
@@ -163,11 +163,13 @@ class Kanta(Generic[T]):
|
|||||||
readonly: If True, open the database read-only. No lock is acquired,
|
readonly: If True, open the database read-only. No lock is acquired,
|
||||||
no background flush task is started, and transactions are
|
no background flush task is started, and transactions are
|
||||||
rejected. The file is not created if missing.
|
rejected. The file is not created if missing.
|
||||||
log: Controls migration logging. ``True`` (default) uses the
|
log: Controls bootstrap and migration logging. ``True`` (default)
|
||||||
``kanta.migrations`` logger. ``False`` suppresses the default
|
uses the ``kanta.changes`` logger for bootstrap records and the
|
||||||
migration log. A :class:`~logging.Logger` instance writes
|
``kanta.migrations`` logger for migration output. ``False``
|
||||||
default migration output to that logger instead. Custom
|
suppresses the default bootstrap and migration logs. A
|
||||||
``@kanta.logmigr`` callbacks run regardless of this setting.
|
:class:`~logging.Logger` instance writes default output to that
|
||||||
|
logger instead. Custom ``@kanta.logmigr`` callbacks run
|
||||||
|
regardless of this setting.
|
||||||
|
|
||||||
Calling ``open`` more than once on the same instance is not allowed.
|
Calling ``open`` more than once on the same instance is not allowed.
|
||||||
|
|
||||||
|
|||||||
+26
-2
@@ -12,7 +12,7 @@ from typing import Any, Generic, TypeVar
|
|||||||
|
|
||||||
from kanta.callbacks import CallbackRegistry, InjectionContext
|
from kanta.callbacks import CallbackRegistry, InjectionContext
|
||||||
from kanta.exceptions import DatabaseError, DataIntegrityError, ReplayError
|
from kanta.exceptions import DatabaseError, DataIntegrityError, ReplayError
|
||||||
from kanta.logging import log_change, migration_logger
|
from kanta.logging import _USER_PATH, changes_logger, log_change, migration_logger
|
||||||
from kanta.migrations import MigrationResult, Migrations
|
from kanta.migrations import MigrationResult, Migrations
|
||||||
from kanta.persistence import PersistenceMixin
|
from kanta.persistence import PersistenceMixin
|
||||||
from kanta.serialization import restore_data_in_place, struct_to_dict
|
from kanta.serialization import restore_data_in_place, struct_to_dict
|
||||||
@@ -277,13 +277,37 @@ class KantaImpl(PersistenceMixin, Generic[T]):
|
|||||||
|
|
||||||
self.statedict = {}
|
self.statedict = {}
|
||||||
current = struct_to_dict(self.data, serializer=self.serializer)
|
current = struct_to_dict(self.data, serializer=self.serializer)
|
||||||
self.queue_change(
|
record = self.queue_change(
|
||||||
self.bootstrap_action,
|
self.bootstrap_action,
|
||||||
current,
|
current,
|
||||||
user=self.bootstrap_user,
|
user=self.bootstrap_user,
|
||||||
mtime=self.bootstrap_mtime,
|
mtime=self.bootstrap_mtime,
|
||||||
force=True,
|
force=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if record is not None and log is not False:
|
||||||
|
logger = log if isinstance(log, logging.Logger) else changes_logger
|
||||||
|
logger.info("Created %s", self.filename.resolve())
|
||||||
|
logfmt = self.callback_registry.build_logfmt(
|
||||||
|
InjectionContext(
|
||||||
|
previous_state={},
|
||||||
|
current_state=current,
|
||||||
|
kanta=self._kanta,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
formatted_user = self.bootstrap_user
|
||||||
|
if formatted_user is not None and logfmt is not None:
|
||||||
|
resolved = logfmt(formatted_user, _USER_PATH)
|
||||||
|
if resolved is not None:
|
||||||
|
formatted_user = resolved
|
||||||
|
log_change(
|
||||||
|
self.bootstrap_action,
|
||||||
|
record.diff,
|
||||||
|
formatted_user,
|
||||||
|
previous={},
|
||||||
|
logfmt=logfmt,
|
||||||
|
logger=logger,
|
||||||
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
self.opened = False
|
self.opened = False
|
||||||
self.file.close()
|
self.file.close()
|
||||||
|
|||||||
@@ -177,6 +177,8 @@ async def test_logfmt_class_injection(tmp_path, format_config, caplog):
|
|||||||
@kanta.logfmt
|
@kanta.logfmt
|
||||||
class UserLogFmt(LogFmt):
|
class UserLogFmt(LogFmt):
|
||||||
def resolve(self, value: str, path: str) -> str | None:
|
def resolve(self, value: str, path: str) -> str | None:
|
||||||
|
if not isinstance(value, str):
|
||||||
|
return None
|
||||||
return self.current_state.get("users", {}).get(value, {}).get("name")
|
return self.current_state.get("users", {}).get(value, {}).get("name")
|
||||||
|
|
||||||
await kanta.open()
|
await kanta.open()
|
||||||
|
|||||||
@@ -623,6 +623,52 @@ async def test_open_log_false_suppresses_migration_log(tmp_path, format_config,
|
|||||||
assert not info_messages
|
assert not info_messages
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_open_log_true_logs_bootstrap(tmp_path, format_config, caplog):
|
||||||
|
path = tmp_path / "test.db"
|
||||||
|
kanta = make_kanta(path, Data, format_config)
|
||||||
|
|
||||||
|
with caplog.at_level(logging.INFO, logger="kanta.changes"):
|
||||||
|
await kanta.open()
|
||||||
|
await kanta.close()
|
||||||
|
|
||||||
|
info_messages = [r.message for r in caplog.records if r.levelno == logging.INFO]
|
||||||
|
assert len(info_messages) >= 2
|
||||||
|
assert "Created" in info_messages[0]
|
||||||
|
assert "bootstrap" in info_messages[1]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_open_log_false_suppresses_bootstrap_log(tmp_path, format_config, caplog):
|
||||||
|
path = tmp_path / "test.db"
|
||||||
|
kanta = make_kanta(path, Data, format_config)
|
||||||
|
|
||||||
|
with caplog.at_level(logging.INFO, logger="kanta.changes"):
|
||||||
|
await kanta.open(log=False)
|
||||||
|
await kanta.close()
|
||||||
|
|
||||||
|
info_messages = [r for r in caplog.records if r.levelno == logging.INFO]
|
||||||
|
assert not info_messages
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_open_log_custom_logger_logs_bootstrap(tmp_path, format_config, caplog):
|
||||||
|
path = tmp_path / "test.db"
|
||||||
|
kanta = make_kanta(path, Data, format_config)
|
||||||
|
|
||||||
|
custom_logger = logging.getLogger("custom.bootstrap")
|
||||||
|
custom_logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
with caplog.at_level(logging.INFO, logger="custom.bootstrap"):
|
||||||
|
await kanta.open(log=custom_logger)
|
||||||
|
await kanta.close()
|
||||||
|
|
||||||
|
info_messages = [r.message for r in caplog.records if r.levelno == logging.INFO]
|
||||||
|
assert len(info_messages) >= 2
|
||||||
|
assert "Created" in info_messages[0]
|
||||||
|
assert "bootstrap" in info_messages[1]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_logmigr_callback_replaces_default_logging(
|
async def test_logmigr_callback_replaces_default_logging(
|
||||||
tmp_path, format_config, caplog
|
tmp_path, format_config, caplog
|
||||||
|
|||||||
Reference in New Issue
Block a user