Download app.py from maxxcarl/sqlLIte: direct link, hf CLI and curl.
- Browser
- Download file 16.1 kB
-
https://huggingface.co/spaces/maxxcarl/sqlLIte/resolve/main/app.py
- Command line
-
hf download hf://spaces/maxxcarl/sqlLIte/app.py
-
curl -L -o app.py https://huggingface.co/spaces/maxxcarl/sqlLIte/resolve/main/app.py
16.1 kB
| import os | |
| import requests | |
| from fastapi import FastAPI, HTTPException, Query | |
| from fastapi.responses import HTMLResponse | |
| from pydantic import BaseModel | |
| app = FastAPI(title="SQLite Multi-Project on HF Space") | |
| SQLITE_URL = "http://localhost:8000" | |
| class QueryRequest(BaseModel): | |
| sql: str | |
| project: str = "default" | |
| class ExecuteRequest(BaseModel): | |
| sql: str | |
| project: str = "default" | |
| class ProjectRequest(BaseModel): | |
| name: str | |
| description: str = "" | |
| def read_root(): | |
| return """ | |
| <html> | |
| <head> | |
| <title>SQLite Multi-Project Server</title> | |
| <style> | |
| body { font-family: Arial; margin: 40px; background: #f5f5f5; } | |
| h1 { color: #333; } | |
| .info { background: white; padding: 20px; border-radius: 8px; margin: 10px 0; } | |
| code { background: #e0e0e0; padding: 2px 6px; border-radius: 3px; } | |
| ul { line-height: 1.8; } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>✅ SQLite Multi-Project Server</h1> | |
| <div class="info"> | |
| <p><strong>Persistent Storage:</strong> /data/ (organized by project)</p> | |
| <p><strong>API Docs:</strong> <a href="/docs">Swagger UI</a></p> | |
| </div> | |
| <h2>Project Management</h2> | |
| <ul> | |
| <li><code>GET /api/v1/projects</code> - List all projects</li> | |
| <li><code>POST /api/v1/projects</code> - Create new project</li> | |
| <li><code>DELETE /api/v1/projects/{name}</code> - Delete project</li> | |
| </ul> | |
| <h2>Database Operations</h2> | |
| <ul> | |
| <li><code>GET /api/v1/tables?project=myproject</code> - List tables</li> | |
| <li><code>GET /api/v1/schema?project=myproject</code> - Get schema</li> | |
| <li><code>POST /api/v1/query</code> - Execute SELECT</li> | |
| <li><code>POST /api/v1/execute</code> - Execute INSERT/UPDATE/DELETE</li> | |
| </ul> | |
| <h2>Example: Multiple Projects</h2> | |
| <pre> | |
| 📁 /data/ | |
| ├── project1.db (users, orders tables) | |
| ├── project2.db (products, inventory tables) | |
| └── project3.db (logs, analytics tables) | |
| </pre> | |
| </body> | |
| </html> | |
| """ | |
| def portal(): | |
| return """ | |
| <!doctype html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |
| <title>SQLite Portal</title> | |
| <style> | |
| body { | |
| font-family: Arial, sans-serif; | |
| margin: 24px; | |
| color: #1f2328; | |
| background: #f6f8fa; | |
| } | |
| h1 { margin: 0 0 8px; } | |
| .meta { color: #57606a; margin-bottom: 16px; } | |
| .grid { | |
| display: grid; | |
| gap: 16px; | |
| grid-template-columns: 280px 1fr; | |
| } | |
| section { | |
| background: #ffffff; | |
| border: 1px solid #d0d7de; | |
| border-radius: 8px; | |
| padding: 16px; | |
| } | |
| textarea { | |
| width: 100%; | |
| min-height: 160px; | |
| font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, | |
| "Liberation Mono", "Courier New", monospace; | |
| border: 1px solid #d0d7de; | |
| border-radius: 6px; | |
| padding: 8px; | |
| box-sizing: border-box; | |
| } | |
| button { | |
| background: #0969da; | |
| color: #ffffff; | |
| border: none; | |
| border-radius: 6px; | |
| padding: 8px 12px; | |
| cursor: pointer; | |
| } | |
| button.secondary { background: #6e7781; } | |
| #tables { list-style: none; padding: 0; margin: 12px 0 0; } | |
| #tables li { padding: 6px 8px; border-bottom: 1px solid #d8dee4; } | |
| #tables li:last-child { border-bottom: none; } | |
| .actions { display: flex; gap: 8px; margin-top: 8px; } | |
| #status { margin-top: 12px; color: #57606a; white-space: pre-wrap; } | |
| table { width: 100%; border-collapse: collapse; margin-top: 12px; } | |
| th, td { | |
| border: 1px solid #d0d7de; | |
| padding: 6px 8px; | |
| text-align: left; | |
| vertical-align: top; | |
| font-size: 14px; | |
| } | |
| th { background: #f6f8fa; } | |
| pre { | |
| background: #f6f8fa; | |
| border: 1px solid #d0d7de; | |
| border-radius: 6px; | |
| padding: 12px; | |
| overflow: auto; | |
| } | |
| @media (max-width: 900px) { | |
| .grid { grid-template-columns: 1fr; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>SQLite Portal</h1> | |
| <div class="meta" id="meta">Loading database info...</div> | |
| <div class="grid"> | |
| <section> | |
| <h2>Projects</h2> | |
| <select id="project"></select> | |
| <div class="actions"> | |
| <button id="refresh">Refresh</button> | |
| </div> | |
| <h3>Tables</h3> | |
| <ul id="tables"></ul> | |
| </section> | |
| <section> | |
| <h2>SQL Editor</h2> | |
| <textarea id="sql">SELECT * FROM products LIMIT 50;</textarea> | |
| <div class="actions"> | |
| <button id="run">Run</button> | |
| <button id="clear" class="secondary">Clear Output</button> | |
| </div> | |
| <div id="status"></div> | |
| <div id="results"></div> | |
| </section> | |
| </div> | |
| <script> | |
| const projectEl = document.getElementById("project"); | |
| const tablesEl = document.getElementById("tables"); | |
| const metaEl = document.getElementById("meta"); | |
| const statusEl = document.getElementById("status"); | |
| const resultsEl = document.getElementById("results"); | |
| const sqlEl = document.getElementById("sql"); | |
| function setStatus(message) { | |
| statusEl.textContent = message || ""; | |
| } | |
| function clearResults() { | |
| resultsEl.innerHTML = ""; | |
| setStatus(""); | |
| } | |
| async function fetchJson(url, options) { | |
| const response = await fetch(url, options); | |
| const data = await response.json(); | |
| if (!response.ok) { | |
| const errorMessage = data.detail || data.error || data.message || response.statusText; | |
| throw new Error(errorMessage); | |
| } | |
| return data; | |
| } | |
| function renderTable(rows) { | |
| if (!Array.isArray(rows) || rows.length === 0) { | |
| resultsEl.innerHTML = "<em>No rows returned.</em>"; | |
| return; | |
| } | |
| const columns = Object.keys(rows[0]); | |
| const table = document.createElement("table"); | |
| const thead = document.createElement("thead"); | |
| const headerRow = document.createElement("tr"); | |
| columns.forEach((col) => { | |
| const th = document.createElement("th"); | |
| th.textContent = col; | |
| headerRow.appendChild(th); | |
| }); | |
| thead.appendChild(headerRow); | |
| table.appendChild(thead); | |
| const tbody = document.createElement("tbody"); | |
| rows.forEach((row) => { | |
| const tr = document.createElement("tr"); | |
| columns.forEach((col) => { | |
| const td = document.createElement("td"); | |
| const value = row[col] === null || row[col] === undefined ? "" : row[col]; | |
| td.textContent = value; | |
| tr.appendChild(td); | |
| }); | |
| tbody.appendChild(tr); | |
| }); | |
| table.appendChild(tbody); | |
| resultsEl.innerHTML = ""; | |
| resultsEl.appendChild(table); | |
| } | |
| function currentProject() { | |
| return projectEl.value || "default"; | |
| } | |
| async function loadInfo() { | |
| try { | |
| metaEl.textContent = `Server: ${window.location.origin} | Project: ${currentProject()}`; | |
| } catch (err) { | |
| metaEl.textContent = `Failed to load info: ${err.message}`; | |
| } | |
| } | |
| async function loadProjects() { | |
| projectEl.innerHTML = ""; | |
| try { | |
| const data = await fetchJson("/api/v1/projects"); | |
| const projects = Array.isArray(data) | |
| ? data | |
| : data.projects || data.data || []; | |
| const names = projects | |
| .map((p) => (typeof p === "string" ? p : p.name)) | |
| .filter(Boolean); | |
| const uniqueNames = names.length ? names : ["default"]; | |
| uniqueNames.forEach((name) => { | |
| const option = document.createElement("option"); | |
| option.value = name; | |
| option.textContent = name; | |
| projectEl.appendChild(option); | |
| }); | |
| } catch (err) { | |
| const option = document.createElement("option"); | |
| option.value = "default"; | |
| option.textContent = "default"; | |
| projectEl.appendChild(option); | |
| setStatus(`Failed to load projects: ${err.message}`); | |
| } | |
| } | |
| async function loadTables() { | |
| tablesEl.innerHTML = ""; | |
| try { | |
| const project = currentProject(); | |
| const data = await fetchJson(`/api/v1/tables?project=${project}`); | |
| const tables = data.tables || data.data || []; | |
| const names = tables | |
| .map((t) => (typeof t === "string" ? t : t.name)) | |
| .filter(Boolean); | |
| if (!names.length) { | |
| tablesEl.innerHTML = "<li>No tables found.</li>"; | |
| return; | |
| } | |
| names.forEach((name) => { | |
| const li = document.createElement("li"); | |
| li.textContent = name; | |
| li.addEventListener("click", () => { | |
| sqlEl.value = `SELECT * FROM ${name} LIMIT 50;`; | |
| }); | |
| tablesEl.appendChild(li); | |
| }); | |
| } catch (err) { | |
| tablesEl.innerHTML = `<li>Error: ${err.message}</li>`; | |
| } | |
| } | |
| async function runSql() { | |
| clearResults(); | |
| const sql = sqlEl.value.trim(); | |
| if (!sql) { | |
| setStatus("Enter SQL to run."); | |
| return; | |
| } | |
| setStatus("Running..."); | |
| const verb = sql.split(/\s+/)[0]?.toUpperCase(); | |
| const isQuery = ["SELECT", "PRAGMA", "WITH", "EXPLAIN"].includes(verb); | |
| const endpoint = isQuery ? "/api/v1/query" : "/api/v1/execute"; | |
| const project = currentProject(); | |
| try { | |
| const result = await fetchJson(endpoint, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ sql, project }), | |
| }); | |
| if (isQuery) { | |
| const rows = result.data || []; | |
| setStatus(`Query returned ${rows.length} rows.`); | |
| renderTable(rows); | |
| } else { | |
| setStatus(`Statement executed.`); | |
| resultsEl.innerHTML = `<pre>${JSON.stringify(result, null, 2)}</pre>`; | |
| } | |
| } catch (err) { | |
| setStatus(`Error: ${err.message}`); | |
| } | |
| } | |
| document.getElementById("refresh").addEventListener("click", () => { | |
| loadProjects().then(loadTables).then(loadInfo); | |
| }); | |
| projectEl.addEventListener("change", () => { | |
| loadTables(); | |
| loadInfo(); | |
| }); | |
| document.getElementById("run").addEventListener("click", runSql); | |
| document.getElementById("clear").addEventListener("click", clearResults); | |
| loadProjects().then(loadTables).then(loadInfo); | |
| </script> | |
| </body> | |
| </html> | |
| """ | |
| def health_check(): | |
| try: | |
| response = requests.get(f"{SQLITE_URL}/api/v1/heartbeat", timeout=5) | |
| if response.status_code == 200: | |
| return {"status": "healthy", "sqlite": "up"} | |
| else: | |
| return {"status": "unhealthy", "detail": response.text} | |
| except Exception as e: | |
| return {"status": "error", "detail": str(e)} | |
| def heartbeat(): | |
| try: | |
| response = requests.get(f"{SQLITE_URL}/api/v1/heartbeat", timeout=5) | |
| return response.json() | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=f"SQLite server error: {str(e)}") | |
| def list_projects(): | |
| try: | |
| response = requests.get(f"{SQLITE_URL}/api/v1/projects", timeout=5) | |
| if response.status_code != 200: | |
| raise HTTPException(status_code=response.status_code, detail=response.text) | |
| return response.json() | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=f"Projects error: {str(e)}") | |
| def create_project(request: ProjectRequest): | |
| try: | |
| response = requests.post(f"{SQLITE_URL}/api/v1/projects", json={"name": request.name, "description": request.description}, timeout=5) | |
| if response.status_code != 200: | |
| raise HTTPException(status_code=response.status_code, detail=response.text) | |
| return response.json() | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=f"Create project error: {str(e)}") | |
| def delete_project(project_name: str): | |
| try: | |
| response = requests.delete(f"{SQLITE_URL}/api/v1/projects/{project_name}", timeout=5) | |
| if response.status_code != 200: | |
| raise HTTPException(status_code=response.status_code, detail=response.text) | |
| return response.json() | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=f"Delete project error: {str(e)}") | |
| def query(request: QueryRequest): | |
| try: | |
| response = requests.post(f"{SQLITE_URL}/api/v1/query", json={"sql": request.sql, "project": request.project}, timeout=10) | |
| if response.status_code != 200: | |
| raise HTTPException(status_code=response.status_code, detail=response.text) | |
| return response.json() | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=f"Query error: {str(e)}") | |
| def execute(request: ExecuteRequest): | |
| try: | |
| response = requests.post(f"{SQLITE_URL}/api/v1/execute", json={"sql": request.sql, "project": request.project}, timeout=10) | |
| if response.status_code != 200: | |
| raise HTTPException(status_code=response.status_code, detail=response.text) | |
| return response.json() | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=f"Execute error: {str(e)}") | |
| def list_tables(project: str = Query("default")): | |
| try: | |
| response = requests.get(f"{SQLITE_URL}/api/v1/tables?project={project}", timeout=5) | |
| if response.status_code != 200: | |
| raise HTTPException(status_code=response.status_code, detail=response.text) | |
| return response.json() | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=f"Tables error: {str(e)}") | |
| def get_schema(project: str = Query("default")): | |
| try: | |
| response = requests.get(f"{SQLITE_URL}/api/v1/schema?project={project}", timeout=5) | |
| if response.status_code != 200: | |
| raise HTTPException(status_code=response.status_code, detail=response.text) | |
| return response.json() | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=f"Schema error: {str(e)}") | |
| if __name__ == "__main__": | |
| import uvicorn | |
| port = int(os.getenv("PORT", 7860)) | |
| uvicorn.run(app, host="0.0.0.0", port=port) | |