piccolo 1.17.1__py3-none-any.whl → 1.18.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.18.0"
@@ -27,6 +27,7 @@ from piccolo.utils.sync import run_sync
27
27
 
28
28
  if t.TYPE_CHECKING: # pragma: no cover
29
29
  from piccolo.columns import Column
30
+ from piccolo.table import Table
30
31
 
31
32
 
32
33
  ###############################################################################
@@ -173,6 +174,61 @@ class Create(t.Generic[TableInstance]):
173
174
  return run_sync(self.run(*args, **kwargs))
174
175
 
175
176
 
177
+ class UpdateSelf:
178
+
179
+ def __init__(
180
+ self,
181
+ row: Table,
182
+ values: t.Dict[t.Union[Column, str], t.Any],
183
+ ):
184
+ self.row = row
185
+ self.values = values
186
+
187
+ async def run(
188
+ self,
189
+ node: t.Optional[str] = None,
190
+ in_pool: bool = True,
191
+ ) -> None:
192
+ if not self.row._exists_in_db:
193
+ raise ValueError("This row doesn't exist in the database.")
194
+
195
+ TableClass = self.row.__class__
196
+
197
+ primary_key = TableClass._meta.primary_key
198
+ primary_key_value = getattr(self.row, primary_key._meta.name)
199
+
200
+ if primary_key_value is None:
201
+ raise ValueError("The primary key is None")
202
+
203
+ columns = [
204
+ TableClass._meta.get_column_by_name(i) if isinstance(i, str) else i
205
+ for i in self.values.keys()
206
+ ]
207
+
208
+ response = (
209
+ await TableClass.update(self.values)
210
+ .where(primary_key == primary_key_value)
211
+ .returning(*columns)
212
+ .run(
213
+ node=node,
214
+ in_pool=in_pool,
215
+ )
216
+ )
217
+
218
+ for key, value in response[0].items():
219
+ setattr(self.row, key, value)
220
+
221
+ def __await__(self) -> t.Generator[None, None, None]:
222
+ """
223
+ If the user doesn't explicity call .run(), proxy to it as a
224
+ convenience.
225
+ """
226
+ return self.run().__await__()
227
+
228
+ def run_sync(self, *args, **kwargs) -> None:
229
+ return run_sync(self.run(*args, **kwargs))
230
+
231
+
176
232
  ###############################################################################
177
233
 
178
234
 
@@ -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]
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.18.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=QE23QxQ71q70fSQ-ah5y_ldAqWh6zl7J9GsIUi8fHIk,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
@@ -166,8 +166,8 @@ 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=XE49RFeInx_RzRD40xzBA5IfmXPmDvKKC6Ql9FV8ntE,13223
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
172
  piccolo/query/methods/select.py,sha256=UH-y2g3Ub7bEowfLObrrhw0W-HepTXWCmuMPhk13roE,21406
173
173
  piccolo/query/methods/table_exists.py,sha256=0yb3n6Jd2ovSBWlZ-gl00K4E7Jnbj7J8qAAX5d7hvNk,1259
@@ -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
@@ -343,6 +343,7 @@ tests/table/test_select.py,sha256=jgeiahIlNFVijxYb3a54g1sJWVfH3llaYrsTBmdicrs,40
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.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,,
@@ -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):
@@ -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