piccolo 1.27.1__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.1.dist-info → piccolo-1.28.0.dist-info}/METADATA +1 -1
  95. {piccolo-1.27.1.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.1.dist-info → piccolo-1.28.0.dist-info}/WHEEL +0 -0
  120. {piccolo-1.27.1.dist-info → piccolo-1.28.0.dist-info}/entry_points.txt +0 -0
  121. {piccolo-1.27.1.dist-info → piccolo-1.28.0.dist-info}/licenses/LICENSE +0 -0
  122. {piccolo-1.27.1.dist-info → piccolo-1.28.0.dist-info}/top_level.txt +0 -0
@@ -8,7 +8,7 @@ import datetime
8
8
  import hashlib
9
9
  import logging
10
10
  import secrets
11
- import typing as t
11
+ from typing import Any, Optional, Union
12
12
 
13
13
  from piccolo.columns import Boolean, Secret, Timestamp, Varchar
14
14
  from piccolo.columns.column_types import Serial
@@ -109,14 +109,14 @@ class BaseUser(Table, tablename="piccolo_user"):
109
109
  ###########################################################################
110
110
 
111
111
  @classmethod
112
- def update_password_sync(cls, user: t.Union[str, int], password: str):
112
+ def update_password_sync(cls, user: Union[str, int], password: str):
113
113
  """
114
114
  A sync equivalent of :meth:`update_password`.
115
115
  """
116
116
  return run_sync(cls.update_password(user, password))
117
117
 
118
118
  @classmethod
119
- async def update_password(cls, user: t.Union[str, int], password: str):
119
+ async def update_password(cls, user: Union[str, int], password: str):
120
120
  """
121
121
  The password is the raw password string e.g. ``'password123'``.
122
122
  The user can be a user ID, or a username.
@@ -139,7 +139,7 @@ class BaseUser(Table, tablename="piccolo_user"):
139
139
 
140
140
  @classmethod
141
141
  def hash_password(
142
- cls, password: str, salt: str = "", iterations: t.Optional[int] = None
142
+ cls, password: str, salt: str = "", iterations: Optional[int] = None
143
143
  ) -> str:
144
144
  """
145
145
  Hashes the password, ready for storage, and for comparing during
@@ -167,7 +167,7 @@ class BaseUser(Table, tablename="piccolo_user"):
167
167
  ).hex()
168
168
  return f"pbkdf2_sha256${iterations}${salt}${hashed}"
169
169
 
170
- def __setattr__(self, name: str, value: t.Any):
170
+ def __setattr__(self, name: str, value: Any):
171
171
  """
172
172
  Make sure that if the password is set, it's stored in a hashed form.
173
173
  """
@@ -177,7 +177,7 @@ class BaseUser(Table, tablename="piccolo_user"):
177
177
  super().__setattr__(name, value)
178
178
 
179
179
  @classmethod
180
- def split_stored_password(cls, password: str) -> t.List[str]:
180
+ def split_stored_password(cls, password: str) -> list[str]:
181
181
  elements = password.split("$")
182
182
  if len(elements) != 4:
183
183
  raise ValueError("Unable to split hashed password")
@@ -186,14 +186,14 @@ class BaseUser(Table, tablename="piccolo_user"):
186
186
  ###########################################################################
187
187
 
188
188
  @classmethod
189
- def login_sync(cls, username: str, password: str) -> t.Optional[int]:
189
+ def login_sync(cls, username: str, password: str) -> Optional[int]:
190
190
  """
191
191
  A sync equivalent of :meth:`login`.
192
192
  """
193
193
  return run_sync(cls.login(username, password))
194
194
 
195
195
  @classmethod
196
- async def login(cls, username: str, password: str) -> t.Optional[int]:
196
+ async def login(cls, username: str, password: str) -> Optional[int]:
197
197
  """
198
198
  Make sure the user exists and the password is valid. If so, the
199
199
  ``last_login`` value is updated in the database.
piccolo/columns/base.py CHANGED
@@ -4,10 +4,20 @@ import copy
4
4
  import datetime
5
5
  import decimal
6
6
  import inspect
7
- import typing as t
8
7
  import uuid
8
+ from collections.abc import Iterable
9
9
  from dataclasses import dataclass, field, fields
10
10
  from enum import Enum
11
+ from typing import (
12
+ TYPE_CHECKING,
13
+ Any,
14
+ Generic,
15
+ Optional,
16
+ TypedDict,
17
+ TypeVar,
18
+ Union,
19
+ cast,
20
+ )
11
21
 
12
22
  from piccolo.columns.choices import Choice
13
23
  from piccolo.columns.combination import Where
@@ -34,8 +44,9 @@ from piccolo.columns.reference import LazyTableReference
34
44
  from piccolo.querystring import QueryString, Selectable
35
45
  from piccolo.utils.warnings import colored_warning
36
46
 
37
- if t.TYPE_CHECKING: # pragma: no cover
47
+ if TYPE_CHECKING: # pragma: no cover
38
48
  from piccolo.columns.column_types import ForeignKey
49
+ from piccolo.query.methods.select import Select
39
50
  from piccolo.table import Table
40
51
 
41
52
 
@@ -77,19 +88,19 @@ class OnUpdate(str, Enum):
77
88
  return self.__str__()
78
89
 
79
90
 
80
- ReferencedTable = t.TypeVar("ReferencedTable", bound="Table")
91
+ ReferencedTable = TypeVar("ReferencedTable", bound="Table")
81
92
 
82
93
 
83
94
  @dataclass
84
- class ForeignKeyMeta(t.Generic[ReferencedTable]):
85
- references: t.Union[t.Type[ReferencedTable], LazyTableReference]
95
+ class ForeignKeyMeta(Generic[ReferencedTable]):
96
+ references: Union[type[ReferencedTable], LazyTableReference]
86
97
  on_delete: OnDelete
87
98
  on_update: OnUpdate
88
- target_column: t.Union[Column, str, None]
89
- proxy_columns: t.List[Column] = field(default_factory=list)
99
+ target_column: Union[Column, str, None]
100
+ proxy_columns: list[Column] = field(default_factory=list)
90
101
 
91
102
  @property
92
- def resolved_references(self) -> t.Type[Table]:
103
+ def resolved_references(self) -> type[Table]:
93
104
  """
94
105
  Evaluates the ``references`` attribute if it's a ``LazyTableReference``,
95
106
  raising a ``ValueError`` if it fails, otherwise returns a ``Table``
@@ -154,18 +165,18 @@ class ColumnMeta:
154
165
  index: bool = False
155
166
  index_method: IndexMethod = IndexMethod.btree
156
167
  required: bool = False
157
- help_text: t.Optional[str] = None
158
- choices: t.Optional[t.Type[Enum]] = None
168
+ help_text: Optional[str] = None
169
+ choices: Optional[type[Enum]] = None
159
170
  secret: bool = False
160
- auto_update: t.Any = ...
171
+ auto_update: Any = ...
161
172
 
162
173
  # Used for representing the table in migrations and the playground.
163
- params: t.Dict[str, t.Any] = field(default_factory=dict)
174
+ params: dict[str, Any] = field(default_factory=dict)
164
175
 
165
176
  ###########################################################################
166
177
 
167
178
  # Lets you to map a column to a database column with a different name.
168
- _db_column_name: t.Optional[str] = None
179
+ _db_column_name: Optional[str] = None
169
180
 
170
181
  @property
171
182
  def db_column_name(self) -> str:
@@ -178,8 +189,8 @@ class ColumnMeta:
178
189
  ###########################################################################
179
190
 
180
191
  # Set by the Table Metaclass:
181
- _name: t.Optional[str] = None
182
- _table: t.Optional[t.Type[Table]] = None
192
+ _name: Optional[str] = None
193
+ _table: Optional[type[Table]] = None
183
194
 
184
195
  @property
185
196
  def name(self) -> str:
@@ -194,7 +205,7 @@ class ColumnMeta:
194
205
  self._name = value
195
206
 
196
207
  @property
197
- def table(self) -> t.Type[Table]:
208
+ def table(self) -> type[Table]:
198
209
  if not self._table:
199
210
  raise ValueError(
200
211
  "`_table` isn't defined - the Table Metaclass should set it."
@@ -202,13 +213,13 @@ class ColumnMeta:
202
213
  return self._table
203
214
 
204
215
  @table.setter
205
- def table(self, value: t.Type[Table]):
216
+ def table(self, value: type[Table]):
206
217
  self._table = value
207
218
 
208
219
  ###########################################################################
209
220
 
210
221
  # Used by Foreign Keys:
211
- call_chain: t.List["ForeignKey"] = field(default_factory=list)
222
+ call_chain: list["ForeignKey"] = field(default_factory=list)
212
223
 
213
224
  ###########################################################################
214
225
 
@@ -220,7 +231,7 @@ class ColumnMeta:
220
231
  else:
221
232
  raise ValueError("The table has no engine defined.")
222
233
 
223
- def get_choices_dict(self) -> t.Optional[t.Dict[str, t.Any]]:
234
+ def get_choices_dict(self) -> Optional[dict[str, Any]]:
224
235
  """
225
236
  Return the choices Enum as a dict. It maps the attribute name to a
226
237
  dict containing the display name, and value.
@@ -251,8 +262,7 @@ class ColumnMeta:
251
262
  if self.call_chain:
252
263
  column_name = (
253
264
  ".".join(
254
- t.cast(str, i._meta.db_column_name)
255
- for i in self.call_chain
265
+ cast(str, i._meta.db_column_name) for i in self.call_chain
256
266
  )
257
267
  + f".{column_name}"
258
268
  )
@@ -350,18 +360,18 @@ class ColumnMeta:
350
360
  return self.copy()
351
361
 
352
362
 
353
- class ColumnKwargs(t.TypedDict, total=False):
363
+ class ColumnKwargs(TypedDict, total=False):
354
364
  null: bool
355
365
  primary_key: bool
356
366
  unique: bool
357
367
  index: bool
358
368
  index_method: IndexMethod
359
369
  required: bool
360
- help_text: t.Optional[str]
361
- choices: t.Optional[t.Type[Enum]]
362
- db_column_name: t.Optional[str]
370
+ help_text: Optional[str]
371
+ choices: Optional[type[Enum]]
372
+ db_column_name: Optional[str]
363
373
  secret: bool
364
- auto_update: t.Any
374
+ auto_update: Any
365
375
 
366
376
 
367
377
  class Column(Selectable):
@@ -463,8 +473,8 @@ class Column(Selectable):
463
473
 
464
474
  """
465
475
 
466
- value_type: t.Type = int
467
- default: t.Any
476
+ value_type: type = int
477
+ default: Any
468
478
 
469
479
  def __init__(
470
480
  self,
@@ -474,11 +484,11 @@ class Column(Selectable):
474
484
  index: bool = False,
475
485
  index_method: IndexMethod = IndexMethod.btree,
476
486
  required: bool = False,
477
- help_text: t.Optional[str] = None,
478
- choices: t.Optional[t.Type[Enum]] = None,
479
- db_column_name: t.Optional[str] = None,
487
+ help_text: Optional[str] = None,
488
+ choices: Optional[type[Enum]] = None,
489
+ db_column_name: Optional[str] = None,
480
490
  secret: bool = False,
481
- auto_update: t.Any = ...,
491
+ auto_update: Any = ...,
482
492
  **kwargs,
483
493
  ) -> None:
484
494
  # This is for backwards compatibility - originally the `primary_key`
@@ -522,12 +532,12 @@ class Column(Selectable):
522
532
  auto_update=auto_update,
523
533
  )
524
534
 
525
- self._alias: t.Optional[str] = None
535
+ self._alias: Optional[str] = None
526
536
 
527
537
  def _validate_default(
528
538
  self,
529
- default: t.Any,
530
- allowed_types: t.Iterable[t.Union[None, t.Type[t.Any]]],
539
+ default: Any,
540
+ allowed_types: Iterable[Union[None, type[Any]]],
531
541
  allow_recursion: bool = True,
532
542
  ) -> bool:
533
543
  """
@@ -564,7 +574,7 @@ class Column(Selectable):
564
574
  )
565
575
 
566
576
  def _validate_choices(
567
- self, choices: t.Type[Enum], allowed_type: t.Type[t.Any]
577
+ self, choices: type[Enum], allowed_type: type[Any]
568
578
  ) -> bool:
569
579
  """
570
580
  Make sure the choices value has values of the allowed_type.
@@ -590,18 +600,40 @@ class Column(Selectable):
590
600
 
591
601
  return True
592
602
 
593
- def is_in(self, values: t.List[t.Any]) -> Where:
594
- if len(values) == 0:
595
- raise ValueError(
596
- "The `values` list argument must contain at least one value."
597
- )
603
+ def is_in(self, values: Union[Select, QueryString, list[Any]]) -> Where:
604
+ from piccolo.query.methods.select import Select
605
+
606
+ if isinstance(values, list):
607
+ if len(values) == 0:
608
+ raise ValueError(
609
+ "The `values` list argument must contain at least one "
610
+ "value."
611
+ )
612
+ elif isinstance(values, Select):
613
+ if len(values.columns_delegate.selected_columns) != 1:
614
+ raise ValueError(
615
+ "A sub select must only return a single column."
616
+ )
617
+ values = values.querystrings[0]
618
+
598
619
  return Where(column=self, values=values, operator=In)
599
620
 
600
- def not_in(self, values: t.List[t.Any]) -> Where:
601
- if len(values) == 0:
602
- raise ValueError(
603
- "The `values` list argument must contain at least one value."
604
- )
621
+ def not_in(self, values: Union[Select, QueryString, list[Any]]) -> Where:
622
+ from piccolo.query.methods.select import Select
623
+
624
+ if isinstance(values, list):
625
+ if len(values) == 0:
626
+ raise ValueError(
627
+ "The `values` list argument must contain at least one "
628
+ "value."
629
+ )
630
+ elif isinstance(values, Select):
631
+ if len(values.columns_delegate.selected_columns) != 1:
632
+ raise ValueError(
633
+ "A sub select must only return a single column."
634
+ )
635
+ values = values.querystrings[0]
636
+
605
637
  return Where(column=self, values=values, operator=NotIn)
606
638
 
607
639
  def like(self, value: str) -> Where:
@@ -628,7 +660,7 @@ class Column(Selectable):
628
660
 
629
661
  """
630
662
  if self._meta.engine_type in ("postgres", "cockroach"):
631
- operator: t.Type[ComparisonOperator] = ILike
663
+ operator: type[ComparisonOperator] = ILike
632
664
  else:
633
665
  colored_warning(
634
666
  "SQLite doesn't support ILIKE, falling back to LIKE."
@@ -799,7 +831,7 @@ class Column(Selectable):
799
831
  virtual_foreign_key.set_proxy_columns()
800
832
  return virtual_foreign_key
801
833
 
802
- def get_default_value(self) -> t.Any:
834
+ def get_default_value(self) -> Any:
803
835
  """
804
836
  If the column has a default attribute, return it. If it's callable,
805
837
  return the response instead.
@@ -845,7 +877,7 @@ class Column(Selectable):
845
877
 
846
878
  def get_sql_value(
847
879
  self,
848
- value: t.Any,
880
+ value: Any,
849
881
  delimiter: str = "'",
850
882
  ) -> str:
851
883
  """
@@ -946,8 +978,8 @@ class Column(Selectable):
946
978
  if not self._meta.null:
947
979
  query += " NOT NULL"
948
980
 
949
- foreign_key_meta = t.cast(
950
- t.Optional[ForeignKeyMeta],
981
+ foreign_key_meta = cast(
982
+ Optional[ForeignKeyMeta],
951
983
  getattr(self, "_foreign_key_meta", None),
952
984
  )
953
985
  if foreign_key_meta:
@@ -1004,4 +1036,4 @@ class Column(Selectable):
1004
1036
  )
1005
1037
 
1006
1038
 
1007
- Self = t.TypeVar("Self", bound=Column)
1039
+ Self = TypeVar("Self", bound=Column)
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
- import typing as t
4
3
  from dataclasses import dataclass
4
+ from typing import Any
5
5
 
6
6
 
7
7
  @dataclass
@@ -29,5 +29,5 @@ class Choice:
29
29
 
30
30
  """
31
31
 
32
- value: t.Any
32
+ value: Any
33
33
  display_name: str