Subham9126 commited on
Commit
b8d96ef
·
verified ·
1 Parent(s): c7bb0a9

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +106 -0
app.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ import subprocess
4
+ import time
5
+ from pathlib import Path
6
+ from fastapi import FastAPI, HTTPException
7
+ from pydantic import BaseModel
8
+ import uvicorn
9
+
10
+ app = FastAPI(title="Stateless ClickHouse Gateway")
11
+
12
+ USER_FILES_DIR = "/app/ch/user_files"
13
+ SQL_DIR = "/app/sql_repo"
14
+
15
+ def sync_git_repo(repo_url, target_dir):
16
+ """Clones or forcefully updates a git repository."""
17
+ os.makedirs(target_dir, exist_ok=True)
18
+ git_dir = os.path.join(target_dir, ".git")
19
+
20
+ if not os.path.exists(git_dir):
21
+ print(f"Cloning {repo_url} into {target_dir}...")
22
+ subprocess.run(["git", "clone", repo_url, target_dir], check=True)
23
+ else:
24
+ print(f"Force updating {repo_url} in {target_dir}...")
25
+ # Fetch all remote changes and forcefully reset to avoid merge conflicts
26
+ subprocess.run(["git", "-C", target_dir, "fetch", "--all"], check=True)
27
+ subprocess.run(["git", "-C", target_dir, "reset", "--hard", "FETCH_HEAD"], check=True)
28
+
29
+ def setup_database():
30
+ """Pulls data/SQL and executes nested SQL scripts."""
31
+ with open("/app/config.json") as f:
32
+ config = json.load(f)
33
+
34
+ # 1. Sync Data Repositories
35
+ for i, repo_url in enumerate(config.get("data_repos", [])):
36
+ repo_dir = os.path.join(USER_FILES_DIR, f"repo_{i}")
37
+ sync_git_repo(repo_url, repo_dir)
38
+
39
+ # 2. Sync SQL Repository
40
+ sync_git_repo(config.get("sql_repo"), SQL_DIR)
41
+
42
+ # 3. Find and Execute SQL scripts recursively (handles nested folders)
43
+ print("--- SCANNING FOR SQL SCRIPTS ---")
44
+
45
+ # rglob finds .sql files in all subdirectories. sorted() ensures deterministic execution.
46
+ sql_paths = sorted(Path(SQL_DIR).rglob("*.sql"))
47
+
48
+ if not sql_paths:
49
+ print("No .sql files found in the repository!")
50
+
51
+ for path in sql_paths:
52
+ print(f"Executing {path.relative_to(SQL_DIR)}...")
53
+ subprocess.run(f"clickhouse-client --queries-file {str(path)}", shell=True, check=True)
54
+
55
+ @app.on_event("startup")
56
+ def startup_event():
57
+ print("--- STARTING CLICKHOUSE ---")
58
+ subprocess.Popen(["clickhouse-server", "--config-file=/etc/clickhouse-server/config.xml"])
59
+
60
+ # Wait for ClickHouse to boot
61
+ ready = False
62
+ for _ in range(45):
63
+ if subprocess.run(["clickhouse-client", "--query", "SELECT 1"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode == 0:
64
+ ready = True
65
+ break
66
+ time.sleep(1)
67
+
68
+ if not ready:
69
+ raise Exception("CRITICAL: ClickHouse failed to start. Container crashing.")
70
+
71
+ print("--- CLICKHOUSE IS ALIVE. ATTEMPTING DATA SYNC ---")
72
+ try:
73
+ setup_database()
74
+ print("--- DATA SYNC COMPLETE ---")
75
+ except Exception as e:
76
+ # Graceful degradation: If git or SQL fails, log it but keep DB running
77
+ print(f"WARNING: Data sync or SQL execution failed: {e}")
78
+ print("ClickHouse is still running and available for manual queries.")
79
+
80
+ print("--- SYSTEM READY ---")
81
+
82
+ @app.post("/refresh")
83
+ def refresh_data():
84
+ """Endpoint to pull the latest Git changes and re-run SQL."""
85
+ try:
86
+ setup_database()
87
+ return {"status": "success", "message": "Git repositories updated and SQL re-executed."}
88
+ except Exception as e:
89
+ raise HTTPException(status_code=500, detail=str(e))
90
+
91
+ class QueryRequest(BaseModel):
92
+ sql: str
93
+
94
+ @app.post("/query")
95
+ def run_query(req: QueryRequest):
96
+ """Endpoint to execute raw SQL against ClickHouse."""
97
+ process = subprocess.run(
98
+ ["clickhouse-client", "--query", req.sql, "--format", "JSON"],
99
+ capture_output=True, text=True
100
+ )
101
+ if process.returncode != 0:
102
+ raise HTTPException(status_code=400, detail=process.stderr)
103
+ return json.loads(process.stdout)
104
+
105
+ if __name__ == "__main__":
106
+ uvicorn.run(app, host="0.0.0.0", port=7860)