piccolo 1.17.1__py3-none-any.whl → 1.19.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 CHANGED
@@ -1 +1 @@
1
- __VERSION__ = "1.17.1"
1
+ __VERSION__ = "1.19.0"
@@ -13,6 +13,8 @@ from piccolo.query.mixins import (
13
13
  CallbackDelegate,
14
14
  CallbackType,
15
15
  LimitDelegate,
16
+ LockRowsDelegate,
17
+ LockStrength,
16
18
  OffsetDelegate,
17
19
  OrderByDelegate,
18
20
  OrderByRaw,
@@ -27,6 +29,7 @@ from piccolo.utils.sync import run_sync
27
29
 
28
30
  if t.TYPE_CHECKING: # pragma: no cover
29
31
  from piccolo.columns import Column
32
+ from piccolo.table import Table
30
33
 
31
34
 
32
35
  ###############################################################################
@@ -173,6 +176,61 @@ class Create(t.Generic[TableInstance]):
173
176
  return run_sync(self.run(*args, **kwargs))
174
177
 
175
178
 
179
+ class UpdateSelf:
180
+
181
+ def __init__(
182
+ self,
183
+ row: Table,
184
+ values: t.Dict[t.Union[Column, str], t.Any],
185
+ ):
186
+ self.row = row
187
+ self.values = values
188
+
189
+ async def run(
190
+ self,
191
+ node: t.Optional[str] = None,
192
+ in_pool: bool = True,
193
+ ) -> None:
194
+ if not self.row._exists_in_db:
195
+ raise ValueError("This row doesn't exist in the database.")
196
+
197
+ TableClass = self.row.__class__
198
+
199
+ primary_key = TableClass._meta.primary_key
200
+ primary_key_value = getattr(self.row, primary_key._meta.name)
201
+
202
+ if primary_key_value is None:
203
+ raise ValueError("The primary key is None")
204
+
205
+ columns = [
206
+ TableClass._meta.get_column_by_name(i) if isinstance(i, str) else i
207
+ for i in self.values.keys()
208
+ ]
209
+
210
+ response = (
211
+ await TableClass.update(self.values)
212
+ .where(primary_key == primary_key_value)
213
+ .returning(*columns)
214
+ .run(
215
+ node=node,
216
+ in_pool=in_pool,
217
+ )
218
+ )
219
+
220
+ for key, value in response[0].items():
221
+ setattr(self.row, key, value)
222
+
223
+ def __await__(self) -> t.Generator[None, None, None]:
224
+ """
225
+ If the user doesn't explicity call .run(), proxy to it as a
226
+ convenience.
227
+ """
228
+ return self.run().__await__()
229
+
230
+ def run_sync(self, *args, **kwargs) -> None:
231
+ return run_sync(self.run(*args, **kwargs))
232
+
233
+
176
234
  ###############################################################################
177
235
 
178
236
 
@@ -194,6 +252,7 @@ class Objects(
194
252
  "callback_delegate",
195
253
  "prefetch_delegate",
196
254
  "where_delegate",
255
+ "lock_rows_delegate",
197
256
  )
198
257
 
199
258
  def __init__(
@@ -213,6 +272,7 @@ class Objects(
213
272
  self.prefetch_delegate = PrefetchDelegate()
214
273
  self.prefetch(*prefetch)
215
274
  self.where_delegate = WhereDelegate()
275
+ self.lock_rows_delegate = LockRowsDelegate()
216
276
 
217
277
  def output(self: Self, load_json: bool = False) -> Self:
218
278
  self.output_delegate.output(
@@ -272,6 +332,26 @@ class Objects(
272
332
  self.limit_delegate.limit(1)
273
333
  return First[TableInstance](query=self)
274
334
 
335
+ def lock_rows(
336
+ self: Self,
337
+ lock_strength: t.Union[
338
+ LockStrength,
339
+ t.Literal[
340
+ "UPDATE",
341
+ "NO KEY UPDATE",
342
+ "KEY SHARE",
343
+ "SHARE",
344
+ ],
345
+ ] = LockStrength.update,
346
+ nowait: bool = False,
347
+ skip_locked: bool = False,
348
+ of: t.Tuple[type[Table], ...] = (),
349
+ ) -> Self:
350
+ self.lock_rows_delegate.lock_rows(
351
+ lock_strength, nowait, skip_locked, of
352
+ )
353
+ return self
354
+
275
355
  def get(self, where: Combinable) -> Get[TableInstance]:
276
356
  self.where_delegate.where(where)
277
357
  self.limit_delegate.limit(1)
@@ -322,6 +402,7 @@ class Objects(
322
402
  "offset_delegate",
323
403
  "output_delegate",
324
404
  "order_by_delegate",
405
+ "lock_rows_delegate",
325
406
  ):
326
407
  setattr(select, attr, getattr(self, attr))
327
408
 
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import typing as t
4
4
 
5
+ from piccolo.engine.base import BaseBatch
5
6
  from piccolo.query.base import Query
6
7
  from piccolo.querystring import QueryString
7
8
 
@@ -21,6 +22,18 @@ class Raw(Query):
21
22
  super().__init__(table, **kwargs)
22
23
  self.querystring = querystring
23
24
 
25
+ async def batch(
26
+ self,
27
+ batch_size: t.Optional[int] = None,
28
+ node: t.Optional[str] = None,
29
+ **kwargs,
30
+ ) -> BaseBatch:
31
+ if batch_size:
32
+ kwargs.update(batch_size=batch_size)
33
+ if node:
34
+ kwargs.update(node=node)
35
+ return await self.table._meta.db.batch(self, **kwargs)
36
+
24
37
  @property
25
38
  def default_querystrings(self) -> t.Sequence[QueryString]:
26
39
  return [self.querystring]
@@ -19,6 +19,8 @@ from piccolo.query.mixins import (
19
19
  DistinctDelegate,
20
20
  GroupByDelegate,
21
21
  LimitDelegate,
22
+ LockRowsDelegate,
23
+ LockStrength,
22
24
  OffsetDelegate,
23
25
  OrderByDelegate,
24
26
  OrderByRaw,
@@ -150,6 +152,7 @@ class Select(Query[TableInstance, t.List[t.Dict[str, t.Any]]]):
150
152
  "output_delegate",
151
153
  "callback_delegate",
152
154
  "where_delegate",
155
+ "lock_rows_delegate",
153
156
  )
154
157
 
155
158
  def __init__(
@@ -174,6 +177,7 @@ class Select(Query[TableInstance, t.List[t.Dict[str, t.Any]]]):
174
177
  self.output_delegate = OutputDelegate()
175
178
  self.callback_delegate = CallbackDelegate()
176
179
  self.where_delegate = WhereDelegate()
180
+ self.lock_rows_delegate = LockRowsDelegate()
177
181
 
178
182
  self.columns(*columns_list)
179
183
 
@@ -219,6 +223,26 @@ class Select(Query[TableInstance, t.List[t.Dict[str, t.Any]]]):
219
223
  self.offset_delegate.offset(number)
220
224
  return self
221
225
 
226
+ def lock_rows(
227
+ self: Self,
228
+ lock_strength: t.Union[
229
+ LockStrength,
230
+ t.Literal[
231
+ "UPDATE",
232
+ "NO KEY UPDATE",
233
+ "KEY SHARE",
234
+ "SHARE",
235
+ ],
236
+ ] = LockStrength.update,
237
+ nowait: bool = False,
238
+ skip_locked: bool = False,
239
+ of: t.Tuple[type[Table], ...] = (),
240
+ ) -> Self:
241
+ self.lock_rows_delegate.lock_rows(
242
+ lock_strength, nowait, skip_locked, of
243
+ )
244
+ return self
245
+
222
246
  async def _splice_m2m_rows(
223
247
  self,
224
248
  response: t.List[t.Dict[str, t.Any]],
@@ -618,6 +642,16 @@ class Select(Query[TableInstance, t.List[t.Dict[str, t.Any]]]):
618
642
  query += "{}"
619
643
  args.append(self.offset_delegate._offset.querystring)
620
644
 
645
+ if self.lock_rows_delegate._lock_rows:
646
+ if engine_type == "sqlite":
647
+ raise NotImplementedError(
648
+ "SQLite doesn't support row locking e.g. SELECT ... FOR "
649
+ "UPDATE"
650
+ )
651
+
652
+ query += "{}"
653
+ args.append(self.lock_rows_delegate._lock_rows.querystring)
654
+
621
655
  querystring = QueryString(query, *args)
622
656
 
623
657
  return [querystring]
piccolo/query/mixins.py CHANGED
@@ -784,3 +784,91 @@ class OnConflictDelegate:
784
784
  target=target, action=action_, values=values, where=where
785
785
  )
786
786
  )
787
+
788
+
789
+ class LockStrength(str, Enum):
790
+ """
791
+ Specify lock strength
792
+
793
+ https://www.postgresql.org/docs/current/sql-select.html#SQL-FOR-UPDATE-SHARE
794
+ """
795
+
796
+ update = "UPDATE"
797
+ no_key_update = "NO KEY UPDATE"
798
+ share = "SHARE"
799
+ key_share = "KEY SHARE"
800
+
801
+
802
+ @dataclass
803
+ class LockRows:
804
+ __slots__ = ("lock_strength", "nowait", "skip_locked", "of")
805
+
806
+ lock_strength: LockStrength
807
+ nowait: bool
808
+ skip_locked: bool
809
+ of: t.Tuple[t.Type[Table], ...]
810
+
811
+ def __post_init__(self):
812
+ if not isinstance(self.lock_strength, LockStrength):
813
+ raise TypeError("lock_strength must be a LockStrength")
814
+ if not isinstance(self.nowait, bool):
815
+ raise TypeError("nowait must be a bool")
816
+ if not isinstance(self.skip_locked, bool):
817
+ raise TypeError("skip_locked must be a bool")
818
+ if not isinstance(self.of, tuple) or not all(
819
+ hasattr(x, "_meta") for x in self.of
820
+ ):
821
+ raise TypeError("of must be a tuple of Table")
822
+ if self.nowait and self.skip_locked:
823
+ raise TypeError(
824
+ "The nowait option cannot be used with skip_locked"
825
+ )
826
+
827
+ @property
828
+ def querystring(self) -> QueryString:
829
+ sql = f" FOR {self.lock_strength.value}"
830
+ if self.of:
831
+ tables = ", ".join(
832
+ i._meta.get_formatted_tablename() for i in self.of
833
+ )
834
+ sql += " OF " + tables
835
+ if self.nowait:
836
+ sql += " NOWAIT"
837
+ if self.skip_locked:
838
+ sql += " SKIP LOCKED"
839
+
840
+ return QueryString(sql)
841
+
842
+ def __str__(self) -> str:
843
+ return self.querystring.__str__()
844
+
845
+
846
+ @dataclass
847
+ class LockRowsDelegate:
848
+
849
+ _lock_rows: t.Optional[LockRows] = None
850
+
851
+ def lock_rows(
852
+ self,
853
+ lock_strength: t.Union[
854
+ LockStrength,
855
+ t.Literal[
856
+ "UPDATE",
857
+ "NO KEY UPDATE",
858
+ "KEY SHARE",
859
+ "SHARE",
860
+ ],
861
+ ] = LockStrength.update,
862
+ nowait=False,
863
+ skip_locked=False,
864
+ of: t.Tuple[type[Table], ...] = (),
865
+ ):
866
+ lock_strength_: LockStrength
867
+ if isinstance(lock_strength, LockStrength):
868
+ lock_strength_ = lock_strength
869
+ elif isinstance(lock_strength, str):
870
+ lock_strength_ = LockStrength(lock_strength.upper())
871
+ else:
872
+ raise ValueError("Unrecognised `lock_strength` value.")
873
+
874
+ self._lock_rows = LockRows(lock_strength_, nowait, skip_locked, of)
piccolo/table.py CHANGED
@@ -46,7 +46,7 @@ from piccolo.query import (
46
46
  )
47
47
  from piccolo.query.methods.create_index import CreateIndex
48
48
  from piccolo.query.methods.indexes import Indexes
49
- from piccolo.query.methods.objects import First
49
+ from piccolo.query.methods.objects import First, UpdateSelf
50
50
  from piccolo.query.methods.refresh import Refresh
51
51
  from piccolo.querystring import QueryString
52
52
  from piccolo.utils import _camel_to_snake
@@ -525,6 +525,43 @@ class Table(metaclass=TableMetaclass):
525
525
  == getattr(self, self._meta.primary_key._meta.name)
526
526
  )
527
527
 
528
+ def update_self(
529
+ self, values: t.Dict[t.Union[Column, str], t.Any]
530
+ ) -> UpdateSelf:
531
+ """
532
+ This allows the user to update a single object - useful when the values
533
+ are derived from the database in some way.
534
+
535
+ For example, if we have the following table::
536
+
537
+ class Band(Table):
538
+ name = Varchar()
539
+ popularity = Integer()
540
+
541
+ And we fetch an object::
542
+
543
+ >>> band = await Band.objects().get(name="Pythonistas")
544
+
545
+ We could use the typical syntax for updating the object::
546
+
547
+ >>> band.popularity += 1
548
+ >>> await band.save()
549
+
550
+ The problem with this, is what if another object has already
551
+ incremented ``popularity``? It would overide the value.
552
+
553
+ Instead we can do this:
554
+
555
+ >>> await band.update_self({
556
+ ... Band.popularity: Band.popularity + 1
557
+ ... })
558
+
559
+ This updates ``popularity`` in the database, and also sets the new
560
+ value for ``popularity`` on the object.
561
+
562
+ """
563
+ return UpdateSelf(row=self, values=values)
564
+
528
565
  def remove(self) -> Delete:
529
566
  """
530
567
  A proxy to a delete query.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: piccolo
3
- Version: 1.17.1
3
+ Version: 1.19.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
@@ -1,10 +1,10 @@
1
- piccolo/__init__.py,sha256=LHM-IUVB0dp8WEkvdMoZPznVSxU-UEwiRDYV3iBGA4w,23
1
+ piccolo/__init__.py,sha256=P8Wjra0Mf_3SPYYqkD2tAWe0dnPdqTSizVJGqA8al_Q,23
2
2
  piccolo/custom_types.py,sha256=7HMQAze-5mieNLfbQ5QgbRQgR2abR7ol0qehv2SqROY,604
3
3
  piccolo/main.py,sha256=1VsFV67FWTUikPTysp64Fmgd9QBVa_9wcwKfwj2UCEA,5117
4
4
  piccolo/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  piccolo/querystring.py,sha256=yZdURtiVSlxkkEVJoFKAmL2OfNxrUr8xfsfuBBB7IuY,9662
6
6
  piccolo/schema.py,sha256=qNNy4tG_HqnXR9t3hHMgYXtGxHabwQAhUpc6RKLJ_gE,7960
7
- piccolo/table.py,sha256=nS3zuhGNPZ4H9s_E3ieFbrE_u1Tr6TenBVw_nrrtmRQ,49766
7
+ piccolo/table.py,sha256=rwLkom0-4ahOTVon9JDoZ92cp5d3nuwo24ES8attxCM,50896
8
8
  piccolo/table_reflection.py,sha256=jrN1nHerDJ4tU09GtNN3hz7ap-7rXnSUjljFO6LB2H0,7094
9
9
  piccolo/apps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
10
  piccolo/apps/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -147,7 +147,7 @@ piccolo/engine/postgres.py,sha256=DekL3KafCdzSAEQ6_EgOiUB1ERXh2xpePYwI9QvmN-c,18
147
147
  piccolo/engine/sqlite.py,sha256=KwJc3UttBP_8qSREbLJshqEfROF17ENf0Ju9BwI5_so,25236
148
148
  piccolo/query/__init__.py,sha256=bcsMV4813rMRAIqGv4DxI4eyO4FmpXkDv9dfTk5pt3A,699
149
149
  piccolo/query/base.py,sha256=iI9Fv3oOw7T4ZWZvRKRwdtClvQtSaAepslH24vwxZVA,14616
150
- piccolo/query/mixins.py,sha256=EFEFb9It4y1mR6_JXLn139h5M9KgeP750STYy5M4MLs,21951
150
+ piccolo/query/mixins.py,sha256=X9HEYnj6uOjgTkGr4vgqTwN_dokJPzVagwbFx385atQ,24468
151
151
  piccolo/query/proxy.py,sha256=Yq4jNc7IWJvdeO3u7_7iPyRy2WhVj8KsIUcIYHBIi9Q,1839
152
152
  piccolo/query/functions/__init__.py,sha256=pZkzOIh7Sg9HPNOeegOwAS46Oxt31ATlSVmwn-lxCbc,605
153
153
  piccolo/query/functions/aggregate.py,sha256=OdjDjr_zyD4S9UbrZ2C3V5mz4OT2sIfAFAdTGr4WL54,4248
@@ -166,10 +166,10 @@ piccolo/query/methods/drop_index.py,sha256=5x3vHpoOmQ1SMhj6L7snKXX6M9l9j1E1PFSO6
166
166
  piccolo/query/methods/exists.py,sha256=lTMjtrFPFygZmaPV3sfQKXc3K0sVqJ2S6PDc3fRK6YQ,1203
167
167
  piccolo/query/methods/indexes.py,sha256=J-QUqaBJwpgahskUH0Cu0Mq7zEKcfVAtDsUVIVX-C4c,943
168
168
  piccolo/query/methods/insert.py,sha256=ssLJ_wn08KnOwwr7t-VILyn1P4hrvM63CfPIcAJWT5k,4701
169
- piccolo/query/methods/objects.py,sha256=iahDUziUtlx7pJ2uBAhdm3hCTmg2AS9C8cal1my5KR0,11705
170
- piccolo/query/methods/raw.py,sha256=VhYpCB52mZk4zqFTsqK5CHKTDGskUjISXTBV7UjohmA,600
169
+ piccolo/query/methods/objects.py,sha256=kWHSgnXFVYe-x6tIHstbZvZic6mQCCGES-L_2Icbg-I,13910
170
+ piccolo/query/methods/raw.py,sha256=wQWR8b-yA_Gr-5lqRMZe9BOAAMBAw8CqTx37qVYvM1A,987
171
171
  piccolo/query/methods/refresh.py,sha256=wg1zghKfwz-VmqK4uWa4GNMiDtK-skTqow591Hb3ONM,5854
172
- piccolo/query/methods/select.py,sha256=UH-y2g3Ub7bEowfLObrrhw0W-HepTXWCmuMPhk13roE,21406
172
+ piccolo/query/methods/select.py,sha256=jNR7CsEPUarffYMsXytKm7iScrQ_nqpRX-5mTPrXfjg,22414
173
173
  piccolo/query/methods/table_exists.py,sha256=0yb3n6Jd2ovSBWlZ-gl00K4E7Jnbj7J8qAAX5d7hvNk,1259
174
174
  piccolo/query/methods/update.py,sha256=LfWqIXEl1aecc0rkVssTFmwyD6wXGhlKcTrUVhtlEsw,3705
175
175
  piccolo/testing/__init__.py,sha256=pRFSqRInfx95AakOq54atmvqoB-ue073q2aR8u8zR40,83
@@ -316,7 +316,7 @@ tests/query/mixins/test_order_by_delegate.py,sha256=mOV3Gxs0XeliONxjWSOniI1z6lbZ
316
316
  tests/table/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
317
317
  tests/table/test_all_columns.py,sha256=wZ7i9mTT9wKWLE2BoQ9jDbPaqnBHfV-ZlsROp7SZq7k,667
318
318
  tests/table/test_alter.py,sha256=pMD38BIFfta1vxFqp8YoaRfMxdwxhQSwcxYO4erpUi8,12394
319
- tests/table/test_batch.py,sha256=l3kSHOT2DQ8FJjbr_bFyMGgQ2BI0Ed5Gs4GeJdEsjVo,3795
319
+ tests/table/test_batch.py,sha256=fhBfC5P_vsk1wlmVLeVCDQ8tz9nrny57aIgRnPJ6tlg,4944
320
320
  tests/table/test_callback.py,sha256=mNp4NOQ4PM_PIKn-WLG4HyvN-YHk7w0Ka_Bljg5Ugd4,6381
321
321
  tests/table/test_constructor.py,sha256=zPbzhKQWzzsQQUK7P9WM8OgCi1ndhXedP6rU0tg6XIM,832
322
322
  tests/table/test_count.py,sha256=qm4dwlQJ5gv8FPSsgYTS-3Gsd_KLgvWlFnmXweKydxw,2297
@@ -339,10 +339,11 @@ tests/table/test_raw.py,sha256=9PTvYngQi41nYd5lKzkJdTqsEcwrdOXcvZjq-W26CwQ,1683
339
339
  tests/table/test_ref.py,sha256=eYNRnYHzNMXuMbV3B1ca5EidpIg4500q6hr1ccuVaso,269
340
340
  tests/table/test_refresh.py,sha256=-BaLS6fZiR2RtQaFa7D9WGBjrbrss1-tt5xz1NE_m8E,9250
341
341
  tests/table/test_repr.py,sha256=uahz3_GffGQrf2mDE-4-Pu4AmSLBAyso6-9rbohCl58,446
342
- tests/table/test_select.py,sha256=jgeiahIlNFVijxYb3a54g1sJWVfH3llaYrsTBmdicrs,40390
342
+ tests/table/test_select.py,sha256=PcLRtHGEZfYNB0Qm2b9P6NFJOH2lWvviRjsr1cEmsMQ,41503
343
343
  tests/table/test_str.py,sha256=eztWNULcjARR1fr9X5n4tojhDNgDfatVyNHwuYrzHAo,1731
344
344
  tests/table/test_table_exists.py,sha256=upv2e9UD32V2QZOShzmcw0reMqRbYiX_jxWx57p25jg,1082
345
345
  tests/table/test_update.py,sha256=Cqi0xX3kEuJ0k-x_emPGB3arXuGWZ9e3CJ3HPFnw9Zw,20505
346
+ tests/table/test_update_self.py,sha256=im6HcM-WLkEhZP0vTL42tYEJZyAZG6gDOxCnbikCBD4,907
346
347
  tests/table/instance/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
347
348
  tests/table/instance/test_create.py,sha256=JD0l7L9dDK1FKPhUs6WC_B2bruPR1qQ8aIqXpEbfiUg,1105
348
349
  tests/table/instance/test_get_related.py,sha256=dAT89KHuO_XEMY6h0ngDHrXp_nJsMoSToTbH_IRclTE,1275
@@ -367,9 +368,9 @@ tests/utils/test_sql_values.py,sha256=vzxRmy16FfLZPH-sAQexBvsF9MXB8n4smr14qoEOS5
367
368
  tests/utils/test_sync.py,sha256=9ytVo56y2vPQePvTeIi9lHIouEhWJbodl1TmzkGFrSo,799
368
369
  tests/utils/test_table_reflection.py,sha256=SIzuat-IpcVj1GCFyOWKShI8YkhdOPPFH7qVrvfyPNE,3794
369
370
  tests/utils/test_warnings.py,sha256=NvSC_cvJ6uZcwAGf1m-hLzETXCqprXELL8zg3TNLVMw,269
370
- piccolo-1.17.1.dist-info/LICENSE,sha256=zFIpi-16uIJ420UMIG75NU0JbDBykvrdnXcj5U_EYBI,1059
371
- piccolo-1.17.1.dist-info/METADATA,sha256=TZvCpeVtvGuvBWlyeNvBGPXN7dFZNSrRVuvUwyCOGRs,5178
372
- piccolo-1.17.1.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
373
- piccolo-1.17.1.dist-info/entry_points.txt,sha256=SJPHET4Fi1bN5F3WqcKkv9SClK3_F1I7m4eQjk6AFh0,46
374
- piccolo-1.17.1.dist-info/top_level.txt,sha256=-SR74VGbk43VoPy1HH-mHm97yoGukLK87HE5kdBW6qM,24
375
- piccolo-1.17.1.dist-info/RECORD,,
371
+ piccolo-1.19.0.dist-info/LICENSE,sha256=zFIpi-16uIJ420UMIG75NU0JbDBykvrdnXcj5U_EYBI,1059
372
+ piccolo-1.19.0.dist-info/METADATA,sha256=sjcIxHmO7mxcywJ7dGiQtleQjBW1uIIBL-ld4CJeVoU,5178
373
+ piccolo-1.19.0.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
374
+ piccolo-1.19.0.dist-info/entry_points.txt,sha256=SJPHET4Fi1bN5F3WqcKkv9SClK3_F1I7m4eQjk6AFh0,46
375
+ piccolo-1.19.0.dist-info/top_level.txt,sha256=-SR74VGbk43VoPy1HH-mHm97yoGukLK87HE5kdBW6qM,24
376
+ piccolo-1.19.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (74.1.2)
2
+ Generator: setuptools (75.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
tests/table/test_batch.py CHANGED
@@ -95,6 +95,47 @@ class TestBatchObjects(DBTestCase):
95
95
  self.assertEqual(iterations, _iterations)
96
96
 
97
97
 
98
+ class TestBatchRaw(DBTestCase):
99
+ def _check_results(self, batch):
100
+ """
101
+ Make sure the data is returned in the correct format.
102
+ """
103
+ self.assertEqual(type(batch), list)
104
+ if len(batch) > 0:
105
+ row = batch[0]
106
+ self.assertIsInstance(row, Manager)
107
+
108
+ async def run_batch(self, batch_size):
109
+ row_count = 0
110
+ iterations = 0
111
+
112
+ async with await Manager.raw("SELECT * FROM manager").batch(
113
+ batch_size=batch_size
114
+ ) as batch:
115
+ async for _batch in batch:
116
+ self._check_results(_batch)
117
+ _row_count = len(_batch)
118
+ row_count += _row_count
119
+ iterations += 1
120
+
121
+ return row_count, iterations
122
+
123
+ async def test_batch(self):
124
+ row_count = 1000
125
+ self.insert_many_rows(row_count)
126
+
127
+ batch_size = 10
128
+
129
+ _row_count, iterations = asyncio.run(
130
+ self.run_batch(batch_size=batch_size), debug=True
131
+ )
132
+
133
+ _iterations = math.ceil(row_count / batch_size)
134
+
135
+ self.assertEqual(_row_count, row_count)
136
+ self.assertEqual(iterations, _iterations)
137
+
138
+
98
139
  @engines_only("postgres", "cockroach")
99
140
  class TestBatchNodeArg(TestCase):
100
141
  def test_batch_extra_node(self):
@@ -1028,6 +1028,40 @@ class TestSelect(DBTestCase):
1028
1028
  response, [{"name": "Pythonistas", "popularity_log": 3.0}]
1029
1029
  )
1030
1030
 
1031
+ @pytest.mark.skipif(
1032
+ is_running_sqlite(),
1033
+ reason="SQLite doesn't support SELECT ... FOR UPDATE.",
1034
+ )
1035
+ def test_lock_rows(self):
1036
+ """
1037
+ Make sure the for_update clause works.
1038
+ """
1039
+ self.insert_rows()
1040
+
1041
+ query = Band.select()
1042
+ self.assertNotIn("FOR UPDATE", query.__str__())
1043
+
1044
+ query = query.lock_rows()
1045
+ self.assertTrue(query.__str__().endswith("FOR UPDATE"))
1046
+
1047
+ query = query.lock_rows(lock_strength="KEY SHARE")
1048
+ self.assertTrue(query.__str__().endswith("FOR KEY SHARE"))
1049
+
1050
+ query = query.lock_rows(skip_locked=True)
1051
+ self.assertTrue(query.__str__().endswith("FOR UPDATE SKIP LOCKED"))
1052
+
1053
+ query = query.lock_rows(nowait=True)
1054
+ self.assertTrue(query.__str__().endswith("FOR UPDATE NOWAIT"))
1055
+
1056
+ query = query.lock_rows(of=(Band,))
1057
+ self.assertTrue(query.__str__().endswith('FOR UPDATE OF "band"'))
1058
+
1059
+ with self.assertRaises(TypeError):
1060
+ query = query.lock_rows(skip_locked=True, nowait=True)
1061
+
1062
+ response = query.run_sync()
1063
+ assert response is not None
1064
+
1031
1065
 
1032
1066
  class TestSelectSecret(TestCase):
1033
1067
  def setUp(self):
@@ -0,0 +1,27 @@
1
+ from piccolo.testing.test_case import AsyncTableTest
2
+ from tests.example_apps.music.tables import Band, Manager
3
+
4
+
5
+ class TestUpdateSelf(AsyncTableTest):
6
+
7
+ tables = [Band, Manager]
8
+
9
+ async def test_update_self(self):
10
+ band = Band({Band.name: "Pythonistas", Band.popularity: 1000})
11
+
12
+ # Make sure we get a ValueError if it's not in the database yet.
13
+ with self.assertRaises(ValueError):
14
+ await band.update_self({Band.popularity: Band.popularity + 1})
15
+
16
+ # Save it, so it's in the database
17
+ await band.save()
18
+
19
+ # Make sure we can successfully update the object
20
+ await band.update_self({Band.popularity: Band.popularity + 1})
21
+
22
+ # Make sure the value was updated on the object
23
+ assert band.popularity == 1001
24
+
25
+ # Make sure the value was updated in the database
26
+ await band.refresh()
27
+ assert band.popularity == 1001