Add basic backend infrastructure
Signed-off-by: Felipe Cardoso <felipe.cardoso@hotmail.it>
This commit is contained in:
0
backend/Dockerfile
Normal file
0
backend/Dockerfile
Normal file
0
backend/app/__init__.py
Normal file
0
backend/app/__init__.py
Normal file
0
backend/app/api/__init__.py
Normal file
0
backend/app/api/__init__.py
Normal file
0
backend/app/api/routes/__init__.py
Normal file
0
backend/app/api/routes/__init__.py
Normal file
34
backend/app/api/routes/samples.py
Normal file
34
backend/app/api/routes/samples.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
|
from fastapi import APIRouter, HTTPException, Query
|
||||||
|
|
||||||
|
from app.models.sample import Sample
|
||||||
|
from app.services.sample_manager import SampleManager
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
sample_manager = SampleManager()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/list", response_model=List[Sample])
|
||||||
|
async def list_samples(
|
||||||
|
limit: int = Query(20, ge=1, le=100),
|
||||||
|
offset: int = Query(0, ge=0)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
List sample images with pagination
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return await sample_manager.list_samples(limit, offset)
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/latest", response_model=List[Sample])
|
||||||
|
async def get_latest_samples(count: int = Query(5, ge=1, le=20)):
|
||||||
|
"""
|
||||||
|
Get the most recent sample images
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return await sample_manager.get_latest_samples(count)
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
29
backend/app/api/routes/training.py
Normal file
29
backend/app/api/routes/training.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
from fastapi import APIRouter, HTTPException
|
||||||
|
|
||||||
|
from app.models.training import TrainingStatus
|
||||||
|
from app.services.training_monitor import TrainingMonitor
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
monitor = TrainingMonitor()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/status", response_model=TrainingStatus)
|
||||||
|
async def get_training_status():
|
||||||
|
"""
|
||||||
|
Get current training status including progress, loss, and learning rate
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return await monitor.get_status()
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/log")
|
||||||
|
async def get_training_log():
|
||||||
|
"""
|
||||||
|
Get recent training log entries
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return await monitor.get_log()
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
0
backend/app/core/__init__.py
Normal file
0
backend/app/core/__init__.py
Normal file
21
backend/app/core/config.py
Normal file
21
backend/app/core/config.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from pydantic_settings import BaseSettings
|
||||||
|
|
||||||
|
|
||||||
|
class Settings(BaseSettings):
|
||||||
|
# SFTP Settings
|
||||||
|
SFTP_HOST: str
|
||||||
|
SFTP_USER: str
|
||||||
|
SFTP_PASSWORD: str
|
||||||
|
SFTP_PATH: str
|
||||||
|
SFTP_PORT: int = 22
|
||||||
|
|
||||||
|
# API Settings
|
||||||
|
API_VER_STR: str = "/api/v1"
|
||||||
|
PROJECT_NAME: str = "Training Monitor"
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
env_file = ".env"
|
||||||
|
case_sensitive = True
|
||||||
|
|
||||||
|
|
||||||
|
settings = Settings()
|
||||||
29
backend/app/main.py
Normal file
29
backend/app/main.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
from fastapi import FastAPI
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
|
||||||
|
from app.api.routes import training, samples
|
||||||
|
from app.core.config import settings
|
||||||
|
|
||||||
|
app = FastAPI(
|
||||||
|
title="Training Monitor API",
|
||||||
|
description="API for monitoring ML training progress and samples",
|
||||||
|
version="1.0.0",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Configure CORS
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"], # In production, replace with specific origins
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Include routers with versioning
|
||||||
|
app.include_router(training.router, prefix=f"{settings.API_VER_STR}/training", tags=["training"])
|
||||||
|
app.include_router(samples.router, prefix=f"{settings.API_VER_STR}/samples", tags=["samples"])
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/health")
|
||||||
|
async def health_check():
|
||||||
|
return {"status": "healthy"}
|
||||||
0
backend/app/models/__init__.py
Normal file
0
backend/app/models/__init__.py
Normal file
11
backend/app/models/sample.py
Normal file
11
backend/app/models/sample.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class Sample(BaseModel):
|
||||||
|
filename: str
|
||||||
|
url: str
|
||||||
|
created_at: datetime
|
||||||
|
step: Optional[int] = None
|
||||||
14
backend/app/models/training.py
Normal file
14
backend/app/models/training.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class TrainingStatus(BaseModel):
|
||||||
|
current_step: int
|
||||||
|
total_steps: int
|
||||||
|
loss: float
|
||||||
|
learning_rate: float
|
||||||
|
eta_seconds: Optional[float]
|
||||||
|
steps_per_second: float
|
||||||
|
updated_at: datetime
|
||||||
0
backend/app/services/__init__.py
Normal file
0
backend/app/services/__init__.py
Normal file
15
backend/app/services/sample_manager.py
Normal file
15
backend/app/services/sample_manager.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
|
from app.models.sample import Sample
|
||||||
|
|
||||||
|
|
||||||
|
class SampleManager:
|
||||||
|
async def list_samples(self, limit: int, offset: int) -> List[Sample]:
|
||||||
|
# Implementation for listing samples from SFTP
|
||||||
|
# This is a placeholder - actual implementation needed
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def get_latest_samples(self, count: int) -> List[Sample]:
|
||||||
|
# Implementation for getting latest samples
|
||||||
|
# This is a placeholder - actual implementation needed
|
||||||
|
pass
|
||||||
13
backend/app/services/training_monitor.py
Normal file
13
backend/app/services/training_monitor.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
from app.models.training import TrainingStatus
|
||||||
|
|
||||||
|
|
||||||
|
class TrainingMonitor:
|
||||||
|
async def get_status(self) -> TrainingStatus:
|
||||||
|
# Implementation for parsing tqdm output
|
||||||
|
# This is a placeholder - actual implementation needed
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def get_log(self):
|
||||||
|
# Implementation for getting recent log entries
|
||||||
|
# This is a placeholder - actual implementation needed
|
||||||
|
pass
|
||||||
13
backend/requirements.txt
Normal file
13
backend/requirements.txt
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
fastapi>=0.104.0
|
||||||
|
uvicorn>=0.24.0
|
||||||
|
python-multipart>=0.0.6
|
||||||
|
python-jose[cryptography]>=3.3.0
|
||||||
|
passlib[bcrypt]>=1.7.4
|
||||||
|
pydantic>=2.4.2
|
||||||
|
pydantic-settings>=2.0.3
|
||||||
|
paramiko>=3.3.1
|
||||||
|
python-dotenv>=1.0.0
|
||||||
|
aiofiles>=23.2.1
|
||||||
|
pytest>=7.4.3
|
||||||
|
httpx>=0.25.1
|
||||||
|
pytest-asyncio>=0.21.1
|
||||||
Reference in New Issue
Block a user