""" FastAPI Dependencies Module """ import logging from asyncio import Event, Lock, Queue from typing import Any, Dict, List, Set from api_utils.context_types import QueueItem def get_logger() -> logging.Logger: from api_utils.server_state import state return state.logger def get_log_ws_manager(): from api_utils.server_state import state return state.log_ws_manager def get_request_queue() -> "Queue[QueueItem]": from typing import cast from api_utils.server_state import state return cast("Queue[QueueItem]", state.request_queue) def get_processing_lock() -> Lock: from typing import cast from api_utils.server_state import state return cast(Lock, state.processing_lock) def get_worker_task(): from api_utils.server_state import state return state.worker_task def get_server_state() -> Dict[str, Any]: from api_utils.server_state import state # Return immutable snapshot to prevent downstream modifications to global references return dict( is_initializing=state.is_initializing, is_playwright_ready=state.is_playwright_ready, is_browser_connected=state.is_browser_connected, is_page_ready=state.is_page_ready, ) def get_page_instance(): from api_utils.server_state import state return state.page_instance def get_model_list_fetch_event() -> Event: from typing import cast from api_utils.server_state import state return cast(Event, state.model_list_fetch_event) def get_parsed_model_list() -> List[Dict[str, Any]]: from api_utils.server_state import state return state.parsed_model_list def get_excluded_model_ids() -> Set[str]: from api_utils.server_state import state return state.excluded_model_ids def get_current_ai_studio_model_id() -> str: from typing import cast from api_utils.server_state import state return cast(str, state.current_ai_studio_model_id) async def ensure_request_lock(): """ Dependency that acts as a 'Parking Lot' for requests. If Auth Rotation is in progress (Lock is cleared) or Quota is Exceeded (Rotation imminent), this will pause the request until the system is ready. """ import asyncio import time from api_utils.server_state import state as server_state from config.global_state import GlobalState logger = server_state.logger # A request is considered "queued" if it has to wait for the lock. is_waiting = ( GlobalState.IS_QUOTA_EXCEEDED or not GlobalState.AUTH_ROTATION_LOCK.is_set() ) if is_waiting: GlobalState.queued_request_count += 1 start_time = time.time() max_total_wait = 60.0 # 60 second hard timeout for request parking try: # Wait loop to handle both Lock and Quota states # We wait if: # 1. Lock is NOT set (Rotation in progress) # 2. Quota IS exceeded (Rotation about to start, or we need to wait for it) while ( GlobalState.IS_QUOTA_EXCEEDED or not GlobalState.AUTH_ROTATION_LOCK.is_set() ): # Check for total timeout if time.time() - start_time > max_total_wait: logger.error( f"🚨 Request parking timeout after {max_total_wait}s. Quota={GlobalState.IS_QUOTA_EXCEEDED}, LockSet={GlobalState.AUTH_ROTATION_LOCK.is_set()}" ) from fastapi import HTTPException raise HTTPException( status_code=530, # Custom code for state resolution timeout detail="System state resolution timeout - please try again later", ) if not GlobalState.AUTH_ROTATION_LOCK.is_set(): # Rotation in progress. Wait for lock to open with timeout. try: await asyncio.wait_for( GlobalState.AUTH_ROTATION_LOCK.wait(), timeout=30.0 ) except asyncio.TimeoutError: logger.warning( "🚨 Lock wait timeout after 30s. Service may be unavailable." ) from fastapi import HTTPException raise HTTPException( status_code=503, detail="Service temporarily unavailable - timeout waiting for system lock", ) else: # Lock is Open, but Quota is still marked Exceeded. # This implies the Watchdog is about to rotate, or we are in a race. # We wait for the recovery event which signals rotation completion. try: if GlobalState.IS_RECOVERING: # If recovery is active, wait for it to finish await asyncio.wait_for( GlobalState.RECOVERY_EVENT.wait(), timeout=30.0 ) else: # Watchdog hasn't started yet, wait briefly await asyncio.sleep(0.1) except asyncio.TimeoutError: await asyncio.sleep(0.1) finally: if is_waiting: GlobalState.queued_request_count -= 1