AIstudioProxyAPI / api_utils /server_state.py
peijun1's picture
Deploy AI Studio Proxy API to Hugging Face Spaces
a5784e9
Raw
History Blame Contribute Delete
4.37 kB
"""
Centralized server state module.
This module contains all shared state variables that were previously in server.py.
It has NO imports from project modules (only stdlib), making it safe to import
at module level anywhere in the codebase without circular dependency issues.
Usage:
from api_utils.server_state import state
# Access state attributes
page = state.page_instance
state.current_ai_studio_model_id = "new-model"
"""
import asyncio
import logging
import multiprocessing
from asyncio import Event, Lock, Queue, Task
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Set
if TYPE_CHECKING:
from playwright.async_api import (
Browser as AsyncBrowser,
)
from playwright.async_api import (
Page as AsyncPage,
)
from playwright.async_api import (
Playwright as AsyncPlaywright,
)
from api_utils.context_types import QueueItem
from models.logging import WebSocketConnectionManager
class ServerState:
"""
Centralized container for all server state.
This class holds all mutable state that needs to be shared across modules.
Using a class allows for better organization and easier testing (state can be reset).
"""
def __init__(self) -> None:
"""Initialize all state variables with default values."""
self.reset()
def reset(self) -> None:
"""Reset all state to initial values. Useful for testing."""
# --- Stream Queue ---
self.STREAM_QUEUE: Optional[multiprocessing.Queue] = None
self.STREAM_PROCESS: Optional[multiprocessing.Process] = None
# --- Playwright/Browser State ---
self.playwright_manager: Optional["AsyncPlaywright"] = None
self.browser_instance: Optional["AsyncBrowser"] = None
self.page_instance: Optional["AsyncPage"] = None
self.is_playwright_ready: bool = False
self.is_browser_connected: bool = False
self.is_page_ready: bool = False
self.is_initializing: bool = False
# --- Proxy Configuration ---
self.PLAYWRIGHT_PROXY_SETTINGS: Optional[Dict[str, str]] = None
# --- Model State ---
self.global_model_list_raw_json: Optional[str] = None
self.parsed_model_list: List[Dict[str, Any]] = []
self.model_list_fetch_event: Event = asyncio.Event()
self.current_ai_studio_model_id: Optional[str] = None
self.current_auth_profile_path: Optional[str] = None
self.model_switching_lock: Lock = Lock()
self.excluded_model_ids: Set[str] = set()
# --- Request Processing State ---
self.request_queue: "Optional[Queue[QueueItem]]" = None
self.processing_lock: Optional[Lock] = None
self.worker_task: "Optional[Task[None]]" = None
# --- Parameter Cache ---
self.page_params_cache: Dict[str, Any] = {}
self.params_cache_lock: Lock = Lock()
# --- Debug Logging State ---
self.console_logs: List[Dict[str, Any]] = []
self.network_log: Dict[str, List[Dict[str, Any]]] = {
"requests": [],
"responses": [],
}
# --- Logging ---
self.logger: logging.Logger = logging.getLogger("AIStudioProxyServer")
self.log_ws_manager: Optional["WebSocketConnectionManager"] = None
# --- Control Flags ---
self.should_exit: bool = False
self.quota_watchdog: Optional[Callable] = None
def clear_debug_logs(self) -> None:
"""Clear console and network logs (called after each request)."""
self.console_logs = []
self.network_log = {"requests": [], "responses": []}
# Global singleton instance
state = ServerState()
# Convenience exports for backward compatibility
# These allow direct attribute access like: from server_state import page_instance
# But the recommended way is: from api_utils.server_state import state; state.page_instance
def __getattr__(name: str) -> Any:
"""
Module-level attribute access for backward compatibility.
Allows:
from server_state import page_instance
Instead of:
from api_utils.server_state import state
page_instance = state.page_instance
"""
if hasattr(state, name):
return getattr(state, name)
raise AttributeError(f"module 'server_state' has no attribute '{name}'")