tracktolib 0.49.1__py3-none-any.whl → 0.51.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.
tracktolib/pg/__init__.py CHANGED
@@ -11,5 +11,6 @@ from .query import (
11
11
  update_returning,
12
12
  update_one,
13
13
  insert_pg,
14
+ OnConflict,
14
15
  )
15
16
  from .utils import iterate_pg, upsert_csv, safe_pg, safe_pg_context, PGError, PGException
tracktolib/pg/query.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import typing
2
2
  from dataclasses import dataclass, field
3
3
  from typing import TypeVar, Iterable, Callable, Generic, Iterator, TypeAlias, overload, Any, Literal
4
+
4
5
  from ..pg_utils import get_conflict_query
5
6
 
6
7
  try:
@@ -32,6 +33,7 @@ def _get_on_conflict_query(
32
33
  constraint: K | None,
33
34
  on_conflict: K | None,
34
35
  where: K | None,
36
+ merge_columns: Iterable[K] | None,
35
37
  ) -> str:
36
38
  _on_conflict = get_conflict_query(
37
39
  columns=columns,
@@ -40,6 +42,7 @@ def _get_on_conflict_query(
40
42
  constraint=constraint,
41
43
  on_conflict=on_conflict,
42
44
  where=where,
45
+ merge_columns=merge_columns,
43
46
  )
44
47
  return f"{query} {_on_conflict}"
45
48
 
@@ -72,6 +75,8 @@ class PGConflictQuery(Generic[K]):
72
75
  query: str | None = None
73
76
  constraint: str | None = None
74
77
  where: str | None = None
78
+ """JSONB keys to merge (like jsonb1 || newjsonb2)"""
79
+ merge_keys: Iterable[K] | None = None
75
80
 
76
81
  def __post_init__(self):
77
82
  _has_keys = 1 if (self.keys or self.ignore_keys) else 0
@@ -172,6 +177,7 @@ class PGInsertQuery(PGQuery):
172
177
  self.on_conflict.constraint,
173
178
  self.on_conflict.query,
174
179
  self.on_conflict.where,
180
+ self.on_conflict.merge_keys,
175
181
  )
176
182
 
177
183
  # Returning
@@ -193,17 +199,30 @@ def get_update_fields(
193
199
  start_from: int = 0,
194
200
  ignore_keys: list[str] | None = None,
195
201
  quote_columns: bool = False,
202
+ merge_keys: list[str] | None = None,
196
203
  ) -> tuple[str, list]:
197
204
  values, fields, where_values = [], [], []
198
205
  counter = 0
206
+ _merge_keys = set(merge_keys or [])
207
+ _ignore_keys = ignore_keys or []
208
+
209
+ _invalid_merge_keys = _merge_keys - set(keys)
210
+ if _invalid_merge_keys:
211
+ raise ValueError(f"Merge keys not in keys found: {_invalid_merge_keys}")
212
+
199
213
  for k in keys:
200
214
  v = item[k]
201
- if ignore_keys and k in ignore_keys:
215
+ if k in _ignore_keys:
202
216
  where_values.append(v)
203
217
  continue
204
218
  values.append(v)
205
219
  _col = f'"{k}"' if quote_columns else k
206
- fields.append(f"{_col} = ${counter + start_from + 1}")
220
+ _counter = counter + start_from + 1
221
+ fields.append(
222
+ f"{_col} = ${_counter}"
223
+ if k not in _merge_keys
224
+ else f"{_col} = COALESCE(t.{_col}, jsonb_build_object()) || " f"${_counter}"
225
+ )
207
226
  counter += 1
208
227
  return ",\n".join(fields), values + where_values
209
228
 
@@ -222,6 +241,8 @@ class PGUpdateQuery(PGQuery):
222
241
  returning: str | list[str] | None = None
223
242
  """If True, the query will return all the updated fields"""
224
243
  return_keys: bool = False
244
+ """Values to update using merge (like {}::jsonb || {}::jsonb)"""
245
+ merge_keys: list[str] | None = None
225
246
 
226
247
  _update_fields: str | None = field(init=False, default=None)
227
248
  _values: list | None = field(init=False, default=None)
@@ -238,6 +259,7 @@ class PGUpdateQuery(PGQuery):
238
259
  start_from=self.start_from or 0,
239
260
  ignore_keys=self.where_keys,
240
261
  quote_columns=self.quote_columns,
262
+ merge_keys=self.merge_keys,
241
263
  )
242
264
  if self.returning and self.return_keys:
243
265
  raise ValueError("Please choose either returning or return_keys")
@@ -263,7 +285,7 @@ class PGUpdateQuery(PGQuery):
263
285
  raise ValueError("No update fields found")
264
286
 
265
287
  query = f"""
266
- UPDATE {self.table}
288
+ UPDATE {self.table} t
267
289
  SET {self._update_fields}
268
290
  {self._get_where_query()}
269
291
  """
@@ -370,8 +392,13 @@ async def update_one(
370
392
  keys: list[str] | None = None,
371
393
  start_from: int | None = None,
372
394
  where: str | None = None,
395
+ merge_keys: list[str] | None = None,
373
396
  ):
374
- query = PGUpdateQuery(table=table, items=[item], start_from=start_from, where_keys=keys, where=where)
397
+ query = PGUpdateQuery(
398
+ table=table, items=[item], start_from=start_from, where_keys=keys, where=where, merge_keys=merge_keys
399
+ )
400
+ print(query.query)
401
+ print(query.values)
375
402
  await conn.execute(query.query, *args, *query.values)
376
403
 
377
404
 
@@ -386,6 +413,7 @@ async def update_returning(
386
413
  where: str | None = None,
387
414
  keys: list[str] | None = None,
388
415
  start_from: int | None = None,
416
+ merge_keys: list[str] | None = None,
389
417
  ) -> Any | None: ...
390
418
 
391
419
 
@@ -400,6 +428,7 @@ async def update_returning(
400
428
  where: str | None = None,
401
429
  keys: list[str] | None = None,
402
430
  start_from: int | None = None,
431
+ merge_keys: list[str] | None = None,
403
432
  ) -> asyncpg.Record | None: ...
404
433
 
405
434
 
@@ -414,6 +443,7 @@ async def update_returning(
414
443
  where: str | None = None,
415
444
  keys: list[str] | None = None,
416
445
  start_from: int | None = None,
446
+ merge_keys: list[str] | None = None,
417
447
  ) -> asyncpg.Record | None: ...
418
448
 
419
449
 
@@ -427,6 +457,7 @@ async def update_returning(
427
457
  where: str | None = None,
428
458
  keys: list[str] | None = None,
429
459
  start_from: int | None = None,
460
+ merge_keys: list[str] | None = None,
430
461
  ) -> Any | asyncpg.Record | None:
431
462
  if returning is not None:
432
463
  returning_values = [returning] if isinstance(returning, str) else returning
@@ -440,6 +471,7 @@ async def update_returning(
440
471
  where_keys=keys,
441
472
  return_keys=return_keys,
442
473
  returning=returning_values,
474
+ merge_keys=merge_keys,
443
475
  )
444
476
  fn = conn.fetchval if len(returning_values or []) == 1 else conn.fetchrow
445
477
  return await fn(query.query, *args, *query.values)
tracktolib/pg_utils.py CHANGED
@@ -1,7 +1,8 @@
1
- from typing_extensions import LiteralString
2
1
  from typing import Iterable
3
2
  from typing import cast
4
3
 
4
+ from typing_extensions import LiteralString
5
+
5
6
 
6
7
  def get_tmp_table_query(
7
8
  schema: LiteralString,
@@ -41,6 +42,7 @@ def get_conflict_query(
41
42
  constraint: str | None = None,
42
43
  on_conflict: str | None = None,
43
44
  where: str | None = None,
45
+ merge_columns: Iterable[str] | None = None,
44
46
  ) -> LiteralString:
45
47
  if on_conflict:
46
48
  return cast(LiteralString, on_conflict)
@@ -55,8 +57,20 @@ def get_conflict_query(
55
57
  else:
56
58
  raise NotImplementedError("update_keys or constraint must be set")
57
59
 
58
- _ignore_columns = [*(update_columns or []), *(ignore_columns or [])]
60
+ _update_columns = update_columns or []
61
+ _ignore_columns = ignore_columns or []
62
+ _merge_columns = merge_columns or []
63
+
64
+ if set(_merge_columns) & set(_update_columns):
65
+ raise ValueError("Duplicate keys found between merge and update")
66
+ if set(_merge_columns) & set(_ignore_columns):
67
+ raise ValueError("Merge column cannot be ignored")
68
+
69
+ _ignore_columns = [*_update_columns, *_ignore_columns, *_merge_columns]
59
70
  fields = ", ".join(f"{x} = COALESCE(EXCLUDED.{x}, t.{x})" for x in columns if x not in _ignore_columns)
71
+ if merge_columns:
72
+ fields = fields + ", " if fields else fields
73
+ fields += ", ".join(f"{x} = COALESCE(t.{x}, jsonb_build_object()) || EXCLUDED.{x}" for x in merge_columns)
60
74
  if not fields:
61
75
  raise ValueError("No fields set")
62
76
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tracktolib
3
- Version: 0.49.1
3
+ Version: 0.51.0
4
4
  Summary: Utility library for python
5
5
  Home-page: https://github.com/tracktor/tracktolib
6
6
  License: MIT
@@ -3,17 +3,17 @@ tracktolib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  tracktolib/api.py,sha256=xArVgRj_g7bw2tEDbjC9qs9f55b9X0-kJoc8-s1rsYo,9616
4
4
  tracktolib/http_utils.py,sha256=c10JGmHaBw3VSDMYhz2dvVw2lo4PUAq1xMub74I7xDc,2625
5
5
  tracktolib/logs.py,sha256=M5RZ8OYKgLEBJeC1AwUCSRbFAH09hwdTjjs-AQ9QGg8,2204
6
- tracktolib/pg/__init__.py,sha256=32kKBJzIDyppszZMAOSMgM3ggz7Ow0TaNKiMs740gyw,333
7
- tracktolib/pg/query.py,sha256=KESY4g0XePUrRIakygmzhPP2vIOfJGWc2PAZsIj_PfI,13580
6
+ tracktolib/pg/__init__.py,sha256=j67e3B3gBbCHLD20QBybptmNdbbVMzNhZE6XjIPuKVo,349
7
+ tracktolib/pg/query.py,sha256=XX5eD02TRWRIiBVj0ENooQ893aycan2fJ55_1J207Rw,14711
8
8
  tracktolib/pg/utils.py,sha256=cL24KEt4SWJQ7LJPzaO3c8Xg0ZLmjhn22DtTWg86nwc,6324
9
9
  tracktolib/pg_sync.py,sha256=z6EB0hELq6t129gnVnX0rNXv2McZXxI8khy-TEG65Ag,5165
10
- tracktolib/pg_utils.py,sha256=8DDlZVSgfNie9M-zL7wilFufXMMvSPw5d7aIDs_pQCM,1976
10
+ tracktolib/pg_utils.py,sha256=VXPpy1jGq6aCgTlfFJDIrq6JDujR83JN5ZRiCi8Lx4E,2582
11
11
  tracktolib/s3/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
12
  tracktolib/s3/minio.py,sha256=wMEjkSes9Fp39fD17IctALpD6zB2xwDRQEmO7Vzan3g,1387
13
13
  tracktolib/s3/s3.py,sha256=d0Q63Zb62ef4jAt05zQwpgYvAoxHC9kSQuHzzK90VvE,4825
14
14
  tracktolib/tests.py,sha256=Pbc4yGQrIWFLBEgX-kDBxhMCqH-bfOJtkH2agvaM6ZQ,382
15
15
  tracktolib/utils.py,sha256=jwLww8bqDu8zEip9uN4yW0lE5_YMWfrAHYtagr8sYOA,5295
16
- tracktolib-0.49.1.dist-info/LICENSE,sha256=uUanH0X7SeZEPdsRTHegMSMTiIHMurt9H0jSwEwKE1Y,1081
17
- tracktolib-0.49.1.dist-info/METADATA,sha256=_HBMlEoVz25S6viwoubRgSxNhZjG51Madd4d3A0F-XA,3641
18
- tracktolib-0.49.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
19
- tracktolib-0.49.1.dist-info/RECORD,,
16
+ tracktolib-0.51.0.dist-info/LICENSE,sha256=uUanH0X7SeZEPdsRTHegMSMTiIHMurt9H0jSwEwKE1Y,1081
17
+ tracktolib-0.51.0.dist-info/METADATA,sha256=v2MsoN7EIuclo58jhPp40YJespn9Oy4rYKM8kg7VRsQ,3641
18
+ tracktolib-0.51.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
19
+ tracktolib-0.51.0.dist-info/RECORD,,