piccolo 1.8.0__py3-none-any.whl → 1.10.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 (60) hide show
  1. piccolo/__init__.py +1 -1
  2. piccolo/apps/fixtures/commands/load.py +1 -1
  3. piccolo/apps/migrations/auto/__init__.py +8 -0
  4. piccolo/apps/migrations/auto/migration_manager.py +2 -1
  5. piccolo/apps/migrations/commands/backwards.py +3 -1
  6. piccolo/apps/migrations/commands/base.py +1 -1
  7. piccolo/apps/migrations/commands/check.py +1 -1
  8. piccolo/apps/migrations/commands/clean.py +1 -1
  9. piccolo/apps/migrations/commands/forwards.py +3 -1
  10. piccolo/apps/migrations/commands/new.py +4 -2
  11. piccolo/apps/schema/commands/generate.py +2 -2
  12. piccolo/apps/shell/commands/run.py +1 -1
  13. piccolo/columns/base.py +55 -29
  14. piccolo/columns/column_types.py +28 -4
  15. piccolo/columns/defaults/base.py +6 -4
  16. piccolo/columns/defaults/date.py +9 -1
  17. piccolo/columns/defaults/interval.py +1 -0
  18. piccolo/columns/defaults/time.py +9 -1
  19. piccolo/columns/defaults/timestamp.py +1 -0
  20. piccolo/columns/defaults/uuid.py +1 -1
  21. piccolo/columns/m2m.py +7 -7
  22. piccolo/columns/operators/comparison.py +4 -0
  23. piccolo/conf/apps.py +9 -4
  24. piccolo/engine/base.py +69 -20
  25. piccolo/engine/cockroach.py +2 -3
  26. piccolo/engine/postgres.py +33 -19
  27. piccolo/engine/sqlite.py +27 -22
  28. piccolo/query/functions/__init__.py +5 -0
  29. piccolo/query/functions/math.py +48 -0
  30. piccolo/query/methods/create_index.py +1 -1
  31. piccolo/query/methods/drop_index.py +1 -1
  32. piccolo/query/methods/objects.py +7 -7
  33. piccolo/query/methods/select.py +13 -7
  34. piccolo/query/mixins.py +3 -10
  35. piccolo/querystring.py +18 -0
  36. piccolo/schema.py +20 -12
  37. piccolo/table.py +22 -21
  38. piccolo/utils/encoding.py +5 -3
  39. {piccolo-1.8.0.dist-info → piccolo-1.10.0.dist-info}/METADATA +1 -1
  40. {piccolo-1.8.0.dist-info → piccolo-1.10.0.dist-info}/RECORD +59 -52
  41. tests/apps/migrations/auto/integration/test_migrations.py +1 -1
  42. tests/columns/test_array.py +91 -19
  43. tests/columns/test_get_sql_value.py +66 -0
  44. tests/conf/test_apps.py +1 -1
  45. tests/engine/test_nested_transaction.py +2 -0
  46. tests/engine/test_transaction.py +1 -2
  47. tests/query/functions/__init__.py +0 -0
  48. tests/query/functions/base.py +34 -0
  49. tests/query/functions/test_functions.py +64 -0
  50. tests/query/functions/test_math.py +39 -0
  51. tests/query/functions/test_string.py +25 -0
  52. tests/query/functions/test_type_conversion.py +134 -0
  53. tests/query/test_querystring.py +136 -0
  54. tests/table/test_indexes.py +4 -2
  55. tests/utils/test_pydantic.py +70 -29
  56. tests/query/test_functions.py +0 -238
  57. {piccolo-1.8.0.dist-info → piccolo-1.10.0.dist-info}/LICENSE +0 -0
  58. {piccolo-1.8.0.dist-info → piccolo-1.10.0.dist-info}/WHEEL +0 -0
  59. {piccolo-1.8.0.dist-info → piccolo-1.10.0.dist-info}/entry_points.txt +0 -0
  60. {piccolo-1.8.0.dist-info → piccolo-1.10.0.dist-info}/top_level.txt +0 -0
@@ -4,7 +4,15 @@ import contextvars
4
4
  import typing as t
5
5
  from dataclasses import dataclass
6
6
 
7
- from piccolo.engine.base import Batch, Engine, validate_savepoint_name
7
+ from typing_extensions import Self
8
+
9
+ from piccolo.engine.base import (
10
+ BaseAtomic,
11
+ BaseBatch,
12
+ BaseTransaction,
13
+ Engine,
14
+ validate_savepoint_name,
15
+ )
8
16
  from piccolo.engine.exceptions import TransactionError
9
17
  from piccolo.query.base import DDL, Query
10
18
  from piccolo.querystring import QueryString
@@ -18,16 +26,17 @@ if t.TYPE_CHECKING: # pragma: no cover
18
26
  from asyncpg.connection import Connection
19
27
  from asyncpg.cursor import Cursor
20
28
  from asyncpg.pool import Pool
29
+ from asyncpg.transaction import Transaction
21
30
 
22
31
 
23
32
  @dataclass
24
- class AsyncBatch(Batch):
33
+ class AsyncBatch(BaseBatch):
25
34
  connection: Connection
26
35
  query: Query
27
36
  batch_size: int
28
37
 
29
38
  # Set internally
30
- _transaction = None
39
+ _transaction: t.Optional[Transaction] = None
31
40
  _cursor: t.Optional[Cursor] = None
32
41
 
33
42
  @property
@@ -36,20 +45,26 @@ class AsyncBatch(Batch):
36
45
  raise ValueError("_cursor not set")
37
46
  return self._cursor
38
47
 
48
+ @property
49
+ def transaction(self) -> Transaction:
50
+ if not self._transaction:
51
+ raise ValueError("The transaction can't be found.")
52
+ return self._transaction
53
+
39
54
  async def next(self) -> t.List[t.Dict]:
40
55
  data = await self.cursor.fetch(self.batch_size)
41
56
  return await self.query._process_results(data)
42
57
 
43
- def __aiter__(self):
58
+ def __aiter__(self: Self) -> Self:
44
59
  return self
45
60
 
46
- async def __anext__(self):
61
+ async def __anext__(self) -> t.List[t.Dict]:
47
62
  response = await self.next()
48
63
  if response == []:
49
64
  raise StopAsyncIteration()
50
65
  return response
51
66
 
52
- async def __aenter__(self):
67
+ async def __aenter__(self: Self) -> Self:
53
68
  self._transaction = self.connection.transaction()
54
69
  await self._transaction.start()
55
70
  querystring = self.query.querystrings[0]
@@ -60,9 +75,9 @@ class AsyncBatch(Batch):
60
75
 
61
76
  async def __aexit__(self, exception_type, exception, traceback):
62
77
  if exception:
63
- await self._transaction.rollback()
78
+ await self.transaction.rollback()
64
79
  else:
65
- await self._transaction.commit()
80
+ await self.transaction.commit()
66
81
 
67
82
  await self.connection.close()
68
83
 
@@ -72,7 +87,7 @@ class AsyncBatch(Batch):
72
87
  ###############################################################################
73
88
 
74
89
 
75
- class Atomic:
90
+ class Atomic(BaseAtomic):
76
91
  """
77
92
  This is useful if you want to build up a transaction programatically, by
78
93
  adding queries to it.
@@ -140,7 +155,7 @@ class Savepoint:
140
155
  )
141
156
 
142
157
 
143
- class PostgresTransaction:
158
+ class PostgresTransaction(BaseTransaction):
144
159
  """
145
160
  Used for wrapping queries in a transaction, using a context manager.
146
161
  Currently it's async only.
@@ -243,7 +258,7 @@ class PostgresTransaction:
243
258
 
244
259
  ###########################################################################
245
260
 
246
- async def __aexit__(self, exception_type, exception, traceback):
261
+ async def __aexit__(self, exception_type, exception, traceback) -> bool:
247
262
  if self._parent:
248
263
  return exception is None
249
264
 
@@ -269,7 +284,7 @@ class PostgresTransaction:
269
284
  ###############################################################################
270
285
 
271
286
 
272
- class PostgresEngine(Engine[t.Optional[PostgresTransaction]]):
287
+ class PostgresEngine(Engine[PostgresTransaction]):
273
288
  """
274
289
  Used to connect to PostgreSQL.
275
290
 
@@ -331,16 +346,10 @@ class PostgresEngine(Engine[t.Optional[PostgresTransaction]]):
331
346
  __slots__ = (
332
347
  "config",
333
348
  "extensions",
334
- "log_queries",
335
- "log_responses",
336
349
  "extra_nodes",
337
350
  "pool",
338
- "current_transaction",
339
351
  )
340
352
 
341
- engine_type = "postgres"
342
- min_version_number = 10
343
-
344
353
  def __init__(
345
354
  self,
346
355
  config: t.Dict[str, t.Any],
@@ -362,7 +371,12 @@ class PostgresEngine(Engine[t.Optional[PostgresTransaction]]):
362
371
  self.current_transaction = contextvars.ContextVar(
363
372
  f"pg_current_transaction_{database_name}", default=None
364
373
  )
365
- super().__init__()
374
+ super().__init__(
375
+ engine_type="postgres",
376
+ log_queries=log_queries,
377
+ log_responses=log_responses,
378
+ min_version_number=10,
379
+ )
366
380
 
367
381
  @staticmethod
368
382
  def _parse_raw_version_string(version_string: str) -> float:
piccolo/engine/sqlite.py CHANGED
@@ -11,7 +11,15 @@ from dataclasses import dataclass
11
11
  from decimal import Decimal
12
12
  from functools import partial, wraps
13
13
 
14
- from piccolo.engine.base import Batch, Engine, validate_savepoint_name
14
+ from typing_extensions import Self
15
+
16
+ from piccolo.engine.base import (
17
+ BaseAtomic,
18
+ BaseBatch,
19
+ BaseTransaction,
20
+ Engine,
21
+ validate_savepoint_name,
22
+ )
15
23
  from piccolo.engine.exceptions import TransactionError
16
24
  from piccolo.query.base import DDL, Query
17
25
  from piccolo.querystring import QueryString
@@ -309,7 +317,7 @@ for column_name in ("TIMESTAMP", "TIMESTAMPTZ", "DATE", "TIME"):
309
317
 
310
318
 
311
319
  @dataclass
312
- class AsyncBatch(Batch):
320
+ class AsyncBatch(BaseBatch):
313
321
  connection: Connection
314
322
  query: Query
315
323
  batch_size: int
@@ -327,16 +335,16 @@ class AsyncBatch(Batch):
327
335
  data = await self.cursor.fetchmany(self.batch_size)
328
336
  return await self.query._process_results(data)
329
337
 
330
- def __aiter__(self):
338
+ def __aiter__(self: Self) -> Self:
331
339
  return self
332
340
 
333
- async def __anext__(self):
341
+ async def __anext__(self) -> t.List[t.Dict]:
334
342
  response = await self.next()
335
343
  if response == []:
336
344
  raise StopAsyncIteration()
337
345
  return response
338
346
 
339
- async def __aenter__(self):
347
+ async def __aenter__(self: Self) -> Self:
340
348
  querystring = self.query.querystrings[0]
341
349
  template, template_args = querystring.compile_string()
342
350
 
@@ -344,7 +352,7 @@ class AsyncBatch(Batch):
344
352
  return self
345
353
 
346
354
  async def __aexit__(self, exception_type, exception, traceback):
347
- await self._cursor.close()
355
+ await self.cursor.close()
348
356
  await self.connection.close()
349
357
  return exception is not None
350
358
 
@@ -363,7 +371,7 @@ class TransactionType(enum.Enum):
363
371
  exclusive = "EXCLUSIVE"
364
372
 
365
373
 
366
- class Atomic:
374
+ class Atomic(BaseAtomic):
367
375
  """
368
376
  Usage:
369
377
 
@@ -384,9 +392,9 @@ class Atomic:
384
392
  ):
385
393
  self.engine = engine
386
394
  self.transaction_type = transaction_type
387
- self.queries: t.List[Query] = []
395
+ self.queries: t.List[t.Union[Query, DDL]] = []
388
396
 
389
- def add(self, *query: Query):
397
+ def add(self, *query: t.Union[Query, DDL]):
390
398
  self.queries += list(query)
391
399
 
392
400
  async def run(self):
@@ -434,7 +442,7 @@ class Savepoint:
434
442
  )
435
443
 
436
444
 
437
- class SQLiteTransaction:
445
+ class SQLiteTransaction(BaseTransaction):
438
446
  """
439
447
  Used for wrapping queries in a transaction, using a context manager.
440
448
  Currently it's async only.
@@ -534,7 +542,7 @@ class SQLiteTransaction:
534
542
 
535
543
  ###########################################################################
536
544
 
537
- async def __aexit__(self, exception_type, exception, traceback):
545
+ async def __aexit__(self, exception_type, exception, traceback) -> bool:
538
546
  if self._parent:
539
547
  return exception is None
540
548
 
@@ -560,16 +568,8 @@ def dict_factory(cursor, row) -> t.Dict:
560
568
  return {col[0]: row[idx] for idx, col in enumerate(cursor.description)}
561
569
 
562
570
 
563
- class SQLiteEngine(Engine[t.Optional[SQLiteTransaction]]):
564
- __slots__ = (
565
- "connection_kwargs",
566
- "current_transaction",
567
- "log_queries",
568
- "log_responses",
569
- )
570
-
571
- engine_type = "sqlite"
572
- min_version_number = 3.25
571
+ class SQLiteEngine(Engine[SQLiteTransaction]):
572
+ __slots__ = ("connection_kwargs",)
573
573
 
574
574
  def __init__(
575
575
  self,
@@ -613,7 +613,12 @@ class SQLiteEngine(Engine[t.Optional[SQLiteTransaction]]):
613
613
  f"sqlite_current_transaction_{path}", default=None
614
614
  )
615
615
 
616
- super().__init__()
616
+ super().__init__(
617
+ engine_type="sqlite",
618
+ min_version_number=3.25,
619
+ log_queries=log_queries,
620
+ log_responses=log_responses,
621
+ )
617
622
 
618
623
  @property
619
624
  def path(self):
@@ -1,17 +1,22 @@
1
1
  from .aggregate import Avg, Count, Max, Min, Sum
2
+ from .math import Abs, Ceil, Floor, Round
2
3
  from .string import Length, Lower, Ltrim, Reverse, Rtrim, Upper
3
4
  from .type_conversion import Cast
4
5
 
5
6
  __all__ = (
7
+ "Abs",
6
8
  "Avg",
7
9
  "Cast",
10
+ "Ceil",
8
11
  "Count",
12
+ "Floor",
9
13
  "Length",
10
14
  "Lower",
11
15
  "Ltrim",
12
16
  "Max",
13
17
  "Min",
14
18
  "Reverse",
19
+ "Round",
15
20
  "Rtrim",
16
21
  "Sum",
17
22
  "Upper",
@@ -0,0 +1,48 @@
1
+ """
2
+ These functions mirror their counterparts in the Postgresql docs:
3
+
4
+ https://www.postgresql.org/docs/current/functions-math.html
5
+
6
+ """
7
+
8
+ from .base import Function
9
+
10
+
11
+ class Abs(Function):
12
+ """
13
+ Absolute value.
14
+ """
15
+
16
+ function_name = "ABS"
17
+
18
+
19
+ class Ceil(Function):
20
+ """
21
+ Nearest integer greater than or equal to argument.
22
+ """
23
+
24
+ function_name = "CEIL"
25
+
26
+
27
+ class Floor(Function):
28
+ """
29
+ Nearest integer less than or equal to argument.
30
+ """
31
+
32
+ function_name = "FLOOR"
33
+
34
+
35
+ class Round(Function):
36
+ """
37
+ Rounds to nearest integer.
38
+ """
39
+
40
+ function_name = "ROUND"
41
+
42
+
43
+ __all__ = (
44
+ "Abs",
45
+ "Ceil",
46
+ "Floor",
47
+ "Round",
48
+ )
@@ -14,7 +14,7 @@ class CreateIndex(DDL):
14
14
  def __init__(
15
15
  self,
16
16
  table: t.Type[Table],
17
- columns: t.List[t.Union[Column, str]],
17
+ columns: t.Union[t.List[Column], t.List[str]],
18
18
  method: IndexMethod = IndexMethod.btree,
19
19
  if_not_exists: bool = False,
20
20
  **kwargs,
@@ -14,7 +14,7 @@ class DropIndex(Query):
14
14
  def __init__(
15
15
  self,
16
16
  table: t.Type[Table],
17
- columns: t.List[t.Union[Column, str]],
17
+ columns: t.Union[t.List[Column], t.List[str]],
18
18
  if_exists: bool = True,
19
19
  **kwargs,
20
20
  ):
@@ -5,7 +5,7 @@ import typing as t
5
5
  from piccolo.columns.column_types import ForeignKey
6
6
  from piccolo.columns.combination import And, Where
7
7
  from piccolo.custom_types import Combinable, TableInstance
8
- from piccolo.engine.base import Batch
8
+ from piccolo.engine.base import BaseBatch
9
9
  from piccolo.query.base import Query
10
10
  from piccolo.query.methods.select import Select
11
11
  from piccolo.query.mixins import (
@@ -268,17 +268,17 @@ class Objects(
268
268
 
269
269
  ###########################################################################
270
270
 
271
- def first(self: Self) -> First[TableInstance]:
271
+ def first(self) -> First[TableInstance]:
272
272
  self.limit_delegate.limit(1)
273
273
  return First[TableInstance](query=self)
274
274
 
275
- def get(self: Self, where: Combinable) -> Get[TableInstance]:
275
+ def get(self, where: Combinable) -> Get[TableInstance]:
276
276
  self.where_delegate.where(where)
277
277
  self.limit_delegate.limit(1)
278
278
  return Get[TableInstance](query=First[TableInstance](query=self))
279
279
 
280
280
  def get_or_create(
281
- self: Self,
281
+ self,
282
282
  where: Combinable,
283
283
  defaults: t.Optional[t.Dict[Column, t.Any]] = None,
284
284
  ) -> GetOrCreate[TableInstance]:
@@ -288,17 +288,17 @@ class Objects(
288
288
  query=self, table_class=self.table, where=where, defaults=defaults
289
289
  )
290
290
 
291
- def create(self: Self, **columns: t.Any) -> Create[TableInstance]:
291
+ def create(self, **columns: t.Any) -> Create[TableInstance]:
292
292
  return Create[TableInstance](table_class=self.table, columns=columns)
293
293
 
294
294
  ###########################################################################
295
295
 
296
296
  async def batch(
297
- self: Self,
297
+ self,
298
298
  batch_size: t.Optional[int] = None,
299
299
  node: t.Optional[str] = None,
300
300
  **kwargs,
301
- ) -> Batch:
301
+ ) -> BaseBatch:
302
302
  if batch_size:
303
303
  kwargs.update(batch_size=batch_size)
304
304
  if node:
@@ -5,11 +5,11 @@ import typing as t
5
5
  from collections import OrderedDict
6
6
 
7
7
  from piccolo.columns import Column, Selectable
8
- from piccolo.columns.column_types import JSON, JSONB, PrimaryKey
8
+ from piccolo.columns.column_types import JSON, JSONB
9
9
  from piccolo.columns.m2m import M2MSelect
10
10
  from piccolo.columns.readable import Readable
11
11
  from piccolo.custom_types import TableInstance
12
- from piccolo.engine.base import Batch
12
+ from piccolo.engine.base import BaseBatch
13
13
  from piccolo.query.base import Query
14
14
  from piccolo.query.mixins import (
15
15
  AsOfDelegate,
@@ -217,7 +217,7 @@ class Select(Query[TableInstance, t.List[t.Dict[str, t.Any]]]):
217
217
  self,
218
218
  response: t.List[t.Dict[str, t.Any]],
219
219
  secondary_table: t.Type[Table],
220
- secondary_table_pk: PrimaryKey,
220
+ secondary_table_pk: Column,
221
221
  m2m_name: str,
222
222
  m2m_select: M2MSelect,
223
223
  as_list: bool = False,
@@ -386,14 +386,20 @@ class Select(Query[TableInstance, t.List[t.Dict[str, t.Any]]]):
386
386
  return self
387
387
 
388
388
  @t.overload
389
- def output(self: Self, *, as_list: bool) -> SelectList: ...
389
+ def output(self: Self, *, as_list: bool) -> SelectList: # type: ignore
390
+ ...
390
391
 
391
392
  @t.overload
392
- def output(self: Self, *, as_json: bool) -> SelectJSON: ...
393
+ def output(self: Self, *, as_json: bool) -> SelectJSON: # type: ignore
394
+ ...
393
395
 
394
396
  @t.overload
395
397
  def output(self: Self, *, load_json: bool) -> Self: ...
396
398
 
399
+ @t.overload
400
+ def output(self: Self, *, load_json: bool, as_list: bool) -> SelectJSON: # type: ignore # noqa: E501
401
+ ...
402
+
397
403
  @t.overload
398
404
  def output(self: Self, *, nested: bool) -> Self: ...
399
405
 
@@ -404,7 +410,7 @@ class Select(Query[TableInstance, t.List[t.Dict[str, t.Any]]]):
404
410
  as_json: bool = False,
405
411
  load_json: bool = False,
406
412
  nested: bool = False,
407
- ):
413
+ ) -> t.Union[Self, SelectJSON, SelectList]:
408
414
  self.output_delegate.output(
409
415
  as_list=as_list,
410
416
  as_json=as_json,
@@ -436,7 +442,7 @@ class Select(Query[TableInstance, t.List[t.Dict[str, t.Any]]]):
436
442
  batch_size: t.Optional[int] = None,
437
443
  node: t.Optional[str] = None,
438
444
  **kwargs,
439
- ) -> Batch:
445
+ ) -> BaseBatch:
440
446
  if batch_size:
441
447
  kwargs.update(batch_size=batch_size)
442
448
  if node:
piccolo/query/mixins.py CHANGED
@@ -207,7 +207,6 @@ class Returning:
207
207
 
208
208
  @dataclass
209
209
  class Output:
210
-
211
210
  as_json: bool = False
212
211
  as_list: bool = False
213
212
  as_objects: bool = False
@@ -236,7 +235,6 @@ class Callback:
236
235
 
237
236
  @dataclass
238
237
  class WhereDelegate:
239
-
240
238
  _where: t.Optional[Combinable] = None
241
239
  _where_columns: t.List[Column] = field(default_factory=list)
242
240
 
@@ -246,7 +244,8 @@ class WhereDelegate:
246
244
  needed.
247
245
  """
248
246
  self._where_columns = []
249
- self._extract_columns(self._where)
247
+ if self._where is not None:
248
+ self._extract_columns(self._where)
250
249
  return self._where_columns
251
250
 
252
251
  def _extract_columns(self, combinable: Combinable):
@@ -277,7 +276,6 @@ class WhereDelegate:
277
276
 
278
277
  @dataclass
279
278
  class OrderByDelegate:
280
-
281
279
  _order_by: OrderBy = field(default_factory=OrderBy)
282
280
 
283
281
  def get_order_by_columns(self) -> t.List[Column]:
@@ -303,7 +301,6 @@ class OrderByDelegate:
303
301
 
304
302
  @dataclass
305
303
  class LimitDelegate:
306
-
307
304
  _limit: t.Optional[Limit] = None
308
305
  _first: bool = False
309
306
 
@@ -330,7 +327,6 @@ class AsOfDelegate:
330
327
 
331
328
  @dataclass
332
329
  class DistinctDelegate:
333
-
334
330
  _distinct: Distinct = field(
335
331
  default_factory=lambda: Distinct(enabled=False, on=None)
336
332
  )
@@ -356,7 +352,6 @@ class ReturningDelegate:
356
352
 
357
353
  @dataclass
358
354
  class CountDelegate:
359
-
360
355
  _count: bool = False
361
356
 
362
357
  def count(self):
@@ -365,7 +360,6 @@ class CountDelegate:
365
360
 
366
361
  @dataclass
367
362
  class AddDelegate:
368
-
369
363
  _add: t.List[Table] = field(default_factory=list)
370
364
 
371
365
  def add(self, *instances: Table, table_class: t.Type[Table]):
@@ -421,8 +415,7 @@ class OutputDelegate:
421
415
  self._output.nested = bool(nested)
422
416
 
423
417
  def copy(self) -> OutputDelegate:
424
- _output = self._output.copy() if self._output is not None else None
425
- return self.__class__(_output=_output)
418
+ return self.__class__(_output=self._output.copy())
426
419
 
427
420
 
428
421
  @dataclass
piccolo/querystring.py CHANGED
@@ -282,12 +282,30 @@ class QueryString(Selectable):
282
282
  def __le__(self, value) -> QueryString:
283
283
  return QueryString("{} <= {}", self, value)
284
284
 
285
+ def __truediv__(self, value) -> QueryString:
286
+ return QueryString("{} / {}", self, value)
287
+
288
+ def __mul__(self, value) -> QueryString:
289
+ return QueryString("{} * {}", self, value)
290
+
291
+ def __pow__(self, value) -> QueryString:
292
+ return QueryString("{} ^ {}", self, value)
293
+
294
+ def __mod__(self, value) -> QueryString:
295
+ return QueryString("{} % {}", self, value)
296
+
285
297
  def is_in(self, value) -> QueryString:
286
298
  return QueryString("{} IN {}", self, value)
287
299
 
288
300
  def not_in(self, value) -> QueryString:
289
301
  return QueryString("{} NOT IN {}", self, value)
290
302
 
303
+ def like(self, value: str) -> QueryString:
304
+ return QueryString("{} LIKE {}", self, value)
305
+
306
+ def ilike(self, value: str) -> QueryString:
307
+ return QueryString("{} ILIKE {}", self, value)
308
+
291
309
 
292
310
  class Unquoted(QueryString):
293
311
  """
piccolo/schema.py CHANGED
@@ -10,10 +10,10 @@ from piccolo.utils.sync import run_sync
10
10
 
11
11
 
12
12
  class SchemaDDLBase(abc.ABC):
13
-
14
13
  db: Engine
15
14
 
16
- @abc.abstractproperty
15
+ @property
16
+ @abc.abstractmethod
17
17
  def ddl(self) -> str:
18
18
  pass
19
19
 
@@ -131,16 +131,19 @@ class ListTables:
131
131
  self.db = db
132
132
  self.schema_name = schema_name
133
133
 
134
- async def run(self):
135
- response = await self.db.run_querystring(
136
- QueryString(
137
- """
134
+ async def run(self) -> t.List[str]:
135
+ response = t.cast(
136
+ t.List[t.Dict],
137
+ await self.db.run_querystring(
138
+ QueryString(
139
+ """
138
140
  SELECT table_name
139
141
  FROM information_schema.tables
140
142
  WHERE table_schema = {}
141
143
  """,
142
- self.schema_name,
143
- )
144
+ self.schema_name,
145
+ )
146
+ ),
144
147
  )
145
148
  return [i["table_name"] for i in response]
146
149
 
@@ -155,9 +158,14 @@ class ListSchemas:
155
158
  def __init__(self, db: Engine):
156
159
  self.db = db
157
160
 
158
- async def run(self):
159
- response = await self.db.run_querystring(
160
- QueryString("SELECT schema_name FROM information_schema.schemata")
161
+ async def run(self) -> t.List[str]:
162
+ response = t.cast(
163
+ t.List[t.Dict],
164
+ await self.db.run_querystring(
165
+ QueryString(
166
+ "SELECT schema_name FROM information_schema.schemata"
167
+ )
168
+ ),
161
169
  )
162
170
  return [i["schema_name"] for i in response]
163
171
 
@@ -179,7 +187,7 @@ class SchemaManager:
179
187
  """
180
188
  db = db or engine_finder()
181
189
 
182
- if not db:
190
+ if db is None:
183
191
  raise ValueError("The DB can't be found.")
184
192
 
185
193
  self.db = db