Migrations cleanup by using a MigrationCtx object for meta.
This commit is contained in:
+11
-3
@@ -19,7 +19,12 @@ import msgspec
|
||||
|
||||
from paskia.db.filelock import LockedFile
|
||||
from paskia.db.logging import log_change
|
||||
from paskia.db.migrations import DBVER, apply_all_migrations, apply_migrations_readonly
|
||||
from paskia.db.migrations import (
|
||||
DBVER,
|
||||
MigrationCtx,
|
||||
apply_all_migrations,
|
||||
apply_migrations_readonly,
|
||||
)
|
||||
from paskia.db.structs import DB, Config, SessionContext
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
@@ -62,7 +67,7 @@ def load_readonly(db_path: str, *, rp_id: str = "localhost") -> DB:
|
||||
return DB(config=Config(rp_id=rp_id))
|
||||
|
||||
# Apply migrations in-memory (no persistence)
|
||||
apply_migrations_readonly(data_dict, version, rp_id=rp_id)
|
||||
apply_migrations_readonly(data_dict, version, MigrationCtx(rp_id=rp_id))
|
||||
|
||||
# Decode to msgspec struct
|
||||
db = msgspec.json.decode(msgspec.json.encode(data_dict), type=DB)
|
||||
@@ -176,7 +181,10 @@ class JsonlStore:
|
||||
|
||||
# Apply schema migrations one at a time
|
||||
await apply_all_migrations(
|
||||
data_dict, self._current_version, persist_migration, rp_id=rp_id
|
||||
data_dict,
|
||||
self._current_version,
|
||||
persist_migration,
|
||||
MigrationCtx(rp_id=rp_id),
|
||||
)
|
||||
|
||||
# Decode to msgspec struct
|
||||
|
||||
+17
-11
@@ -8,28 +8,36 @@ Each migration should be idempotent and only run when needed.
|
||||
import base64
|
||||
from collections.abc import Awaitable, Callable
|
||||
|
||||
import msgspec
|
||||
|
||||
from paskia.util.crypto import secret_key
|
||||
|
||||
|
||||
def migrate_v1(d: dict, **kwargs) -> None:
|
||||
class MigrationCtx(msgspec.Struct):
|
||||
"""Context passed to each migration function."""
|
||||
|
||||
rp_id: str
|
||||
|
||||
|
||||
def migrate_v1(d: dict, ctx: MigrationCtx) -> None:
|
||||
"""Remove Org.created_at fields."""
|
||||
for org_data in d["orgs"].values():
|
||||
org_data.pop("created_at", None)
|
||||
|
||||
|
||||
def migrate_v2(d: dict, *, rp_id: str = "localhost") -> None:
|
||||
def migrate_v2(d: dict, ctx: MigrationCtx) -> None:
|
||||
"""Add config field if missing."""
|
||||
if "config" not in d:
|
||||
d["config"] = {"rp_id": rp_id}
|
||||
d["config"] = {"rp_id": ctx.rp_id}
|
||||
|
||||
|
||||
def migrate_v3(d: dict, **kwargs) -> None:
|
||||
def migrate_v3(d: dict, ctx: MigrationCtx) -> None:
|
||||
"""Ensure all users have visits field."""
|
||||
for user_data in d["users"].values():
|
||||
user_data.setdefault("visits", 0)
|
||||
|
||||
|
||||
def migrate_v4(d: dict, **kwargs) -> None:
|
||||
def migrate_v4(d: dict, ctx: MigrationCtx) -> None:
|
||||
"""OpenID Connect support and hardened session keys."""
|
||||
# Session keys changed to hashes, drop old sessions
|
||||
d["sessions"] = {}
|
||||
@@ -48,15 +56,14 @@ DBVER = len(migrations) # Used by bootstrap to set initial version
|
||||
def apply_migrations_readonly(
|
||||
data_dict: dict,
|
||||
current_version: int,
|
||||
*,
|
||||
rp_id: str = "localhost",
|
||||
ctx: MigrationCtx,
|
||||
) -> int:
|
||||
"""Apply migration functions in-place without persistence.
|
||||
|
||||
Returns the new version after all migrations.
|
||||
"""
|
||||
while current_version < DBVER:
|
||||
migrations[current_version](data_dict, rp_id=rp_id)
|
||||
migrations[current_version](data_dict, ctx)
|
||||
current_version += 1
|
||||
return current_version
|
||||
|
||||
@@ -65,10 +72,9 @@ async def apply_all_migrations(
|
||||
data_dict: dict,
|
||||
current_version: int,
|
||||
persist: Callable[[str, int, dict], Awaitable[None]],
|
||||
*,
|
||||
rp_id: str = "localhost",
|
||||
ctx: MigrationCtx,
|
||||
) -> None:
|
||||
while current_version < DBVER:
|
||||
migrations[current_version](data_dict, rp_id=rp_id)
|
||||
migrations[current_version](data_dict, ctx)
|
||||
current_version += 1
|
||||
await persist(f"migrate:v{current_version}", current_version, data_dict)
|
||||
|
||||
Reference in New Issue
Block a user