# ta_ml/api/backup.py """ API endpoints for backup and cache management. """ import logging from fastapi import APIRouter, Depends, HTTPException from fastapi.responses import FileResponse from models.api import CacheFileListResponse, BackupUploadResponse from services.backup_service import BackupService from api.dependencies import get_backup_service logger = logging.getLogger(__name__) router = APIRouter(prefix="/backup", tags=["Cache Backup"]) @router.get( "/list-cache", response_model=CacheFileListResponse, summary="List all files in HF cache", description="Returns a JSON array of all cached file paths relative to the root of the Hugging Face cache directory.", responses={ 200: {"description": "A list of all files in the cache."}, 404: {"description": "The Hugging Face cache directory was not found."}, }, ) def list_cache_files( backup_service: BackupService = Depends(get_backup_service) ): try: files = backup_service.list_cache_files() return CacheFileListResponse(files=files) except FileNotFoundError as e: logger.warning(f"List cache failed: {e}") raise HTTPException(status_code=404, detail=str(e)) @router.get( "/download-log", summary="Download training logs", description="Serves the `training_logs.pkl` file, which contains a history of all structured logs from training runs, directly for download.", response_class=FileResponse, responses={ 200: { "description": "The log file is successfully streamed as a download.", "content": {"application/octet-stream": {}}, }, 404: {"description": "The log file `training_logs.pkl` was not found in the cache."}, }, ) def download_log(backup_service: BackupService = Depends(get_backup_service)): try: log_file_path = backup_service.get_log_file_for_download() return FileResponse( path=log_file_path, filename="training_logs.pkl", media_type="application/octet-stream", ) except FileNotFoundError as e: logger.warning(f"Log file download failed: {e}") raise HTTPException(status_code=404, detail=str(e)) @router.get( "/download-cache", summary="Download HF cache as ZIP", description="Creates a ZIP archive of the entire Hugging Face cache directory on the fly and serves it for download. This can be useful for creating a full backup of all downloaded models and datasets. **Warning**: This may be a slow and resource-intensive operation if the cache is large.", response_class=FileResponse, responses={ 200: { "description": "The ZIP archive is successfully streamed as a download.", "content": {"application/zip": {}}, }, 404: {"description": "The Hugging Face cache directory was not found."}, 500: {"description": "An error occurred during the ZIP archive creation."}, }, ) def download_cache(backup_service: BackupService = Depends(get_backup_service)): try: archive_path = backup_service.get_cache_archive_for_download() return FileResponse( path=archive_path, filename="hf_cache_backup.zip", media_type="application/zip", ) except FileNotFoundError as e: raise HTTPException(status_code=404, detail=str(e)) except Exception as e: logger.error(f"Cache archiving failed: {e}", exc_info=True) raise HTTPException( status_code=500, detail="Failed to create cache archive.") @router.post( "/upload-cache", response_model=BackupUploadResponse, summary="Upload HF cache to Model repo", description=( "Uploads the entire contents of the local Hugging Face cache directory to a designated subfolder (`cache_backup/`) in a Hugging Face Model repository. This is useful for persisting a cache between different environments. " "**Requires environment variables**: `HF_TOKEN` (with write access) and `MODEL_REPO_ID` must be set." ), responses={ 200: {"description": "The cache was uploaded successfully."}, 400: {"description": "A required environment variable (`HF_TOKEN` or `MODEL_REPO_ID`) is missing, or another client-side error occurred."}, 500: {"description": "The upload process failed due to an API error or other issue. Check logs for details."}, }, ) def upload_cache_to_hub( backup_service: BackupService = Depends(get_backup_service) ): try: repo_id = backup_service.upload_cache_to_hub() return BackupUploadResponse( status="Cache uploaded successfully", repo=repo_id ) except ValueError as e: raise HTTPException(status_code=400, detail=str(e)) except Exception as e: logger.error(f"Cache upload to Hub failed: {e}", exc_info=True) raise HTTPException(status_code=500, detail=f"Upload failed: {e}")