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)
         | 
