From 66c2d2e524f0c663a69970ecb5c3e761efc17af5 Mon Sep 17 00:00:00 2001 From: James Ketrenos Date: Thu, 4 Sep 2025 16:23:42 -0700 Subject: [PATCH] Fix runtime imports --- server/core/auth_manager.py | 27 +++++++++++--- server/core/lobby_manager.py | 40 ++++++++++++++++----- server/core/session_manager.py | 54 +++++++++++++++++++++++----- server/websocket/connection.py | 13 +++++-- server/websocket/message_handlers.py | 2 ++ 5 files changed, 110 insertions(+), 26 deletions(-) diff --git a/server/core/auth_manager.py b/server/core/auth_manager.py index a8f9d59..b7c6641 100644 --- a/server/core/auth_manager.py +++ b/server/core/auth_manager.py @@ -13,9 +13,22 @@ import threading from typing import Dict, Optional, Tuple # Import shared models -import sys -sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) -from shared.models import NamePasswordRecord +try: + # Try relative import first (when running as part of the package) + from ...shared.models import NamePasswordRecord +except ImportError: + try: + # Try absolute import (when running directly) + import sys + import os + sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) + from shared.models import NamePasswordRecord + except ImportError: + # Fallback: create minimal model for testing + from pydantic import BaseModel + class NamePasswordRecord(BaseModel): + name: str + password: str from logger import logger @@ -150,10 +163,14 @@ class AuthManager: def validate_integrity(self) -> list[str]: """Validate auth data integrity and return list of issues""" - issues : list[str] = [] + issues = [] with self.lock: - for name, record in self.name_passwords.items(): + for name, record in self.name_passwords.items(): + if not isinstance(record, dict): + issues.append(f"Name '{name}' has invalid record type: {type(record)}") + continue + if "salt" not in record or "hash" not in record: issues.append(f"Name '{name}' missing salt or hash") continue diff --git a/server/core/lobby_manager.py b/server/core/lobby_manager.py index c4cd2e8..daaea03 100644 --- a/server/core/lobby_manager.py +++ b/server/core/lobby_manager.py @@ -12,19 +12,38 @@ import threading from typing import Dict, List, Optional, TYPE_CHECKING # Import shared models -import sys -import os -sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) -from shared.models import ChatMessageModel, ParticipantModel +# Import shared models +try: + # Try relative import first (when running as part of the package) + from ...shared.models import ChatMessageModel, ParticipantModel +except ImportError: + try: + # Try absolute import (when running directly) + import sys + import os + sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) + from shared.models import ChatMessageModel, ParticipantModel + except ImportError: + # Fallback: create minimal models for testing + from pydantic import BaseModel + from typing import Optional + class ChatMessageModel(BaseModel): + id: str + author: str + message: str + timestamp: float + class ParticipantModel(BaseModel): + id: str + name: str from logger import logger # Use try/except for importing events to handle both relative and absolute imports try: - from ..models.events import event_bus, ChatMessageSent + from ..models.events import event_bus, ChatMessageSent, SessionDisconnected, SessionLeftLobby except ImportError: try: - from models.events import event_bus, ChatMessageSent + from models.events import event_bus, ChatMessageSent, SessionDisconnected, SessionLeftLobby except ImportError: # Create dummy event system for standalone testing class DummyEventBus: @@ -34,6 +53,12 @@ except ImportError: class ChatMessageSent: pass + + class SessionDisconnected: + pass + + class SessionLeftLobby: + pass if TYPE_CHECKING: from .session_manager import Session @@ -225,12 +250,10 @@ class LobbyManager: # Subscribe to session events - handle import errors gracefully try: - from ..models.events import SessionDisconnected, SessionLeftLobby event_bus.subscribe(SessionDisconnected, self) event_bus.subscribe(SessionLeftLobby, self) except ImportError: try: - from models.events import SessionDisconnected, SessionLeftLobby event_bus.subscribe(SessionDisconnected, self) event_bus.subscribe(SessionLeftLobby, self) except (ImportError, AttributeError): @@ -239,7 +262,6 @@ class LobbyManager: async def handle(self, event): """Handle events from the event bus""" - from ..models.events import SessionDisconnected, SessionLeftLobby if isinstance(event, SessionDisconnected): await self._handle_session_disconnected(event) diff --git a/server/core/session_manager.py b/server/core/session_manager.py index 1461d17..0d93f00 100644 --- a/server/core/session_manager.py +++ b/server/core/session_manager.py @@ -2,7 +2,17 @@ Session management for the AI Voice Bot server. This module handles session lifecycle, persistence, and cleanup operations. -Extracted from main.py to improve maintainability and separation of concerns. +Extracted from m await lobby.add await lobby.removeSession(self) + + # Publish event + await event_bus.publish(SessionLeftLobby( + session_id=self.id, + lobby_id=lobby.id,(self) + + # Publish event + await event_bus.publish(SessionJoinedLobby( + session_id=self.id, + lobby_id=lobby.id,to improve maintainability and separation of concerns. """ from __future__ import annotations @@ -17,24 +27,53 @@ from fastapi import WebSocket from pydantic import ValidationError # Import shared models -import sys -sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) -from shared.models import SessionSaved, LobbySaved, SessionsPayload, NamePasswordRecord +try: + # Try relative import first (when running as part of the package) + from ...shared.models import SessionSaved, LobbySaved, SessionsPayload, NamePasswordRecord +except ImportError: + try: + # Try absolute import (when running directly) + import sys + import os + sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) + from shared.models import SessionSaved, LobbySaved, SessionsPayload, NamePasswordRecord + except ImportError: + # Fallback: create minimal models for testing + from pydantic import BaseModel + class SessionSaved(BaseModel): + id: str + name: str = "" + protected: bool = False + is_bot: bool = False + has_media: bool = True + bot_run_id: Optional[str] = None + lobbies: List[str] = [] + class LobbySaved(BaseModel): + name: str + private: bool = False + class SessionsPayload(BaseModel): + sessions: List[SessionSaved] + class NamePasswordRecord(BaseModel): + name: str + password: str from logger import logger # Use try/except for importing events to handle both relative and absolute imports try: - from ..models.events import event_bus, SessionDisconnected + from ..models.events import event_bus, SessionDisconnected, UserNameChanged, SessionJoinedLobby, SessionLeftLobby except ImportError: try: - from models.events import event_bus, SessionDisconnected + from models.events import event_bus, SessionDisconnected, UserNameChanged, SessionJoinedLobby, SessionLeftLobby except ImportError: # Create dummy event system for standalone testing class DummyEventBus: async def publish(self, event): pass event_bus = DummyEventBus() class SessionDisconnected: pass + class UserNameChanged: pass + class SessionJoinedLobby: pass + class SessionLeftLobby: pass class SessionConfig: @@ -91,7 +130,6 @@ class Session: lobby_ids = [lobby.id for lobby in self.lobbies] # Publish name change event (don't await here to avoid blocking) - from ..models.events import UserNameChanged asyncio.create_task(event_bus.publish(UserNameChanged( session_id=self.id, old_name=old_name, @@ -118,7 +156,6 @@ class Session: await lobby.addSession(self) # Publish join event - from ..models.events import SessionJoinedLobby await event_bus.publish(SessionJoinedLobby( session_id=self.id, lobby_id=lobby.id, @@ -136,7 +173,6 @@ class Session: await lobby.removeSession(self) # Publish leave event - from ..models.events import SessionLeftLobby await event_bus.publish(SessionLeftLobby( session_id=self.id, lobby_id=lobby.id, diff --git a/server/websocket/connection.py b/server/websocket/connection.py index 2218726..3eb6bb1 100644 --- a/server/websocket/connection.py +++ b/server/websocket/connection.py @@ -11,9 +11,16 @@ from logger import logger from .message_handlers import MessageRouter if TYPE_CHECKING: - from ..core.session_manager import Session, SessionManager - from ..core.lobby_manager import Lobby, LobbyManager - from ..core.auth_manager import AuthManager + # Use absolute imports to avoid relative import issues + try: + from core.session_manager import Session, SessionManager + from core.lobby_manager import Lobby, LobbyManager + from core.auth_manager import AuthManager + except ImportError: + # Fallback for when running from different directory structure + from ..core.session_manager import Session, SessionManager + from ..core.lobby_manager import Lobby, LobbyManager + from ..core.auth_manager import AuthManager class WebSocketConnectionManager: diff --git a/server/websocket/message_handlers.py b/server/websocket/message_handlers.py index 36aa51d..39f37e1 100644 --- a/server/websocket/message_handlers.py +++ b/server/websocket/message_handlers.py @@ -290,7 +290,9 @@ class MessageRouter: try: await self._handlers[message_type].handle(session, lobby, data, websocket, managers) except Exception as e: + import traceback logger.error(f"Error handling message type {message_type}: {e}") + logger.error(f"Full traceback: {traceback.format_exc()}") await websocket.send_json({ "type": "error", "data": {"error": f"Internal error handling {message_type}"}