"""Migrations-Laufzeit. Jedes Plugin (und der Kern selbst) liefert geordnete Migrationen als Liste von Migration(version, up). Der Stand wird pro Plugin in der Tabelle `schema_migrations` festgehalten. Die `up`-Funktionen bekommen eine SQLAlchemy-Connection und können bei Bedarf pro Dialekt verzweigen — so bleiben die Migrationen SQLite- und Postgres-tauglich. """ from __future__ import annotations from collections.abc import Callable from sqlalchemy import Column, Connection, DateTime, MetaData, Table, String, func, select from redaktionskern.contracts import Migration _metadata = MetaData() schema_migrations = Table( "schema_migrations", _metadata, Column("plugin", String(100), primary_key=True), Column("version", String(200), primary_key=True), Column("applied_at", DateTime, nullable=False, server_default=func.now()), ) def run_migrations( conn: Connection, plugin: str, migrations: list[Migration] ) -> list[str]: """Führt noch nicht angewendete Migrationen aus und gibt deren Versionen zurück. Der Aufrufer ist für die Transaktion verantwortlich (engine.begin()). """ schema_migrations.create(conn, checkfirst=True) angewendet = { version for (version,) in conn.execute( select(schema_migrations.c.version).where( schema_migrations.c.plugin == plugin ) ) } neu_angewendet: list[str] = [] for migration in sorted(migrations, key=lambda m: m.version): if migration.version in angewendet: continue migration.up(conn) conn.execute( schema_migrations.insert().values(plugin=plugin, version=migration.version) ) neu_angewendet.append(migration.version) return neu_angewendet