Skip to content

Commit 9145384

Browse files
committed
add SQL Alchemy binds
1 parent e5ace17 commit 9145384

2 files changed

Lines changed: 61 additions & 17 deletions

File tree

api/__init__.py

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,16 @@ def create_app():
1515
bar_app = Flask(__name__)
1616
CORS(bar_app)
1717

18-
# Load configuration
19-
if os.environ.get("CI"):
20-
# Travis
21-
print("We are now loading configuration.")
22-
bar_app.config.from_pyfile(os.getcwd() + "/config/BAR_API.cfg", silent=True)
18+
# Detect execution environment.
19+
# Priority: BAR server > GitHub CI > local development
20+
is_bar = bool(os.environ.get("BAR"))
21+
is_ci = bool(os.environ.get("CI"))
2322

24-
elif os.environ.get("BAR"):
25-
# The BAR
23+
# Load configuration
24+
if is_bar:
25+
# --- BAR server ---
26+
# Uses MySQL databases via SQLALCHEMY_BINDS defined in the server config.
27+
# SQLite mirrors are never built in this environment.
2628
bar_app.config.from_pyfile(os.environ.get("BAR_API_PATH"), silent=True)
2729

2830
# Load environment variables on the BAR
@@ -32,21 +34,51 @@ def create_app():
3234
os.environ["PHENIX_VERSION"] = bar_app.config.get("PHENIX_VERSION")
3335
if bar_app.config.get("PATH"):
3436
os.environ["PATH"] = bar_app.config.get("PATH") + ":/usr/local/phenix-1.18.2-3874/build/bin"
37+
38+
# Auto-populate MySQL binds for all eFP databases using a single base URI.
39+
# Set MYSQL_EFP_BASE_URI = 'mysql://user:pass@host' in the BAR server config
40+
# to avoid manually listing every database in SQLALCHEMY_BINDS.
41+
# Only adds databases that are not already explicitly configured.
42+
mysql_efp_base = bar_app.config.get("MYSQL_EFP_BASE_URI")
43+
if mysql_efp_base:
44+
from api.models.efp_schemas import SIMPLE_EFP_DATABASE_SCHEMAS
45+
binds = bar_app.config.get("SQLALCHEMY_BINDS") or {}
46+
base = mysql_efp_base.rstrip("/")
47+
for db_name in SIMPLE_EFP_DATABASE_SCHEMAS:
48+
if db_name not in binds:
49+
binds[db_name] = f"{base}/{db_name}"
50+
bar_app.config["SQLALCHEMY_BINDS"] = binds
51+
52+
elif is_ci:
53+
# --- GitHub CI (Travis / GitHub Actions) ---
54+
# Loads the repo's committed config which sets TESTING=True and MySQL SQLALCHEMY_BINDS.
55+
# SQLite mirrors are then built from the SQL files in config/databases/ and override
56+
# the MySQL binds so tests run without a real MySQL instance.
57+
print("We are now loading configuration.")
58+
bar_app.config.from_pyfile(os.getcwd() + "/config/BAR_API.cfg", silent=True)
59+
3560
else:
36-
# The localhost
61+
# --- Local development ---
62+
# Loads the developer's personal config from ~/.config/BAR_API.cfg (if it exists).
63+
# If no SQLALCHEMY_BINDS are configured, falls back to pre-built SQLite mirrors
64+
# in config/databases/ or auto-builds them from SQL files.
3765
bar_app.config.from_pyfile(os.path.expanduser("~") + "/.config/BAR_API.cfg", silent=True)
3866

3967
repo_root = Path(__file__).resolve().parents[1]
4068
db_dir = repo_root / "config" / "databases"
41-
if db_dir.exists():
42-
is_test_run = (
43-
bar_app.config.get("TESTING")
44-
or "pytest" in os.sys.modules
45-
or os.environ.get("BAR_API_AUTO_SQLITE_MIRRORS") == "1"
69+
if db_dir.exists() and not is_bar:
70+
# On BAR, MySQL binds come from the server config — never build SQLite mirrors there.
71+
# For CI and local dev, determine whether to build SQLite mirrors.
72+
needs_sqlite_mirrors = (
73+
is_ci # always build on CI
74+
or bar_app.config.get("TESTING") # config requests test mode
75+
or "pytest" in os.sys.modules # running under pytest
76+
or os.environ.get("BAR_API_AUTO_SQLITE_MIRRORS") == "1" # explicit override
4677
)
4778

48-
# For tests/local dev, build sqlite mirrors in a temp directory (no repo db files needed).
49-
if is_test_run and not os.environ.get("BAR"):
79+
if needs_sqlite_mirrors:
80+
# Build SQLite mirrors in a temp directory from the SQL schema/seed files.
81+
# These override any MySQL SQLALCHEMY_BINDS so tests run without MySQL.
5082
from api.utils.sqlite_mirror_utils import build_sqlite_db
5183

5284
tmp_root = Path(tempfile.gettempdir()) / "bar_api_sqlite"
@@ -74,7 +106,8 @@ def create_app():
74106

75107
bar_app.config["SQLALCHEMY_BINDS"] = sqlite_binds
76108

77-
# If no binds were configured and we're not in test mode, fall back to local sqlite mirrors.
109+
# Local dev fallback: if no binds are configured yet, use pre-built SQLite files
110+
# from config/databases/ (populated by scripts/build_sqlite_mirrors.py).
78111
if not bar_app.config.get("SQLALCHEMY_BINDS"):
79112
binds = {}
80113
for db_path in db_dir.glob("*.db"):

api/services/efp_data.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,16 @@ def _iter_engine_candidates(database: str) -> Iterable[Tuple[str, Engine, bool]]
247247

248248
if db_path.exists():
249249
sqlite_engine = create_engine(f"sqlite:///{db_path}")
250+
# ensure a fast UPPER() expression index exists so case-insensitive
251+
# gene lookups use the index rather than a full table scan
252+
try:
253+
with sqlite_engine.begin() as _conn:
254+
_conn.execute(text(
255+
"CREATE INDEX IF NOT EXISTS ix_upper_probeset "
256+
"ON sample_data (UPPER(data_probeset_id))"
257+
))
258+
except Exception:
259+
pass # read-only db or schema mismatch — best-effort
250260
yield ("sqlite_mirror", sqlite_engine, True)
251261

252262
@staticmethod
@@ -419,7 +429,8 @@ def query_efp_database_dynamic(
419429
try:
420430
with Session(engine) as session:
421431
results = session.execute(query_sql, params).all()
422-
break
432+
if results: # only stop if we got actual rows; empty → try next candidate
433+
break
423434
except SQLAlchemyError as exc:
424435
last_error = f"{source_label} failed: {exc}"
425436
print(f"[warn] {last_error}")

0 commit comments

Comments
 (0)