Skip to content

Commit

Permalink
Don't try to undo DatabaseWrapper monkey patching
Browse files Browse the repository at this point in the history
Prior to this commit, the SQLPanel would monkey patch the .cursor() and
.chunked_cursor() methods of each DatabaseWrapper connection in
.enable_instrumentation(), and then undo the monkey patch in
.disable_instrumentation().  However, for the same reasons as
a6b65a7, stop trying to undo the monkey
patching in .disable_instrumentation() and simply use a .djdt_logger
attribute on the DatabaseWrapper to determine if the cursors should be
wrapped.
  • Loading branch information
living180 committed May 10, 2023
1 parent df67c89 commit 217238b
Show file tree
Hide file tree
Showing 2 changed files with 19 additions and 27 deletions.
7 changes: 4 additions & 3 deletions debug_toolbar/panels/sql/panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from debug_toolbar.panels import Panel
from debug_toolbar.panels.sql import views
from debug_toolbar.panels.sql.forms import SQLSelectForm
from debug_toolbar.panels.sql.tracking import unwrap_cursor, wrap_cursor
from debug_toolbar.panels.sql.tracking import wrap_cursor
from debug_toolbar.panels.sql.utils import contrasting_color_generator, reformat_sql
from debug_toolbar.utils import render_stacktrace

Expand Down Expand Up @@ -190,11 +190,12 @@ def get_urls(cls):
def enable_instrumentation(self):
# This is thread-safe because database connections are thread-local.
for connection in connections.all():
wrap_cursor(connection, self)
wrap_cursor(connection)
connection._djdt_logger = self

def disable_instrumentation(self):
for connection in connections.all():
unwrap_cursor(connection)
connection._djdt_logger = None

def generate_stats(self, request, response):
colors = contrasting_color_generator()
Expand Down
39 changes: 15 additions & 24 deletions debug_toolbar/panels/sql/tracking.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import json
from time import time

import django.test.testcases
from django.utils.encoding import force_str

from debug_toolbar import settings as dt_settings
Expand Down Expand Up @@ -31,10 +32,15 @@ class SQLQueryTriggered(Exception):
"""Thrown when template panel triggers a query"""


def wrap_cursor(connection, panel):
def wrap_cursor(connection):
# If running a Django SimpleTestCase, which isn't allowed to access the database,
# don't perform any monkey patching.
if isinstance(connection.cursor, django.test.testcases._DatabaseFailure):
return
if not hasattr(connection, "_djdt_cursor"):
connection._djdt_cursor = connection.cursor
connection._djdt_chunked_cursor = connection.chunked_cursor
connection._djdt_logger = None

def cursor(*args, **kwargs):
# Per the DB API cursor() does not accept any arguments. There's
Expand All @@ -43,48 +49,33 @@ def cursor(*args, **kwargs):
# See:
# https://github.com/jazzband/django-debug-toolbar/pull/615
# https://github.com/jazzband/django-debug-toolbar/pull/896
logger = connection._djdt_logger
cursor = connection._djdt_cursor(*args, **kwargs)
if logger is None:
return cursor
if allow_sql.get():
wrapper = NormalCursorWrapper
else:
wrapper = ExceptionCursorWrapper
return wrapper(connection._djdt_cursor(*args, **kwargs), connection, panel)
return wrapper(cursor, connection, logger)

def chunked_cursor(*args, **kwargs):
# prevent double wrapping
# solves https://github.com/jazzband/django-debug-toolbar/issues/1239
logger = connection._djdt_logger
cursor = connection._djdt_chunked_cursor(*args, **kwargs)
if not isinstance(cursor, BaseCursorWrapper):
if logger is not None and not isinstance(cursor, BaseCursorWrapper):
if allow_sql.get():
wrapper = NormalCursorWrapper
else:
wrapper = ExceptionCursorWrapper
return wrapper(cursor, connection, panel)
return wrapper(cursor, connection, logger)
return cursor

connection.cursor = cursor
connection.chunked_cursor = chunked_cursor


def unwrap_cursor(connection):
if hasattr(connection, "_djdt_cursor"):
# Sometimes the cursor()/chunked_cursor() methods of the DatabaseWrapper
# instance are already monkey patched before wrap_cursor() is called. (In
# particular, Django's SimpleTestCase monkey patches those methods for any
# disallowed databases to raise an exception if they are accessed.) Thus only
# delete our monkey patch if the method we saved is the same as the class
# method. Otherwise, restore the prior monkey patch from our saved method.
if connection._djdt_cursor == connection.__class__.cursor:
del connection.cursor
else:
connection.cursor = connection._djdt_cursor
del connection._djdt_cursor
if connection._djdt_chunked_cursor == connection.__class__.chunked_cursor:
del connection.chunked_cursor
else:
connection.chunked_cursor = connection._djdt_chunked_cursor
del connection._djdt_chunked_cursor


class BaseCursorWrapper:
pass

Expand Down

0 comments on commit 217238b

Please sign in to comment.