From 59cad3aadd5eb3e8efd8d3d648bfebde9fb86fea Mon Sep 17 00:00:00 2001 From: Leandro de Souza Date: Mon, 23 Oct 2023 12:52:40 -0300 Subject: [PATCH 1/4] typing: adds a GetResponse class and type middleware/toolbar modules --- debug_toolbar/_stubs.py | 10 +++++++--- debug_toolbar/middleware.py | 11 +++++++++-- debug_toolbar/panels/__init__.py | 3 ++- debug_toolbar/toolbar.py | 5 ++++- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/debug_toolbar/_stubs.py b/debug_toolbar/_stubs.py index c536a0fe7..730d8bdcd 100644 --- a/debug_toolbar/_stubs.py +++ b/debug_toolbar/_stubs.py @@ -1,8 +1,7 @@ -from __future__ import annotations - -from typing import Any, NamedTuple, Optional +from typing import Any, List, NamedTuple, Optional, Protocol, Tuple from django import template as dj_template +from django.http import HttpRequest, HttpResponse class InspectStack(NamedTuple): @@ -24,3 +23,8 @@ 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: + ... diff --git a/debug_toolbar/middleware.py b/debug_toolbar/middleware.py index 2292bde8b..564167cd6 100644 --- a/debug_toolbar/middleware.py +++ b/debug_toolbar/middleware.py @@ -13,6 +13,7 @@ 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 @@ -20,7 +21,12 @@ from debug_toolbar.utils import clear_stack_trace_caches, is_processable_html_response -def show_toolbar(request): +from ._stubs import GetResponse + +_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. """ @@ -108,7 +114,8 @@ 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. diff --git a/debug_toolbar/panels/__init__.py b/debug_toolbar/panels/__init__.py index a53ba6652..65e82d073 100644 --- a/debug_toolbar/panels/__init__.py +++ b/debug_toolbar/panels/__init__.py @@ -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 @@ -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 diff --git a/debug_toolbar/toolbar.py b/debug_toolbar/toolbar.py index 38f1a3803..0f1297a0b 100644 --- a/debug_toolbar/toolbar.py +++ b/debug_toolbar/toolbar.py @@ -11,6 +11,7 @@ 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 @@ -23,13 +24,15 @@ logger = logging.getLogger(__name__) +from ._stubs import GetResponse + 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): self.request = request self.config = dt_settings.get_config().copy() panels = [] From 521b7e212b181f595d96810a1c8c7988be2969c6 Mon Sep 17 00:00:00 2001 From: Leandro de Souza Date: Mon, 23 Oct 2023 13:11:25 -0300 Subject: [PATCH 2/4] typing: adds type hints to the toolbar module --- debug_toolbar/toolbar.py | 48 ++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/debug_toolbar/toolbar.py b/debug_toolbar/toolbar.py index 0f1297a0b..3c956dde4 100644 --- a/debug_toolbar/toolbar.py +++ b/debug_toolbar/toolbar.py @@ -6,6 +6,9 @@ import re import uuid from functools import cache +from collections import OrderedDict +from functools import lru_cache +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type from django.apps import apps from django.conf import settings @@ -14,7 +17,7 @@ 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, path, resolve, include, re_path 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 @@ -26,13 +29,16 @@ from ._stubs import GetResponse +if TYPE_CHECKING: + from .panels import Panel + class DebugToolbar: # for internal testing use only _created = Signal() store = None - def __init__(self, request: HttpRequest, get_response: GetResponse): + def __init__(self, request: HttpRequest, get_response: GetResponse, request_id=None): self.request = request self.config = dt_settings.get_config().copy() panels = [] @@ -42,9 +48,16 @@ def __init__(self, request: HttpRequest, get_response: GetResponse): 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 = {} + # Use OrderedDict for the _panels attribute so that items can be efficiently + # removed using FIFO order in the DebugToolbar.store() method. The .popitem() + # method of Python's built-in dict only supports LIFO removal. + self._panels = OrderedDict() # type: ignore[var-annotated] + while panels: + panel = panels.pop() + self._panels[panel.panel_id] = panel + self.stats: Dict[str, Any] = {} + self.server_timing_stats: Dict[str, Any] = {} + self.store_id: Optional[str] = None self.request_id = request_id self.init_store() self._created.send(request, toolbar=self) @@ -52,14 +65,14 @@ def __init__(self, request: HttpRequest, get_response: GetResponse): # 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. """ @@ -75,7 +88,7 @@ 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. """ @@ -83,7 +96,7 @@ def get_panel_by_id(self, panel_id): # Handle rendering the toolbar in HTML - def render_toolbar(self): + def render_toolbar(self) -> str: """ Renders the overall Toolbar with panels inside. """ @@ -104,7 +117,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. @@ -131,7 +144,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): @@ -143,10 +156,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 @@ -162,7 +175,7 @@ def get_urls(cls): 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. """ @@ -174,7 +187,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 @@ -188,7 +204,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. """ From e3a53d62ba1c3ab56d69284fb50dc4b17e4b385c Mon Sep 17 00:00:00 2001 From: Johanan Oppong Amoateng Date: Mon, 13 Oct 2025 20:07:54 +0000 Subject: [PATCH 3/4] refactor: optimize panel handling in DebugToolbar initialization --- debug_toolbar/toolbar.py | 42 +++++++++++++++++----------------------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/debug_toolbar/toolbar.py b/debug_toolbar/toolbar.py index 3c956dde4..af5529dbd 100644 --- a/debug_toolbar/toolbar.py +++ b/debug_toolbar/toolbar.py @@ -6,9 +6,7 @@ import re import uuid from functools import cache -from collections import OrderedDict -from functools import lru_cache -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type +from typing import TYPE_CHECKING, Any, Optional from django.apps import apps from django.conf import settings @@ -17,7 +15,7 @@ from django.http import HttpRequest from django.template import TemplateSyntaxError from django.template.loader import render_to_string -from django.urls import URLPattern, path, resolve, include, re_path +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 @@ -25,9 +23,10 @@ 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__) -from ._stubs import GetResponse if TYPE_CHECKING: from .panels import Panel @@ -38,26 +37,21 @@ class DebugToolbar: _created = Signal() store = None - def __init__(self, request: HttpRequest, get_response: GetResponse, 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 - # Use OrderedDict for the _panels attribute so that items can be efficiently - # removed using FIFO order in the DebugToolbar.store() method. The .popitem() - # method of Python's built-in dict only supports LIFO removal. - self._panels = OrderedDict() # type: ignore[var-annotated] - while panels: - panel = panels.pop() - self._panels[panel.panel_id] = panel - self.stats: Dict[str, Any] = {} - self.server_timing_stats: Dict[str, Any] = {} - self.store_id: Optional[str] = None + 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) @@ -65,14 +59,14 @@ def __init__(self, request: HttpRequest, get_response: GetResponse, request_id=N # Manage panels @property - def panels(self) -> List["Panel"]: + def panels(self) -> list["Panel"]: """ Get a list of all available panels. """ return list(self._panels.values()) @property - def enabled_panels(self) -> List["Panel"]: + def enabled_panels(self) -> list["Panel"]: """ Get a list of panels enabled for the current request. """ @@ -144,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: Optional[List[Type["Panel"]]] = None + _panel_classes: Optional[list[type["Panel"]]] = None @classmethod def get_panel_classes(cls): @@ -156,10 +150,10 @@ def get_panel_classes(cls): cls._panel_classes = panel_classes return cls._panel_classes - _urlpatterns: Optional[List[URLPattern]] = None + _urlpatterns: Optional[list[URLPattern]] = None @classmethod - def get_urls(cls) -> List[URLPattern]: + def get_urls(cls) -> list[URLPattern]: if cls._urlpatterns is None: from . import views @@ -169,7 +163,7 @@ def get_urls(cls) -> List[URLPattern]: 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 @@ -235,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 From 92160e35d17f942448a7544cc30520ff90ffc53d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 20:40:50 +0000 Subject: [PATCH 4/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- debug_toolbar/_stubs.py | 5 ++--- debug_toolbar/middleware.py | 2 -- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/debug_toolbar/_stubs.py b/debug_toolbar/_stubs.py index 730d8bdcd..98d5dd33e 100644 --- a/debug_toolbar/_stubs.py +++ b/debug_toolbar/_stubs.py @@ -1,4 +1,4 @@ -from typing import Any, List, NamedTuple, Optional, Protocol, Tuple +from typing import Any, NamedTuple, Optional, Protocol from django import template as dj_template from django.http import HttpRequest, HttpResponse @@ -26,5 +26,4 @@ class RequestContext(dj_template.RequestContext): class GetResponse(Protocol): - def __call__(self, request: HttpRequest) -> HttpResponse: - ... + def __call__(self, request: HttpRequest) -> HttpResponse: ... diff --git a/debug_toolbar/middleware.py b/debug_toolbar/middleware.py index 564167cd6..ffd688665 100644 --- a/debug_toolbar/middleware.py +++ b/debug_toolbar/middleware.py @@ -20,7 +20,6 @@ from debug_toolbar.toolbar import DebugToolbar from debug_toolbar.utils import clear_stack_trace_caches, is_processable_html_response - from ._stubs import GetResponse _HTML_TYPES = ("text/html", "application/xhtml+xml") @@ -114,7 +113,6 @@ class DebugToolbarMiddleware: sync_capable = True async_capable = True - def __init__(self, get_response: GetResponse): self.get_response = get_response # If get_response is a coroutine function, turns us into async mode so