Skip to content

Commit 6d99086

Browse files
committed
added .output(load_json=True) option
1 parent 135789d commit 6d99086

File tree

7 files changed

+125
-10
lines changed

7 files changed

+125
-10
lines changed

piccolo/query/base.py

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@
33
from time import time
44
import typing as t
55

6+
from piccolo.columns.column_types import JSON, JSONB
7+
from piccolo.query.mixins import ColumnsDelegate
68
from piccolo.querystring import QueryString
79
from piccolo.utils.sync import run_sync
8-
from piccolo.utils.encoding import dump_json
10+
from piccolo.utils.encoding import dump_json, load_json
911

1012
if t.TYPE_CHECKING: # pragma: no cover
1113
from piccolo.table import Table # noqa
14+
from piccolo.query.mixins import OutputDelegate
1215

1316

1417
class Timer:
@@ -51,9 +54,54 @@ async def _process_results(self, results):
5154
if hasattr(self, "run_callback"):
5255
self.run_callback(raw)
5356

54-
raw = await self.response_handler(raw)
57+
output: t.Optional[OutputDelegate] = getattr(
58+
self, "output_delegate", None
59+
)
60+
61+
#######################################################################
62+
63+
if output and output._output.load_json:
64+
columns_delegate: t.Optional[ColumnsDelegate] = getattr(
65+
self, "columns_delegate", None
66+
)
67+
68+
if columns_delegate is not None:
69+
json_columns = [
70+
i
71+
for i in columns_delegate.selected_columns
72+
if isinstance(i, (JSON, JSONB))
73+
]
74+
else:
75+
json_columns = self.table._meta.json_columns
76+
77+
json_column_names = []
78+
for column in json_columns:
79+
if column.alias is not None:
80+
json_column_names.append(column.alias)
81+
elif len(column._meta.call_chain) > 0:
82+
json_column_names.append(
83+
column.get_select_string(
84+
engine_type=column._meta.engine_type
85+
)
86+
)
87+
else:
88+
json_column_names.append(column._meta.name)
89+
90+
processed_raw = []
91+
92+
for row in raw:
93+
new_row = {**row}
94+
for json_column_name in json_column_names:
95+
value = new_row.get(json_column_name)
96+
if value is not None:
97+
new_row[json_column_name] = load_json(value)
98+
processed_raw.append(new_row)
5599

56-
output = getattr(self, "output_delegate", None)
100+
raw = processed_raw
101+
102+
#######################################################################
103+
104+
raw = await self.response_handler(raw)
57105

58106
if output:
59107
if output._output.as_objects:

piccolo/query/methods/objects.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ def __init__(self, table: t.Type[Table], **kwargs):
4444
self.output_delegate._output.as_objects = True
4545
self.where_delegate = WhereDelegate()
4646

47+
def output(self, load_json: bool = False) -> Objects:
48+
self.output_delegate.output(
49+
as_list=False, as_json=False, load_json=load_json
50+
)
51+
return self
52+
4753
def limit(self, number: int) -> Objects:
4854
self.limit_delegate.limit(number)
4955
return self

piccolo/query/methods/select.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,15 @@ def order_by(self, *columns: Column, ascending=True) -> Select:
129129
self.order_by_delegate.order_by(*_columns, ascending=ascending)
130130
return self
131131

132-
def output(self, as_list: bool = False, as_json: bool = False) -> Select:
133-
self.output_delegate.output(as_list=as_list, as_json=as_json)
132+
def output(
133+
self,
134+
as_list: bool = False,
135+
as_json: bool = False,
136+
load_json: bool = False,
137+
) -> Select:
138+
self.output_delegate.output(
139+
as_list=as_list, as_json=as_json, load_json=load_json
140+
)
134141
return self
135142

136143
def where(self, where: Combinable) -> Select:

piccolo/query/mixins.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,14 @@ class Output:
7676
as_json: bool = False
7777
as_list: bool = False
7878
as_objects: bool = False
79+
load_json: bool = False
7980

8081
def copy(self) -> Output:
8182
return self.__class__(
8283
as_json=self.as_json,
8384
as_list=self.as_list,
8485
as_objects=self.as_objects,
86+
load_json=self.load_json,
8587
)
8688

8789

@@ -191,6 +193,7 @@ def output(
191193
self,
192194
as_list: t.Optional[bool] = None,
193195
as_json: t.Optional[bool] = None,
196+
load_json: t.Optional[bool] = None,
194197
):
195198
"""
196199
:param as_list:
@@ -199,13 +202,19 @@ def output(
199202
:param as_json:
200203
The results are serialised into JSON. It's equivalent to running
201204
`json.dumps` on the result.
205+
:param load_json:
206+
If True, any JSON fields will have the JSON values returned from
207+
the database loaded as Python objects.
202208
"""
203209
if as_list is not None:
204210
self._output.as_list = bool(as_list)
205211

206-
if type(as_json) is bool:
212+
if as_json is not None:
207213
self._output.as_json = bool(as_json)
208214

215+
if load_json is not None:
216+
self._output.load_json = bool(load_json)
217+
209218
def copy(self) -> OutputDelegate:
210219
_output = self._output.copy() if self._output is not None else None
211220
return self.__class__(_output=_output)

piccolo/table.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ def __init_subclass__(
217217
default_columns=default_columns,
218218
non_default_columns=non_default_columns,
219219
foreign_key_columns=foreign_key_columns,
220+
json_columns=json_columns,
220221
secret_columns=secret_columns,
221222
tags=tags,
222223
help_text=help_text,

tests/table/test_metaclass.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
from piccolo.columns.column_types import ForeignKey
1+
from piccolo.columns.column_types import ForeignKey, JSON, JSONB
22
from unittest import TestCase
33

44
from piccolo.columns import Secret
55
from piccolo.table import Table
66

7-
from ..example_app.tables import Band
7+
from tests.example_app.tables import Band
88

99

1010
class TestMetaClass(TestCase):
@@ -63,3 +63,16 @@ class Classified(Table):
6363
self.assertEqual(
6464
Classified._meta.secret_columns, [Classified.top_secret]
6565
)
66+
67+
def test_json_columns(self):
68+
"""
69+
Make sure TableMeta.json_columns are setup correctly.
70+
"""
71+
72+
class MyTable(Table):
73+
column_a = JSON()
74+
column_b = JSONB()
75+
76+
self.assertEqual(
77+
MyTable._meta.json_columns, [MyTable.column_a, MyTable.column_b]
78+
)

tests/table/test_output.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
from unittest import TestCase
12
import json
23

3-
from ..base import DBTestCase
4-
from ..example_app.tables import Band
4+
from tests.base import DBTestCase
5+
from tests.example_app.tables import Band, RecordingStudio
56

67

78
class TestOutputList(DBTestCase):
@@ -28,3 +29,33 @@ def test_output_as_json(self):
2829
response = Band.select(Band.name).output(as_json=True).run_sync()
2930

3031
self.assertTrue(json.loads(response) == [{"name": "Pythonistas"}])
32+
33+
34+
class TestOutputLoadJSON(TestCase):
35+
def setUp(self):
36+
RecordingStudio.create_table().run_sync()
37+
38+
def tearDown(self):
39+
RecordingStudio.alter().drop_table().run_sync()
40+
41+
def test_select(self):
42+
json = {"a": 123}
43+
44+
RecordingStudio(facilities=json, facilities_b=json).save().run_sync()
45+
46+
results = RecordingStudio.select().output(load_json=True).run_sync()
47+
48+
self.assertEqual(
49+
results,
50+
[{"id": 1, "facilities": {"a": 123}, "facilities_b": {"a": 123}}],
51+
)
52+
53+
def test_objects(self):
54+
json = {"a": 123}
55+
56+
RecordingStudio(facilities=json, facilities_b=json).save().run_sync()
57+
58+
results = RecordingStudio.objects().output(load_json=True).run_sync()
59+
60+
self.assertEqual(results[0].facilities, json)
61+
self.assertEqual(results[0].facilities_b, json)

0 commit comments

Comments
 (0)