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