piccolo 1.27.1__py3-none-any.whl → 1.29.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 (132) hide show
  1. piccolo/__init__.py +1 -1
  2. piccolo/apps/app/commands/new.py +3 -3
  3. piccolo/apps/asgi/commands/new.py +2 -3
  4. piccolo/apps/asgi/commands/templates/app/_blacksheep_app.py.jinja +57 -29
  5. piccolo/apps/asgi/commands/templates/app/_esmerald_app.py.jinja +48 -21
  6. piccolo/apps/asgi/commands/templates/app/_falcon_app.py.jinja +63 -8
  7. piccolo/apps/asgi/commands/templates/app/_fastapi_app.py.jinja +51 -24
  8. piccolo/apps/asgi/commands/templates/app/_litestar_app.py.jinja +34 -10
  9. piccolo/apps/asgi/commands/templates/app/_quart_app.py.jinja +38 -15
  10. piccolo/apps/asgi/commands/templates/app/_sanic_app.py.jinja +34 -11
  11. piccolo/apps/fixtures/commands/dump.py +8 -8
  12. piccolo/apps/fixtures/commands/load.py +5 -5
  13. piccolo/apps/fixtures/commands/shared.py +9 -9
  14. piccolo/apps/migrations/auto/diffable_table.py +12 -12
  15. piccolo/apps/migrations/auto/migration_manager.py +59 -66
  16. piccolo/apps/migrations/auto/operations.py +14 -14
  17. piccolo/apps/migrations/auto/schema_differ.py +35 -34
  18. piccolo/apps/migrations/auto/schema_snapshot.py +3 -4
  19. piccolo/apps/migrations/auto/serialisation.py +27 -24
  20. piccolo/apps/migrations/auto/serialisation_legacy.py +2 -2
  21. piccolo/apps/migrations/commands/backwards.py +1 -2
  22. piccolo/apps/migrations/commands/base.py +12 -12
  23. piccolo/apps/migrations/commands/check.py +2 -3
  24. piccolo/apps/migrations/commands/clean.py +3 -3
  25. piccolo/apps/migrations/commands/forwards.py +1 -2
  26. piccolo/apps/migrations/commands/new.py +6 -6
  27. piccolo/apps/migrations/tables.py +3 -3
  28. piccolo/apps/playground/commands/run.py +72 -13
  29. piccolo/apps/schema/commands/generate.py +49 -49
  30. piccolo/apps/schema/commands/graph.py +5 -5
  31. piccolo/apps/shell/commands/run.py +1 -2
  32. piccolo/apps/sql_shell/commands/run.py +4 -4
  33. piccolo/apps/tester/commands/run.py +3 -3
  34. piccolo/apps/user/commands/change_permissions.py +6 -6
  35. piccolo/apps/user/commands/create.py +7 -7
  36. piccolo/apps/user/commands/list.py +2 -2
  37. piccolo/apps/user/tables.py +8 -8
  38. piccolo/columns/base.py +84 -52
  39. piccolo/columns/choices.py +2 -2
  40. piccolo/columns/column_types.py +299 -177
  41. piccolo/columns/combination.py +15 -12
  42. piccolo/columns/defaults/base.py +4 -4
  43. piccolo/columns/defaults/date.py +4 -3
  44. piccolo/columns/defaults/interval.py +4 -3
  45. piccolo/columns/defaults/time.py +4 -3
  46. piccolo/columns/defaults/timestamp.py +4 -3
  47. piccolo/columns/defaults/timestamptz.py +4 -3
  48. piccolo/columns/defaults/uuid.py +3 -2
  49. piccolo/columns/m2m.py +28 -35
  50. piccolo/columns/readable.py +4 -3
  51. piccolo/columns/reference.py +9 -9
  52. piccolo/conf/apps.py +53 -54
  53. piccolo/custom_types.py +28 -6
  54. piccolo/engine/base.py +14 -14
  55. piccolo/engine/cockroach.py +5 -4
  56. piccolo/engine/finder.py +2 -2
  57. piccolo/engine/postgres.py +20 -19
  58. piccolo/engine/sqlite.py +23 -22
  59. piccolo/query/base.py +30 -29
  60. piccolo/query/functions/__init__.py +12 -0
  61. piccolo/query/functions/aggregate.py +4 -3
  62. piccolo/query/functions/array.py +151 -0
  63. piccolo/query/functions/base.py +3 -3
  64. piccolo/query/functions/datetime.py +22 -22
  65. piccolo/query/functions/string.py +4 -4
  66. piccolo/query/functions/type_conversion.py +30 -15
  67. piccolo/query/methods/alter.py +47 -46
  68. piccolo/query/methods/count.py +11 -10
  69. piccolo/query/methods/create.py +6 -5
  70. piccolo/query/methods/create_index.py +9 -8
  71. piccolo/query/methods/delete.py +7 -6
  72. piccolo/query/methods/drop_index.py +7 -6
  73. piccolo/query/methods/exists.py +6 -5
  74. piccolo/query/methods/indexes.py +4 -4
  75. piccolo/query/methods/insert.py +21 -14
  76. piccolo/query/methods/objects.py +60 -50
  77. piccolo/query/methods/raw.py +7 -6
  78. piccolo/query/methods/refresh.py +8 -7
  79. piccolo/query/methods/select.py +56 -49
  80. piccolo/query/methods/table_exists.py +5 -5
  81. piccolo/query/methods/update.py +8 -7
  82. piccolo/query/mixins.py +56 -61
  83. piccolo/query/operators/json.py +11 -11
  84. piccolo/query/proxy.py +8 -9
  85. piccolo/querystring.py +14 -15
  86. piccolo/schema.py +10 -10
  87. piccolo/table.py +105 -98
  88. piccolo/table_reflection.py +9 -9
  89. piccolo/testing/model_builder.py +16 -13
  90. piccolo/testing/random_builder.py +14 -2
  91. piccolo/testing/test_case.py +4 -4
  92. piccolo/utils/dictionary.py +3 -3
  93. piccolo/utils/encoding.py +5 -5
  94. piccolo/utils/lazy_loader.py +3 -3
  95. piccolo/utils/list.py +7 -8
  96. piccolo/utils/objects.py +4 -6
  97. piccolo/utils/pydantic.py +21 -24
  98. piccolo/utils/sql_values.py +3 -3
  99. piccolo/utils/sync.py +4 -3
  100. piccolo/utils/warnings.py +1 -2
  101. {piccolo-1.27.1.dist-info → piccolo-1.29.0.dist-info}/METADATA +1 -1
  102. {piccolo-1.27.1.dist-info → piccolo-1.29.0.dist-info}/RECORD +132 -131
  103. tests/apps/fixtures/commands/test_dump_load.py +1 -2
  104. tests/apps/migrations/auto/integration/test_migrations.py +32 -7
  105. tests/apps/migrations/auto/test_migration_manager.py +2 -2
  106. tests/apps/migrations/auto/test_schema_differ.py +22 -23
  107. tests/apps/migrations/commands/test_forwards_backwards.py +3 -3
  108. tests/columns/m2m/base.py +20 -49
  109. tests/columns/test_array.py +176 -10
  110. tests/columns/test_boolean.py +2 -4
  111. tests/columns/test_combination.py +29 -1
  112. tests/columns/test_db_column_name.py +2 -2
  113. tests/engine/test_extra_nodes.py +2 -2
  114. tests/engine/test_pool.py +3 -3
  115. tests/engine/test_transaction.py +4 -4
  116. tests/query/test_freeze.py +4 -4
  117. tests/table/instance/test_get_related.py +2 -2
  118. tests/table/test_alter.py +4 -4
  119. tests/table/test_indexes.py +1 -2
  120. tests/table/test_metaclass.py +7 -3
  121. tests/table/test_refresh.py +2 -2
  122. tests/table/test_select.py +58 -0
  123. tests/table/test_str.py +30 -22
  124. tests/table/test_update.py +18 -3
  125. tests/testing/test_model_builder.py +1 -2
  126. tests/testing/test_random_builder.py +5 -0
  127. tests/utils/test_pydantic.py +152 -134
  128. tests/utils/test_table_reflection.py +1 -2
  129. {piccolo-1.27.1.dist-info → piccolo-1.29.0.dist-info}/WHEEL +0 -0
  130. {piccolo-1.27.1.dist-info → piccolo-1.29.0.dist-info}/entry_points.txt +0 -0
  131. {piccolo-1.27.1.dist-info → piccolo-1.29.0.dist-info}/licenses/LICENSE +0 -0
  132. {piccolo-1.27.1.dist-info → piccolo-1.29.0.dist-info}/top_level.txt +0 -0
piccolo/table.py CHANGED
@@ -3,9 +3,10 @@ from __future__ import annotations
3
3
  import inspect
4
4
  import itertools
5
5
  import types
6
- import typing as t
7
6
  import warnings
7
+ from collections.abc import Sequence
8
8
  from dataclasses import dataclass, field
9
+ from typing import TYPE_CHECKING, Any, Optional, Union, cast, overload
9
10
 
10
11
  from piccolo.columns import Column
11
12
  from piccolo.columns.column_types import (
@@ -15,7 +16,6 @@ from piccolo.columns.column_types import (
15
16
  Email,
16
17
  ForeignKey,
17
18
  ReferencedTable,
18
- Secret,
19
19
  Serial,
20
20
  )
21
21
  from piccolo.columns.defaults.base import Default
@@ -55,7 +55,7 @@ from piccolo.utils.sql_values import convert_to_sql_value
55
55
  from piccolo.utils.sync import run_sync
56
56
  from piccolo.utils.warnings import colored_warning
57
57
 
58
- if t.TYPE_CHECKING: # pragma: no cover
58
+ if TYPE_CHECKING: # pragma: no cover
59
59
  from piccolo.querystring import Selectable
60
60
 
61
61
  PROTECTED_TABLENAMES = ("user",)
@@ -65,7 +65,7 @@ TABLENAME_WARNING = (
65
65
  )
66
66
 
67
67
 
68
- TABLE_REGISTRY: t.List[t.Type[Table]] = []
68
+ TABLE_REGISTRY: list[type[Table]] = []
69
69
 
70
70
 
71
71
  @dataclass
@@ -75,26 +75,26 @@ class TableMeta:
75
75
  """
76
76
 
77
77
  tablename: str = ""
78
- columns: t.List[Column] = field(default_factory=list)
79
- default_columns: t.List[Column] = field(default_factory=list)
80
- non_default_columns: t.List[Column] = field(default_factory=list)
81
- array_columns: t.List[Array] = field(default_factory=list)
82
- email_columns: t.List[Email] = field(default_factory=list)
83
- foreign_key_columns: t.List[ForeignKey] = field(default_factory=list)
78
+ columns: list[Column] = field(default_factory=list)
79
+ default_columns: list[Column] = field(default_factory=list)
80
+ non_default_columns: list[Column] = field(default_factory=list)
81
+ array_columns: list[Array] = field(default_factory=list)
82
+ email_columns: list[Email] = field(default_factory=list)
83
+ foreign_key_columns: list[ForeignKey] = field(default_factory=list)
84
84
  primary_key: Column = field(default_factory=Column)
85
- json_columns: t.List[t.Union[JSON, JSONB]] = field(default_factory=list)
86
- secret_columns: t.List[Secret] = field(default_factory=list)
87
- auto_update_columns: t.List[Column] = field(default_factory=list)
88
- tags: t.List[str] = field(default_factory=list)
89
- help_text: t.Optional[str] = None
90
- _db: t.Optional[Engine] = None
91
- m2m_relationships: t.List[M2M] = field(default_factory=list)
92
- schema: t.Optional[str] = None
85
+ json_columns: list[Union[JSON, JSONB]] = field(default_factory=list)
86
+ secret_columns: list[Column] = field(default_factory=list)
87
+ auto_update_columns: list[Column] = field(default_factory=list)
88
+ tags: list[str] = field(default_factory=list)
89
+ help_text: Optional[str] = None
90
+ _db: Optional[Engine] = None
91
+ m2m_relationships: list[M2M] = field(default_factory=list)
92
+ schema: Optional[str] = None
93
93
 
94
94
  # Records reverse foreign key relationships - i.e. when the current table
95
95
  # is the target of a foreign key. Used by external libraries such as
96
96
  # Piccolo API.
97
- _foreign_key_references: t.List[ForeignKey] = field(default_factory=list)
97
+ _foreign_key_references: list[ForeignKey] = field(default_factory=list)
98
98
 
99
99
  def get_formatted_tablename(
100
100
  self, include_schema: bool = True, quoted: bool = True
@@ -120,8 +120,8 @@ class TableMeta:
120
120
  return ".".join(components)
121
121
 
122
122
  @property
123
- def foreign_key_references(self) -> t.List[ForeignKey]:
124
- foreign_keys: t.List[ForeignKey] = list(self._foreign_key_references)
123
+ def foreign_key_references(self) -> list[ForeignKey]:
124
+ foreign_keys: list[ForeignKey] = list(self._foreign_key_references)
125
125
  lazy_column_references = LAZY_COLUMN_REFERENCES.for_tablename(
126
126
  tablename=self.tablename
127
127
  )
@@ -173,11 +173,11 @@ class TableMeta:
173
173
 
174
174
  return column_object
175
175
 
176
- def get_auto_update_values(self) -> t.Dict[Column, t.Any]:
176
+ def get_auto_update_values(self) -> dict[Column, Any]:
177
177
  """
178
178
  If columns have ``auto_update`` defined, then we retrieve these values.
179
179
  """
180
- output: t.Dict[Column, t.Any] = {}
180
+ output: dict[Column, Any] = {}
181
181
  for column in self.auto_update_columns:
182
182
  value = column._meta.auto_update
183
183
  if callable(value):
@@ -203,7 +203,7 @@ class TableMetaclass(type):
203
203
 
204
204
  # `SessionsBase` is a `Table` subclass:
205
205
  def session_auth(
206
- session_table: t.Type[SessionsBase] = SessionsBase
206
+ session_table: type[SessionsBase] = SessionsBase
207
207
  ):
208
208
  ...
209
209
 
@@ -227,11 +227,11 @@ class Table(metaclass=TableMetaclass):
227
227
 
228
228
  def __init_subclass__(
229
229
  cls,
230
- tablename: t.Optional[str] = None,
231
- db: t.Optional[Engine] = None,
232
- tags: t.Optional[t.List[str]] = None,
233
- help_text: t.Optional[str] = None,
234
- schema: t.Optional[str] = None,
230
+ tablename: Optional[str] = None,
231
+ db: Optional[Engine] = None,
232
+ tags: Optional[list[str]] = None,
233
+ help_text: Optional[str] = None,
234
+ schema: Optional[str] = None,
235
235
  ): # sourcery no-metrics
236
236
  """
237
237
  Automatically populate the _meta, which includes the tablename, and
@@ -268,17 +268,17 @@ class Table(metaclass=TableMetaclass):
268
268
  if tablename in PROTECTED_TABLENAMES:
269
269
  warnings.warn(TABLENAME_WARNING.format(tablename=tablename))
270
270
 
271
- columns: t.List[Column] = []
272
- default_columns: t.List[Column] = []
273
- non_default_columns: t.List[Column] = []
274
- array_columns: t.List[Array] = []
275
- foreign_key_columns: t.List[ForeignKey] = []
276
- secret_columns: t.List[Secret] = []
277
- json_columns: t.List[t.Union[JSON, JSONB]] = []
278
- email_columns: t.List[Email] = []
279
- auto_update_columns: t.List[Column] = []
280
- primary_key: t.Optional[Column] = None
281
- m2m_relationships: t.List[M2M] = []
271
+ columns: list[Column] = []
272
+ default_columns: list[Column] = []
273
+ non_default_columns: list[Column] = []
274
+ array_columns: list[Array] = []
275
+ foreign_key_columns: list[ForeignKey] = []
276
+ secret_columns: list[Column] = []
277
+ json_columns: list[Union[JSON, JSONB]] = []
278
+ email_columns: list[Email] = []
279
+ auto_update_columns: list[Column] = []
280
+ primary_key: Optional[Column] = None
281
+ m2m_relationships: list[M2M] = []
282
282
 
283
283
  attribute_names = itertools.chain(
284
284
  *[i.__dict__.keys() for i in reversed(cls.__mro__)]
@@ -314,15 +314,15 @@ class Table(metaclass=TableMetaclass):
314
314
  if isinstance(column, Email):
315
315
  email_columns.append(column)
316
316
 
317
- if isinstance(column, Secret):
318
- secret_columns.append(column)
319
-
320
317
  if isinstance(column, ForeignKey):
321
318
  foreign_key_columns.append(column)
322
319
 
323
320
  if isinstance(column, (JSON, JSONB)):
324
321
  json_columns.append(column)
325
322
 
323
+ if column._meta.secret:
324
+ secret_columns.append(column)
325
+
326
326
  if column._meta.auto_update is not ...:
327
327
  auto_update_columns.append(column)
328
328
 
@@ -372,7 +372,7 @@ class Table(metaclass=TableMetaclass):
372
372
 
373
373
  def __init__(
374
374
  self,
375
- _data: t.Optional[t.Dict[Column, t.Any]] = None,
375
+ _data: Optional[dict[Column, Any]] = None,
376
376
  _ignore_missing: bool = False,
377
377
  _exists_in_db: bool = False,
378
378
  **kwargs,
@@ -412,7 +412,7 @@ class Table(metaclass=TableMetaclass):
412
412
 
413
413
  # This is used by get_or_create to indicate to the user whether it
414
414
  # was an existing row or not.
415
- self._was_created: t.Optional[bool] = None
415
+ self._was_created: Optional[bool] = None
416
416
 
417
417
  for column in self._meta.columns:
418
418
  value = _data.get(column, ...)
@@ -423,7 +423,7 @@ class Table(metaclass=TableMetaclass):
423
423
 
424
424
  if value is ...:
425
425
  value = kwargs.pop(
426
- t.cast(str, column._meta.db_column_name), ...
426
+ cast(str, column._meta.db_column_name), ...
427
427
  )
428
428
 
429
429
  if value is ...:
@@ -456,7 +456,7 @@ class Table(metaclass=TableMetaclass):
456
456
 
457
457
  @classmethod
458
458
  def from_dict(
459
- cls: t.Type[TableInstance], data: t.Dict[str, t.Any]
459
+ cls: type[TableInstance], data: dict[str, Any]
460
460
  ) -> TableInstance:
461
461
  """
462
462
  Used when loading fixtures. It can be overriden by subclasses in case
@@ -468,8 +468,8 @@ class Table(metaclass=TableMetaclass):
468
468
  ###########################################################################
469
469
 
470
470
  def save(
471
- self, columns: t.Optional[t.Sequence[t.Union[Column, str]]] = None
472
- ) -> t.Union[Insert, Update]:
471
+ self, columns: Optional[Sequence[Union[Column, str]]] = None
472
+ ) -> Union[Insert, Update]:
473
473
  """
474
474
  A proxy to an insert or update query.
475
475
 
@@ -504,7 +504,7 @@ class Table(metaclass=TableMetaclass):
504
504
  for i in columns
505
505
  ]
506
506
 
507
- values: t.Dict[Column, t.Any] = {
507
+ values: dict[Column, Any] = {
508
508
  i: getattr(self, i._meta.name, None) for i in column_instances
509
509
  }
510
510
 
@@ -525,9 +525,7 @@ class Table(metaclass=TableMetaclass):
525
525
  == getattr(self, self._meta.primary_key._meta.name)
526
526
  )
527
527
 
528
- def update_self(
529
- self, values: t.Dict[t.Union[Column, str], t.Any]
530
- ) -> UpdateSelf:
528
+ def update_self(self, values: dict[Union[Column, str], Any]) -> UpdateSelf:
531
529
  """
532
530
  This allows the user to update a single object - useful when the values
533
531
  are derived from the database in some way.
@@ -581,7 +579,7 @@ class Table(metaclass=TableMetaclass):
581
579
 
582
580
  def refresh(
583
581
  self,
584
- columns: t.Optional[t.Sequence[Column]] = None,
582
+ columns: Optional[Sequence[Column]] = None,
585
583
  load_json: bool = False,
586
584
  ) -> Refresh:
587
585
  """
@@ -611,16 +609,16 @@ class Table(metaclass=TableMetaclass):
611
609
  """
612
610
  return Refresh(instance=self, columns=columns, load_json=load_json)
613
611
 
614
- @t.overload
612
+ @overload
615
613
  def get_related(
616
614
  self, foreign_key: ForeignKey[ReferencedTable]
617
615
  ) -> GetRelated[ReferencedTable]: ...
618
616
 
619
- @t.overload
617
+ @overload
620
618
  def get_related(self, foreign_key: str) -> GetRelated[Table]: ...
621
619
 
622
620
  def get_related(
623
- self, foreign_key: t.Union[str, ForeignKey[ReferencedTable]]
621
+ self, foreign_key: Union[str, ForeignKey[ReferencedTable]]
624
622
  ) -> GetRelated[ReferencedTable]:
625
623
  """
626
624
  Used to fetch a ``Table`` instance, for the target of a foreign key.
@@ -666,7 +664,7 @@ class Table(metaclass=TableMetaclass):
666
664
  self,
667
665
  *rows: Table,
668
666
  m2m: M2M,
669
- extra_column_values: t.Dict[t.Union[Column, str], t.Any] = {},
667
+ extra_column_values: dict[Union[Column, str], Any] = {},
670
668
  ) -> M2MAddRelated:
671
669
  """
672
670
  Save the row if it doesn't already exist in the database, and insert
@@ -733,7 +731,7 @@ class Table(metaclass=TableMetaclass):
733
731
  m2m=m2m,
734
732
  )
735
733
 
736
- def to_dict(self, *columns: Column) -> t.Dict[str, t.Any]:
734
+ def to_dict(self, *columns: Column) -> dict[str, Any]:
737
735
  """
738
736
  A convenience method which returns a dictionary, mapping column names
739
737
  to values for this table instance.
@@ -785,7 +783,7 @@ class Table(metaclass=TableMetaclass):
785
783
  )
786
784
  return output
787
785
 
788
- def __setitem__(self, key: str, value: t.Any):
786
+ def __setitem__(self, key: str, value: Any):
789
787
  setattr(self, key, value)
790
788
 
791
789
  def __getitem__(self, key: str):
@@ -858,8 +856,8 @@ class Table(metaclass=TableMetaclass):
858
856
 
859
857
  @classmethod
860
858
  def all_related(
861
- cls, exclude: t.Optional[t.List[t.Union[str, ForeignKey]]] = None
862
- ) -> t.List[ForeignKey]:
859
+ cls, exclude: Optional[list[Union[str, ForeignKey]]] = None
860
+ ) -> list[ForeignKey]:
863
861
  """
864
862
  Used in conjunction with ``objects`` queries. Just as we can use
865
863
  ``all_related`` on a ``ForeignKey``, you can also use it for the table
@@ -908,8 +906,8 @@ class Table(metaclass=TableMetaclass):
908
906
 
909
907
  @classmethod
910
908
  def all_columns(
911
- cls, exclude: t.Optional[t.Sequence[t.Union[str, Column]]] = None
912
- ) -> t.List[Column]:
909
+ cls, exclude: Optional[Sequence[Union[str, Column]]] = None
910
+ ) -> list[Column]:
913
911
  """
914
912
  Used in conjunction with ``select`` queries. Just as we can use
915
913
  ``all_columns`` to retrieve all of the columns from a related table,
@@ -975,7 +973,7 @@ class Table(metaclass=TableMetaclass):
975
973
 
976
974
  @classmethod
977
975
  def insert(
978
- cls: t.Type[TableInstance], *rows: TableInstance
976
+ cls: type[TableInstance], *rows: TableInstance
979
977
  ) -> Insert[TableInstance]:
980
978
  """
981
979
  Insert rows into the database.
@@ -993,7 +991,7 @@ class Table(metaclass=TableMetaclass):
993
991
  return query
994
992
 
995
993
  @classmethod
996
- def raw(cls, sql: str, *args: t.Any) -> Raw:
994
+ def raw(cls, sql: str, *args: Any) -> Raw:
997
995
  """
998
996
  Execute raw SQL queries on the underlying engine - use with caution!
999
997
 
@@ -1012,8 +1010,8 @@ class Table(metaclass=TableMetaclass):
1012
1010
 
1013
1011
  @classmethod
1014
1012
  def _process_column_args(
1015
- cls, *columns: t.Union[Selectable, str]
1016
- ) -> t.Sequence[Selectable]:
1013
+ cls, *columns: Union[Selectable, str]
1014
+ ) -> Sequence[Selectable]:
1017
1015
  """
1018
1016
  Users can specify some column arguments as either Column instances, or
1019
1017
  as strings representing the column name, for convenience.
@@ -1030,7 +1028,7 @@ class Table(metaclass=TableMetaclass):
1030
1028
 
1031
1029
  @classmethod
1032
1030
  def select(
1033
- cls, *columns: t.Union[Selectable, str], exclude_secrets=False
1031
+ cls, *columns: Union[Selectable, str], exclude_secrets=False
1034
1032
  ) -> Select:
1035
1033
  """
1036
1034
  Get data in the form of a list of dictionaries, with each dictionary
@@ -1109,8 +1107,8 @@ class Table(metaclass=TableMetaclass):
1109
1107
 
1110
1108
  @classmethod
1111
1109
  def objects(
1112
- cls: t.Type[TableInstance],
1113
- *prefetch: t.Union[ForeignKey, t.List[ForeignKey]],
1110
+ cls: type[TableInstance],
1111
+ *prefetch: Union[ForeignKey, list[ForeignKey]],
1114
1112
  ) -> Objects[TableInstance]:
1115
1113
  """
1116
1114
  Returns a list of table instances (each representing a row), which you
@@ -1151,8 +1149,8 @@ class Table(metaclass=TableMetaclass):
1151
1149
  @classmethod
1152
1150
  def count(
1153
1151
  cls,
1154
- column: t.Optional[Column] = None,
1155
- distinct: t.Optional[t.Sequence[Column]] = None,
1152
+ column: Optional[Column] = None,
1153
+ distinct: Optional[Sequence[Column]] = None,
1156
1154
  ) -> Count:
1157
1155
  """
1158
1156
  Count the number of matching rows::
@@ -1224,7 +1222,7 @@ class Table(metaclass=TableMetaclass):
1224
1222
  @classmethod
1225
1223
  def update(
1226
1224
  cls,
1227
- values: t.Optional[t.Dict[t.Union[Column, str], t.Any]] = None,
1225
+ values: Optional[dict[Union[Column, str], Any]] = None,
1228
1226
  force: bool = False,
1229
1227
  use_auto_update: bool = True,
1230
1228
  **kwargs,
@@ -1288,7 +1286,7 @@ class Table(metaclass=TableMetaclass):
1288
1286
  @classmethod
1289
1287
  def create_index(
1290
1288
  cls,
1291
- columns: t.Union[t.List[Column], t.List[str]],
1289
+ columns: Union[list[Column], list[str]],
1292
1290
  method: IndexMethod = IndexMethod.btree,
1293
1291
  if_not_exists: bool = False,
1294
1292
  ) -> CreateIndex:
@@ -1311,7 +1309,7 @@ class Table(metaclass=TableMetaclass):
1311
1309
  @classmethod
1312
1310
  def drop_index(
1313
1311
  cls,
1314
- columns: t.Union[t.List[Column], t.List[str]],
1312
+ columns: Union[list[Column], list[str]],
1315
1313
  if_exists: bool = True,
1316
1314
  ) -> DropIndex:
1317
1315
  """
@@ -1328,7 +1326,7 @@ class Table(metaclass=TableMetaclass):
1328
1326
  ###########################################################################
1329
1327
 
1330
1328
  @classmethod
1331
- def _get_index_name(cls, column_names: t.List[str]) -> str:
1329
+ def _get_index_name(cls, column_names: list[str]) -> str:
1332
1330
  """
1333
1331
  Generates an index name from the table name and column names.
1334
1332
  """
@@ -1338,7 +1336,7 @@ class Table(metaclass=TableMetaclass):
1338
1336
 
1339
1337
  @classmethod
1340
1338
  def _table_str(
1341
- cls, abbreviated=False, excluded_params: t.Optional[t.List[str]] = None
1339
+ cls, abbreviated=False, excluded_params: Optional[list[str]] = None
1342
1340
  ):
1343
1341
  """
1344
1342
  Returns a basic string representation of the table and its columns.
@@ -1357,7 +1355,7 @@ class Table(metaclass=TableMetaclass):
1357
1355
  spacer = "\n "
1358
1356
  columns = []
1359
1357
  for col in cls._meta.columns:
1360
- params: t.List[str] = []
1358
+ params: list[str] = []
1361
1359
  for key, value in col._meta.params.items():
1362
1360
  if key in excluded_params:
1363
1361
  continue
@@ -1374,6 +1372,15 @@ class Table(metaclass=TableMetaclass):
1374
1372
  columns.append(
1375
1373
  f"{col._meta.name} = {col.__class__.__name__}({params_string})"
1376
1374
  )
1375
+
1376
+ for m2m_relationship in cls._meta.m2m_relationships:
1377
+ joining_table_name = (
1378
+ m2m_relationship._meta.resolved_joining_table.__name__
1379
+ )
1380
+ columns.append(
1381
+ f"{m2m_relationship._meta.name} = M2M({joining_table_name})"
1382
+ )
1383
+
1377
1384
  columns_string = spacer.join(columns)
1378
1385
  tablename = repr(cls._meta.tablename)
1379
1386
 
@@ -1392,10 +1399,10 @@ class Table(metaclass=TableMetaclass):
1392
1399
 
1393
1400
  def create_table_class(
1394
1401
  class_name: str,
1395
- bases: t.Tuple[t.Type] = (Table,),
1396
- class_kwargs: t.Dict[str, t.Any] = {},
1397
- class_members: t.Dict[str, t.Any] = {},
1398
- ) -> t.Type[Table]:
1402
+ bases: tuple[type] = (Table,),
1403
+ class_kwargs: dict[str, Any] = {},
1404
+ class_members: dict[str, Any] = {},
1405
+ ) -> type[Table]:
1399
1406
  """
1400
1407
  Used to dynamically create ``Table``subclasses at runtime. Most users
1401
1408
  will not require this. It's mostly used internally for Piccolo's
@@ -1411,8 +1418,8 @@ def create_table_class(
1411
1418
  For example, `{'my_column': Varchar()}`.
1412
1419
 
1413
1420
  """
1414
- return t.cast(
1415
- t.Type[Table],
1421
+ return cast(
1422
+ type[Table],
1416
1423
  types.new_class(
1417
1424
  name=class_name,
1418
1425
  bases=bases,
@@ -1427,7 +1434,7 @@ def create_table_class(
1427
1434
 
1428
1435
 
1429
1436
  async def create_db_tables(
1430
- *tables: t.Type[Table], if_not_exists: bool = False
1437
+ *tables: type[Table], if_not_exists: bool = False
1431
1438
  ) -> None:
1432
1439
  """
1433
1440
  Creates the database table for each ``Table`` class passed in. The tables
@@ -1458,7 +1465,7 @@ async def create_db_tables(
1458
1465
 
1459
1466
 
1460
1467
  def create_db_tables_sync(
1461
- *tables: t.Type[Table], if_not_exists: bool = False
1468
+ *tables: type[Table], if_not_exists: bool = False
1462
1469
  ) -> None:
1463
1470
  """
1464
1471
  A sync wrapper around :func:`create_db_tables`.
@@ -1466,7 +1473,7 @@ def create_db_tables_sync(
1466
1473
  run_sync(create_db_tables(*tables, if_not_exists=if_not_exists))
1467
1474
 
1468
1475
 
1469
- def create_tables(*tables: t.Type[Table], if_not_exists: bool = False) -> None:
1476
+ def create_tables(*tables: type[Table], if_not_exists: bool = False) -> None:
1470
1477
  """
1471
1478
  This original implementation has been replaced, because it was synchronous,
1472
1479
  and felt at odds with the rest of the Piccolo codebase which is async
@@ -1485,7 +1492,7 @@ def create_tables(*tables: t.Type[Table], if_not_exists: bool = False) -> None:
1485
1492
  return create_db_tables_sync(*tables, if_not_exists=if_not_exists)
1486
1493
 
1487
1494
 
1488
- async def drop_db_tables(*tables: t.Type[Table]) -> None:
1495
+ async def drop_db_tables(*tables: type[Table]) -> None:
1489
1496
  """
1490
1497
  Drops the database table for each ``Table`` class passed in. The tables
1491
1498
  are dropped in the correct order, based on their foreign keys.
@@ -1518,14 +1525,14 @@ async def drop_db_tables(*tables: t.Type[Table]) -> None:
1518
1525
  await atomic.run()
1519
1526
 
1520
1527
 
1521
- def drop_db_tables_sync(*tables: t.Type[Table]) -> None:
1528
+ def drop_db_tables_sync(*tables: type[Table]) -> None:
1522
1529
  """
1523
1530
  A sync wrapper around :func:`drop_db_tables`.
1524
1531
  """
1525
1532
  run_sync(drop_db_tables(*tables))
1526
1533
 
1527
1534
 
1528
- def drop_tables(*tables: t.Type[Table]) -> None:
1535
+ def drop_tables(*tables: type[Table]) -> None:
1529
1536
  """
1530
1537
  This original implementation has been replaced, because it was synchronous,
1531
1538
  and felt at odds with the rest of the Piccolo codebase which is async
@@ -1548,8 +1555,8 @@ def drop_tables(*tables: t.Type[Table]) -> None:
1548
1555
 
1549
1556
 
1550
1557
  def sort_table_classes(
1551
- table_classes: t.List[t.Type[Table]],
1552
- ) -> t.List[t.Type[Table]]:
1558
+ table_classes: list[type[Table]],
1559
+ ) -> list[type[Table]]:
1553
1560
  """
1554
1561
  Sort the table classes based on their foreign keys, so they can be created
1555
1562
  in the correct order.
@@ -1564,7 +1571,7 @@ def sort_table_classes(
1564
1571
  sorter = TopologicalSorter(graph)
1565
1572
  ordered_tablenames = tuple(sorter.static_order())
1566
1573
 
1567
- output: t.List[t.Type[Table]] = []
1574
+ output: list[type[Table]] = []
1568
1575
  for tablename in ordered_tablenames:
1569
1576
  table_class = table_class_dict.get(tablename)
1570
1577
  if table_class is not None:
@@ -1574,10 +1581,10 @@ def sort_table_classes(
1574
1581
 
1575
1582
 
1576
1583
  def _get_graph(
1577
- table_classes: t.List[t.Type[Table]],
1584
+ table_classes: list[type[Table]],
1578
1585
  iterations: int = 0,
1579
1586
  max_iterations: int = 5,
1580
- ) -> t.Dict[str, t.Set[str]]:
1587
+ ) -> dict[str, set[str]]:
1581
1588
  """
1582
1589
  Analyses the tables based on their foreign keys, and returns a data
1583
1590
  structure like:
@@ -1590,13 +1597,13 @@ def _get_graph(
1590
1597
  to it via a foreign key.
1591
1598
 
1592
1599
  """
1593
- output: t.Dict[str, t.Set[str]] = {}
1600
+ output: dict[str, set[str]] = {}
1594
1601
 
1595
1602
  if iterations >= max_iterations:
1596
1603
  return output
1597
1604
 
1598
1605
  for table_class in table_classes:
1599
- dependents: t.Set[str] = set()
1606
+ dependents: set[str] = set()
1600
1607
  for fk in table_class._meta.foreign_key_columns:
1601
1608
  referenced_table = fk._foreign_key_meta.resolved_references
1602
1609
 
@@ -4,8 +4,8 @@ tables.
4
4
  """
5
5
 
6
6
  import asyncio
7
- import typing as t
8
7
  from dataclasses import dataclass
8
+ from typing import Any, Optional, Union
9
9
 
10
10
  from piccolo.apps.schema.commands.generate import get_output_schema
11
11
  from piccolo.engine import engine_finder
@@ -58,7 +58,7 @@ class Singleton(type):
58
58
  A metaclass that creates a Singleton base class when called.
59
59
  """
60
60
 
61
- _instances: t.Dict = {}
61
+ _instances: dict = {}
62
62
 
63
63
  def __call__(cls, *args, **kwargs):
64
64
  if cls not in cls._instances:
@@ -80,7 +80,7 @@ class TableStorage(metaclass=Singleton):
80
80
  works with Postgres.
81
81
  """
82
82
 
83
- def __init__(self, engine: t.Optional[Engine] = None):
83
+ def __init__(self, engine: Optional[Engine] = None):
84
84
  """
85
85
  :param engine:
86
86
  Which engine to use to make the database queries. If not specified,
@@ -89,13 +89,13 @@ class TableStorage(metaclass=Singleton):
89
89
  """
90
90
  self.engine = engine or engine_finder()
91
91
  self.tables = ImmutableDict()
92
- self._schema_tables: t.Dict[str, t.List[str]] = {}
92
+ self._schema_tables: dict[str, list[str]] = {}
93
93
 
94
94
  async def reflect(
95
95
  self,
96
96
  schema_name: str = "public",
97
- include: t.Union[t.List[str], str, None] = None,
98
- exclude: t.Union[t.List[str], str, None] = None,
97
+ include: Union[list[str], str, None] = None,
98
+ exclude: Union[list[str], str, None] = None,
99
99
  keep_existing: bool = False,
100
100
  ) -> None:
101
101
  """
@@ -154,7 +154,7 @@ class TableStorage(metaclass=Singleton):
154
154
  dict.clear(self.tables)
155
155
  self._schema_tables.clear()
156
156
 
157
- async def get_table(self, tablename: str) -> t.Optional[t.Type[Table]]:
157
+ async def get_table(self, tablename: str) -> Optional[type[Table]]:
158
158
  """
159
159
  Returns the ``Table`` class if it exists. If the table is not present
160
160
  in ``TableStorage``, it will try to reflect it.
@@ -177,7 +177,7 @@ class TableStorage(metaclass=Singleton):
177
177
  table_class = self.tables.get(tablename)
178
178
  return table_class
179
179
 
180
- async def _add_table(self, schema_name: str, table: t.Type[Table]) -> None:
180
+ async def _add_table(self, schema_name: str, table: type[Table]) -> None:
181
181
  if issubclass(table, Table):
182
182
  table_name = self._get_table_name(
183
183
  table._meta.tablename, schema_name
@@ -229,7 +229,7 @@ class TableStorage(metaclass=Singleton):
229
229
  raise ValueError("Couldn't find schema name.")
230
230
 
231
231
  @staticmethod
232
- def _to_list(value: t.Any) -> t.List:
232
+ def _to_list(value: Any) -> list:
233
233
  if isinstance(value, list):
234
234
  return value
235
235
  elif isinstance(value, (tuple, set)):