queueflow 0.4.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.
- {queueflow-0.4.0/queueflow.egg-info → queueflow-0.5.0}/PKG-INFO +84 -14
- {queueflow-0.4.0 → queueflow-0.5.0}/README.md +83 -13
- {queueflow-0.4.0 → queueflow-0.5.0}/pyproject.toml +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/__init__.py +7 -2
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/api/cron_api.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/api/dlq_api.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/api/health_api.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/api/jobs_api.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/api/system_api.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/api/worker_api.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/api/workflows_api.py +540 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/api_client.py +2 -2
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/configuration.py +3 -3
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/exceptions.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/facade.py +203 -7
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/__init__.py +6 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/backoff_strategy.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/cancel_jobs_request.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/cancel_jobs_response.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/complete_job_request.py +1 -1
- queueflow-0.5.0/queueflow/models/condition_op.py +44 -0
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/create_batch_jobs_request.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/create_batch_jobs_response.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/create_cron_request.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/create_cron_response.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/create_job_request.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/create_job_response.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/create_workflow_request.py +15 -5
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/create_workflow_response.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/cron_schedule.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/dead_letter.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/error_body.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/fail_job_request.py +1 -1
- queueflow-0.4.0/queueflow/models/workflow_step_state.py → queueflow-0.5.0/queueflow/models/fan_out.py +21 -16
- queueflow-0.5.0/queueflow/models/fan_out_progress.py +92 -0
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/health_status.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/heartbeat_request.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/heartbeat_response.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/job.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/job_config.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/job_config_request.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/job_status.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/lease_jobs_request.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/lease_jobs_response.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/leased_job.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/list_crons_response.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/list_dead_letters_response.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/list_jobs_response.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/list_workflows_response.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/on_failure.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/on_success.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/queue_control_response.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/queue_stats.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/queues_response.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/ready_status.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/replay_dead_letter_response.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/replay_dead_letters_request.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/replay_dead_letters_response.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/replayed_dead_letter.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/stats_snapshot.py +1 -1
- queueflow-0.5.0/queueflow/models/step_condition.py +147 -0
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/step_status.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/tasks_response.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/who_am_i_response.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/workflow.py +41 -3
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/workflow_diagram_response.py +1 -1
- queueflow-0.5.0/queueflow/models/workflow_event.py +107 -0
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/workflow_status.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/workflow_step.py +48 -6
- queueflow-0.5.0/queueflow/models/workflow_step_state.py +137 -0
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/models/workflow_step_states_response.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/rest.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0/queueflow.egg-info}/PKG-INFO +84 -14
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow.egg-info/SOURCES.txt +10 -0
- {queueflow-0.4.0 → queueflow-0.5.0}/setup.py +2 -2
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_backoff_strategy.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_cancel_jobs_request.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_cancel_jobs_response.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_complete_job_request.py +1 -1
- queueflow-0.5.0/test/test_condition_op.py +33 -0
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_create_batch_jobs_request.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_create_batch_jobs_response.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_create_cron_request.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_create_cron_response.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_create_job_request.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_create_job_response.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_create_workflow_request.py +75 -4
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_create_workflow_response.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_cron_api.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_cron_schedule.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_dead_letter.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_dlq_api.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_error_body.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_facade.py +236 -0
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_fail_job_request.py +1 -1
- queueflow-0.5.0/test/test_fan_out.py +54 -0
- queueflow-0.5.0/test/test_fan_out_progress.py +56 -0
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_health_api.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_health_status.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_heartbeat_request.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_heartbeat_response.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_job.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_job_config.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_job_config_request.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_job_status.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_jobs_api.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_lease_jobs_request.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_lease_jobs_response.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_leased_job.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_list_crons_response.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_list_dead_letters_response.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_list_jobs_response.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_list_workflows_response.py +81 -5
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_live.py +221 -0
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_on_failure.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_on_success.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_queue_control_response.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_queue_stats.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_queues_response.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_ready_status.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_replay_dead_letter_response.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_replay_dead_letters_request.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_replay_dead_letters_response.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_replayed_dead_letter.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_stats_snapshot.py +1 -1
- queueflow-0.5.0/test/test_step_condition.py +97 -0
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_step_status.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_system_api.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_tasks_response.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_who_am_i_response.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_worker_api.py +1 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_workflow.py +80 -4
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_workflow_diagram_response.py +1 -1
- queueflow-0.5.0/test/test_workflow_event.py +83 -0
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_workflow_status.py +1 -1
- queueflow-0.5.0/test/test_workflow_step.py +151 -0
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_workflow_step_state.py +9 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_workflow_step_states_response.py +17 -1
- {queueflow-0.4.0 → queueflow-0.5.0}/test/test_workflows_api.py +14 -1
- queueflow-0.4.0/test/test_workflow_step.py +0 -73
- {queueflow-0.4.0 → queueflow-0.5.0}/LICENSE +0 -0
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/api/__init__.py +0 -0
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/api_response.py +0 -0
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow/py.typed +0 -0
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow.egg-info/dependency_links.txt +0 -0
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow.egg-info/requires.txt +0 -0
- {queueflow-0.4.0 → queueflow-0.5.0}/queueflow.egg-info/top_level.txt +0 -0
- {queueflow-0.4.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
|
+
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
|
|
@@ -137,26 +137,92 @@ while qf.cancel_jobs(queue="orders", task_name="send_email", limit=500) == 500:
|
|
|
137
137
|
| `qf.create_workflow(builder_or_request)` | Create a workflow from a `wf()` builder or a raw `CreateWorkflowRequest`. |
|
|
138
138
|
| `qf.wait_for_workflow(workflow_id, timeout=60.0, interval=0.5)` | Poll until a terminal workflow state. |
|
|
139
139
|
| `qf.workflows.get_workflow(id)` / `list_workflows(...)` / `cancel_workflow(id)` | Fetch / list / cancel. |
|
|
140
|
-
| `qf.
|
|
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`. |
|
|
141
143
|
| `qf.workflows.get_workflow_diagram(id)` | Mermaid (`graph TD`) diagram of the DAG. |
|
|
142
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
|
+
|
|
143
156
|
### Workflow builder
|
|
144
157
|
|
|
145
158
|
```python
|
|
146
159
|
from queueflow.facade import wf
|
|
147
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
|
+
|
|
148
168
|
dag = (
|
|
149
169
|
wf("order_123")
|
|
150
|
-
.step("validate", "validate_order")
|
|
151
|
-
.step("
|
|
152
|
-
.step("
|
|
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")
|
|
153
179
|
.context(source="web")
|
|
180
|
+
.metadata(app="shop")
|
|
181
|
+
.timeout_secs(3600)
|
|
154
182
|
)
|
|
155
|
-
# .build() runs locally first: duplicate names, dangling deps, and
|
|
156
|
-
# 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.
|
|
157
185
|
```
|
|
158
186
|
|
|
159
|
-
|
|
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.
|
|
160
226
|
|
|
161
227
|
### Worker
|
|
162
228
|
|
|
@@ -333,11 +399,12 @@ except ApiException as err:
|
|
|
333
399
|
print(err.status, err.body)
|
|
334
400
|
```
|
|
335
401
|
|
|
336
|
-
## Known
|
|
402
|
+
## Known limitations
|
|
337
403
|
|
|
338
|
-
`JobsApi.stream_job_events`
|
|
339
|
-
|
|
340
|
-
|
|
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.
|
|
341
408
|
|
|
342
409
|
## Conformance tests
|
|
343
410
|
|
|
@@ -346,8 +413,11 @@ closes it (terminal status or the 15-minute cap). Use `wait_for_job` (polling) i
|
|
|
346
413
|
and waits on an `echo` job, checks idempotent re-creation, runs a two-step workflow, exercises the
|
|
347
414
|
cron and dead-letter endpoints, reads stats, runs `run_worker` against a dedicated queue, and
|
|
348
415
|
exercises `whoami`, bulk cancel, queue pause/resume and bulk dead-letter replay, each scoped to a
|
|
349
|
-
queue unique to the run.
|
|
350
|
-
|
|
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.
|
|
351
421
|
|
|
352
422
|
```bash
|
|
353
423
|
QUEUEFLOW_URL=http://localhost:8000 \
|
|
@@ -96,26 +96,92 @@ while qf.cancel_jobs(queue="orders", task_name="send_email", limit=500) == 500:
|
|
|
96
96
|
| `qf.create_workflow(builder_or_request)` | Create a workflow from a `wf()` builder or a raw `CreateWorkflowRequest`. |
|
|
97
97
|
| `qf.wait_for_workflow(workflow_id, timeout=60.0, interval=0.5)` | Poll until a terminal workflow state. |
|
|
98
98
|
| `qf.workflows.get_workflow(id)` / `list_workflows(...)` / `cancel_workflow(id)` | Fetch / list / cancel. |
|
|
99
|
-
| `qf.
|
|
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`. |
|
|
100
102
|
| `qf.workflows.get_workflow_diagram(id)` | Mermaid (`graph TD`) diagram of the DAG. |
|
|
101
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
|
+
|
|
102
115
|
### Workflow builder
|
|
103
116
|
|
|
104
117
|
```python
|
|
105
118
|
from queueflow.facade import wf
|
|
106
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
|
+
|
|
107
127
|
dag = (
|
|
108
128
|
wf("order_123")
|
|
109
|
-
.step("validate", "validate_order")
|
|
110
|
-
.step("
|
|
111
|
-
.step("
|
|
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")
|
|
112
138
|
.context(source="web")
|
|
139
|
+
.metadata(app="shop")
|
|
140
|
+
.timeout_secs(3600)
|
|
113
141
|
)
|
|
114
|
-
# .build() runs locally first: duplicate names, dangling deps, and
|
|
115
|
-
# 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.
|
|
116
144
|
```
|
|
117
145
|
|
|
118
|
-
|
|
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.
|
|
119
185
|
|
|
120
186
|
### Worker
|
|
121
187
|
|
|
@@ -292,11 +358,12 @@ except ApiException as err:
|
|
|
292
358
|
print(err.status, err.body)
|
|
293
359
|
```
|
|
294
360
|
|
|
295
|
-
## Known
|
|
361
|
+
## Known limitations
|
|
296
362
|
|
|
297
|
-
`JobsApi.stream_job_events`
|
|
298
|
-
|
|
299
|
-
|
|
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.
|
|
300
367
|
|
|
301
368
|
## Conformance tests
|
|
302
369
|
|
|
@@ -305,8 +372,11 @@ closes it (terminal status or the 15-minute cap). Use `wait_for_job` (polling) i
|
|
|
305
372
|
and waits on an `echo` job, checks idempotent re-creation, runs a two-step workflow, exercises the
|
|
306
373
|
cron and dead-letter endpoints, reads stats, runs `run_worker` against a dedicated queue, and
|
|
307
374
|
exercises `whoami`, bulk cancel, queue pause/resume and bulk dead-letter replay, each scoped to a
|
|
308
|
-
queue unique to the run.
|
|
309
|
-
|
|
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.
|
|
310
380
|
|
|
311
381
|
```bash
|
|
312
382
|
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: 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.
|
|
17
|
+
__version__ = "0.5.0"
|
|
18
18
|
|
|
19
19
|
# import apis into sdk package
|
|
20
20
|
from queueflow.api.cron_api import CronApi
|
|
@@ -41,6 +41,7 @@ from queueflow.models.backoff_strategy import BackoffStrategy
|
|
|
41
41
|
from queueflow.models.cancel_jobs_request import CancelJobsRequest
|
|
42
42
|
from queueflow.models.cancel_jobs_response import CancelJobsResponse
|
|
43
43
|
from queueflow.models.complete_job_request import CompleteJobRequest
|
|
44
|
+
from queueflow.models.condition_op import ConditionOp
|
|
44
45
|
from queueflow.models.create_batch_jobs_request import CreateBatchJobsRequest
|
|
45
46
|
from queueflow.models.create_batch_jobs_response import CreateBatchJobsResponse
|
|
46
47
|
from queueflow.models.create_cron_request import CreateCronRequest
|
|
@@ -53,6 +54,8 @@ from queueflow.models.cron_schedule import CronSchedule
|
|
|
53
54
|
from queueflow.models.dead_letter import DeadLetter
|
|
54
55
|
from queueflow.models.error_body import ErrorBody
|
|
55
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
|
|
56
59
|
from queueflow.models.health_status import HealthStatus
|
|
57
60
|
from queueflow.models.heartbeat_request import HeartbeatRequest
|
|
58
61
|
from queueflow.models.heartbeat_response import HeartbeatResponse
|
|
@@ -78,11 +81,13 @@ from queueflow.models.replay_dead_letters_request import ReplayDeadLettersReques
|
|
|
78
81
|
from queueflow.models.replay_dead_letters_response import ReplayDeadLettersResponse
|
|
79
82
|
from queueflow.models.replayed_dead_letter import ReplayedDeadLetter
|
|
80
83
|
from queueflow.models.stats_snapshot import StatsSnapshot
|
|
84
|
+
from queueflow.models.step_condition import StepCondition
|
|
81
85
|
from queueflow.models.step_status import StepStatus
|
|
82
86
|
from queueflow.models.tasks_response import TasksResponse
|
|
83
87
|
from queueflow.models.who_am_i_response import WhoAmIResponse
|
|
84
88
|
from queueflow.models.workflow import Workflow
|
|
85
89
|
from queueflow.models.workflow_diagram_response import WorkflowDiagramResponse
|
|
90
|
+
from queueflow.models.workflow_event import WorkflowEvent
|
|
86
91
|
from queueflow.models.workflow_status import WorkflowStatus
|
|
87
92
|
from queueflow.models.workflow_step import WorkflowStep
|
|
88
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.
|
|
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.
|
|
@@ -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.
|
|
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.
|
|
@@ -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.
|
|
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.
|
|
@@ -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.
|
|
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.
|
|
@@ -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.
|
|
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.
|
|
@@ -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.
|
|
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.
|