|
1 | 1 | from fastapi import APIRouter, HTTPException, status, Query |
2 | 2 | from typing import Optional |
3 | | -from ..models.schemas import JobListResponse, JobResponse, JobStatus, ProblemType |
| 3 | +from ..models.schemas import JobListResponse, JobResponse, JobStatus, ProblemType, JobUpdateRequest |
4 | 4 | from ..services.dynamo_service import dynamodb_service |
5 | 5 | from ..services.s3_service import s3_service |
6 | 6 | from ..utils.helpers import get_settings |
@@ -34,7 +34,9 @@ async def get_job_status(job_id: str): |
34 | 34 | started_at=job.get('started_at'), |
35 | 35 | completed_at=job.get('completed_at'), |
36 | 36 | metrics=job.get('metrics'), |
37 | | - error_message=job.get('error_message') |
| 37 | + error_message=job.get('error_message'), |
| 38 | + tags=job.get('tags'), |
| 39 | + notes=job.get('notes') |
38 | 40 | ) |
39 | 41 |
|
40 | 42 | # Generate download URLs if job is completed |
@@ -170,6 +172,68 @@ async def delete_job(job_id: str, delete_data: bool = True): |
170 | 172 | ) |
171 | 173 |
|
172 | 174 |
|
| 175 | +@router.patch("/{job_id}", response_model=JobResponse) |
| 176 | +async def update_job_metadata(job_id: str, request: JobUpdateRequest): |
| 177 | + """ |
| 178 | + Update job metadata (tags and notes) for experiment tracking. |
| 179 | + Tags can be used to categorize jobs (e.g., "experiment-1", "baseline", "production"). |
| 180 | + Notes can store observations or comments about the training run. |
| 181 | + """ |
| 182 | + try: |
| 183 | + # Verify job exists |
| 184 | + job = dynamodb_service.get_job(job_id) |
| 185 | + if not job: |
| 186 | + raise HTTPException( |
| 187 | + status_code=status.HTTP_404_NOT_FOUND, |
| 188 | + detail="Job not found" |
| 189 | + ) |
| 190 | + |
| 191 | + # Validate tags if provided |
| 192 | + if request.tags is not None: |
| 193 | + if len(request.tags) > 10: |
| 194 | + raise HTTPException( |
| 195 | + status_code=status.HTTP_400_BAD_REQUEST, |
| 196 | + detail="Maximum 10 tags allowed per job" |
| 197 | + ) |
| 198 | + # Validate individual tag length |
| 199 | + for tag in request.tags: |
| 200 | + if not tag.strip(): |
| 201 | + raise HTTPException( |
| 202 | + status_code=status.HTTP_400_BAD_REQUEST, |
| 203 | + detail="Tags cannot be empty or whitespace" |
| 204 | + ) |
| 205 | + if len(tag) > 50: |
| 206 | + raise HTTPException( |
| 207 | + status_code=status.HTTP_400_BAD_REQUEST, |
| 208 | + detail="Each tag must be 50 characters or less" |
| 209 | + ) |
| 210 | + |
| 211 | + # Validate notes length if provided (defense-in-depth, Pydantic also validates) |
| 212 | + if request.notes is not None and len(request.notes) > 1000: |
| 213 | + raise HTTPException( |
| 214 | + status_code=status.HTTP_400_BAD_REQUEST, |
| 215 | + detail="Notes must be 1000 characters or less" |
| 216 | + ) |
| 217 | + |
| 218 | + # Update job metadata in DynamoDB |
| 219 | + dynamodb_service.update_job_metadata( |
| 220 | + job_id=job_id, |
| 221 | + tags=request.tags, |
| 222 | + notes=request.notes |
| 223 | + ) |
| 224 | + |
| 225 | + # Return updated job |
| 226 | + return await get_job_status(job_id) |
| 227 | + |
| 228 | + except HTTPException: |
| 229 | + raise |
| 230 | + except Exception as e: |
| 231 | + raise HTTPException( |
| 232 | + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, |
| 233 | + detail=f"Error updating job metadata: {str(e)}" |
| 234 | + ) |
| 235 | + |
| 236 | + |
173 | 237 | @router.get("", response_model=JobListResponse) |
174 | 238 | async def list_jobs( |
175 | 239 | limit: int = Query(default=20, ge=1, le=100), |
|
0 commit comments