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
@@ -5,12 +5,13 @@ import builtins
5
5
  import datetime
6
6
  import decimal
7
7
  import inspect
8
- import typing as t
9
8
  import uuid
10
9
  import warnings
10
+ from collections.abc import Callable, Iterable
11
11
  from copy import deepcopy
12
12
  from dataclasses import dataclass, field
13
13
  from enum import Enum
14
+ from typing import Any, Optional
14
15
 
15
16
  from piccolo.columns import Column
16
17
  from piccolo.columns.defaults.base import Default
@@ -61,8 +62,8 @@ class UniqueGlobalNamesMeta(type):
61
62
 
62
63
  @staticmethod
63
64
  def get_unique_class_attribute_values(
64
- class_attributes: t.Dict[str, t.Any],
65
- ) -> t.Set[t.Any]:
65
+ class_attributes: dict[str, Any],
66
+ ) -> set[Any]:
66
67
  """
67
68
  Return class attribute values.
68
69
 
@@ -87,9 +88,9 @@ class UniqueGlobalNamesMeta(type):
87
88
 
88
89
  @staticmethod
89
90
  def merge_class_attributes(
90
- class_attributes1: t.Dict[str, t.Any],
91
- class_attributes2: t.Dict[str, t.Any],
92
- ) -> t.Dict[str, t.Any]:
91
+ class_attributes1: dict[str, Any],
92
+ class_attributes2: dict[str, Any],
93
+ ) -> dict[str, Any]:
93
94
  """
94
95
  Merges two class attribute dictionaries.
95
96
 
@@ -104,12 +105,12 @@ class UniqueGlobalNamesMeta(type):
104
105
  return dict(**class_attributes1, **class_attributes2)
105
106
 
106
107
  @staticmethod
107
- def get_column_class_attributes() -> t.Dict[str, str]:
108
+ def get_column_class_attributes() -> dict[str, str]:
108
109
  """Automatically generates global names for each column type."""
109
110
 
110
111
  import piccolo.columns.column_types
111
112
 
112
- class_attributes: t.Dict[str, str] = {}
113
+ class_attributes: dict[str, str] = {}
113
114
  for module_global in piccolo.columns.column_types.__dict__.values():
114
115
  try:
115
116
  if module_global is not Column and issubclass(
@@ -150,7 +151,7 @@ class UniqueGlobalNames(metaclass=UniqueGlobalNamesMeta):
150
151
  EXTERNAL_UUID = f"{EXTERNAL_MODULE_UUID}.{uuid.UUID.__name__}"
151
152
 
152
153
  # This attribute is set in metaclass
153
- unique_names: t.Set[str]
154
+ unique_names: set[str]
154
155
 
155
156
  @classmethod
156
157
  def warn_if_is_conflicting_name(
@@ -172,7 +173,7 @@ class UniqueGlobalNames(metaclass=UniqueGlobalNamesMeta):
172
173
 
173
174
  @staticmethod
174
175
  def warn_if_are_conflicting_objects(
175
- objects: t.Iterable[CanConflictWithGlobalNames],
176
+ objects: Iterable[CanConflictWithGlobalNames],
176
177
  ) -> None:
177
178
  """
178
179
  Call each object's ``raise_if_is_conflicting_with_global_name`` method.
@@ -192,8 +193,8 @@ class UniqueGlobalNameConflictWarning(UserWarning):
192
193
  @dataclass
193
194
  class Import(CanConflictWithGlobalNames):
194
195
  module: str
195
- target: t.Optional[str] = None
196
- expect_conflict_with_global_name: t.Optional[str] = None
196
+ target: Optional[str] = None
197
+ expect_conflict_with_global_name: Optional[str] = None
197
198
 
198
199
  def __post_init__(self) -> None:
199
200
  if (
@@ -256,9 +257,9 @@ class Definition(CanConflictWithGlobalNames, abc.ABC):
256
257
 
257
258
  @dataclass
258
259
  class SerialisedParams:
259
- params: t.Dict[str, t.Any]
260
- extra_imports: t.List[Import]
261
- extra_definitions: t.List[Definition] = field(default_factory=list)
260
+ params: dict[str, Any]
261
+ extra_imports: list[Import]
262
+ extra_definitions: list[Definition] = field(default_factory=list)
262
263
 
263
264
 
264
265
  ###############################################################################
@@ -273,7 +274,7 @@ def check_equality(self, other):
273
274
 
274
275
  @dataclass
275
276
  class SerialisedBuiltin:
276
- builtin: t.Any
277
+ builtin: Any
277
278
 
278
279
  def __hash__(self):
279
280
  return hash(self.builtin.__name__)
@@ -335,7 +336,7 @@ class SerialisedEnumInstance:
335
336
 
336
337
  @dataclass
337
338
  class SerialisedTableType(Definition):
338
- table_type: t.Type[Table]
339
+ table_type: type[Table]
339
340
 
340
341
  def __hash__(self):
341
342
  return hash(
@@ -361,7 +362,7 @@ class SerialisedTableType(Definition):
361
362
 
362
363
  # When creating a ForeignKey, the user can specify a column other than
363
364
  # the primary key to reference.
364
- serialised_target_columns: t.Set[SerialisedColumnInstance] = set()
365
+ serialised_target_columns: set[SerialisedColumnInstance] = set()
365
366
 
366
367
  for fk_column in self.table_type._meta._foreign_key_references:
367
368
  target_column = fk_column._foreign_key_meta.target_column
@@ -426,7 +427,7 @@ class SerialisedTableType(Definition):
426
427
 
427
428
  @dataclass
428
429
  class SerialisedEnumType:
429
- enum_type: t.Type[Enum]
430
+ enum_type: type[Enum]
430
431
 
431
432
  def __hash__(self):
432
433
  return hash(self.__repr__())
@@ -442,7 +443,7 @@ class SerialisedEnumType:
442
443
 
443
444
  @dataclass
444
445
  class SerialisedCallable:
445
- callable_: t.Callable
446
+ callable_: Callable
446
447
 
447
448
  def __hash__(self):
448
449
  return hash(self.callable_.__name__)
@@ -487,14 +488,14 @@ class SerialisedDecimal:
487
488
  ###############################################################################
488
489
 
489
490
 
490
- def serialise_params(params: t.Dict[str, t.Any]) -> SerialisedParams:
491
+ def serialise_params(params: dict[str, Any]) -> SerialisedParams:
491
492
  """
492
493
  When writing column params to a migration file, we need to serialise some
493
494
  of the values.
494
495
  """
495
496
  params = deepcopy(params)
496
- extra_imports: t.List[Import] = []
497
- extra_definitions: t.List[Definition] = []
497
+ extra_imports: list[Import] = []
498
+ extra_definitions: list[Definition] = []
498
499
 
499
500
  for key, value in params.items():
500
501
  # Builtins, such as str, list and dict.
@@ -725,7 +726,7 @@ def serialise_params(params: t.Dict[str, t.Any]) -> SerialisedParams:
725
726
  )
726
727
 
727
728
 
728
- def deserialise_params(params: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]:
729
+ def deserialise_params(params: dict[str, Any]) -> dict[str, Any]:
729
730
  """
730
731
  When reading column params from a migration file, we need to convert
731
732
  them from their serialised form.
@@ -737,6 +738,8 @@ def deserialise_params(params: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]:
737
738
  if isinstance(value, str) and not isinstance(value, Enum):
738
739
  if value != "self":
739
740
  params[key] = deserialise_legacy_params(name=key, value=value)
741
+ elif isinstance(value, SerialisedColumnInstance):
742
+ params[key] = value.instance
740
743
  elif isinstance(value, SerialisedClassInstance):
741
744
  params[key] = value.instance
742
745
  elif isinstance(value, SerialisedUUID):
@@ -1,14 +1,14 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import datetime
4
- import typing as t
4
+ from typing import Any
5
5
 
6
6
  from piccolo.columns.column_types import OnDelete, OnUpdate
7
7
  from piccolo.columns.defaults.timestamp import TimestampNow
8
8
  from piccolo.table import create_table_class
9
9
 
10
10
 
11
- def deserialise_legacy_params(name: str, value: str) -> t.Any:
11
+ def deserialise_legacy_params(name: str, value: str) -> Any:
12
12
  """
13
13
  Earlier versions of Piccolo serialised parameters differently. This is
14
14
  here purely for backwards compatibility.
@@ -2,7 +2,6 @@ from __future__ import annotations
2
2
 
3
3
  import os
4
4
  import sys
5
- import typing as t
6
5
 
7
6
  from piccolo.apps.migrations.auto.migration_manager import MigrationManager
8
7
  from piccolo.apps.migrations.commands.base import (
@@ -31,7 +30,7 @@ class BackwardsMigrationManager(BaseMigrationManager):
31
30
  super().__init__()
32
31
 
33
32
  async def run_migrations_backwards(self, app_config: AppConfig):
34
- migration_modules: t.Dict[str, MigrationModule] = (
33
+ migration_modules: dict[str, MigrationModule] = (
35
34
  self.get_migration_modules(
36
35
  app_config.resolved_migrations_folder_path
37
36
  )
@@ -3,8 +3,8 @@ from __future__ import annotations
3
3
  import importlib
4
4
  import os
5
5
  import sys
6
- import typing as t
7
6
  from dataclasses import dataclass
7
+ from typing import Optional, cast
8
8
 
9
9
  from piccolo.apps.migrations.auto.diffable_table import DiffableTable
10
10
  from piccolo.apps.migrations.auto.migration_manager import MigrationManager
@@ -16,7 +16,7 @@ from piccolo.conf.apps import AppConfig, Finder, MigrationModule
16
16
  @dataclass
17
17
  class MigrationResult:
18
18
  success: bool
19
- message: t.Optional[str] = None
19
+ message: Optional[str] = None
20
20
 
21
21
 
22
22
  class BaseMigrationManager(Finder):
@@ -32,7 +32,7 @@ class BaseMigrationManager(Finder):
32
32
 
33
33
  def get_migration_modules(
34
34
  self, folder_path: str
35
- ) -> t.Dict[str, MigrationModule]:
35
+ ) -> dict[str, MigrationModule]:
36
36
  """
37
37
  Imports the migration modules in the given folder path, and returns
38
38
  a mapping of migration ID to the corresponding migration module.
@@ -50,8 +50,8 @@ class BaseMigrationManager(Finder):
50
50
  if ((i not in excluded) and i.endswith(".py"))
51
51
  ]
52
52
 
53
- modules: t.List[MigrationModule] = [
54
- t.cast(MigrationModule, importlib.import_module(name))
53
+ modules: list[MigrationModule] = [
54
+ cast(MigrationModule, importlib.import_module(name))
55
55
  for name in migration_names
56
56
  ]
57
57
  for m in modules:
@@ -62,8 +62,8 @@ class BaseMigrationManager(Finder):
62
62
  return migration_modules
63
63
 
64
64
  def get_migration_ids(
65
- self, migration_module_dict: t.Dict[str, MigrationModule]
66
- ) -> t.List[str]:
65
+ self, migration_module_dict: dict[str, MigrationModule]
66
+ ) -> list[str]:
67
67
  """
68
68
  Returns a list of migration IDs, from the Python migration files.
69
69
  """
@@ -72,9 +72,9 @@ class BaseMigrationManager(Finder):
72
72
  async def get_migration_managers(
73
73
  self,
74
74
  app_config: AppConfig,
75
- max_migration_id: t.Optional[str] = None,
75
+ max_migration_id: Optional[str] = None,
76
76
  offset: int = 0,
77
- ) -> t.List[MigrationManager]:
77
+ ) -> list[MigrationManager]:
78
78
  """
79
79
  Call the forwards coroutine in each migration module. Each one should
80
80
  return a `MigrationManger`. Combine all of the results, and return in
@@ -84,11 +84,11 @@ class BaseMigrationManager(Finder):
84
84
  If set, only MigrationManagers up to and including the given
85
85
  migration ID will be returned.
86
86
  """
87
- migration_managers: t.List[MigrationManager] = []
87
+ migration_managers: list[MigrationManager] = []
88
88
 
89
89
  migrations_folder = app_config.resolved_migrations_folder_path
90
90
 
91
- migration_modules: t.Dict[str, MigrationModule] = (
91
+ migration_modules: dict[str, MigrationModule] = (
92
92
  self.get_migration_modules(migrations_folder)
93
93
  )
94
94
 
@@ -118,7 +118,7 @@ class BaseMigrationManager(Finder):
118
118
  self,
119
119
  app_name: str,
120
120
  table_class_name: str,
121
- max_migration_id: t.Optional[str] = None,
121
+ max_migration_id: Optional[str] = None,
122
122
  offset: int = 0,
123
123
  ) -> DiffableTable:
124
124
  """
@@ -1,5 +1,4 @@
1
1
  import dataclasses
2
- import typing as t
3
2
 
4
3
  from piccolo.apps.migrations.commands.base import BaseMigrationManager
5
4
  from piccolo.apps.migrations.tables import Migration
@@ -19,11 +18,11 @@ class CheckMigrationManager(BaseMigrationManager):
19
18
  self.app_name = app_name
20
19
  super().__init__()
21
20
 
22
- async def get_migration_statuses(self) -> t.List[MigrationStatus]:
21
+ async def get_migration_statuses(self) -> list[MigrationStatus]:
23
22
  # Make sure the migration table exists, otherwise we'll get an error.
24
23
  await self.create_migration_table()
25
24
 
26
- migration_statuses: t.List[MigrationStatus] = []
25
+ migration_statuses: list[MigrationStatus] = []
27
26
 
28
27
  app_modules = self.get_app_modules()
29
28
 
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- import typing as t
3
+ from typing import cast
4
4
 
5
5
  from piccolo.apps.migrations.commands.base import BaseMigrationManager
6
6
  from piccolo.apps.migrations.tables import Migration
@@ -12,7 +12,7 @@ class CleanMigrationManager(BaseMigrationManager):
12
12
  self.auto_agree = auto_agree
13
13
  super().__init__()
14
14
 
15
- def get_migration_ids_to_remove(self) -> t.List[str]:
15
+ def get_migration_ids_to_remove(self) -> list[str]:
16
16
  """
17
17
  Returns a list of migration ID strings, which are rows in the table,
18
18
  but don't have a corresponding migration module on disk.
@@ -37,7 +37,7 @@ class CleanMigrationManager(BaseMigrationManager):
37
37
  if len(migration_ids) > 0:
38
38
  query = query.where(Migration.name.not_in(migration_ids))
39
39
 
40
- return t.cast(t.List[str], query.run_sync())
40
+ return cast(list[str], query.run_sync())
41
41
 
42
42
  async def run(self):
43
43
  print("Checking the migration table ...")
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import sys
4
- import typing as t
5
4
 
6
5
  from piccolo.apps.migrations.auto.migration_manager import MigrationManager
7
6
  from piccolo.apps.migrations.commands.base import (
@@ -32,7 +31,7 @@ class ForwardsMigrationManager(BaseMigrationManager):
32
31
  app_name=app_config.app_name
33
32
  )
34
33
 
35
- migration_modules: t.Dict[str, MigrationModule] = (
34
+ migration_modules: dict[str, MigrationModule] = (
36
35
  self.get_migration_modules(
37
36
  app_config.resolved_migrations_folder_path
38
37
  )
@@ -3,10 +3,10 @@ from __future__ import annotations
3
3
  import datetime
4
4
  import os
5
5
  import string
6
- import typing as t
7
6
  from dataclasses import dataclass
8
7
  from itertools import chain
9
8
  from types import ModuleType
9
+ from typing import Optional
10
10
 
11
11
  import black
12
12
  import jinja2
@@ -33,7 +33,7 @@ JINJA_ENV = jinja2.Environment(
33
33
  loader=jinja2.FileSystemLoader(searchpath=TEMPLATE_DIRECTORY),
34
34
  )
35
35
 
36
- MIGRATION_MODULES: t.Dict[str, ModuleType] = {}
36
+ MIGRATION_MODULES: dict[str, ModuleType] = {}
37
37
 
38
38
  VALID_PYTHON_MODULE_CHARACTERS = string.ascii_lowercase + string.digits + "_"
39
39
 
@@ -115,7 +115,7 @@ async def _create_new_migration(
115
115
  app_config: AppConfig,
116
116
  auto: bool = False,
117
117
  description: str = "",
118
- auto_input: t.Optional[str] = None,
118
+ auto_input: Optional[str] = None,
119
119
  ) -> NewMigrationMeta:
120
120
  """
121
121
  Creates a new migration file on disk.
@@ -170,13 +170,13 @@ async def _create_new_migration(
170
170
 
171
171
 
172
172
  class AutoMigrationManager(BaseMigrationManager):
173
- def __init__(self, auto_input: t.Optional[str] = None, *args, **kwargs):
173
+ def __init__(self, auto_input: Optional[str] = None, *args, **kwargs):
174
174
  self.auto_input = auto_input
175
175
  super().__init__(*args, **kwargs)
176
176
 
177
177
  async def get_alter_statements(
178
178
  self, app_config: AppConfig
179
- ) -> t.List[AlterStatements]:
179
+ ) -> list[AlterStatements]:
180
180
  """
181
181
  Works out which alter statements are required.
182
182
  """
@@ -214,7 +214,7 @@ async def new(
214
214
  app_name: str,
215
215
  auto: bool = False,
216
216
  desc: str = "",
217
- auto_input: t.Optional[str] = None,
217
+ auto_input: Optional[str] = None,
218
218
  ):
219
219
  """
220
220
  Creates a new migration file in the migrations folder.
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- import typing as t
3
+ from typing import Optional
4
4
 
5
5
  from piccolo.columns import Timestamp, Varchar
6
6
  from piccolo.columns.defaults.timestamp import TimestampNow
@@ -14,8 +14,8 @@ class Migration(Table):
14
14
 
15
15
  @classmethod
16
16
  async def get_migrations_which_ran(
17
- cls, app_name: t.Optional[str] = None
18
- ) -> t.List[str]:
17
+ cls, app_name: Optional[str] = None
18
+ ) -> list[str]:
19
19
  """
20
20
  Returns the names of migrations which have already run, by inspecting
21
21
  the database.
@@ -8,15 +8,19 @@ import sys
8
8
  import uuid
9
9
  from decimal import Decimal
10
10
  from enum import Enum
11
+ from typing import Optional
11
12
 
12
13
  from piccolo.columns import (
13
14
  JSON,
15
+ M2M,
14
16
  UUID,
17
+ Array,
15
18
  Boolean,
16
19
  Date,
17
20
  ForeignKey,
18
21
  Integer,
19
22
  Interval,
23
+ LazyTableReference,
20
24
  Numeric,
21
25
  Serial,
22
26
  Text,
@@ -24,7 +28,7 @@ from piccolo.columns import (
24
28
  Varchar,
25
29
  )
26
30
  from piccolo.columns.readable import Readable
27
- from piccolo.engine import PostgresEngine, SQLiteEngine
31
+ from piccolo.engine import CockroachEngine, PostgresEngine, SQLiteEngine
28
32
  from piccolo.engine.base import Engine
29
33
  from piccolo.table import Table
30
34
  from piccolo.utils.warnings import colored_string
@@ -47,6 +51,7 @@ class Band(Table):
47
51
  name = Varchar(length=50)
48
52
  manager = ForeignKey(references=Manager, null=True)
49
53
  popularity = Integer()
54
+ genres = M2M(LazyTableReference("GenreToBand", module_path=__name__))
50
55
 
51
56
  @classmethod
52
57
  def get_readable(cls) -> Readable:
@@ -149,6 +154,7 @@ class Album(Table):
149
154
  band = ForeignKey(Band)
150
155
  release_date = Date()
151
156
  recorded_at = ForeignKey(RecordingStudio)
157
+ awards = Array(Varchar())
152
158
 
153
159
  @classmethod
154
160
  def get_readable(cls) -> Readable:
@@ -158,6 +164,26 @@ class Album(Table):
158
164
  )
159
165
 
160
166
 
167
+ class Genre(Table):
168
+ id: Serial
169
+ name = Varchar()
170
+ bands = M2M(LazyTableReference("GenreToBand", module_path=__name__))
171
+
172
+ @classmethod
173
+ def get_readable(cls) -> Readable:
174
+ return Readable(
175
+ template="%s",
176
+ columns=[cls.name],
177
+ )
178
+
179
+
180
+ class GenreToBand(Table):
181
+ id: Serial
182
+ band = ForeignKey(Band)
183
+ genre = ForeignKey(Genre)
184
+ reason = Text(null=True, default=None)
185
+
186
+
161
187
  TABLES = (
162
188
  Manager,
163
189
  Band,
@@ -168,6 +194,8 @@ TABLES = (
168
194
  DiscountCode,
169
195
  RecordingStudio,
170
196
  Album,
197
+ Genre,
198
+ GenreToBand,
171
199
  )
172
200
 
173
201
 
@@ -265,6 +293,7 @@ def populate():
265
293
  Album.recorded_at: recording_studio_1,
266
294
  Album.band: pythonistas,
267
295
  Album.release_date: datetime.date(year=2021, month=1, day=1),
296
+ Album.awards: ["Grammy Award 2021"],
268
297
  }
269
298
  ),
270
299
  Album(
@@ -273,35 +302,55 @@ def populate():
273
302
  Album.recorded_at: recording_studio_2,
274
303
  Album.band: rustaceans,
275
304
  Album.release_date: datetime.date(year=2022, month=2, day=2),
305
+ Album.awards: ["Mercury Prize 2022"],
276
306
  }
277
307
  ),
278
308
  ).run_sync()
279
309
 
310
+ genres = Genre.insert(
311
+ Genre(name="Rock"),
312
+ Genre(name="Classical"),
313
+ Genre(name="Folk"),
314
+ ).run_sync()
315
+
316
+ GenreToBand.insert(
317
+ GenreToBand(
318
+ band=pythonistas.id,
319
+ genre=genres[0]["id"],
320
+ reason="Because they rock.",
321
+ ),
322
+ GenreToBand(band=pythonistas.id, genre=genres[2]["id"]),
323
+ GenreToBand(band=rustaceans.id, genre=genres[2]["id"]),
324
+ GenreToBand(band=c_sharps.id, genre=genres[0]["id"]),
325
+ GenreToBand(band=c_sharps.id, genre=genres[1]["id"]),
326
+ ).run_sync()
327
+
280
328
 
281
329
  def run(
282
330
  engine: str = "sqlite",
283
- user: str = "piccolo",
284
- password: str = "piccolo",
331
+ user: Optional[str] = None,
332
+ password: Optional[str] = None,
285
333
  database: str = "piccolo_playground",
286
334
  host: str = "localhost",
287
- port: int = 5432,
335
+ port: Optional[int] = None,
288
336
  ipython_profile: bool = False,
289
337
  ):
290
338
  """
291
339
  Creates a test database to play with.
292
340
 
293
341
  :param engine:
294
- Which database engine to use - options are sqlite or postgres
342
+ Which database engine to use - options are sqlite, postgres or
343
+ cockroach
295
344
  :param user:
296
- Postgres user
345
+ Database user (ignored for SQLite)
297
346
  :param password:
298
- Postgres password
347
+ Database password (ignored for SQLite)
299
348
  :param database:
300
- Postgres database
349
+ Database name (ignored for SQLite)
301
350
  :param host:
302
- Postgres host
351
+ Database host (ignored for SQLite)
303
352
  :param port:
304
- Postgres port
353
+ Database port (ignored for SQLite)
305
354
  :param ipython_profile:
306
355
  Set to true to use your own IPython profile. Located at ~/.ipython/.
307
356
  For more info see the IPython docs
@@ -320,9 +369,19 @@ def run(
320
369
  {
321
370
  "host": host,
322
371
  "database": database,
323
- "user": user,
324
- "password": password,
325
- "port": port,
372
+ "user": user or "piccolo",
373
+ "password": password or "piccolo",
374
+ "port": port or 5432,
375
+ }
376
+ )
377
+ elif engine.upper() == "COCKROACH":
378
+ db = CockroachEngine(
379
+ {
380
+ "host": host,
381
+ "database": database,
382
+ "user": user or "root",
383
+ "password": password or "",
384
+ "port": port or 26257,
326
385
  }
327
386
  )
328
387
  else: