piccolo 1.19.1__py3-none-any.whl → 1.21.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- piccolo/__init__.py +1 -1
- piccolo/query/methods/objects.py +58 -1
- piccolo/query/methods/select.py +1 -1
- piccolo/table.py +7 -18
- {piccolo-1.19.1.dist-info → piccolo-1.21.0.dist-info}/METADATA +1 -1
- {piccolo-1.19.1.dist-info → piccolo-1.21.0.dist-info}/RECORD +12 -12
- {piccolo-1.19.1.dist-info → piccolo-1.21.0.dist-info}/WHEEL +1 -1
- tests/columns/test_db_column_name.py +58 -13
- tests/table/instance/test_get_related.py +81 -25
- {piccolo-1.19.1.dist-info → piccolo-1.21.0.dist-info}/LICENSE +0 -0
- {piccolo-1.19.1.dist-info → piccolo-1.21.0.dist-info}/entry_points.txt +0 -0
- {piccolo-1.19.1.dist-info → piccolo-1.21.0.dist-info}/top_level.txt +0 -0
piccolo/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__VERSION__ = "1.
|
1
|
+
__VERSION__ = "1.21.0"
|
piccolo/query/methods/objects.py
CHANGED
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import typing as t
|
4
4
|
|
5
|
-
from piccolo.columns.column_types import ForeignKey
|
5
|
+
from piccolo.columns.column_types import ForeignKey, ReferencedTable
|
6
6
|
from piccolo.columns.combination import And, Where
|
7
7
|
from piccolo.custom_types import Combinable, TableInstance
|
8
8
|
from piccolo.engine.base import BaseBatch
|
@@ -231,6 +231,63 @@ class UpdateSelf:
|
|
231
231
|
return run_sync(self.run(*args, **kwargs))
|
232
232
|
|
233
233
|
|
234
|
+
class GetRelated(t.Generic[ReferencedTable]):
|
235
|
+
|
236
|
+
def __init__(self, row: Table, foreign_key: ForeignKey[ReferencedTable]):
|
237
|
+
self.row = row
|
238
|
+
self.foreign_key = foreign_key
|
239
|
+
|
240
|
+
async def run(
|
241
|
+
self,
|
242
|
+
node: t.Optional[str] = None,
|
243
|
+
in_pool: bool = True,
|
244
|
+
) -> t.Optional[ReferencedTable]:
|
245
|
+
if not self.row._exists_in_db:
|
246
|
+
raise ValueError("The object doesn't exist in the database.")
|
247
|
+
|
248
|
+
root_table = self.row.__class__
|
249
|
+
|
250
|
+
data = (
|
251
|
+
await root_table.select(
|
252
|
+
*[
|
253
|
+
i.as_alias(i._meta.name)
|
254
|
+
for i in self.foreign_key.all_columns()
|
255
|
+
]
|
256
|
+
)
|
257
|
+
.where(
|
258
|
+
root_table._meta.primary_key
|
259
|
+
== getattr(self.row, root_table._meta.primary_key._meta.name)
|
260
|
+
)
|
261
|
+
.first()
|
262
|
+
.run(node=node, in_pool=in_pool)
|
263
|
+
)
|
264
|
+
|
265
|
+
# Make sure that some values were returned:
|
266
|
+
if data is None or not any(data.values()):
|
267
|
+
return None
|
268
|
+
|
269
|
+
references = t.cast(
|
270
|
+
t.Type[ReferencedTable],
|
271
|
+
self.foreign_key._foreign_key_meta.resolved_references,
|
272
|
+
)
|
273
|
+
|
274
|
+
referenced_object = references(**data)
|
275
|
+
referenced_object._exists_in_db = True
|
276
|
+
return referenced_object
|
277
|
+
|
278
|
+
def __await__(
|
279
|
+
self,
|
280
|
+
) -> t.Generator[None, None, t.Optional[ReferencedTable]]:
|
281
|
+
"""
|
282
|
+
If the user doesn't explicity call .run(), proxy to it as a
|
283
|
+
convenience.
|
284
|
+
"""
|
285
|
+
return self.run().__await__()
|
286
|
+
|
287
|
+
def run_sync(self, *args, **kwargs) -> t.Optional[ReferencedTable]:
|
288
|
+
return run_sync(self.run(*args, **kwargs))
|
289
|
+
|
290
|
+
|
234
291
|
###############################################################################
|
235
292
|
|
236
293
|
|
piccolo/query/methods/select.py
CHANGED
@@ -534,7 +534,7 @@ class Select(Query[TableInstance, t.List[t.Dict[str, t.Any]]]):
|
|
534
534
|
_joins.append(
|
535
535
|
f'LEFT JOIN {right_tablename} "{table_alias}"'
|
536
536
|
" ON "
|
537
|
-
f'({left_tablename}."{key._meta.
|
537
|
+
f'({left_tablename}."{key._meta.db_column_name}" = "{table_alias}"."{pk_name}")' # noqa: E501
|
538
538
|
)
|
539
539
|
|
540
540
|
joins.extend(_joins)
|
piccolo/table.py
CHANGED
@@ -46,7 +46,7 @@ from piccolo.query import (
|
|
46
46
|
)
|
47
47
|
from piccolo.query.methods.create_index import CreateIndex
|
48
48
|
from piccolo.query.methods.indexes import Indexes
|
49
|
-
from piccolo.query.methods.objects import
|
49
|
+
from piccolo.query.methods.objects import GetRelated, UpdateSelf
|
50
50
|
from piccolo.query.methods.refresh import Refresh
|
51
51
|
from piccolo.querystring import QueryString
|
52
52
|
from piccolo.utils import _camel_to_snake
|
@@ -612,14 +612,14 @@ class Table(metaclass=TableMetaclass):
|
|
612
612
|
@t.overload
|
613
613
|
def get_related(
|
614
614
|
self, foreign_key: ForeignKey[ReferencedTable]
|
615
|
-
) ->
|
615
|
+
) -> GetRelated[ReferencedTable]: ...
|
616
616
|
|
617
617
|
@t.overload
|
618
|
-
def get_related(self, foreign_key: str) ->
|
618
|
+
def get_related(self, foreign_key: str) -> GetRelated[Table]: ...
|
619
619
|
|
620
620
|
def get_related(
|
621
621
|
self, foreign_key: t.Union[str, ForeignKey[ReferencedTable]]
|
622
|
-
) ->
|
622
|
+
) -> GetRelated[ReferencedTable]:
|
623
623
|
"""
|
624
624
|
Used to fetch a ``Table`` instance, for the target of a foreign key.
|
625
625
|
|
@@ -630,8 +630,8 @@ class Table(metaclass=TableMetaclass):
|
|
630
630
|
>>> print(manager.name)
|
631
631
|
'Guido'
|
632
632
|
|
633
|
-
It can only follow foreign keys
|
634
|
-
|
633
|
+
It can only follow foreign keys multiple levels deep. For example,
|
634
|
+
``Concert.band_1.manager``.
|
635
635
|
|
636
636
|
"""
|
637
637
|
if isinstance(foreign_key, str):
|
@@ -645,18 +645,7 @@ class Table(metaclass=TableMetaclass):
|
|
645
645
|
"ForeignKey column."
|
646
646
|
)
|
647
647
|
|
648
|
-
|
649
|
-
|
650
|
-
references = foreign_key._foreign_key_meta.resolved_references
|
651
|
-
|
652
|
-
return (
|
653
|
-
references.objects()
|
654
|
-
.where(
|
655
|
-
foreign_key._foreign_key_meta.resolved_target_column
|
656
|
-
== getattr(self, column_name)
|
657
|
-
)
|
658
|
-
.first()
|
659
|
-
)
|
648
|
+
return GetRelated(foreign_key=foreign_key, row=self)
|
660
649
|
|
661
650
|
def get_m2m(self, m2m: M2M) -> M2MGetRelated:
|
662
651
|
"""
|
@@ -1,10 +1,10 @@
|
|
1
|
-
piccolo/__init__.py,sha256=
|
1
|
+
piccolo/__init__.py,sha256=lfg4osIcO7gnlgKb0zY1KT8t1Ngd2pATD-6YnBkIDcU,23
|
2
2
|
piccolo/custom_types.py,sha256=7HMQAze-5mieNLfbQ5QgbRQgR2abR7ol0qehv2SqROY,604
|
3
3
|
piccolo/main.py,sha256=1VsFV67FWTUikPTysp64Fmgd9QBVa_9wcwKfwj2UCEA,5117
|
4
4
|
piccolo/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
5
|
piccolo/querystring.py,sha256=yZdURtiVSlxkkEVJoFKAmL2OfNxrUr8xfsfuBBB7IuY,9662
|
6
6
|
piccolo/schema.py,sha256=qNNy4tG_HqnXR9t3hHMgYXtGxHabwQAhUpc6RKLJ_gE,7960
|
7
|
-
piccolo/table.py,sha256=
|
7
|
+
piccolo/table.py,sha256=UvEbagMYRkTbyFHTUwUshZlL_dC4UKDP7vUOwF8OXmg,50593
|
8
8
|
piccolo/table_reflection.py,sha256=jrN1nHerDJ4tU09GtNN3hz7ap-7rXnSUjljFO6LB2H0,7094
|
9
9
|
piccolo/apps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
10
|
piccolo/apps/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -166,10 +166,10 @@ piccolo/query/methods/drop_index.py,sha256=5x3vHpoOmQ1SMhj6L7snKXX6M9l9j1E1PFSO6
|
|
166
166
|
piccolo/query/methods/exists.py,sha256=lTMjtrFPFygZmaPV3sfQKXc3K0sVqJ2S6PDc3fRK6YQ,1203
|
167
167
|
piccolo/query/methods/indexes.py,sha256=J-QUqaBJwpgahskUH0Cu0Mq7zEKcfVAtDsUVIVX-C4c,943
|
168
168
|
piccolo/query/methods/insert.py,sha256=ssLJ_wn08KnOwwr7t-VILyn1P4hrvM63CfPIcAJWT5k,4701
|
169
|
-
piccolo/query/methods/objects.py,sha256=
|
169
|
+
piccolo/query/methods/objects.py,sha256=BfCOIbNMj7FWkmK5STaINkfDFmwzZvDZi60jCumjs1o,15627
|
170
170
|
piccolo/query/methods/raw.py,sha256=wQWR8b-yA_Gr-5lqRMZe9BOAAMBAw8CqTx37qVYvM1A,987
|
171
171
|
piccolo/query/methods/refresh.py,sha256=wg1zghKfwz-VmqK4uWa4GNMiDtK-skTqow591Hb3ONM,5854
|
172
|
-
piccolo/query/methods/select.py,sha256=
|
172
|
+
piccolo/query/methods/select.py,sha256=41OW-DIE_wr5VdxSusMKNT2aUhzQsCwK2Qh1XqgXHg0,22424
|
173
173
|
piccolo/query/methods/table_exists.py,sha256=0yb3n6Jd2ovSBWlZ-gl00K4E7Jnbj7J8qAAX5d7hvNk,1259
|
174
174
|
piccolo/query/methods/update.py,sha256=LfWqIXEl1aecc0rkVssTFmwyD6wXGhlKcTrUVhtlEsw,3705
|
175
175
|
piccolo/testing/__init__.py,sha256=pRFSqRInfx95AakOq54atmvqoB-ue073q2aR8u8zR40,83
|
@@ -253,7 +253,7 @@ tests/columns/test_bytea.py,sha256=doN8S1eFVU4ntSXIg4IgMSZcbvqW1WJ-AEm3OjKLGkI,1
|
|
253
253
|
tests/columns/test_choices.py,sha256=q8TLe7nvGERXyGO_XEryEBR-DuWwFY1jPpscsrXjdXo,4066
|
254
254
|
tests/columns/test_combination.py,sha256=BuBwR7k5X1EkOWraZpjqU6gvtb6ow_k-7N1KQBiW2RA,1681
|
255
255
|
tests/columns/test_date.py,sha256=QLC6kJMQwM-1mbUP4ksJVM7P8WwjzGZyynH3rHHdSew,1030
|
256
|
-
tests/columns/test_db_column_name.py,sha256=
|
256
|
+
tests/columns/test_db_column_name.py,sha256=0wz6y4GNGy4nhMdHmYzEnChQGpK2UhWFFKrnmmML3Mk,9027
|
257
257
|
tests/columns/test_defaults.py,sha256=rwlU1fXt3cCl7C51eLlZXqgWkE-K5W0pHvTrwkAKyCo,2896
|
258
258
|
tests/columns/test_double_precision.py,sha256=7rhcSfDkb2fBh_zEG4UGwD_GW1sy6U9-8NooHuCS09Q,544
|
259
259
|
tests/columns/test_get_sql_value.py,sha256=mKgsInN374jzV99y9mg_ZiG-AvnJgz36SZi89xL7RZM,1768
|
@@ -346,7 +346,7 @@ tests/table/test_update.py,sha256=Cqi0xX3kEuJ0k-x_emPGB3arXuGWZ9e3CJ3HPFnw9Zw,20
|
|
346
346
|
tests/table/test_update_self.py,sha256=im6HcM-WLkEhZP0vTL42tYEJZyAZG6gDOxCnbikCBD4,907
|
347
347
|
tests/table/instance/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
348
348
|
tests/table/instance/test_create.py,sha256=JD0l7L9dDK1FKPhUs6WC_B2bruPR1qQ8aIqXpEbfiUg,1105
|
349
|
-
tests/table/instance/test_get_related.py,sha256=
|
349
|
+
tests/table/instance/test_get_related.py,sha256=eracFunh4Qlj5BEkI7OsrOyefRZM0rxrXnFX92VL1ZE,3285
|
350
350
|
tests/table/instance/test_get_related_readable.py,sha256=QDMMZykxPsTWcsl8ZIZtmQVLwSGCw7QBilLepAAAnWg,4694
|
351
351
|
tests/table/instance/test_instantiate.py,sha256=jvtaqSa_zN1lHQiykN4EnwitZqkWAbXle5IJtyhKuHY,958
|
352
352
|
tests/table/instance/test_remove.py,sha256=Zv22ZZqot61rjCVWL1PHDf1oxELcBnmMXx1gsST6j80,648
|
@@ -368,9 +368,9 @@ tests/utils/test_sql_values.py,sha256=vzxRmy16FfLZPH-sAQexBvsF9MXB8n4smr14qoEOS5
|
|
368
368
|
tests/utils/test_sync.py,sha256=9ytVo56y2vPQePvTeIi9lHIouEhWJbodl1TmzkGFrSo,799
|
369
369
|
tests/utils/test_table_reflection.py,sha256=SIzuat-IpcVj1GCFyOWKShI8YkhdOPPFH7qVrvfyPNE,3794
|
370
370
|
tests/utils/test_warnings.py,sha256=NvSC_cvJ6uZcwAGf1m-hLzETXCqprXELL8zg3TNLVMw,269
|
371
|
-
piccolo-1.
|
372
|
-
piccolo-1.
|
373
|
-
piccolo-1.
|
374
|
-
piccolo-1.
|
375
|
-
piccolo-1.
|
376
|
-
piccolo-1.
|
371
|
+
piccolo-1.21.0.dist-info/LICENSE,sha256=zFIpi-16uIJ420UMIG75NU0JbDBykvrdnXcj5U_EYBI,1059
|
372
|
+
piccolo-1.21.0.dist-info/METADATA,sha256=CMfZm7IYerdMTQLFwzuyCPWZMlrGf1G_shqoq2LlnGM,5178
|
373
|
+
piccolo-1.21.0.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
|
374
|
+
piccolo-1.21.0.dist-info/entry_points.txt,sha256=SJPHET4Fi1bN5F3WqcKkv9SClK3_F1I7m4eQjk6AFh0,46
|
375
|
+
piccolo-1.21.0.dist-info/top_level.txt,sha256=-SR74VGbk43VoPy1HH-mHm97yoGukLK87HE5kdBW6qM,24
|
376
|
+
piccolo-1.21.0.dist-info/RECORD,,
|
@@ -1,12 +1,20 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
import typing as t
|
2
|
+
|
3
|
+
from piccolo.columns.column_types import ForeignKey, Integer, Serial, Varchar
|
4
|
+
from piccolo.table import Table, create_db_tables_sync, drop_db_tables_sync
|
3
5
|
from tests.base import DBTestCase, engine_is, engines_only, engines_skip
|
4
6
|
|
5
7
|
|
8
|
+
class Manager(Table):
|
9
|
+
id: Serial
|
10
|
+
name = Varchar()
|
11
|
+
|
12
|
+
|
6
13
|
class Band(Table):
|
7
14
|
id: Serial
|
8
15
|
name = Varchar(db_column_name="regrettable_column_name")
|
9
16
|
popularity = Integer()
|
17
|
+
manager = ForeignKey(Manager, db_column_name="manager_fk")
|
10
18
|
|
11
19
|
|
12
20
|
class TestDBColumnName(DBTestCase):
|
@@ -22,10 +30,15 @@ class TestDBColumnName(DBTestCase):
|
|
22
30
|
"""
|
23
31
|
|
24
32
|
def setUp(self):
|
25
|
-
Band
|
33
|
+
create_db_tables_sync(Band, Manager)
|
26
34
|
|
27
35
|
def tearDown(self):
|
28
|
-
Band
|
36
|
+
drop_db_tables_sync(Band, Manager)
|
37
|
+
|
38
|
+
def insert_band(self, manager: t.Optional[Manager] = None) -> Band:
|
39
|
+
band = Band(name="Pythonistas", popularity=1000, manager=manager)
|
40
|
+
band.save().run_sync()
|
41
|
+
return band
|
29
42
|
|
30
43
|
@engines_only("postgres", "cockroach")
|
31
44
|
def test_column_name_correct(self):
|
@@ -45,8 +58,7 @@ class TestDBColumnName(DBTestCase):
|
|
45
58
|
"""
|
46
59
|
Make sure save queries work correctly.
|
47
60
|
"""
|
48
|
-
|
49
|
-
band.save().run_sync()
|
61
|
+
self.insert_band()
|
50
62
|
|
51
63
|
band_from_db = Band.objects().first().run_sync()
|
52
64
|
assert band_from_db is not None
|
@@ -56,11 +68,7 @@ class TestDBColumnName(DBTestCase):
|
|
56
68
|
"""
|
57
69
|
Make sure create queries work correctly.
|
58
70
|
"""
|
59
|
-
band = (
|
60
|
-
Band.objects()
|
61
|
-
.create(name="Pythonistas", popularity=1000)
|
62
|
-
.run_sync()
|
63
|
-
)
|
71
|
+
band = self.insert_band()
|
64
72
|
self.assertEqual(band.name, "Pythonistas")
|
65
73
|
|
66
74
|
band_from_db = Band.objects().first().run_sync()
|
@@ -74,7 +82,7 @@ class TestDBColumnName(DBTestCase):
|
|
74
82
|
name to it's alias, but it's hard to predict what behaviour the user
|
75
83
|
wants.
|
76
84
|
"""
|
77
|
-
|
85
|
+
self.insert_band()
|
78
86
|
|
79
87
|
# Make sure we can select all columns
|
80
88
|
bands = Band.select().run_sync()
|
@@ -86,6 +94,7 @@ class TestDBColumnName(DBTestCase):
|
|
86
94
|
"id": bands[0]["id"],
|
87
95
|
"regrettable_column_name": "Pythonistas",
|
88
96
|
"popularity": 1000,
|
97
|
+
"manager_fk": None,
|
89
98
|
}
|
90
99
|
],
|
91
100
|
)
|
@@ -97,6 +106,7 @@ class TestDBColumnName(DBTestCase):
|
|
97
106
|
"id": 1,
|
98
107
|
"regrettable_column_name": "Pythonistas",
|
99
108
|
"popularity": 1000,
|
109
|
+
"manager_fk": None,
|
100
110
|
}
|
101
111
|
],
|
102
112
|
)
|
@@ -123,11 +133,36 @@ class TestDBColumnName(DBTestCase):
|
|
123
133
|
],
|
124
134
|
)
|
125
135
|
|
136
|
+
def test_join(self):
|
137
|
+
"""
|
138
|
+
Make sure that foreign keys with a ``db_column_name`` specified still
|
139
|
+
work for joins.
|
140
|
+
|
141
|
+
https://github.com/piccolo-orm/piccolo/issues/1101
|
142
|
+
|
143
|
+
"""
|
144
|
+
manager = Manager.objects().create(name="Guido").run_sync()
|
145
|
+
band = self.insert_band(manager=manager)
|
146
|
+
|
147
|
+
bands = Band.select().where(Band.manager.name == "Guido").run_sync()
|
148
|
+
|
149
|
+
self.assertListEqual(
|
150
|
+
bands,
|
151
|
+
[
|
152
|
+
{
|
153
|
+
"id": band.id,
|
154
|
+
"manager_fk": manager.id,
|
155
|
+
"popularity": 1000,
|
156
|
+
"regrettable_column_name": "Pythonistas",
|
157
|
+
}
|
158
|
+
],
|
159
|
+
)
|
160
|
+
|
126
161
|
def test_update(self):
|
127
162
|
"""
|
128
163
|
Make sure update queries work correctly.
|
129
164
|
"""
|
130
|
-
|
165
|
+
self.insert_band()
|
131
166
|
|
132
167
|
Band.update({Band.name: "Pythonistas 2"}, force=True).run_sync()
|
133
168
|
|
@@ -140,6 +175,7 @@ class TestDBColumnName(DBTestCase):
|
|
140
175
|
"id": bands[0]["id"],
|
141
176
|
"regrettable_column_name": "Pythonistas 2",
|
142
177
|
"popularity": 1000,
|
178
|
+
"manager_fk": None,
|
143
179
|
}
|
144
180
|
],
|
145
181
|
)
|
@@ -151,6 +187,7 @@ class TestDBColumnName(DBTestCase):
|
|
151
187
|
"id": 1,
|
152
188
|
"regrettable_column_name": "Pythonistas 2",
|
153
189
|
"popularity": 1000,
|
190
|
+
"manager_fk": None,
|
154
191
|
}
|
155
192
|
],
|
156
193
|
)
|
@@ -166,6 +203,7 @@ class TestDBColumnName(DBTestCase):
|
|
166
203
|
"id": bands[0]["id"],
|
167
204
|
"regrettable_column_name": "Pythonistas 3",
|
168
205
|
"popularity": 1000,
|
206
|
+
"manager_fk": None,
|
169
207
|
}
|
170
208
|
],
|
171
209
|
)
|
@@ -177,6 +215,7 @@ class TestDBColumnName(DBTestCase):
|
|
177
215
|
"id": 1,
|
178
216
|
"regrettable_column_name": "Pythonistas 3",
|
179
217
|
"popularity": 1000,
|
218
|
+
"manager_fk": None,
|
180
219
|
}
|
181
220
|
],
|
182
221
|
)
|
@@ -199,11 +238,13 @@ class TestDBColumnName(DBTestCase):
|
|
199
238
|
"id": 1,
|
200
239
|
"regrettable_column_name": "Pythonistas",
|
201
240
|
"popularity": 1000,
|
241
|
+
"manager_fk": None,
|
202
242
|
},
|
203
243
|
{
|
204
244
|
"id": 2,
|
205
245
|
"regrettable_column_name": "Rustaceans",
|
206
246
|
"popularity": 500,
|
247
|
+
"manager_fk": None,
|
207
248
|
},
|
208
249
|
],
|
209
250
|
)
|
@@ -218,6 +259,7 @@ class TestDBColumnName(DBTestCase):
|
|
218
259
|
"id": 1,
|
219
260
|
"regrettable_column_name": "Pythonistas",
|
220
261
|
"popularity": 1000,
|
262
|
+
"manager_fk": None,
|
221
263
|
}
|
222
264
|
],
|
223
265
|
)
|
@@ -244,11 +286,13 @@ class TestDBColumnName(DBTestCase):
|
|
244
286
|
"id": result[0]["id"],
|
245
287
|
"regrettable_column_name": "Pythonistas",
|
246
288
|
"popularity": 1000,
|
289
|
+
"manager_fk": None,
|
247
290
|
},
|
248
291
|
{
|
249
292
|
"id": result[1]["id"],
|
250
293
|
"regrettable_column_name": "Rustaceans",
|
251
294
|
"popularity": 500,
|
295
|
+
"manager_fk": None,
|
252
296
|
},
|
253
297
|
],
|
254
298
|
)
|
@@ -263,6 +307,7 @@ class TestDBColumnName(DBTestCase):
|
|
263
307
|
"id": result[0]["id"],
|
264
308
|
"regrettable_column_name": "Pythonistas",
|
265
309
|
"popularity": 1000,
|
310
|
+
"manager_fk": None,
|
266
311
|
}
|
267
312
|
],
|
268
313
|
)
|
@@ -1,42 +1,98 @@
|
|
1
1
|
import typing as t
|
2
|
-
from unittest import TestCase
|
3
2
|
|
4
|
-
from
|
3
|
+
from piccolo.testing.test_case import AsyncTableTest
|
4
|
+
from tests.example_apps.music.tables import Band, Concert, Manager, Venue
|
5
5
|
|
6
|
-
TABLES = [Manager, Band]
|
7
6
|
|
7
|
+
class TestGetRelated(AsyncTableTest):
|
8
|
+
tables = [Manager, Band, Concert, Venue]
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
for table in TABLES:
|
12
|
-
table.create_table().run_sync()
|
10
|
+
async def asyncSetUp(self):
|
11
|
+
await super().asyncSetUp()
|
13
12
|
|
14
|
-
|
15
|
-
|
16
|
-
table.alter().drop_table().run_sync()
|
13
|
+
# Setup two pairs of manager/band, so we can make sure the correct
|
14
|
+
# objects are returned.
|
17
15
|
|
18
|
-
|
16
|
+
self.manager = Manager(name="Guido")
|
17
|
+
await self.manager.save()
|
18
|
+
|
19
|
+
self.band = Band(
|
20
|
+
name="Pythonistas", manager=self.manager.id, popularity=100
|
21
|
+
)
|
22
|
+
await self.band.save()
|
23
|
+
|
24
|
+
self.manager_2 = Manager(name="Graydon")
|
25
|
+
await self.manager_2.save()
|
26
|
+
|
27
|
+
self.band_2 = Band(
|
28
|
+
name="Rustaceans", manager=self.manager_2.id, popularity=100
|
29
|
+
)
|
30
|
+
await self.band_2.save()
|
31
|
+
|
32
|
+
async def test_foreign_key(self) -> None:
|
19
33
|
"""
|
20
34
|
Make sure you can get a related object from another object instance.
|
21
35
|
"""
|
22
|
-
manager =
|
23
|
-
manager
|
36
|
+
manager = await self.band.get_related(Band.manager)
|
37
|
+
assert manager is not None
|
38
|
+
self.assertTrue(manager.id == self.manager.id)
|
39
|
+
|
40
|
+
manager_2 = await self.band_2.get_related(Band.manager)
|
41
|
+
assert manager_2 is not None
|
42
|
+
self.assertTrue(manager_2.id == self.manager_2.id)
|
24
43
|
|
25
|
-
|
26
|
-
|
44
|
+
async def test_non_foreign_key(self):
|
45
|
+
"""
|
46
|
+
Make sure that non-ForeignKey raise an exception.
|
47
|
+
"""
|
48
|
+
with self.assertRaises(ValueError):
|
49
|
+
self.band.get_related(Band.name) # type: ignore
|
27
50
|
|
28
|
-
|
29
|
-
|
30
|
-
|
51
|
+
async def test_string(self):
|
52
|
+
"""
|
53
|
+
Make sure it also works using a string representation of a foreign key.
|
54
|
+
"""
|
55
|
+
manager = t.cast(Manager, await self.band.get_related("manager"))
|
56
|
+
self.assertTrue(manager.id == self.manager.id)
|
31
57
|
|
32
|
-
|
58
|
+
async def test_invalid_string(self):
|
59
|
+
"""
|
60
|
+
Make sure an exception is raised if the foreign key string is invalid.
|
61
|
+
"""
|
33
62
|
with self.assertRaises(ValueError):
|
34
|
-
band.get_related(
|
63
|
+
self.band.get_related("abc123")
|
35
64
|
|
36
|
-
|
37
|
-
|
38
|
-
|
65
|
+
async def test_multiple_levels(self):
|
66
|
+
"""
|
67
|
+
Make sure ``get_related`` works multiple levels deep.
|
68
|
+
"""
|
69
|
+
concert = Concert(band_1=self.band, band_2=self.band_2)
|
70
|
+
await concert.save()
|
71
|
+
|
72
|
+
manager = await concert.get_related(Concert.band_1._.manager)
|
73
|
+
assert manager is not None
|
74
|
+
self.assertTrue(manager.id == self.manager.id)
|
75
|
+
|
76
|
+
manager_2 = await concert.get_related(Concert.band_2._.manager)
|
77
|
+
assert manager_2 is not None
|
78
|
+
self.assertTrue(manager_2.id == self.manager_2.id)
|
79
|
+
|
80
|
+
async def test_no_match(self):
|
81
|
+
"""
|
82
|
+
If not related object exists, make sure ``None`` is returned.
|
83
|
+
"""
|
84
|
+
concert = Concert(band_1=self.band, band_2=None)
|
85
|
+
await concert.save()
|
86
|
+
|
87
|
+
manager_2 = await concert.get_related(Concert.band_2._.manager)
|
88
|
+
assert manager_2 is None
|
89
|
+
|
90
|
+
async def test_not_in_db(self):
|
91
|
+
"""
|
92
|
+
If the object we're calling ``get_related`` on doesn't exist in the
|
93
|
+
database, then make sure an error is raised.
|
94
|
+
"""
|
95
|
+
concert = Concert(band_1=self.band, band_2=self.band_2)
|
39
96
|
|
40
|
-
# Test an invalid string
|
41
97
|
with self.assertRaises(ValueError):
|
42
|
-
|
98
|
+
await concert.get_related(Concert.band_1._.manager)
|
File without changes
|
File without changes
|
File without changes
|