Create api.py
Browse files
api.py
ADDED
@@ -0,0 +1,208 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import FastAPI, HTTPException, BackgroundTasks
|
2 |
+
from fastapi.middleware.cors import CORSMiddleware
|
3 |
+
from pydantic import BaseModel
|
4 |
+
from typing import Dict, Any, Optional
|
5 |
+
import json
|
6 |
+
import os
|
7 |
+
from datetime import datetime
|
8 |
+
from huggingface_hub import HfApi, Repository
|
9 |
+
import hashlib
|
10 |
+
import uuid
|
11 |
+
|
12 |
+
# FastAPI app
|
13 |
+
app = FastAPI(title="ML Tracker API", version="1.0.0")
|
14 |
+
|
15 |
+
# CORS middleware
|
16 |
+
app.add_middleware(
|
17 |
+
CORSMiddleware,
|
18 |
+
allow_origins=["*"],
|
19 |
+
allow_credentials=True,
|
20 |
+
allow_methods=["*"],
|
21 |
+
allow_headers=["*"],
|
22 |
+
)
|
23 |
+
|
24 |
+
# Pydantic models
|
25 |
+
class LogRequest(BaseModel):
|
26 |
+
api_key: str
|
27 |
+
experiment: str
|
28 |
+
step: int
|
29 |
+
metrics: Dict[str, Any]
|
30 |
+
timestamp: Optional[float] = None
|
31 |
+
config: Optional[Dict[str, Any]] = None
|
32 |
+
|
33 |
+
class ExperimentResponse(BaseModel):
|
34 |
+
experiment: str
|
35 |
+
total_steps: int
|
36 |
+
created_at: str
|
37 |
+
last_updated: str
|
38 |
+
metrics: list
|
39 |
+
|
40 |
+
# In-memory storage (in production, use HF Hub or database)
|
41 |
+
experiments_db = {}
|
42 |
+
api_keys_db = {}
|
43 |
+
|
44 |
+
def verify_api_key(api_key: str) -> bool:
|
45 |
+
"""Verify if the API key is valid"""
|
46 |
+
# In production, verify against HF Hub or database
|
47 |
+
return True # Simplified for demo
|
48 |
+
|
49 |
+
def get_user_from_api_key(api_key: str) -> str:
|
50 |
+
"""Get username from API key"""
|
51 |
+
# In production, lookup from database
|
52 |
+
return hashlib.sha256(api_key.encode()).hexdigest()[:8]
|
53 |
+
|
54 |
+
@app.get("/")
|
55 |
+
async def root():
|
56 |
+
return {"message": "ML Tracker API - Free W&B Alternative", "version": "1.0.0"}
|
57 |
+
|
58 |
+
@app.post("/api/log")
|
59 |
+
async def log_metrics(request: LogRequest, background_tasks: BackgroundTasks):
|
60 |
+
"""Log metrics for an experiment"""
|
61 |
+
try:
|
62 |
+
# Verify API key
|
63 |
+
if not verify_api_key(request.api_key):
|
64 |
+
raise HTTPException(status_code=401, detail="Invalid API key")
|
65 |
+
|
66 |
+
user_id = get_user_from_api_key(request.api_key)
|
67 |
+
experiment_key = f"{user_id}_{request.experiment}"
|
68 |
+
|
69 |
+
# Initialize experiment if not exists
|
70 |
+
if experiment_key not in experiments_db:
|
71 |
+
experiments_db[experiment_key] = {
|
72 |
+
"experiment": request.experiment,
|
73 |
+
"user_id": user_id,
|
74 |
+
"created_at": datetime.now().isoformat(),
|
75 |
+
"metrics": [],
|
76 |
+
"config": request.config or {}
|
77 |
+
}
|
78 |
+
|
79 |
+
# Add metrics
|
80 |
+
metric_entry = {
|
81 |
+
"step": request.step,
|
82 |
+
"timestamp": request.timestamp or datetime.now().timestamp(),
|
83 |
+
"metrics": request.metrics
|
84 |
+
}
|
85 |
+
|
86 |
+
experiments_db[experiment_key]["metrics"].append(metric_entry)
|
87 |
+
experiments_db[experiment_key]["last_updated"] = datetime.now().isoformat()
|
88 |
+
|
89 |
+
# Update config if provided
|
90 |
+
if request.config:
|
91 |
+
experiments_db[experiment_key]["config"].update(request.config)
|
92 |
+
|
93 |
+
# Background task to save to HF Hub (simplified)
|
94 |
+
background_tasks.add_task(save_to_hub, experiment_key)
|
95 |
+
|
96 |
+
return {
|
97 |
+
"status": "success",
|
98 |
+
"experiment": request.experiment,
|
99 |
+
"step": request.step,
|
100 |
+
"metrics_logged": len(request.metrics)
|
101 |
+
}
|
102 |
+
|
103 |
+
except Exception as e:
|
104 |
+
raise HTTPException(status_code=500, detail=f"Error logging metrics: {str(e)}")
|
105 |
+
|
106 |
+
@app.get("/api/experiments")
|
107 |
+
async def get_experiments(api_key: str):
|
108 |
+
"""Get all experiments for a user"""
|
109 |
+
try:
|
110 |
+
if not verify_api_key(api_key):
|
111 |
+
raise HTTPException(status_code=401, detail="Invalid API key")
|
112 |
+
|
113 |
+
user_id = get_user_from_api_key(api_key)
|
114 |
+
user_experiments = []
|
115 |
+
|
116 |
+
for exp_key, exp_data in experiments_db.items():
|
117 |
+
if exp_data["user_id"] == user_id:
|
118 |
+
user_experiments.append({
|
119 |
+
"experiment": exp_data["experiment"],
|
120 |
+
"total_steps": len(exp_data["metrics"]),
|
121 |
+
"created_at": exp_data["created_at"],
|
122 |
+
"last_updated": exp_data.get("last_updated", exp_data["created_at"])
|
123 |
+
})
|
124 |
+
|
125 |
+
return {"experiments": user_experiments}
|
126 |
+
|
127 |
+
except Exception as e:
|
128 |
+
raise HTTPException(status_code=500, detail=f"Error fetching experiments: {str(e)}")
|
129 |
+
|
130 |
+
@app.get("/api/experiment/{experiment_name}")
|
131 |
+
async def get_experiment(experiment_name: str, api_key: str):
|
132 |
+
"""Get detailed data for a specific experiment"""
|
133 |
+
try:
|
134 |
+
if not verify_api_key(api_key):
|
135 |
+
raise HTTPException(status_code=401, detail="Invalid API key")
|
136 |
+
|
137 |
+
user_id = get_user_from_api_key(api_key)
|
138 |
+
experiment_key = f"{user_id}_{experiment_name}"
|
139 |
+
|
140 |
+
if experiment_key not in experiments_db:
|
141 |
+
raise HTTPException(status_code=404, detail="Experiment not found")
|
142 |
+
|
143 |
+
exp_data = experiments_db[experiment_key]
|
144 |
+
|
145 |
+
return {
|
146 |
+
"experiment": exp_data["experiment"],
|
147 |
+
"created_at": exp_data["created_at"],
|
148 |
+
"last_updated": exp_data.get("last_updated", exp_data["created_at"]),
|
149 |
+
"total_steps": len(exp_data["metrics"]),
|
150 |
+
"config": exp_data["config"],
|
151 |
+
"metrics": exp_data["metrics"]
|
152 |
+
}
|
153 |
+
|
154 |
+
except Exception as e:
|
155 |
+
raise HTTPException(status_code=500, detail=f"Error fetching experiment: {str(e)}")
|
156 |
+
|
157 |
+
@app.delete("/api/experiment/{experiment_name}")
|
158 |
+
async def delete_experiment(experiment_name: str, api_key: str):
|
159 |
+
"""Delete an experiment"""
|
160 |
+
try:
|
161 |
+
if not verify_api_key(api_key):
|
162 |
+
raise HTTPException(status_code=401, detail="Invalid API key")
|
163 |
+
|
164 |
+
user_id = get_user_from_api_key(api_key)
|
165 |
+
experiment_key = f"{user_id}_{experiment_name}"
|
166 |
+
|
167 |
+
if experiment_key not in experiments_db:
|
168 |
+
raise HTTPException(status_code=404, detail="Experiment not found")
|
169 |
+
|
170 |
+
del experiments_db[experiment_key]
|
171 |
+
|
172 |
+
return {"status": "success", "message": f"Experiment '{experiment_name}' deleted"}
|
173 |
+
|
174 |
+
except Exception as e:
|
175 |
+
raise HTTPException(status_code=500, detail=f"Error deleting experiment: {str(e)}")
|
176 |
+
|
177 |
+
async def save_to_hub(experiment_key: str):
|
178 |
+
"""Save experiment data to HuggingFace Hub (background task)"""
|
179 |
+
try:
|
180 |
+
# In production, save to HF Hub datasets
|
181 |
+
# For demo, we'll just log
|
182 |
+
print(f"Saving experiment {experiment_key} to HF Hub...")
|
183 |
+
|
184 |
+
# Example HF Hub integration:
|
185 |
+
# api = HfApi(token=user_token)
|
186 |
+
# repo_id = f"{username}/ml-tracker-data"
|
187 |
+
# api.upload_file(
|
188 |
+
# path_or_fileobj=json.dumps(experiments_db[experiment_key]),
|
189 |
+
# path_in_repo=f"experiments/{experiment_key}.json",
|
190 |
+
# repo_id=repo_id,
|
191 |
+
# repo_type="dataset"
|
192 |
+
# )
|
193 |
+
|
194 |
+
except Exception as e:
|
195 |
+
print(f"Error saving to hub: {str(e)}")
|
196 |
+
|
197 |
+
@app.get("/api/health")
|
198 |
+
async def health_check():
|
199 |
+
"""Health check endpoint"""
|
200 |
+
return {
|
201 |
+
"status": "healthy",
|
202 |
+
"timestamp": datetime.now().isoformat(),
|
203 |
+
"total_experiments": len(experiments_db)
|
204 |
+
}
|
205 |
+
|
206 |
+
if __name__ == "__main__":
|
207 |
+
import uvicorn
|
208 |
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|