pgtask 0.1.3__tar.gz → 0.3.0__tar.gz

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 (77) hide show
  1. {pgtask-0.1.3 → pgtask-0.3.0}/Cargo.lock +143 -9
  2. {pgtask-0.1.3 → pgtask-0.3.0}/Cargo.toml +3 -1
  3. pgtask-0.3.0/PKG-INFO +107 -0
  4. pgtask-0.3.0/README.md +88 -0
  5. pgtask-0.3.0/crates/pgtask/Cargo.toml +23 -0
  6. pgtask-0.3.0/crates/pgtask/README.md +100 -0
  7. {pgtask-0.1.3 → pgtask-0.3.0}/crates/pgtask/src/lib.rs +1 -1
  8. {pgtask-0.1.3 → pgtask-0.3.0}/crates/pgtask-core/Cargo.toml +4 -0
  9. pgtask-0.3.0/crates/pgtask-core/README.md +13 -0
  10. {pgtask-0.1.3 → pgtask-0.3.0}/crates/pgtask-core/src/lib.rs +1 -1
  11. {pgtask-0.1.3 → pgtask-0.3.0}/crates/pgtask-core/src/schedule.rs +15 -10
  12. {pgtask-0.1.3 → pgtask-0.3.0}/crates/pgtask-core/src/task.rs +1 -0
  13. pgtask-0.3.0/crates/pgtask-core/tests/.gitignore +4 -0
  14. pgtask-0.3.0/crates/pgtask-core/tests/retry_properties.rs +124 -0
  15. {pgtask-0.1.3 → pgtask-0.3.0}/crates/pgtask-core/tests/schedule_clock.rs +10 -9
  16. pgtask-0.3.0/crates/pgtask-core/tests/schedule_properties.rs +231 -0
  17. {pgtask-0.1.3 → pgtask-0.3.0}/crates/pgtask-otel/Cargo.toml +1 -0
  18. pgtask-0.3.0/crates/pgtask-otel/README.md +13 -0
  19. {pgtask-0.1.3 → pgtask-0.3.0}/crates/pgtask-otel/src/lib.rs +3 -3
  20. {pgtask-0.1.3 → pgtask-0.3.0}/crates/pgtask-otel/src/metrics.rs +15 -4
  21. {pgtask-0.1.3 → pgtask-0.3.0}/crates/pgtask-postgres/Cargo.toml +7 -2
  22. pgtask-0.3.0/crates/pgtask-postgres/README.md +15 -0
  23. pgtask-0.3.0/crates/pgtask-postgres/migrations/0003_remove_redundant_notifications.sql +63 -0
  24. pgtask-0.3.0/crates/pgtask-postgres/migrations/0004_sample_queue_demand.sql +176 -0
  25. pgtask-0.3.0/crates/pgtask-postgres/migrations/0005_serialise_result_wait_registration.sql +164 -0
  26. pgtask-0.3.0/crates/pgtask-postgres/migrations/0006_keep_resumed_tasks_claimable.sql +91 -0
  27. pgtask-0.3.0/crates/pgtask-postgres/migrations/0007_backfill_failed_attempts.sql +11 -0
  28. pgtask-0.3.0/crates/pgtask-postgres/migrations/0008_index_worker_pagination.sql +1 -0
  29. {pgtask-0.1.3 → pgtask-0.3.0}/crates/pgtask-postgres/src/lib.rs +2 -2
  30. {pgtask-0.1.3 → pgtask-0.3.0}/crates/pgtask-postgres/src/store.rs +63 -15
  31. pgtask-0.3.0/crates/pgtask-postgres/tests/.gitignore +4 -0
  32. {pgtask-0.1.3 → pgtask-0.3.0}/crates/pgtask-postgres/tests/corruption.rs +3 -1
  33. pgtask-0.3.0/crates/pgtask-postgres/tests/guarantees.rs +740 -0
  34. pgtask-0.3.0/crates/pgtask-postgres/tests/linearizability.rs +1340 -0
  35. pgtask-0.3.0/crates/pgtask-postgres/tests/model.rs +476 -0
  36. {pgtask-0.1.3 → pgtask-0.3.0}/crates/pgtask-postgres/tests/queue.rs +258 -8
  37. pgtask-0.3.0/crates/pgtask-postgres/tests/races.rs +364 -0
  38. pgtask-0.3.0/crates/pgtask-postgres/tests/result_wait_locking.rs +219 -0
  39. pgtask-0.3.0/crates/pgtask-postgres/tests/round_trip_properties.rs +184 -0
  40. {pgtask-0.1.3 → pgtask-0.3.0}/crates/pgtask-postgres/tests/schedule.rs +37 -13
  41. {pgtask-0.1.3 → pgtask-0.3.0}/crates/pgtask-postgres/tests/sql_surface.rs +10 -0
  42. {pgtask-0.1.3 → pgtask-0.3.0}/crates/pgtask-postgres/tests/sql_surface.txt +3 -0
  43. {pgtask-0.1.3 → pgtask-0.3.0}/crates/pgtask-worker/Cargo.toml +4 -3
  44. pgtask-0.3.0/crates/pgtask-worker/README.md +13 -0
  45. pgtask-0.3.0/crates/pgtask-worker/src/lib.rs +17 -0
  46. {pgtask-0.1.3 → pgtask-0.3.0}/crates/pgtask-worker/src/runtime.rs +97 -37
  47. {pgtask-0.1.3 → pgtask-0.3.0}/crates/pgtask-worker/tests/worker.rs +344 -9
  48. {pgtask-0.1.3 → pgtask-0.3.0}/python/pgtask/__init__.py +4 -0
  49. {pgtask-0.1.3 → pgtask-0.3.0}/python/pgtask/_native.pyi +1 -0
  50. {pgtask-0.1.3 → pgtask-0.3.0}/python/pgtask/client.py +57 -4
  51. {pgtask-0.1.3 → pgtask-0.3.0}/sdks/python/Cargo.toml +1 -1
  52. pgtask-0.3.0/sdks/python/README.md +88 -0
  53. {pgtask-0.1.3 → pgtask-0.3.0}/sdks/python/src/lib.rs +13 -0
  54. {pgtask-0.1.3 → pgtask-0.3.0}/sdks/python/tests/test_python_sdk.py +158 -1
  55. pgtask-0.1.3/PKG-INFO +0 -42
  56. pgtask-0.1.3/README.md +0 -23
  57. pgtask-0.1.3/crates/pgtask/Cargo.toml +0 -20
  58. pgtask-0.1.3/crates/pgtask-worker/src/lib.rs +0 -11
  59. pgtask-0.1.3/sdks/python/README.md +0 -23
  60. {pgtask-0.1.3 → pgtask-0.3.0}/crates/pgtask/tests/public_api.rs +0 -0
  61. {pgtask-0.1.3 → pgtask-0.3.0}/crates/pgtask-core/src/identifier.rs +0 -0
  62. {pgtask-0.1.3 → pgtask-0.3.0}/crates/pgtask-core/src/retry.rs +0 -0
  63. {pgtask-0.1.3 → pgtask-0.3.0}/crates/pgtask-core/tests/types.rs +0 -0
  64. {pgtask-0.1.3 → pgtask-0.3.0}/crates/pgtask-otel/src/propagation.rs +0 -0
  65. {pgtask-0.1.3 → pgtask-0.3.0}/crates/pgtask-otel/tests/propagation.rs +0 -0
  66. {pgtask-0.1.3 → pgtask-0.3.0}/crates/pgtask-postgres/build.rs +0 -0
  67. {pgtask-0.1.3 → pgtask-0.3.0}/crates/pgtask-postgres/migrations/0001_initial.sql +0 -0
  68. {pgtask-0.1.3 → pgtask-0.3.0}/crates/pgtask-postgres/tests/results.rs +0 -0
  69. {pgtask-0.1.3 → pgtask-0.3.0}/crates/pgtask-postgres/tests/signals.rs +0 -0
  70. {pgtask-0.1.3 → pgtask-0.3.0}/crates/pgtask-postgres/tests/sql_surface_baseline.txt +0 -0
  71. {pgtask-0.1.3 → pgtask-0.3.0}/crates/pgtask-worker/src/health.rs +0 -0
  72. {pgtask-0.1.3 → pgtask-0.3.0}/crates/pgtask-worker/src/registry.rs +0 -0
  73. {pgtask-0.1.3 → pgtask-0.3.0}/crates/pgtask-worker/tests/durable_restarts.rs +0 -0
  74. {pgtask-0.1.3 → pgtask-0.3.0}/pyproject.toml +0 -0
  75. {pgtask-0.1.3 → pgtask-0.3.0}/python/pgtask/py.typed +0 -0
  76. {pgtask-0.1.3 → pgtask-0.3.0}/sdks/python/tests/__init__.py +0 -0
  77. {pgtask-0.1.3 → pgtask-0.3.0}/sdks/python/uv.lock +0 -0
@@ -155,6 +155,21 @@ version = "0.22.1"
155
155
  source = "registry+https://github.com/rust-lang/crates.io-index"
156
156
  checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
157
157
 
158
+ [[package]]
159
+ name = "bit-set"
160
+ version = "0.8.0"
161
+ source = "registry+https://github.com/rust-lang/crates.io-index"
162
+ checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
163
+ dependencies = [
164
+ "bit-vec",
165
+ ]
166
+
167
+ [[package]]
168
+ name = "bit-vec"
169
+ version = "0.8.0"
170
+ source = "registry+https://github.com/rust-lang/crates.io-index"
171
+ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
172
+
158
173
  [[package]]
159
174
  name = "bitflags"
160
175
  version = "2.13.1"
@@ -480,6 +495,12 @@ dependencies = [
480
495
  "pin-project-lite",
481
496
  ]
482
497
 
498
+ [[package]]
499
+ name = "fastrand"
500
+ version = "2.5.0"
501
+ source = "registry+https://github.com/rust-lang/crates.io-index"
502
+ checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
503
+
483
504
  [[package]]
484
505
  name = "find-msvc-tools"
485
506
  version = "0.1.11"
@@ -497,6 +518,12 @@ dependencies = [
497
518
  "spin",
498
519
  ]
499
520
 
521
+ [[package]]
522
+ name = "fnv"
523
+ version = "1.0.7"
524
+ source = "registry+https://github.com/rust-lang/crates.io-index"
525
+ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
526
+
500
527
  [[package]]
501
528
  name = "foldhash"
502
529
  version = "0.2.0"
@@ -990,6 +1017,12 @@ dependencies = [
990
1017
  "vcpkg",
991
1018
  ]
992
1019
 
1020
+ [[package]]
1021
+ name = "linux-raw-sys"
1022
+ version = "0.12.1"
1023
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1024
+ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
1025
+
993
1026
  [[package]]
994
1027
  name = "litemap"
995
1028
  version = "0.8.3"
@@ -1162,18 +1195,20 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
1162
1195
 
1163
1196
  [[package]]
1164
1197
  name = "pgtask"
1165
- version = "0.1.3"
1198
+ version = "0.3.0"
1166
1199
  dependencies = [
1167
1200
  "pgtask-core",
1168
1201
  "pgtask-otel",
1169
1202
  "pgtask-postgres",
1170
1203
  "pgtask-worker",
1171
1204
  "serde_json",
1205
+ "tokio",
1206
+ "tokio-util",
1172
1207
  ]
1173
1208
 
1174
1209
  [[package]]
1175
1210
  name = "pgtask-bench"
1176
- version = "0.1.3"
1211
+ version = "0.3.0"
1177
1212
  dependencies = [
1178
1213
  "chrono",
1179
1214
  "pgtask",
@@ -1186,7 +1221,7 @@ dependencies = [
1186
1221
 
1187
1222
  [[package]]
1188
1223
  name = "pgtask-cli"
1189
- version = "0.1.3"
1224
+ version = "0.3.0"
1190
1225
  dependencies = [
1191
1226
  "clap",
1192
1227
  "pgtask",
@@ -1196,10 +1231,11 @@ dependencies = [
1196
1231
 
1197
1232
  [[package]]
1198
1233
  name = "pgtask-core"
1199
- version = "0.1.3"
1234
+ version = "0.3.0"
1200
1235
  dependencies = [
1201
1236
  "chrono",
1202
1237
  "cron",
1238
+ "proptest",
1203
1239
  "rand 0.9.5",
1204
1240
  "serde",
1205
1241
  "serde_json",
@@ -1209,7 +1245,7 @@ dependencies = [
1209
1245
 
1210
1246
  [[package]]
1211
1247
  name = "pgtask-otel"
1212
- version = "0.1.3"
1248
+ version = "0.3.0"
1213
1249
  dependencies = [
1214
1250
  "opentelemetry",
1215
1251
  "opentelemetry_sdk",
@@ -1220,11 +1256,13 @@ dependencies = [
1220
1256
 
1221
1257
  [[package]]
1222
1258
  name = "pgtask-postgres"
1223
- version = "0.1.3"
1259
+ version = "0.3.0"
1224
1260
  dependencies = [
1225
1261
  "chrono",
1226
1262
  "pgtask-core",
1227
1263
  "pgtask-otel",
1264
+ "proptest",
1265
+ "proptest-state-machine",
1228
1266
  "serde_json",
1229
1267
  "sqlx",
1230
1268
  "thiserror",
@@ -1235,7 +1273,7 @@ dependencies = [
1235
1273
 
1236
1274
  [[package]]
1237
1275
  name = "pgtask-python"
1238
- version = "0.1.3"
1276
+ version = "0.3.0"
1239
1277
  dependencies = [
1240
1278
  "chrono",
1241
1279
  "pgtask",
@@ -1252,7 +1290,7 @@ dependencies = [
1252
1290
 
1253
1291
  [[package]]
1254
1292
  name = "pgtask-web"
1255
- version = "0.1.3"
1293
+ version = "0.3.0"
1256
1294
  dependencies = [
1257
1295
  "axum",
1258
1296
  "chrono",
@@ -1271,7 +1309,7 @@ dependencies = [
1271
1309
 
1272
1310
  [[package]]
1273
1311
  name = "pgtask-worker"
1274
- version = "0.1.3"
1312
+ version = "0.3.0"
1275
1313
  dependencies = [
1276
1314
  "axum",
1277
1315
  "chrono",
@@ -1375,6 +1413,34 @@ dependencies = [
1375
1413
  "unicode-ident",
1376
1414
  ]
1377
1415
 
1416
+ [[package]]
1417
+ name = "proptest"
1418
+ version = "1.11.0"
1419
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1420
+ checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744"
1421
+ dependencies = [
1422
+ "bit-set",
1423
+ "bit-vec",
1424
+ "bitflags",
1425
+ "num-traits",
1426
+ "rand 0.9.5",
1427
+ "rand_chacha",
1428
+ "rand_xorshift",
1429
+ "regex-syntax",
1430
+ "rusty-fork",
1431
+ "tempfile",
1432
+ "unarray",
1433
+ ]
1434
+
1435
+ [[package]]
1436
+ name = "proptest-state-machine"
1437
+ version = "0.3.1"
1438
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1439
+ checksum = "e943d140e09d07740fb496487c51fb8eb31c70389ac4a2e9dcd8a0d9fdf228d4"
1440
+ dependencies = [
1441
+ "proptest",
1442
+ ]
1443
+
1378
1444
  [[package]]
1379
1445
  name = "pyo3"
1380
1446
  version = "0.29.2"
@@ -1456,6 +1522,12 @@ dependencies = [
1456
1522
  "serde",
1457
1523
  ]
1458
1524
 
1525
+ [[package]]
1526
+ name = "quick-error"
1527
+ version = "1.2.3"
1528
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1529
+ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
1530
+
1459
1531
  [[package]]
1460
1532
  name = "quote"
1461
1533
  version = "1.0.47"
@@ -1538,6 +1610,15 @@ version = "0.10.1"
1538
1610
  source = "registry+https://github.com/rust-lang/crates.io-index"
1539
1611
  checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
1540
1612
 
1613
+ [[package]]
1614
+ name = "rand_xorshift"
1615
+ version = "0.4.0"
1616
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1617
+ checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a"
1618
+ dependencies = [
1619
+ "rand_core 0.9.5",
1620
+ ]
1621
+
1541
1622
  [[package]]
1542
1623
  name = "redox_syscall"
1543
1624
  version = "0.5.18"
@@ -1578,6 +1659,19 @@ dependencies = [
1578
1659
  "windows-sys 0.52.0",
1579
1660
  ]
1580
1661
 
1662
+ [[package]]
1663
+ name = "rustix"
1664
+ version = "1.1.4"
1665
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1666
+ checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
1667
+ dependencies = [
1668
+ "bitflags",
1669
+ "errno",
1670
+ "libc",
1671
+ "linux-raw-sys",
1672
+ "windows-sys 0.61.2",
1673
+ ]
1674
+
1581
1675
  [[package]]
1582
1676
  name = "rustls"
1583
1677
  version = "0.23.43"
@@ -1630,6 +1724,18 @@ version = "1.0.23"
1630
1724
  source = "registry+https://github.com/rust-lang/crates.io-index"
1631
1725
  checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
1632
1726
 
1727
+ [[package]]
1728
+ name = "rusty-fork"
1729
+ version = "0.3.1"
1730
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1731
+ checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2"
1732
+ dependencies = [
1733
+ "fnv",
1734
+ "quick-error",
1735
+ "tempfile",
1736
+ "wait-timeout",
1737
+ ]
1738
+
1633
1739
  [[package]]
1634
1740
  name = "ryu"
1635
1741
  version = "1.0.23"
@@ -2093,6 +2199,19 @@ version = "0.13.5"
2093
2199
  source = "registry+https://github.com/rust-lang/crates.io-index"
2094
2200
  checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
2095
2201
 
2202
+ [[package]]
2203
+ name = "tempfile"
2204
+ version = "3.27.0"
2205
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2206
+ checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
2207
+ dependencies = [
2208
+ "fastrand",
2209
+ "getrandom 0.4.3",
2210
+ "once_cell",
2211
+ "rustix",
2212
+ "windows-sys 0.61.2",
2213
+ ]
2214
+
2096
2215
  [[package]]
2097
2216
  name = "thiserror"
2098
2217
  version = "2.0.20"
@@ -2310,6 +2429,12 @@ version = "1.20.1"
2310
2429
  source = "registry+https://github.com/rust-lang/crates.io-index"
2311
2430
  checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
2312
2431
 
2432
+ [[package]]
2433
+ name = "unarray"
2434
+ version = "0.1.4"
2435
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2436
+ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
2437
+
2313
2438
  [[package]]
2314
2439
  name = "unicode-bidi"
2315
2440
  version = "0.3.18"
@@ -2397,6 +2522,15 @@ version = "0.9.5"
2397
2522
  source = "registry+https://github.com/rust-lang/crates.io-index"
2398
2523
  checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
2399
2524
 
2525
+ [[package]]
2526
+ name = "wait-timeout"
2527
+ version = "0.2.1"
2528
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2529
+ checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11"
2530
+ dependencies = [
2531
+ "libc",
2532
+ ]
2533
+
2400
2534
  [[package]]
2401
2535
  name = "wasi"
2402
2536
  version = "0.11.1+wasi-snapshot-preview1"
@@ -7,7 +7,7 @@ edition = "2024"
7
7
  license = "MIT"
8
8
  repository = "https://github.com/Kludex/pgtask"
9
9
  rust-version = "1.94"
10
- version = "0.1.3"
10
+ version = "0.3.0"
11
11
 
12
12
  [workspace.dependencies]
13
13
  axum = "0.8.9"
@@ -18,6 +18,8 @@ futures = "0.3.34"
18
18
  html-escape = "0.2.15"
19
19
  opentelemetry = "0.32.0"
20
20
  opentelemetry_sdk = "0.32.1"
21
+ proptest = "1"
22
+ proptest-state-machine = "0.3"
21
23
  pyo3 = "0.29.2"
22
24
  pyo3-async-runtimes = "0.29.0"
23
25
  pythonize = "0.29.0"
pgtask-0.3.0/PKG-INFO ADDED
@@ -0,0 +1,107 @@
1
+ Metadata-Version: 2.4
2
+ Name: pgtask
3
+ Version: 0.3.0
4
+ Classifier: Development Status :: 2 - Pre-Alpha
5
+ Classifier: Programming Language :: Python :: 3 :: Only
6
+ Classifier: Programming Language :: Python :: 3.10
7
+ Classifier: Programming Language :: Python :: 3.11
8
+ Classifier: Programming Language :: Python :: 3.12
9
+ Classifier: Programming Language :: Python :: 3.13
10
+ Classifier: Programming Language :: Python :: 3.14
11
+ Classifier: Programming Language :: Rust
12
+ Requires-Dist: opentelemetry-api>=1.30
13
+ Requires-Dist: psycopg>=3.2
14
+ Summary: PostgreSQL-native durable tasks and workflows
15
+ License-Expression: MIT
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
18
+
19
+ # pgtask
20
+
21
+ Durable tasks and workflows that live entirely in PostgreSQL.
22
+
23
+ There is no broker, no coordinator, and no extension to install. If your database is up, your queue is up.
24
+
25
+ > [!WARNING]
26
+ > `pgtask` is under active development. It is not ready for production use.
27
+
28
+ ## Installation
29
+
30
+ ```console
31
+ pip install pgtask
32
+ ```
33
+
34
+ ## Define a task and run a worker
35
+
36
+ A registry maps a task name to the function that runs it. A worker claims work for that queue, one lease
37
+ at a time, so a worker that dies hands its tasks back instead of taking them with it.
38
+
39
+ ```python
40
+ from __future__ import annotations
41
+
42
+ import asyncio
43
+ import os
44
+
45
+ from pgtask import Client, Task, TaskRegistry, Worker
46
+
47
+ tasks = TaskRegistry(queue_name="reports")
48
+
49
+
50
+ @tasks.task("reports.render")
51
+ async def render(task: Task, payload: dict) -> dict:
52
+ return {"report_id": payload["report_id"], "attempt": task.attempt}
53
+
54
+
55
+ async def main() -> None:
56
+ database_url = os.environ["PGTASK_DATABASE_URL"]
57
+ client = await Client.connect(database_url)
58
+ await client.migrate()
59
+ await Worker(database_url, tasks).run()
60
+
61
+
62
+ asyncio.run(main())
63
+ ```
64
+
65
+ Delivery is at least once, so write handlers that are safe to run again.
66
+
67
+ ## Enqueue a task
68
+
69
+ ```python
70
+ from __future__ import annotations
71
+
72
+ import asyncio
73
+ import os
74
+
75
+ from pgtask import Client, EnqueueRequest
76
+
77
+
78
+ async def main() -> None:
79
+ client = await Client.connect(os.environ["PGTASK_DATABASE_URL"])
80
+ handle = await client.enqueue(
81
+ EnqueueRequest("reports.render", {"report_id": "report-123"}, queue_name="reports")
82
+ )
83
+ print(await handle.result(timeout=30.0))
84
+
85
+
86
+ asyncio.run(main())
87
+ ```
88
+
89
+ ## Enqueue inside your transaction
90
+
91
+ This is the reason to keep the queue in the database. Pass your own connection and the task commits with
92
+ your data or not at all. Roll back and the task never existed, which is what an outbox table is usually
93
+ for.
94
+
95
+ ```python
96
+ async with connection.transaction():
97
+ await connection.execute("INSERT INTO reports (id) VALUES (%s)", ("report-123",))
98
+ await Client.enqueue_on(connection, request)
99
+ ```
100
+
101
+ `connection` is your `psycopg` connection, not one of ours.
102
+
103
+ ## Documentation
104
+
105
+ [kludex.github.io/pgtask](https://kludex.github.io/pgtask/) covers workers, durable execution, signals,
106
+ cancellation, scheduling, and OpenTelemetry propagation.
107
+
pgtask-0.3.0/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # pgtask
2
+
3
+ Durable tasks and workflows that live entirely in PostgreSQL.
4
+
5
+ There is no broker, no coordinator, and no extension to install. If your database is up, your queue is up.
6
+
7
+ > [!WARNING]
8
+ > `pgtask` is under active development. It is not ready for production use.
9
+
10
+ ## Installation
11
+
12
+ ```console
13
+ pip install pgtask
14
+ ```
15
+
16
+ ## Define a task and run a worker
17
+
18
+ A registry maps a task name to the function that runs it. A worker claims work for that queue, one lease
19
+ at a time, so a worker that dies hands its tasks back instead of taking them with it.
20
+
21
+ ```python
22
+ from __future__ import annotations
23
+
24
+ import asyncio
25
+ import os
26
+
27
+ from pgtask import Client, Task, TaskRegistry, Worker
28
+
29
+ tasks = TaskRegistry(queue_name="reports")
30
+
31
+
32
+ @tasks.task("reports.render")
33
+ async def render(task: Task, payload: dict) -> dict:
34
+ return {"report_id": payload["report_id"], "attempt": task.attempt}
35
+
36
+
37
+ async def main() -> None:
38
+ database_url = os.environ["PGTASK_DATABASE_URL"]
39
+ client = await Client.connect(database_url)
40
+ await client.migrate()
41
+ await Worker(database_url, tasks).run()
42
+
43
+
44
+ asyncio.run(main())
45
+ ```
46
+
47
+ Delivery is at least once, so write handlers that are safe to run again.
48
+
49
+ ## Enqueue a task
50
+
51
+ ```python
52
+ from __future__ import annotations
53
+
54
+ import asyncio
55
+ import os
56
+
57
+ from pgtask import Client, EnqueueRequest
58
+
59
+
60
+ async def main() -> None:
61
+ client = await Client.connect(os.environ["PGTASK_DATABASE_URL"])
62
+ handle = await client.enqueue(
63
+ EnqueueRequest("reports.render", {"report_id": "report-123"}, queue_name="reports")
64
+ )
65
+ print(await handle.result(timeout=30.0))
66
+
67
+
68
+ asyncio.run(main())
69
+ ```
70
+
71
+ ## Enqueue inside your transaction
72
+
73
+ This is the reason to keep the queue in the database. Pass your own connection and the task commits with
74
+ your data or not at all. Roll back and the task never existed, which is what an outbox table is usually
75
+ for.
76
+
77
+ ```python
78
+ async with connection.transaction():
79
+ await connection.execute("INSERT INTO reports (id) VALUES (%s)", ("report-123",))
80
+ await Client.enqueue_on(connection, request)
81
+ ```
82
+
83
+ `connection` is your `psycopg` connection, not one of ours.
84
+
85
+ ## Documentation
86
+
87
+ [kludex.github.io/pgtask](https://kludex.github.io/pgtask/) covers workers, durable execution, signals,
88
+ cancellation, scheduling, and OpenTelemetry propagation.
@@ -0,0 +1,23 @@
1
+ [package]
2
+ name = "pgtask"
3
+ description = "PostgreSQL-native durable tasks and workflows"
4
+ edition.workspace = true
5
+ license.workspace = true
6
+ repository.workspace = true
7
+ rust-version.workspace = true
8
+ version.workspace = true
9
+ readme = "README.md"
10
+
11
+ [dependencies]
12
+ pgtask-core = { path = "../pgtask-core", version = "0.3.0" }
13
+ pgtask-otel = { path = "../pgtask-otel", version = "0.3.0" }
14
+ pgtask-postgres = { path = "../pgtask-postgres", version = "0.3.0" }
15
+ pgtask-worker = { path = "../pgtask-worker", version = "0.3.0" }
16
+
17
+ [dev-dependencies]
18
+ serde_json.workspace = true
19
+ tokio.workspace = true
20
+ tokio-util.workspace = true
21
+
22
+ [lints]
23
+ workspace = true
@@ -0,0 +1,100 @@
1
+ # pgtask
2
+
3
+ Durable tasks and workflows that live entirely in PostgreSQL.
4
+
5
+ There is no broker, no coordinator, and no extension to install. If your database is up, your queue is up.
6
+
7
+ > [!WARNING]
8
+ > `pgtask` is under active development. It is not ready for production use.
9
+
10
+ ## Installation
11
+
12
+ ```console
13
+ cargo add pgtask tokio tokio-util serde_json
14
+ ```
15
+
16
+ ## Run a worker
17
+
18
+ A worker declares which tasks it can handle and claims work from a queue. Every claim takes a lease, so
19
+ when a worker dies its tasks return to the queue instead of disappearing with it.
20
+
21
+ ```rust,no_run
22
+ use std::error::Error;
23
+
24
+ use pgtask::{
25
+ core::{HandlerVersion, QueueName, RetryPolicy, TaskName},
26
+ postgres::Store,
27
+ worker::{HandlerRegistry, Worker, WorkerConfig},
28
+ };
29
+ use serde_json::json;
30
+ use tokio_util::sync::CancellationToken;
31
+
32
+ #[tokio::main]
33
+ async fn main() -> Result<(), Box<dyn Error>> {
34
+ let store = Store::connect(&std::env::var("PGTASK_DATABASE_URL")?).await?;
35
+ store.migrate().await?;
36
+
37
+ let mut registry = HandlerRegistry::new();
38
+ registry.register(
39
+ TaskName::new("reports.render")?,
40
+ HandlerVersion::default(),
41
+ RetryPolicy::Never,
42
+ |task| async move { Ok(json!({ "attempt": task.attempt })) },
43
+ );
44
+
45
+ let config = WorkerConfig::new(QueueName::new("reports")?);
46
+ Worker::new(store, registry, config)?.run(CancellationToken::new()).await?;
47
+ Ok(())
48
+ }
49
+ ```
50
+
51
+ Cancel the token to shut down. The worker stops claiming, finishes what it is already running, and lets
52
+ the rest expire back to the queue.
53
+
54
+ ## Enqueue inside your transaction
55
+
56
+ This is the reason to keep the queue in the database. You pass your own transaction, so the task commits
57
+ with your data or not at all. Roll back and the task never existed.
58
+
59
+ ```rust,no_run
60
+ use std::error::Error;
61
+
62
+ use pgtask::{
63
+ core::{EnqueueRequest, QueueName, TaskName},
64
+ postgres::Store,
65
+ };
66
+ use serde_json::json;
67
+
68
+ #[tokio::main]
69
+ async fn main() -> Result<(), Box<dyn Error>> {
70
+ let store = Store::connect(&std::env::var("PGTASK_DATABASE_URL")?).await?;
71
+
72
+ let mut request = EnqueueRequest::new(TaskName::new("reports.render")?, json!({"report_id": "report-123"}));
73
+ request.queue_name = QueueName::new("reports")?;
74
+
75
+ let mut transaction = store.pool().begin().await?;
76
+ // Your own writes belong here, in the same transaction.
77
+ let result = Store::enqueue_on(&mut transaction, &request).await?;
78
+ transaction.commit().await?;
79
+
80
+ println!("enqueued {}", result.task_id);
81
+ Ok(())
82
+ }
83
+ ```
84
+
85
+ Without a transaction of your own, `store.enqueue(&request)` does the same on its own connection.
86
+
87
+ ## Crates
88
+
89
+ This crate re-exports the others. Depend on it unless you need one layer on its own.
90
+
91
+ | Crate | What it holds |
92
+ | --- | --- |
93
+ | `pgtask-core` | Task, queue, schedule, and retry types shared by every layer |
94
+ | `pgtask-postgres` | The storage protocol: migrate, enqueue, claim, complete |
95
+ | `pgtask-worker` | The worker runtime, scheduler, and retention loop |
96
+ | `pgtask-otel` | OpenTelemetry spans, metrics, and trace propagation |
97
+
98
+ ## Documentation
99
+
100
+ [kludex.github.io/pgtask](https://kludex.github.io/pgtask/)
@@ -1,4 +1,4 @@
1
- #![doc = "PostgreSQL-native durable tasks and workflows."]
1
+ #![doc = include_str!("../README.md")]
2
2
 
3
3
  pub use pgtask_core as core;
4
4
  pub use pgtask_otel as otel;
@@ -6,6 +6,7 @@ license.workspace = true
6
6
  repository.workspace = true
7
7
  rust-version.workspace = true
8
8
  version.workspace = true
9
+ readme = "README.md"
9
10
 
10
11
  [dependencies]
11
12
  chrono.workspace = true
@@ -16,5 +17,8 @@ serde_json.workspace = true
16
17
  thiserror.workspace = true
17
18
  uuid.workspace = true
18
19
 
20
+ [dev-dependencies]
21
+ proptest.workspace = true
22
+
19
23
  [lints]
20
24
  workspace = true
@@ -0,0 +1,13 @@
1
+ # pgtask-core
2
+
3
+ The types every layer of `pgtask` agrees on: tasks, queues, schedules, retry policies, and the states a
4
+ task can be in.
5
+
6
+ There is no database access and no runtime here. The crate exists so the storage layer, the worker, and
7
+ the telemetry layer share one definition of a task instead of three that drift apart.
8
+
9
+ You probably want [`pgtask`](https://crates.io/crates/pgtask), which re-exports this as `pgtask::core`.
10
+
11
+ ## Documentation
12
+
13
+ [kludex.github.io/pgtask](https://kludex.github.io/pgtask/)
@@ -20,7 +20,7 @@ pub use task::{
20
20
  pub const STORAGE_PROTOCOL_MIN_VERSION: u32 = 1;
21
21
 
22
22
  /// Newest storage protocol understood by this release.
23
- pub const STORAGE_PROTOCOL_MAX_VERSION: u32 = 1;
23
+ pub const STORAGE_PROTOCOL_MAX_VERSION: u32 = 2;
24
24
 
25
25
  /// Current storage protocol emitted by this release.
26
26
  pub const STORAGE_PROTOCOL_VERSION: u32 = STORAGE_PROTOCOL_MAX_VERSION;