taskito 0.4.0__tar.gz → 0.6.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 (173) hide show
  1. {taskito-0.4.0 → taskito-0.6.0}/Cargo.lock +23 -10
  2. {taskito-0.4.0 → taskito-0.6.0}/PKG-INFO +6 -3
  3. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/Cargo.toml +1 -1
  4. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/scheduler/maintenance.rs +2 -1
  5. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/scheduler/mod.rs +47 -0
  6. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/scheduler/poller.rs +33 -0
  7. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/scheduler/result_handler.rs +31 -4
  8. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/diesel_common/jobs.rs +13 -0
  9. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/mod.rs +40 -7
  10. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/models.rs +6 -0
  11. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/postgres/mod.rs +14 -0
  12. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/postgres/workers.rs +17 -3
  13. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/redis_backend/jobs.rs +18 -0
  14. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/redis_backend/workers.rs +26 -10
  15. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/schema.rs +3 -0
  16. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/sqlite/mod.rs +11 -0
  17. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/sqlite/tests.rs +25 -0
  18. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/sqlite/workers.rs +17 -3
  19. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/traits.rs +14 -2
  20. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/tests/rust/storage_tests.rs +11 -2
  21. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-python/Cargo.toml +4 -1
  22. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-python/src/async_worker.rs +1 -0
  23. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-python/src/lib.rs +3 -0
  24. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-python/src/py_config.rs +9 -1
  25. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-python/src/py_queue/inspection.rs +3 -0
  26. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-python/src/py_queue/mod.rs +11 -1
  27. taskito-0.6.0/crates/taskito-python/src/py_queue/worker.rs +493 -0
  28. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-python/src/py_worker.rs +8 -2
  29. {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/__init__.py +24 -2
  30. {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/_taskito.pyi +49 -0
  31. {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/app.py +581 -83
  32. taskito-0.6.0/py_src/taskito/async_support/__init__.py +25 -0
  33. taskito-0.6.0/py_src/taskito/async_support/context.py +32 -0
  34. taskito-0.6.0/py_src/taskito/async_support/executor.py +218 -0
  35. taskito-0.6.0/py_src/taskito/async_support/helpers.py +17 -0
  36. taskito-0.6.0/py_src/taskito/async_support/locks.py +95 -0
  37. taskito-0.6.0/py_src/taskito/async_support/mixins.py +183 -0
  38. taskito-0.6.0/py_src/taskito/async_support/result.py +61 -0
  39. {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/cli.py +111 -0
  40. {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/context.py +10 -3
  41. {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/contrib/django/admin.py +24 -5
  42. {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/contrib/django/apps.py +8 -2
  43. {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/contrib/django/management/commands/taskito_dashboard.py +14 -2
  44. {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/contrib/django/management/commands/taskito_info.py +7 -3
  45. taskito-0.6.0/py_src/taskito/contrib/fastapi.py +425 -0
  46. {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/contrib/flask.py +27 -10
  47. taskito-0.6.0/py_src/taskito/contrib/otel.py +130 -0
  48. taskito-0.6.0/py_src/taskito/contrib/prometheus.py +366 -0
  49. taskito-0.6.0/py_src/taskito/contrib/sentry.py +103 -0
  50. {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/dashboard.py +82 -29
  51. {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/events.py +5 -1
  52. {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/exceptions.py +28 -0
  53. {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/health.py +16 -0
  54. taskito-0.6.0/py_src/taskito/inject.py +42 -0
  55. taskito-0.6.0/py_src/taskito/interception/__init__.py +25 -0
  56. taskito-0.6.0/py_src/taskito/interception/built_in.py +363 -0
  57. taskito-0.6.0/py_src/taskito/interception/converters.py +243 -0
  58. taskito-0.6.0/py_src/taskito/interception/errors.py +45 -0
  59. taskito-0.6.0/py_src/taskito/interception/interceptor.py +207 -0
  60. taskito-0.6.0/py_src/taskito/interception/metrics.py +46 -0
  61. taskito-0.6.0/py_src/taskito/interception/reconstruct.py +69 -0
  62. taskito-0.6.0/py_src/taskito/interception/registry.py +86 -0
  63. taskito-0.6.0/py_src/taskito/interception/strategy.py +13 -0
  64. taskito-0.6.0/py_src/taskito/interception/walker.py +266 -0
  65. {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/locks.py +6 -80
  66. {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/middleware.py +16 -0
  67. {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/mixins.py +11 -95
  68. taskito-0.6.0/py_src/taskito/proxies/__init__.py +14 -0
  69. taskito-0.6.0/py_src/taskito/proxies/built_in.py +77 -0
  70. taskito-0.6.0/py_src/taskito/proxies/handler.py +30 -0
  71. taskito-0.6.0/py_src/taskito/proxies/handlers/__init__.py +9 -0
  72. taskito-0.6.0/py_src/taskito/proxies/handlers/boto3_client.py +54 -0
  73. taskito-0.6.0/py_src/taskito/proxies/handlers/file.py +78 -0
  74. taskito-0.6.0/py_src/taskito/proxies/handlers/gcs_client.py +75 -0
  75. taskito-0.6.0/py_src/taskito/proxies/handlers/httpx_client.py +61 -0
  76. taskito-0.6.0/py_src/taskito/proxies/handlers/logger.py +32 -0
  77. taskito-0.6.0/py_src/taskito/proxies/handlers/requests_session.py +49 -0
  78. taskito-0.6.0/py_src/taskito/proxies/metrics.py +86 -0
  79. taskito-0.6.0/py_src/taskito/proxies/no_proxy.py +22 -0
  80. taskito-0.6.0/py_src/taskito/proxies/reconstruct.py +203 -0
  81. taskito-0.6.0/py_src/taskito/proxies/registry.py +32 -0
  82. taskito-0.6.0/py_src/taskito/proxies/schema.py +50 -0
  83. taskito-0.6.0/py_src/taskito/proxies/signing.py +35 -0
  84. taskito-0.6.0/py_src/taskito/resources/__init__.py +22 -0
  85. taskito-0.6.0/py_src/taskito/resources/definition.py +39 -0
  86. taskito-0.6.0/py_src/taskito/resources/frozen.py +40 -0
  87. taskito-0.6.0/py_src/taskito/resources/graph.py +76 -0
  88. taskito-0.6.0/py_src/taskito/resources/health.py +105 -0
  89. taskito-0.6.0/py_src/taskito/resources/pool.py +144 -0
  90. taskito-0.6.0/py_src/taskito/resources/runtime.py +266 -0
  91. taskito-0.6.0/py_src/taskito/resources/thread_local.py +61 -0
  92. taskito-0.6.0/py_src/taskito/resources/toml_config.py +117 -0
  93. {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/result.py +3 -47
  94. taskito-0.6.0/py_src/taskito/scaler.py +121 -0
  95. {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/task.py +21 -0
  96. taskito-0.6.0/py_src/taskito/templates/dashboard.css +368 -0
  97. taskito-0.6.0/py_src/taskito/templates/dashboard.html +40 -0
  98. taskito-0.6.0/py_src/taskito/templates/js/actions.js +34 -0
  99. taskito-0.6.0/py_src/taskito/templates/js/app.js +93 -0
  100. taskito-0.6.0/py_src/taskito/templates/js/chart.js +150 -0
  101. taskito-0.6.0/py_src/taskito/templates/js/components.js +58 -0
  102. taskito-0.6.0/py_src/taskito/templates/js/utils.js +19 -0
  103. taskito-0.6.0/py_src/taskito/templates/js/views.js +348 -0
  104. {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/testing.py +73 -1
  105. {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/webhooks.py +20 -5
  106. {taskito-0.4.0 → taskito-0.6.0}/pyproject.toml +5 -4
  107. taskito-0.4.0/crates/taskito-python/src/py_queue/worker.rs +0 -255
  108. taskito-0.4.0/py_src/taskito/contrib/fastapi.py +0 -317
  109. taskito-0.4.0/py_src/taskito/contrib/otel.py +0 -93
  110. taskito-0.4.0/py_src/taskito/contrib/prometheus.py +0 -202
  111. taskito-0.4.0/py_src/taskito/contrib/sentry.py +0 -68
  112. taskito-0.4.0/py_src/taskito/templates/dashboard.html +0 -1226
  113. {taskito-0.4.0 → taskito-0.6.0}/Cargo.toml +0 -0
  114. {taskito-0.4.0 → taskito-0.6.0}/LICENSE +0 -0
  115. {taskito-0.4.0 → taskito-0.6.0}/README.md +0 -0
  116. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/error.rs +0 -0
  117. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/job.rs +0 -0
  118. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/lib.rs +0 -0
  119. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/periodic.rs +0 -0
  120. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/resilience/circuit_breaker.rs +0 -0
  121. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/resilience/dlq.rs +0 -0
  122. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/resilience/mod.rs +0 -0
  123. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/resilience/rate_limiter.rs +0 -0
  124. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/resilience/retry.rs +0 -0
  125. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/diesel_common/locks.rs +0 -0
  126. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/diesel_common/logs.rs +0 -0
  127. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/diesel_common/metrics.rs +0 -0
  128. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/diesel_common/mod.rs +0 -0
  129. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/postgres/archival.rs +0 -0
  130. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/postgres/circuit_breakers.rs +0 -0
  131. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/postgres/dead_letter.rs +0 -0
  132. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/postgres/jobs.rs +0 -0
  133. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/postgres/locks.rs +0 -0
  134. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/postgres/logs.rs +0 -0
  135. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/postgres/metrics.rs +0 -0
  136. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/postgres/periodic.rs +0 -0
  137. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/postgres/queue_state.rs +0 -0
  138. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/postgres/rate_limits.rs +0 -0
  139. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/postgres/trait_impl.rs +0 -0
  140. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/redis_backend/archival.rs +0 -0
  141. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/redis_backend/circuit_breakers.rs +0 -0
  142. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/redis_backend/dead_letter.rs +0 -0
  143. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/redis_backend/locks.rs +0 -0
  144. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/redis_backend/logs.rs +0 -0
  145. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/redis_backend/metrics.rs +0 -0
  146. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/redis_backend/mod.rs +0 -0
  147. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/redis_backend/periodic.rs +0 -0
  148. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/redis_backend/queue_state.rs +0 -0
  149. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/redis_backend/rate_limits.rs +0 -0
  150. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/redis_backend/trait_impl.rs +0 -0
  151. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/sqlite/archival.rs +0 -0
  152. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/sqlite/circuit_breakers.rs +0 -0
  153. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/sqlite/dead_letter.rs +0 -0
  154. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/sqlite/jobs.rs +0 -0
  155. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/sqlite/locks.rs +0 -0
  156. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/sqlite/logs.rs +0 -0
  157. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/sqlite/metrics.rs +0 -0
  158. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/sqlite/periodic.rs +0 -0
  159. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/sqlite/queue_state.rs +0 -0
  160. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/sqlite/rate_limits.rs +0 -0
  161. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/storage/sqlite/trait_impl.rs +0 -0
  162. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/src/worker.rs +0 -0
  163. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-core/tests/rust.rs +0 -0
  164. {taskito-0.4.0 → taskito-0.6.0}/crates/taskito-python/src/py_job.rs +0 -0
  165. {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/canvas.py +0 -0
  166. {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/contrib/__init__.py +0 -0
  167. {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/contrib/django/__init__.py +0 -0
  168. {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/contrib/django/management/__init__.py +0 -0
  169. {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/contrib/django/management/commands/__init__.py +0 -0
  170. {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/contrib/django/management/commands/taskito_worker.py +0 -0
  171. {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/contrib/django/settings.py +0 -0
  172. {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/py.typed +0 -0
  173. {taskito-0.4.0 → taskito-0.6.0}/py_src/taskito/serializers.py +0 -0
@@ -69,9 +69,9 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
69
69
 
70
70
  [[package]]
71
71
  name = "cc"
72
- version = "1.2.56"
72
+ version = "1.2.57"
73
73
  source = "registry+https://github.com/rust-lang/crates.io-index"
74
- checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2"
74
+ checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423"
75
75
  dependencies = [
76
76
  "find-msvc-tools",
77
77
  "shlex",
@@ -194,9 +194,9 @@ dependencies = [
194
194
 
195
195
  [[package]]
196
196
  name = "diesel"
197
- version = "2.3.6"
197
+ version = "2.3.7"
198
198
  source = "registry+https://github.com/rust-lang/crates.io-index"
199
- checksum = "d9b6c2fc184a6fb6ebcf5f9a5e3bbfa84d8fd268cdfcce4ed508979a6259494d"
199
+ checksum = "f4ae09a41a4b89f94ec1e053623da8340d996bc32c6517d325a9daad9b239358"
200
200
  dependencies = [
201
201
  "bitflags",
202
202
  "byteorder",
@@ -693,9 +693,9 @@ dependencies = [
693
693
 
694
694
  [[package]]
695
695
  name = "once_cell"
696
- version = "1.21.3"
696
+ version = "1.21.4"
697
697
  source = "registry+https://github.com/rust-lang/crates.io-index"
698
- checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
698
+ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
699
699
 
700
700
  [[package]]
701
701
  name = "openssl-src"
@@ -708,9 +708,9 @@ dependencies = [
708
708
 
709
709
  [[package]]
710
710
  name = "openssl-sys"
711
- version = "0.9.111"
711
+ version = "0.9.112"
712
712
  source = "registry+https://github.com/rust-lang/crates.io-index"
713
- checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321"
713
+ checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb"
714
714
  dependencies = [
715
715
  "cc",
716
716
  "libc",
@@ -1199,9 +1199,20 @@ version = "0.12.16"
1199
1199
  source = "registry+https://github.com/rust-lang/crates.io-index"
1200
1200
  checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
1201
1201
 
1202
+ [[package]]
1203
+ name = "taskito-async"
1204
+ version = "0.6.0"
1205
+ dependencies = [
1206
+ "async-trait",
1207
+ "crossbeam-channel",
1208
+ "pyo3",
1209
+ "taskito-core",
1210
+ "tokio",
1211
+ ]
1212
+
1202
1213
  [[package]]
1203
1214
  name = "taskito-core"
1204
- version = "0.4.0"
1215
+ version = "0.6.0"
1205
1216
  dependencies = [
1206
1217
  "async-trait",
1207
1218
  "chrono",
@@ -1225,11 +1236,13 @@ dependencies = [
1225
1236
 
1226
1237
  [[package]]
1227
1238
  name = "taskito-python"
1228
- version = "0.4.0"
1239
+ version = "0.6.0"
1229
1240
  dependencies = [
1230
1241
  "async-trait",
1231
1242
  "crossbeam-channel",
1232
1243
  "pyo3",
1244
+ "serde_json",
1245
+ "taskito-async",
1233
1246
  "taskito-core",
1234
1247
  "tokio",
1235
1248
  "uuid",
@@ -1,11 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: taskito
3
- Version: 0.4.0
3
+ Version: 0.6.0
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
7
7
  Classifier: Programming Language :: Python :: 3
8
- Classifier: Programming Language :: Python :: 3.9
9
8
  Classifier: Programming Language :: Python :: 3.10
10
9
  Classifier: Programming Language :: Python :: 3.11
11
10
  Classifier: Programming Language :: Python :: 3.12
@@ -14,6 +13,7 @@ Classifier: Programming Language :: Rust
14
13
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
15
14
  Classifier: Topic :: System :: Distributed Computing
16
15
  Requires-Dist: cloudpickle>=3.0
16
+ Requires-Dist: boto3>=1.20 ; extra == 'aws'
17
17
  Requires-Dist: pytest>=7.0 ; extra == 'dev'
18
18
  Requires-Dist: pytest-asyncio>=0.21 ; extra == 'dev'
19
19
  Requires-Dist: pytest-cov>=4.0 ; extra == 'dev'
@@ -25,17 +25,20 @@ Requires-Dist: cryptography ; extra == 'encryption'
25
25
  Requires-Dist: fastapi>=0.100.0 ; extra == 'fastapi'
26
26
  Requires-Dist: pydantic>=2.0 ; extra == 'fastapi'
27
27
  Requires-Dist: flask>=2.0 ; extra == 'flask'
28
+ Requires-Dist: google-cloud-storage>=2.0 ; extra == 'gcs'
28
29
  Requires-Dist: msgpack ; extra == 'msgpack'
29
30
  Requires-Dist: opentelemetry-api ; extra == 'otel'
30
31
  Requires-Dist: opentelemetry-sdk ; extra == 'otel'
31
32
  Requires-Dist: prometheus-client ; extra == 'prometheus'
32
33
  Requires-Dist: sentry-sdk ; extra == 'sentry'
34
+ Provides-Extra: aws
33
35
  Provides-Extra: dev
34
36
  Provides-Extra: django
35
37
  Provides-Extra: docs
36
38
  Provides-Extra: encryption
37
39
  Provides-Extra: fastapi
38
40
  Provides-Extra: flask
41
+ Provides-Extra: gcs
39
42
  Provides-Extra: msgpack
40
43
  Provides-Extra: otel
41
44
  Provides-Extra: postgres
@@ -44,7 +47,7 @@ Provides-Extra: redis
44
47
  Provides-Extra: sentry
45
48
  License-File: LICENSE
46
49
  Summary: Rust-powered task queue for Python. No broker required.
47
- Requires-Python: >=3.9
50
+ Requires-Python: >=3.10
48
51
  Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
49
52
  Project-URL: Changelog, https://github.com/pratyush618/taskito/blob/master/docs/changelog.md
50
53
  Project-URL: Documentation, https://taskito.grigori.in
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "taskito-core"
3
- version = "0.4.0"
3
+ version = "0.6.0"
4
4
  edition = "2021"
5
5
 
6
6
  [features]
@@ -33,7 +33,7 @@ impl Scheduler {
33
33
 
34
34
  for job in stale_jobs {
35
35
  let error = format!("job timed out after {}ms", job.timeout_ms);
36
- self.handle_result(JobResult::Failure {
36
+ let _ = self.handle_result(JobResult::Failure {
37
37
  job_id: job.id.clone(),
38
38
  error,
39
39
  retry_count: job.retry_count,
@@ -41,6 +41,7 @@ impl Scheduler {
41
41
  task_name: job.task_name.clone(),
42
42
  wall_time_ns: 0,
43
43
  should_retry: true,
44
+ timed_out: true,
44
45
  })?;
45
46
  }
46
47
 
@@ -60,6 +60,7 @@ pub enum JobResult {
60
60
  task_name: String,
61
61
  wall_time_ns: i64,
62
62
  should_retry: bool,
63
+ timed_out: bool,
63
64
  },
64
65
  Cancelled {
65
66
  job_id: String,
@@ -68,12 +69,45 @@ pub enum JobResult {
68
69
  },
69
70
  }
70
71
 
72
+ /// Outcome of processing a job result, returned to the caller for
73
+ /// Python-side middleware hook dispatch.
74
+ #[derive(Debug, Clone)]
75
+ pub enum ResultOutcome {
76
+ /// Task completed successfully.
77
+ Success { job_id: String, task_name: String },
78
+ /// Task failed and will be retried.
79
+ Retry {
80
+ job_id: String,
81
+ task_name: String,
82
+ error: String,
83
+ retry_count: i32,
84
+ timed_out: bool,
85
+ },
86
+ /// Task exhausted retries and moved to the dead-letter queue.
87
+ DeadLettered {
88
+ job_id: String,
89
+ task_name: String,
90
+ error: String,
91
+ timed_out: bool,
92
+ },
93
+ /// Task was cancelled during execution.
94
+ Cancelled { job_id: String, task_name: String },
95
+ }
96
+
71
97
  /// Per-task configuration for retry, rate limiting, and circuit breaker.
72
98
  #[derive(Debug, Clone)]
73
99
  pub struct TaskConfig {
74
100
  pub retry_policy: RetryPolicy,
75
101
  pub rate_limit: Option<crate::resilience::rate_limiter::RateLimitConfig>,
76
102
  pub circuit_breaker: Option<CircuitBreakerConfig>,
103
+ pub max_concurrent: Option<i32>,
104
+ }
105
+
106
+ /// Per-queue configuration for rate limiting and concurrency caps.
107
+ #[derive(Debug, Clone)]
108
+ pub struct QueueConfig {
109
+ pub rate_limit: Option<crate::resilience::rate_limiter::RateLimitConfig>,
110
+ pub max_concurrent: Option<i32>,
77
111
  }
78
112
 
79
113
  /// The central scheduler that coordinates job dispatch, retries, rate limiting, and circuit breakers.
@@ -83,6 +117,7 @@ pub struct Scheduler {
83
117
  dlq: DeadLetterQueue,
84
118
  circuit_breaker: CircuitBreaker,
85
119
  task_configs: HashMap<String, TaskConfig>,
120
+ queue_configs: HashMap<String, QueueConfig>,
86
121
  queues: Vec<String>,
87
122
  config: SchedulerConfig,
88
123
  shutdown: Arc<Notify>,
@@ -109,6 +144,7 @@ impl Scheduler {
109
144
  dlq,
110
145
  circuit_breaker,
111
146
  task_configs: HashMap::new(),
147
+ queue_configs: HashMap::new(),
112
148
  queues,
113
149
  config,
114
150
  shutdown: Arc::new(Notify::new()),
@@ -124,6 +160,10 @@ impl Scheduler {
124
160
  self.shutdown.clone()
125
161
  }
126
162
 
163
+ pub fn register_queue_config(&mut self, queue_name: String, config: QueueConfig) {
164
+ self.queue_configs.insert(queue_name, config);
165
+ }
166
+
127
167
  pub fn register_task(&mut self, task_name: String, config: TaskConfig) {
128
168
  if let Some(ref cb_config) = config.circuit_breaker {
129
169
  if let Err(e) = self.circuit_breaker.register(&task_name, cb_config) {
@@ -276,6 +316,7 @@ mod tests {
276
316
  },
277
317
  rate_limit: None,
278
318
  circuit_breaker: None,
319
+ max_concurrent: None,
279
320
  },
280
321
  );
281
322
 
@@ -290,6 +331,7 @@ mod tests {
290
331
  task_name: "retry_task".to_string(),
291
332
  wall_time_ns: 500_000,
292
333
  should_retry: true,
334
+ timed_out: false,
293
335
  })
294
336
  .unwrap();
295
337
 
@@ -312,6 +354,7 @@ mod tests {
312
354
  task_name: "exhausted_task".to_string(),
313
355
  wall_time_ns: 100,
314
356
  should_retry: true,
357
+ timed_out: false,
315
358
  })
316
359
  .unwrap();
317
360
 
@@ -337,6 +380,7 @@ mod tests {
337
380
  task_name: "no_retry_task".to_string(),
338
381
  wall_time_ns: 100,
339
382
  should_retry: false,
383
+ timed_out: false,
340
384
  })
341
385
  .unwrap();
342
386
 
@@ -373,6 +417,7 @@ mod tests {
373
417
  refill_rate: 0.0,
374
418
  }),
375
419
  circuit_breaker: None,
420
+ max_concurrent: None,
376
421
  },
377
422
  );
378
423
 
@@ -414,6 +459,7 @@ mod tests {
414
459
  retry_policy: RetryPolicy::default(),
415
460
  rate_limit: None,
416
461
  circuit_breaker: Some(cb_config),
462
+ max_concurrent: None,
417
463
  },
418
464
  );
419
465
 
@@ -452,6 +498,7 @@ mod tests {
452
498
  },
453
499
  rate_limit: None,
454
500
  circuit_breaker: None,
501
+ max_concurrent: None,
455
502
  },
456
503
  );
457
504
 
@@ -14,6 +14,9 @@ const CIRCUIT_BREAKER_RETRY_DELAY_MS: i64 = 5000;
14
14
  /// Delay before re-scheduling a rate-limited job (ms).
15
15
  const RATE_LIMIT_RETRY_DELAY_MS: i64 = 1000;
16
16
 
17
+ /// Delay before re-scheduling a concurrency-limited job (ms).
18
+ const CONCURRENCY_RETRY_DELAY_MS: i64 = 500;
19
+
17
20
  impl Scheduler {
18
21
  pub(super) fn try_dispatch(&self, job_tx: &tokio::sync::mpsc::Sender<Job>) -> Result<bool> {
19
22
  let now = now_millis();
@@ -53,6 +56,26 @@ impl Scheduler {
53
56
  None => return Ok(false),
54
57
  };
55
58
 
59
+ // Check queue-level limits
60
+ if let Some(qcfg) = self.queue_configs.get(&job.queue) {
61
+ if let Some(ref rl_config) = qcfg.rate_limit {
62
+ let key = format!("queue:{}", job.queue);
63
+ if !self.rate_limiter.try_acquire(&key, rl_config)? {
64
+ self.storage
65
+ .retry(&job.id, now + RATE_LIMIT_RETRY_DELAY_MS)?;
66
+ return Ok(false);
67
+ }
68
+ }
69
+ if let Some(max_conc) = qcfg.max_concurrent {
70
+ let stats = self.storage.stats_by_queue(&job.queue)?;
71
+ if stats.running >= max_conc as i64 {
72
+ self.storage
73
+ .retry(&job.id, now + CONCURRENCY_RETRY_DELAY_MS)?;
74
+ return Ok(false);
75
+ }
76
+ }
77
+ }
78
+
56
79
  // Check circuit breaker for this task
57
80
  if let Some(config) = self.task_configs.get(&job.task_name) {
58
81
  if config.circuit_breaker.is_some() && !self.circuit_breaker.allow(&job.task_name)? {
@@ -68,6 +91,16 @@ impl Scheduler {
68
91
  return Ok(false);
69
92
  }
70
93
  }
94
+
95
+ // Check per-task concurrency limit
96
+ if let Some(max_conc) = config.max_concurrent {
97
+ let running = self.storage.count_running_by_task(&job.task_name)?;
98
+ if running >= max_conc as i64 {
99
+ self.storage
100
+ .retry(&job.id, now + CONCURRENCY_RETRY_DELAY_MS)?;
101
+ return Ok(false);
102
+ }
103
+ }
71
104
  }
72
105
 
73
106
  // Claim exactly-once execution
@@ -3,11 +3,14 @@ use log::{error, warn};
3
3
  use crate::error::Result;
4
4
  use crate::storage::Storage;
5
5
 
6
- use super::{JobResult, Scheduler};
6
+ use super::{JobResult, ResultOutcome, Scheduler};
7
7
 
8
8
  impl Scheduler {
9
9
  /// Handle a completed or failed job result from a worker.
10
- pub fn handle_result(&self, result: JobResult) -> Result<()> {
10
+ ///
11
+ /// Returns a [`ResultOutcome`] describing the action taken, so the
12
+ /// caller can dispatch Python-side middleware hooks and events.
13
+ pub fn handle_result(&self, result: JobResult) -> Result<ResultOutcome> {
11
14
  match result {
12
15
  JobResult::Success {
13
16
  job_id,
@@ -32,6 +35,11 @@ impl Scheduler {
32
35
  if let Err(e) = self.circuit_breaker.record_success(task_name) {
33
36
  error!("circuit breaker error for {task_name}: {e}");
34
37
  }
38
+
39
+ Ok(ResultOutcome::Success {
40
+ job_id,
41
+ task_name: task_name.clone(),
42
+ })
35
43
  }
36
44
  JobResult::Failure {
37
45
  job_id,
@@ -41,6 +49,7 @@ impl Scheduler {
41
49
  task_name,
42
50
  wall_time_ns,
43
51
  should_retry,
52
+ timed_out,
44
53
  } => {
45
54
  // Clear execution claim so it can be retried
46
55
  if let Err(e) = self.storage.complete_execution(&job_id) {
@@ -68,7 +77,12 @@ impl Scheduler {
68
77
  Some(job) => self.dlq.move_to_dlq(&job, &error, None)?,
69
78
  None => warn!("job {job_id} disappeared before DLQ move"),
70
79
  }
71
- return Ok(());
80
+ return Ok(ResultOutcome::DeadLettered {
81
+ job_id,
82
+ task_name,
83
+ error,
84
+ timed_out,
85
+ });
72
86
  }
73
87
 
74
88
  let policy = self
@@ -86,12 +100,25 @@ impl Scheduler {
86
100
  if retry_count < effective_max {
87
101
  let next_at = policy.next_retry_at(retry_count);
88
102
  self.storage.retry(&job_id, next_at)?;
103
+ Ok(ResultOutcome::Retry {
104
+ job_id,
105
+ task_name,
106
+ error,
107
+ retry_count,
108
+ timed_out,
109
+ })
89
110
  } else {
90
111
  // Move to DLQ
91
112
  match self.storage.get_job(&job_id)? {
92
113
  Some(job) => self.dlq.move_to_dlq(&job, &error, None)?,
93
114
  None => warn!("job {job_id} disappeared before DLQ move"),
94
115
  }
116
+ Ok(ResultOutcome::DeadLettered {
117
+ job_id,
118
+ task_name,
119
+ error,
120
+ timed_out,
121
+ })
95
122
  }
96
123
  }
97
124
  JobResult::Cancelled {
@@ -113,8 +140,8 @@ impl Scheduler {
113
140
  {
114
141
  error!("failed to record metric for cancelled job {job_id}: {e}");
115
142
  }
143
+ Ok(ResultOutcome::Cancelled { job_id, task_name })
116
144
  }
117
145
  }
118
- Ok(())
119
146
  }
120
147
  }
@@ -916,6 +916,19 @@ macro_rules! impl_diesel_job_ops {
916
916
  Ok(affected as u64)
917
917
  }
918
918
 
919
+ /// Count running jobs for a specific task name (for per-task concurrency limiting).
920
+ pub fn count_running_by_task(&self, task_name: &str) -> Result<i64> {
921
+ let mut conn = self.conn()?;
922
+
923
+ let count: i64 = jobs::table
924
+ .filter(jobs::task_name.eq(task_name))
925
+ .filter(jobs::status.eq(JobStatus::Running as i32))
926
+ .count()
927
+ .get_result(&mut conn)?;
928
+
929
+ Ok(count)
930
+ }
931
+
919
932
  /// Purge job errors older than the given timestamp.
920
933
  pub fn purge_job_errors(&self, older_than_ms: i64) -> Result<u64> {
921
934
  let mut conn = self.conn()?;
@@ -344,11 +344,18 @@ macro_rules! impl_storage {
344
344
  worker_id: &str,
345
345
  queues: &str,
346
346
  tags: Option<&str>,
347
+ resources: Option<&str>,
348
+ resource_health: Option<&str>,
349
+ threads: i32,
347
350
  ) -> $crate::error::Result<()> {
348
- self.register_worker(worker_id, queues, tags)
351
+ self.register_worker(worker_id, queues, tags, resources, resource_health, threads)
349
352
  }
350
- fn heartbeat(&self, worker_id: &str) -> $crate::error::Result<()> {
351
- self.heartbeat(worker_id)
353
+ fn heartbeat(
354
+ &self,
355
+ worker_id: &str,
356
+ resource_health: Option<&str>,
357
+ ) -> $crate::error::Result<()> {
358
+ self.heartbeat(worker_id, resource_health)
352
359
  }
353
360
  fn list_workers(
354
361
  &self,
@@ -437,6 +444,12 @@ macro_rules! impl_storage {
437
444
  ) -> $crate::error::Result<u64> {
438
445
  self.purge_execution_claims(older_than_ms)
439
446
  }
447
+ fn count_running_by_task(
448
+ &self,
449
+ task_name: &str,
450
+ ) -> $crate::error::Result<i64> {
451
+ self.count_running_by_task(task_name)
452
+ }
440
453
  fn stats_by_queue(
441
454
  &self,
442
455
  queue_name: &str,
@@ -705,11 +718,28 @@ impl Storage for StorageBackend {
705
718
  fn list_circuit_breakers(&self) -> Result<Vec<models::CircuitBreakerRow>> {
706
719
  delegate!(self, list_circuit_breakers)
707
720
  }
708
- fn register_worker(&self, worker_id: &str, queues: &str, tags: Option<&str>) -> Result<()> {
709
- delegate!(self, register_worker, worker_id, queues, tags)
721
+ fn register_worker(
722
+ &self,
723
+ worker_id: &str,
724
+ queues: &str,
725
+ tags: Option<&str>,
726
+ resources: Option<&str>,
727
+ resource_health: Option<&str>,
728
+ threads: i32,
729
+ ) -> Result<()> {
730
+ delegate!(
731
+ self,
732
+ register_worker,
733
+ worker_id,
734
+ queues,
735
+ tags,
736
+ resources,
737
+ resource_health,
738
+ threads
739
+ )
710
740
  }
711
- fn heartbeat(&self, worker_id: &str) -> Result<()> {
712
- delegate!(self, heartbeat, worker_id)
741
+ fn heartbeat(&self, worker_id: &str, resource_health: Option<&str>) -> Result<()> {
742
+ delegate!(self, heartbeat, worker_id, resource_health)
713
743
  }
714
744
  fn list_workers(&self) -> Result<Vec<models::WorkerRow>> {
715
745
  delegate!(self, list_workers)
@@ -768,6 +798,9 @@ impl Storage for StorageBackend {
768
798
  fn purge_execution_claims(&self, older_than_ms: i64) -> Result<u64> {
769
799
  delegate!(self, purge_execution_claims, older_than_ms)
770
800
  }
801
+ fn count_running_by_task(&self, task_name: &str) -> Result<i64> {
802
+ delegate!(self, count_running_by_task, task_name)
803
+ }
771
804
  fn stats_by_queue(&self, queue_name: &str) -> Result<QueueStats> {
772
805
  delegate!(self, stats_by_queue, queue_name)
773
806
  }
@@ -284,6 +284,9 @@ pub struct WorkerRow {
284
284
  pub queues: String,
285
285
  pub status: String,
286
286
  pub tags: Option<String>,
287
+ pub resources: Option<String>,
288
+ pub resource_health: Option<String>,
289
+ pub threads: i32,
287
290
  }
288
291
 
289
292
  #[derive(Insertable, AsChangeset, Debug)]
@@ -294,6 +297,9 @@ pub struct NewWorkerRow<'a> {
294
297
  pub queues: &'a str,
295
298
  pub status: &'a str,
296
299
  pub tags: Option<&'a str>,
300
+ pub resources: Option<&'a str>,
301
+ pub resource_health: Option<&'a str>,
302
+ pub threads: i32,
297
303
  }
298
304
 
299
305
  // ── Queue State ─────────────────────────────────────────────────
@@ -340,6 +340,20 @@ impl PostgresStorage {
340
340
  "ALTER TABLE workers ADD COLUMN IF NOT EXISTS tags TEXT",
341
341
  );
342
342
 
343
+ // Migration: add resource advertisement columns to workers
344
+ migration_alter(
345
+ &mut conn,
346
+ "ALTER TABLE workers ADD COLUMN IF NOT EXISTS resources TEXT",
347
+ );
348
+ migration_alter(
349
+ &mut conn,
350
+ "ALTER TABLE workers ADD COLUMN IF NOT EXISTS resource_health TEXT",
351
+ );
352
+ migration_alter(
353
+ &mut conn,
354
+ "ALTER TABLE workers ADD COLUMN IF NOT EXISTS threads INTEGER NOT NULL DEFAULT 0",
355
+ );
356
+
343
357
  diesel::sql_query(
344
358
  "CREATE TABLE IF NOT EXISTS queue_state (
345
359
  queue_name TEXT PRIMARY KEY,
@@ -11,7 +11,15 @@ const DEAD_WORKER_THRESHOLD_MS: i64 = 30_000;
11
11
 
12
12
  impl PostgresStorage {
13
13
  /// Register a new worker or update an existing one.
14
- pub fn register_worker(&self, worker_id: &str, queues: &str, tags: Option<&str>) -> Result<()> {
14
+ pub fn register_worker(
15
+ &self,
16
+ worker_id: &str,
17
+ queues: &str,
18
+ tags: Option<&str>,
19
+ resources: Option<&str>,
20
+ resource_health: Option<&str>,
21
+ threads: i32,
22
+ ) -> Result<()> {
15
23
  let mut conn = self.conn()?;
16
24
  let now = now_millis();
17
25
 
@@ -21,6 +29,9 @@ impl PostgresStorage {
21
29
  queues,
22
30
  status: "active",
23
31
  tags,
32
+ resources,
33
+ resource_health,
34
+ threads,
24
35
  };
25
36
 
26
37
  diesel::insert_into(workers::table)
@@ -34,13 +45,16 @@ impl PostgresStorage {
34
45
  }
35
46
 
36
47
  /// Update the heartbeat timestamp for a worker.
37
- pub fn heartbeat(&self, worker_id: &str) -> Result<()> {
48
+ pub fn heartbeat(&self, worker_id: &str, resource_health: Option<&str>) -> Result<()> {
38
49
  let mut conn = self.conn()?;
39
50
  let now = now_millis();
40
51
 
41
52
  diesel::update(workers::table)
42
53
  .filter(workers::worker_id.eq(worker_id))
43
- .set(workers::last_heartbeat.eq(now))
54
+ .set((
55
+ workers::last_heartbeat.eq(now),
56
+ workers::resource_health.eq(resource_health),
57
+ ))
44
58
  .execute(&mut conn)?;
45
59
 
46
60
  Ok(())
@@ -725,6 +725,24 @@ impl RedisStorage {
725
725
  Ok(stats)
726
726
  }
727
727
 
728
+ /// Count running jobs for a specific task name (for per-task concurrency limiting).
729
+ pub fn count_running_by_task(&self, task_name: &str) -> Result<i64> {
730
+ let mut conn = self.conn()?;
731
+ let by_task_key = self.key(&["jobs", "by_task", task_name]);
732
+ let job_ids: Vec<String> = conn.smembers(&by_task_key).map_err(map_err)?;
733
+
734
+ let mut count: i64 = 0;
735
+ for id in &job_ids {
736
+ if let Some(job) = self.load_job(&mut conn, id)? {
737
+ if job.status == JobStatus::Running {
738
+ count += 1;
739
+ }
740
+ }
741
+ }
742
+
743
+ Ok(count)
744
+ }
745
+
728
746
  pub fn stats_by_queue(&self, queue_name: &str) -> Result<QueueStats> {
729
747
  let mut conn = self.conn()?;
730
748
  let by_queue_key = self.key(&["jobs", "by_queue", queue_name]);