Source code for debug_toolbar.core.storage

"""Storage backend for toolbar request history using LRU cache."""

from __future__ import annotations

import json
import threading
from collections import OrderedDict
from pathlib import Path
from typing import TYPE_CHECKING, Any
from uuid import UUID

if TYPE_CHECKING:
    from debug_toolbar.core.context import RequestContext


[docs] class ToolbarStorage: """Thread-safe LRU storage for toolbar request history. This storage maintains a bounded history of request data, automatically evicting the oldest entries when the maximum size is reached. Attributes: max_size: Maximum number of requests to store. """ __slots__ = ("_lock", "_store", "max_size")
[docs] def __init__(self, max_size: int = 50) -> None: """Initialize the storage. Args: max_size: Maximum number of requests to store. Defaults to 50. """ self._store: OrderedDict[UUID, dict[str, Any]] = OrderedDict() self._lock = threading.Lock() self.max_size = max_size
[docs] def store(self, request_id: UUID, data: dict[str, Any]) -> None: """Store request data. Args: request_id: Unique identifier for the request. data: Dictionary of data to store. """ with self._lock: if request_id in self._store: self._store.move_to_end(request_id) self._store[request_id] = data while len(self._store) > self.max_size: self._store.popitem(last=False)
[docs] def get(self, request_id: UUID) -> dict[str, Any] | None: """Retrieve request data. Args: request_id: Unique identifier for the request. Returns: The stored data, or None if not found. """ with self._lock: return self._store.get(request_id)
[docs] def get_all(self) -> list[tuple[UUID, dict[str, Any]]]: """Get all stored requests. Returns: List of (request_id, data) tuples, newest first. """ with self._lock: return list(reversed(self._store.items()))
[docs] def clear(self) -> None: """Clear all stored requests.""" with self._lock: self._store.clear()
def __len__(self) -> int: """Get the number of stored requests.""" with self._lock: return len(self._store)
[docs] def store_from_context(self, context: RequestContext) -> None: """Store data from a request context. Args: context: The RequestContext to store. """ data = { "panel_data": context.panel_data.copy(), "timing_data": context.timing_data.copy(), "metadata": context.metadata.copy(), } self.store(context.request_id, data)
[docs] class FileToolbarStorage(ToolbarStorage): """File-backed storage for sharing data between processes. Extends ToolbarStorage to persist data to a JSON file, enabling the web app and MCP server to share request history. Attributes: file_path: Path to the JSON storage file. """ __slots__ = ("file_path",)
[docs] def __init__(self, file_path: str | Path, max_size: int = 50) -> None: """Initialize file-backed storage. Args: file_path: Path to the JSON file for persistence. max_size: Maximum number of requests to store. Defaults to 50. """ super().__init__(max_size) self.file_path = Path(file_path) self._load()
def _load(self) -> None: """Load data from file if it exists.""" if self.file_path.exists(): try: with self._lock: data = json.loads(self.file_path.read_text()) self._store.clear() for item in data: request_id = UUID(item["request_id"]) self._store[request_id] = item["data"] except (json.JSONDecodeError, KeyError, ValueError): pass def _save(self) -> None: """Save data to file.""" with self._lock: data = [{"request_id": str(rid), "data": d} for rid, d in self._store.items()] self.file_path.parent.mkdir(parents=True, exist_ok=True) self.file_path.write_text(json.dumps(data, default=str))
[docs] def store(self, request_id: UUID, data: dict[str, Any]) -> None: """Store request data and persist to file.""" super().store(request_id, data) self._save()
[docs] def clear(self) -> None: """Clear all stored requests and the file.""" super().clear() if self.file_path.exists(): self.file_path.unlink()
[docs] def reload(self) -> None: """Reload data from file (useful for MCP server to get fresh data).""" self._load()
[docs] def get(self, request_id: UUID) -> dict[str, Any] | None: """Retrieve request data, reloading from file first.""" self._load() return super().get(request_id)
[docs] def get_all(self) -> list[tuple[UUID, dict[str, Any]]]: """Get all stored requests, reloading from file first.""" self._load() return super().get_all()