piccolo 1.9.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 (47) 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/column_types.py +28 -4
  14. piccolo/columns/defaults/base.py +1 -1
  15. piccolo/columns/defaults/date.py +9 -1
  16. piccolo/columns/defaults/interval.py +1 -0
  17. piccolo/columns/defaults/time.py +9 -1
  18. piccolo/columns/defaults/timestamp.py +1 -0
  19. piccolo/columns/defaults/uuid.py +1 -1
  20. piccolo/columns/m2m.py +7 -7
  21. piccolo/columns/operators/comparison.py +4 -0
  22. piccolo/conf/apps.py +9 -4
  23. piccolo/engine/base.py +69 -20
  24. piccolo/engine/cockroach.py +2 -3
  25. piccolo/engine/postgres.py +33 -19
  26. piccolo/engine/sqlite.py +27 -22
  27. piccolo/query/methods/create_index.py +1 -1
  28. piccolo/query/methods/drop_index.py +1 -1
  29. piccolo/query/methods/objects.py +7 -7
  30. piccolo/query/methods/select.py +13 -7
  31. piccolo/query/mixins.py +3 -10
  32. piccolo/schema.py +18 -11
  33. piccolo/table.py +22 -21
  34. piccolo/utils/encoding.py +5 -3
  35. {piccolo-1.9.0.dist-info → piccolo-1.10.0.dist-info}/METADATA +1 -1
  36. {piccolo-1.9.0.dist-info → piccolo-1.10.0.dist-info}/RECORD +47 -47
  37. tests/apps/migrations/auto/integration/test_migrations.py +1 -1
  38. tests/columns/test_array.py +28 -0
  39. tests/conf/test_apps.py +1 -1
  40. tests/engine/test_nested_transaction.py +2 -0
  41. tests/engine/test_transaction.py +1 -2
  42. tests/table/test_indexes.py +4 -2
  43. tests/utils/test_pydantic.py +70 -29
  44. {piccolo-1.9.0.dist-info → piccolo-1.10.0.dist-info}/LICENSE +0 -0
  45. {piccolo-1.9.0.dist-info → piccolo-1.10.0.dist-info}/WHEEL +0 -0
  46. {piccolo-1.9.0.dist-info → piccolo-1.10.0.dist-info}/entry_points.txt +0 -0
  47. {piccolo-1.9.0.dist-info → piccolo-1.10.0.dist-info}/top_level.txt +0 -0
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):
@@ -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/schema.py CHANGED
@@ -10,7 +10,6 @@ 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
@@ -132,16 +131,19 @@ class ListTables:
132
131
  self.db = db
133
132
  self.schema_name = schema_name
134
133
 
135
- async def run(self):
136
- response = await self.db.run_querystring(
137
- QueryString(
138
- """
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
+ """
139
140
  SELECT table_name
140
141
  FROM information_schema.tables
141
142
  WHERE table_schema = {}
142
143
  """,
143
- self.schema_name,
144
- )
144
+ self.schema_name,
145
+ )
146
+ ),
145
147
  )
146
148
  return [i["table_name"] for i in response]
147
149
 
@@ -156,9 +158,14 @@ class ListSchemas:
156
158
  def __init__(self, db: Engine):
157
159
  self.db = db
158
160
 
159
- async def run(self):
160
- response = await self.db.run_querystring(
161
- 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
+ ),
162
169
  )
163
170
  return [i["schema_name"] for i in response]
164
171
 
@@ -180,7 +187,7 @@ class SchemaManager:
180
187
  """
181
188
  db = db or engine_finder()
182
189
 
183
- if not db:
190
+ if db is None:
184
191
  raise ValueError("The DB can't be found.")
185
192
 
186
193
  self.db = db
piccolo/table.py CHANGED
@@ -143,8 +143,11 @@ class TableMeta:
143
143
  def db(self, value: Engine):
144
144
  self._db = value
145
145
 
146
- def refresh_db(self):
147
- self.db = engine_finder()
146
+ def refresh_db(self) -> None:
147
+ engine = engine_finder()
148
+ if engine is None:
149
+ raise ValueError("The engine can't be found")
150
+ self.db = engine
148
151
 
149
152
  def get_column_by_name(self, name: str) -> Column:
150
153
  """
@@ -184,8 +187,8 @@ class TableMeta:
184
187
 
185
188
 
186
189
  class TableMetaclass(type):
187
- def __str__(cls):
188
- return cls._table_str()
190
+ def __str__(cls) -> str:
191
+ return cls._table_str() # type: ignore
189
192
 
190
193
  def __repr__(cls):
191
194
  """
@@ -822,7 +825,7 @@ class Table(metaclass=TableMetaclass):
822
825
  @classmethod
823
826
  def all_related(
824
827
  cls, exclude: t.Optional[t.List[t.Union[str, ForeignKey]]] = None
825
- ) -> t.List[Column]:
828
+ ) -> t.List[ForeignKey]:
826
829
  """
827
830
  Used in conjunction with ``objects`` queries. Just as we can use
828
831
  ``all_related`` on a ``ForeignKey``, you can also use it for the table
@@ -1251,7 +1254,7 @@ class Table(metaclass=TableMetaclass):
1251
1254
  @classmethod
1252
1255
  def create_index(
1253
1256
  cls,
1254
- columns: t.List[t.Union[Column, str]],
1257
+ columns: t.Union[t.List[Column], t.List[str]],
1255
1258
  method: IndexMethod = IndexMethod.btree,
1256
1259
  if_not_exists: bool = False,
1257
1260
  ) -> CreateIndex:
@@ -1273,7 +1276,9 @@ class Table(metaclass=TableMetaclass):
1273
1276
 
1274
1277
  @classmethod
1275
1278
  def drop_index(
1276
- cls, columns: t.List[t.Union[Column, str]], if_exists: bool = True
1279
+ cls,
1280
+ columns: t.Union[t.List[Column], t.List[str]],
1281
+ if_exists: bool = True,
1277
1282
  ) -> DropIndex:
1278
1283
  """
1279
1284
  Drop a table index. If multiple columns are specified, this refers
@@ -1464,22 +1469,18 @@ async def drop_db_tables(*tables: t.Type[Table]) -> None:
1464
1469
  # SQLite doesn't support CASCADE, so we have to drop them in the
1465
1470
  # correct order.
1466
1471
  sorted_table_classes = reversed(sort_table_classes(list(tables)))
1467
- atomic = engine.atomic()
1468
- atomic.add(
1469
- *[
1470
- Alter(table=table).drop_table(if_exists=True)
1471
- for table in sorted_table_classes
1472
- ]
1473
- )
1472
+ ddl_statements = [
1473
+ Alter(table=table).drop_table(if_exists=True)
1474
+ for table in sorted_table_classes
1475
+ ]
1474
1476
  else:
1475
- atomic = engine.atomic()
1476
- atomic.add(
1477
- *[
1478
- table.alter().drop_table(cascade=True, if_exists=True)
1479
- for table in tables
1480
- ]
1481
- )
1477
+ ddl_statements = [
1478
+ table.alter().drop_table(cascade=True, if_exists=True)
1479
+ for table in tables
1480
+ ]
1482
1481
 
1482
+ atomic = engine.atomic()
1483
+ atomic.add(*ddl_statements)
1483
1484
  await atomic.run()
1484
1485
 
1485
1486
 
piccolo/utils/encoding.py CHANGED
@@ -19,13 +19,15 @@ def dump_json(data: t.Any, pretty: bool = False) -> str:
19
19
  orjson_params["option"] = (
20
20
  orjson.OPT_INDENT_2 | orjson.OPT_APPEND_NEWLINE # type: ignore
21
21
  )
22
- return orjson.dumps(data, **orjson_params).decode("utf8")
22
+ return orjson.dumps(data, **orjson_params).decode( # type: ignore
23
+ "utf8"
24
+ )
23
25
  else:
24
26
  params: t.Dict[str, t.Any] = {"default": str}
25
27
  if pretty:
26
28
  params["indent"] = 2
27
- return json.dumps(data, **params)
29
+ return json.dumps(data, **params) # type: ignore
28
30
 
29
31
 
30
32
  def load_json(data: str) -> t.Any:
31
- return orjson.loads(data) if ORJSON else json.loads(data)
33
+ return orjson.loads(data) if ORJSON else json.loads(data) # type: ignore
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: piccolo
3
- Version: 1.9.0
3
+ Version: 1.10.0
4
4
  Summary: A fast, user friendly ORM and query builder which supports asyncio.
5
5
  Home-page: https://github.com/piccolo-orm/piccolo
6
6
  Author: Daniel Townsend