-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Binary parameter handling for GeoDjango #2169
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
base: main
Are you sure you want to change the base?
Conversation
for more information, see https://pre-commit.ci
…f docs/changes.rst.
updates: - [github.com/astral-sh/ruff-pre-commit: v0.12.4 → v0.12.5](astral-sh/ruff-pre-commit@v0.12.4...v0.12.5) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
for more information, see https://pre-commit.ci
@tim-schilling I added some commits following what you were pointing out, let me know what do you think! |
import json | ||
|
||
|
||
class DebugToolbarJSONDecoder(json.JSONDecoder): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than the approach you've taken here, you may want to consider going with something like:
import base64
import json
def object_hook(obj):
for key, value in obj.items():
if isinstance(value, str) and value.startswith("__djdt_binary__"):
_, encoded = value.split("__djdt_binary__", maxsplit=1)
obj[key] = base64.b64decode(encoded)
return obj
class DebugToolbarJSONDecoder(json.JSONDecoder):
"""Custom JSON decoder that reconstructs binary data during parsing."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.object_hook is None:
self.object_hook = object_hook
This will require you to change the tests. The decoding will only work if there's an actual object being passed in. Most of the tests are passing in a list of values. Using an object/dictionary is more representative of what will actually be used since this is for parameters for a SQL query which is a dictionary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I got it, I did not know about this object_hook
possibility.
# 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")} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should move this logic into a shared utility that way we don't have the magic value "__djdt_binary__"
floating around the code-base. Plus it would be clearer how the logic gets paired together.
I'm also curious if we should be using this with the Store logic. If we do, then we could potentially ignore this and handle it within DebugToolbarJSONEncoder
and then have DebugToolbarJSONDecoder
as above. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the feedback @tim-schilling ! I think an encoders.py or moving even everything into utils.py is a more consistent way.
About your second question, what do you mean with using the store logic?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Store logic converts all the panels data into JSON. So this binary parameter would eventually be converted to JSON to be added to the Store. So if the Store handles binary data properly, it may remove the need for the SQL panel to deal with it itself. Does that make more sense?
self.assertEqual(len(reconstructed), 1) | ||
self.assertEqual(reconstructed[0], binary_data) | ||
self.assertIsInstance(reconstructed[0], bytes) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think there's a benefit to being this verbose. Try to be more concise such as:
self.assertEqual(len(reconstructed), 1) | |
self.assertEqual(reconstructed[0], binary_data) | |
self.assertIsInstance(reconstructed[0], bytes) | |
self.assertEqual(reconstructed, [binary_data]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for continuing to work on this! I have a few directional changes. If you want more specific feedback, I can provide it. I didn't want to be overly prescriptive.
Co-authored-by: Tim Schilling <[email protected]>
No please! Tell me more, because I see that you are pointing to a deeper idea that I did not catch yet. |
@eduzen oh, I meant I was writing out the solution when I did the last round of review. It seemed like you may be interested in working through the issues to arrive at your own implementation and I didn't want to deprive you of the opportunity. So I should have said was, "if you'd like me to make these edits myself, please let me know." |
Description
hey folks, this is my very first PR in the django-commons. I did this because I faced the issue myself in one project that I'm working on. I don't know how I can test this in that project. So I will wait for some of you to tell me how I can do that... but in the meantime this is the fix for the SQL Explain functionality for GeoDjango queries with binary parameters.
Fixes #423
SQL Explain feature throws a 500 error when used with GeoDjango queries containing binary parameters. This affects any query using PostGIS functions like ST_GeomFromEWKB(), ST_Distance_Sphere(), etc.
I'm trying to create a robust binary parameter handling system:
Checklist:
docs/changes.rst
.