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

1118 Encode strings as JSON in where clauses #1119

Open
wants to merge 4 commits into
base: master
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions piccolo/query/operators/json.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from __future__ import annotations

import json
import typing as t

from piccolo.querystring import QueryString
from piccolo.utils.encoding import dump_json
from piccolo.utils.encoding import dump_json, load_json

if t.TYPE_CHECKING:
from piccolo.columns.column_types import JSON
Expand All @@ -12,9 +13,34 @@
class JSONQueryString(QueryString):

def clean_value(self, value: t.Any):
if not isinstance(value, (str, QueryString)):
value = dump_json(value)
return value
"""
We need to pass a valid JSON string to Postgres.

There are lots of different use cases to account for::

# A JSON string is passed in - in which case, leave it.
RecordingStudio.facilities == '{"mixing_desk": true}'

# A Python dict is passed in - we need to convert this to JSON.
RecordingStudio.facilities == {"mixing_desk": True}

# A string is passed in, but it isn't valid JSON, so we need to
# convert it to a JSON string (i.e. '"Alice Jones"').
RecordingStudio.facilities["technicians"][0]["name"] == "Alice Jones"

""" # noqa: E501
if isinstance(value, QueryString):
return value
elif isinstance(value, str):
# The string might already be valid JSON, in which case, leave it.
try:
load_json(value)
except json.JSONDecodeError:
pass
else:
return value

return dump_json(value)

def __eq__(self, value) -> QueryString: # type: ignore[override]
value = self.clean_value(value)
Expand Down
59 changes: 57 additions & 2 deletions tests/columns/test_jsonb.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,9 +263,9 @@ class TestFromPath(AsyncTableTest):

tables = [RecordingStudio, Instrument]

async def test_from_path(self):
async def test_select(self):
"""
Make sure ``from_path`` can be used for complex nested data.
Make sure ``from_path`` can be used when selecting complex nested data.
"""
await RecordingStudio(
name="Abbey Road",
Expand All @@ -284,3 +284,58 @@ async def test_from_path(self):
).output(load_json=True)
assert response is not None
self.assertListEqual(response, [{"technician_name": "Alice Jones"}])

async def test_where(self):
"""
Make sure ``from_path`` can be used in a ``where`` clause.
"""
await RecordingStudio.insert(
RecordingStudio(
name="Abbey Road",
facilities={
"restaurant": False,
"mixing_desk": False,
"instruments": {"electric_guitars": 4, "drum_kits": 2},
"technicians": [
{"name": "Alice Jones"},
{"name": "Bob Williams"},
],
},
),
RecordingStudio(
name="Electric Lady",
facilities={
"restaurant": True,
"mixing_desk": True,
"instruments": {"electric_guitars": 6, "drum_kits": 3},
"technicians": [
{"name": "Frank Smith"},
],
},
),
)

# Test array indexing
response = (
await RecordingStudio.select(RecordingStudio.name)
.where(
RecordingStudio.facilities.from_path(
["technicians", 0, "name"]
)
== "Alice Jones"
)
.output(load_json=True)
)
assert response is not None
self.assertListEqual(response, [{"name": "Abbey Road"}])

# Test boolean
response = (
await RecordingStudio.select(RecordingStudio.name)
.where(
RecordingStudio.facilities.from_path(["restaurant"]).eq(True)
)
.output(load_json=True)
)
assert response is not None
self.assertListEqual(response, [{"name": "Electric Lady"}])
Loading