Abrar144 commited on
Commit
f424c28
Β·
1 Parent(s): 54aaba4

Lazy st.secrets, prefer env vars; switch to SQLAlchemy engine for pandas 2.2 compatibility

Browse files
Files changed (2) hide show
  1. tools/tools.py +37 -15
  2. utils/config.py +10 -5
tools/tools.py CHANGED
@@ -10,9 +10,37 @@ Two layers:
10
  import pandas as pd
11
  import psycopg2
12
  import re
 
 
13
  from utils.config import CLEAN_DB_URI, METRIC_LIBRARY
14
 
15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  # ════════════════════════════════════════════════════════════════
17
  # SECTION 1: DETERMINISTIC SQL BUILD ENGINE
18
  # ════════════════════════════════════════════════════════════════
@@ -418,9 +446,7 @@ def execute_metric_query(metric_name, filters=None, group_by=None):
418
  return None, f"Could not build SQL for '{metric_name}'"
419
 
420
  try:
421
- conn = psycopg2.connect(CLEAN_DB_URI)
422
- df = pd.read_sql(sql, conn)
423
- conn.close()
424
  return df, sql
425
  except Exception as e:
426
  return None, f"SQL Error: {str(e)}\nSQL: {sql}"
@@ -468,9 +494,7 @@ def execute_custom_sql(sql_query):
468
  return None, "BLOCKED: Both fact tables without CTEs. Use execute_metric_query('revpar', ...) instead."
469
 
470
  try:
471
- conn = psycopg2.connect(CLEAN_DB_URI)
472
- df = pd.read_sql(sql_query, conn)
473
- conn.close()
474
  return df, None
475
  except Exception as e:
476
  return None, f"SQL Error: {str(e)}"
@@ -479,18 +503,16 @@ def execute_custom_sql(sql_query):
479
  def get_database_context():
480
  """Direct Python function to get DB context."""
481
  try:
482
- conn = psycopg2.connect(CLEAN_DB_URI)
483
 
484
  weeks = pd.read_sql(
485
- "SELECT week_no, COUNT(*) as days FROM dim_date GROUP BY week_no ORDER BY week_no::int", conn
486
  )
487
- cities = pd.read_sql("SELECT DISTINCT city FROM dim_hotels ORDER BY city", conn)['city'].tolist()
488
- properties = pd.read_sql("SELECT property_id, property_name, category, city FROM dim_hotels ORDER BY property_name", conn)
489
- platforms = pd.read_sql("SELECT DISTINCT booking_platform FROM fact_bookings ORDER BY booking_platform", conn)['booking_platform'].tolist()
490
- rooms = pd.read_sql("SELECT room_id, room_class FROM dim_rooms ORDER BY room_id", conn)
491
- date_range = pd.read_sql("SELECT MIN(date) as start_date, MAX(date) as end_date FROM dim_date", conn).iloc[0]
492
-
493
- conn.close()
494
 
495
  latest_week = weeks.iloc[-1]['week_no']
496
  full_weeks = weeks[weeks['days'] >= 7]
 
10
  import pandas as pd
11
  import psycopg2
12
  import re
13
+ from sqlalchemy import create_engine
14
+ from sqlalchemy.engine import Engine
15
  from utils.config import CLEAN_DB_URI, METRIC_LIBRARY
16
 
17
 
18
+ # ════════════════════════════════════════════════════════════════
19
+ # SHARED SQLAlchemy ENGINE (pandas 2.2+ requires this; raw psycopg2
20
+ # connections raise warnings/errors in pd.read_sql).
21
+ # ════════════════════════════════════════════════════════════════
22
+
23
+ _engine: Engine | None = None
24
+
25
+
26
+ def _get_engine() -> Engine:
27
+ """Lazily build a single SQLAlchemy engine for the lifetime of the process."""
28
+ global _engine
29
+ if _engine is None:
30
+ if not CLEAN_DB_URI:
31
+ raise RuntimeError(
32
+ "CLEAN_SUPABASE_DB_URI is not set. "
33
+ "Add it as a secret in HF Space settings or in .env locally."
34
+ )
35
+ uri = CLEAN_DB_URI
36
+ if uri.startswith("postgres://"):
37
+ uri = uri.replace("postgres://", "postgresql+psycopg2://", 1)
38
+ elif uri.startswith("postgresql://"):
39
+ uri = uri.replace("postgresql://", "postgresql+psycopg2://", 1)
40
+ _engine = create_engine(uri, pool_pre_ping=True, pool_size=2, max_overflow=3)
41
+ return _engine
42
+
43
+
44
  # ════════════════════════════════════════════════════════════════
45
  # SECTION 1: DETERMINISTIC SQL BUILD ENGINE
46
  # ════════════════════════════════════════════════════════════════
 
446
  return None, f"Could not build SQL for '{metric_name}'"
447
 
448
  try:
449
+ df = pd.read_sql(sql, _get_engine())
 
 
450
  return df, sql
451
  except Exception as e:
452
  return None, f"SQL Error: {str(e)}\nSQL: {sql}"
 
494
  return None, "BLOCKED: Both fact tables without CTEs. Use execute_metric_query('revpar', ...) instead."
495
 
496
  try:
497
+ df = pd.read_sql(sql_query, _get_engine())
 
 
498
  return df, None
499
  except Exception as e:
500
  return None, f"SQL Error: {str(e)}"
 
503
  def get_database_context():
504
  """Direct Python function to get DB context."""
505
  try:
506
+ engine = _get_engine()
507
 
508
  weeks = pd.read_sql(
509
+ "SELECT week_no, COUNT(*) as days FROM dim_date GROUP BY week_no ORDER BY week_no::int", engine
510
  )
511
+ cities = pd.read_sql("SELECT DISTINCT city FROM dim_hotels ORDER BY city", engine)['city'].tolist()
512
+ properties = pd.read_sql("SELECT property_id, property_name, category, city FROM dim_hotels ORDER BY property_name", engine)
513
+ platforms = pd.read_sql("SELECT DISTINCT booking_platform FROM fact_bookings ORDER BY booking_platform", engine)['booking_platform'].tolist()
514
+ rooms = pd.read_sql("SELECT room_id, room_class FROM dim_rooms ORDER BY room_id", engine)
515
+ date_range = pd.read_sql("SELECT MIN(date) as start_date, MAX(date) as end_date FROM dim_date", engine).iloc[0]
 
 
516
 
517
  latest_week = weeks.iloc[-1]['week_no']
518
  full_weeks = weeks[weeks['days'] >= 7]
utils/config.py CHANGED
@@ -25,17 +25,22 @@ def _load_env():
25
 
26
  _load_env()
27
 
28
- # Try Streamlit secrets first (for cloud deployment),
29
- # then fall back to env vars (for local development)
 
 
30
  def _get_secret(key):
31
- """Get secret from Streamlit secrets (cloud) or env vars (local)."""
 
 
 
32
  try:
33
- import streamlit as st
34
  if hasattr(st, "secrets") and key in st.secrets:
35
  return st.secrets[key]
36
  except Exception:
37
  pass
38
- return os.getenv(key)
39
 
40
  # ════════════════════════════════════════════════
41
  # CONNECTION & LLM CONFIG
 
25
 
26
  _load_env()
27
 
28
+ # Try environment variables FIRST (works on HF Spaces, local .env, and Codespaces),
29
+ # then fall back to Streamlit secrets (Streamlit Cloud only).
30
+ # Reading st.secrets at import time can interfere with set_page_config on multipage apps,
31
+ # so it is only consulted when env vars are unset.
32
  def _get_secret(key):
33
+ """Get secret from env vars first; fall back to Streamlit secrets if available."""
34
+ val = os.getenv(key)
35
+ if val:
36
+ return val
37
  try:
38
+ import streamlit as st # noqa: WPS433
39
  if hasattr(st, "secrets") and key in st.secrets:
40
  return st.secrets[key]
41
  except Exception:
42
  pass
43
+ return None
44
 
45
  # ════════════════════════════════════════════════
46
  # CONNECTION & LLM CONFIG