Melika Kheirieh commited on
Commit
4c2cf14
·
1 Parent(s): e51bd0f

fix(sqlite): unify adapter path resolution and load from env for stable pipeline execution

Browse files
adapters/db/sqlite_adapter.py CHANGED
@@ -2,6 +2,7 @@ import sqlite3
2
  import logging
3
  from typing import List, Tuple, Any
4
  from adapters.db.base import DBAdapter
 
5
 
6
  log = logging.getLogger(__name__)
7
 
@@ -11,13 +12,15 @@ class SQLiteAdapter(DBAdapter):
11
  dialect = "sqlite"
12
 
13
  def __init__(self, path: str):
14
- self.path = path
 
15
  log.info("SQLiteAdapter initialized with DB path: %s", self.path)
16
 
17
  def preview_schema(self, limit_per_table: int = 0) -> str:
18
- with sqlite3.connect(self.path, uri=True) as conn:
 
19
  cur = conn.cursor()
20
- cur.execute("PRAGMA foreign_keys = ON")
21
  tables = [t[0] for t in cur.fetchall()]
22
  lines = []
23
  for t in tables:
@@ -28,9 +31,11 @@ class SQLiteAdapter(DBAdapter):
28
 
29
  def execute(self, sql: str) -> Tuple[List[Tuple[Any, ...]], List[str]]:
30
  # enforce read-only connection
31
- uri = f"file:{self.path}?mode=ro&uri=true"
32
  log.info("SQLiteAdapter opening read-only connection to: %s", uri)
33
- with sqlite3.connect(uri, uri=True, timeout=3) as conn:
 
 
34
  cur = conn.cursor()
35
  log.debug("Executing SQL: %s", sql.strip().replace("\n", " "))
36
  cur.execute(sql)
 
2
  import logging
3
  from typing import List, Tuple, Any
4
  from adapters.db.base import DBAdapter
5
+ from pathlib import Path
6
 
7
  log = logging.getLogger(__name__)
8
 
 
12
  dialect = "sqlite"
13
 
14
  def __init__(self, path: str):
15
+ # resolve absolute path for safety
16
+ self.path = Path(path).resolve()
17
  log.info("SQLiteAdapter initialized with DB path: %s", self.path)
18
 
19
  def preview_schema(self, limit_per_table: int = 0) -> str:
20
+ uri = self.path.as_uri()
21
+ with sqlite3.connect(f"{uri}?mode=ro", uri=True) as conn:
22
  cur = conn.cursor()
23
+ cur.execute("SELECT name FROM sqlite_master WHERE type='table';")
24
  tables = [t[0] for t in cur.fetchall()]
25
  lines = []
26
  for t in tables:
 
31
 
32
  def execute(self, sql: str) -> Tuple[List[Tuple[Any, ...]], List[str]]:
33
  # enforce read-only connection
34
+ uri = self.path.as_uri()
35
  log.info("SQLiteAdapter opening read-only connection to: %s", uri)
36
+ if not self.path.exists():
37
+ raise FileNotFoundError(f"SQLite DB does not exist: {self.path}")
38
+ with sqlite3.connect(f"{uri}?mode=ro", uri=True, timeout=3) as conn:
39
  cur = conn.cursor()
40
  log.debug("Executing SQL: %s", sql.strip().replace("\n", " "))
41
  cur.execute(sql)
app/main.py CHANGED
@@ -87,7 +87,9 @@ def readyz() -> str:
87
  else:
88
  from adapters.db.sqlite_adapter import SQLiteAdapter
89
 
90
- sq = SQLiteAdapter(os.getenv("SQLITE_DB_PATH", "data/chinook.db"))
 
 
91
  ping_fn = getattr(sq, "ping", None)
92
  if callable(ping_fn):
93
  ping_fn()
 
87
  else:
88
  from adapters.db.sqlite_adapter import SQLiteAdapter
89
 
90
+ sq = SQLiteAdapter(
91
+ os.getenv("DEFAULT_SQLITE_PATH", "data/Chinook_Sqlite.sqlite")
92
+ )
93
  ping_fn = getattr(sq, "ping", None)
94
  if callable(ping_fn):
95
  ping_fn()
app/routers/dev.py CHANGED
@@ -8,6 +8,11 @@ from adapters.db.sqlite_adapter import SQLiteAdapter
8
 
9
  from dataclasses import is_dataclass, asdict
10
  from typing import Any
 
 
 
 
 
11
 
12
 
13
  def _is_dataclass_instance(x: Any) -> bool:
@@ -53,10 +58,20 @@ def dev_safety_check(body: SQLBody):
53
  def dev_verifier_check(body: SQLBody):
54
  """
55
  Run the Verifier stage directly on a raw SQL string
56
- with a real adapter connection.
57
  """
 
 
 
 
58
  try:
59
- adapter = SQLiteAdapter("data/chinook.db")
 
 
 
 
 
 
60
  except Exception as e:
61
  raise HTTPException(status_code=500, detail=f"Adapter init failed: {e}")
62
 
 
8
 
9
  from dataclasses import is_dataclass, asdict
10
  from typing import Any
11
+ import yaml
12
+ from pathlib import Path
13
+ import os
14
+
15
+ CONFIG_PATH = os.getenv("PIPELINE_CONFIG", "configs/sqlite_pipeline.yaml")
16
 
17
 
18
  def _is_dataclass_instance(x: Any) -> bool:
 
58
  def dev_verifier_check(body: SQLBody):
59
  """
60
  Run the Verifier stage directly on a raw SQL string
61
+ with a real adapter connection loaded from YAML config.
62
  """
63
+ config_path = Path(CONFIG_PATH)
64
+ if not config_path.exists():
65
+ raise HTTPException(status_code=500, detail="Config file not found")
66
+
67
  try:
68
+ with config_path.open("r") as f:
69
+ config = yaml.safe_load(f)
70
+ dsn = config.get("adapter", {}).get("dsn")
71
+ if not dsn:
72
+ raise ValueError("Missing adapter.dsn in config file")
73
+
74
+ adapter = SQLiteAdapter(dsn)
75
  except Exception as e:
76
  raise HTTPException(status_code=500, detail=f"Adapter init failed: {e}")
77
 
configs/sqlite_pipeline.yaml CHANGED
@@ -8,7 +8,7 @@ repair: default
8
 
9
  adapter:
10
  kind: sqlite
11
- dsn: data/chinook.db
12
 
13
  llm:
14
  provider: openai
 
8
 
9
  adapter:
10
  kind: sqlite
11
+ dsn: data/Chinook_Sqlite.sqlite
12
 
13
  llm:
14
  provider: openai
tests/test_pipeline_factory.py CHANGED
@@ -3,20 +3,28 @@ from nl2sql.pipeline_factory import (
3
  pipeline_from_config_with_adapter,
4
  )
5
  from adapters.db.sqlite_adapter import SQLiteAdapter
 
 
 
 
 
 
6
 
7
 
8
  def test_pipeline_from_config_builds_and_runs(tmp_path):
9
- p = pipeline_from_config("configs/sqlite_pipeline.yaml")
10
  result = p.run(user_query="Top 3 albums by sales")
11
  assert result.sql is not None
12
  assert isinstance(result.traces, list)
13
 
14
 
15
  def test_pipeline_from_config_with_adapter_override(tmp_path):
16
- adapter = SQLiteAdapter("data/chinook.db")
17
- p = pipeline_from_config_with_adapter(
18
- "configs/sqlite_pipeline.yaml", adapter=adapter
19
- )
 
 
20
  result = p.run(user_query="Count customers")
21
  assert "SELECT" in result.sql.upper()
22
  assert isinstance(result.traces, list)
@@ -25,7 +33,7 @@ def test_pipeline_from_config_with_adapter_override(tmp_path):
25
  def test_full_pipeline_from_yaml(monkeypatch):
26
  from nl2sql.pipeline_factory import pipeline_from_config
27
 
28
- p = pipeline_from_config("configs/sqlite_pipeline.yaml")
29
  res = p.run(user_query="List all artists")
30
  assert res.ok
31
  assert isinstance(res.sql, str)
 
3
  pipeline_from_config_with_adapter,
4
  )
5
  from adapters.db.sqlite_adapter import SQLiteAdapter
6
+ import yaml
7
+ from pathlib import Path
8
+ import os
9
+
10
+ CONFIG_PATH = os.getenv("PIPELINE_CONFIG", "configs/sqlite_pipeline.yaml")
11
+ config_path = Path(CONFIG_PATH)
12
 
13
 
14
  def test_pipeline_from_config_builds_and_runs(tmp_path):
15
+ p = pipeline_from_config(CONFIG_PATH)
16
  result = p.run(user_query="Top 3 albums by sales")
17
  assert result.sql is not None
18
  assert isinstance(result.traces, list)
19
 
20
 
21
  def test_pipeline_from_config_with_adapter_override(tmp_path):
22
+ with config_path.open("r") as f:
23
+ config = yaml.safe_load(f)
24
+ dsn = config.get("adapter", {}).get("dsn")
25
+
26
+ adapter = SQLiteAdapter(dsn)
27
+ p = pipeline_from_config_with_adapter(CONFIG_PATH, adapter=adapter)
28
  result = p.run(user_query="Count customers")
29
  assert "SELECT" in result.sql.upper()
30
  assert isinstance(result.traces, list)
 
33
  def test_full_pipeline_from_yaml(monkeypatch):
34
  from nl2sql.pipeline_factory import pipeline_from_config
35
 
36
+ p = pipeline_from_config(CONFIG_PATH)
37
  res = p.run(user_query="List all artists")
38
  assert res.ok
39
  assert isinstance(res.sql, str)