queueflow 0.2.2__tar.gz → 0.4.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.
- {queueflow-0.2.2/queueflow.egg-info → queueflow-0.4.0}/PKG-INFO +98 -9
- {queueflow-0.2.2 → queueflow-0.4.0}/README.md +97 -8
- {queueflow-0.2.2 → queueflow-0.4.0}/pyproject.toml +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/__init__.py +11 -2
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/api/cron_api.py +35 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/api/dlq_api.py +314 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/api/health_api.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/api/jobs_api.py +317 -1
- queueflow-0.4.0/queueflow/api/system_api.py +1551 -0
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/api/worker_api.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/api/workflows_api.py +35 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/api_client.py +2 -2
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/configuration.py +3 -3
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/exceptions.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/facade.py +305 -41
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/__init__.py +10 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/backoff_strategy.py +1 -1
- queueflow-0.4.0/queueflow/models/cancel_jobs_request.py +128 -0
- queueflow-0.4.0/queueflow/models/cancel_jobs_response.py +88 -0
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/complete_job_request.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/create_batch_jobs_request.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/create_batch_jobs_response.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/create_cron_request.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/create_cron_response.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/create_job_request.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/create_job_response.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/create_workflow_request.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/create_workflow_response.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/cron_schedule.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/dead_letter.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/error_body.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/fail_job_request.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/health_status.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/heartbeat_request.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/heartbeat_response.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/job.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/job_config.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/job_config_request.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/job_status.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/lease_jobs_request.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/lease_jobs_response.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/leased_job.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/list_crons_response.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/list_dead_letters_response.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/list_jobs_response.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/list_workflows_response.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/on_failure.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/on_success.py +1 -1
- queueflow-0.4.0/queueflow/models/queue_control_response.py +94 -0
- queueflow-0.4.0/queueflow/models/queue_stats.py +103 -0
- queueflow-0.4.0/queueflow/models/queues_response.py +95 -0
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/ready_status.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/replay_dead_letter_response.py +1 -1
- queueflow-0.4.0/queueflow/models/replay_dead_letters_request.py +121 -0
- queueflow-0.4.0/queueflow/models/replay_dead_letters_response.py +98 -0
- queueflow-0.4.0/queueflow/models/replayed_dead_letter.py +101 -0
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/stats_snapshot.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/step_status.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/tasks_response.py +1 -1
- queueflow-0.4.0/queueflow/models/who_am_i_response.py +89 -0
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/workflow.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/workflow_diagram_response.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/workflow_status.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/workflow_step.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/workflow_step_state.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/models/workflow_step_states_response.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/rest.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0/queueflow.egg-info}/PKG-INFO +98 -9
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow.egg-info/SOURCES.txt +18 -0
- {queueflow-0.2.2 → queueflow-0.4.0}/setup.py +3 -3
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_backoff_strategy.py +1 -1
- queueflow-0.4.0/test/test_cancel_jobs_request.py +56 -0
- queueflow-0.4.0/test/test_cancel_jobs_response.py +52 -0
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_complete_job_request.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_create_batch_jobs_request.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_create_batch_jobs_response.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_create_cron_request.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_create_cron_response.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_create_job_request.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_create_job_response.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_create_workflow_request.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_create_workflow_response.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_cron_api.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_cron_schedule.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_dead_letter.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_dlq_api.py +7 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_error_body.py +1 -1
- queueflow-0.4.0/test/test_facade.py +826 -0
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_fail_job_request.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_health_api.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_health_status.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_heartbeat_request.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_heartbeat_response.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_job.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_job_config.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_job_config_request.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_job_status.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_jobs_api.py +7 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_lease_jobs_request.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_lease_jobs_response.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_leased_job.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_list_crons_response.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_list_dead_letters_response.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_list_jobs_response.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_list_workflows_response.py +1 -1
- queueflow-0.4.0/test/test_live.py +372 -0
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_on_failure.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_on_success.py +1 -1
- queueflow-0.4.0/test/test_queue_control_response.py +58 -0
- queueflow-0.4.0/test/test_queue_stats.py +60 -0
- queueflow-0.4.0/test/test_queues_response.py +68 -0
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_ready_status.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_replay_dead_letter_response.py +1 -1
- queueflow-0.4.0/test/test_replay_dead_letters_request.py +55 -0
- queueflow-0.4.0/test/test_replay_dead_letters_response.py +64 -0
- queueflow-0.4.0/test/test_replayed_dead_letter.py +54 -0
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_stats_snapshot.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_step_status.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_system_api.py +25 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_tasks_response.py +1 -1
- queueflow-0.4.0/test/test_who_am_i_response.py +54 -0
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_worker_api.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_workflow.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_workflow_diagram_response.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_workflow_status.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_workflow_step.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_workflow_step_state.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_workflow_step_states_response.py +1 -1
- {queueflow-0.2.2 → queueflow-0.4.0}/test/test_workflows_api.py +1 -1
- queueflow-0.2.2/queueflow/api/system_api.py +0 -523
- queueflow-0.2.2/test/test_facade.py +0 -426
- queueflow-0.2.2/test/test_live.py +0 -167
- {queueflow-0.2.2 → queueflow-0.4.0}/LICENSE +0 -0
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/api/__init__.py +0 -0
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/api_response.py +0 -0
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow/py.typed +0 -0
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow.egg-info/dependency_links.txt +0 -0
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow.egg-info/requires.txt +0 -0
- {queueflow-0.2.2 → queueflow-0.4.0}/queueflow.egg-info/top_level.txt +0 -0
- {queueflow-0.2.2 → queueflow-0.4.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: queueflow
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.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
|
|
@@ -93,9 +93,12 @@ qf = QueueFlow(
|
|
|
93
93
|
base_url, # required, e.g. "http://localhost:8000"
|
|
94
94
|
token, # required: tenant bearer token (any non-empty token on the dev server)
|
|
95
95
|
worker_token=None, # credential for qf.worker and run_worker (defaults to token; dev mode only)
|
|
96
|
+
timeout=30.0, # default per-request timeout in seconds; a (connect, read) pair or None also work
|
|
96
97
|
)
|
|
97
98
|
```
|
|
98
99
|
|
|
100
|
+
Any generated api method accepts `_request_timeout=` to override the default for one call.
|
|
101
|
+
|
|
99
102
|
The generated per-tag clients hang off the facade for anything the helpers do not cover:
|
|
100
103
|
`qf.jobs`, `qf.workflows`, `qf.worker`, `qf.cron`, `qf.dlq`, `qf.system`, `qf.health_api`.
|
|
101
104
|
|
|
@@ -106,8 +109,9 @@ The generated per-tag clients hang off the facade for anything the helpers do no
|
|
|
106
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`. |
|
|
107
110
|
| `qf.wait_for_job(job_id, timeout=60.0, interval=0.5)` | Poll until `completed` / `failed` / `cancelled`; raises `WaitTimeout`. |
|
|
108
111
|
| `qf.jobs.get_job(id)` | Fetch a job. |
|
|
109
|
-
| `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. |
|
|
110
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. |
|
|
111
115
|
| `qf.jobs.create_batch_jobs(CreateBatchJobsRequest(...))` | Enqueue up to 1000 jobs at once. |
|
|
112
116
|
|
|
113
117
|
`idempotency_key` is sent as the `Idempotency-Key` header: re-submitting the same key returns the
|
|
@@ -116,6 +120,16 @@ original job instead of creating a duplicate. `run_at` (a `datetime`) delays the
|
|
|
116
120
|
List responses carry `next_cursor` when there are more pages; pass it back as `cursor` for keyset
|
|
117
121
|
pagination (cheaper than deep `offset`).
|
|
118
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
|
+
|
|
119
133
|
### Workflows
|
|
120
134
|
|
|
121
135
|
| Method | Description |
|
|
@@ -168,16 +182,50 @@ def transcode(job, ctx):
|
|
|
168
182
|
return {"ok": True}
|
|
169
183
|
|
|
170
184
|
stop = threading.Event() # set it (e.g. from a signal handler) to drain
|
|
171
|
-
qf.run_worker(
|
|
185
|
+
result = qf.run_worker(
|
|
186
|
+
"orders",
|
|
187
|
+
{"send_email": send_email, "transcode": transcode},
|
|
188
|
+
stop=stop,
|
|
189
|
+
concurrency=4,
|
|
190
|
+
)
|
|
191
|
+
for job in result.unfinished: # handlers still running when the drain gave up
|
|
192
|
+
print("abandoned", job.id)
|
|
172
193
|
```
|
|
173
194
|
|
|
174
|
-
|
|
175
|
-
|
|
195
|
+
```python
|
|
196
|
+
run_worker(
|
|
197
|
+
queue, handlers,
|
|
198
|
+
lease_secs=30, # lease per job; each in-flight job is heartbeated at half this
|
|
199
|
+
wait_secs=20, # long-poll wait when the queue is empty
|
|
200
|
+
stop=None, # threading.Event: set it to stop
|
|
201
|
+
on_error=None, # called on transient lease errors (default: throttled log)
|
|
202
|
+
concurrency=1, # handlers running at once, on a ThreadPoolExecutor
|
|
203
|
+
poll_wait_secs=5, # cap on wait_secs while `stop` is provided (bounds stop latency)
|
|
204
|
+
drain_timeout=30.0, # seconds to wait for in-flight handlers after stop
|
|
205
|
+
) -> WorkerRunResult # .unfinished: jobs still running when the drain gave up
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
`run_worker` heartbeats each in-flight job at half the lease interval, stops reporting when the
|
|
176
209
|
lease is lost, and applies the server's retry policy on errors. Handlers take `(job)` or
|
|
177
210
|
`(job, ctx)` and return the result dict (or `None` for `{}`). Delivery is at-least-once, so make
|
|
178
211
|
handlers idempotent. The low-level calls (`qf.worker.lease_jobs`, `heartbeat_job`, `complete_job`,
|
|
179
212
|
`fail_job`) are also exposed.
|
|
180
213
|
|
|
214
|
+
Concurrency: up to `concurrency` handlers run at once. Each lease asks for as many jobs as there
|
|
215
|
+
are free slots, and no lease is issued while every slot is busy.
|
|
216
|
+
|
|
217
|
+
Stop and drain: set `stop`. The generated urllib3 client cannot cancel an in-flight long poll, so
|
|
218
|
+
while `stop` is provided each lease waits at most `poll_wait_secs` (never more than `wait_secs`),
|
|
219
|
+
which bounds stop latency to about that long; without `stop`, the full `wait_secs` is used. After
|
|
220
|
+
the loop exits, handlers already running get up to `drain_timeout` seconds to finish and report.
|
|
221
|
+
Any handler still running after that is abandoned: its `ctx.cancelled` flips, nothing is reported
|
|
222
|
+
(the lease expires and the server redelivers), and the job is listed in the returned
|
|
223
|
+
`WorkerRunResult.unfinished`. Python cannot kill a thread, so an abandoned handler keeps its pool
|
|
224
|
+
thread until it returns; long handlers should check `ctx.cancelled`.
|
|
225
|
+
|
|
226
|
+
Timeouts: every request uses the client-wide `timeout` (30s by default, see Client), except the
|
|
227
|
+
lease long poll, which gets its own timeout of the effective wait plus 35 seconds.
|
|
228
|
+
|
|
181
229
|
Rules the runtime enforces, and that any hand-rolled loop over `qf.worker` must honour too:
|
|
182
230
|
|
|
183
231
|
1. Worker routes authenticate with the **worker token**, not a tenant token. Pass it as
|
|
@@ -201,18 +249,58 @@ Rules the runtime enforces, and that any hand-rolled loop over `qf.worker` must
|
|
|
201
249
|
|
|
202
250
|
| Method | Description |
|
|
203
251
|
| --- | --- |
|
|
204
|
-
| `qf.dlq.list_dead_letters(
|
|
252
|
+
| `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. |
|
|
205
253
|
| `qf.replay_dead_letter(id)` | Re-run one as a fresh job and return it (at most once; a second replay is a 409). |
|
|
254
|
+
| `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`. |
|
|
255
|
+
|
|
256
|
+
```python
|
|
257
|
+
for r in qf.replay_dead_letters(queue="orders", task_name="send_email"):
|
|
258
|
+
if r.error:
|
|
259
|
+
print("dead letter", r.dead_letter_id, "not replayed:", r.error)
|
|
260
|
+
else:
|
|
261
|
+
print("dead letter", r.dead_letter_id, "replayed as", r.job_id)
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Already-replayed entries are never considered again, so repeating the call walks a large backlog;
|
|
265
|
+
an empty list means nothing was left to replay.
|
|
206
266
|
|
|
207
267
|
### System
|
|
208
268
|
|
|
209
269
|
```python
|
|
270
|
+
qf.whoami() # the tenant this token is scoped to and its access level
|
|
210
271
|
qf.system.get_stats() # engine counters
|
|
211
272
|
qf.system.list_tasks() # registered task handler names
|
|
273
|
+
qf.queues() # live per-queue backlog: pending, scheduled, running, oldest pending age, paused
|
|
274
|
+
qf.pause_queue(name) # stop claims of your jobs on a queue; running jobs finish
|
|
275
|
+
qf.resume_queue(name) # let claims flow again
|
|
212
276
|
qf.health_api.get_health()
|
|
213
277
|
qf.health_api.get_ready()
|
|
214
278
|
```
|
|
215
279
|
|
|
280
|
+
`qf.whoami()` (`GET /api/v1/whoami`) returns a `WhoAmIResponse` with `tenant` and `access`
|
|
281
|
+
(`read_write`, or `read_only` for a read-only key), handy for validating a credential at startup.
|
|
282
|
+
|
|
283
|
+
`qf.queues()` (`GET /api/v1/queues`) returns a `QueueStats` per queue so you can decide whether to
|
|
284
|
+
add workers. A 0.5.0 or later server lists every queue your tenant has enqueued to (drained ones
|
|
285
|
+
with zero counts); older servers list only queues with non-terminal jobs. Its `paused` flag says
|
|
286
|
+
whether claims are paused for your tenant. `qf.system.list_queues()` is the raw call.
|
|
287
|
+
|
|
288
|
+
`qf.pause_queue(name)` / `qf.resume_queue(name)` (`POST /api/v1/queues/{queue}/pause` and
|
|
289
|
+
`/resume`) gate workers' claims of your tenant's jobs on that queue. Pausing lets running jobs
|
|
290
|
+
finish while pending ones wait, so the queue is drained once the returned `running` count reaches
|
|
291
|
+
zero; resuming wakes parked workers immediately. Both are idempotent and return a
|
|
292
|
+
`QueueControlResponse` (`queue`, `paused`, `running`, `pending`).
|
|
293
|
+
|
|
294
|
+
```python
|
|
295
|
+
import time
|
|
296
|
+
|
|
297
|
+
qf.pause_queue("orders")
|
|
298
|
+
while qf.pause_queue("orders").running > 0: # idempotent, so it doubles as a status poll
|
|
299
|
+
time.sleep(1)
|
|
300
|
+
deploy_new_handlers()
|
|
301
|
+
qf.resume_queue("orders")
|
|
302
|
+
```
|
|
303
|
+
|
|
216
304
|
## Authentication
|
|
217
305
|
|
|
218
306
|
Every request carries `Authorization: Bearer <token>`. Two credentials exist:
|
|
@@ -256,9 +344,10 @@ closes it (terminal status or the 15-minute cap). Use `wait_for_job` (polling) i
|
|
|
256
344
|
`test/test_live.py` runs the facade against a real QueueFlow server (started with the default
|
|
257
345
|
`queueflow serve`, i.e. `--mode all`, so the built-in `echo` handler is registered). It creates
|
|
258
346
|
and waits on an `echo` job, checks idempotent re-creation, runs a two-step workflow, exercises the
|
|
259
|
-
cron and dead-letter endpoints, reads stats,
|
|
260
|
-
|
|
261
|
-
|
|
347
|
+
cron and dead-letter endpoints, reads stats, runs `run_worker` against a dedicated queue, and
|
|
348
|
+
exercises `whoami`, bulk cancel, queue pause/resume and bulk dead-letter replay, each scoped to a
|
|
349
|
+
queue unique to the run. The tests are marked `live` and skip themselves unless `QUEUEFLOW_URL` is
|
|
350
|
+
set, so plain `pytest` and CI stay offline.
|
|
262
351
|
|
|
263
352
|
```bash
|
|
264
353
|
QUEUEFLOW_URL=http://localhost:8000 \
|
|
@@ -52,9 +52,12 @@ qf = QueueFlow(
|
|
|
52
52
|
base_url, # required, e.g. "http://localhost:8000"
|
|
53
53
|
token, # required: tenant bearer token (any non-empty token on the dev server)
|
|
54
54
|
worker_token=None, # credential for qf.worker and run_worker (defaults to token; dev mode only)
|
|
55
|
+
timeout=30.0, # default per-request timeout in seconds; a (connect, read) pair or None also work
|
|
55
56
|
)
|
|
56
57
|
```
|
|
57
58
|
|
|
59
|
+
Any generated api method accepts `_request_timeout=` to override the default for one call.
|
|
60
|
+
|
|
58
61
|
The generated per-tag clients hang off the facade for anything the helpers do not cover:
|
|
59
62
|
`qf.jobs`, `qf.workflows`, `qf.worker`, `qf.cron`, `qf.dlq`, `qf.system`, `qf.health_api`.
|
|
60
63
|
|
|
@@ -65,8 +68,9 @@ The generated per-tag clients hang off the facade for anything the helpers do no
|
|
|
65
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`. |
|
|
66
69
|
| `qf.wait_for_job(job_id, timeout=60.0, interval=0.5)` | Poll until `completed` / `failed` / `cancelled`; raises `WaitTimeout`. |
|
|
67
70
|
| `qf.jobs.get_job(id)` | Fetch a job. |
|
|
68
|
-
| `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. |
|
|
69
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. |
|
|
70
74
|
| `qf.jobs.create_batch_jobs(CreateBatchJobsRequest(...))` | Enqueue up to 1000 jobs at once. |
|
|
71
75
|
|
|
72
76
|
`idempotency_key` is sent as the `Idempotency-Key` header: re-submitting the same key returns the
|
|
@@ -75,6 +79,16 @@ original job instead of creating a duplicate. `run_at` (a `datetime`) delays the
|
|
|
75
79
|
List responses carry `next_cursor` when there are more pages; pass it back as `cursor` for keyset
|
|
76
80
|
pagination (cheaper than deep `offset`).
|
|
77
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
|
+
|
|
78
92
|
### Workflows
|
|
79
93
|
|
|
80
94
|
| Method | Description |
|
|
@@ -127,16 +141,50 @@ def transcode(job, ctx):
|
|
|
127
141
|
return {"ok": True}
|
|
128
142
|
|
|
129
143
|
stop = threading.Event() # set it (e.g. from a signal handler) to drain
|
|
130
|
-
qf.run_worker(
|
|
144
|
+
result = qf.run_worker(
|
|
145
|
+
"orders",
|
|
146
|
+
{"send_email": send_email, "transcode": transcode},
|
|
147
|
+
stop=stop,
|
|
148
|
+
concurrency=4,
|
|
149
|
+
)
|
|
150
|
+
for job in result.unfinished: # handlers still running when the drain gave up
|
|
151
|
+
print("abandoned", job.id)
|
|
131
152
|
```
|
|
132
153
|
|
|
133
|
-
|
|
134
|
-
|
|
154
|
+
```python
|
|
155
|
+
run_worker(
|
|
156
|
+
queue, handlers,
|
|
157
|
+
lease_secs=30, # lease per job; each in-flight job is heartbeated at half this
|
|
158
|
+
wait_secs=20, # long-poll wait when the queue is empty
|
|
159
|
+
stop=None, # threading.Event: set it to stop
|
|
160
|
+
on_error=None, # called on transient lease errors (default: throttled log)
|
|
161
|
+
concurrency=1, # handlers running at once, on a ThreadPoolExecutor
|
|
162
|
+
poll_wait_secs=5, # cap on wait_secs while `stop` is provided (bounds stop latency)
|
|
163
|
+
drain_timeout=30.0, # seconds to wait for in-flight handlers after stop
|
|
164
|
+
) -> WorkerRunResult # .unfinished: jobs still running when the drain gave up
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
`run_worker` heartbeats each in-flight job at half the lease interval, stops reporting when the
|
|
135
168
|
lease is lost, and applies the server's retry policy on errors. Handlers take `(job)` or
|
|
136
169
|
`(job, ctx)` and return the result dict (or `None` for `{}`). Delivery is at-least-once, so make
|
|
137
170
|
handlers idempotent. The low-level calls (`qf.worker.lease_jobs`, `heartbeat_job`, `complete_job`,
|
|
138
171
|
`fail_job`) are also exposed.
|
|
139
172
|
|
|
173
|
+
Concurrency: up to `concurrency` handlers run at once. Each lease asks for as many jobs as there
|
|
174
|
+
are free slots, and no lease is issued while every slot is busy.
|
|
175
|
+
|
|
176
|
+
Stop and drain: set `stop`. The generated urllib3 client cannot cancel an in-flight long poll, so
|
|
177
|
+
while `stop` is provided each lease waits at most `poll_wait_secs` (never more than `wait_secs`),
|
|
178
|
+
which bounds stop latency to about that long; without `stop`, the full `wait_secs` is used. After
|
|
179
|
+
the loop exits, handlers already running get up to `drain_timeout` seconds to finish and report.
|
|
180
|
+
Any handler still running after that is abandoned: its `ctx.cancelled` flips, nothing is reported
|
|
181
|
+
(the lease expires and the server redelivers), and the job is listed in the returned
|
|
182
|
+
`WorkerRunResult.unfinished`. Python cannot kill a thread, so an abandoned handler keeps its pool
|
|
183
|
+
thread until it returns; long handlers should check `ctx.cancelled`.
|
|
184
|
+
|
|
185
|
+
Timeouts: every request uses the client-wide `timeout` (30s by default, see Client), except the
|
|
186
|
+
lease long poll, which gets its own timeout of the effective wait plus 35 seconds.
|
|
187
|
+
|
|
140
188
|
Rules the runtime enforces, and that any hand-rolled loop over `qf.worker` must honour too:
|
|
141
189
|
|
|
142
190
|
1. Worker routes authenticate with the **worker token**, not a tenant token. Pass it as
|
|
@@ -160,18 +208,58 @@ Rules the runtime enforces, and that any hand-rolled loop over `qf.worker` must
|
|
|
160
208
|
|
|
161
209
|
| Method | Description |
|
|
162
210
|
| --- | --- |
|
|
163
|
-
| `qf.dlq.list_dead_letters(
|
|
211
|
+
| `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. |
|
|
164
212
|
| `qf.replay_dead_letter(id)` | Re-run one as a fresh job and return it (at most once; a second replay is a 409). |
|
|
213
|
+
| `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`. |
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
for r in qf.replay_dead_letters(queue="orders", task_name="send_email"):
|
|
217
|
+
if r.error:
|
|
218
|
+
print("dead letter", r.dead_letter_id, "not replayed:", r.error)
|
|
219
|
+
else:
|
|
220
|
+
print("dead letter", r.dead_letter_id, "replayed as", r.job_id)
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Already-replayed entries are never considered again, so repeating the call walks a large backlog;
|
|
224
|
+
an empty list means nothing was left to replay.
|
|
165
225
|
|
|
166
226
|
### System
|
|
167
227
|
|
|
168
228
|
```python
|
|
229
|
+
qf.whoami() # the tenant this token is scoped to and its access level
|
|
169
230
|
qf.system.get_stats() # engine counters
|
|
170
231
|
qf.system.list_tasks() # registered task handler names
|
|
232
|
+
qf.queues() # live per-queue backlog: pending, scheduled, running, oldest pending age, paused
|
|
233
|
+
qf.pause_queue(name) # stop claims of your jobs on a queue; running jobs finish
|
|
234
|
+
qf.resume_queue(name) # let claims flow again
|
|
171
235
|
qf.health_api.get_health()
|
|
172
236
|
qf.health_api.get_ready()
|
|
173
237
|
```
|
|
174
238
|
|
|
239
|
+
`qf.whoami()` (`GET /api/v1/whoami`) returns a `WhoAmIResponse` with `tenant` and `access`
|
|
240
|
+
(`read_write`, or `read_only` for a read-only key), handy for validating a credential at startup.
|
|
241
|
+
|
|
242
|
+
`qf.queues()` (`GET /api/v1/queues`) returns a `QueueStats` per queue so you can decide whether to
|
|
243
|
+
add workers. A 0.5.0 or later server lists every queue your tenant has enqueued to (drained ones
|
|
244
|
+
with zero counts); older servers list only queues with non-terminal jobs. Its `paused` flag says
|
|
245
|
+
whether claims are paused for your tenant. `qf.system.list_queues()` is the raw call.
|
|
246
|
+
|
|
247
|
+
`qf.pause_queue(name)` / `qf.resume_queue(name)` (`POST /api/v1/queues/{queue}/pause` and
|
|
248
|
+
`/resume`) gate workers' claims of your tenant's jobs on that queue. Pausing lets running jobs
|
|
249
|
+
finish while pending ones wait, so the queue is drained once the returned `running` count reaches
|
|
250
|
+
zero; resuming wakes parked workers immediately. Both are idempotent and return a
|
|
251
|
+
`QueueControlResponse` (`queue`, `paused`, `running`, `pending`).
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
import time
|
|
255
|
+
|
|
256
|
+
qf.pause_queue("orders")
|
|
257
|
+
while qf.pause_queue("orders").running > 0: # idempotent, so it doubles as a status poll
|
|
258
|
+
time.sleep(1)
|
|
259
|
+
deploy_new_handlers()
|
|
260
|
+
qf.resume_queue("orders")
|
|
261
|
+
```
|
|
262
|
+
|
|
175
263
|
## Authentication
|
|
176
264
|
|
|
177
265
|
Every request carries `Authorization: Bearer <token>`. Two credentials exist:
|
|
@@ -215,9 +303,10 @@ closes it (terminal status or the 15-minute cap). Use `wait_for_job` (polling) i
|
|
|
215
303
|
`test/test_live.py` runs the facade against a real QueueFlow server (started with the default
|
|
216
304
|
`queueflow serve`, i.e. `--mode all`, so the built-in `echo` handler is registered). It creates
|
|
217
305
|
and waits on an `echo` job, checks idempotent re-creation, runs a two-step workflow, exercises the
|
|
218
|
-
cron and dead-letter endpoints, reads stats,
|
|
219
|
-
|
|
220
|
-
|
|
306
|
+
cron and dead-letter endpoints, reads stats, runs `run_worker` against a dedicated queue, and
|
|
307
|
+
exercises `whoami`, bulk cancel, queue pause/resume and bulk dead-letter replay, each scoped to a
|
|
308
|
+
queue unique to the run. The tests are marked `live` and skip themselves unless `QUEUEFLOW_URL` is
|
|
309
|
+
set, so plain `pytest` and CI stay offline.
|
|
221
310
|
|
|
222
311
|
```bash
|
|
223
312
|
QUEUEFLOW_URL=http://localhost:8000 \
|
|
@@ -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:
|
|
10
|
+
The version of the OpenAPI document: 0.5.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.
|
|
17
|
+
__version__ = "0.4.0"
|
|
18
18
|
|
|
19
19
|
# import apis into sdk package
|
|
20
20
|
from queueflow.api.cron_api import CronApi
|
|
@@ -38,6 +38,8 @@ 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
|
|
42
44
|
from queueflow.models.create_batch_jobs_request import CreateBatchJobsRequest
|
|
43
45
|
from queueflow.models.create_batch_jobs_response import CreateBatchJobsResponse
|
|
@@ -67,11 +69,18 @@ from queueflow.models.list_jobs_response import ListJobsResponse
|
|
|
67
69
|
from queueflow.models.list_workflows_response import ListWorkflowsResponse
|
|
68
70
|
from queueflow.models.on_failure import OnFailure
|
|
69
71
|
from queueflow.models.on_success import OnSuccess
|
|
72
|
+
from queueflow.models.queue_control_response import QueueControlResponse
|
|
73
|
+
from queueflow.models.queue_stats import QueueStats
|
|
74
|
+
from queueflow.models.queues_response import QueuesResponse
|
|
70
75
|
from queueflow.models.ready_status import ReadyStatus
|
|
71
76
|
from queueflow.models.replay_dead_letter_response import ReplayDeadLetterResponse
|
|
77
|
+
from queueflow.models.replay_dead_letters_request import ReplayDeadLettersRequest
|
|
78
|
+
from queueflow.models.replay_dead_letters_response import ReplayDeadLettersResponse
|
|
79
|
+
from queueflow.models.replayed_dead_letter import ReplayedDeadLetter
|
|
72
80
|
from queueflow.models.stats_snapshot import StatsSnapshot
|
|
73
81
|
from queueflow.models.step_status import StepStatus
|
|
74
82
|
from queueflow.models.tasks_response import TasksResponse
|
|
83
|
+
from queueflow.models.who_am_i_response import WhoAmIResponse
|
|
75
84
|
from queueflow.models.workflow import Workflow
|
|
76
85
|
from queueflow.models.workflow_diagram_response import WorkflowDiagramResponse
|
|
77
86
|
from queueflow.models.workflow_status import WorkflowStatus
|
|
@@ -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:
|
|
8
|
+
The version of the OpenAPI document: 0.5.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))
|