Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions debug_toolbar/_stubs.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
from __future__ import annotations

from typing import Any, NamedTuple, Optional
from typing import Any, NamedTuple, Optional, Protocol

from django import template as dj_template
from django.http import HttpRequest, HttpResponse


class InspectStack(NamedTuple):
Expand All @@ -24,3 +23,7 @@ class RenderContext(dj_template.context.RenderContext):
class RequestContext(dj_template.RequestContext):
template: dj_template.Template
render_context: RenderContext


class GetResponse(Protocol):
def __call__(self, request: HttpRequest) -> HttpResponse: ...
9 changes: 7 additions & 2 deletions debug_toolbar/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,19 @@
sync_to_async,
)
from django.conf import settings
from django.http import HttpRequest
from django.utils.module_loading import import_string

from debug_toolbar import settings as dt_settings
from debug_toolbar.toolbar import DebugToolbar
from debug_toolbar.utils import clear_stack_trace_caches, is_processable_html_response

from ._stubs import GetResponse

def show_toolbar(request):
_HTML_TYPES = ("text/html", "application/xhtml+xml")


def show_toolbar(request: HttpRequest):
"""
Default function to determine whether to show the toolbar on a given page.
"""
Expand Down Expand Up @@ -108,7 +113,7 @@ class DebugToolbarMiddleware:
sync_capable = True
async_capable = True

def __init__(self, get_response):
def __init__(self, get_response: GetResponse):
self.get_response = get_response
# If get_response is a coroutine function, turns us into async mode so
# a thread is not consumed during a whole request.
Expand Down
3 changes: 2 additions & 1 deletion debug_toolbar/panels/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from django.utils.functional import classproperty

from debug_toolbar import settings as dt_settings
from debug_toolbar._stubs import GetResponse
from debug_toolbar.utils import get_name_from_obj


Expand All @@ -13,7 +14,7 @@ class Panel:

is_async = False

def __init__(self, toolbar, get_response):
def __init__(self, toolbar, get_response: GetResponse):
self.toolbar = toolbar
self.get_response = get_response
self.from_store = False
Expand Down
51 changes: 32 additions & 19 deletions debug_toolbar/toolbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,57 +6,67 @@
import re
import uuid
from functools import cache
from typing import TYPE_CHECKING, Any, Optional

from django.apps import apps
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.dispatch import Signal
from django.http import HttpRequest
from django.template import TemplateSyntaxError
from django.template.loader import render_to_string
from django.urls import include, path, re_path, resolve
from django.urls import URLPattern, include, path, re_path, resolve
from django.urls.exceptions import Resolver404
from django.utils.module_loading import import_string
from django.utils.translation import get_language, override as lang_override

from debug_toolbar import APP_NAME, settings as dt_settings
from debug_toolbar.store import get_store

from ._stubs import GetResponse

logger = logging.getLogger(__name__)


if TYPE_CHECKING:
from .panels import Panel


class DebugToolbar:
# for internal testing use only
_created = Signal()
store = None

def __init__(self, request, get_response, request_id=None):
def __init__(
self, request: HttpRequest, get_response: GetResponse, request_id=None
):
self.request = request
self.config = dt_settings.get_config().copy()
panels = []
for panel_class in reversed(self.get_panel_classes()):
for panel_class in reversed(list(self.get_panel_classes())):
panel = panel_class(self, get_response)
panels.append(panel)
if panel.enabled:
get_response = panel.process_request
self.process_request = get_response
self._panels = {panel.panel_id: panel for panel in reversed(panels)}
self.stats = {}
self.server_timing_stats = {}
self._panels = {panel.panel_id: panel for panel in panels} # type: ignore[var-annotated]
self.stats: dict[str, Any] = {}
self.server_timing_stats: dict[str, Any] = {}
self.request_id = request_id
self.init_store()
self._created.send(request, toolbar=self)

# Manage panels

@property
def panels(self):
def panels(self) -> list["Panel"]:
"""
Get a list of all available panels.
"""
return list(self._panels.values())

@property
def enabled_panels(self):
def enabled_panels(self) -> list["Panel"]:
"""
Get a list of panels enabled for the current request.
"""
Expand All @@ -72,15 +82,15 @@ def csp_nonce(self):
"""
return getattr(self.request, "csp_nonce", None)

def get_panel_by_id(self, panel_id):
def get_panel_by_id(self, panel_id: str) -> "Panel":
"""
Get the panel with the given id, which is the class name by default.
"""
return self._panels[panel_id]

# Handle rendering the toolbar in HTML

def render_toolbar(self):
def render_toolbar(self) -> str:
"""
Renders the overall Toolbar with panels inside.
"""
Expand All @@ -101,7 +111,7 @@ def render_toolbar(self):
else:
raise

def should_render_panels(self):
def should_render_panels(self) -> bool:
"""Determine whether the panels should be rendered during the request

If False, the panels will be loaded via Ajax.
Expand All @@ -128,7 +138,7 @@ def fetch(cls, request_id, panel_id=None):
# Manually implement class-level caching of panel classes and url patterns
# because it's more obvious than going through an abstraction.

_panel_classes = None
_panel_classes: Optional[list[type["Panel"]]] = None

@classmethod
def get_panel_classes(cls):
Expand All @@ -140,10 +150,10 @@ def get_panel_classes(cls):
cls._panel_classes = panel_classes
return cls._panel_classes

_urlpatterns = None
_urlpatterns: Optional[list[URLPattern]] = None

@classmethod
def get_urls(cls):
def get_urls(cls) -> list[URLPattern]:
if cls._urlpatterns is None:
from . import views

Expand All @@ -153,13 +163,13 @@ def get_urls(cls):
path("render_panel/", views.render_panel, name="render_panel"),
]
# Per-panel URLs
for panel_class in cls.get_panel_classes():
for panel_class in list(cls.get_panel_classes()):
urlpatterns += panel_class.get_urls()
cls._urlpatterns = urlpatterns
return cls._urlpatterns

@classmethod
def is_toolbar_request(cls, request):
def is_toolbar_request(cls, request) -> bool:
"""
Determine if the request is for a DebugToolbar view.
"""
Expand All @@ -171,7 +181,10 @@ def is_toolbar_request(cls, request):
)
except Resolver404:
return False
return resolver_match.namespaces and resolver_match.namespaces[-1] == APP_NAME
return (
bool(resolver_match.namespaces)
and resolver_match.namespaces[-1] == APP_NAME
)

@staticmethod
@cache
Expand All @@ -185,7 +198,7 @@ def get_observe_request():
return func_or_path


def observe_request(request):
def observe_request(request: HttpRequest):
"""
Determine whether to update the toolbar from a client side request.
"""
Expand Down Expand Up @@ -216,7 +229,7 @@ def from_store(cls, request_id, panel_id=None):
)
toolbar._panels = {}

for panel_class in reversed(cls.get_panel_classes()):
for panel_class in reversed(list(cls.get_panel_classes())):
panel = panel_class(toolbar, from_store_get_response)
if panel_id and panel.panel_id != panel_id:
continue
Expand Down
Loading