Skip to content

Commit 09cca3f

Browse files
Add type hints to the toolbar and middleware modules
Co-Authored-By: Tim Schilling <[email protected]>
1 parent d7b125b commit 09cca3f

File tree

5 files changed

+75
-35
lines changed

5 files changed

+75
-35
lines changed

debug_toolbar/_stubs.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
from __future__ import annotations
22

3-
from typing import Any, NamedTuple, Optional
3+
from typing import Any, NamedTuple, Optional, Protocol
44

55
from django import template as dj_template
6+
from django.http import HttpRequest, HttpResponse
67

78

89
class InspectStack(NamedTuple):
@@ -24,3 +25,7 @@ class RenderContext(dj_template.context.RenderContext):
2425
class RequestContext(dj_template.RequestContext):
2526
template: dj_template.Template
2627
render_context: RenderContext
28+
29+
30+
class GetResponse(Protocol):
31+
def __call__(self, request: HttpRequest) -> HttpResponse: ...

debug_toolbar/middleware.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import re
66
import socket
77
from functools import cache
8+
from typing import Callable
89

910
from asgiref.sync import (
1011
async_to_sync,
@@ -13,14 +14,19 @@
1314
sync_to_async,
1415
)
1516
from django.conf import settings
17+
from django.http import HttpRequest, HttpResponse
1618
from django.utils.module_loading import import_string
1719

1820
from debug_toolbar import settings as dt_settings
21+
from debug_toolbar._stubs import GetResponse
22+
from debug_toolbar.panels import Panel
1923
from debug_toolbar.toolbar import DebugToolbar
2024
from debug_toolbar.utils import clear_stack_trace_caches, is_processable_html_response
2125

26+
_HTML_TYPES = ("text/html", "application/xhtml+xml")
2227

23-
def show_toolbar(request):
28+
29+
def show_toolbar(request: HttpRequest) -> bool:
2430
"""
2531
Default function to determine whether to show the toolbar on a given page.
2632
"""
@@ -35,7 +41,7 @@ def show_toolbar(request):
3541
return False
3642

3743

38-
def show_toolbar_with_docker(request):
44+
def show_toolbar_with_docker(request: HttpRequest) -> bool:
3945
"""
4046
Default function to determine whether to show the toolbar on a given page.
4147
"""
@@ -67,7 +73,7 @@ def show_toolbar_with_docker(request):
6773

6874

6975
@cache
70-
def show_toolbar_func_or_path():
76+
def show_toolbar_func_or_path() -> Callable:
7177
"""
7278
Fetch the show toolbar callback from settings
7379
@@ -82,7 +88,7 @@ def show_toolbar_func_or_path():
8288
return func_or_path
8389

8490

85-
def get_show_toolbar(async_mode):
91+
def get_show_toolbar(async_mode) -> Callable:
8692
"""
8793
Get the callback function to show the toolbar.
8894
@@ -108,7 +114,7 @@ class DebugToolbarMiddleware:
108114
sync_capable = True
109115
async_capable = True
110116

111-
def __init__(self, get_response):
117+
def __init__(self, get_response: GetResponse):
112118
self.get_response = get_response
113119
# If get_response is a coroutine function, turns us into async mode so
114120
# a thread is not consumed during a whole request.
@@ -119,7 +125,7 @@ def __init__(self, get_response):
119125
# __call__ to avoid swapping out dunder methods.
120126
markcoroutinefunction(self)
121127

122-
def __call__(self, request):
128+
def __call__(self, request: HttpRequest) -> HttpResponse:
123129
# Decide whether the toolbar is active for this request.
124130
if self.async_mode:
125131
return self.__acall__(request)
@@ -144,7 +150,7 @@ def __call__(self, request):
144150

145151
return self._postprocess(request, response, toolbar)
146152

147-
async def __acall__(self, request):
153+
async def __acall__(self, request: HttpRequest) -> HttpResponse:
148154
# Decide whether the toolbar is active for this request.
149155
show_toolbar = get_show_toolbar(async_mode=self.async_mode)
150156

@@ -172,7 +178,9 @@ async def __acall__(self, request):
172178

173179
return self._postprocess(request, response, toolbar)
174180

175-
def _postprocess(self, request, response, toolbar):
181+
def _postprocess(
182+
self, request: HttpRequest, response: HttpResponse, toolbar: DebugToolbar
183+
) -> HttpResponse:
176184
"""
177185
Post-process the response.
178186
"""
@@ -206,7 +214,7 @@ def _postprocess(self, request, response, toolbar):
206214
return response
207215

208216
@staticmethod
209-
def get_headers(request, panels):
217+
def get_headers(request: HttpRequest, panels: list[Panel]) -> dict[str, str]:
210218
headers = {}
211219
for panel in panels:
212220
for header, value in panel.get_headers(request).items():

debug_toolbar/panels/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from django.utils.functional import classproperty
44

55
from debug_toolbar import settings as dt_settings
6+
from debug_toolbar._stubs import GetResponse
67
from debug_toolbar.utils import get_name_from_obj
78

89

@@ -13,7 +14,7 @@ class Panel:
1314

1415
is_async = False
1516

16-
def __init__(self, toolbar, get_response):
17+
def __init__(self, toolbar, get_response: GetResponse):
1718
self.toolbar = toolbar
1819
self.get_response = get_response
1920
self.from_store = False

debug_toolbar/toolbar.py

Lines changed: 48 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,46 @@
22
The main DebugToolbar class that loads and renders the Toolbar.
33
"""
44

5+
from __future__ import annotations
6+
57
import logging
68
import re
79
import uuid
810
from functools import cache
11+
from typing import Callable
912

1013
from django.apps import apps
1114
from django.conf import settings
1215
from django.core.exceptions import ImproperlyConfigured
1316
from django.dispatch import Signal
17+
from django.http import HttpRequest
1418
from django.template import TemplateSyntaxError
1519
from django.template.loader import render_to_string
16-
from django.urls import include, path, re_path, resolve
20+
from django.urls import URLPattern, URLResolver, include, path, re_path, resolve
1721
from django.urls.exceptions import Resolver404
1822
from django.utils.module_loading import import_string
1923
from django.utils.translation import get_language, override as lang_override
2024

2125
from debug_toolbar import APP_NAME, settings as dt_settings
22-
from debug_toolbar.store import get_store
26+
from debug_toolbar._stubs import GetResponse
27+
from debug_toolbar.store import BaseStore, get_store
28+
29+
from .panels import Panel
2330

2431
logger = logging.getLogger(__name__)
2532

2633

2734
class DebugToolbar:
2835
# for internal testing use only
2936
_created = Signal()
30-
store = None
31-
32-
def __init__(self, request, get_response, request_id=None):
37+
store: BaseStore = None
38+
39+
def __init__(
40+
self,
41+
request: HttpRequest,
42+
get_response: GetResponse,
43+
request_id: str | None = None,
44+
):
3345
self.request = request
3446
self.config = dt_settings.get_config().copy()
3547
panels = []
@@ -49,21 +61,21 @@ def __init__(self, request, get_response, request_id=None):
4961
# Manage panels
5062

5163
@property
52-
def panels(self):
64+
def panels(self) -> list[Panel]:
5365
"""
5466
Get a list of all available panels.
5567
"""
5668
return list(self._panels.values())
5769

5870
@property
59-
def enabled_panels(self):
71+
def enabled_panels(self) -> list[Panel]:
6072
"""
6173
Get a list of panels enabled for the current request.
6274
"""
6375
return [panel for panel in self._panels.values() if panel.enabled]
6476

6577
@property
66-
def csp_nonce(self):
78+
def csp_nonce(self) -> str | None:
6779
"""
6880
Look up the Content Security Policy nonce if there is one.
6981
@@ -72,15 +84,15 @@ def csp_nonce(self):
7284
"""
7385
return getattr(self.request, "csp_nonce", None)
7486

75-
def get_panel_by_id(self, panel_id):
87+
def get_panel_by_id(self, panel_id: str) -> Panel:
7688
"""
7789
Get the panel with the given id, which is the class name by default.
7890
"""
7991
return self._panels[panel_id]
8092

8193
# Handle rendering the toolbar in HTML
8294

83-
def render_toolbar(self):
95+
def render_toolbar(self) -> str:
8496
"""
8597
Renders the overall Toolbar with panels inside.
8698
"""
@@ -101,7 +113,7 @@ def render_toolbar(self):
101113
else:
102114
raise
103115

104-
def should_render_panels(self):
116+
def should_render_panels(self) -> bool:
105117
"""Determine whether the panels should be rendered during the request
106118
107119
If False, the panels will be loaded via Ajax.
@@ -121,17 +133,19 @@ def init_store(self):
121133
self.store.set(self.request_id)
122134

123135
@classmethod
124-
def fetch(cls, request_id, panel_id=None):
136+
def fetch(
137+
cls, request_id: str, panel_id: str | None = None
138+
) -> StoredDebugToolbar | None:
125139
if get_store().exists(request_id):
126140
return StoredDebugToolbar.from_store(request_id, panel_id=panel_id)
127141

128142
# Manually implement class-level caching of panel classes and url patterns
129143
# because it's more obvious than going through an abstraction.
130144

131-
_panel_classes = None
145+
_panel_classes: list[Panel] | None = None
132146

133147
@classmethod
134-
def get_panel_classes(cls):
148+
def get_panel_classes(cls) -> list[type[Panel]] | None:
135149
if cls._panel_classes is None:
136150
# Load panels in a temporary variable for thread safety.
137151
panel_classes = [
@@ -140,10 +154,10 @@ def get_panel_classes(cls):
140154
cls._panel_classes = panel_classes
141155
return cls._panel_classes
142156

143-
_urlpatterns = None
157+
_urlpatterns: list[URLPattern | URLResolver] | None = None
144158

145159
@classmethod
146-
def get_urls(cls):
160+
def get_urls(cls) -> list[URLPattern | URLResolver]:
147161
if cls._urlpatterns is None:
148162
from . import views
149163

@@ -159,7 +173,7 @@ def get_urls(cls):
159173
return cls._urlpatterns
160174

161175
@classmethod
162-
def is_toolbar_request(cls, request):
176+
def is_toolbar_request(cls, request: HttpRequest) -> bool:
163177
"""
164178
Determine if the request is for a DebugToolbar view.
165179
"""
@@ -171,11 +185,14 @@ def is_toolbar_request(cls, request):
171185
)
172186
except Resolver404:
173187
return False
174-
return resolver_match.namespaces and resolver_match.namespaces[-1] == APP_NAME
188+
return (
189+
bool(resolver_match.namespaces)
190+
and resolver_match.namespaces[-1] == APP_NAME
191+
)
175192

176193
@staticmethod
177194
@cache
178-
def get_observe_request():
195+
def get_observe_request() -> Callable:
179196
# If OBSERVE_REQUEST_CALLBACK is a string, which is the recommended
180197
# setup, resolve it to the corresponding callable.
181198
func_or_path = dt_settings.get_config()["OBSERVE_REQUEST_CALLBACK"]
@@ -185,22 +202,27 @@ def get_observe_request():
185202
return func_or_path
186203

187204

188-
def observe_request(request):
205+
def observe_request(request: HttpRequest) -> bool:
189206
"""
190207
Determine whether to update the toolbar from a client side request.
191208
"""
192209
return True
193210

194211

195-
def from_store_get_response(request):
212+
def from_store_get_response(request: HttpRequest | None) -> None:
196213
logger.warning(
197214
"get_response was called for debug toolbar after being loaded from the store. No request exists in this scenario as the request is not stored, only the panel's data."
198215
)
199216
return None
200217

201218

202219
class StoredDebugToolbar(DebugToolbar):
203-
def __init__(self, request, get_response, request_id=None):
220+
def __init__(
221+
self,
222+
request: HttpRequest | None,
223+
get_response: GetResponse,
224+
request_id: str | None = None,
225+
):
204226
self.request = None
205227
self.config = dt_settings.get_config().copy()
206228
self.process_request = get_response
@@ -210,7 +232,9 @@ def __init__(self, request, get_response, request_id=None):
210232
self.init_store()
211233

212234
@classmethod
213-
def from_store(cls, request_id, panel_id=None):
235+
def from_store(
236+
cls, request_id: str, panel_id: str | None = None
237+
) -> StoredDebugToolbar:
214238
toolbar = StoredDebugToolbar(
215239
None, from_store_get_response, request_id=request_id
216240
)
@@ -226,7 +250,7 @@ def from_store(cls, request_id, panel_id=None):
226250
return toolbar
227251

228252

229-
def debug_toolbar_urls(prefix="__debug__"):
253+
def debug_toolbar_urls(prefix: str = "__debug__") -> list[URLPattern | URLResolver]:
230254
"""
231255
Return a URL pattern for serving toolbar in debug mode.
232256

docs/panels.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,8 @@ There is no public CSS API at this time.
386386

387387
.. automethod:: debug_toolbar.panels.Panel.run_checks
388388

389+
.. autoclass:: debug_toolbar._stubs.GetResponse
390+
389391
.. _javascript-api:
390392

391393
JavaScript API

0 commit comments

Comments
 (0)