Source code for debug_toolbar.core.toolbar

"""Main debug toolbar manager class."""

from __future__ import annotations

import importlib
import logging
import time
from typing import TYPE_CHECKING, Any

from debug_toolbar.core.config import DebugToolbarConfig
from debug_toolbar.core.context import RequestContext, ensure_request_context, get_request_context, set_request_context
from debug_toolbar.core.storage import ToolbarStorage

logger = logging.getLogger(__name__)

if TYPE_CHECKING:
    from debug_toolbar.core.panel import Panel


[docs] class DebugToolbar: """Main debug toolbar manager. Responsible for: - Panel lifecycle management (create, enable/disable, destroy) - Request context initialization - Data collection orchestration - Toolbar rendering coordination """ __slots__ = ("_config", "_panel_classes", "_panels", "_storage")
[docs] def __init__( self, config: DebugToolbarConfig | None = None, ) -> None: """Initialize the debug toolbar. Args: config: Configuration options. Uses defaults if not provided. """ self._config = config or DebugToolbarConfig() self._storage = ( self._config.storage if self._config.storage is not None else ToolbarStorage(max_size=self._config.max_request_history) ) self._panel_classes: list[type[Panel]] = [] self._panels: list[Panel] = [] if self._config.enabled: self._load_panels()
@property def config(self) -> DebugToolbarConfig: """Get the toolbar configuration.""" return self._config @property def storage(self) -> ToolbarStorage: """Get the toolbar storage.""" return self._storage @property def panels(self) -> list[Panel]: """Get all panel instances.""" return self._panels @property def enabled_panels(self) -> list[Panel]: """Get only enabled panel instances.""" return [p for p in self._panels if p.enabled] def _load_panels(self) -> None: """Load and instantiate panel classes.""" self._panel_classes = [] for panel_spec in self._config.get_all_panels(): if isinstance(panel_spec, str): panel_class = self._import_panel(panel_spec) else: panel_class = panel_spec if panel_class is not None: self._panel_classes.append(panel_class) self._panels = [cls(self) for cls in self._panel_classes] def _import_panel(self, import_path: str) -> type[Panel] | None: """Import a panel class from a dotted path. Args: import_path: Dotted import path like 'module.submodule.ClassName'. Returns: The panel class, or None if import fails. """ try: module_path, class_name = import_path.rsplit(".", 1) module = importlib.import_module(module_path) return getattr(module, class_name) except (ImportError, AttributeError, ValueError) as e: logger.warning("Failed to import panel '%s': %s", import_path, e) return None
[docs] def get_panel(self, panel_id: str) -> Panel | None: """Get a panel by its ID. Args: panel_id: The panel's unique identifier. Returns: The panel instance, or None if not found. """ for panel in self._panels: if panel.get_panel_id() == panel_id: return panel return None
[docs] async def process_request(self) -> RequestContext: """Start processing a request. Creates a new request context and notifies all panels. Returns: The new request context. """ context = ensure_request_context() context.record_timing("request_start", time.perf_counter()) for panel in self.enabled_panels: await panel.process_request(context) return context
[docs] async def process_response(self, context: RequestContext | None = None) -> None: """Finish processing a request. Collects stats from all panels and stores in history. Args: context: The request context. Uses current context if not provided. """ if context is None: context = get_request_context() if context is None: return context.record_timing("request_end", time.perf_counter()) start = context.get_timing("request_start") end = context.get_timing("request_end") if start is not None and end is not None: context.record_timing("total_time", end - start) for panel in self.enabled_panels: stats = await panel.generate_stats(context) panel.record_stats(context, stats) await panel.process_response(context) self._storage.store_from_context(context) set_request_context(None)
[docs] def get_server_timing_header(self, context: RequestContext | None = None) -> str: """Generate Server-Timing header value. Args: context: The request context. Uses current context if not provided. Returns: Server-Timing header value string. """ if context is None: context = get_request_context() if context is None: return "" timings: list[str] = [] total_time = context.get_timing("total_time") if total_time is not None: timings.append(f'total;dur={total_time * 1000:.2f};desc="Total"') for panel in self.enabled_panels: panel_timings = panel.generate_server_timing(context) for name, duration in panel_timings.items(): timings.append(f'{name};dur={duration * 1000:.2f};desc="{panel.title}"') return ", ".join(timings)
[docs] def get_toolbar_data(self, context: RequestContext | None = None) -> dict[str, Any]: """Get all data for rendering the toolbar. Args: context: The request context. Uses current context if not provided. Returns: Dictionary with toolbar data including all panel stats. """ if context is None: context = get_request_context() if context is None: return {} panels_data = [] for panel in self.enabled_panels: panels_data.append( { "panel_id": panel.get_panel_id(), "title": panel.title, "nav_title": panel.get_nav_title(), "nav_subtitle": panel.get_nav_subtitle(), "has_content": panel.has_content, "stats": panel.get_stats(context), } ) return { "request_id": str(context.request_id), "panels": panels_data, "timing": context.timing_data, "metadata": context.metadata, }