Files
natureinpots_community/migrations/env.py
2025-07-09 01:05:45 -05:00

118 lines
4.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# File: migrations/env.py
import os
import sys
import warnings
import json
import importlib
from logging.config import fileConfig
from sqlalchemy import create_engine, pool
from alembic import context
# ─── Suppress harmless warnings about FK cycles ───────────────────────────────
warnings.filterwarnings(
"ignore",
r"Cannot correctly sort tables; there are unresolvable cycles between tables.*"
)
# ─── Ensure project root is on sys.path ──────────────────────────────────────
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
sys.path.insert(0, project_root)
# ─── Load environment vars so DB URL is available ────────────────────────────
from dotenv import load_dotenv, find_dotenv
dotenv = find_dotenv()
if dotenv:
load_dotenv(dotenv, override=True)
# ─── Dynamically import every plugins models *before* capturing metadata ────
plugins_dir = os.path.join(project_root, "plugins")
for plugin in sorted(os.listdir(plugins_dir)):
manifest = os.path.join(plugins_dir, plugin, "plugin.json")
if not os.path.isfile(manifest):
continue
try:
meta = json.load(open(manifest))
except Exception:
continue
for model_mod in meta.get("models", []):
try:
importlib.import_module(model_mod)
except ImportError:
pass
for sp in meta.get("subplugins", []):
for model_mod in sp.get("models", []):
try:
importlib.import_module(model_mod)
except ImportError:
pass
# ─── Alembic config & logging ────────────────────────────────────────────────
config = context.config
fileConfig(config.config_file_name)
# ─── Now import the applications metadata ───────────────────────────────────
from app import db
target_metadata = db.metadata
# ─── Hook to skip unwanted objects (never drop tables) ───────────────────────
def include_object(obj, name, type_, reflected, compare_to):
# skip tables present in DB but not in models
if type_ == "table" and reflected and compare_to is None:
return False
# skip constraints & indexes
if type_ in ("foreign_key_constraint", "unique_constraint", "index"):
return False
return True
# ─── Helper to build the DB URL ───────────────────────────────────────────────
def get_url():
url = config.get_main_option("sqlalchemy.url")
if url:
return url.strip()
url = os.environ.get("DATABASE_URL")
if url:
return url
u = os.environ.get("MYSQL_USER")
p = os.environ.get("MYSQL_PASSWORD")
h = os.environ.get("MYSQL_HOST", "db")
pt= os.environ.get("MYSQL_PORT", "3306")
dbn= os.environ.get("MYSQL_DATABASE")
if u and p and dbn:
return f"mysql+pymysql://{u}:{p}@{h}:{pt}/{dbn}"
raise RuntimeError("No DB URL configured")
# ─── Offline migrations ──────────────────────────────────────────────────────
def run_migrations_offline():
context.configure(
url=get_url(),
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
compare_type=True,
compare_server_default=True,
include_object=include_object,
)
with context.begin_transaction():
context.run_migrations()
# ─── Online migrations ───────────────────────────────────────────────────────
def run_migrations_online():
engine = create_engine(get_url(), poolclass=pool.NullPool)
with engine.connect() as conn:
context.configure(
connection=conn,
target_metadata=target_metadata,
compare_type=True,
compare_server_default=True,
include_object=include_object,
)
with context.begin_transaction():
context.run_migrations()
# ─── Entrypoint ─────────────────────────────────────────────────────────────
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()