Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type hints: toolbar/middleware #1848

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 7 additions & 1 deletion debug_toolbar/_stubs.py
@@ -1,6 +1,7 @@
from typing import Any, List, NamedTuple, Optional, Tuple
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):
Expand All @@ -22,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:
...
7 changes: 5 additions & 2 deletions debug_toolbar/middleware.py
Expand Up @@ -6,16 +6,19 @@
from functools import lru_cache

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

from ._stubs import GetResponse

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


def show_toolbar(request):
def show_toolbar(request: HttpRequest):
"""
Default function to determine whether to show the toolbar on a given page.
"""
Expand All @@ -39,7 +42,7 @@ class DebugToolbarMiddleware:
on outgoing response.
"""

def __init__(self, get_response):
def __init__(self, get_response: GetResponse):
self.get_response = get_response

def __call__(self, request):
Expand Down
3 changes: 2 additions & 1 deletion debug_toolbar/panels/__init__.py
@@ -1,6 +1,7 @@
from django.template.loader import render_to_string

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 @@ -9,7 +10,7 @@ class Panel:
Base class for panels.
"""

def __init__(self, toolbar, get_response):
def __init__(self, toolbar, get_response: GetResponse):
self.toolbar = toolbar
self.get_response = get_response

Expand Down
46 changes: 28 additions & 18 deletions debug_toolbar/toolbar.py
Expand Up @@ -5,25 +5,32 @@
import uuid
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.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 path, resolve
from django.urls import URLPattern, 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 ._stubs import GetResponse

if TYPE_CHECKING:
from .panels import Panel


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

def __init__(self, request, get_response):
def __init__(self, request: HttpRequest, get_response: GetResponse):
self.request = request
self.config = dt_settings.get_config().copy()
panels = []
Expand All @@ -36,40 +43,40 @@ def __init__(self, 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()
self._panels = OrderedDict() # type: ignore[var-annotated]
while panels:
panel = panels.pop()
self._panels[panel.panel_id] = panel
self.stats = {}
self.server_timing_stats = {}
self.store_id = None
self.stats: Dict[str, Any] = {}
self.server_timing_stats: Dict[str, Any] = {}
self.store_id: Optional[str] = None
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.
"""
return [panel for panel in self._panels.values() if panel.enabled]

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 @@ -90,7 +97,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 @@ -106,7 +113,7 @@ def should_render_panels(self):

# Handle storing toolbars in memory and fetching them later on

_store = OrderedDict()
_store = OrderedDict() # type: ignore[var-annotated]

def store(self):
# Store already exists.
Expand All @@ -124,7 +131,7 @@ def fetch(cls, store_id):
# 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 @@ -136,10 +143,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 @@ -155,7 +162,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.
"""
Expand All @@ -167,7 +174,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
@lru_cache(maxsize=None)
Expand All @@ -181,7 +191,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