Source code for gobstopper.sessions.storage
"""
Session storage backends for Gobstopper
"""
import abc
import os
import pickle
import time
from pathlib import Path
from typing import Optional, Dict, Any, Awaitable
SESSION_EXPIRATION_TIME = 3600 * 24 * 7 # 1 week
[docs]
class BaseSessionStorage(abc.ABC):
"""Abstract base class for session storage."""
[docs]
@abc.abstractmethod
def load(self, session_id: str) -> Optional[Dict[str, Any]]:
"""Load a session from storage."""
raise NotImplementedError
[docs]
@abc.abstractmethod
def save(self, session_id: str, data: Dict[str, Any]):
"""Save a session to storage."""
raise NotImplementedError
[docs]
@abc.abstractmethod
def delete(self, session_id: str):
"""Delete a session from storage."""
raise NotImplementedError
[docs]
@abc.abstractmethod
def cleanup(self):
"""Clean up expired sessions."""
raise NotImplementedError
[docs]
class AsyncBaseSessionStorage(abc.ABC):
[docs]
@abc.abstractmethod
async def load(self, session_id: str) -> Optional[Dict[str, Any]]: ...
[docs]
@abc.abstractmethod
async def save(self, session_id: str, data: Dict[str, Any]) -> None: ...
[docs]
@abc.abstractmethod
async def delete(self, session_id: str) -> None: ...
[docs]
async def cleanup(self) -> None: ...
# Utility for middleware
[docs]
async def maybe_await(result):
if hasattr(result, "__await__"):
return await result
return result
[docs]
class FileSessionStorage(BaseSessionStorage):
"""File-based session storage."""
[docs]
def __init__(self, directory: Path):
self.directory = directory
if not self.directory.exists():
self.directory.mkdir(parents=True, exist_ok=True)
[docs]
def load(self, session_id: str) -> Optional[Dict[str, Any]]:
"""Load session data from a file."""
filepath = self.directory / session_id
if not filepath.exists():
return None
try:
with open(filepath, 'rb') as f:
session_data = pickle.load(f)
if session_data.get('expires_at', 0) < time.time():
self.delete(session_id)
return None
return session_data.get('data')
except (pickle.UnpicklingError, FileNotFoundError):
return None
[docs]
def save(self, session_id: str, data: Dict[str, Any]):
"""Save session data to a file."""
filepath = self.directory / session_id
session_data = {
'data': data,
'expires_at': time.time() + SESSION_EXPIRATION_TIME
}
with open(filepath, 'wb') as f:
pickle.dump(session_data, f)
[docs]
def delete(self, session_id: str):
"""Delete a session file."""
filepath = self.directory / session_id
if filepath.exists():
filepath.unlink()
[docs]
def cleanup(self):
"""Remove expired session files."""
now = time.time()
for filepath in self.directory.iterdir():
try:
with open(filepath, 'rb') as f:
session_data = pickle.load(f)
if session_data.get('expires_at', 0) < now:
filepath.unlink()
except (pickle.UnpicklingError, FileNotFoundError):
# Ignore corrupted or missing files
pass