├── README.md └── main.py /README.md: -------------------------------------------------------------------------------- 1 | # diagrams-as-code -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from logging.config import fileConfig 3 | from typing import Optional 4 | 5 | from flask import current_app 6 | from alembic import context 7 | from sqlalchemy.engine import Engine 8 | 9 | # Get Alembic configuration 10 | config = context.config 11 | 12 | # Configure logging if a config file is specified 13 | if config.config_file_name: 14 | fileConfig(config.config_file_name) 15 | 16 | logger = logging.getLogger("alembic.env") 17 | 18 | 19 | def get_engine() -> Optional[Engine]: 20 | """Returns the database engine.""" 21 | try: 22 | migrate_ext = current_app.extensions.get("migrate") 23 | if not migrate_ext or not hasattr(migrate_ext, "db"): 24 | logger.error("Flask-Migrate extension not found.") 25 | return None 26 | return migrate_ext.db.get_engine() 27 | except (AttributeError, KeyError, TypeError) as e: 28 | logger.error(f"Error retrieving the database engine: {e}") 29 | return None 30 | 31 | 32 | def get_engine_url() -> str: 33 | """Returns the database URL.""" 34 | engine = get_engine() 35 | if not engine: 36 | raise RuntimeError("Failed to retrieve the database engine.") 37 | 38 | try: 39 | return engine.url.render_as_string(hide_password=False).replace("%", "%%") 40 | except AttributeError: 41 | return str(engine.url).replace("%", "%%") 42 | 43 | 44 | # Set the database URL for Alembic 45 | config.set_main_option("sqlalchemy.url", get_engine_url()) 46 | 47 | # Retrieve the database object 48 | target_db = current_app.extensions["migrate"].db 49 | 50 | 51 | def get_metadata(): 52 | """Returns the database metadata for autogeneration.""" 53 | return getattr(target_db, "metadatas", {}).get(None, target_db.metadata) 54 | 55 | 56 | def run_migrations_offline(): 57 | """Runs migrations in offline mode.""" 58 | url = config.get_main_option("sqlalchemy.url") 59 | if not url: 60 | raise RuntimeError("Database URL is not set.") 61 | 62 | context.configure(url=url, target_metadata=get_metadata(), literal_binds=True) 63 | 64 | with context.begin_transaction(): 65 | context.run_migrations() 66 | 67 | 68 | def run_migrations_online(): 69 | """Runs migrations in online mode.""" 70 | 71 | def process_revision_directives(context, revision, directives): 72 | """Prevents empty migrations from being generated.""" 73 | cmd_opts = getattr(config, "cmd_opts", None) 74 | if cmd_opts and getattr(cmd_opts, "autogenerate", False): 75 | script = directives[0] 76 | if script.upgrade_ops.is_empty(): 77 | directives[:] = [] 78 | logger.info("No schema changes detected. Migration not created.") 79 | 80 | # Retrieve Alembic configuration arguments 81 | conf_args = getattr(current_app.extensions["migrate"], "configure_args", {}) 82 | conf_args.setdefault("process_revision_directives", process_revision_directives) 83 | 84 | # Get the database engine 85 | connectable = get_engine() 86 | if not connectable: 87 | raise RuntimeError("Failed to connect to the database.") 88 | 89 | with connectable.connect() as connection: 90 | context.configure(connection=connection, target_metadata=get_metadata(), **conf_args) 91 | 92 | with context.begin_transaction(): 93 | context.run_migrations() 94 | 95 | 96 | # Determine the migration mode 97 | if context.is_offline_mode(): 98 | run_migrations_offline() 99 | else: 100 | run_migrations_online() 101 | --------------------------------------------------------------------------------