Files
fast-next-template/backend/app/alembic/env.py
Felipe Cardoso 2bbe925cef Clean up Alembic migrations
- Removed outdated and redundant Alembic migration files to streamline the migration directory. This improves maintainability and eliminates duplicate or unused scripts.
2025-11-27 09:12:30 +01:00

161 lines
4.9 KiB
Python

import sys
from logging.config import fileConfig
from pathlib import Path
from alembic import context
from sqlalchemy import create_engine, engine_from_config, pool, text
from sqlalchemy.engine.url import make_url
from sqlalchemy.exc import OperationalError
# Get the path to the app directory (parent of 'alembic')
app_dir = Path(__file__).resolve().parent.parent
# Add the app directory to Python path
sys.path.append(str(app_dir.parent))
# Import Core modules
from app.core.config import settings
# Import all models to ensure they're registered with SQLAlchemy
from app.models import *
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
def include_object(object, name, type_, reflected, compare_to):
"""
Filter objects for autogenerate.
Skip comparing functional indexes (like LOWER(column)) and partial indexes
(with WHERE clauses) as Alembic cannot reliably detect these from models.
These should be managed manually via dedicated performance migrations.
Convention: Any index starting with "ix_perf_" is automatically excluded.
This allows adding new performance indexes without updating this file.
"""
if type_ == "index" and name:
# Convention-based: any index prefixed with ix_perf_ is manual
if name.startswith("ix_perf_"):
return False
return True
# Interpret the config file for Python logging.
# This line sets up loggers basically.
if config.config_file_name is not None:
fileConfig(config.config_file_name)
# add your model's MetaData object here
# for 'autogenerate' support
target_metadata = Base.metadata
# Override the SQLAlchemy URL with the one from settings
config.set_main_option("sqlalchemy.url", settings.database_url)
def ensure_database_exists(db_url: str) -> None:
"""
Ensure the target PostgreSQL database exists.
If connection to the target DB fails because it doesn't exist, connect to the
default 'postgres' database and create it. Safe to call multiple times.
"""
try:
# First, try connecting to the target database
test_engine = create_engine(db_url, poolclass=pool.NullPool)
with test_engine.connect() as conn:
conn.execute(text("SELECT 1"))
test_engine.dispose()
return
except OperationalError:
# Likely the database does not exist; proceed to create it
pass
url = make_url(db_url)
# Only handle PostgreSQL here
if url.get_backend_name() != "postgresql":
return
target_db = url.database
if not target_db:
return
# Build admin URL pointing to the default 'postgres' database
admin_url = url.set(database="postgres")
# CREATE DATABASE cannot run inside a transaction
admin_engine = create_engine(
str(admin_url), isolation_level="AUTOCOMMIT", poolclass=pool.NullPool
)
try:
with admin_engine.connect() as conn:
exists = conn.execute(
text("SELECT 1 FROM pg_database WHERE datname = :dbname"),
{"dbname": target_db},
).scalar()
if not exists:
# Quote the database name safely
dbname_quoted = '"' + target_db.replace('"', '""') + '"'
conn.execute(text(f"CREATE DATABASE {dbname_quoted}"))
finally:
admin_engine.dispose()
def run_migrations_offline() -> None:
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
compare_type=True,
include_object=include_object,
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online() -> None:
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
# Ensure the target database exists (handles first-run cases)
ensure_database_exists(settings.database_url)
connectable = engine_from_config(
config.get_section(config.config_ini_section, {}),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata,
compare_type=True,
include_object=include_object,
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()