litestar-debug-toolbar 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- debug_toolbar/__init__.py +35 -0
- debug_toolbar/core/__init__.py +19 -0
- debug_toolbar/core/config.py +71 -0
- debug_toolbar/core/context.py +104 -0
- debug_toolbar/core/panel.py +147 -0
- debug_toolbar/core/panels/__init__.py +31 -0
- debug_toolbar/core/panels/alerts.py +379 -0
- debug_toolbar/core/panels/cache.py +425 -0
- debug_toolbar/core/panels/flamegraph.py +125 -0
- debug_toolbar/core/panels/headers.py +373 -0
- debug_toolbar/core/panels/logging.py +95 -0
- debug_toolbar/core/panels/memory/__init__.py +7 -0
- debug_toolbar/core/panels/memory/base.py +57 -0
- debug_toolbar/core/panels/memory/memray.py +224 -0
- debug_toolbar/core/panels/memory/panel.py +184 -0
- debug_toolbar/core/panels/memory/tracemalloc.py +142 -0
- debug_toolbar/core/panels/profiling.py +389 -0
- debug_toolbar/core/panels/request.py +50 -0
- debug_toolbar/core/panels/response.py +43 -0
- debug_toolbar/core/panels/settings.py +230 -0
- debug_toolbar/core/panels/templates.py +250 -0
- debug_toolbar/core/panels/timer.py +73 -0
- debug_toolbar/core/panels/versions.py +62 -0
- debug_toolbar/core/storage.py +93 -0
- debug_toolbar/core/toolbar.py +221 -0
- debug_toolbar/extras/__init__.py +5 -0
- debug_toolbar/extras/advanced_alchemy/__init__.py +10 -0
- debug_toolbar/extras/advanced_alchemy/panel.py +587 -0
- debug_toolbar/litestar/__init__.py +15 -0
- debug_toolbar/litestar/config.py +84 -0
- debug_toolbar/litestar/middleware.py +373 -0
- debug_toolbar/litestar/panels/__init__.py +8 -0
- debug_toolbar/litestar/panels/events.py +210 -0
- debug_toolbar/litestar/panels/routes.py +41 -0
- debug_toolbar/litestar/plugin.py +96 -0
- debug_toolbar/litestar/routes/__init__.py +7 -0
- debug_toolbar/litestar/routes/handlers.py +2422 -0
- debug_toolbar/py.typed +1 -0
- litestar_debug_toolbar-0.2.0.dist-info/METADATA +325 -0
- litestar_debug_toolbar-0.2.0.dist-info/RECORD +41 -0
- litestar_debug_toolbar-0.2.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Debug Toolbar - Async-native debug toolbar for Python ASGI applications.
|
|
2
|
+
|
|
3
|
+
This package provides a framework-agnostic debug toolbar with optional integrations
|
|
4
|
+
for popular frameworks like Litestar.
|
|
5
|
+
|
|
6
|
+
Basic usage with core components:
|
|
7
|
+
from debug_toolbar import DebugToolbar, DebugToolbarConfig
|
|
8
|
+
|
|
9
|
+
For Litestar integration:
|
|
10
|
+
from debug_toolbar.litestar import DebugToolbarPlugin, LitestarDebugToolbarConfig
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from debug_toolbar.core import (
|
|
16
|
+
DebugToolbar,
|
|
17
|
+
DebugToolbarConfig,
|
|
18
|
+
Panel,
|
|
19
|
+
RequestContext,
|
|
20
|
+
ToolbarStorage,
|
|
21
|
+
get_request_context,
|
|
22
|
+
set_request_context,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"DebugToolbar",
|
|
27
|
+
"DebugToolbarConfig",
|
|
28
|
+
"Panel",
|
|
29
|
+
"RequestContext",
|
|
30
|
+
"ToolbarStorage",
|
|
31
|
+
"get_request_context",
|
|
32
|
+
"set_request_context",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Core debug toolbar components - Framework-agnostic."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from debug_toolbar.core.config import DebugToolbarConfig
|
|
6
|
+
from debug_toolbar.core.context import RequestContext, get_request_context, set_request_context
|
|
7
|
+
from debug_toolbar.core.panel import Panel
|
|
8
|
+
from debug_toolbar.core.storage import ToolbarStorage
|
|
9
|
+
from debug_toolbar.core.toolbar import DebugToolbar
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"DebugToolbar",
|
|
13
|
+
"DebugToolbarConfig",
|
|
14
|
+
"Panel",
|
|
15
|
+
"RequestContext",
|
|
16
|
+
"ToolbarStorage",
|
|
17
|
+
"get_request_context",
|
|
18
|
+
"set_request_context",
|
|
19
|
+
]
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Configuration system for the debug toolbar."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable, Sequence
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import TYPE_CHECKING, Literal
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from debug_toolbar.core.panel import Panel
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class DebugToolbarConfig:
|
|
15
|
+
"""Configuration for the debug toolbar.
|
|
16
|
+
|
|
17
|
+
Attributes:
|
|
18
|
+
enabled: Whether the toolbar is enabled. Defaults to True.
|
|
19
|
+
panels: List of panel classes or import paths to include.
|
|
20
|
+
intercept_redirects: Whether to intercept redirects for debugging.
|
|
21
|
+
show_toolbar_callback: Optional callback to determine if toolbar should be shown.
|
|
22
|
+
insert_before: HTML tag to insert toolbar before. Defaults to "</body>".
|
|
23
|
+
max_request_history: Maximum number of requests to store in history.
|
|
24
|
+
api_path: URL path prefix for toolbar API endpoints.
|
|
25
|
+
static_path: URL path prefix for static assets.
|
|
26
|
+
allowed_hosts: List of allowed hosts. Empty list means all hosts.
|
|
27
|
+
extra_panels: Additional panels to add beyond defaults.
|
|
28
|
+
exclude_panels: Panel names to exclude from defaults.
|
|
29
|
+
memory_backend: Memory profiling backend. "auto" selects best available.
|
|
30
|
+
panel_display_depth: Max depth for nested data rendering. Defaults to 10.
|
|
31
|
+
panel_display_max_items: Max items to show in arrays/objects. Defaults to 100.
|
|
32
|
+
panel_display_max_string: Max string length before truncation. Defaults to 1000.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
enabled: bool = True
|
|
36
|
+
panels: Sequence[str | type[Panel]] = field(
|
|
37
|
+
default_factory=lambda: [
|
|
38
|
+
"debug_toolbar.core.panels.timer.TimerPanel",
|
|
39
|
+
"debug_toolbar.core.panels.request.RequestPanel",
|
|
40
|
+
"debug_toolbar.core.panels.response.ResponsePanel",
|
|
41
|
+
"debug_toolbar.core.panels.logging.LoggingPanel",
|
|
42
|
+
"debug_toolbar.core.panels.versions.VersionsPanel",
|
|
43
|
+
]
|
|
44
|
+
)
|
|
45
|
+
intercept_redirects: bool = False
|
|
46
|
+
show_toolbar_callback: Callable[..., bool] | None = None
|
|
47
|
+
insert_before: str = "</body>"
|
|
48
|
+
max_request_history: int = 50
|
|
49
|
+
api_path: str = "/_debug_toolbar"
|
|
50
|
+
static_path: str = "/_debug_toolbar/static"
|
|
51
|
+
allowed_hosts: Sequence[str] = field(default_factory=list)
|
|
52
|
+
extra_panels: Sequence[str | type[Panel]] = field(default_factory=list)
|
|
53
|
+
exclude_panels: Sequence[str] = field(default_factory=list)
|
|
54
|
+
memory_backend: Literal["tracemalloc", "memray", "auto"] = "auto"
|
|
55
|
+
panel_display_depth: int = 10
|
|
56
|
+
panel_display_max_items: int = 100
|
|
57
|
+
panel_display_max_string: int = 1000
|
|
58
|
+
|
|
59
|
+
def get_all_panels(self) -> list[str | type[Panel]]:
|
|
60
|
+
"""Get all panels including extras, excluding excluded panels."""
|
|
61
|
+
all_panels = list(self.panels) + list(self.extra_panels)
|
|
62
|
+
if not self.exclude_panels:
|
|
63
|
+
return all_panels
|
|
64
|
+
|
|
65
|
+
excluded = set(self.exclude_panels)
|
|
66
|
+
return [
|
|
67
|
+
p
|
|
68
|
+
for p in all_panels
|
|
69
|
+
if (isinstance(p, str) and p.split(".")[-1] not in excluded)
|
|
70
|
+
or (isinstance(p, type) and p.__name__ not in excluded)
|
|
71
|
+
]
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Request context management using contextvars for async-safe data propagation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from contextvars import ContextVar
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import Any
|
|
8
|
+
from uuid import UUID, uuid4
|
|
9
|
+
|
|
10
|
+
_request_context: ContextVar[RequestContext | None] = ContextVar("request_context", default=None)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class RequestContext:
|
|
15
|
+
"""Request-scoped context for debug toolbar data collection.
|
|
16
|
+
|
|
17
|
+
This context is stored in a contextvar and is accessible throughout the
|
|
18
|
+
request lifecycle without passing it explicitly through the call stack.
|
|
19
|
+
|
|
20
|
+
Attributes:
|
|
21
|
+
request_id: Unique identifier for this request.
|
|
22
|
+
panel_data: Dictionary of data collected by panels, keyed by panel_id.
|
|
23
|
+
timing_data: Dictionary of timing measurements.
|
|
24
|
+
metadata: Additional metadata about the request.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
request_id: UUID = field(default_factory=uuid4)
|
|
28
|
+
panel_data: dict[str, dict[str, Any]] = field(default_factory=dict)
|
|
29
|
+
timing_data: dict[str, float] = field(default_factory=dict)
|
|
30
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
31
|
+
|
|
32
|
+
def store_panel_data(self, panel_id: str, key: str, value: Any) -> None:
|
|
33
|
+
"""Store data for a specific panel.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
panel_id: The panel's identifier.
|
|
37
|
+
key: The data key.
|
|
38
|
+
value: The data value.
|
|
39
|
+
"""
|
|
40
|
+
if panel_id not in self.panel_data:
|
|
41
|
+
self.panel_data[panel_id] = {}
|
|
42
|
+
self.panel_data[panel_id][key] = value
|
|
43
|
+
|
|
44
|
+
def get_panel_data(self, panel_id: str) -> dict[str, Any]:
|
|
45
|
+
"""Get all data for a specific panel.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
panel_id: The panel's identifier.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Dictionary of panel data, or empty dict if no data exists.
|
|
52
|
+
"""
|
|
53
|
+
return self.panel_data.get(panel_id, {})
|
|
54
|
+
|
|
55
|
+
def record_timing(self, name: str, duration: float) -> None:
|
|
56
|
+
"""Record a timing measurement.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
name: The name of the timing measurement.
|
|
60
|
+
duration: The duration in seconds.
|
|
61
|
+
"""
|
|
62
|
+
self.timing_data[name] = duration
|
|
63
|
+
|
|
64
|
+
def get_timing(self, name: str) -> float | None:
|
|
65
|
+
"""Get a timing measurement.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
name: The name of the timing measurement.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
The duration in seconds, or None if not recorded.
|
|
72
|
+
"""
|
|
73
|
+
return self.timing_data.get(name)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def get_request_context() -> RequestContext | None:
|
|
77
|
+
"""Get the current request context.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
The current RequestContext, or None if no context is set.
|
|
81
|
+
"""
|
|
82
|
+
return _request_context.get()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def set_request_context(context: RequestContext | None) -> None:
|
|
86
|
+
"""Set the current request context.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
context: The RequestContext to set, or None to clear.
|
|
90
|
+
"""
|
|
91
|
+
_request_context.set(context)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def ensure_request_context() -> RequestContext:
|
|
95
|
+
"""Get or create a request context.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
The current RequestContext, creating a new one if none exists.
|
|
99
|
+
"""
|
|
100
|
+
ctx = get_request_context()
|
|
101
|
+
if ctx is None:
|
|
102
|
+
ctx = RequestContext()
|
|
103
|
+
set_request_context(ctx)
|
|
104
|
+
return ctx
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""Base panel class for debug toolbar panels."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from typing import TYPE_CHECKING, Any, ClassVar
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from debug_toolbar.core.context import RequestContext
|
|
10
|
+
from debug_toolbar.core.toolbar import DebugToolbar
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Panel(ABC):
|
|
14
|
+
"""Abstract base class for debug toolbar panels.
|
|
15
|
+
|
|
16
|
+
Panels are responsible for collecting and displaying specific types of
|
|
17
|
+
debug information. Each panel should override the abstract methods to
|
|
18
|
+
provide its functionality.
|
|
19
|
+
|
|
20
|
+
Class Attributes:
|
|
21
|
+
panel_id: Unique identifier for the panel. Defaults to class name.
|
|
22
|
+
title: Display title shown in the toolbar.
|
|
23
|
+
template: Template name for rendering the panel content.
|
|
24
|
+
has_content: Whether this panel has detailed content.
|
|
25
|
+
nav_title: Short title for the toolbar navigation.
|
|
26
|
+
nav_subtitle: Subtitle shown in the toolbar navigation.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
panel_id: ClassVar[str] = ""
|
|
30
|
+
title: ClassVar[str] = ""
|
|
31
|
+
template: ClassVar[str] = ""
|
|
32
|
+
has_content: ClassVar[bool] = True
|
|
33
|
+
nav_title: ClassVar[str] = ""
|
|
34
|
+
nav_subtitle: ClassVar[str] = ""
|
|
35
|
+
|
|
36
|
+
__slots__ = ("_enabled", "_toolbar")
|
|
37
|
+
|
|
38
|
+
def __init__(self, toolbar: DebugToolbar) -> None:
|
|
39
|
+
"""Initialize the panel.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
toolbar: The parent DebugToolbar instance.
|
|
43
|
+
"""
|
|
44
|
+
self._toolbar = toolbar
|
|
45
|
+
self._enabled = True
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def get_panel_id(cls) -> str:
|
|
49
|
+
"""Get the panel's unique identifier.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
The panel_id class variable, or the class name if not set.
|
|
53
|
+
"""
|
|
54
|
+
return cls.panel_id or cls.__name__
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def enabled(self) -> bool:
|
|
58
|
+
"""Check if the panel is enabled."""
|
|
59
|
+
return self._enabled
|
|
60
|
+
|
|
61
|
+
@enabled.setter
|
|
62
|
+
def enabled(self, value: bool) -> None:
|
|
63
|
+
"""Set whether the panel is enabled."""
|
|
64
|
+
self._enabled = value
|
|
65
|
+
|
|
66
|
+
@abstractmethod
|
|
67
|
+
async def generate_stats(self, context: RequestContext) -> dict[str, Any]:
|
|
68
|
+
"""Generate statistics for this panel.
|
|
69
|
+
|
|
70
|
+
This method is called during request processing to collect data.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
context: The current request context.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Dictionary of statistics data.
|
|
77
|
+
"""
|
|
78
|
+
...
|
|
79
|
+
|
|
80
|
+
async def process_request(self, context: RequestContext) -> None:
|
|
81
|
+
"""Process the request phase.
|
|
82
|
+
|
|
83
|
+
Override this to perform actions at the start of request processing.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
context: The current request context.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
async def process_response(self, context: RequestContext) -> None:
|
|
90
|
+
"""Process the response phase.
|
|
91
|
+
|
|
92
|
+
Override this to perform actions at the end of request processing.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
context: The current request context.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
def get_stats(self, context: RequestContext) -> dict[str, Any]:
|
|
99
|
+
"""Get stored stats from context.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
context: The current request context.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Dictionary of panel stats.
|
|
106
|
+
"""
|
|
107
|
+
return context.get_panel_data(self.get_panel_id())
|
|
108
|
+
|
|
109
|
+
def record_stats(self, context: RequestContext, stats: dict[str, Any]) -> None:
|
|
110
|
+
"""Record stats to the context.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
context: The current request context.
|
|
114
|
+
stats: Dictionary of stats to store.
|
|
115
|
+
"""
|
|
116
|
+
panel_id = self.get_panel_id()
|
|
117
|
+
for key, value in stats.items():
|
|
118
|
+
context.store_panel_data(panel_id, key, value)
|
|
119
|
+
|
|
120
|
+
def generate_server_timing(self, context: RequestContext) -> dict[str, float]:
|
|
121
|
+
"""Generate Server-Timing header data.
|
|
122
|
+
|
|
123
|
+
Override this to contribute to the Server-Timing header.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
context: The current request context.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
Dictionary mapping metric names to durations in seconds.
|
|
130
|
+
"""
|
|
131
|
+
return {}
|
|
132
|
+
|
|
133
|
+
def get_nav_title(self) -> str:
|
|
134
|
+
"""Get the navigation title.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
The nav_title class variable, or title if not set.
|
|
138
|
+
"""
|
|
139
|
+
return self.nav_title or self.title
|
|
140
|
+
|
|
141
|
+
def get_nav_subtitle(self) -> str:
|
|
142
|
+
"""Get the navigation subtitle.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
The nav_subtitle class variable.
|
|
146
|
+
"""
|
|
147
|
+
return self.nav_subtitle
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Built-in panels for the async debug toolbar."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from debug_toolbar.core.panels.alerts import AlertsPanel
|
|
6
|
+
from debug_toolbar.core.panels.cache import CachePanel
|
|
7
|
+
from debug_toolbar.core.panels.headers import HeadersPanel
|
|
8
|
+
from debug_toolbar.core.panels.logging import LoggingPanel
|
|
9
|
+
from debug_toolbar.core.panels.memory import MemoryPanel
|
|
10
|
+
from debug_toolbar.core.panels.profiling import ProfilingPanel
|
|
11
|
+
from debug_toolbar.core.panels.request import RequestPanel
|
|
12
|
+
from debug_toolbar.core.panels.response import ResponsePanel
|
|
13
|
+
from debug_toolbar.core.panels.settings import SettingsPanel
|
|
14
|
+
from debug_toolbar.core.panels.templates import TemplatesPanel
|
|
15
|
+
from debug_toolbar.core.panels.timer import TimerPanel
|
|
16
|
+
from debug_toolbar.core.panels.versions import VersionsPanel
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"AlertsPanel",
|
|
20
|
+
"CachePanel",
|
|
21
|
+
"HeadersPanel",
|
|
22
|
+
"LoggingPanel",
|
|
23
|
+
"MemoryPanel",
|
|
24
|
+
"ProfilingPanel",
|
|
25
|
+
"RequestPanel",
|
|
26
|
+
"ResponsePanel",
|
|
27
|
+
"SettingsPanel",
|
|
28
|
+
"TemplatesPanel",
|
|
29
|
+
"TimerPanel",
|
|
30
|
+
"VersionsPanel",
|
|
31
|
+
]
|