piccolo 1.27.0__py3-none-any.whl → 1.28.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.
Files changed (122) hide show
  1. piccolo/__init__.py +1 -1
  2. piccolo/apps/app/commands/new.py +3 -3
  3. piccolo/apps/asgi/commands/new.py +1 -2
  4. piccolo/apps/fixtures/commands/dump.py +8 -8
  5. piccolo/apps/fixtures/commands/load.py +5 -5
  6. piccolo/apps/fixtures/commands/shared.py +9 -9
  7. piccolo/apps/migrations/auto/diffable_table.py +12 -12
  8. piccolo/apps/migrations/auto/migration_manager.py +59 -66
  9. piccolo/apps/migrations/auto/operations.py +14 -14
  10. piccolo/apps/migrations/auto/schema_differ.py +35 -34
  11. piccolo/apps/migrations/auto/schema_snapshot.py +3 -4
  12. piccolo/apps/migrations/auto/serialisation.py +27 -24
  13. piccolo/apps/migrations/auto/serialisation_legacy.py +2 -2
  14. piccolo/apps/migrations/commands/backwards.py +1 -2
  15. piccolo/apps/migrations/commands/base.py +12 -12
  16. piccolo/apps/migrations/commands/check.py +2 -3
  17. piccolo/apps/migrations/commands/clean.py +3 -3
  18. piccolo/apps/migrations/commands/forwards.py +1 -2
  19. piccolo/apps/migrations/commands/new.py +6 -6
  20. piccolo/apps/migrations/tables.py +3 -3
  21. piccolo/apps/playground/commands/run.py +29 -13
  22. piccolo/apps/schema/commands/generate.py +49 -49
  23. piccolo/apps/schema/commands/graph.py +5 -5
  24. piccolo/apps/shell/commands/run.py +1 -2
  25. piccolo/apps/sql_shell/commands/run.py +4 -4
  26. piccolo/apps/tester/commands/run.py +3 -3
  27. piccolo/apps/user/commands/change_permissions.py +6 -6
  28. piccolo/apps/user/commands/create.py +7 -7
  29. piccolo/apps/user/commands/list.py +2 -2
  30. piccolo/apps/user/tables.py +8 -8
  31. piccolo/columns/base.py +84 -52
  32. piccolo/columns/choices.py +2 -2
  33. piccolo/columns/column_types.py +297 -175
  34. piccolo/columns/combination.py +15 -12
  35. piccolo/columns/defaults/base.py +4 -4
  36. piccolo/columns/defaults/date.py +4 -3
  37. piccolo/columns/defaults/interval.py +4 -3
  38. piccolo/columns/defaults/time.py +4 -3
  39. piccolo/columns/defaults/timestamp.py +4 -3
  40. piccolo/columns/defaults/timestamptz.py +4 -3
  41. piccolo/columns/defaults/uuid.py +3 -2
  42. piccolo/columns/m2m.py +28 -35
  43. piccolo/columns/readable.py +4 -3
  44. piccolo/columns/reference.py +9 -9
  45. piccolo/conf/apps.py +53 -54
  46. piccolo/custom_types.py +28 -6
  47. piccolo/engine/base.py +14 -14
  48. piccolo/engine/cockroach.py +5 -4
  49. piccolo/engine/finder.py +2 -2
  50. piccolo/engine/postgres.py +20 -19
  51. piccolo/engine/sqlite.py +23 -22
  52. piccolo/query/base.py +30 -29
  53. piccolo/query/functions/__init__.py +12 -0
  54. piccolo/query/functions/aggregate.py +4 -3
  55. piccolo/query/functions/array.py +151 -0
  56. piccolo/query/functions/base.py +3 -3
  57. piccolo/query/functions/datetime.py +22 -22
  58. piccolo/query/functions/string.py +4 -4
  59. piccolo/query/functions/type_conversion.py +30 -15
  60. piccolo/query/methods/alter.py +47 -46
  61. piccolo/query/methods/count.py +11 -10
  62. piccolo/query/methods/create.py +6 -5
  63. piccolo/query/methods/create_index.py +9 -8
  64. piccolo/query/methods/delete.py +7 -6
  65. piccolo/query/methods/drop_index.py +7 -6
  66. piccolo/query/methods/exists.py +6 -5
  67. piccolo/query/methods/indexes.py +4 -4
  68. piccolo/query/methods/insert.py +21 -14
  69. piccolo/query/methods/objects.py +60 -50
  70. piccolo/query/methods/raw.py +7 -6
  71. piccolo/query/methods/refresh.py +8 -7
  72. piccolo/query/methods/select.py +56 -49
  73. piccolo/query/methods/table_exists.py +5 -5
  74. piccolo/query/methods/update.py +8 -7
  75. piccolo/query/mixins.py +56 -61
  76. piccolo/query/operators/json.py +11 -11
  77. piccolo/query/proxy.py +8 -9
  78. piccolo/querystring.py +14 -15
  79. piccolo/schema.py +10 -10
  80. piccolo/table.py +93 -94
  81. piccolo/table_reflection.py +9 -9
  82. piccolo/testing/model_builder.py +12 -11
  83. piccolo/testing/random_builder.py +2 -2
  84. piccolo/testing/test_case.py +4 -4
  85. piccolo/utils/dictionary.py +3 -3
  86. piccolo/utils/encoding.py +5 -5
  87. piccolo/utils/lazy_loader.py +3 -3
  88. piccolo/utils/list.py +7 -8
  89. piccolo/utils/objects.py +4 -6
  90. piccolo/utils/pydantic.py +21 -24
  91. piccolo/utils/sql_values.py +3 -3
  92. piccolo/utils/sync.py +4 -3
  93. piccolo/utils/warnings.py +1 -2
  94. {piccolo-1.27.0.dist-info → piccolo-1.28.0.dist-info}/METADATA +1 -1
  95. {piccolo-1.27.0.dist-info → piccolo-1.28.0.dist-info}/RECORD +122 -121
  96. tests/apps/fixtures/commands/test_dump_load.py +1 -2
  97. tests/apps/migrations/auto/integration/test_migrations.py +32 -7
  98. tests/apps/migrations/auto/test_migration_manager.py +2 -2
  99. tests/apps/migrations/auto/test_schema_differ.py +22 -23
  100. tests/apps/migrations/commands/test_forwards_backwards.py +3 -3
  101. tests/columns/m2m/base.py +2 -2
  102. tests/columns/test_array.py +176 -10
  103. tests/columns/test_boolean.py +2 -4
  104. tests/columns/test_combination.py +29 -1
  105. tests/columns/test_db_column_name.py +2 -2
  106. tests/engine/test_extra_nodes.py +2 -2
  107. tests/engine/test_pool.py +3 -3
  108. tests/engine/test_transaction.py +4 -4
  109. tests/query/test_freeze.py +4 -4
  110. tests/table/instance/test_get_related.py +2 -2
  111. tests/table/test_alter.py +4 -4
  112. tests/table/test_indexes.py +1 -2
  113. tests/table/test_refresh.py +2 -2
  114. tests/table/test_select.py +58 -0
  115. tests/table/test_update.py +3 -3
  116. tests/testing/test_model_builder.py +1 -2
  117. tests/utils/test_pydantic.py +36 -36
  118. tests/utils/test_table_reflection.py +1 -2
  119. {piccolo-1.27.0.dist-info → piccolo-1.28.0.dist-info}/WHEEL +0 -0
  120. {piccolo-1.27.0.dist-info → piccolo-1.28.0.dist-info}/entry_points.txt +0 -0
  121. {piccolo-1.27.0.dist-info → piccolo-1.28.0.dist-info}/licenses/LICENSE +0 -0
  122. {piccolo-1.27.0.dist-info → piccolo-1.28.0.dist-info}/top_level.txt +0 -0
tests/table/test_alter.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- import typing as t
3
+ from typing import Any, Union
4
4
  from unittest import TestCase
5
5
 
6
6
  import pytest
@@ -26,7 +26,7 @@ from tests.example_apps.music.tables import Band, Manager
26
26
  class TestRenameColumn(DBTestCase):
27
27
  def _test_rename(
28
28
  self,
29
- existing_column: t.Union[Column, str],
29
+ existing_column: Union[Column, str],
30
30
  new_column_name: str = "rating",
31
31
  ):
32
32
  self.insert_row()
@@ -90,7 +90,7 @@ class TestDropColumn(DBTestCase):
90
90
  SQLite has very limited support for ALTER statements.
91
91
  """
92
92
 
93
- def _test_drop(self, column: t.Union[str, Column]):
93
+ def _test_drop(self, column: Union[str, Column]):
94
94
  self.insert_row()
95
95
 
96
96
  Band.alter().drop_column(column).run_sync()
@@ -109,7 +109,7 @@ class TestDropColumn(DBTestCase):
109
109
 
110
110
  class TestAddColumn(DBTestCase):
111
111
  def _test_add_column(
112
- self, column: Column, column_name: str, expected_value: t.Any
112
+ self, column: Column, column_name: str, expected_value: Any
113
113
  ):
114
114
  self.insert_row()
115
115
  Band.alter().add_column(column_name, column).run_sync()
@@ -1,4 +1,3 @@
1
- import typing as t
2
1
  from unittest import TestCase
3
2
 
4
3
  from piccolo.columns.base import Column
@@ -52,7 +51,7 @@ class TestProblematicColumnName(TestCase):
52
51
  Make sure we can add an index to a column with a problematic name
53
52
  (which clashes with a SQL keyword).
54
53
  """
55
- columns: t.List[Column] = [Concert.order]
54
+ columns: list[Column] = [Concert.order]
56
55
  Concert.create_index(columns=columns).run_sync()
57
56
  index_name = Concert._get_index_name([i._meta.name for i in columns])
58
57
 
@@ -1,4 +1,4 @@
1
- import typing as t
1
+ from typing import cast
2
2
 
3
3
  from piccolo.testing.test_case import TableTest
4
4
  from tests.base import DBTestCase
@@ -293,6 +293,6 @@ class TestRefreshWithLoadJSON(TableTest):
293
293
  self.recording_studio.refresh(load_json=True).run_sync()
294
294
 
295
295
  self.assertDictEqual(
296
- t.cast(dict, self.recording_studio.facilities),
296
+ cast(dict, self.recording_studio.facilities),
297
297
  {"electric piano": True},
298
298
  )
@@ -258,6 +258,64 @@ class TestSelect(DBTestCase):
258
258
 
259
259
  self.assertEqual(response, [{"name": "Rustaceans"}])
260
260
 
261
+ def test_is_in(self):
262
+ self.insert_rows()
263
+
264
+ response = (
265
+ Band.select(Band.name)
266
+ .where(Band.manager._.name.is_in(["Guido"]))
267
+ .run_sync()
268
+ )
269
+
270
+ self.assertListEqual(response, [{"name": "Pythonistas"}])
271
+
272
+ def test_is_in_subquery(self):
273
+ self.insert_rows()
274
+
275
+ # This is a contrived example, just for testing.
276
+ response = (
277
+ Band.select(Band.name)
278
+ .where(
279
+ Band.manager.is_in(
280
+ Manager.select(Manager.id).where(Manager.name == "Guido")
281
+ )
282
+ )
283
+ .run_sync()
284
+ )
285
+
286
+ self.assertListEqual(response, [{"name": "Pythonistas"}])
287
+
288
+ def test_not_in(self):
289
+ self.insert_rows()
290
+
291
+ response = (
292
+ Band.select(Band.name)
293
+ .where(Band.manager._.name.not_in(["Guido"]))
294
+ .run_sync()
295
+ )
296
+
297
+ self.assertListEqual(
298
+ response, [{"name": "Rustaceans"}, {"name": "CSharps"}]
299
+ )
300
+
301
+ def test_not_in_subquery(self):
302
+ self.insert_rows()
303
+
304
+ # This is a contrived example, just for testing.
305
+ response = (
306
+ Band.select(Band.name)
307
+ .where(
308
+ Band.manager.not_in(
309
+ Manager.select(Manager.id).where(Manager.name == "Guido")
310
+ )
311
+ )
312
+ .run_sync()
313
+ )
314
+
315
+ self.assertListEqual(
316
+ response, [{"name": "Rustaceans"}, {"name": "CSharps"}]
317
+ )
318
+
261
319
  def test_where_is_null(self):
262
320
  self.insert_rows()
263
321
 
@@ -1,6 +1,6 @@
1
1
  import dataclasses
2
2
  import datetime
3
- import typing as t
3
+ from typing import Any
4
4
  from unittest import TestCase
5
5
 
6
6
  import pytest
@@ -181,9 +181,9 @@ DATE_DELTA = datetime.timedelta(days=1)
181
181
  class OperatorTestCase:
182
182
  description: str
183
183
  column: Column
184
- initial: t.Any
184
+ initial: Any
185
185
  querystring: QueryString
186
- expected: t.Any
186
+ expected: Any
187
187
 
188
188
 
189
189
  TEST_CASES = [
@@ -1,6 +1,5 @@
1
1
  import asyncio
2
2
  import json
3
- import typing as t
4
3
  import unittest
5
4
 
6
5
  from piccolo.columns import (
@@ -83,7 +82,7 @@ class TestModelBuilder(unittest.TestCase):
83
82
  drop_db_tables_sync(*TABLES)
84
83
 
85
84
  def test_async(self):
86
- async def build_model(table_class: t.Type[Table]):
85
+ async def build_model(table_class: type[Table]):
87
86
  return await ModelBuilder.build(table_class)
88
87
 
89
88
  for table_class in TABLES:
@@ -1,5 +1,5 @@
1
1
  import decimal
2
- import typing as t
2
+ from typing import Optional, cast
3
3
  from unittest import TestCase
4
4
 
5
5
  import pydantic
@@ -138,7 +138,7 @@ class TestArrayColumn(TestCase):
138
138
 
139
139
  self.assertEqual(
140
140
  pydantic_model.model_fields["members"].annotation,
141
- t.List[t.List[pydantic.constr(max_length=255)]],
141
+ list[list[pydantic.constr(max_length=255)]],
142
142
  )
143
143
 
144
144
  # Should not raise a validation error:
@@ -630,8 +630,8 @@ class TestNestedModel(TestCase):
630
630
 
631
631
  #######################################################################
632
632
 
633
- ManagerModel = t.cast(
634
- t.Type[pydantic.BaseModel],
633
+ ManagerModel = cast(
634
+ type[pydantic.BaseModel],
635
635
  BandModel.model_fields["manager"].annotation,
636
636
  )
637
637
  self.assertTrue(issubclass(ManagerModel, pydantic.BaseModel))
@@ -641,8 +641,8 @@ class TestNestedModel(TestCase):
641
641
 
642
642
  #######################################################################
643
643
 
644
- CountryModel = t.cast(
645
- t.Type[pydantic.BaseModel],
644
+ CountryModel = cast(
645
+ type[pydantic.BaseModel],
646
646
  ManagerModel.model_fields["country"].annotation,
647
647
  )
648
648
  self.assertTrue(issubclass(CountryModel, pydantic.BaseModel))
@@ -681,8 +681,8 @@ class TestNestedModel(TestCase):
681
681
 
682
682
  BandModel = create_pydantic_model(table=Band, nested=(Band.manager,))
683
683
 
684
- ManagerModel = t.cast(
685
- t.Type[pydantic.BaseModel],
684
+ ManagerModel = cast(
685
+ type[pydantic.BaseModel],
686
686
  BandModel.model_fields["manager"].annotation,
687
687
  )
688
688
  self.assertTrue(issubclass(ManagerModel, pydantic.BaseModel))
@@ -694,7 +694,7 @@ class TestNestedModel(TestCase):
694
694
  AssistantManagerType = BandModel.model_fields[
695
695
  "assistant_manager"
696
696
  ].annotation
697
- self.assertIs(AssistantManagerType, t.Optional[int])
697
+ self.assertIs(AssistantManagerType, Optional[int])
698
698
 
699
699
  #######################################################################
700
700
  # Test two levels deep
@@ -703,8 +703,8 @@ class TestNestedModel(TestCase):
703
703
  table=Band, nested=(Band.manager._.country,)
704
704
  )
705
705
 
706
- ManagerModel = t.cast(
707
- t.Type[pydantic.BaseModel],
706
+ ManagerModel = cast(
707
+ type[pydantic.BaseModel],
708
708
  BandModel.model_fields["manager"].annotation,
709
709
  )
710
710
  self.assertTrue(issubclass(ManagerModel, pydantic.BaseModel))
@@ -713,14 +713,14 @@ class TestNestedModel(TestCase):
713
713
  )
714
714
  self.assertEqual(ManagerModel.__qualname__, "Band.manager")
715
715
 
716
- AssistantManagerType = t.cast(
717
- t.Type[pydantic.BaseModel],
716
+ AssistantManagerType = cast(
717
+ type[pydantic.BaseModel],
718
718
  BandModel.model_fields["assistant_manager"].annotation,
719
719
  )
720
- self.assertIs(AssistantManagerType, t.Optional[int])
720
+ self.assertIs(AssistantManagerType, Optional[int])
721
721
 
722
- CountryModel = t.cast(
723
- t.Type[pydantic.BaseModel],
722
+ CountryModel = cast(
723
+ type[pydantic.BaseModel],
724
724
  ManagerModel.model_fields["country"].annotation,
725
725
  )
726
726
  self.assertTrue(issubclass(CountryModel, pydantic.BaseModel))
@@ -737,10 +737,10 @@ class TestNestedModel(TestCase):
737
737
  )
738
738
 
739
739
  VenueModel = ConcertModel.model_fields["venue"].annotation
740
- self.assertIs(VenueModel, t.Optional[int])
740
+ self.assertIs(VenueModel, Optional[int])
741
741
 
742
- BandModel = t.cast(
743
- t.Type[pydantic.BaseModel],
742
+ BandModel = cast(
743
+ type[pydantic.BaseModel],
744
744
  ConcertModel.model_fields["band_1"].annotation,
745
745
  )
746
746
  self.assertTrue(issubclass(BandModel, pydantic.BaseModel))
@@ -750,8 +750,8 @@ class TestNestedModel(TestCase):
750
750
  )
751
751
  self.assertEqual(BandModel.__qualname__, "Concert.band_1")
752
752
 
753
- ManagerModel = t.cast(
754
- t.Type[pydantic.BaseModel],
753
+ ManagerModel = cast(
754
+ type[pydantic.BaseModel],
755
755
  BandModel.model_fields["manager"].annotation,
756
756
  )
757
757
  self.assertTrue(issubclass(ManagerModel, pydantic.BaseModel))
@@ -764,10 +764,10 @@ class TestNestedModel(TestCase):
764
764
  AssistantManagerType = BandModel.model_fields[
765
765
  "assistant_manager"
766
766
  ].annotation
767
- self.assertIs(AssistantManagerType, t.Optional[int])
767
+ self.assertIs(AssistantManagerType, Optional[int])
768
768
 
769
769
  CountryModel = ManagerModel.model_fields["country"].annotation
770
- self.assertIs(CountryModel, t.Optional[int])
770
+ self.assertIs(CountryModel, Optional[int])
771
771
 
772
772
  #######################################################################
773
773
  # Test with `model_name` arg
@@ -778,8 +778,8 @@ class TestNestedModel(TestCase):
778
778
  model_name="MyConcertModel",
779
779
  )
780
780
 
781
- BandModel = t.cast(
782
- t.Type[pydantic.BaseModel],
781
+ BandModel = cast(
782
+ type[pydantic.BaseModel],
783
783
  MyConcertModel.model_fields["band_1"].annotation,
784
784
  )
785
785
  self.assertEqual(BandModel.__qualname__, "MyConcertModel.band_1")
@@ -810,8 +810,8 @@ class TestNestedModel(TestCase):
810
810
  table=Band, nested=True, include_default_columns=True
811
811
  )
812
812
 
813
- ManagerModel = t.cast(
814
- t.Type[pydantic.BaseModel],
813
+ ManagerModel = cast(
814
+ type[pydantic.BaseModel],
815
815
  BandModel.model_fields["manager"].annotation,
816
816
  )
817
817
  self.assertTrue(issubclass(ManagerModel, pydantic.BaseModel))
@@ -820,8 +820,8 @@ class TestNestedModel(TestCase):
820
820
  ["id", "name", "country"],
821
821
  )
822
822
 
823
- CountryModel = t.cast(
824
- t.Type[pydantic.BaseModel],
823
+ CountryModel = cast(
824
+ type[pydantic.BaseModel],
825
825
  ManagerModel.model_fields["country"].annotation,
826
826
  )
827
827
  self.assertTrue(issubclass(CountryModel, pydantic.BaseModel))
@@ -855,27 +855,27 @@ class TestRecursionDepth(TestCase):
855
855
  table=Concert, nested=True, max_recursion_depth=2
856
856
  )
857
857
 
858
- VenueModel = t.cast(
859
- t.Type[pydantic.BaseModel],
858
+ VenueModel = cast(
859
+ type[pydantic.BaseModel],
860
860
  ConcertModel.model_fields["venue"].annotation,
861
861
  )
862
862
  self.assertTrue(issubclass(VenueModel, pydantic.BaseModel))
863
863
 
864
- BandModel = t.cast(
865
- t.Type[pydantic.BaseModel],
864
+ BandModel = cast(
865
+ type[pydantic.BaseModel],
866
866
  ConcertModel.model_fields["band"].annotation,
867
867
  )
868
868
  self.assertTrue(issubclass(BandModel, pydantic.BaseModel))
869
869
 
870
- ManagerModel = t.cast(
871
- t.Type[pydantic.BaseModel],
870
+ ManagerModel = cast(
871
+ type[pydantic.BaseModel],
872
872
  BandModel.model_fields["manager"].annotation,
873
873
  )
874
874
  self.assertTrue(issubclass(ManagerModel, pydantic.BaseModel))
875
875
 
876
876
  # We should have hit the recursion depth:
877
877
  CountryModel = ManagerModel.model_fields["country"].annotation
878
- self.assertIs(CountryModel, t.Optional[int])
878
+ self.assertIs(CountryModel, Optional[int])
879
879
 
880
880
 
881
881
  class TestDBColumnName(TestCase):
@@ -1,4 +1,3 @@
1
- import typing as t
2
1
  from unittest import TestCase
3
2
 
4
3
  from piccolo.columns import Varchar
@@ -22,7 +21,7 @@ class TestTableStorage(TestCase):
22
21
  table_class.alter().drop_table(if_exists=True).run_sync()
23
22
 
24
23
  def _compare_table_columns(
25
- self, table_1: t.Type[Table], table_2: t.Type[Table]
24
+ self, table_1: type[Table], table_2: type[Table]
26
25
  ):
27
26
  """
28
27
  Make sure that for each column in table_1, there is a corresponding