queueflow 0.3.0__tar.gz → 0.5.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 (150) hide show
  1. {queueflow-0.3.0/queueflow.egg-info → queueflow-0.5.0}/PKG-INFO +138 -21
  2. {queueflow-0.3.0 → queueflow-0.5.0}/README.md +137 -20
  3. {queueflow-0.3.0 → queueflow-0.5.0}/pyproject.toml +1 -1
  4. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/__init__.py +14 -2
  5. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/api/cron_api.py +35 -1
  6. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/api/dlq_api.py +314 -1
  7. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/api/health_api.py +1 -1
  8. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/api/jobs_api.py +317 -1
  9. queueflow-0.5.0/queueflow/api/system_api.py +1551 -0
  10. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/api/worker_api.py +1 -1
  11. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/api/workflows_api.py +574 -1
  12. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/api_client.py +2 -2
  13. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/configuration.py +3 -3
  14. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/exceptions.py +1 -1
  15. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/facade.py +297 -10
  16. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/__init__.py +13 -1
  17. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/backoff_strategy.py +1 -1
  18. queueflow-0.5.0/queueflow/models/cancel_jobs_request.py +128 -0
  19. queueflow-0.5.0/queueflow/models/cancel_jobs_response.py +88 -0
  20. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/complete_job_request.py +1 -1
  21. queueflow-0.5.0/queueflow/models/condition_op.py +44 -0
  22. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/create_batch_jobs_request.py +1 -1
  23. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/create_batch_jobs_response.py +1 -1
  24. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/create_cron_request.py +1 -1
  25. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/create_cron_response.py +1 -1
  26. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/create_job_request.py +1 -1
  27. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/create_job_response.py +1 -1
  28. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/create_workflow_request.py +15 -5
  29. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/create_workflow_response.py +1 -1
  30. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/cron_schedule.py +1 -1
  31. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/dead_letter.py +1 -1
  32. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/error_body.py +1 -1
  33. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/fail_job_request.py +1 -1
  34. queueflow-0.5.0/queueflow/models/fan_out.py +102 -0
  35. queueflow-0.5.0/queueflow/models/fan_out_progress.py +92 -0
  36. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/health_status.py +1 -1
  37. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/heartbeat_request.py +1 -1
  38. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/heartbeat_response.py +1 -1
  39. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/job.py +1 -1
  40. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/job_config.py +1 -1
  41. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/job_config_request.py +1 -1
  42. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/job_status.py +1 -1
  43. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/lease_jobs_request.py +1 -1
  44. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/lease_jobs_response.py +1 -1
  45. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/leased_job.py +1 -1
  46. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/list_crons_response.py +1 -1
  47. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/list_dead_letters_response.py +1 -1
  48. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/list_jobs_response.py +1 -1
  49. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/list_workflows_response.py +1 -1
  50. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/on_failure.py +1 -1
  51. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/on_success.py +1 -1
  52. queueflow-0.5.0/queueflow/models/queue_control_response.py +94 -0
  53. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/queue_stats.py +5 -3
  54. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/queues_response.py +2 -2
  55. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/ready_status.py +1 -1
  56. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/replay_dead_letter_response.py +1 -1
  57. queueflow-0.5.0/queueflow/models/replay_dead_letters_request.py +121 -0
  58. queueflow-0.5.0/queueflow/models/replay_dead_letters_response.py +98 -0
  59. queueflow-0.3.0/queueflow/models/workflow_step_state.py → queueflow-0.5.0/queueflow/models/replayed_dead_letter.py +18 -14
  60. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/stats_snapshot.py +1 -1
  61. queueflow-0.5.0/queueflow/models/step_condition.py +147 -0
  62. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/step_status.py +1 -1
  63. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/tasks_response.py +1 -1
  64. queueflow-0.5.0/queueflow/models/who_am_i_response.py +89 -0
  65. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/workflow.py +41 -3
  66. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/workflow_diagram_response.py +1 -1
  67. queueflow-0.5.0/queueflow/models/workflow_event.py +107 -0
  68. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/workflow_status.py +1 -1
  69. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/workflow_step.py +48 -6
  70. queueflow-0.5.0/queueflow/models/workflow_step_state.py +137 -0
  71. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/models/workflow_step_states_response.py +1 -1
  72. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/rest.py +1 -1
  73. {queueflow-0.3.0 → queueflow-0.5.0/queueflow.egg-info}/PKG-INFO +138 -21
  74. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow.egg-info/SOURCES.txt +24 -0
  75. {queueflow-0.3.0 → queueflow-0.5.0}/setup.py +2 -2
  76. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_backoff_strategy.py +1 -1
  77. queueflow-0.5.0/test/test_cancel_jobs_request.py +56 -0
  78. queueflow-0.5.0/test/test_cancel_jobs_response.py +52 -0
  79. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_complete_job_request.py +1 -1
  80. queueflow-0.5.0/test/test_condition_op.py +33 -0
  81. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_create_batch_jobs_request.py +1 -1
  82. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_create_batch_jobs_response.py +1 -1
  83. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_create_cron_request.py +1 -1
  84. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_create_cron_response.py +1 -1
  85. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_create_job_request.py +1 -1
  86. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_create_job_response.py +1 -1
  87. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_create_workflow_request.py +75 -4
  88. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_create_workflow_response.py +1 -1
  89. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_cron_api.py +1 -1
  90. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_cron_schedule.py +1 -1
  91. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_dead_letter.py +1 -1
  92. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_dlq_api.py +7 -1
  93. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_error_body.py +1 -1
  94. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_facade.py +394 -0
  95. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_fail_job_request.py +1 -1
  96. queueflow-0.5.0/test/test_fan_out.py +54 -0
  97. queueflow-0.5.0/test/test_fan_out_progress.py +56 -0
  98. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_health_api.py +1 -1
  99. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_health_status.py +1 -1
  100. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_heartbeat_request.py +1 -1
  101. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_heartbeat_response.py +1 -1
  102. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_job.py +1 -1
  103. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_job_config.py +1 -1
  104. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_job_config_request.py +1 -1
  105. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_job_status.py +1 -1
  106. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_jobs_api.py +7 -1
  107. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_lease_jobs_request.py +1 -1
  108. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_lease_jobs_response.py +1 -1
  109. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_leased_job.py +1 -1
  110. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_list_crons_response.py +1 -1
  111. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_list_dead_letters_response.py +1 -1
  112. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_list_jobs_response.py +1 -1
  113. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_list_workflows_response.py +81 -5
  114. queueflow-0.5.0/test/test_live.py +593 -0
  115. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_on_failure.py +1 -1
  116. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_on_success.py +1 -1
  117. queueflow-0.5.0/test/test_queue_control_response.py +58 -0
  118. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_queue_stats.py +2 -1
  119. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_queues_response.py +3 -1
  120. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_ready_status.py +1 -1
  121. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_replay_dead_letter_response.py +1 -1
  122. queueflow-0.5.0/test/test_replay_dead_letters_request.py +55 -0
  123. queueflow-0.5.0/test/test_replay_dead_letters_response.py +64 -0
  124. queueflow-0.5.0/test/test_replayed_dead_letter.py +54 -0
  125. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_stats_snapshot.py +1 -1
  126. queueflow-0.5.0/test/test_step_condition.py +97 -0
  127. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_step_status.py +1 -1
  128. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_system_api.py +19 -1
  129. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_tasks_response.py +1 -1
  130. queueflow-0.5.0/test/test_who_am_i_response.py +54 -0
  131. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_worker_api.py +1 -1
  132. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_workflow.py +80 -4
  133. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_workflow_diagram_response.py +1 -1
  134. queueflow-0.5.0/test/test_workflow_event.py +83 -0
  135. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_workflow_status.py +1 -1
  136. queueflow-0.5.0/test/test_workflow_step.py +151 -0
  137. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_workflow_step_state.py +9 -1
  138. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_workflow_step_states_response.py +17 -1
  139. {queueflow-0.3.0 → queueflow-0.5.0}/test/test_workflows_api.py +14 -1
  140. queueflow-0.3.0/queueflow/api/system_api.py +0 -767
  141. queueflow-0.3.0/test/test_live.py +0 -229
  142. queueflow-0.3.0/test/test_workflow_step.py +0 -73
  143. {queueflow-0.3.0 → queueflow-0.5.0}/LICENSE +0 -0
  144. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/api/__init__.py +0 -0
  145. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/api_response.py +0 -0
  146. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow/py.typed +0 -0
  147. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow.egg-info/dependency_links.txt +0 -0
  148. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow.egg-info/requires.txt +0 -0
  149. {queueflow-0.3.0 → queueflow-0.5.0}/queueflow.egg-info/top_level.txt +0 -0
  150. {queueflow-0.3.0 → queueflow-0.5.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: queueflow
3
- Version: 0.3.0
3
+ Version: 0.5.0
4
4
  Summary: Python client for QueueFlow, a PostgreSQL-native distributed job queue and workflow engine.
5
5
  Home-page: https://github.com/elision-labs/queueflow-sdk-python
6
6
  Author: QueueFlow
@@ -109,8 +109,9 @@ The generated per-tag clients hang off the facade for anything the helpers do no
109
109
  | `qf.create_job(task, payload=None, priority=None, max_retries=None, timeout=None, queue=None, retry_backoff=None, retry_delay_secs=None, retry_max_delay_secs=None, jitter_factor=None, idempotency_key=None, run_at=None)` | Enqueue a job and return the created `Job`. |
110
110
  | `qf.wait_for_job(job_id, timeout=60.0, interval=0.5)` | Poll until `completed` / `failed` / `cancelled`; raises `WaitTimeout`. |
111
111
  | `qf.jobs.get_job(id)` | Fetch a job. |
112
- | `qf.jobs.list_jobs(status=..., queue=..., limit=..., offset=..., order_by=..., include_total=..., cursor=..., created_after=..., created_before=...)` | List jobs. |
112
+ | `qf.jobs.list_jobs(status=..., queue=..., task_name=..., limit=..., offset=..., order_by=..., include_total=..., cursor=..., created_after=..., created_before=...)` | List jobs. |
113
113
  | `qf.jobs.cancel_job(id)` | Cancel a job. |
114
+ | `qf.cancel_jobs(queue=None, task_name=None, status=None, created_after=None, created_before=None, limit=None)` | Cancel every matching non-terminal job of your tenant, oldest first, up to `limit` (default 100); returns the count. |
114
115
  | `qf.jobs.create_batch_jobs(CreateBatchJobsRequest(...))` | Enqueue up to 1000 jobs at once. |
115
116
 
116
117
  `idempotency_key` is sent as the `Idempotency-Key` header: re-submitting the same key returns the
@@ -119,6 +120,16 @@ original job instead of creating a duplicate. `run_at` (a `datetime`) delays the
119
120
  List responses carry `next_cursor` when there are more pages; pass it back as `cursor` for keyset
120
121
  pagination (cheaper than deep `offset`).
121
122
 
123
+ `cancel_jobs` takes optional filters that combine: `queue`, `task_name`, `status` (`pending`,
124
+ `retrying` or `running`; all three when omitted, a terminal status is a 400) and the half-open
125
+ `datetime` window `[created_after, created_before)`. A count below `limit` means the matching set is
126
+ exhausted, so loop until then to drain a large backlog:
127
+
128
+ ```python
129
+ while qf.cancel_jobs(queue="orders", task_name="send_email", limit=500) == 500:
130
+ pass
131
+ ```
132
+
122
133
  ### Workflows
123
134
 
124
135
  | Method | Description |
@@ -126,26 +137,92 @@ pagination (cheaper than deep `offset`).
126
137
  | `qf.create_workflow(builder_or_request)` | Create a workflow from a `wf()` builder or a raw `CreateWorkflowRequest`. |
127
138
  | `qf.wait_for_workflow(workflow_id, timeout=60.0, interval=0.5)` | Poll until a terminal workflow state. |
128
139
  | `qf.workflows.get_workflow(id)` / `list_workflows(...)` / `cancel_workflow(id)` | Fetch / list / cancel. |
129
- | `qf.workflows.get_workflow_step_states(id)` | Runtime status of every step, in declaration order. |
140
+ | `qf.workflow_steps(workflow_id)` | Live progress of every step, in declaration order: `name`, `status`, `job_id` (the first child, for a fan-out), `error` (`condition not met` for a step skipped by its `when`), `started_at`, `completed_at`, `child_workflow_id` (sub-workflow steps) and `fan_out` (`total`, `completed`, `failed`). |
141
+ | `qf.retry_workflow(workflow_id)` | Re-run a `failed`, `partially_failed` or `cancelled` workflow from where it stopped and return its refreshed record (server 0.6.0). A running or completed workflow is a 409. |
142
+ | `qf.workflows.get_workflow_step_states(id)` | The raw step-states call behind `qf.workflow_steps`. |
130
143
  | `qf.workflows.get_workflow_diagram(id)` | Mermaid (`graph TD`) diagram of the DAG. |
131
144
 
145
+ The `Workflow` record carries `status`, the accumulated `context` (step results keyed by step name),
146
+ the step definitions and, from server 0.6.0, `error_message`, `timeout_secs` and `deadline_at`,
147
+ `retry_count`, and `parent_workflow_id` / `parent_step_name` on a child spawned by a sub-workflow
148
+ step.
149
+
150
+ `retry_workflow` sends every step that did not complete (failed, cancelled, or skipped by a failure
151
+ policy) back to `pending`; completed steps and condition-skipped steps keep their results and do not
152
+ run again. The workflow becomes `running`, its `error_message` clears, `retry_count` increments and
153
+ the deadline, if any, is re-armed from the original `timeout_secs`. Fix the cause first: a retry
154
+ with the same inputs fails the same way.
155
+
132
156
  ### Workflow builder
133
157
 
134
158
  ```python
135
159
  from queueflow.facade import wf
136
160
 
161
+ fulfilment = (
162
+ wf("fulfilment")
163
+ .step("pick", "pick_items", queue="warehouse")
164
+ .step("pack", "pack_parcel", after=["pick"], queue="warehouse")
165
+ .timeout_secs(1800)
166
+ )
167
+
137
168
  dag = (
138
169
  wf("order_123")
139
- .step("validate", "validate_order")
140
- .step("pay", "process_payment", after=["validate"])
141
- .step("ship", "create_shipment", after=["pay"], on_failure="continue")
170
+ .step("validate", "validate_order", payload={"order_id": 123})
171
+ .step("charge", "charge_payment", after=["validate"])
172
+ .step("fraud", "fraud_check", after=["validate"], on_failure="halt")
173
+ .step("review", "queue_for_review", after=["fraud"],
174
+ when={"path": "fraud.flags", "op": "eq", "value": {"manual": True}})
175
+ .step("resize", "resize_image", after=["validate"], queue="images", on_failure="continue",
176
+ fan_out={"path": "validate.images", "item_key": "image"})
177
+ .sub_workflow("fulfil", fulfilment, after=["charge", "fraud"], payload={"warehouse": "eu-1"})
178
+ .step("confirm", "send_confirmation", after=["fulfil"], on_failure="skip")
142
179
  .context(source="web")
180
+ .metadata(app="shop")
181
+ .timeout_secs(3600)
143
182
  )
144
- # .build() runs locally first: duplicate names, dangling deps, and cycles raise
145
- # WorkflowValidationError before any network round-trip.
183
+ # .build() runs locally first: duplicate names, dangling deps, cycles, and malformed
184
+ # when / fan_out shapes raise WorkflowValidationError before any network round-trip.
146
185
  ```
147
186
 
148
- `step()` also accepts `payload=`, `on_success=`, and `config=` (a `models.JobConfig` override).
187
+ | Builder method | Description |
188
+ | --- | --- |
189
+ | `step(name, task, after=None, payload=None, on_failure=None, on_success=None, config=None, queue=None, when=None, fan_out=None)` | A task step. `config` is a `models.JobConfig` override; `on_failure` is `halt` (default), `skip` or `continue`. |
190
+ | `sub_workflow(name, child, after=None, payload=None, on_failure=None, on_success=None, when=None)` | A step that runs `child` (a `wf()` builder, built and validated here, or a `CreateWorkflowRequest`) as an inline child workflow instead of a task. |
191
+ | `context(**kwargs)` / `metadata(**kwargs)` | Seed the shared context / store bookkeeping JSON on the workflow. Both accumulate across calls. |
192
+ | `timeout_secs(secs)` | Whole-workflow deadline. When it passes, every unfinished step is cancelled and the workflow ends `failed` with `error_message` `workflow timed out after <secs>s`. |
193
+ | `build()` | Validate locally and return the `CreateWorkflowRequest`; `qf.create_workflow` calls it for you. |
194
+
195
+ `queue`, `when`, `fan_out`, `sub_workflow` and `timeout_secs` need server 0.6.0 or later.
196
+
197
+ **Queues.** `queue=` puts the step's job(s) on that queue (the server's default queue when
198
+ omitted), so a step can be routed to the workers that know its task.
199
+
200
+ **Conditions.** `when=` gates a step on the workflow context at the moment it becomes ready. It
201
+ takes a dict in the wire shape or a `models.StepCondition`. A leaf names a dotted `path` (step
202
+ results live under their step name, so `validate.ok` is the `ok` key of `validate`'s result) and
203
+ compares the value there with `value` using `op`: `eq` (default with a value), `ne`, `gt`, `gte`,
204
+ `lt`, `lte`, `exists` (default without a value), `contains`, `in`. `{"all": [...]}`,
205
+ `{"any": [...]}` and `{"not": {...}}` combine leaves. A false condition marks the step `skipped`
206
+ with the error `condition not met`; unlike a step skipped by a failure policy, its dependents still
207
+ run, simply without a result for it in `_context`. With this release a leaf's `value` must be a JSON
208
+ object (see [Known limitations](#known-limitations)); `exists` needs no value.
209
+
210
+ **Fan-out.** `fan_out=` (`{"path": ..., "item_key": "item", "max_items": 10000}`, or a
211
+ `models.FanOut`) runs the task once per element of the array at `path`, all children created in one
212
+ atomic claim and run concurrently across the queue's workers, each with its own retries. A child's
213
+ payload is the step payload plus the element under `item_key` and its position under `_index`; its
214
+ metadata carries `fan_out_index` and `fan_out_total`. The step completes with the result
215
+ `{"results": [r0, r1, ...]}` in element order (`_context.<step>.results` downstream); a failed child
216
+ follows the step's `on_failure`. The step state's `fan_out` reports `total`, `completed` and
217
+ `failed`, and its `job_id` is the first child.
218
+
219
+ **Sub-workflows.** `sub_workflow()` runs the child as a workflow of its own, in the same tenant,
220
+ with `parent_workflow_id` and `parent_step_name` set; the parent's step state carries its
221
+ `child_workflow_id`. The child's context is its declared `context`, then the step's `payload`, then
222
+ the parent's whole context under `_parent`. A completed child completes the step with the child's
223
+ final context as the step result; a failed, partially failed or cancelled child fails the step under
224
+ its `on_failure`. Cancelling or timing out the parent cancels a running child. Children nest up to
225
+ 8 levels and carry their own step `queue`s.
149
226
 
150
227
  ### Worker
151
228
 
@@ -238,22 +315,57 @@ Rules the runtime enforces, and that any hand-rolled loop over `qf.worker` must
238
315
 
239
316
  | Method | Description |
240
317
  | --- | --- |
241
- | `qf.dlq.list_dead_letters(...)` / `get_dead_letter(id)` | Inspect terminally-failed jobs. |
318
+ | `qf.dlq.list_dead_letters(task_name=..., replayed=..., limit=..., offset=..., order_by=..., include_total=..., cursor=..., created_after=..., created_before=...)` / `get_dead_letter(id)` | Inspect terminally-failed jobs. `replayed=False` lists only entries not yet replayed, `True` only the replayed ones. |
242
319
  | `qf.replay_dead_letter(id)` | Re-run one as a fresh job and return it (at most once; a second replay is a 409). |
320
+ | `qf.replay_dead_letters(queue=None, task_name=None, created_after=None, created_before=None, limit=None)` | Replay every unreplayed entry matching the filter, oldest first, up to `limit` (default 100). Returns a `ReplayedDeadLetter` per entry: `job_id` on success, otherwise `error`. |
321
+
322
+ ```python
323
+ for r in qf.replay_dead_letters(queue="orders", task_name="send_email"):
324
+ if r.error:
325
+ print("dead letter", r.dead_letter_id, "not replayed:", r.error)
326
+ else:
327
+ print("dead letter", r.dead_letter_id, "replayed as", r.job_id)
328
+ ```
329
+
330
+ Already-replayed entries are never considered again, so repeating the call walks a large backlog;
331
+ an empty list means nothing was left to replay.
243
332
 
244
333
  ### System
245
334
 
246
335
  ```python
336
+ qf.whoami() # the tenant this token is scoped to and its access level
247
337
  qf.system.get_stats() # engine counters
248
338
  qf.system.list_tasks() # registered task handler names
249
- qf.queues() # live per-queue backlog: pending, scheduled, running, oldest pending age
339
+ qf.queues() # live per-queue backlog: pending, scheduled, running, oldest pending age, paused
340
+ qf.pause_queue(name) # stop claims of your jobs on a queue; running jobs finish
341
+ qf.resume_queue(name) # let claims flow again
250
342
  qf.health_api.get_health()
251
343
  qf.health_api.get_ready()
252
344
  ```
253
345
 
254
- `qf.queues()` (`GET /api/v1/queues`) returns a `QueueStats` per queue that currently has
255
- non-terminal jobs, so you can decide whether to add workers; idle queues are omitted.
256
- `qf.system.list_queues()` is the raw call.
346
+ `qf.whoami()` (`GET /api/v1/whoami`) returns a `WhoAmIResponse` with `tenant` and `access`
347
+ (`read_write`, or `read_only` for a read-only key), handy for validating a credential at startup.
348
+
349
+ `qf.queues()` (`GET /api/v1/queues`) returns a `QueueStats` per queue so you can decide whether to
350
+ add workers. A 0.5.0 or later server lists every queue your tenant has enqueued to (drained ones
351
+ with zero counts); older servers list only queues with non-terminal jobs. Its `paused` flag says
352
+ whether claims are paused for your tenant. `qf.system.list_queues()` is the raw call.
353
+
354
+ `qf.pause_queue(name)` / `qf.resume_queue(name)` (`POST /api/v1/queues/{queue}/pause` and
355
+ `/resume`) gate workers' claims of your tenant's jobs on that queue. Pausing lets running jobs
356
+ finish while pending ones wait, so the queue is drained once the returned `running` count reaches
357
+ zero; resuming wakes parked workers immediately. Both are idempotent and return a
358
+ `QueueControlResponse` (`queue`, `paused`, `running`, `pending`).
359
+
360
+ ```python
361
+ import time
362
+
363
+ qf.pause_queue("orders")
364
+ while qf.pause_queue("orders").running > 0: # idempotent, so it doubles as a status poll
365
+ time.sleep(1)
366
+ deploy_new_handlers()
367
+ qf.resume_queue("orders")
368
+ ```
257
369
 
258
370
  ## Authentication
259
371
 
@@ -287,20 +399,25 @@ except ApiException as err:
287
399
  print(err.status, err.body)
288
400
  ```
289
401
 
290
- ## Known limitation: `stream_job_events`
402
+ ## Known limitations
291
403
 
292
- `JobsApi.stream_job_events` cannot stream: it buffers the whole SSE response until the server
293
- closes it (terminal status or the 15-minute cap). Use `wait_for_job` (polling) instead, or the
294
- `stream_job_events_without_preload_content` variant with a hand-rolled SSE parser.
404
+ **SSE streams buffer.** `JobsApi.stream_job_events` and `WorkflowsApi.stream_workflow_events`
405
+ cannot stream: the generated client buffers the whole SSE response until the server closes it
406
+ (terminal status or the 15-minute cap). Use `wait_for_job` / `wait_for_workflow` (polling) instead,
407
+ or the `*_without_preload_content` variants with a hand-rolled SSE parser.
295
408
 
296
409
  ## Conformance tests
297
410
 
298
411
  `test/test_live.py` runs the facade against a real QueueFlow server (started with the default
299
412
  `queueflow serve`, i.e. `--mode all`, so the built-in `echo` handler is registered). It creates
300
413
  and waits on an `echo` job, checks idempotent re-creation, runs a two-step workflow, exercises the
301
- cron and dead-letter endpoints, reads stats, and runs `run_worker` against a dedicated queue. The
302
- tests are marked `live` and skip themselves unless `QUEUEFLOW_URL` is set, so plain `pytest` and
303
- CI stay offline.
414
+ cron and dead-letter endpoints, reads stats, runs `run_worker` against a dedicated queue, and
415
+ exercises `whoami`, bulk cancel, queue pause/resume and bulk dead-letter replay, each scoped to a
416
+ queue unique to the run. Against a 0.6.0 server it also runs its own worker on a run-unique queue
417
+ to cover a condition-skipped step whose dependents still run, a fan-out with ordered results and
418
+ progress counts, a sub-workflow child linked to its parent, and a retry after a non-retryable
419
+ failure. The tests are marked `live` and skip themselves unless `QUEUEFLOW_URL` is set, so plain
420
+ `pytest` and CI stay offline.
304
421
 
305
422
  ```bash
306
423
  QUEUEFLOW_URL=http://localhost:8000 \
@@ -68,8 +68,9 @@ The generated per-tag clients hang off the facade for anything the helpers do no
68
68
  | `qf.create_job(task, payload=None, priority=None, max_retries=None, timeout=None, queue=None, retry_backoff=None, retry_delay_secs=None, retry_max_delay_secs=None, jitter_factor=None, idempotency_key=None, run_at=None)` | Enqueue a job and return the created `Job`. |
69
69
  | `qf.wait_for_job(job_id, timeout=60.0, interval=0.5)` | Poll until `completed` / `failed` / `cancelled`; raises `WaitTimeout`. |
70
70
  | `qf.jobs.get_job(id)` | Fetch a job. |
71
- | `qf.jobs.list_jobs(status=..., queue=..., limit=..., offset=..., order_by=..., include_total=..., cursor=..., created_after=..., created_before=...)` | List jobs. |
71
+ | `qf.jobs.list_jobs(status=..., queue=..., task_name=..., limit=..., offset=..., order_by=..., include_total=..., cursor=..., created_after=..., created_before=...)` | List jobs. |
72
72
  | `qf.jobs.cancel_job(id)` | Cancel a job. |
73
+ | `qf.cancel_jobs(queue=None, task_name=None, status=None, created_after=None, created_before=None, limit=None)` | Cancel every matching non-terminal job of your tenant, oldest first, up to `limit` (default 100); returns the count. |
73
74
  | `qf.jobs.create_batch_jobs(CreateBatchJobsRequest(...))` | Enqueue up to 1000 jobs at once. |
74
75
 
75
76
  `idempotency_key` is sent as the `Idempotency-Key` header: re-submitting the same key returns the
@@ -78,6 +79,16 @@ original job instead of creating a duplicate. `run_at` (a `datetime`) delays the
78
79
  List responses carry `next_cursor` when there are more pages; pass it back as `cursor` for keyset
79
80
  pagination (cheaper than deep `offset`).
80
81
 
82
+ `cancel_jobs` takes optional filters that combine: `queue`, `task_name`, `status` (`pending`,
83
+ `retrying` or `running`; all three when omitted, a terminal status is a 400) and the half-open
84
+ `datetime` window `[created_after, created_before)`. A count below `limit` means the matching set is
85
+ exhausted, so loop until then to drain a large backlog:
86
+
87
+ ```python
88
+ while qf.cancel_jobs(queue="orders", task_name="send_email", limit=500) == 500:
89
+ pass
90
+ ```
91
+
81
92
  ### Workflows
82
93
 
83
94
  | Method | Description |
@@ -85,26 +96,92 @@ pagination (cheaper than deep `offset`).
85
96
  | `qf.create_workflow(builder_or_request)` | Create a workflow from a `wf()` builder or a raw `CreateWorkflowRequest`. |
86
97
  | `qf.wait_for_workflow(workflow_id, timeout=60.0, interval=0.5)` | Poll until a terminal workflow state. |
87
98
  | `qf.workflows.get_workflow(id)` / `list_workflows(...)` / `cancel_workflow(id)` | Fetch / list / cancel. |
88
- | `qf.workflows.get_workflow_step_states(id)` | Runtime status of every step, in declaration order. |
99
+ | `qf.workflow_steps(workflow_id)` | Live progress of every step, in declaration order: `name`, `status`, `job_id` (the first child, for a fan-out), `error` (`condition not met` for a step skipped by its `when`), `started_at`, `completed_at`, `child_workflow_id` (sub-workflow steps) and `fan_out` (`total`, `completed`, `failed`). |
100
+ | `qf.retry_workflow(workflow_id)` | Re-run a `failed`, `partially_failed` or `cancelled` workflow from where it stopped and return its refreshed record (server 0.6.0). A running or completed workflow is a 409. |
101
+ | `qf.workflows.get_workflow_step_states(id)` | The raw step-states call behind `qf.workflow_steps`. |
89
102
  | `qf.workflows.get_workflow_diagram(id)` | Mermaid (`graph TD`) diagram of the DAG. |
90
103
 
104
+ The `Workflow` record carries `status`, the accumulated `context` (step results keyed by step name),
105
+ the step definitions and, from server 0.6.0, `error_message`, `timeout_secs` and `deadline_at`,
106
+ `retry_count`, and `parent_workflow_id` / `parent_step_name` on a child spawned by a sub-workflow
107
+ step.
108
+
109
+ `retry_workflow` sends every step that did not complete (failed, cancelled, or skipped by a failure
110
+ policy) back to `pending`; completed steps and condition-skipped steps keep their results and do not
111
+ run again. The workflow becomes `running`, its `error_message` clears, `retry_count` increments and
112
+ the deadline, if any, is re-armed from the original `timeout_secs`. Fix the cause first: a retry
113
+ with the same inputs fails the same way.
114
+
91
115
  ### Workflow builder
92
116
 
93
117
  ```python
94
118
  from queueflow.facade import wf
95
119
 
120
+ fulfilment = (
121
+ wf("fulfilment")
122
+ .step("pick", "pick_items", queue="warehouse")
123
+ .step("pack", "pack_parcel", after=["pick"], queue="warehouse")
124
+ .timeout_secs(1800)
125
+ )
126
+
96
127
  dag = (
97
128
  wf("order_123")
98
- .step("validate", "validate_order")
99
- .step("pay", "process_payment", after=["validate"])
100
- .step("ship", "create_shipment", after=["pay"], on_failure="continue")
129
+ .step("validate", "validate_order", payload={"order_id": 123})
130
+ .step("charge", "charge_payment", after=["validate"])
131
+ .step("fraud", "fraud_check", after=["validate"], on_failure="halt")
132
+ .step("review", "queue_for_review", after=["fraud"],
133
+ when={"path": "fraud.flags", "op": "eq", "value": {"manual": True}})
134
+ .step("resize", "resize_image", after=["validate"], queue="images", on_failure="continue",
135
+ fan_out={"path": "validate.images", "item_key": "image"})
136
+ .sub_workflow("fulfil", fulfilment, after=["charge", "fraud"], payload={"warehouse": "eu-1"})
137
+ .step("confirm", "send_confirmation", after=["fulfil"], on_failure="skip")
101
138
  .context(source="web")
139
+ .metadata(app="shop")
140
+ .timeout_secs(3600)
102
141
  )
103
- # .build() runs locally first: duplicate names, dangling deps, and cycles raise
104
- # WorkflowValidationError before any network round-trip.
142
+ # .build() runs locally first: duplicate names, dangling deps, cycles, and malformed
143
+ # when / fan_out shapes raise WorkflowValidationError before any network round-trip.
105
144
  ```
106
145
 
107
- `step()` also accepts `payload=`, `on_success=`, and `config=` (a `models.JobConfig` override).
146
+ | Builder method | Description |
147
+ | --- | --- |
148
+ | `step(name, task, after=None, payload=None, on_failure=None, on_success=None, config=None, queue=None, when=None, fan_out=None)` | A task step. `config` is a `models.JobConfig` override; `on_failure` is `halt` (default), `skip` or `continue`. |
149
+ | `sub_workflow(name, child, after=None, payload=None, on_failure=None, on_success=None, when=None)` | A step that runs `child` (a `wf()` builder, built and validated here, or a `CreateWorkflowRequest`) as an inline child workflow instead of a task. |
150
+ | `context(**kwargs)` / `metadata(**kwargs)` | Seed the shared context / store bookkeeping JSON on the workflow. Both accumulate across calls. |
151
+ | `timeout_secs(secs)` | Whole-workflow deadline. When it passes, every unfinished step is cancelled and the workflow ends `failed` with `error_message` `workflow timed out after <secs>s`. |
152
+ | `build()` | Validate locally and return the `CreateWorkflowRequest`; `qf.create_workflow` calls it for you. |
153
+
154
+ `queue`, `when`, `fan_out`, `sub_workflow` and `timeout_secs` need server 0.6.0 or later.
155
+
156
+ **Queues.** `queue=` puts the step's job(s) on that queue (the server's default queue when
157
+ omitted), so a step can be routed to the workers that know its task.
158
+
159
+ **Conditions.** `when=` gates a step on the workflow context at the moment it becomes ready. It
160
+ takes a dict in the wire shape or a `models.StepCondition`. A leaf names a dotted `path` (step
161
+ results live under their step name, so `validate.ok` is the `ok` key of `validate`'s result) and
162
+ compares the value there with `value` using `op`: `eq` (default with a value), `ne`, `gt`, `gte`,
163
+ `lt`, `lte`, `exists` (default without a value), `contains`, `in`. `{"all": [...]}`,
164
+ `{"any": [...]}` and `{"not": {...}}` combine leaves. A false condition marks the step `skipped`
165
+ with the error `condition not met`; unlike a step skipped by a failure policy, its dependents still
166
+ run, simply without a result for it in `_context`. With this release a leaf's `value` must be a JSON
167
+ object (see [Known limitations](#known-limitations)); `exists` needs no value.
168
+
169
+ **Fan-out.** `fan_out=` (`{"path": ..., "item_key": "item", "max_items": 10000}`, or a
170
+ `models.FanOut`) runs the task once per element of the array at `path`, all children created in one
171
+ atomic claim and run concurrently across the queue's workers, each with its own retries. A child's
172
+ payload is the step payload plus the element under `item_key` and its position under `_index`; its
173
+ metadata carries `fan_out_index` and `fan_out_total`. The step completes with the result
174
+ `{"results": [r0, r1, ...]}` in element order (`_context.<step>.results` downstream); a failed child
175
+ follows the step's `on_failure`. The step state's `fan_out` reports `total`, `completed` and
176
+ `failed`, and its `job_id` is the first child.
177
+
178
+ **Sub-workflows.** `sub_workflow()` runs the child as a workflow of its own, in the same tenant,
179
+ with `parent_workflow_id` and `parent_step_name` set; the parent's step state carries its
180
+ `child_workflow_id`. The child's context is its declared `context`, then the step's `payload`, then
181
+ the parent's whole context under `_parent`. A completed child completes the step with the child's
182
+ final context as the step result; a failed, partially failed or cancelled child fails the step under
183
+ its `on_failure`. Cancelling or timing out the parent cancels a running child. Children nest up to
184
+ 8 levels and carry their own step `queue`s.
108
185
 
109
186
  ### Worker
110
187
 
@@ -197,22 +274,57 @@ Rules the runtime enforces, and that any hand-rolled loop over `qf.worker` must
197
274
 
198
275
  | Method | Description |
199
276
  | --- | --- |
200
- | `qf.dlq.list_dead_letters(...)` / `get_dead_letter(id)` | Inspect terminally-failed jobs. |
277
+ | `qf.dlq.list_dead_letters(task_name=..., replayed=..., limit=..., offset=..., order_by=..., include_total=..., cursor=..., created_after=..., created_before=...)` / `get_dead_letter(id)` | Inspect terminally-failed jobs. `replayed=False` lists only entries not yet replayed, `True` only the replayed ones. |
201
278
  | `qf.replay_dead_letter(id)` | Re-run one as a fresh job and return it (at most once; a second replay is a 409). |
279
+ | `qf.replay_dead_letters(queue=None, task_name=None, created_after=None, created_before=None, limit=None)` | Replay every unreplayed entry matching the filter, oldest first, up to `limit` (default 100). Returns a `ReplayedDeadLetter` per entry: `job_id` on success, otherwise `error`. |
280
+
281
+ ```python
282
+ for r in qf.replay_dead_letters(queue="orders", task_name="send_email"):
283
+ if r.error:
284
+ print("dead letter", r.dead_letter_id, "not replayed:", r.error)
285
+ else:
286
+ print("dead letter", r.dead_letter_id, "replayed as", r.job_id)
287
+ ```
288
+
289
+ Already-replayed entries are never considered again, so repeating the call walks a large backlog;
290
+ an empty list means nothing was left to replay.
202
291
 
203
292
  ### System
204
293
 
205
294
  ```python
295
+ qf.whoami() # the tenant this token is scoped to and its access level
206
296
  qf.system.get_stats() # engine counters
207
297
  qf.system.list_tasks() # registered task handler names
208
- qf.queues() # live per-queue backlog: pending, scheduled, running, oldest pending age
298
+ qf.queues() # live per-queue backlog: pending, scheduled, running, oldest pending age, paused
299
+ qf.pause_queue(name) # stop claims of your jobs on a queue; running jobs finish
300
+ qf.resume_queue(name) # let claims flow again
209
301
  qf.health_api.get_health()
210
302
  qf.health_api.get_ready()
211
303
  ```
212
304
 
213
- `qf.queues()` (`GET /api/v1/queues`) returns a `QueueStats` per queue that currently has
214
- non-terminal jobs, so you can decide whether to add workers; idle queues are omitted.
215
- `qf.system.list_queues()` is the raw call.
305
+ `qf.whoami()` (`GET /api/v1/whoami`) returns a `WhoAmIResponse` with `tenant` and `access`
306
+ (`read_write`, or `read_only` for a read-only key), handy for validating a credential at startup.
307
+
308
+ `qf.queues()` (`GET /api/v1/queues`) returns a `QueueStats` per queue so you can decide whether to
309
+ add workers. A 0.5.0 or later server lists every queue your tenant has enqueued to (drained ones
310
+ with zero counts); older servers list only queues with non-terminal jobs. Its `paused` flag says
311
+ whether claims are paused for your tenant. `qf.system.list_queues()` is the raw call.
312
+
313
+ `qf.pause_queue(name)` / `qf.resume_queue(name)` (`POST /api/v1/queues/{queue}/pause` and
314
+ `/resume`) gate workers' claims of your tenant's jobs on that queue. Pausing lets running jobs
315
+ finish while pending ones wait, so the queue is drained once the returned `running` count reaches
316
+ zero; resuming wakes parked workers immediately. Both are idempotent and return a
317
+ `QueueControlResponse` (`queue`, `paused`, `running`, `pending`).
318
+
319
+ ```python
320
+ import time
321
+
322
+ qf.pause_queue("orders")
323
+ while qf.pause_queue("orders").running > 0: # idempotent, so it doubles as a status poll
324
+ time.sleep(1)
325
+ deploy_new_handlers()
326
+ qf.resume_queue("orders")
327
+ ```
216
328
 
217
329
  ## Authentication
218
330
 
@@ -246,20 +358,25 @@ except ApiException as err:
246
358
  print(err.status, err.body)
247
359
  ```
248
360
 
249
- ## Known limitation: `stream_job_events`
361
+ ## Known limitations
250
362
 
251
- `JobsApi.stream_job_events` cannot stream: it buffers the whole SSE response until the server
252
- closes it (terminal status or the 15-minute cap). Use `wait_for_job` (polling) instead, or the
253
- `stream_job_events_without_preload_content` variant with a hand-rolled SSE parser.
363
+ **SSE streams buffer.** `JobsApi.stream_job_events` and `WorkflowsApi.stream_workflow_events`
364
+ cannot stream: the generated client buffers the whole SSE response until the server closes it
365
+ (terminal status or the 15-minute cap). Use `wait_for_job` / `wait_for_workflow` (polling) instead,
366
+ or the `*_without_preload_content` variants with a hand-rolled SSE parser.
254
367
 
255
368
  ## Conformance tests
256
369
 
257
370
  `test/test_live.py` runs the facade against a real QueueFlow server (started with the default
258
371
  `queueflow serve`, i.e. `--mode all`, so the built-in `echo` handler is registered). It creates
259
372
  and waits on an `echo` job, checks idempotent re-creation, runs a two-step workflow, exercises the
260
- cron and dead-letter endpoints, reads stats, and runs `run_worker` against a dedicated queue. The
261
- tests are marked `live` and skip themselves unless `QUEUEFLOW_URL` is set, so plain `pytest` and
262
- CI stay offline.
373
+ cron and dead-letter endpoints, reads stats, runs `run_worker` against a dedicated queue, and
374
+ exercises `whoami`, bulk cancel, queue pause/resume and bulk dead-letter replay, each scoped to a
375
+ queue unique to the run. Against a 0.6.0 server it also runs its own worker on a run-unique queue
376
+ to cover a condition-skipped step whose dependents still run, a fan-out with ordered results and
377
+ progress counts, a sub-workflow child linked to its parent, and a retry after a non-retryable
378
+ failure. The tests are marked `live` and skip themselves unless `QUEUEFLOW_URL` is set, so plain
379
+ `pytest` and CI stay offline.
263
380
 
264
381
  ```bash
265
382
  QUEUEFLOW_URL=http://localhost:8000 \
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "queueflow"
7
- version = "0.3.0"
7
+ version = "0.5.0"
8
8
  description = "Python client for QueueFlow, a PostgreSQL-native distributed job queue and workflow engine."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -7,14 +7,14 @@
7
7
 
8
8
  REST API for QueueFlow, a PostgreSQL-native distributed job queue and workflow engine.
9
9
 
10
- The version of the OpenAPI document: 0.2.0
10
+ The version of the OpenAPI document: 0.6.0
11
11
  Generated by OpenAPI Generator (https://openapi-generator.tech)
12
12
 
13
13
  Do not edit the class manually.
14
14
  """ # noqa: E501
15
15
 
16
16
 
17
- __version__ = "0.3.0"
17
+ __version__ = "0.5.0"
18
18
 
19
19
  # import apis into sdk package
20
20
  from queueflow.api.cron_api import CronApi
@@ -38,7 +38,10 @@ from queueflow.exceptions import ApiException
38
38
 
39
39
  # import models into sdk package
40
40
  from queueflow.models.backoff_strategy import BackoffStrategy
41
+ from queueflow.models.cancel_jobs_request import CancelJobsRequest
42
+ from queueflow.models.cancel_jobs_response import CancelJobsResponse
41
43
  from queueflow.models.complete_job_request import CompleteJobRequest
44
+ from queueflow.models.condition_op import ConditionOp
42
45
  from queueflow.models.create_batch_jobs_request import CreateBatchJobsRequest
43
46
  from queueflow.models.create_batch_jobs_response import CreateBatchJobsResponse
44
47
  from queueflow.models.create_cron_request import CreateCronRequest
@@ -51,6 +54,8 @@ from queueflow.models.cron_schedule import CronSchedule
51
54
  from queueflow.models.dead_letter import DeadLetter
52
55
  from queueflow.models.error_body import ErrorBody
53
56
  from queueflow.models.fail_job_request import FailJobRequest
57
+ from queueflow.models.fan_out import FanOut
58
+ from queueflow.models.fan_out_progress import FanOutProgress
54
59
  from queueflow.models.health_status import HealthStatus
55
60
  from queueflow.models.heartbeat_request import HeartbeatRequest
56
61
  from queueflow.models.heartbeat_response import HeartbeatResponse
@@ -67,15 +72,22 @@ from queueflow.models.list_jobs_response import ListJobsResponse
67
72
  from queueflow.models.list_workflows_response import ListWorkflowsResponse
68
73
  from queueflow.models.on_failure import OnFailure
69
74
  from queueflow.models.on_success import OnSuccess
75
+ from queueflow.models.queue_control_response import QueueControlResponse
70
76
  from queueflow.models.queue_stats import QueueStats
71
77
  from queueflow.models.queues_response import QueuesResponse
72
78
  from queueflow.models.ready_status import ReadyStatus
73
79
  from queueflow.models.replay_dead_letter_response import ReplayDeadLetterResponse
80
+ from queueflow.models.replay_dead_letters_request import ReplayDeadLettersRequest
81
+ from queueflow.models.replay_dead_letters_response import ReplayDeadLettersResponse
82
+ from queueflow.models.replayed_dead_letter import ReplayedDeadLetter
74
83
  from queueflow.models.stats_snapshot import StatsSnapshot
84
+ from queueflow.models.step_condition import StepCondition
75
85
  from queueflow.models.step_status import StepStatus
76
86
  from queueflow.models.tasks_response import TasksResponse
87
+ from queueflow.models.who_am_i_response import WhoAmIResponse
77
88
  from queueflow.models.workflow import Workflow
78
89
  from queueflow.models.workflow_diagram_response import WorkflowDiagramResponse
90
+ from queueflow.models.workflow_event import WorkflowEvent
79
91
  from queueflow.models.workflow_status import WorkflowStatus
80
92
  from queueflow.models.workflow_step import WorkflowStep
81
93
  from queueflow.models.workflow_step_state import WorkflowStepState
@@ -5,7 +5,7 @@
5
5
 
6
6
  REST API for QueueFlow, a PostgreSQL-native distributed job queue and workflow engine.
7
7
 
8
- The version of the OpenAPI document: 0.2.0
8
+ The version of the OpenAPI document: 0.6.0
9
9
  Generated by OpenAPI Generator (https://openapi-generator.tech)
10
10
 
11
11
  Do not edit the class manually.
@@ -862,6 +862,8 @@ class CronApi:
862
862
  self,
863
863
  status: Annotated[Optional[StrictStr], Field(description="Filter by status (e.g. `pending`, `completed`).")] = None,
864
864
  queue: Annotated[Optional[StrictStr], Field(description="Filter by queue name (jobs only).")] = None,
865
+ task_name: Annotated[Optional[StrictStr], Field(description="Filter by task name (jobs and dead letters).")] = None,
866
+ replayed: Annotated[Optional[StrictBool], Field(description="Dead letters only: `false` lists entries not yet replayed, `true` the replayed ones; both when omitted.")] = None,
865
867
  limit: Annotated[Optional[StrictInt], Field(description="Page size, 1..=100 (default 50).")] = None,
866
868
  offset: Annotated[Optional[StrictInt], Field(description="Number of records to skip (default 0).")] = None,
867
869
  order_by: Annotated[Optional[StrictStr], Field(description="`created_at ASC` or `created_at DESC` (default DESC).")] = None,
@@ -889,6 +891,10 @@ class CronApi:
889
891
  :type status: str
890
892
  :param queue: Filter by queue name (jobs only).
891
893
  :type queue: str
894
+ :param task_name: Filter by task name (jobs and dead letters).
895
+ :type task_name: str
896
+ :param replayed: Dead letters only: `false` lists entries not yet replayed, `true` the replayed ones; both when omitted.
897
+ :type replayed: bool
892
898
  :param limit: Page size, 1..=100 (default 50).
893
899
  :type limit: int
894
900
  :param offset: Number of records to skip (default 0).
@@ -928,6 +934,8 @@ class CronApi:
928
934
  _param = self._list_crons_serialize(
929
935
  status=status,
930
936
  queue=queue,
937
+ task_name=task_name,
938
+ replayed=replayed,
931
939
  limit=limit,
932
940
  offset=offset,
933
941
  order_by=order_by,
@@ -961,6 +969,8 @@ class CronApi:
961
969
  self,
962
970
  status: Annotated[Optional[StrictStr], Field(description="Filter by status (e.g. `pending`, `completed`).")] = None,
963
971
  queue: Annotated[Optional[StrictStr], Field(description="Filter by queue name (jobs only).")] = None,
972
+ task_name: Annotated[Optional[StrictStr], Field(description="Filter by task name (jobs and dead letters).")] = None,
973
+ replayed: Annotated[Optional[StrictBool], Field(description="Dead letters only: `false` lists entries not yet replayed, `true` the replayed ones; both when omitted.")] = None,
964
974
  limit: Annotated[Optional[StrictInt], Field(description="Page size, 1..=100 (default 50).")] = None,
965
975
  offset: Annotated[Optional[StrictInt], Field(description="Number of records to skip (default 0).")] = None,
966
976
  order_by: Annotated[Optional[StrictStr], Field(description="`created_at ASC` or `created_at DESC` (default DESC).")] = None,
@@ -988,6 +998,10 @@ class CronApi:
988
998
  :type status: str
989
999
  :param queue: Filter by queue name (jobs only).
990
1000
  :type queue: str
1001
+ :param task_name: Filter by task name (jobs and dead letters).
1002
+ :type task_name: str
1003
+ :param replayed: Dead letters only: `false` lists entries not yet replayed, `true` the replayed ones; both when omitted.
1004
+ :type replayed: bool
991
1005
  :param limit: Page size, 1..=100 (default 50).
992
1006
  :type limit: int
993
1007
  :param offset: Number of records to skip (default 0).
@@ -1027,6 +1041,8 @@ class CronApi:
1027
1041
  _param = self._list_crons_serialize(
1028
1042
  status=status,
1029
1043
  queue=queue,
1044
+ task_name=task_name,
1045
+ replayed=replayed,
1030
1046
  limit=limit,
1031
1047
  offset=offset,
1032
1048
  order_by=order_by,
@@ -1060,6 +1076,8 @@ class CronApi:
1060
1076
  self,
1061
1077
  status: Annotated[Optional[StrictStr], Field(description="Filter by status (e.g. `pending`, `completed`).")] = None,
1062
1078
  queue: Annotated[Optional[StrictStr], Field(description="Filter by queue name (jobs only).")] = None,
1079
+ task_name: Annotated[Optional[StrictStr], Field(description="Filter by task name (jobs and dead letters).")] = None,
1080
+ replayed: Annotated[Optional[StrictBool], Field(description="Dead letters only: `false` lists entries not yet replayed, `true` the replayed ones; both when omitted.")] = None,
1063
1081
  limit: Annotated[Optional[StrictInt], Field(description="Page size, 1..=100 (default 50).")] = None,
1064
1082
  offset: Annotated[Optional[StrictInt], Field(description="Number of records to skip (default 0).")] = None,
1065
1083
  order_by: Annotated[Optional[StrictStr], Field(description="`created_at ASC` or `created_at DESC` (default DESC).")] = None,
@@ -1087,6 +1105,10 @@ class CronApi:
1087
1105
  :type status: str
1088
1106
  :param queue: Filter by queue name (jobs only).
1089
1107
  :type queue: str
1108
+ :param task_name: Filter by task name (jobs and dead letters).
1109
+ :type task_name: str
1110
+ :param replayed: Dead letters only: `false` lists entries not yet replayed, `true` the replayed ones; both when omitted.
1111
+ :type replayed: bool
1090
1112
  :param limit: Page size, 1..=100 (default 50).
1091
1113
  :type limit: int
1092
1114
  :param offset: Number of records to skip (default 0).
@@ -1126,6 +1148,8 @@ class CronApi:
1126
1148
  _param = self._list_crons_serialize(
1127
1149
  status=status,
1128
1150
  queue=queue,
1151
+ task_name=task_name,
1152
+ replayed=replayed,
1129
1153
  limit=limit,
1130
1154
  offset=offset,
1131
1155
  order_by=order_by,
@@ -1154,6 +1178,8 @@ class CronApi:
1154
1178
  self,
1155
1179
  status,
1156
1180
  queue,
1181
+ task_name,
1182
+ replayed,
1157
1183
  limit,
1158
1184
  offset,
1159
1185
  order_by,
@@ -1191,6 +1217,14 @@ class CronApi:
1191
1217
 
1192
1218
  _query_params.append(('queue', queue))
1193
1219
 
1220
+ if task_name is not None:
1221
+
1222
+ _query_params.append(('task_name', task_name))
1223
+
1224
+ if replayed is not None:
1225
+
1226
+ _query_params.append(('replayed', replayed))
1227
+
1194
1228
  if limit is not None:
1195
1229
 
1196
1230
  _query_params.append(('limit', limit))