piccolo 0.114.0__py3-none-any.whl → 0.115.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.
@@ -0,0 +1,436 @@
1
+ import asyncio
2
+ import datetime
3
+ import decimal
4
+ import uuid
5
+ from unittest import TestCase
6
+
7
+ from tests.base import engines_skip
8
+
9
+ try:
10
+ from asyncpg.pgproto.pgproto import UUID as asyncpgUUID
11
+ except ImportError:
12
+ # In case someone is running the tests for SQLite and doesn't have asyncpg
13
+ # installed.
14
+ from uuid import UUID as asyncpgUUID
15
+
16
+ from piccolo.columns.column_types import (
17
+ JSON,
18
+ JSONB,
19
+ UUID,
20
+ Array,
21
+ BigInt,
22
+ Boolean,
23
+ Bytea,
24
+ Date,
25
+ DoublePrecision,
26
+ ForeignKey,
27
+ Integer,
28
+ Interval,
29
+ LazyTableReference,
30
+ Numeric,
31
+ Real,
32
+ SmallInt,
33
+ Text,
34
+ Timestamp,
35
+ Timestamptz,
36
+ Varchar,
37
+ )
38
+ from piccolo.columns.m2m import M2M
39
+ from piccolo.engine.finder import engine_finder
40
+ from piccolo.table import Table, create_db_tables_sync, drop_db_tables_sync
41
+
42
+ from .base import M2MBase
43
+
44
+ engine = engine_finder()
45
+
46
+
47
+ class Band(Table):
48
+ name = Varchar()
49
+ genres = M2M(LazyTableReference("GenreToBand", module_path=__name__))
50
+
51
+
52
+ class Genre(Table):
53
+ name = Varchar()
54
+ bands = M2M(LazyTableReference("GenreToBand", module_path=__name__))
55
+
56
+
57
+ class GenreToBand(Table):
58
+ band = ForeignKey(Band)
59
+ genre = ForeignKey(Genre)
60
+ reason = Text(help_text="For testing additional columns on join tables.")
61
+
62
+
63
+ class TestM2M(M2MBase, TestCase):
64
+ band = Band
65
+ genre = Genre
66
+ genre_to_band = GenreToBand
67
+ all_tables = [Band, Genre, GenreToBand]
68
+
69
+
70
+ ###############################################################################
71
+
72
+ # A schema using custom primary keys
73
+
74
+
75
+ class Customer(Table):
76
+ uuid = UUID(primary_key=True)
77
+ name = Varchar()
78
+ concerts = M2M(
79
+ LazyTableReference("CustomerToConcert", module_path=__name__)
80
+ )
81
+
82
+
83
+ class Concert(Table):
84
+ uuid = UUID(primary_key=True)
85
+ name = Varchar()
86
+ customers = M2M(
87
+ LazyTableReference("CustomerToConcert", module_path=__name__)
88
+ )
89
+
90
+
91
+ class CustomerToConcert(Table):
92
+ customer = ForeignKey(Customer)
93
+ concert = ForeignKey(Concert)
94
+
95
+
96
+ CUSTOM_PK_SCHEMA = [Customer, Concert, CustomerToConcert]
97
+
98
+
99
+ class TestM2MCustomPrimaryKey(TestCase):
100
+ """
101
+ Make sure the M2M functionality works correctly when the tables have custom
102
+ primary key columns.
103
+ """
104
+
105
+ def setUp(self):
106
+ create_db_tables_sync(*CUSTOM_PK_SCHEMA, if_not_exists=True)
107
+
108
+ bob = Customer.objects().create(name="Bob").run_sync()
109
+ sally = Customer.objects().create(name="Sally").run_sync()
110
+ fred = Customer.objects().create(name="Fred").run_sync()
111
+
112
+ rockfest = Concert.objects().create(name="Rockfest").run_sync()
113
+ folkfest = Concert.objects().create(name="Folkfest").run_sync()
114
+ classicfest = Concert.objects().create(name="Classicfest").run_sync()
115
+
116
+ CustomerToConcert.insert(
117
+ CustomerToConcert(customer=bob, concert=rockfest),
118
+ CustomerToConcert(customer=bob, concert=classicfest),
119
+ CustomerToConcert(customer=sally, concert=rockfest),
120
+ CustomerToConcert(customer=sally, concert=folkfest),
121
+ CustomerToConcert(customer=fred, concert=classicfest),
122
+ ).run_sync()
123
+
124
+ def tearDown(self):
125
+ drop_db_tables_sync(*CUSTOM_PK_SCHEMA)
126
+
127
+ @engines_skip("cockroach")
128
+ def test_select(self):
129
+ """
130
+ 🐛 Cockroach bug: https://github.com/cockroachdb/cockroach/issues/71908 "could not decorrelate subquery" error under asyncpg
131
+ """ # noqa: E501
132
+ response = Customer.select(
133
+ Customer.name, Customer.concerts(Concert.name, as_list=True)
134
+ ).run_sync()
135
+
136
+ self.assertListEqual(
137
+ response,
138
+ [
139
+ {"name": "Bob", "concerts": ["Rockfest", "Classicfest"]},
140
+ {"name": "Sally", "concerts": ["Rockfest", "Folkfest"]},
141
+ {"name": "Fred", "concerts": ["Classicfest"]},
142
+ ],
143
+ )
144
+
145
+ # Now try it in reverse.
146
+ response = Concert.select(
147
+ Concert.name, Concert.customers(Customer.name, as_list=True)
148
+ ).run_sync()
149
+
150
+ self.assertListEqual(
151
+ response,
152
+ [
153
+ {"name": "Rockfest", "customers": ["Bob", "Sally"]},
154
+ {"name": "Folkfest", "customers": ["Sally"]},
155
+ {"name": "Classicfest", "customers": ["Bob", "Fred"]},
156
+ ],
157
+ )
158
+
159
+ def test_add_m2m(self):
160
+ """
161
+ Make sure we can add items to the joining table.
162
+ """
163
+ customer: Customer = (
164
+ Customer.objects().get(Customer.name == "Bob").run_sync()
165
+ )
166
+ customer.add_m2m(
167
+ Concert(name="Jazzfest"), m2m=Customer.concerts
168
+ ).run_sync()
169
+
170
+ self.assertTrue(
171
+ Concert.exists().where(Concert.name == "Jazzfest").run_sync()
172
+ )
173
+
174
+ self.assertEqual(
175
+ CustomerToConcert.count()
176
+ .where(
177
+ CustomerToConcert.customer.name == "Bob",
178
+ CustomerToConcert.concert.name == "Jazzfest",
179
+ )
180
+ .run_sync(),
181
+ 1,
182
+ )
183
+
184
+ def test_add_m2m_within_transaction(self):
185
+ """
186
+ Make sure we can add items to the joining table, when within an
187
+ existing transaction.
188
+
189
+ https://github.com/piccolo-orm/piccolo/issues/674
190
+
191
+ """
192
+ engine = Customer._meta.db
193
+
194
+ async def add_m2m_in_transaction():
195
+ async with engine.transaction():
196
+ customer: Customer = await Customer.objects().get(
197
+ Customer.name == "Bob"
198
+ )
199
+ await customer.add_m2m(
200
+ Concert(name="Jazzfest"), m2m=Customer.concerts
201
+ )
202
+
203
+ asyncio.run(add_m2m_in_transaction())
204
+
205
+ self.assertTrue(
206
+ Concert.exists().where(Concert.name == "Jazzfest").run_sync()
207
+ )
208
+
209
+ self.assertEqual(
210
+ CustomerToConcert.count()
211
+ .where(
212
+ CustomerToConcert.customer.name == "Bob",
213
+ CustomerToConcert.concert.name == "Jazzfest",
214
+ )
215
+ .run_sync(),
216
+ 1,
217
+ )
218
+
219
+ def test_get_m2m(self):
220
+ """
221
+ Make sure we can get related items via the joining table.
222
+ """
223
+ customer: Customer = (
224
+ Customer.objects().get(Customer.name == "Bob").run_sync()
225
+ )
226
+
227
+ concerts = customer.get_m2m(Customer.concerts).run_sync()
228
+
229
+ self.assertTrue(all(isinstance(i, Table) for i in concerts))
230
+
231
+ self.assertCountEqual(
232
+ [i.name for i in concerts], ["Rockfest", "Classicfest"]
233
+ )
234
+
235
+
236
+ ###############################################################################
237
+
238
+ # Test a very complex schema
239
+
240
+
241
+ class SmallTable(Table):
242
+ varchar_col = Varchar()
243
+ mega_rows = M2M(LazyTableReference("SmallToMega", module_path=__name__))
244
+
245
+
246
+ if engine.engine_type != "cockroach": # type: ignore
247
+
248
+ class MegaTable(Table): # type: ignore
249
+ """
250
+ A table containing all of the column types and different column kwargs
251
+ """
252
+
253
+ array_col = Array(Varchar())
254
+ bigint_col = BigInt()
255
+ boolean_col = Boolean()
256
+ bytea_col = Bytea()
257
+ date_col = Date()
258
+ double_precision_col = DoublePrecision()
259
+ integer_col = Integer()
260
+ interval_col = Interval()
261
+ json_col = JSON()
262
+ jsonb_col = JSONB()
263
+ numeric_col = Numeric(digits=(5, 2))
264
+ real_col = Real()
265
+ smallint_col = SmallInt()
266
+ text_col = Text()
267
+ timestamp_col = Timestamp()
268
+ timestamptz_col = Timestamptz()
269
+ uuid_col = UUID()
270
+ varchar_col = Varchar()
271
+
272
+ else:
273
+
274
+ class MegaTable(Table): # type: ignore
275
+ """
276
+ Special version for Cockroach.
277
+ A table containing all of the column types and different column kwargs
278
+ """
279
+
280
+ array_col = Array(Varchar())
281
+ bigint_col = BigInt()
282
+ boolean_col = Boolean()
283
+ bytea_col = Bytea()
284
+ date_col = Date()
285
+ double_precision_col = DoublePrecision()
286
+ integer_col = BigInt()
287
+ interval_col = Interval()
288
+ json_col = JSONB()
289
+ jsonb_col = JSONB()
290
+ numeric_col = Numeric(digits=(5, 2))
291
+ real_col = Real()
292
+ smallint_col = SmallInt()
293
+ text_col = Text()
294
+ timestamp_col = Timestamp()
295
+ timestamptz_col = Timestamptz()
296
+ uuid_col = UUID()
297
+ varchar_col = Varchar()
298
+
299
+
300
+ class SmallToMega(Table):
301
+ small = ForeignKey(MegaTable)
302
+ mega = ForeignKey(SmallTable)
303
+
304
+
305
+ COMPLEX_SCHEMA = [MegaTable, SmallTable, SmallToMega]
306
+
307
+
308
+ class TestM2MComplexSchema(TestCase):
309
+ """
310
+ By using a very complex schema containing every column type, we can catch
311
+ more edge cases.
312
+ """
313
+
314
+ def setUp(self):
315
+ create_db_tables_sync(*COMPLEX_SCHEMA, if_not_exists=True)
316
+
317
+ small_table = SmallTable(varchar_col="Test")
318
+ small_table.save().run_sync()
319
+
320
+ mega_table = MegaTable(
321
+ array_col=["bob", "sally"],
322
+ bigint_col=1,
323
+ boolean_col=True,
324
+ bytea_col="hello".encode("utf8"),
325
+ date_col=datetime.date(year=2021, month=1, day=1),
326
+ double_precision_col=1.344,
327
+ integer_col=1,
328
+ interval_col=datetime.timedelta(seconds=10),
329
+ json_col={"a": 1},
330
+ jsonb_col={"a": 1},
331
+ numeric_col=decimal.Decimal("1.1"),
332
+ real_col=1.1,
333
+ smallint_col=1,
334
+ text_col="hello",
335
+ timestamp_col=datetime.datetime(year=2021, month=1, day=1),
336
+ timestamptz_col=datetime.datetime(
337
+ year=2021, month=1, day=1, tzinfo=datetime.timezone.utc
338
+ ),
339
+ uuid_col=uuid.UUID("12783854-c012-4c15-8183-8eecb46f2c4e"),
340
+ varchar_col="hello",
341
+ )
342
+ mega_table.save().run_sync()
343
+
344
+ SmallToMega(small=small_table, mega=mega_table).save().run_sync()
345
+
346
+ self.mega_table = mega_table
347
+
348
+ def tearDown(self):
349
+ drop_db_tables_sync(*COMPLEX_SCHEMA)
350
+
351
+ @engines_skip("cockroach")
352
+ def test_select_all(self):
353
+ """
354
+ Fetch all of the columns from the related table to make sure they're
355
+ returned correctly.
356
+ """
357
+ """
358
+ 🐛 Cockroach bug: https://github.com/cockroachdb/cockroach/issues/71908 "could not decorrelate subquery" error under asyncpg
359
+ """ # noqa: E501
360
+ response = SmallTable.select(
361
+ SmallTable.varchar_col, SmallTable.mega_rows(load_json=True)
362
+ ).run_sync()
363
+
364
+ self.assertEqual(len(response), 1)
365
+ mega_rows = response[0]["mega_rows"]
366
+
367
+ self.assertEqual(len(mega_rows), 1)
368
+ mega_row = mega_rows[0]
369
+
370
+ for key, value in mega_row.items():
371
+ # Make sure that every value in the response matches what we saved.
372
+ self.assertAlmostEqual(
373
+ getattr(self.mega_table, key),
374
+ value,
375
+ msg=f"{key} doesn't match",
376
+ )
377
+
378
+ @engines_skip("cockroach")
379
+ def test_select_single(self):
380
+ """
381
+ Make sure each column can be selected one at a time.
382
+ """
383
+ """
384
+ 🐛 Cockroach bug: https://github.com/cockroachdb/cockroach/issues/71908 "could not decorrelate subquery" error under asyncpg
385
+ """ # noqa: E501
386
+ for column in MegaTable._meta.columns:
387
+ response = SmallTable.select(
388
+ SmallTable.varchar_col,
389
+ SmallTable.mega_rows(column, load_json=True),
390
+ ).run_sync()
391
+
392
+ data = response[0]["mega_rows"][0]
393
+ column_name = column._meta.name
394
+
395
+ original_value = getattr(self.mega_table, column_name)
396
+ returned_value = data[column_name]
397
+
398
+ if type(column) == UUID:
399
+ self.assertIn(type(returned_value), (uuid.UUID, asyncpgUUID))
400
+ else:
401
+ self.assertEqual(
402
+ type(original_value),
403
+ type(returned_value),
404
+ msg=f"{column_name} type isn't correct",
405
+ )
406
+
407
+ self.assertAlmostEqual(
408
+ original_value,
409
+ returned_value,
410
+ msg=f"{column_name} doesn't match",
411
+ )
412
+
413
+ # Test it as a list too
414
+ response = SmallTable.select(
415
+ SmallTable.varchar_col,
416
+ SmallTable.mega_rows(column, as_list=True, load_json=True),
417
+ ).run_sync()
418
+
419
+ original_value = getattr(self.mega_table, column_name)
420
+ returned_value = response[0]["mega_rows"][0]
421
+
422
+ if type(column) == UUID:
423
+ self.assertIn(type(returned_value), (uuid.UUID, asyncpgUUID))
424
+ self.assertEqual(str(original_value), str(returned_value))
425
+ else:
426
+ self.assertEqual(
427
+ type(original_value),
428
+ type(returned_value),
429
+ msg=f"{column_name} type isn't correct",
430
+ )
431
+
432
+ self.assertAlmostEqual(
433
+ original_value,
434
+ returned_value,
435
+ msg=f"{column_name} doesn't match",
436
+ )
@@ -0,0 +1,48 @@
1
+ from unittest import TestCase
2
+
3
+ from piccolo.columns.column_types import (
4
+ ForeignKey,
5
+ LazyTableReference,
6
+ Text,
7
+ Varchar,
8
+ )
9
+ from piccolo.columns.m2m import M2M
10
+ from piccolo.schema import SchemaManager
11
+ from piccolo.table import Table
12
+ from tests.base import engines_skip
13
+
14
+ from .base import M2MBase
15
+
16
+
17
+ class Band(Table, schema="schema_1"):
18
+ name = Varchar()
19
+ genres = M2M(LazyTableReference("GenreToBand", module_path=__name__))
20
+
21
+
22
+ class Genre(Table, schema="schema_1"):
23
+ name = Varchar()
24
+ bands = M2M(LazyTableReference("GenreToBand", module_path=__name__))
25
+
26
+
27
+ class GenreToBand(Table, schema="schema_1"):
28
+ band = ForeignKey(Band)
29
+ genre = ForeignKey(Genre)
30
+ reason = Text(help_text="For testing additional columns on join tables.")
31
+
32
+
33
+ @engines_skip("sqlite")
34
+ class TestM2MWithSchema(M2MBase, TestCase):
35
+ """
36
+ Make sure that when the tables exist in a non-public schema, that M2M still
37
+ works.
38
+ """
39
+
40
+ band = Band
41
+ genre = Genre
42
+ genre_to_band = GenreToBand
43
+ all_tables = [Band, Genre, GenreToBand]
44
+
45
+ def tearDown(self):
46
+ SchemaManager().drop_schema(
47
+ schema_name="schema_1", cascade=True
48
+ ).run_sync()
tests/table/test_count.py CHANGED
@@ -50,6 +50,10 @@ class TestCount(TestCase):
50
50
 
51
51
  self.assertEqual(response, 3)
52
52
 
53
+ # Test the method also works
54
+ response = Band.count().distinct([Band.popularity]).run_sync()
55
+ self.assertEqual(response, 3)
56
+
53
57
  def test_count_distinct_multiple(self):
54
58
  Band.insert(
55
59
  Band(name="Pythonistas", popularity=10),