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
@@ -1,5 +1,5 @@
1
1
  import asyncio
2
- import typing as t
2
+ from typing import cast
3
3
  from unittest import TestCase
4
4
 
5
5
  import pytest
@@ -169,7 +169,7 @@ class TestTransactionExists(TestCase):
169
169
  """
170
170
  Make sure we can detect when code is within a transaction.
171
171
  """
172
- engine = t.cast(SQLiteEngine, Manager._meta.db)
172
+ engine = cast(SQLiteEngine, Manager._meta.db)
173
173
 
174
174
  async def run_inside_transaction():
175
175
  async with engine.transaction():
@@ -198,7 +198,7 @@ class TestTransactionType(TestCase):
198
198
 
199
199
  https://github.com/piccolo-orm/piccolo/issues/687
200
200
  """
201
- engine = t.cast(SQLiteEngine, Manager._meta.db)
201
+ engine = cast(SQLiteEngine, Manager._meta.db)
202
202
 
203
203
  async def run_transaction(name: str):
204
204
  async with engine.transaction(
@@ -234,7 +234,7 @@ class TestTransactionType(TestCase):
234
234
  """
235
235
  Similar to above, but with ``Atomic``.
236
236
  """
237
- engine = t.cast(SQLiteEngine, Manager._meta.db)
237
+ engine = cast(SQLiteEngine, Manager._meta.db)
238
238
 
239
239
  async def run_atomic(name: str):
240
240
  atomic = engine.atomic(transaction_type=TransactionType.immediate)
@@ -1,6 +1,6 @@
1
1
  import timeit
2
- import typing as t
3
2
  from dataclasses import dataclass
3
+ from typing import Any, Union
4
4
  from unittest import mock
5
5
 
6
6
  from piccolo.columns import Integer, Varchar
@@ -12,8 +12,8 @@ from tests.example_apps.music.tables import Band
12
12
 
13
13
  @dataclass
14
14
  class QueryResponse:
15
- query: t.Union[Query, FrozenQuery]
16
- response: t.Any
15
+ query: Union[Query, FrozenQuery]
16
+ response: Any
17
17
 
18
18
 
19
19
  class TestFreeze(DBTestCase):
@@ -23,7 +23,7 @@ class TestFreeze(DBTestCase):
23
23
  """
24
24
  self.insert_rows()
25
25
 
26
- query_responses: t.List[QueryResponse] = [
26
+ query_responses: list[QueryResponse] = [
27
27
  QueryResponse(
28
28
  query=(
29
29
  Band.select(Band.name)
@@ -1,4 +1,4 @@
1
- import typing as t
1
+ from typing import cast
2
2
 
3
3
  from piccolo.testing.test_case import AsyncTableTest
4
4
  from tests.example_apps.music.tables import Band, Concert, Manager, Venue
@@ -52,7 +52,7 @@ class TestGetRelated(AsyncTableTest):
52
52
  """
53
53
  Make sure it also works using a string representation of a foreign key.
54
54
  """
55
- manager = t.cast(Manager, await self.band.get_related("manager"))
55
+ manager = cast(Manager, await self.band.get_related("manager"))
56
56
  self.assertTrue(manager.id == self.manager.id)
57
57
 
58
58
  async def test_invalid_string(self):
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,13 +1,13 @@
1
1
  from unittest import TestCase
2
2
  from unittest.mock import MagicMock, patch
3
3
 
4
- from piccolo.columns import Secret
5
4
  from piccolo.columns.column_types import (
6
5
  JSON,
7
6
  JSONB,
8
7
  Array,
9
8
  Email,
10
9
  ForeignKey,
10
+ Secret,
11
11
  Varchar,
12
12
  )
13
13
  from piccolo.table import TABLENAME_WARNING, Table
@@ -99,14 +99,18 @@ class TestMetaClass(TestCase):
99
99
 
100
100
  def test_secret_columns(self):
101
101
  """
102
- Make sure TableMeta.secret_columns are setup correctly.
102
+ Make sure TableMeta.secret_columns are setup correctly with the
103
+ ``secret=True`` argument and ``Secret`` column type.
103
104
  """
104
105
 
105
106
  class Classified(Table):
106
107
  top_secret = Secret()
108
+ confidential = Varchar(secret=True)
109
+ public = Varchar()
107
110
 
108
111
  self.assertEqual(
109
- Classified._meta.secret_columns, [Classified.top_secret]
112
+ Classified._meta.secret_columns,
113
+ [Classified.top_secret, Classified.confidential],
110
114
  )
111
115
 
112
116
  def test_json_columns(self):
@@ -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
 
tests/table/test_str.py CHANGED
@@ -1,30 +1,20 @@
1
1
  from unittest import TestCase
2
2
 
3
- from tests.base import engine_is
4
- from tests.example_apps.music.tables import Manager
3
+ from piccolo.apps.playground.commands.run import Genre, Manager
5
4
 
6
5
 
7
6
  class TestTableStr(TestCase):
8
- def test_str(self):
9
- if engine_is("cockroach"):
10
- self.assertEqual(
11
- Manager._table_str(),
12
- (
13
- "class Manager(Table, tablename='manager'):\n"
14
- " id = Serial(null=False, primary_key=True, unique=False, index=False, index_method=IndexMethod.btree, choices=None, db_column_name='id', secret=False)\n" # noqa: E501
15
- " name = Varchar(length=50, default='', null=False, primary_key=False, unique=False, index=False, index_method=IndexMethod.btree, choices=None, db_column_name=None, secret=False)\n" # noqa: E501
16
- ),
17
- )
18
- else:
19
- self.assertEqual(
20
- Manager._table_str(),
21
- (
22
- "class Manager(Table, tablename='manager'):\n"
23
- " id = Serial(null=False, primary_key=True, unique=False, index=False, index_method=IndexMethod.btree, choices=None, db_column_name='id', secret=False)\n" # noqa: E501
24
- " name = Varchar(length=50, default='', null=False, primary_key=False, unique=False, index=False, index_method=IndexMethod.btree, choices=None, db_column_name=None, secret=False)\n" # noqa: E501
25
- ),
26
- )
7
+ def test_all_attributes(self):
8
+ self.assertEqual(
9
+ Manager._table_str(),
10
+ (
11
+ "class Manager(Table, tablename='manager'):\n"
12
+ " id = Serial(null=False, primary_key=True, unique=False, index=False, index_method=IndexMethod.btree, choices=None, db_column_name='id', secret=False)\n" # noqa: E501
13
+ " name = Varchar(length=50, default='', null=False, primary_key=False, unique=False, index=False, index_method=IndexMethod.btree, choices=None, db_column_name=None, secret=False)\n" # noqa: E501
14
+ ),
15
+ )
27
16
 
17
+ def test_abbreviated(self):
28
18
  self.assertEqual(
29
19
  Manager._table_str(abbreviated=True),
30
20
  (
@@ -34,5 +24,23 @@ class TestTableStr(TestCase):
34
24
  ),
35
25
  )
36
26
 
37
- # We should also be able to print it directly.
27
+ def test_m2m(self):
28
+ """
29
+ Make sure M2M relationships appear in the Table string.
30
+ """
31
+
32
+ self.assertEqual(
33
+ Genre._table_str(abbreviated=True),
34
+ (
35
+ "class Genre(Table):\n"
36
+ " id = Serial()\n"
37
+ " name = Varchar()\n"
38
+ " bands = M2M(GenreToBand)\n"
39
+ ),
40
+ )
41
+
42
+ def test_print(self):
43
+ """
44
+ Make sure we can print it directly without any errors.
45
+ """
38
46
  print(Manager)
@@ -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
@@ -158,6 +158,7 @@ class TestUpdate(DBTestCase):
158
158
 
159
159
  class MyTable(Table):
160
160
  integer = Integer(null=True)
161
+ other_integer = Integer(null=True, default=5)
161
162
  timestamp = Timestamp(null=True)
162
163
  timestamptz = Timestamptz(null=True)
163
164
  date = Date(null=True)
@@ -181,9 +182,9 @@ DATE_DELTA = datetime.timedelta(days=1)
181
182
  class OperatorTestCase:
182
183
  description: str
183
184
  column: Column
184
- initial: t.Any
185
+ initial: Any
185
186
  querystring: QueryString
186
- expected: t.Any
187
+ expected: Any
187
188
 
188
189
 
189
190
  TEST_CASES = [
@@ -295,6 +296,20 @@ TEST_CASES = [
295
296
  querystring=2000 - MyTable.integer,
296
297
  expected=1000,
297
298
  ),
299
+ OperatorTestCase(
300
+ description="Subtract Integer Columns",
301
+ column=MyTable.integer,
302
+ initial=1000,
303
+ querystring=MyTable.integer - MyTable.other_integer,
304
+ expected=995,
305
+ ),
306
+ OperatorTestCase(
307
+ description="Add Integer Columns",
308
+ column=MyTable.integer,
309
+ initial=1000,
310
+ querystring=MyTable.integer + MyTable.other_integer,
311
+ expected=1005,
312
+ ),
298
313
  OperatorTestCase(
299
314
  description="Multiply Integer",
300
315
  column=MyTable.integer,
@@ -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,3 +1,4 @@
1
+ import decimal
1
2
  import unittest
2
3
  from enum import Enum
3
4
 
@@ -35,6 +36,10 @@ class TestRandomBuilder(unittest.TestCase):
35
36
  random_float = RandomBuilder.next_float(maximum=1000)
36
37
  self.assertLessEqual(random_float, 1000)
37
38
 
39
+ def test_next_decimal(self):
40
+ random_decimal = RandomBuilder.next_decimal(precision=4, scale=2)
41
+ self.assertLessEqual(random_decimal, decimal.Decimal("99.99"))
42
+
38
43
  def test_next_int(self):
39
44
  random_int = RandomBuilder.next_int()
40
45
  self.assertLessEqual(random_int, 2147483647)