sqlLIte / app.py
gcharanteja
feat: add Swagger documentation for API endpoints and integrate Swagger UI
de703f2
Raw
History Blame Contribute Delete
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 = ""
@app.get("/", response_class=HTMLResponse)
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>
"""
@app.get("/portal", response_class=HTMLResponse)
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>
"""
@app.get("/health")
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)}
@app.get("/api/v1/heartbeat")
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)}")
@app.get("/api/v1/projects")
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)}")
@app.post("/api/v1/projects")
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)}")
@app.delete("/api/v1/projects/{project_name}")
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)}")
@app.post("/api/v1/query")
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)}")
@app.post("/api/v1/execute")
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)}")
@app.get("/api/v1/tables")
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)}")
@app.get("/api/v1/schema")
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)