z90486091 commited on
Commit
ccec00a
·
verified ·
1 Parent(s): b0dae83

Create db_syncer.py

Browse files
Files changed (1) hide show
  1. db_syncer.py +108 -0
db_syncer.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ db_syncer.py — watches litellm.db for changes, commits to HF Space repo every 5 mins.
4
+ Run as background process inside the container.
5
+
6
+ Required env vars:
7
+ HF_TOKEN — HuggingFace write token
8
+ HF_REPO_ID — e.g. Ins0mn1a/llm_router-v1
9
+ DB_PATH — path to litellm.db (default: /app/data/litellm.db)
10
+ SYNC_INTERVAL — seconds between syncs (default: 300)
11
+ """
12
+
13
+ import hashlib
14
+ import os
15
+ import time
16
+ import logging
17
+ import base64
18
+
19
+ import requests
20
+
21
+ logging.basicConfig(
22
+ format="%(asctime)s [db_syncer] %(levelname)s %(message)s",
23
+ level=logging.INFO,
24
+ )
25
+ log = logging.getLogger("db_syncer")
26
+
27
+ HF_TOKEN = os.environ["HF_TOKEN"]
28
+ HF_REPO_ID = os.environ["HF_REPO_ID"]
29
+ DB_PATH = os.environ.get("DB_PATH", "/app/data/litellm.db")
30
+ SYNC_INTERVAL = int(os.environ.get("SYNC_INTERVAL", "300"))
31
+ HF_API = "https://huggingface.co/api"
32
+ REPO_FILE_PATH = "litellm.db"
33
+
34
+
35
+ def file_hash(path: str) -> str:
36
+ h = hashlib.sha256()
37
+ with open(path, "rb") as f:
38
+ for chunk in iter(lambda: f.read(65536), b""):
39
+ h.update(chunk)
40
+ return h.hexdigest()
41
+
42
+
43
+ def get_remote_sha(headers: dict) -> str | None:
44
+ """Get current file SHA from HF repo (needed for updates)."""
45
+ url = f"{HF_API}/spaces/{HF_REPO_ID}/raw/{REPO_FILE_PATH}"
46
+ r = requests.head(url, headers=headers)
47
+ if r.status_code == 200:
48
+ return r.headers.get("X-SHA")
49
+ return None
50
+
51
+
52
+ def upload_db(headers: dict) -> bool:
53
+ """Upload litellm.db to HF Space repo via API."""
54
+ with open(DB_PATH, "rb") as f:
55
+ content = base64.b64encode(f.read()).decode()
56
+
57
+ payload = {
58
+ "content": content,
59
+ "encoding": "base64",
60
+ "message": f"db_syncer: auto-sync litellm.db",
61
+ }
62
+
63
+ url = f"{HF_API}/spaces/{HF_REPO_ID}/upload/{REPO_FILE_PATH}"
64
+ r = requests.post(url, headers=headers, json=payload, timeout=60)
65
+
66
+ if r.status_code in (200, 201):
67
+ log.info("Synced litellm.db to HF Space repo")
68
+ return True
69
+ else:
70
+ log.error(f"Sync failed: {r.status_code} {r.text[:200]}")
71
+ return False
72
+
73
+
74
+ def main():
75
+ log.info(f"Starting db_syncer — watching {DB_PATH} every {SYNC_INTERVAL}s")
76
+ log.info(f"Target repo: {HF_REPO_ID}")
77
+
78
+ headers = {
79
+ "Authorization": f"Bearer {HF_TOKEN}",
80
+ "Content-Type": "application/json",
81
+ }
82
+
83
+ last_hash = None
84
+
85
+ while True:
86
+ try:
87
+ if not os.path.exists(DB_PATH):
88
+ log.warning(f"{DB_PATH} not found — waiting...")
89
+ time.sleep(30)
90
+ continue
91
+
92
+ current_hash = file_hash(DB_PATH)
93
+
94
+ if current_hash != last_hash:
95
+ log.info(f"Change detected (hash: {current_hash[:8]}...) — syncing")
96
+ if upload_db(headers):
97
+ last_hash = current_hash
98
+ else:
99
+ log.debug("No change detected — skipping sync")
100
+
101
+ except Exception as e:
102
+ log.error(f"Sync error: {e}")
103
+
104
+ time.sleep(SYNC_INTERVAL)
105
+
106
+
107
+ if __name__ == "__main__":
108
+ main()