Spaces:
Build error
Build error
| """ | |
| Module de serveur API pour le leaderboard parser. | |
| Ce module contient la configuration FastAPI et les endpoints pour le mode serveur. | |
| """ | |
| import datetime | |
| import threading | |
| import logging | |
| import os | |
| from fastapi import FastAPI, HTTPException | |
| from fastapi.responses import JSONResponse, HTMLResponse | |
| from src.file_utils import format_datetime | |
| # Initialiser le logger | |
| logger = logging.getLogger("leaderboard-parser") | |
| # Variables globales pour suivre l'état du serveur | |
| processing_status = "idle" | |
| processing_error = None | |
| last_run_time = None | |
| # Initialiser l'application FastAPI | |
| app = FastAPI(title="Leaderboard Parser API") | |
| # Cette fonction sera importée depuis main.py | |
| process_leaderboards = None | |
| def initialize_server(process_function): | |
| """ | |
| Initialise le serveur avec la fonction de traitement des leaderboards. | |
| Cette fonction doit être appelée avant de démarrer le serveur. | |
| Args: | |
| process_function: Fonction qui traite les leaderboards | |
| """ | |
| global process_leaderboards, last_run_time | |
| process_leaderboards = process_function | |
| # Initialiser last_run_time à la date actuelle | |
| last_run_time = datetime.datetime.now() | |
| logger.info(f"Serveur initialisé avec la fonction de traitement. last_run_time initialisé à {last_run_time.isoformat()}") | |
| # Endpoints API | |
| async def root(): | |
| """Root endpoint serving a simple HTML page with status info""" | |
| global processing_status, last_run_time, processing_error | |
| next_run = format_datetime(last_run_time + datetime.timedelta(hours=int(os.environ.get("LEADERBOARD_REPROCESS_INTERVAL_HOURS", 24)))) if last_run_time else "Not yet scheduled" | |
| last_run = format_datetime(last_run_time) if last_run_time else "Never" | |
| # Calculate timestamp for next run to use in JavaScript | |
| next_run_timestamp = "" | |
| if last_run_time: | |
| next_run_time = last_run_time + datetime.timedelta(hours=int(os.environ.get("LEADERBOARD_REPROCESS_INTERVAL_HOURS", 24))) | |
| next_run_timestamp = next_run_time.timestamp() * 1000 # Convert to milliseconds for JavaScript | |
| html_content = f""" | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <title>Leaderboard Parser Status</title> | |
| <style> | |
| body {{ | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| height: 100vh; | |
| margin: 0; | |
| background-color: #f7f7f7; | |
| }} | |
| .status-box {{ | |
| padding: 30px; | |
| text-align: center; | |
| background-color: white; | |
| border-radius: 12px; | |
| box-shadow: 0 4px 8px rgba(0, 0, 0, 0.05); | |
| max-width: 500px; | |
| }} | |
| h1 {{ | |
| margin-top: 0; | |
| color: #333; | |
| font-weight: 600; | |
| }} | |
| p {{ | |
| margin: 12px 0; | |
| font-size: 16px; | |
| line-height: 1.5; | |
| }} | |
| .status-label {{ | |
| font-weight: bold; | |
| display: inline-block; | |
| min-width: 80px; | |
| }} | |
| .status-running {{ color: #1a73e8; font-weight: bold; }} | |
| .status-idle {{ color: #188038; font-weight: bold; }} | |
| .status-failed {{ color: #d93025; font-weight: bold; }} | |
| .countdown {{ | |
| margin-top: 10px; | |
| padding: 8px; | |
| background-color: #f5f5f5; | |
| border-radius: 6px; | |
| display: inline-block; | |
| min-width: 250px; | |
| }} | |
| </style> | |
| </head> | |
| <body> | |
| <div class="status-box"> | |
| <h1>Leaderboard Parser</h1> | |
| <p><span class="status-label">Status</span> <span class="status-{processing_status}">{processing_status}</span></p> | |
| <p><span class="status-label">Last run</span> {last_run}</p> | |
| {f'<p style="color: #d93025; margin-top: 20px;"><span class="status-label">Error</span> {processing_error}</p>' if processing_error else ''} | |
| <div class="countdown" id="countdown"> | |
| Calculating time remaining... | |
| </div> | |
| </div> | |
| <script> | |
| // Set the date we're counting down to (from server) | |
| const nextRunTimestamp = {next_run_timestamp}; | |
| // Function to format time remaining in a human-readable way | |
| function formatTimeRemaining(distance) {{ | |
| const hours = Math.floor(distance / (1000 * 60 * 60)); | |
| const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)); | |
| const seconds = Math.floor((distance % (1000 * 60)) / 1000); | |
| return `Next run in <b>${{hours}}h ${{minutes}}m ${{seconds}}s</b>`; | |
| }} | |
| // Update the countdown every 1 second | |
| function updateCountdown() {{ | |
| // Get current timestamp | |
| const now = new Date().getTime(); | |
| // If no next run is scheduled | |
| if (!nextRunTimestamp) {{ | |
| document.getElementById("countdown").innerHTML = "No next run scheduled"; | |
| return; | |
| }} | |
| // Find the time remaining | |
| const distance = nextRunTimestamp - now; | |
| // If the countdown is over | |
| if (distance < 0) {{ | |
| document.getElementById("countdown").innerHTML = "Scheduled run in progress or overdue"; | |
| }} else {{ | |
| // Display the result | |
| document.getElementById("countdown").innerHTML = formatTimeRemaining(distance); | |
| }} | |
| }} | |
| // Initial call to display right away | |
| updateCountdown(); | |
| // Set up interval to update every second | |
| setInterval(updateCountdown, 1000); | |
| </script> | |
| </body> | |
| </html> | |
| """ | |
| return HTMLResponse(content=html_content) | |
| async def get_status(): | |
| """Get the current status of the parser""" | |
| global processing_status, last_run_time, processing_error | |
| return { | |
| "status": processing_status, | |
| "last_run": format_datetime(last_run_time) if last_run_time else None, | |
| "next_run": format_datetime(last_run_time + datetime.timedelta(hours=int(os.environ.get("LEADERBOARD_REPROCESS_INTERVAL_HOURS", 24)))) if last_run_time else None, | |
| "error": processing_error | |
| } | |
| async def trigger_run(): | |
| """Manually trigger a leaderboard processing run""" | |
| global processing_status, process_leaderboards | |
| if not process_leaderboards: | |
| raise HTTPException(status_code=500, detail="Server not properly initialized") | |
| if processing_status == "running": | |
| raise HTTPException(status_code=409, detail="Processing is already running") | |
| # Start processing in a separate thread | |
| threading.Thread(target=lambda: process_leaderboards()).start() | |
| return {"status": "started", "message": "Processing started"} | |
| async def health_check(): | |
| """Health check endpoint for HF Spaces to verify the app is running""" | |
| return {"status": "ok"} |