From db28e8141bdbf806b6d95c39297c2ad6a2b82c7e Mon Sep 17 00:00:00 2001 From: Eduardo Enriquez Date: Sun, 27 Jul 2025 22:24:17 +0200 Subject: [PATCH 1/7] Binary parameter handling for GeoDjango --- debug_toolbar/panels/sql/forms.py | 29 ++++- debug_toolbar/panels/sql/tracking.py | 6 + tests/panels/test_sql_geodjango_fix.py | 171 +++++++++++++++++++++++++ 3 files changed, 203 insertions(+), 3 deletions(-) create mode 100644 tests/panels/test_sql_geodjango_fix.py diff --git a/debug_toolbar/panels/sql/forms.py b/debug_toolbar/panels/sql/forms.py index 44906924d..9c829f9a0 100644 --- a/debug_toolbar/panels/sql/forms.py +++ b/debug_toolbar/panels/sql/forms.py @@ -1,4 +1,5 @@ import json +import base64 from django import forms from django.core.exceptions import ValidationError @@ -10,6 +11,23 @@ from debug_toolbar.toolbar import DebugToolbar +def _reconstruct_params(params): + """ + Reconstruct parameters that were encoded for JSON storage, + especially binary data that was base64 encoded. + """ + if isinstance(params, list): + return [_reconstruct_params(param) for param in params] + elif isinstance(params, dict): + if "__djdt_binary__" in params: + # Reconstruct binary data from base64 + return base64.b64decode(params["__djdt_binary__"]) + else: + return {key: _reconstruct_params(value) for key, value in params.items()} + else: + return params + + class SQLSelectForm(forms.Form): """ Validate params @@ -69,10 +87,15 @@ def clean(self): cleaned_data["query"] = query return cleaned_data + def _get_query_params(self): + """Get reconstructed parameters for the current query""" + query = self.cleaned_data["query"] + return _reconstruct_params(json.loads(query["params"])) + def select(self): query = self.cleaned_data["query"] sql = query["raw_sql"] - params = json.loads(query["params"]) + params = self._get_query_params() with self.cursor as cursor: cursor.execute(sql, params) headers = [d[0] for d in cursor.description] @@ -82,7 +105,7 @@ def select(self): def explain(self): query = self.cleaned_data["query"] sql = query["raw_sql"] - params = json.loads(query["params"]) + params = self._get_query_params() vendor = query["vendor"] with self.cursor as cursor: if vendor == "sqlite": @@ -101,7 +124,7 @@ def explain(self): def profile(self): query = self.cleaned_data["query"] sql = query["raw_sql"] - params = json.loads(query["params"]) + params = self._get_query_params() with self.cursor as cursor: cursor.execute("SET PROFILING=1") # Enable profiling cursor.execute(sql, params) # Execute SELECT diff --git a/debug_toolbar/panels/sql/tracking.py b/debug_toolbar/panels/sql/tracking.py index 45e0c0c17..4701ed487 100644 --- a/debug_toolbar/panels/sql/tracking.py +++ b/debug_toolbar/panels/sql/tracking.py @@ -2,6 +2,7 @@ import contextvars import datetime import json +import base64 from time import perf_counter import django.test.testcases @@ -126,6 +127,11 @@ def _decode(self, param): if isinstance(param, dict): return {key: self._decode(value) for key, value in param.items()} + # Handle binary data (e.g., GeoDjango EWKB geometry data) + if isinstance(param, (bytes, bytearray)): + # Mark as binary data for later reconstruction + return {"__djdt_binary__": base64.b64encode(param).decode('ascii')} + # make sure datetime, date and time are converted to string by force_str CONVERT_TYPES = (datetime.datetime, datetime.date, datetime.time) try: diff --git a/tests/panels/test_sql_geodjango_fix.py b/tests/panels/test_sql_geodjango_fix.py new file mode 100644 index 000000000..f6900528b --- /dev/null +++ b/tests/panels/test_sql_geodjango_fix.py @@ -0,0 +1,171 @@ +""" +Tests for GeoDjango binary parameter handling fix +""" +import json +import base64 +import unittest + +from debug_toolbar.panels.sql.forms import _reconstruct_params +from debug_toolbar.panels.sql.tracking import NormalCursorMixin + +from ..base import BaseTestCase + + +class MockCursor: + """Mock cursor for testing""" + pass + + +class MockConnection: + """Mock database connection for testing""" + vendor = "postgresql" + alias = "default" + + +class MockLogger: + """Mock logger for testing""" + def record(self, **kwargs): + pass + + +class TestCursor(NormalCursorMixin): + """Test cursor that can be instantiated""" + def __init__(self): + # Initialize with mock objects + self.cursor = MockCursor() + self.db = MockConnection() + self.logger = MockLogger() + + +class GeoDjangoBinaryParameterTest(BaseTestCase): + """Test cases for GeoDjango binary parameter handling""" + + def test_binary_parameter_encoding_decoding(self): + """Test that binary parameters are properly encoded and decoded""" + # Create a test cursor with the _decode method + cursor = TestCursor() + + # Test binary data similar to GeoDjango EWKB geometry + binary_data = b'\x01\x01\x00\x00\x20\xe6\x10\x00\x00\xff\xfe\xfd' + + # Test encoding (what happens when query is logged) + encoded = cursor._decode(binary_data) + + # Should be marked as binary data + self.assertIsInstance(encoded, dict) + self.assertIn("__djdt_binary__", encoded) + + # Should be base64 encoded + expected_b64 = base64.b64encode(binary_data).decode('ascii') + self.assertEqual(encoded["__djdt_binary__"], expected_b64) + + # Test JSON serialization (what happens in tracking.py) + json_params = json.dumps([encoded]) + + # Test parsing back from JSON + parsed = json.loads(json_params) + + # Test reconstruction (what happens in forms.py) + reconstructed = _reconstruct_params(parsed) + + # Should recover original binary data + self.assertEqual(len(reconstructed), 1) + self.assertEqual(reconstructed[0], binary_data) + self.assertIsInstance(reconstructed[0], bytes) + + def test_mixed_parameter_types(self): + """Test that mixed parameter types are handled correctly""" + cursor = TestCursor() + + # Test with mixed types including binary data + params = [ + "string_param", + 42, + b'\x01\x02\x03', # binary data + None, + ["nested", "list"], + ] + + # Encode each parameter + encoded_params = [cursor._decode(p) for p in params] + + # Serialize to JSON + json_str = json.dumps(encoded_params) + + # Parse and reconstruct + parsed = json.loads(json_str) + reconstructed = _reconstruct_params(parsed) + + # Check each parameter + self.assertEqual(reconstructed[0], "string_param") # string unchanged + self.assertEqual(reconstructed[1], 42) # int unchanged + self.assertEqual(reconstructed[2], b'\x01\x02\x03') # binary restored + self.assertIsNone(reconstructed[3]) # None unchanged + self.assertEqual(reconstructed[4], ["nested", "list"]) # list unchanged + + def test_nested_binary_data(self): + """Test binary data nested in lists and dicts""" + cursor = TestCursor() + + # Test nested structures with binary data + nested_params = [ + [b'\x01\x02', "string", b'\x03\x04'], + {"key": b'\x05\x06', "other": "value"}, + ] + + # Encode + encoded = [cursor._decode(p) for p in nested_params] + + # Serialize and parse + json_str = json.dumps(encoded) + parsed = json.loads(json_str) + reconstructed = _reconstruct_params(parsed) + + # Check nested list + self.assertEqual(reconstructed[0][0], b'\x01\x02') + self.assertEqual(reconstructed[0][1], "string") + self.assertEqual(reconstructed[0][2], b'\x03\x04') + + # Check nested dict + self.assertEqual(reconstructed[1]["key"], b'\x05\x06') + self.assertEqual(reconstructed[1]["other"], "value") + + def test_empty_binary_data(self): + """Test handling of empty binary data""" + cursor = TestCursor() + + # Test empty bytes + empty_bytes = b'' + encoded = cursor._decode(empty_bytes) + + # Should still be marked as binary + self.assertIsInstance(encoded, dict) + self.assertIn("__djdt_binary__", encoded) + + # Reconstruct + json_str = json.dumps([encoded]) + parsed = json.loads(json_str) + reconstructed = _reconstruct_params(parsed) + + self.assertEqual(reconstructed[0], empty_bytes) + + def test_bytearray_support(self): + """Test that bytearray is also handled as binary data""" + cursor = TestCursor() + + # Test bytearray + byte_array = bytearray(b'\x01\x02\x03\x04') + encoded = cursor._decode(byte_array) + + # Should be marked as binary + self.assertIn("__djdt_binary__", encoded) + + # Reconstruct (should become bytes, not bytearray) + json_str = json.dumps([encoded]) + parsed = json.loads(json_str) + reconstructed = _reconstruct_params(parsed) + + # Should be equal in content (bytes vs bytearray comparison works) + self.assertEqual(reconstructed[0], byte_array) + # Should be bytes type after reconstruction + self.assertIsInstance(reconstructed[0], bytes) \ No newline at end of file From 5205a50c075729b244eb883be6c19aa2f530e793 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 27 Jul 2025 20:31:40 +0000 Subject: [PATCH 2/7] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- debug_toolbar/panels/sql/forms.py | 2 +- debug_toolbar/panels/sql/tracking.py | 4 +- tests/panels/test_sql_geodjango_fix.py | 84 +++++++++++++------------- 3 files changed, 46 insertions(+), 44 deletions(-) diff --git a/debug_toolbar/panels/sql/forms.py b/debug_toolbar/panels/sql/forms.py index 9c829f9a0..176d9406a 100644 --- a/debug_toolbar/panels/sql/forms.py +++ b/debug_toolbar/panels/sql/forms.py @@ -1,5 +1,5 @@ -import json import base64 +import json from django import forms from django.core.exceptions import ValidationError diff --git a/debug_toolbar/panels/sql/tracking.py b/debug_toolbar/panels/sql/tracking.py index 4701ed487..c6474bb8e 100644 --- a/debug_toolbar/panels/sql/tracking.py +++ b/debug_toolbar/panels/sql/tracking.py @@ -1,8 +1,8 @@ +import base64 import contextlib import contextvars import datetime import json -import base64 from time import perf_counter import django.test.testcases @@ -130,7 +130,7 @@ def _decode(self, param): # Handle binary data (e.g., GeoDjango EWKB geometry data) if isinstance(param, (bytes, bytearray)): # Mark as binary data for later reconstruction - return {"__djdt_binary__": base64.b64encode(param).decode('ascii')} + return {"__djdt_binary__": base64.b64encode(param).decode("ascii")} # make sure datetime, date and time are converted to string by force_str CONVERT_TYPES = (datetime.datetime, datetime.date, datetime.time) diff --git a/tests/panels/test_sql_geodjango_fix.py b/tests/panels/test_sql_geodjango_fix.py index f6900528b..6835f7d09 100644 --- a/tests/panels/test_sql_geodjango_fix.py +++ b/tests/panels/test_sql_geodjango_fix.py @@ -1,9 +1,9 @@ """ Tests for GeoDjango binary parameter handling fix """ -import json + import base64 -import unittest +import json from debug_toolbar.panels.sql.forms import _reconstruct_params from debug_toolbar.panels.sql.tracking import NormalCursorMixin @@ -13,23 +13,25 @@ class MockCursor: """Mock cursor for testing""" - pass class MockConnection: """Mock database connection for testing""" + vendor = "postgresql" alias = "default" class MockLogger: """Mock logger for testing""" + def record(self, **kwargs): pass class TestCursor(NormalCursorMixin): """Test cursor that can be instantiated""" + def __init__(self): # Initialize with mock objects self.cursor = MockCursor() @@ -44,30 +46,30 @@ def test_binary_parameter_encoding_decoding(self): """Test that binary parameters are properly encoded and decoded""" # Create a test cursor with the _decode method cursor = TestCursor() - + # Test binary data similar to GeoDjango EWKB geometry - binary_data = b'\x01\x01\x00\x00\x20\xe6\x10\x00\x00\xff\xfe\xfd' - + binary_data = b"\x01\x01\x00\x00\x20\xe6\x10\x00\x00\xff\xfe\xfd" + # Test encoding (what happens when query is logged) encoded = cursor._decode(binary_data) - + # Should be marked as binary data self.assertIsInstance(encoded, dict) self.assertIn("__djdt_binary__", encoded) - + # Should be base64 encoded - expected_b64 = base64.b64encode(binary_data).decode('ascii') + expected_b64 = base64.b64encode(binary_data).decode("ascii") self.assertEqual(encoded["__djdt_binary__"], expected_b64) - + # Test JSON serialization (what happens in tracking.py) json_params = json.dumps([encoded]) - + # Test parsing back from JSON parsed = json.loads(json_params) - + # Test reconstruction (what happens in forms.py) reconstructed = _reconstruct_params(parsed) - + # Should recover original binary data self.assertEqual(len(reconstructed), 1) self.assertEqual(reconstructed[0], binary_data) @@ -76,96 +78,96 @@ def test_binary_parameter_encoding_decoding(self): def test_mixed_parameter_types(self): """Test that mixed parameter types are handled correctly""" cursor = TestCursor() - + # Test with mixed types including binary data params = [ "string_param", 42, - b'\x01\x02\x03', # binary data + b"\x01\x02\x03", # binary data None, ["nested", "list"], ] - + # Encode each parameter encoded_params = [cursor._decode(p) for p in params] - + # Serialize to JSON json_str = json.dumps(encoded_params) - + # Parse and reconstruct parsed = json.loads(json_str) reconstructed = _reconstruct_params(parsed) - + # Check each parameter self.assertEqual(reconstructed[0], "string_param") # string unchanged self.assertEqual(reconstructed[1], 42) # int unchanged - self.assertEqual(reconstructed[2], b'\x01\x02\x03') # binary restored + self.assertEqual(reconstructed[2], b"\x01\x02\x03") # binary restored self.assertIsNone(reconstructed[3]) # None unchanged self.assertEqual(reconstructed[4], ["nested", "list"]) # list unchanged def test_nested_binary_data(self): """Test binary data nested in lists and dicts""" cursor = TestCursor() - + # Test nested structures with binary data nested_params = [ - [b'\x01\x02', "string", b'\x03\x04'], - {"key": b'\x05\x06', "other": "value"}, + [b"\x01\x02", "string", b"\x03\x04"], + {"key": b"\x05\x06", "other": "value"}, ] - + # Encode encoded = [cursor._decode(p) for p in nested_params] - + # Serialize and parse json_str = json.dumps(encoded) parsed = json.loads(json_str) reconstructed = _reconstruct_params(parsed) - + # Check nested list - self.assertEqual(reconstructed[0][0], b'\x01\x02') + self.assertEqual(reconstructed[0][0], b"\x01\x02") self.assertEqual(reconstructed[0][1], "string") - self.assertEqual(reconstructed[0][2], b'\x03\x04') - + self.assertEqual(reconstructed[0][2], b"\x03\x04") + # Check nested dict - self.assertEqual(reconstructed[1]["key"], b'\x05\x06') + self.assertEqual(reconstructed[1]["key"], b"\x05\x06") self.assertEqual(reconstructed[1]["other"], "value") def test_empty_binary_data(self): """Test handling of empty binary data""" cursor = TestCursor() - + # Test empty bytes - empty_bytes = b'' + empty_bytes = b"" encoded = cursor._decode(empty_bytes) - + # Should still be marked as binary self.assertIsInstance(encoded, dict) self.assertIn("__djdt_binary__", encoded) - + # Reconstruct json_str = json.dumps([encoded]) parsed = json.loads(json_str) reconstructed = _reconstruct_params(parsed) - + self.assertEqual(reconstructed[0], empty_bytes) def test_bytearray_support(self): """Test that bytearray is also handled as binary data""" cursor = TestCursor() - + # Test bytearray - byte_array = bytearray(b'\x01\x02\x03\x04') + byte_array = bytearray(b"\x01\x02\x03\x04") encoded = cursor._decode(byte_array) - + # Should be marked as binary self.assertIn("__djdt_binary__", encoded) - + # Reconstruct (should become bytes, not bytearray) json_str = json.dumps([encoded]) parsed = json.loads(json_str) reconstructed = _reconstruct_params(parsed) - + # Should be equal in content (bytes vs bytearray comparison works) self.assertEqual(reconstructed[0], byte_array) # Should be bytes type after reconstruction - self.assertIsInstance(reconstructed[0], bytes) \ No newline at end of file + self.assertIsInstance(reconstructed[0], bytes) From f8e2f672b6c45c7f4bd3a3d1047eb608742a0d85 Mon Sep 17 00:00:00 2001 From: Eduardo Enriquez Date: Sun, 27 Jul 2025 22:34:43 +0200 Subject: [PATCH 3/7] Update the checklist for: have added an item to the Pending section of docs/changes.rst. --- docs/changes.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/changes.rst b/docs/changes.rst index 5c7c0844b..62dd555a1 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -1,6 +1,14 @@ Change log ========== +Pending +------- + +* Fixed SQL Explain functionality for GeoDjango queries with binary parameters. + Binary data (such as EWKB geometry) is now properly handled through base64 + encoding, preventing "parse error - invalid geometry" errors when using + Explain on spatial queries. + 6.0.0 (2025-07-22) ------------------ From f3ac881610ee7233a377cd447663dd5e44cefebe Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 3 Aug 2025 09:43:44 +0200 Subject: [PATCH 4/7] [pre-commit.ci] pre-commit autoupdate (#2170) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.12.4 → v0.12.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.12.4...v0.12.5) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 47388752c..f652544fe 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -34,7 +34,7 @@ repos: - id: biome-check verbose: true - repo: https://github.com/astral-sh/ruff-pre-commit - rev: 'v0.12.4' + rev: 'v0.12.5' hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] From a6228231ce33b3c9f207558abb814f55ae69c721 Mon Sep 17 00:00:00 2001 From: eduzen Date: Sun, 3 Aug 2025 16:46:10 +0200 Subject: [PATCH 5/7] Add DebugToolBarJSONDecoder class for better organization of code --- debug_toolbar/panels/sql/decoders.py | 26 ++++++++++++++++++++++++++ debug_toolbar/panels/sql/forms.py | 3 ++- 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 debug_toolbar/panels/sql/decoders.py diff --git a/debug_toolbar/panels/sql/decoders.py b/debug_toolbar/panels/sql/decoders.py new file mode 100644 index 000000000..deb84c05a --- /dev/null +++ b/debug_toolbar/panels/sql/decoders.py @@ -0,0 +1,26 @@ +import base64 +import json + + +class DebugToolbarJSONDecoder(json.JSONDecoder): + """Custom JSON decoder that reconstructs binary data during parsing.""" + + def decode(self, s): + """Override decode to apply reconstruction after parsing.""" + obj = super().decode(s) + return self._reconstruct_params(obj) + + def _reconstruct_params(self, params): + """Reconstruct parameters, handling lists and dicts recursively.""" + if isinstance(params, list): + return [self._reconstruct_params(param) for param in params] + elif isinstance(params, dict): + if "__djdt_binary__" in params: + return base64.b64decode(params["__djdt_binary__"]) + else: + return { + key: self._reconstruct_params(value) + for key, value in params.items() + } + else: + return params diff --git a/debug_toolbar/panels/sql/forms.py b/debug_toolbar/panels/sql/forms.py index 176d9406a..45514ace5 100644 --- a/debug_toolbar/panels/sql/forms.py +++ b/debug_toolbar/panels/sql/forms.py @@ -7,6 +7,7 @@ from django.utils.functional import cached_property from django.utils.translation import gettext_lazy as _ +from debug_toolbar.panels.sql.decoders import DebugToolbarJSONDecoder from debug_toolbar.panels.sql.utils import is_select_query, reformat_sql from debug_toolbar.toolbar import DebugToolbar @@ -90,7 +91,7 @@ def clean(self): def _get_query_params(self): """Get reconstructed parameters for the current query""" query = self.cleaned_data["query"] - return _reconstruct_params(json.loads(query["params"])) + return json.loads(query["params"], cls=DebugToolbarJSONDecoder) def select(self): query = self.cleaned_data["query"] From 85557394bcbb4dc4f6612cd369c68bc59b94b409 Mon Sep 17 00:00:00 2001 From: eduzen Date: Sun, 3 Aug 2025 16:56:57 +0200 Subject: [PATCH 6/7] Fix tests for new DebugToolbarJSONDecoder --- debug_toolbar/panels/sql/forms.py | 16 --------- tests/panels/test_sql_geodjango_fix.py | 50 ++++---------------------- 2 files changed, 7 insertions(+), 59 deletions(-) diff --git a/debug_toolbar/panels/sql/forms.py b/debug_toolbar/panels/sql/forms.py index 45514ace5..8f4dd125f 100644 --- a/debug_toolbar/panels/sql/forms.py +++ b/debug_toolbar/panels/sql/forms.py @@ -12,22 +12,6 @@ from debug_toolbar.toolbar import DebugToolbar -def _reconstruct_params(params): - """ - Reconstruct parameters that were encoded for JSON storage, - especially binary data that was base64 encoded. - """ - if isinstance(params, list): - return [_reconstruct_params(param) for param in params] - elif isinstance(params, dict): - if "__djdt_binary__" in params: - # Reconstruct binary data from base64 - return base64.b64decode(params["__djdt_binary__"]) - else: - return {key: _reconstruct_params(value) for key, value in params.items()} - else: - return params - class SQLSelectForm(forms.Form): """ diff --git a/tests/panels/test_sql_geodjango_fix.py b/tests/panels/test_sql_geodjango_fix.py index 6835f7d09..b29fab22b 100644 --- a/tests/panels/test_sql_geodjango_fix.py +++ b/tests/panels/test_sql_geodjango_fix.py @@ -5,7 +5,7 @@ import base64 import json -from debug_toolbar.panels.sql.forms import _reconstruct_params +from debug_toolbar.panels.sql.decoders import DebugToolbarJSONDecoder from debug_toolbar.panels.sql.tracking import NormalCursorMixin from ..base import BaseTestCase @@ -33,7 +33,6 @@ class TestCursor(NormalCursorMixin): """Test cursor that can be instantiated""" def __init__(self): - # Initialize with mock objects self.cursor = MockCursor() self.db = MockConnection() self.logger = MockLogger() @@ -44,33 +43,21 @@ class GeoDjangoBinaryParameterTest(BaseTestCase): def test_binary_parameter_encoding_decoding(self): """Test that binary parameters are properly encoded and decoded""" - # Create a test cursor with the _decode method cursor = TestCursor() # Test binary data similar to GeoDjango EWKB geometry binary_data = b"\x01\x01\x00\x00\x20\xe6\x10\x00\x00\xff\xfe\xfd" - - # Test encoding (what happens when query is logged) encoded = cursor._decode(binary_data) - # Should be marked as binary data self.assertIsInstance(encoded, dict) self.assertIn("__djdt_binary__", encoded) - # Should be base64 encoded expected_b64 = base64.b64encode(binary_data).decode("ascii") self.assertEqual(encoded["__djdt_binary__"], expected_b64) - # Test JSON serialization (what happens in tracking.py) json_params = json.dumps([encoded]) + reconstructed = json.loads(json_params, cls=DebugToolbarJSONDecoder) - # Test parsing back from JSON - parsed = json.loads(json_params) - - # Test reconstruction (what happens in forms.py) - reconstructed = _reconstruct_params(parsed) - - # Should recover original binary data self.assertEqual(len(reconstructed), 1) self.assertEqual(reconstructed[0], binary_data) self.assertIsInstance(reconstructed[0], bytes) @@ -79,26 +66,19 @@ def test_mixed_parameter_types(self): """Test that mixed parameter types are handled correctly""" cursor = TestCursor() - # Test with mixed types including binary data params = [ "string_param", 42, - b"\x01\x02\x03", # binary data + b"\x01\x02\x03", None, ["nested", "list"], ] - # Encode each parameter encoded_params = [cursor._decode(p) for p in params] - # Serialize to JSON json_str = json.dumps(encoded_params) + reconstructed = json.loads(json_str, cls=DebugToolbarJSONDecoder) - # Parse and reconstruct - parsed = json.loads(json_str) - reconstructed = _reconstruct_params(parsed) - - # Check each parameter self.assertEqual(reconstructed[0], "string_param") # string unchanged self.assertEqual(reconstructed[1], 42) # int unchanged self.assertEqual(reconstructed[2], b"\x01\x02\x03") # binary restored @@ -109,26 +89,20 @@ def test_nested_binary_data(self): """Test binary data nested in lists and dicts""" cursor = TestCursor() - # Test nested structures with binary data nested_params = [ [b"\x01\x02", "string", b"\x03\x04"], {"key": b"\x05\x06", "other": "value"}, ] - # Encode encoded = [cursor._decode(p) for p in nested_params] - # Serialize and parse json_str = json.dumps(encoded) - parsed = json.loads(json_str) - reconstructed = _reconstruct_params(parsed) + reconstructed = json.loads(json_str, cls=DebugToolbarJSONDecoder) - # Check nested list self.assertEqual(reconstructed[0][0], b"\x01\x02") self.assertEqual(reconstructed[0][1], "string") self.assertEqual(reconstructed[0][2], b"\x03\x04") - # Check nested dict self.assertEqual(reconstructed[1]["key"], b"\x05\x06") self.assertEqual(reconstructed[1]["other"], "value") @@ -136,18 +110,14 @@ def test_empty_binary_data(self): """Test handling of empty binary data""" cursor = TestCursor() - # Test empty bytes empty_bytes = b"" encoded = cursor._decode(empty_bytes) - # Should still be marked as binary self.assertIsInstance(encoded, dict) self.assertIn("__djdt_binary__", encoded) - # Reconstruct json_str = json.dumps([encoded]) - parsed = json.loads(json_str) - reconstructed = _reconstruct_params(parsed) + reconstructed = json.loads(json_str, cls=DebugToolbarJSONDecoder) self.assertEqual(reconstructed[0], empty_bytes) @@ -155,19 +125,13 @@ def test_bytearray_support(self): """Test that bytearray is also handled as binary data""" cursor = TestCursor() - # Test bytearray byte_array = bytearray(b"\x01\x02\x03\x04") encoded = cursor._decode(byte_array) - # Should be marked as binary self.assertIn("__djdt_binary__", encoded) - # Reconstruct (should become bytes, not bytearray) json_str = json.dumps([encoded]) - parsed = json.loads(json_str) - reconstructed = _reconstruct_params(parsed) + reconstructed = json.loads(json_str, cls=DebugToolbarJSONDecoder) - # Should be equal in content (bytes vs bytearray comparison works) self.assertEqual(reconstructed[0], byte_array) - # Should be bytes type after reconstruction self.assertIsInstance(reconstructed[0], bytes) From 457879b9bf423440d6109c1fc2340c0d12fe2f9a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 3 Aug 2025 14:57:09 +0000 Subject: [PATCH 7/7] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- debug_toolbar/panels/sql/forms.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/debug_toolbar/panels/sql/forms.py b/debug_toolbar/panels/sql/forms.py index 8f4dd125f..0fe26e078 100644 --- a/debug_toolbar/panels/sql/forms.py +++ b/debug_toolbar/panels/sql/forms.py @@ -1,4 +1,3 @@ -import base64 import json from django import forms @@ -12,7 +11,6 @@ from debug_toolbar.toolbar import DebugToolbar - class SQLSelectForm(forms.Form): """ Validate params