Release v0.1.6

This commit is contained in:
2026-07-07 16:00:38 +02:00
parent a2053518d1
commit 150b720f12
149 changed files with 14311 additions and 8005 deletions

View File

@@ -10,8 +10,11 @@ from alembic.runtime.migration import MigrationContext
from sqlalchemy import create_engine, inspect, text
from govoplan_core.core.migrations import MigrationMetadataPlan, migration_metadata_plan
from govoplan_core.core.module_management import load_startup_enabled_modules, startup_candidate_module_ids
from govoplan_core.db.session import configure_database
from govoplan_core.server.default_config import get_server_config
from govoplan_core.server.registry import build_platform_registry
from govoplan_core.server.config import ManifestFactory
from govoplan_core.server.registry import available_module_manifests, build_platform_registry
from govoplan_core.settings import settings
# Historic development databases could be created partly through Alembic and
@@ -133,11 +136,32 @@ class MigrationResult:
current_revision: str | None
def registered_module_migration_plan() -> MigrationMetadataPlan:
def registered_module_migration_plan(
database_url: str | None = None,
*,
enabled_modules: tuple[str, ...] | list[str] | None = None,
manifest_factories: tuple[ManifestFactory, ...] = (),
) -> MigrationMetadataPlan:
server_config = get_server_config()
active_database_url = database_url or settings.database_url
if active_database_url:
configure_database(active_database_url)
active_manifest_factories = manifest_factories or tuple(server_config.manifest_factories)
raw_enabled_modules = tuple(enabled_modules) if enabled_modules is not None else load_startup_enabled_modules(server_config.enabled_modules)
candidate_modules = startup_candidate_module_ids(server_config.enabled_modules, raw_enabled_modules)
available_modules = available_module_manifests(
active_manifest_factories,
enabled_modules=candidate_modules,
ignore_load_errors=True,
)
active_enabled_modules = (
tuple(enabled_modules)
if enabled_modules is not None
else load_startup_enabled_modules(server_config.enabled_modules, available=available_modules)
)
registry = build_platform_registry(
server_config.enabled_modules,
manifest_factories=server_config.manifest_factories,
active_enabled_modules,
manifest_factories=active_manifest_factories,
)
return migration_metadata_plan(registry)
@@ -146,18 +170,32 @@ def _repo_root() -> Path:
return Path(__file__).resolve().parents[3]
def alembic_config(*, database_url: str | None = None) -> Config:
def alembic_config(
*,
database_url: str | None = None,
enabled_modules: tuple[str, ...] | list[str] | None = None,
manifest_factories: tuple[ManifestFactory, ...] = (),
) -> Config:
root = _repo_root()
config = Config(str(root / "alembic.ini"))
config.set_main_option("script_location", str(root / "alembic"))
plan = registered_module_migration_plan()
active_database_url = database_url or settings.database_url
plan = registered_module_migration_plan(
active_database_url,
enabled_modules=enabled_modules,
manifest_factories=manifest_factories,
)
version_locations = [str(root / "alembic" / "versions")]
version_locations.extend(location for location in plan.script_locations if location)
config.set_main_option("version_locations", os.pathsep.join(dict.fromkeys(version_locations)))
config.set_main_option("version_path_separator", "os")
config.attributes["database_url"] = database_url or settings.database_url
config.attributes["database_url"] = active_database_url
if enabled_modules is not None:
config.attributes["enabled_modules"] = tuple(enabled_modules)
if manifest_factories:
config.attributes["manifest_factories"] = tuple(manifest_factories)
return config
@@ -166,7 +204,12 @@ def database_revision(database_url: str | None = None) -> str | None:
engine = create_engine(url)
try:
with engine.connect() as connection:
return MigrationContext.configure(connection).get_current_revision()
heads = MigrationContext.configure(connection).get_current_heads()
if not heads:
return None
if len(heads) == 1:
return heads[0]
return ",".join(sorted(heads))
finally:
engine.dispose()
@@ -227,7 +270,9 @@ def reconcile_legacy_create_all_schema(database_url: str | None = None) -> str |
engine = create_engine(url)
try:
with engine.connect() as connection:
current = MigrationContext.configure(connection).get_current_revision()
heads = MigrationContext.configure(connection).get_current_heads()
current = heads[0] if len(heads) == 1 else None
has_no_revision = len(heads) == 0
schema = inspect(connection)
tables = set(schema.get_table_names())
@@ -259,10 +304,10 @@ def reconcile_legacy_create_all_schema(database_url: str | None = None) -> str |
# run normally.
_backfill_user_lock_state_for_create_all_schema(url)
target = REVISION_HIERARCHICAL_SETTINGS
elif current is None and has_create_all_hierarchical_schema:
elif has_no_revision and has_create_all_hierarchical_schema:
_backfill_user_lock_state_for_create_all_schema(url)
target = REVISION_HIERARCHICAL_SETTINGS
elif current is None and has_file_storage and has_file_folders:
elif has_no_revision and has_file_storage and has_file_folders:
# This is the other create_all-only development shape. The strict
# column checks above ensure that we only stamp a complete known schema.
target = REVISION_FILE_FOLDERS
@@ -278,11 +323,20 @@ def migrate_database(
*,
database_url: str | None = None,
reconcile_legacy_schema: bool = True,
enabled_modules: tuple[str, ...] | list[str] | None = None,
manifest_factories: tuple[ManifestFactory, ...] = (),
) -> MigrationResult:
url = database_url or settings.database_url
previous = database_revision(url)
reconciled = reconcile_legacy_create_all_schema(url) if reconcile_legacy_schema else None
command.upgrade(alembic_config(database_url=url), "head")
command.upgrade(
alembic_config(
database_url=url,
enabled_modules=enabled_modules,
manifest_factories=manifest_factories,
),
"heads",
)
current = database_revision(url)
return MigrationResult(
previous_revision=previous,