Skip to content

Commit 70fdefb

Browse files
committed
Improve SQLite metrics runner
1 parent 1ea78ca commit 70fdefb

File tree

3 files changed

+97
-0
lines changed

3 files changed

+97
-0
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ dependencies = [
2222
"psycopg2-binary",
2323
"Jinja2",
2424
"rich>=14.0.0",
25+
"pysqlite3-binary>=0.5.4",
2526
]
2627

2728
[dependency-groups]

scripts/run_metrics_sqlite.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#!/usr/bin/env -S uv run
2+
"""Run dashboard metrics against the SQLite fixture."""
3+
4+
from __future__ import annotations
5+
6+
import argparse
7+
import pysqlite3 as sqlite3
8+
import re
9+
from pathlib import Path
10+
from typing import Sequence
11+
12+
import sys
13+
14+
sys.path.append(str(Path(__file__).resolve().parent.parent))
15+
from scripts.generate_dashboard import load_metrics
16+
17+
18+
def concat(*args: Sequence[object]) -> str:
19+
return "".join(str(a) for a in args)
20+
21+
22+
def regexp(pattern: str, value: object) -> int:
23+
if value is None:
24+
return 0
25+
return 1 if re.search(pattern, str(value)) else 0
26+
27+
28+
def adapt_sql(sql: str) -> str:
29+
"""Lightweight translation of Postgres SQL to SQLite."""
30+
# Drop postgres style casts
31+
sql = re.sub(r"::\w+", "", sql)
32+
# Replace regex operators
33+
sql = re.sub(r"([\w:\".]+)\s*!~\s*'([^']*)'", r"NOT regexp('\2', \1)", sql)
34+
sql = re.sub(r"([\w:\".]+)\s*~\s*'([^']*)'", r"regexp('\2', \1)", sql)
35+
# Replace ILIKE with case-insensitive LIKE
36+
sql = re.sub(r"\bILIKE\b", "LIKE", sql, flags=re.IGNORECASE)
37+
# Replace string_agg with group_concat
38+
sql = re.sub(r"String_agg", "group_concat", sql, flags=re.IGNORECASE)
39+
# Drop columns not present in the fixture
40+
sql = sql.replace("i.date_fin_valid,", "")
41+
sql = sql.replace("opa.way && caclr.geom_3857 AND ", "")
42+
sql = re.sub(r"\blat_wgs84\b", "ST_Y(geom)", sql)
43+
sql = re.sub(r"\blon_wgs84\b", "ST_X(geom)", sql)
44+
return sql
45+
46+
47+
def run(
48+
db_path: Path,
49+
metric_dir: str = "metrics",
50+
include_dir: str = "includes",
51+
only: str | None = None,
52+
) -> None:
53+
conn = sqlite3.connect(db_path)
54+
conn.enable_load_extension(True)
55+
conn.load_extension("mod_spatialite")
56+
conn.create_function("concat", -1, concat)
57+
conn.create_function("regexp", 2, regexp)
58+
59+
metrics = load_metrics(metric_dir, include_dir)
60+
if only:
61+
metrics = [m for m in metrics if m.slug == only]
62+
for metric in metrics:
63+
sql = adapt_sql(metric.sql)
64+
try:
65+
cur = conn.execute(sql)
66+
rows = cur.fetchall()
67+
print(f"{metric.slug}: {len(rows)} rows")
68+
except Exception as exc: # pragma: no cover - manual script
69+
print(f"{metric.slug}: ERROR {exc}")
70+
71+
conn.close()
72+
73+
74+
def main() -> None:
75+
parser = argparse.ArgumentParser(description=__doc__)
76+
parser.add_argument(
77+
"db", type=Path, nargs="?", default=Path("tests/fixture.sqlite"), help="Path to SQLite DB"
78+
)
79+
parser.add_argument("--metric", help="Run only a single metric")
80+
args = parser.parse_args()
81+
run(args.db, only=args.metric)
82+
83+
84+
if __name__ == "__main__":
85+
main()

uv.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)