env.py 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. """
  2. alembic/env.py
  3. ──────────────
  4. Alembic runtime environment for PMDI.
  5. · Reads DATABASE_URL from the environment (same source as the FastAPI app)
  6. · Imports all ORM models so Alembic can perform autogenerate comparisons
  7. · Supports both offline (--sql) and online migration execution
  8. """
  9. from __future__ import annotations
  10. import os
  11. from logging.config import fileConfig
  12. from alembic import context
  13. from sqlalchemy import engine_from_config, pool
  14. # Import the shared declarative Base AND all models so Alembic sees every table
  15. from app.database import Base # noqa: F401
  16. from app.models import ( # noqa: F401
  17. AdminUser, Client, EmailTemplate, Invoice, NotificationLog,
  18. )
  19. # ── Alembic config object ──────────────────────────────────────────────────
  20. config = context.config
  21. # Set up logging from alembic.ini
  22. if config.config_file_name is not None:
  23. fileConfig(config.config_file_name)
  24. # Override sqlalchemy.url from the environment so credentials are never
  25. # stored in alembic.ini or any config file.
  26. database_url = os.environ.get("DATABASE_URL")
  27. if database_url:
  28. config.set_main_option("sqlalchemy.url", database_url)
  29. target_metadata = Base.metadata
  30. # ── Offline mode ───────────────────────────────────────────────────────────
  31. def run_migrations_offline() -> None:
  32. """Generate SQL script without a live database connection."""
  33. url = config.get_main_option("sqlalchemy.url")
  34. context.configure(
  35. url=url,
  36. target_metadata=target_metadata,
  37. literal_binds=True,
  38. dialect_opts={"paramstyle": "named"},
  39. compare_type=True,
  40. compare_server_default=True,
  41. )
  42. with context.begin_transaction():
  43. context.run_migrations()
  44. # ── Online mode ────────────────────────────────────────────────────────────
  45. def run_migrations_online() -> None:
  46. """Run migrations against a live database connection."""
  47. connectable = engine_from_config(
  48. config.get_section(config.config_ini_section, {}),
  49. prefix="sqlalchemy.",
  50. poolclass=pool.NullPool,
  51. )
  52. with connectable.connect() as connection:
  53. context.configure(
  54. connection=connection,
  55. target_metadata=target_metadata,
  56. compare_type=True,
  57. compare_server_default=True,
  58. )
  59. with context.begin_transaction():
  60. context.run_migrations()
  61. if context.is_offline_mode():
  62. run_migrations_offline()
  63. else:
  64. run_migrations_online()