50 lines
1.5 KiB
Python
50 lines
1.5 KiB
Python
# ai_mouse/server/deps.py
|
|
"""Shared dependencies for the ai-mouse server package."""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from dataclasses import dataclass, field
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Data directory
|
|
# ---------------------------------------------------------------------------
|
|
_HERE = Path(__file__).resolve().parent
|
|
_DATA_DIR = _HERE.parent.parent / "data"
|
|
|
|
|
|
def get_data_dir() -> Path:
|
|
"""Return the resolved data directory (sites/ai_mouse/data/)."""
|
|
return _DATA_DIR
|
|
|
|
|
|
def validate_path(path: Path, base: Path) -> Path:
|
|
"""Resolve *path* and ensure it lives under *base*. Raises ValueError on traversal."""
|
|
resolved = path.resolve()
|
|
base_resolved = base.resolve()
|
|
if not str(resolved).startswith(str(base_resolved)):
|
|
raise ValueError(f"Path traversal detected: {path}")
|
|
return resolved
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Session state
|
|
# ---------------------------------------------------------------------------
|
|
@dataclass
|
|
class SessionState:
|
|
"""Mutable singleton holding collectors initialised at runtime."""
|
|
|
|
collector: Optional[object] = field(default=None)
|
|
scroll_collector: Optional[object] = field(default=None)
|
|
|
|
|
|
_state = SessionState()
|
|
|
|
|
|
def get_state() -> SessionState:
|
|
"""Return the module-level session state singleton."""
|
|
return _state
|