piccolo 1.18.0__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.18.0"
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,
@@ -250,6 +252,7 @@ class Objects(
250
252
  "callback_delegate",
251
253
  "prefetch_delegate",
252
254
  "where_delegate",
255
+ "lock_rows_delegate",
253
256
  )
254
257
 
255
258
  def __init__(
@@ -269,6 +272,7 @@ class Objects(
269
272
  self.prefetch_delegate = PrefetchDelegate()
270
273
  self.prefetch(*prefetch)
271
274
  self.where_delegate = WhereDelegate()
275
+ self.lock_rows_delegate = LockRowsDelegate()
272
276
 
273
277
  def output(self: Self, load_json: bool = False) -> Self:
274
278
  self.output_delegate.output(
@@ -328,6 +332,26 @@ class Objects(
328
332
  self.limit_delegate.limit(1)
329
333
  return First[TableInstance](query=self)
330
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
+
331
355
  def get(self, where: Combinable) -> Get[TableInstance]:
332
356
  self.where_delegate.where(where)
333
357
  self.limit_delegate.limit(1)
@@ -378,6 +402,7 @@ class Objects(
378
402
  "offset_delegate",
379
403
  "output_delegate",
380
404
  "order_by_delegate",
405
+ "lock_rows_delegate",
381
406
  ):
382
407
  setattr(select, attr, getattr(self, attr))
383
408
 
@@ -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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: piccolo
3
- Version: 1.18.0
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,4 +1,4 @@
1
- piccolo/__init__.py,sha256=QE23QxQ71q70fSQ-ah5y_ldAqWh6zl7J9GsIUi8fHIk,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
@@ -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=XE49RFeInx_RzRD40xzBA5IfmXPmDvKKC6Ql9FV8ntE,13223
169
+ piccolo/query/methods/objects.py,sha256=kWHSgnXFVYe-x6tIHstbZvZic6mQCCGES-L_2Icbg-I,13910
170
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
@@ -339,7 +339,7 @@ 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
@@ -368,9 +368,9 @@ tests/utils/test_sql_values.py,sha256=vzxRmy16FfLZPH-sAQexBvsF9MXB8n4smr14qoEOS5
368
368
  tests/utils/test_sync.py,sha256=9ytVo56y2vPQePvTeIi9lHIouEhWJbodl1TmzkGFrSo,799
369
369
  tests/utils/test_table_reflection.py,sha256=SIzuat-IpcVj1GCFyOWKShI8YkhdOPPFH7qVrvfyPNE,3794
370
370
  tests/utils/test_warnings.py,sha256=NvSC_cvJ6uZcwAGf1m-hLzETXCqprXELL8zg3TNLVMw,269
371
- piccolo-1.18.0.dist-info/LICENSE,sha256=zFIpi-16uIJ420UMIG75NU0JbDBykvrdnXcj5U_EYBI,1059
372
- piccolo-1.18.0.dist-info/METADATA,sha256=_lAgMQsJ-zW7SJkRvz-KQhmrog7hhhBxojwopzFTA_4,5178
373
- piccolo-1.18.0.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
374
- piccolo-1.18.0.dist-info/entry_points.txt,sha256=SJPHET4Fi1bN5F3WqcKkv9SClK3_F1I7m4eQjk6AFh0,46
375
- piccolo-1.18.0.dist-info/top_level.txt,sha256=-SR74VGbk43VoPy1HH-mHm97yoGukLK87HE5kdBW6qM,24
376
- piccolo-1.18.0.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,,
@@ -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):