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.
- piccolo/__init__.py +1 -1
- piccolo/apps/fixtures/commands/load.py +1 -1
- piccolo/apps/migrations/auto/__init__.py +8 -0
- piccolo/apps/migrations/auto/migration_manager.py +2 -1
- piccolo/apps/migrations/commands/backwards.py +3 -1
- piccolo/apps/migrations/commands/base.py +1 -1
- piccolo/apps/migrations/commands/check.py +1 -1
- piccolo/apps/migrations/commands/clean.py +1 -1
- piccolo/apps/migrations/commands/forwards.py +3 -1
- piccolo/apps/migrations/commands/new.py +4 -2
- piccolo/apps/schema/commands/generate.py +2 -2
- piccolo/apps/shell/commands/run.py +1 -1
- piccolo/columns/base.py +55 -29
- piccolo/columns/column_types.py +28 -4
- piccolo/columns/defaults/base.py +6 -4
- piccolo/columns/defaults/date.py +9 -1
- piccolo/columns/defaults/interval.py +1 -0
- piccolo/columns/defaults/time.py +9 -1
- piccolo/columns/defaults/timestamp.py +1 -0
- piccolo/columns/defaults/uuid.py +1 -1
- piccolo/columns/m2m.py +7 -7
- piccolo/columns/operators/comparison.py +4 -0
- piccolo/conf/apps.py +9 -4
- piccolo/engine/base.py +69 -20
- piccolo/engine/cockroach.py +2 -3
- piccolo/engine/postgres.py +33 -19
- piccolo/engine/sqlite.py +27 -22
- piccolo/query/functions/__init__.py +5 -0
- piccolo/query/functions/math.py +48 -0
- piccolo/query/methods/create_index.py +1 -1
- piccolo/query/methods/drop_index.py +1 -1
- piccolo/query/methods/objects.py +7 -7
- piccolo/query/methods/select.py +13 -7
- piccolo/query/mixins.py +3 -10
- piccolo/querystring.py +18 -0
- piccolo/schema.py +20 -12
- piccolo/table.py +22 -21
- piccolo/utils/encoding.py +5 -3
- {piccolo-1.8.0.dist-info → piccolo-1.10.0.dist-info}/METADATA +1 -1
- {piccolo-1.8.0.dist-info → piccolo-1.10.0.dist-info}/RECORD +59 -52
- tests/apps/migrations/auto/integration/test_migrations.py +1 -1
- tests/columns/test_array.py +91 -19
- tests/columns/test_get_sql_value.py +66 -0
- tests/conf/test_apps.py +1 -1
- tests/engine/test_nested_transaction.py +2 -0
- tests/engine/test_transaction.py +1 -2
- tests/query/functions/__init__.py +0 -0
- tests/query/functions/base.py +34 -0
- tests/query/functions/test_functions.py +64 -0
- tests/query/functions/test_math.py +39 -0
- tests/query/functions/test_string.py +25 -0
- tests/query/functions/test_type_conversion.py +134 -0
- tests/query/test_querystring.py +136 -0
- tests/table/test_indexes.py +4 -2
- tests/utils/test_pydantic.py +70 -29
- tests/query/test_functions.py +0 -238
- {piccolo-1.8.0.dist-info → piccolo-1.10.0.dist-info}/LICENSE +0 -0
- {piccolo-1.8.0.dist-info → piccolo-1.10.0.dist-info}/WHEEL +0 -0
- {piccolo-1.8.0.dist-info → piccolo-1.10.0.dist-info}/entry_points.txt +0 -0
- {piccolo-1.8.0.dist-info → piccolo-1.10.0.dist-info}/top_level.txt +0 -0
piccolo/engine/postgres.py
CHANGED
@@ -4,7 +4,15 @@ import contextvars
|
|
4
4
|
import typing as t
|
5
5
|
from dataclasses import dataclass
|
6
6
|
|
7
|
-
from
|
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(
|
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.
|
78
|
+
await self.transaction.rollback()
|
64
79
|
else:
|
65
|
-
await self.
|
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[
|
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
|
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(
|
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.
|
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[
|
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
|
+
)
|
piccolo/query/methods/objects.py
CHANGED
@@ -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
|
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
|
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
|
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
|
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
|
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
|
297
|
+
self,
|
298
298
|
batch_size: t.Optional[int] = None,
|
299
299
|
node: t.Optional[str] = None,
|
300
300
|
**kwargs,
|
301
|
-
) ->
|
301
|
+
) -> BaseBatch:
|
302
302
|
if batch_size:
|
303
303
|
kwargs.update(batch_size=batch_size)
|
304
304
|
if node:
|
piccolo/query/methods/select.py
CHANGED
@@ -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
|
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
|
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:
|
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
|
-
) ->
|
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.
|
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
|
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
|
-
@
|
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 =
|
136
|
-
|
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
|
-
|
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 =
|
160
|
-
|
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
|
190
|
+
if db is None:
|
183
191
|
raise ValueError("The DB can't be found.")
|
184
192
|
|
185
193
|
self.db = db
|