IDAgentsFreshTest / user_session_manager.py
IDAgents Developer
Implement per-user session isolation system - Each authenticated user now has isolated workspace with separate chat histories and agent data
d952de8
raw
history blame
6.65 kB
"""
User Session Manager for ID Agents
===================================
Manages isolated user sessions so each authenticated user has their own
chat histories, agents, and application state. This prevents users from
seeing or interfering with each other's work.
Usage:
from user_session_manager import session_manager
# In a Gradio function with request parameter:
def my_function(user_input, request: gr.Request):
username = request.username
# Get user-specific data
user_data = session_manager.get_user_data(username, "my_key", default_value=[])
# Update user-specific data
session_manager.set_user_data(username, "my_key", new_value)
"""
import threading
from typing import Dict, Any, Optional
from collections import defaultdict
import json
import logging
logger = logging.getLogger(__name__)
class UserSessionManager:
"""
Thread-safe manager for user-specific session data.
Each authenticated user gets their own isolated storage space.
"""
def __init__(self):
self._sessions: Dict[str, Dict[str, Any]] = defaultdict(dict)
self._lock = threading.Lock()
logger.info("UserSessionManager initialized")
def get_user_data(self, username: str, key: str, default: Any = None) -> Any:
"""
Get user-specific data by key.
Args:
username: The authenticated username
key: The data key (e.g., 'chat_history', 'agents', etc.)
default: Default value if key doesn't exist
Returns:
The stored value or default
"""
with self._lock:
if username not in self._sessions:
self._sessions[username] = {}
return self._sessions[username].get(key, default)
def set_user_data(self, username: str, key: str, value: Any) -> None:
"""
Set user-specific data.
Args:
username: The authenticated username
key: The data key
value: The value to store
"""
with self._lock:
if username not in self._sessions:
self._sessions[username] = {}
self._sessions[username][key] = value
logger.debug(f"Set {key} for user {username}")
def update_user_data(self, username: str, key: str, update_func: callable) -> Any:
"""
Atomically update user-specific data using a function.
Args:
username: The authenticated username
key: The data key
update_func: Function that takes current value and returns new value
Returns:
The updated value
"""
with self._lock:
if username not in self._sessions:
self._sessions[username] = {}
current = self._sessions[username].get(key)
updated = update_func(current)
self._sessions[username][key] = updated
return updated
def clear_user_data(self, username: str, key: Optional[str] = None) -> None:
"""
Clear user-specific data.
Args:
username: The authenticated username
key: Specific key to clear, or None to clear all user data
"""
with self._lock:
if username not in self._sessions:
return
if key is None:
# Clear all data for this user
self._sessions[username].clear()
logger.info(f"Cleared all data for user {username}")
else:
# Clear specific key
if key in self._sessions[username]:
del self._sessions[username][key]
logger.debug(f"Cleared {key} for user {username}")
def get_all_user_keys(self, username: str) -> list:
"""Get all keys stored for a user."""
with self._lock:
if username not in self._sessions:
return []
return list(self._sessions[username].keys())
def user_exists(self, username: str) -> bool:
"""Check if user has any session data."""
with self._lock:
return username in self._sessions and bool(self._sessions[username])
def get_active_users(self) -> list:
"""Get list of users with active sessions."""
with self._lock:
return [u for u, data in self._sessions.items() if data]
def get_user_stats(self, username: str) -> Dict[str, Any]:
"""Get statistics about user's session."""
with self._lock:
if username not in self._sessions:
return {"exists": False}
data = self._sessions[username]
return {
"exists": True,
"keys": list(data.keys()),
"data_size": len(str(data))
}
# Global singleton instance
session_manager = UserSessionManager()
def get_username_from_request(request: Any) -> str:
"""
Extract username from Gradio request object.
Args:
request: Gradio gr.Request object
Returns:
Username string, or "anonymous" if not authenticated
"""
if request is None:
return "anonymous"
# Gradio stores username in request.username after basic auth
username = getattr(request, "username", None)
if username is None or username == "":
return "anonymous"
return str(username)
# Session data keys (constants for consistency)
class SessionKeys:
"""Constants for session data keys."""
SIMPLE_CHAT_HISTORY = "simple_chat_history"
BUILDER_CHAT_HISTORIES = "builder_chat_histories"
DEPLOYED_CHAT_HISTORIES = "deployed_chat_histories"
ACTIVE_CHILDREN = "active_children"
PATIENT_DATA = "patient_data"
AGENT_OUTPUT = "agent_output"
PREFILL_FLAG = "prefill_flag"
if __name__ == "__main__":
# Simple test
print("Testing UserSessionManager...")
# Test basic operations
session_manager.set_user_data("user1", "chat_history", [["Hi", "Hello"]])
session_manager.set_user_data("user2", "chat_history", [["Hey", "Hi there"]])
print("User1 chat:", session_manager.get_user_data("user1", "chat_history"))
print("User2 chat:", session_manager.get_user_data("user2", "chat_history"))
print("Active users:", session_manager.get_active_users())
print("User1 stats:", session_manager.get_user_stats("user1"))
print("✅ UserSessionManager test complete")