49a9493ca0
Vorher: alembic/env.py importierte nur from app.models import * — das laedt im frischen Prozess nur die 48 CORE-Modelle. Contact und ~80 weitere Tabellen liegen physikalisch in Plugins (lazy __getattr__ feuert bei Wildcard-Import nie). Metadatensortierung scheiterte an contact_merge_history -> contacts (NoReferencedTableError, Astra-Repro); alembic check haette gegen ein unvollstaendiges Schema verglichen. Fix: deterministische Plugin-Model-Discovery in env.py — gleiches Muster wie tests/conftest.py: Registry discover_builtins, dann pro Plugin das models-Modul importieren (ImportError = kein models-Modul, bewusst uebersprungen). Side-effect-frei (nur Modell-Registrierung, kein DB-Zugriff). Beweis: frischer Prozess laedt jetzt 129 Tabellen, Sortierung OK (Vorher: 48 + NoReferencedTableError). Bekannt und separat offen: der contacts/contactpersons-FK-Zyklus (SAWarning, dokumentiert) und der entity_attachments.dms_file_id-FK auf die DMS-Tabelle (R3). Verifikation: Syntax OK, ruff clean.
85 lines
2.8 KiB
Python
85 lines
2.8 KiB
Python
"""Alembic migration environment."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
|
|
# F19 (Astra P1): deterministic full-model discovery for Alembic.
|
|
# `from app.models import *` only loads CORE models (48 tables in a fresh
|
|
# process). Contact and ~80 other tables physically live in plugins
|
|
# (e.g. app.plugins.builtins.contacts.models) — the lazy package
|
|
# __getattr__ never fires for wildcard imports. Without the plugin models
|
|
# the metadata sort fails (contact_merge_history → contacts FK) and
|
|
# `alembic check` compares against an incomplete schema.
|
|
import importlib
|
|
from logging.config import fileConfig
|
|
|
|
from sqlalchemy import pool
|
|
from sqlalchemy.engine import Connection
|
|
from sqlalchemy.ext.asyncio import async_engine_from_config
|
|
|
|
from alembic import context
|
|
from app.config import get_settings
|
|
from app.core.db import Base
|
|
from app.models import * # noqa: F401,F403
|
|
from app.plugins.registry import get_registry
|
|
|
|
_registry = get_registry()
|
|
_registry.discover_builtins()
|
|
for _plugin_name in _registry.list_discovered():
|
|
try:
|
|
importlib.import_module(f"app.plugins.builtins.{_plugin_name}.models")
|
|
except ImportError:
|
|
pass # plugin has no models module
|
|
|
|
config = context.config
|
|
if config.config_file_name is not None:
|
|
fileConfig(config.config_file_name)
|
|
|
|
target_metadata = Base.metadata
|
|
settings = get_settings()
|
|
# ⚠️ RLS Migration History: 21 Migrationen mit 8 Disable-Zyklen. Dies ist historisch bedingt
|
|
# und zeigt trial-and-error. Aktuelle RLS-Konfiguration ist stabil (113 Tabellen).
|
|
# Bei neuen RLS-Änderungen nur noch Migration-Runner nutzen.
|
|
# Use migration_database_url (crm_migration role, table owner) for Alembic
|
|
config.set_main_option("sqlalchemy.url", settings.migration_database_url or settings.database_url)
|
|
|
|
|
|
def run_migrations_offline() -> None:
|
|
url = config.get_main_option("sqlalchemy.url")
|
|
context.configure(
|
|
url=url,
|
|
target_metadata=target_metadata,
|
|
literal_binds=True,
|
|
dialect_opts={"paramstyle": "named"},
|
|
)
|
|
with context.begin_transaction():
|
|
context.run_migrations()
|
|
|
|
|
|
def do_run_migrations(connection: Connection) -> None:
|
|
context.configure(connection=connection, target_metadata=target_metadata)
|
|
with context.begin_transaction():
|
|
context.run_migrations()
|
|
|
|
|
|
async def run_async_migrations() -> None:
|
|
connectable = async_engine_from_config(
|
|
config.get_section(config.config_ini_section, {}),
|
|
prefix="sqlalchemy.",
|
|
poolclass=pool.NullPool,
|
|
)
|
|
async with connectable.connect() as connection:
|
|
await connection.run_sync(do_run_migrations)
|
|
await connectable.dispose()
|
|
|
|
|
|
def run_migrations_online() -> None:
|
|
asyncio.run(run_async_migrations())
|
|
|
|
|
|
if context.is_offline_mode():
|
|
run_migrations_offline()
|
|
else:
|
|
run_migrations_online()
|