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.
@@ -1,69 +1,29 @@
1
- import asyncio
2
- import datetime
3
- import decimal
4
- import uuid
5
- from unittest import TestCase
1
+ import typing as t
6
2
 
7
- from tests.base import engine_is, 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
3
  from piccolo.engine.finder import engine_finder
40
4
  from piccolo.table import Table, create_db_tables_sync, drop_db_tables_sync
5
+ from tests.base import engine_is, engines_skip
41
6
 
42
7
  engine = engine_finder()
43
8
 
44
9
 
45
- class Band(Table):
46
- name = Varchar()
47
- genres = M2M(LazyTableReference("GenreToBand", module_path=__name__))
48
-
49
-
50
- class Genre(Table):
51
- name = Varchar()
52
- bands = M2M(LazyTableReference("GenreToBand", module_path=__name__))
53
-
54
-
55
- class GenreToBand(Table):
56
- band = ForeignKey(Band)
57
- genre = ForeignKey(Genre)
58
- reason = Text(help_text="For testing additional columns on join tables.")
59
-
60
-
61
- SIMPLE_SCHEMA = [Band, Genre, GenreToBand]
10
+ class M2MBase:
11
+ """
12
+ This allows us to test M2M when the tables are in different schemas
13
+ (public vs non-public).
14
+ """
62
15
 
16
+ band: t.Type[Table]
17
+ genre: t.Type[Table]
18
+ genre_to_band: t.Type[Table]
19
+ all_tables: t.List[t.Type[Table]]
63
20
 
64
- class TestM2M(TestCase):
65
21
  def setUp(self):
66
- create_db_tables_sync(*SIMPLE_SCHEMA, if_not_exists=True)
22
+ Band = self.band
23
+ Genre = self.genre
24
+ GenreToBand = self.genre_to_band
25
+
26
+ create_db_tables_sync(*self.all_tables, if_not_exists=True)
67
27
 
68
28
  if engine_is("cockroach"):
69
29
  bands = (
@@ -115,13 +75,16 @@ class TestM2M(TestCase):
115
75
  ).run_sync()
116
76
 
117
77
  def tearDown(self):
118
- drop_db_tables_sync(*SIMPLE_SCHEMA)
78
+ drop_db_tables_sync(*self.all_tables)
119
79
 
120
80
  @engines_skip("cockroach")
121
81
  def test_select_name(self):
122
82
  """
123
83
  🐛 Cockroach bug: https://github.com/cockroachdb/cockroach/issues/71908 "could not decorrelate subquery" error under asyncpg
124
84
  """ # noqa: E501
85
+ Band = self.band
86
+ Genre = self.genre
87
+
125
88
  response = Band.select(
126
89
  Band.name, Band.genres(Genre.name, as_list=True)
127
90
  ).run_sync()
@@ -155,6 +118,10 @@ class TestM2M(TestCase):
155
118
  """
156
119
  🐛 Cockroach bug: https://github.com/cockroachdb/cockroach/issues/71908 "could not decorrelate subquery" error under asyncpg
157
120
  """ # noqa: E501
121
+ Band = self.band
122
+ Genre = self.genre
123
+ GenreToBand = self.genre_to_band
124
+
158
125
  GenreToBand.delete(force=True).run_sync()
159
126
 
160
127
  # Try it with a list response
@@ -189,6 +156,9 @@ class TestM2M(TestCase):
189
156
  """
190
157
  🐛 Cockroach bug: https://github.com/cockroachdb/cockroach/issues/71908 "could not decorrelate subquery" error under asyncpg
191
158
  """ # noqa: E501
159
+ Band = self.band
160
+ Genre = self.genre
161
+
192
162
  response = Band.select(
193
163
  Band.name, Band.genres(Genre.id, Genre.name)
194
164
  ).run_sync()
@@ -248,6 +218,9 @@ class TestM2M(TestCase):
248
218
  """
249
219
  🐛 Cockroach bug: https://github.com/cockroachdb/cockroach/issues/71908 "could not decorrelate subquery" error under asyncpg
250
220
  """ # noqa: E501
221
+ Band = self.band
222
+ Genre = self.genre
223
+
251
224
  response = Band.select(
252
225
  Band.name, Band.genres(Genre.id, as_list=True)
253
226
  ).run_sync()
@@ -284,6 +257,9 @@ class TestM2M(TestCase):
284
257
  🐛 Cockroach bug: https://github.com/cockroachdb/cockroach/issues/71908 "could not decorrelate subquery" error under asyncpg
285
258
 
286
259
  """ # noqa: E501
260
+ Band = self.band
261
+ Genre = self.genre
262
+
287
263
  response = Band.select(
288
264
  Band.name, Band.genres(Genre.all_columns(exclude=(Genre.id,)))
289
265
  ).run_sync()
@@ -312,6 +288,10 @@ class TestM2M(TestCase):
312
288
  """
313
289
  Make sure we can add items to the joining table.
314
290
  """
291
+ Band = self.band
292
+ Genre = self.genre
293
+ GenreToBand = self.genre_to_band
294
+
315
295
  band: Band = Band.objects().get(Band.name == "Pythonistas").run_sync()
316
296
  band.add_m2m(Genre(name="Punk Rock"), m2m=Band.genres).run_sync()
317
297
 
@@ -334,6 +314,10 @@ class TestM2M(TestCase):
334
314
  Make sure the ``extra_column_values`` parameter for ``add_m2m`` works
335
315
  correctly when the dictionary keys are strings.
336
316
  """
317
+ Band = self.band
318
+ Genre = self.genre
319
+ GenreToBand = self.genre_to_band
320
+
337
321
  reason = "Their second album was very punk rock."
338
322
 
339
323
  band: Band = Band.objects().get(Band.name == "Pythonistas").run_sync()
@@ -361,6 +345,10 @@ class TestM2M(TestCase):
361
345
  Make sure the ``extra_column_values`` parameter for ``add_m2m`` works
362
346
  correctly when the dictionary keys are ``Column`` classes.
363
347
  """
348
+ Band = self.band
349
+ Genre = self.genre
350
+ GenreToBand = self.genre_to_band
351
+
364
352
  reason = "Their second album was very punk rock."
365
353
 
366
354
  band: Band = Band.objects().get(Band.name == "Pythonistas").run_sync()
@@ -387,6 +375,10 @@ class TestM2M(TestCase):
387
375
  """
388
376
  Make sure we can add an existing element to the joining table.
389
377
  """
378
+ Band = self.band
379
+ Genre = self.genre
380
+ GenreToBand = self.genre_to_band
381
+
390
382
  band: Band = Band.objects().get(Band.name == "Pythonistas").run_sync()
391
383
 
392
384
  genre: Genre = (
@@ -414,6 +406,8 @@ class TestM2M(TestCase):
414
406
  """
415
407
  Make sure we can get related items via the joining table.
416
408
  """
409
+ Band = self.band
410
+
417
411
  band: Band = Band.objects().get(Band.name == "Pythonistas").run_sync()
418
412
 
419
413
  genres = band.get_m2m(Band.genres).run_sync()
@@ -426,6 +420,10 @@ class TestM2M(TestCase):
426
420
  """
427
421
  Make sure we can remove related items via the joining table.
428
422
  """
423
+ Band = self.band
424
+ Genre = self.genre
425
+ GenreToBand = self.genre_to_band
426
+
429
427
  band: Band = Band.objects().get(Band.name == "Pythonistas").run_sync()
430
428
 
431
429
  genre = Genre.objects().get(Genre.name == "Rock").run_sync()
@@ -462,372 +460,3 @@ class TestM2M(TestCase):
462
460
  .run_sync(),
463
461
  1,
464
462
  )
465
-
466
-
467
- ###############################################################################
468
-
469
- # A schema using custom primary keys
470
-
471
-
472
- class Customer(Table):
473
- uuid = UUID(primary_key=True)
474
- name = Varchar()
475
- concerts = M2M(
476
- LazyTableReference("CustomerToConcert", module_path=__name__)
477
- )
478
-
479
-
480
- class Concert(Table):
481
- uuid = UUID(primary_key=True)
482
- name = Varchar()
483
- customers = M2M(
484
- LazyTableReference("CustomerToConcert", module_path=__name__)
485
- )
486
-
487
-
488
- class CustomerToConcert(Table):
489
- customer = ForeignKey(Customer)
490
- concert = ForeignKey(Concert)
491
-
492
-
493
- CUSTOM_PK_SCHEMA = [Customer, Concert, CustomerToConcert]
494
-
495
-
496
- class TestM2MCustomPrimaryKey(TestCase):
497
- """
498
- Make sure the M2M functionality works correctly when the tables have custom
499
- primary key columns.
500
- """
501
-
502
- def setUp(self):
503
- create_db_tables_sync(*CUSTOM_PK_SCHEMA, if_not_exists=True)
504
-
505
- bob = Customer.objects().create(name="Bob").run_sync()
506
- sally = Customer.objects().create(name="Sally").run_sync()
507
- fred = Customer.objects().create(name="Fred").run_sync()
508
-
509
- rockfest = Concert.objects().create(name="Rockfest").run_sync()
510
- folkfest = Concert.objects().create(name="Folkfest").run_sync()
511
- classicfest = Concert.objects().create(name="Classicfest").run_sync()
512
-
513
- CustomerToConcert.insert(
514
- CustomerToConcert(customer=bob, concert=rockfest),
515
- CustomerToConcert(customer=bob, concert=classicfest),
516
- CustomerToConcert(customer=sally, concert=rockfest),
517
- CustomerToConcert(customer=sally, concert=folkfest),
518
- CustomerToConcert(customer=fred, concert=classicfest),
519
- ).run_sync()
520
-
521
- def tearDown(self):
522
- drop_db_tables_sync(*CUSTOM_PK_SCHEMA)
523
-
524
- @engines_skip("cockroach")
525
- def test_select(self):
526
- """
527
- 🐛 Cockroach bug: https://github.com/cockroachdb/cockroach/issues/71908 "could not decorrelate subquery" error under asyncpg
528
- """ # noqa: E501
529
- response = Customer.select(
530
- Customer.name, Customer.concerts(Concert.name, as_list=True)
531
- ).run_sync()
532
-
533
- self.assertListEqual(
534
- response,
535
- [
536
- {"name": "Bob", "concerts": ["Rockfest", "Classicfest"]},
537
- {"name": "Sally", "concerts": ["Rockfest", "Folkfest"]},
538
- {"name": "Fred", "concerts": ["Classicfest"]},
539
- ],
540
- )
541
-
542
- # Now try it in reverse.
543
- response = Concert.select(
544
- Concert.name, Concert.customers(Customer.name, as_list=True)
545
- ).run_sync()
546
-
547
- self.assertListEqual(
548
- response,
549
- [
550
- {"name": "Rockfest", "customers": ["Bob", "Sally"]},
551
- {"name": "Folkfest", "customers": ["Sally"]},
552
- {"name": "Classicfest", "customers": ["Bob", "Fred"]},
553
- ],
554
- )
555
-
556
- def test_add_m2m(self):
557
- """
558
- Make sure we can add items to the joining table.
559
- """
560
- customer: Customer = (
561
- Customer.objects().get(Customer.name == "Bob").run_sync()
562
- )
563
- customer.add_m2m(
564
- Concert(name="Jazzfest"), m2m=Customer.concerts
565
- ).run_sync()
566
-
567
- self.assertTrue(
568
- Concert.exists().where(Concert.name == "Jazzfest").run_sync()
569
- )
570
-
571
- self.assertEqual(
572
- CustomerToConcert.count()
573
- .where(
574
- CustomerToConcert.customer.name == "Bob",
575
- CustomerToConcert.concert.name == "Jazzfest",
576
- )
577
- .run_sync(),
578
- 1,
579
- )
580
-
581
- def test_add_m2m_within_transaction(self):
582
- """
583
- Make sure we can add items to the joining table, when within an
584
- existing transaction.
585
-
586
- https://github.com/piccolo-orm/piccolo/issues/674
587
-
588
- """
589
- engine = Customer._meta.db
590
-
591
- async def add_m2m_in_transaction():
592
- async with engine.transaction():
593
- customer: Customer = await Customer.objects().get(
594
- Customer.name == "Bob"
595
- )
596
- await customer.add_m2m(
597
- Concert(name="Jazzfest"), m2m=Customer.concerts
598
- )
599
-
600
- asyncio.run(add_m2m_in_transaction())
601
-
602
- self.assertTrue(
603
- Concert.exists().where(Concert.name == "Jazzfest").run_sync()
604
- )
605
-
606
- self.assertEqual(
607
- CustomerToConcert.count()
608
- .where(
609
- CustomerToConcert.customer.name == "Bob",
610
- CustomerToConcert.concert.name == "Jazzfest",
611
- )
612
- .run_sync(),
613
- 1,
614
- )
615
-
616
- def test_get_m2m(self):
617
- """
618
- Make sure we can get related items via the joining table.
619
- """
620
- customer: Customer = (
621
- Customer.objects().get(Customer.name == "Bob").run_sync()
622
- )
623
-
624
- concerts = customer.get_m2m(Customer.concerts).run_sync()
625
-
626
- self.assertTrue(all(isinstance(i, Table) for i in concerts))
627
-
628
- self.assertCountEqual(
629
- [i.name for i in concerts], ["Rockfest", "Classicfest"]
630
- )
631
-
632
-
633
- ###############################################################################
634
-
635
- # Test a very complex schema
636
-
637
-
638
- class SmallTable(Table):
639
- varchar_col = Varchar()
640
- mega_rows = M2M(LazyTableReference("SmallToMega", module_path=__name__))
641
-
642
-
643
- if engine.engine_type != "cockroach": # type: ignore
644
-
645
- class MegaTable(Table): # type: ignore
646
- """
647
- A table containing all of the column types and different column kwargs
648
- """
649
-
650
- array_col = Array(Varchar())
651
- bigint_col = BigInt()
652
- boolean_col = Boolean()
653
- bytea_col = Bytea()
654
- date_col = Date()
655
- double_precision_col = DoublePrecision()
656
- integer_col = Integer()
657
- interval_col = Interval()
658
- json_col = JSON()
659
- jsonb_col = JSONB()
660
- numeric_col = Numeric(digits=(5, 2))
661
- real_col = Real()
662
- smallint_col = SmallInt()
663
- text_col = Text()
664
- timestamp_col = Timestamp()
665
- timestamptz_col = Timestamptz()
666
- uuid_col = UUID()
667
- varchar_col = Varchar()
668
-
669
- else:
670
-
671
- class MegaTable(Table): # type: ignore
672
- """
673
- Special version for Cockroach.
674
- A table containing all of the column types and different column kwargs
675
- """
676
-
677
- array_col = Array(Varchar())
678
- bigint_col = BigInt()
679
- boolean_col = Boolean()
680
- bytea_col = Bytea()
681
- date_col = Date()
682
- double_precision_col = DoublePrecision()
683
- integer_col = BigInt()
684
- interval_col = Interval()
685
- json_col = JSONB()
686
- jsonb_col = JSONB()
687
- numeric_col = Numeric(digits=(5, 2))
688
- real_col = Real()
689
- smallint_col = SmallInt()
690
- text_col = Text()
691
- timestamp_col = Timestamp()
692
- timestamptz_col = Timestamptz()
693
- uuid_col = UUID()
694
- varchar_col = Varchar()
695
-
696
-
697
- class SmallToMega(Table):
698
- small = ForeignKey(MegaTable)
699
- mega = ForeignKey(SmallTable)
700
-
701
-
702
- COMPLEX_SCHEMA = [MegaTable, SmallTable, SmallToMega]
703
-
704
-
705
- class TestM2MComplexSchema(TestCase):
706
- """
707
- By using a very complex schema containing every column type, we can catch
708
- more edge cases.
709
- """
710
-
711
- def setUp(self):
712
- create_db_tables_sync(*COMPLEX_SCHEMA, if_not_exists=True)
713
-
714
- small_table = SmallTable(varchar_col="Test")
715
- small_table.save().run_sync()
716
-
717
- mega_table = MegaTable(
718
- array_col=["bob", "sally"],
719
- bigint_col=1,
720
- boolean_col=True,
721
- bytea_col="hello".encode("utf8"),
722
- date_col=datetime.date(year=2021, month=1, day=1),
723
- double_precision_col=1.344,
724
- integer_col=1,
725
- interval_col=datetime.timedelta(seconds=10),
726
- json_col={"a": 1},
727
- jsonb_col={"a": 1},
728
- numeric_col=decimal.Decimal("1.1"),
729
- real_col=1.1,
730
- smallint_col=1,
731
- text_col="hello",
732
- timestamp_col=datetime.datetime(year=2021, month=1, day=1),
733
- timestamptz_col=datetime.datetime(
734
- year=2021, month=1, day=1, tzinfo=datetime.timezone.utc
735
- ),
736
- uuid_col=uuid.UUID("12783854-c012-4c15-8183-8eecb46f2c4e"),
737
- varchar_col="hello",
738
- )
739
- mega_table.save().run_sync()
740
-
741
- SmallToMega(small=small_table, mega=mega_table).save().run_sync()
742
-
743
- self.mega_table = mega_table
744
-
745
- def tearDown(self):
746
- drop_db_tables_sync(*COMPLEX_SCHEMA)
747
-
748
- @engines_skip("cockroach")
749
- def test_select_all(self):
750
- """
751
- Fetch all of the columns from the related table to make sure they're
752
- returned correctly.
753
- """
754
- """
755
- 🐛 Cockroach bug: https://github.com/cockroachdb/cockroach/issues/71908 "could not decorrelate subquery" error under asyncpg
756
- """ # noqa: E501
757
- response = SmallTable.select(
758
- SmallTable.varchar_col, SmallTable.mega_rows(load_json=True)
759
- ).run_sync()
760
-
761
- self.assertEqual(len(response), 1)
762
- mega_rows = response[0]["mega_rows"]
763
-
764
- self.assertEqual(len(mega_rows), 1)
765
- mega_row = mega_rows[0]
766
-
767
- for key, value in mega_row.items():
768
- # Make sure that every value in the response matches what we saved.
769
- self.assertAlmostEqual(
770
- getattr(self.mega_table, key),
771
- value,
772
- msg=f"{key} doesn't match",
773
- )
774
-
775
- @engines_skip("cockroach")
776
- def test_select_single(self):
777
- """
778
- Make sure each column can be selected one at a time.
779
- """
780
- """
781
- 🐛 Cockroach bug: https://github.com/cockroachdb/cockroach/issues/71908 "could not decorrelate subquery" error under asyncpg
782
- """ # noqa: E501
783
- for column in MegaTable._meta.columns:
784
- response = SmallTable.select(
785
- SmallTable.varchar_col,
786
- SmallTable.mega_rows(column, load_json=True),
787
- ).run_sync()
788
-
789
- data = response[0]["mega_rows"][0]
790
- column_name = column._meta.name
791
-
792
- original_value = getattr(self.mega_table, column_name)
793
- returned_value = data[column_name]
794
-
795
- if type(column) == UUID:
796
- self.assertIn(type(returned_value), (uuid.UUID, asyncpgUUID))
797
- else:
798
- self.assertEqual(
799
- type(original_value),
800
- type(returned_value),
801
- msg=f"{column_name} type isn't correct",
802
- )
803
-
804
- self.assertAlmostEqual(
805
- original_value,
806
- returned_value,
807
- msg=f"{column_name} doesn't match",
808
- )
809
-
810
- # Test it as a list too
811
- response = SmallTable.select(
812
- SmallTable.varchar_col,
813
- SmallTable.mega_rows(column, as_list=True, load_json=True),
814
- ).run_sync()
815
-
816
- original_value = getattr(self.mega_table, column_name)
817
- returned_value = response[0]["mega_rows"][0]
818
-
819
- if type(column) == UUID:
820
- self.assertIn(type(returned_value), (uuid.UUID, asyncpgUUID))
821
- self.assertEqual(str(original_value), str(returned_value))
822
- else:
823
- self.assertEqual(
824
- type(original_value),
825
- type(returned_value),
826
- msg=f"{column_name} type isn't correct",
827
- )
828
-
829
- self.assertAlmostEqual(
830
- original_value,
831
- returned_value,
832
- msg=f"{column_name} doesn't match",
833
- )