Skip to content
Merged
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
9 changes: 8 additions & 1 deletion piccolo/query/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,14 @@ async def _process_results(self, results) -> QueryResponseType:
if output._output.nested:
return cast(
QueryResponseType,
[make_nested_object(row, self.table) for row in raw],
[
make_nested_object(
row,
self.table,
load_json=output._output.load_json,
)
for row in raw
],
)
else:
return cast(
Expand Down
20 changes: 17 additions & 3 deletions piccolo/utils/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@
from typing import TYPE_CHECKING, Any

from piccolo.columns.column_types import ForeignKey
from piccolo.utils import encoding

if TYPE_CHECKING: # pragma: no cover
from piccolo.table import Table


def make_nested_object(row: dict[str, Any], table_class: type[Table]) -> Table:
def make_nested_object(
row: dict[str, Any],
table_class: type[Table],
load_json: bool = False,
) -> Table:
"""
Takes a nested dictionary such as this:

Expand Down Expand Up @@ -38,6 +43,12 @@ def make_nested_object(row: dict[str, Any], table_class: type[Table]) -> Table:
"""
table_params: dict[str, Any] = {}

json_column_names = (
[column._meta.name for column in table_class._meta.json_columns]
if load_json
else []
)

for key, value in row.items():
if isinstance(value, dict):
# This is probably a related table.
Expand All @@ -51,12 +62,15 @@ def make_nested_object(row: dict[str, Any], table_class: type[Table]) -> Table:
fk_column._foreign_key_meta.resolved_references
)
table_params[key] = make_nested_object(
value, related_table_class
value,
related_table_class,
load_json=load_json,
)
else:
# The value doesn't belong to a foreign key, so just append it.
table_params[key] = value

elif load_json and key in json_column_names:
table_params[key] = encoding.load_json(value)
else:
table_params[key] = value

Expand Down
50 changes: 42 additions & 8 deletions tests/table/test_join.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import decimal
from unittest import TestCase

from piccolo.testing.test_case import TableTest
from tests.base import engine_is
from tests.example_apps.music.tables import (
Band,
Concert,
Instrument,
Manager,
RecordingStudio,
Signing,
Ticket,
Venue,
Expand All @@ -23,12 +25,20 @@ def test_create_join(self):
table.alter().drop_table().run_sync()


class TestJoin(TestCase):
tables = [Manager, Band, Venue, Concert, Ticket, Signing]
class TestJoin(TableTest):
tables = [
Manager,
Band,
Venue,
Concert,
Ticket,
Signing,
Instrument,
RecordingStudio,
]

def setUp(self):
for table in self.tables:
table.create_table().run_sync()
super().setUp()

manager_1 = Manager(name="Guido")
manager_1.save().run_sync()
Expand Down Expand Up @@ -56,9 +66,13 @@ def setUp(self):
signing = Signing(with_=band_1)
signing.save().run_sync()

def tearDown(self):
for table in reversed(self.tables):
table.alter().drop_table().run_sync()
recording_studio = RecordingStudio(facilities={"restaurant": True})
recording_studio.save().run_sync()

instrument = Instrument(
name="Piccolo", recording_studio=recording_studio
)
instrument.save().run_sync()

###########################################################################

Expand Down Expand Up @@ -399,6 +413,26 @@ def test_objects__all_related__deep(self):
self.assertIsInstance(ticket.concert.band_1.manager, Manager)
self.assertIsInstance(ticket.concert.band_2.manager, Manager)

def test_objects_nested_with_load_json(self):
"""
Make sure that nested objects works alongside ``load_json`` (i.e. the
JSON on nested objects gets loaded).

https://github.com/piccolo-orm/piccolo/issues/1383

"""
instrument = (
Instrument.objects(Instrument.recording_studio)
.output(load_json=True)
.first()
.run_sync()
)
assert instrument is not None
self.assertDictEqual(
instrument.recording_studio.facilities,
{"restaurant": True},
)

def test_objects_prefetch_clause(self):
"""
Make sure that ``prefetch`` clause works correctly.
Expand Down
Loading