11import os
2- import pathlib
32import logging
3+ import pathlib
44from alembic import command
55from alembic .config import Config
66from sqlalchemy import create_engine , inspect , text
1010
1111
1212def _mask_url (url : str ) -> str :
13- """Mask password in a SQLAlchemy URL for safe logging."""
1413 try :
15- if not url or '://' not in url :
16- return url
17- scheme , rest = url .split ('://' , 1 )
18- if '@' not in rest :
19- return url
20- creds , tail = rest .split ('@' , 1 )
21- if ':' not in creds :
22- return url
23- user , _ = creds .split (':' , 1 )
24- return f"{ scheme } ://{ user } :******@{ tail } "
14+ # Basic masking: replace password between ://user:pass@ with ******
15+ if '://' in url and '@' in url and ':' in url .split ('://' , 1 )[1 ]:
16+ scheme , rest = url .split ('://' , 1 )
17+ creds , tail = rest .split ('@' , 1 )
18+ if ':' in creds :
19+ user , _ = creds .split (':' , 1 )
20+ return f"{ scheme } ://{ user } :******@{ tail } "
2521 except Exception :
26- return url
22+ pass
23+ return url
2724
2825
2926def run_migrations_on_startup ():
30- """
31- Programmatic migration runner for BIOMERO.
32-
33- Controlled by env:
34- - BIOMERO_RUN_MIGRATIONS=1 to enable (default 1)
35- - SQLALCHEMY_URL for DB connection string
36- - BIOMERO_ALLOW_AUTO_STAMP=1 to adopt existing schema by stamping head
37- """
3827 if os .getenv ("BIOMERO_RUN_MIGRATIONS" , "1" ) != "1" :
3928 return
4029
41- logger = logging .getLogger (__name__ )
42-
4330 # Prefer the DB URL from EngineManager (engine already created).
4431 db_url = None
4532 try :
4633 from .database import EngineManager
4734 if getattr (EngineManager , "_engine" , None ):
48- # Stringify without exposing password
4935 db_url = str (EngineManager ._engine .url )
5036 except Exception :
5137 pass
52-
53- # Fallback to env var if needed
38+ # Fallback to env vars if tracker not available
5439 if not db_url :
5540 db_url = os .getenv ("SQLALCHEMY_URL" )
5641 if not db_url :
5742 raise RuntimeError (
58- "BIOMERO migrations: No DB URL. Ensure EngineManager is "
43+ "BIOMERO migrations: No DB URL found . Ensure EngineManager is "
5944 "initialized or set SQLALCHEMY_URL."
6045 )
6146
47+ logger = logging .getLogger (__name__ )
6248 logger .info (f"BIOMERO Alembic DB: { _mask_url (db_url )} " )
63-
64- engine = create_engine (db_url )
49+
50+ # Try to reuse existing engine if available to avoid connection issues
51+ engine = None
52+ try :
53+ from .database import EngineManager
54+ if getattr (EngineManager , "_engine" , None ):
55+ engine = EngineManager ._engine
56+ except Exception :
57+ pass
58+
59+ if engine is None :
60+ engine = create_engine (db_url )
6561
6662 cfg = Config ()
6763 cfg .set_main_option ("script_location" , MIGRATIONS_DIR )
@@ -70,9 +66,17 @@ def run_migrations_on_startup():
7066
7167 insp = inspect (engine )
7268 has_version_table = insp .has_table (VERSION_TABLE )
69+ # Allow auto-stamp if explicitly enabled OR if tables were just created
70+ # in this process (fresh install detected by Base.metadata.after_create).
7371 allow_stamp = os .getenv ("BIOMERO_ALLOW_AUTO_STAMP" , "0" ) == "1"
74- created_any_tables = os .getenv ("BIOMERO_CREATED_ANY_TABLES" , "0" ) == "1"
72+ try :
73+ from .database import CREATED_ANY_TABLES
74+ allow_stamp = allow_stamp or CREATED_ANY_TABLES
75+ except Exception :
76+ pass
7577
78+ # Postgres advisory lock to prevent concurrent migrations
79+ # from multiple replicas
7680 is_pg = engine .dialect .name == "postgresql"
7781
7882 with engine .begin () as conn :
@@ -84,23 +88,16 @@ def run_migrations_on_startup():
8488 )
8589 )
8690 try :
87- # Auto-stamp scenarios:
88- # 1) Fresh install: our process just created tables ->
89- # stamp unconditionally.
90- # 2) Adoption: admin allowed stamping and existing known tables
91- # are present.
92- if not has_version_table :
93- if created_any_tables :
94- command .stamp (cfg , "head" )
95- elif allow_stamp :
96- known_tables = {
97- "biomero_job_view" ,
98- "biomero_job_progress_view" ,
99- "biomero_workflow_progress_view" ,
100- "biomero_task_execution" ,
101- }
102- if any (insp .has_table (t ) for t in known_tables ):
103- command .stamp (cfg , "head" )
91+ if allow_stamp and not has_version_table :
92+ # Check if any BIOMERO table already exists (reliable table name)
93+ known_tables = {
94+ "biomero_job_view" ,
95+ "biomero_job_progress_view" ,
96+ "biomero_workflow_progress_view" ,
97+ "biomero_task_execution"
98+ }
99+ if any (insp .has_table (t ) for t in known_tables ):
100+ command .stamp (cfg , "head" ) # baseline existing DB
104101 command .upgrade (cfg , "head" )
105102 finally :
106103 if is_pg :
0 commit comments