File size: 6,831 Bytes
bcee114
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
#!/usr/bin/env python3
"""Local smoke test for `app.persistence` β€” does not start the FastAPI app.

Runs three checks:
  1) Configuration check: are HF_TOKEN / HF_NAMESPACE / HF_DATASET_REPO set?
  2) `pull_db(force=True)` β€” downloads fin_evalops.db from the Dataset repo
     to a temp path, verifies it's a valid SQLite file.
  3) `push_db(reason='local-test', force=True)` β€” backs up the local DB and
     uploads it; verifies the file ends up in the Dataset repo.

Usage:
    # 1. Quick: skip push (just verify config + pull)
    python backend/scripts/test_persistence.py --no-push

    # 2. Full: pull + push (uses your real HF_TOKEN from env)
    python backend/scripts/test_persistence.py

    # 3. Custom repo
    HF_DATASET_REPO=my-test-db python backend/scripts/test_persistence.py

Notes:
    - This script MUTATES the Dataset repo (creates/updates a commit). Use a
      throwaway `HF_DATASET_REPO` for the first run.
    - Requires `huggingface-hub` (added to backend/requirements.txt).
"""
from __future__ import annotations

import argparse
import os
import shutil
import sqlite3
import sys
import tempfile
from pathlib import Path

# Make `app` importable regardless of where this script is run from
HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(HERE.parent))

from app import persistence  # noqa: E402
from app.config import settings  # noqa: E402


# ---------------------------------------------------------------------------
def _print_status() -> None:
    print("=" * 60)
    print("Fin-EvalOps HF persistence β€” local smoke test")
    print("=" * 60)
    print(f"  HF_TOKEN         : {'set (' + settings.hf_token[:6] + '...)' if settings.hf_token else '<empty>'}")
    print(f"  HF_NAMESPACE     : {settings.hf_namespace or '<empty>'}")
    print(f"  HF_DATASET_REPO  : {settings.hf_dataset_repo or '<empty>'}")
    print(f"  HF_PUSH_INTERVAL : {settings.hf_push_interval}s")
    print(f"  local DB path    : {settings.db_path_abs}")
    print(f"  local DB exists  : {Path(settings.db_path_abs).exists()}")
    if Path(settings.db_path_abs).exists():
        print(f"  local DB size    : {Path(settings.db_path_abs).stat().st_size:,} bytes")
    print()


def _check_config() -> bool:
    missing = []
    if not settings.hf_token:
        missing.append("HF_TOKEN")
    if not settings.hf_namespace:
        missing.append("HF_NAMESPACE")
    if not settings.hf_dataset_repo:
        missing.append("HF_DATASET_REPO")
    if missing:
        print(f"❌ Missing required env vars: {', '.join(missing)}")
        print("   Export them and retry, e.g.:")
        print("     export HF_TOKEN=hf_xxx")
        print("     export HF_NAMESPACE=yourname")
        return False
    return True


def _check_sqlite(path: Path) -> bool:
    try:
        con = sqlite3.connect(str(path))
        cur = con.execute("SELECT name FROM sqlite_master WHERE type='table'")
        tables = [r[0] for r in cur.fetchall()]
        con.close()
        print(f"   valid SQLite, {len(tables)} tables: {', '.join(tables[:6])}{'...' if len(tables) > 6 else ''}")
        return True
    except Exception as exc:
        print(f"   ❌ not a valid SQLite file: {exc}")
        return False


def _download_to_temp() -> Path | None:
    """Download via huggingface_hub to a temp path (bypasses local DB)."""
    from huggingface_hub import hf_hub_download
    tmp = Path(tempfile.mkdtemp(prefix="hf_pull_"))
    try:
        path = hf_hub_download(
            repo_id=f"{settings.hf_namespace}/{settings.hf_dataset_repo}",
            filename="fin_evalops.db",
            repo_type="dataset",
            token=settings.hf_token,
            local_dir=str(tmp),
        )
        p = Path(path)
        print(f"   downloaded to {p}  ({p.stat().st_size:,} bytes)")
        return p
    except Exception as exc:
        print(f"   download failed: {type(exc).__name__}: {exc}")
        shutil.rmtree(tmp, ignore_errors=True)
        return None


# ---------------------------------------------------------------------------
def main() -> int:
    ap = argparse.ArgumentParser(description=__doc__.split("\n\n", 1)[0])
    ap.add_argument("--no-push", action="store_true", help="skip the push step (pull only)")
    ap.add_argument("--no-pull", action="store_true", help="skip the pull step (push only)")
    args = ap.parse_args()

    _print_status()

    if not _check_config():
        return 1

    failures = 0

    # ---- 1) pull ----
    if not args.no_pull:
        print("πŸ“₯ Step 1/3: pull fin_evalops.db from HF Dataset repo")
        # Use the helper that bypasses local-DB-exists guard (we want to test
        # even if the local DB is already populated).
        print("   testing raw huggingface_hub download…")
        p = _download_to_temp()
        if not p:
            print("   (This is OK on first deploy β€” Dataset repo may not exist yet.)")
        else:
            _check_sqlite(p)
            shutil.rmtree(p.parent, ignore_errors=True)
        print()

    # ---- 2) push via persistence.push_db ----
    if not args.no_push:
        if not Path(settings.db_path_abs).exists():
            print("⚠️  Step 2/3: local DB missing; creating a minimal test DB first")
            Path(settings.db_path_abs).parent.mkdir(parents=True, exist_ok=True)
            con = sqlite3.connect(str(settings.db_path_abs))
            con.execute("CREATE TABLE IF NOT EXISTS _smoke_test (ts TEXT, msg TEXT)")
            con.execute("INSERT INTO _smoke_test VALUES (datetime('now'), 'local smoke test')")
            con.commit()
            con.close()
            print(f"   created {settings.db_path_abs} with a _smoke_test row")
        else:
            print(f"   using existing local DB ({Path(settings.db_path_abs).stat().st_size:,} bytes)")

        print("πŸ“€ Step 2/3: push_db(reason='local-test', force=True)")
        ok = persistence.push_db(reason="local-test", force=True)
        print(f"   result: {'βœ… ok' if ok else '❌ failed'}")
        if not ok:
            failures += 1
        print()

    # ---- 3) status ----
    print("πŸ“Š Step 3/3: persistence_status snapshot")
    from huggingface_hub import HfApi
    try:
        api = HfApi(token=settings.hf_token)
        files = api.list_repo_files(
            repo_id=f"{settings.hf_namespace}/{settings.hf_dataset_repo}",
            repo_type="dataset",
        )
        print(f"   files in {settings.hf_namespace}/{settings.hf_dataset_repo}: {files}")
    except Exception as exc:
        print(f"   could not list files: {type(exc).__name__}: {exc}")

    print()
    print("=" * 60)
    if failures:
        print(f"❌ {failures} step(s) failed")
    else:
        print("βœ… all steps passed")
    print("=" * 60)
    return 0 if failures == 0 else 1


if __name__ == "__main__":
    sys.exit(main())