workly 0.2.1__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.
- {workly-0.2.1 → workly-0.4.0}/PKG-INFO +26 -5
- {workly-0.2.1 → workly-0.4.0}/README.md +24 -3
- {workly-0.2.1 → workly-0.4.0}/pyproject.toml +2 -2
- {workly-0.2.1 → workly-0.4.0}/pyproject.toml.orig +2 -2
- {workly-0.2.1 → workly-0.4.0}/src/workly/__init__.py +43 -2
- {workly-0.2.1 → workly-0.4.0}/src/workly/_async_client.py +159 -21
- {workly-0.2.1 → workly-0.4.0}/src/workly/_client.py +154 -21
- {workly-0.2.1 → workly-0.4.0}/src/workly/_models.py +368 -14
- {workly-0.2.1 → workly-0.4.0}/src/workly/_requests.py +136 -11
- {workly-0.2.1 → workly-0.4.0}/LICENSE +0 -0
- {workly-0.2.1 → workly-0.4.0}/src/workly/_errors.py +0 -0
- {workly-0.2.1 → workly-0.4.0}/src/workly/_signing.py +0 -0
- {workly-0.2.1 → workly-0.4.0}/src/workly/_transport.py +0 -0
- {workly-0.2.1 → workly-0.4.0}/src/workly/py.typed +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: workly
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Python SDK for Workly, the open-source HTTP task queue
|
|
5
5
|
Keywords: task queue,background jobs,webhooks,http
|
|
6
6
|
License-Expression: Apache-2.0
|
|
7
7
|
License-File: LICENSE
|
|
8
|
-
Classifier: Development Status ::
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
9
|
Classifier: Framework :: AsyncIO
|
|
10
10
|
Classifier: Intended Audience :: Developers
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -156,7 +156,7 @@ client.queues.delete("emails") # delete the queue and all its tasks
|
|
|
156
156
|
|
|
157
157
|
## Schedules
|
|
158
158
|
|
|
159
|
-
A schedule calls a URL on a cron timetable; each call is a run, retried and recorded like a task. Schedules are separate from queues. `put` is declarative, like `queues.put`. See [Schedules](https://
|
|
159
|
+
A schedule calls a URL on a cron timetable; each call is a run, retried and recorded like a task. Schedules are separate from queues. `put` is declarative, like `queues.put`. See [Schedules](https://workly.run/docs/schedules/).
|
|
160
160
|
|
|
161
161
|
```python
|
|
162
162
|
schedule = client.schedules.put(
|
|
@@ -171,7 +171,7 @@ schedule = client.schedules.put(
|
|
|
171
171
|
print(schedule.next_runs) # the next five times
|
|
172
172
|
|
|
173
173
|
run = client.schedules.trigger("nightly-report") # run it now
|
|
174
|
-
for run in client.
|
|
174
|
+
for run in client.runs.list(schedule="nightly-report", state="failed"):
|
|
175
175
|
print(run.id, run.scheduled_for, run.last_error)
|
|
176
176
|
client.runs.attempts(run.id)
|
|
177
177
|
client.runs.retry(run.id) # deliver it again, e.g. a failed or skipped run
|
|
@@ -184,7 +184,7 @@ Runs are delivered with `Workly-Run-Id`, `Workly-Schedule` and `Workly-Scheduled
|
|
|
184
184
|
|
|
185
185
|
## Task groups
|
|
186
186
|
|
|
187
|
-
A group tracks a set of tasks and enqueues a callback task once it's closed and they've all finished: a `POST` of the group's status and counts. See [Task groups](https://
|
|
187
|
+
A group tracks a set of tasks and enqueues a callback task once it's closed and they've all finished: a `POST` of the group's status and counts. See [Task groups](https://workly.run/docs/groups/). Like every callback, it goes to Workly's `_callbacks` queue.
|
|
188
188
|
|
|
189
189
|
```python
|
|
190
190
|
from workly import Callback
|
|
@@ -212,6 +212,27 @@ for group in client.groups.list(name="daily-stock-prices", status="failed"):
|
|
|
212
212
|
print(group.id, group.finished_at)
|
|
213
213
|
```
|
|
214
214
|
|
|
215
|
+
## Callbacks
|
|
216
|
+
|
|
217
|
+
Tasks and schedules take the same `on_complete`: each time the task, or a run of the schedule, succeeds, fails or is cancelled, Workly POSTs a summary of it to the URL. See [Callbacks](https://workly.run/docs/callbacks/).
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
client.tasks.enqueue(
|
|
221
|
+
"exports",
|
|
222
|
+
url="https://api.example.com/jobs/export",
|
|
223
|
+
payload={"export_id": 42},
|
|
224
|
+
on_complete=Callback(url="https://api.example.com/jobs/export-done"),
|
|
225
|
+
)
|
|
226
|
+
client.schedules.put(
|
|
227
|
+
"nightly-report",
|
|
228
|
+
cron="0 3 * * *",
|
|
229
|
+
url="https://api.example.com/reports/nightly",
|
|
230
|
+
on_complete={"url": "https://api.example.com/reports/nightly-done"},
|
|
231
|
+
)
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
The summary arrives as the callback task's JSON body: `task_id`, `queue`, `state`, `attempt_count`, `last_status`, `last_error` and `finished_at` for a task; `run_id`, `schedule` and the same outcome fields for a run.
|
|
235
|
+
|
|
215
236
|
## Async
|
|
216
237
|
|
|
217
238
|
`AsyncWorkly` has the same methods, as coroutines:
|
|
@@ -136,7 +136,7 @@ client.queues.delete("emails") # delete the queue and all its tasks
|
|
|
136
136
|
|
|
137
137
|
## Schedules
|
|
138
138
|
|
|
139
|
-
A schedule calls a URL on a cron timetable; each call is a run, retried and recorded like a task. Schedules are separate from queues. `put` is declarative, like `queues.put`. See [Schedules](https://
|
|
139
|
+
A schedule calls a URL on a cron timetable; each call is a run, retried and recorded like a task. Schedules are separate from queues. `put` is declarative, like `queues.put`. See [Schedules](https://workly.run/docs/schedules/).
|
|
140
140
|
|
|
141
141
|
```python
|
|
142
142
|
schedule = client.schedules.put(
|
|
@@ -151,7 +151,7 @@ schedule = client.schedules.put(
|
|
|
151
151
|
print(schedule.next_runs) # the next five times
|
|
152
152
|
|
|
153
153
|
run = client.schedules.trigger("nightly-report") # run it now
|
|
154
|
-
for run in client.
|
|
154
|
+
for run in client.runs.list(schedule="nightly-report", state="failed"):
|
|
155
155
|
print(run.id, run.scheduled_for, run.last_error)
|
|
156
156
|
client.runs.attempts(run.id)
|
|
157
157
|
client.runs.retry(run.id) # deliver it again, e.g. a failed or skipped run
|
|
@@ -164,7 +164,7 @@ Runs are delivered with `Workly-Run-Id`, `Workly-Schedule` and `Workly-Scheduled
|
|
|
164
164
|
|
|
165
165
|
## Task groups
|
|
166
166
|
|
|
167
|
-
A group tracks a set of tasks and enqueues a callback task once it's closed and they've all finished: a `POST` of the group's status and counts. See [Task groups](https://
|
|
167
|
+
A group tracks a set of tasks and enqueues a callback task once it's closed and they've all finished: a `POST` of the group's status and counts. See [Task groups](https://workly.run/docs/groups/). Like every callback, it goes to Workly's `_callbacks` queue.
|
|
168
168
|
|
|
169
169
|
```python
|
|
170
170
|
from workly import Callback
|
|
@@ -192,6 +192,27 @@ for group in client.groups.list(name="daily-stock-prices", status="failed"):
|
|
|
192
192
|
print(group.id, group.finished_at)
|
|
193
193
|
```
|
|
194
194
|
|
|
195
|
+
## Callbacks
|
|
196
|
+
|
|
197
|
+
Tasks and schedules take the same `on_complete`: each time the task, or a run of the schedule, succeeds, fails or is cancelled, Workly POSTs a summary of it to the URL. See [Callbacks](https://workly.run/docs/callbacks/).
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
client.tasks.enqueue(
|
|
201
|
+
"exports",
|
|
202
|
+
url="https://api.example.com/jobs/export",
|
|
203
|
+
payload={"export_id": 42},
|
|
204
|
+
on_complete=Callback(url="https://api.example.com/jobs/export-done"),
|
|
205
|
+
)
|
|
206
|
+
client.schedules.put(
|
|
207
|
+
"nightly-report",
|
|
208
|
+
cron="0 3 * * *",
|
|
209
|
+
url="https://api.example.com/reports/nightly",
|
|
210
|
+
on_complete={"url": "https://api.example.com/reports/nightly-done"},
|
|
211
|
+
)
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
The summary arrives as the callback task's JSON body: `task_id`, `queue`, `state`, `attempt_count`, `last_status`, `last_error` and `finished_at` for a task; `run_id`, `schedule` and the same outcome fields for a run.
|
|
215
|
+
|
|
195
216
|
## Async
|
|
196
217
|
|
|
197
218
|
`AsyncWorkly` has the same methods, as coroutines:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "workly"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.0"
|
|
4
4
|
description = "Python SDK for Workly, the open-source HTTP task queue"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "Apache-2.0"
|
|
@@ -14,7 +14,7 @@ keywords = [
|
|
|
14
14
|
"http",
|
|
15
15
|
]
|
|
16
16
|
classifiers = [
|
|
17
|
-
"Development Status ::
|
|
17
|
+
"Development Status :: 5 - Production/Stable",
|
|
18
18
|
"Framework :: AsyncIO",
|
|
19
19
|
"Intended Audience :: Developers",
|
|
20
20
|
"Programming Language :: Python :: 3",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "workly"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.0"
|
|
4
4
|
description = "Python SDK for Workly, the open-source HTTP task queue"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "Apache-2.0"
|
|
@@ -9,7 +9,7 @@ requires-python = ">=3.11"
|
|
|
9
9
|
dependencies = ["httpx>=0.27,<1"]
|
|
10
10
|
keywords = ["task queue", "background jobs", "webhooks", "http"]
|
|
11
11
|
classifiers = [
|
|
12
|
-
"Development Status ::
|
|
12
|
+
"Development Status :: 5 - Production/Stable",
|
|
13
13
|
"Framework :: AsyncIO",
|
|
14
14
|
"Intended Audience :: Developers",
|
|
15
15
|
"Programming Language :: Python :: 3",
|
|
@@ -7,9 +7,21 @@ from ._async_client import (
|
|
|
7
7
|
AsyncRuns,
|
|
8
8
|
AsyncSchedules,
|
|
9
9
|
AsyncTasks,
|
|
10
|
+
AsyncWorkflowRuns,
|
|
11
|
+
AsyncWorkflows,
|
|
10
12
|
AsyncWorkly,
|
|
11
13
|
)
|
|
12
|
-
from ._client import
|
|
14
|
+
from ._client import (
|
|
15
|
+
Alerts,
|
|
16
|
+
Groups,
|
|
17
|
+
Queues,
|
|
18
|
+
Runs,
|
|
19
|
+
Schedules,
|
|
20
|
+
Tasks,
|
|
21
|
+
WorkflowRuns,
|
|
22
|
+
Workflows,
|
|
23
|
+
Workly,
|
|
24
|
+
)
|
|
13
25
|
from ._errors import (
|
|
14
26
|
APIConnectionError,
|
|
15
27
|
APIError,
|
|
@@ -34,6 +46,7 @@ from ._models import (
|
|
|
34
46
|
AttemptOutcome,
|
|
35
47
|
Callback,
|
|
36
48
|
CronPreview,
|
|
49
|
+
CronSpan,
|
|
37
50
|
Delivery,
|
|
38
51
|
EnqueueResult,
|
|
39
52
|
FailureGroup,
|
|
@@ -55,12 +68,23 @@ from ._models import (
|
|
|
55
68
|
Schedule,
|
|
56
69
|
SkipReason,
|
|
57
70
|
SortOrder,
|
|
71
|
+
StepStatus,
|
|
58
72
|
Task,
|
|
59
73
|
TaskPage,
|
|
60
74
|
TaskSort,
|
|
61
75
|
TaskState,
|
|
76
|
+
TaskWorkflowStep,
|
|
77
|
+
WaitFor,
|
|
78
|
+
Workflow,
|
|
79
|
+
WorkflowRun,
|
|
80
|
+
WorkflowRunPage,
|
|
81
|
+
WorkflowRunStatus,
|
|
82
|
+
WorkflowRunSummary,
|
|
83
|
+
WorkflowStep,
|
|
84
|
+
WorkflowStepConfig,
|
|
85
|
+
WorkflowStepTask,
|
|
62
86
|
)
|
|
63
|
-
from ._requests import CallbackDict, GroupCallbackSpec, TaskSpec, __version__
|
|
87
|
+
from ._requests import CallbackDict, GroupCallbackSpec, TaskSpec, WorkflowStepSpec, __version__
|
|
64
88
|
from ._signing import sign, verify_signature
|
|
65
89
|
|
|
66
90
|
__all__ = [
|
|
@@ -79,6 +103,8 @@ __all__ = [
|
|
|
79
103
|
"AsyncRuns",
|
|
80
104
|
"AsyncSchedules",
|
|
81
105
|
"AsyncTasks",
|
|
106
|
+
"AsyncWorkflowRuns",
|
|
107
|
+
"AsyncWorkflows",
|
|
82
108
|
"AsyncWorkly",
|
|
83
109
|
"Attempt",
|
|
84
110
|
"AttemptOutcome",
|
|
@@ -88,6 +114,7 @@ __all__ = [
|
|
|
88
114
|
"CallbackDict",
|
|
89
115
|
"ConflictError",
|
|
90
116
|
"CronPreview",
|
|
117
|
+
"CronSpan",
|
|
91
118
|
"Delivery",
|
|
92
119
|
"EnqueueResult",
|
|
93
120
|
"FailureGroup",
|
|
@@ -119,12 +146,26 @@ __all__ = [
|
|
|
119
146
|
"SignatureVerificationError",
|
|
120
147
|
"SkipReason",
|
|
121
148
|
"SortOrder",
|
|
149
|
+
"StepStatus",
|
|
122
150
|
"Task",
|
|
123
151
|
"TaskPage",
|
|
124
152
|
"TaskSort",
|
|
125
153
|
"TaskSpec",
|
|
126
154
|
"TaskState",
|
|
155
|
+
"TaskWorkflowStep",
|
|
127
156
|
"Tasks",
|
|
157
|
+
"WaitFor",
|
|
158
|
+
"Workflow",
|
|
159
|
+
"WorkflowRun",
|
|
160
|
+
"WorkflowRunPage",
|
|
161
|
+
"WorkflowRunStatus",
|
|
162
|
+
"WorkflowRunSummary",
|
|
163
|
+
"WorkflowRuns",
|
|
164
|
+
"WorkflowStep",
|
|
165
|
+
"WorkflowStepConfig",
|
|
166
|
+
"WorkflowStepSpec",
|
|
167
|
+
"WorkflowStepTask",
|
|
168
|
+
"Workflows",
|
|
128
169
|
"Workly",
|
|
129
170
|
"WorklyError",
|
|
130
171
|
"__version__",
|
|
@@ -39,6 +39,11 @@ from ._models import (
|
|
|
39
39
|
TaskPage,
|
|
40
40
|
TaskSort,
|
|
41
41
|
TaskState,
|
|
42
|
+
Workflow,
|
|
43
|
+
WorkflowRun,
|
|
44
|
+
WorkflowRunPage,
|
|
45
|
+
WorkflowRunStatus,
|
|
46
|
+
WorkflowRunSummary,
|
|
42
47
|
)
|
|
43
48
|
|
|
44
49
|
|
|
@@ -69,6 +74,8 @@ class AsyncWorkly:
|
|
|
69
74
|
self.groups = AsyncGroups(self)
|
|
70
75
|
self.schedules = AsyncSchedules(self)
|
|
71
76
|
self.runs = AsyncRuns(self)
|
|
77
|
+
self.workflows = AsyncWorkflows(self)
|
|
78
|
+
self.workflow_runs = AsyncWorkflowRuns(self)
|
|
72
79
|
self.alerts = AsyncAlerts(self)
|
|
73
80
|
|
|
74
81
|
@property
|
|
@@ -216,6 +223,7 @@ class AsyncTasks:
|
|
|
216
223
|
max_attempts: int | None = None,
|
|
217
224
|
timeout_seconds: float | None = None,
|
|
218
225
|
group_id: str | None = None,
|
|
226
|
+
on_complete: Callback | r.CallbackDict | None = None,
|
|
219
227
|
traceparent: str | None = None,
|
|
220
228
|
) -> Task:
|
|
221
229
|
"""Enqueue a task to deliver to `url`.
|
|
@@ -224,7 +232,8 @@ class AsyncTasks:
|
|
|
224
232
|
created with default settings if it doesn't exist. If
|
|
225
233
|
`idempotency_key` matches a task already in the queue, that task is
|
|
226
234
|
returned and nothing is enqueued. With `group_id`, the task joins
|
|
227
|
-
that group, which must be open.
|
|
235
|
+
that group, which must be open. `on_complete` is called each time the
|
|
236
|
+
task succeeds, fails or is cancelled, with a `TaskSummary`.
|
|
228
237
|
"""
|
|
229
238
|
spec = {
|
|
230
239
|
"url": url,
|
|
@@ -239,6 +248,7 @@ class AsyncTasks:
|
|
|
239
248
|
"max_attempts": max_attempts,
|
|
240
249
|
"timeout_seconds": timeout_seconds,
|
|
241
250
|
"group_id": group_id,
|
|
251
|
+
"on_complete": on_complete,
|
|
242
252
|
}
|
|
243
253
|
return Task._from_json(await self._client._send(r.enqueue(queue, spec, traceparent)))
|
|
244
254
|
|
|
@@ -443,7 +453,8 @@ class AsyncSchedules:
|
|
|
443
453
|
name: str,
|
|
444
454
|
*,
|
|
445
455
|
cron: str,
|
|
446
|
-
url: str,
|
|
456
|
+
url: str | None = None,
|
|
457
|
+
workflow: str | None = None,
|
|
447
458
|
timezone: str | None = None,
|
|
448
459
|
payload: Any = None,
|
|
449
460
|
body: str | None = None,
|
|
@@ -458,16 +469,23 @@ class AsyncSchedules:
|
|
|
458
469
|
retry_on_4xx: bool | None = None,
|
|
459
470
|
overlap: OverlapPolicy | None = None,
|
|
460
471
|
missed: MissedPolicy | None = None,
|
|
472
|
+
on_complete: Callback | r.CallbackDict | None = None,
|
|
461
473
|
) -> Schedule:
|
|
462
474
|
"""Create or update a schedule that calls `url` on `cron` (read in `timezone`, default UTC).
|
|
463
475
|
|
|
476
|
+
With `workflow` instead of `url`, each run starts a run of that
|
|
477
|
+
workflow, with `payload` as its input.
|
|
478
|
+
|
|
464
479
|
Declarative: the schedule ends up with exactly these settings, and
|
|
465
480
|
omitted ones are reset to their defaults. It doesn't change whether
|
|
466
|
-
the schedule is paused. `next_runs` on the result shows when it runs
|
|
481
|
+
the schedule is paused. `next_runs` on the result shows when it runs
|
|
482
|
+
(`next_runs_if_resumed` while it is paused). `on_complete` is called
|
|
483
|
+
each time a run succeeds, fails or is cancelled, with a `RunSummary`.
|
|
467
484
|
"""
|
|
468
485
|
config = dict(
|
|
469
486
|
cron=cron,
|
|
470
487
|
url=url,
|
|
488
|
+
workflow=workflow,
|
|
471
489
|
timezone=timezone,
|
|
472
490
|
payload=payload,
|
|
473
491
|
body=body,
|
|
@@ -482,6 +500,7 @@ class AsyncSchedules:
|
|
|
482
500
|
retry_on_4xx=retry_on_4xx,
|
|
483
501
|
overlap=overlap,
|
|
484
502
|
missed=missed,
|
|
503
|
+
on_complete=on_complete,
|
|
485
504
|
)
|
|
486
505
|
return Schedule._from_json(await self._client._send(r.put_schedule(name, config)))
|
|
487
506
|
|
|
@@ -497,49 +516,65 @@ class AsyncSchedules:
|
|
|
497
516
|
"""Fire again from the next slot. Slots that passed while paused aren't run."""
|
|
498
517
|
return Schedule._from_json(await self._client._send(r.resume_schedule(name)))
|
|
499
518
|
|
|
500
|
-
async def preview_cron(
|
|
519
|
+
async def preview_cron(
|
|
520
|
+
self,
|
|
521
|
+
cron: str,
|
|
522
|
+
*,
|
|
523
|
+
timezone: str | None = None,
|
|
524
|
+
from_: datetime | None = None,
|
|
525
|
+
until: datetime | None = None,
|
|
526
|
+
) -> CronPreview:
|
|
501
527
|
"""The next 5 times a schedule with this cron would run, without creating it.
|
|
502
528
|
|
|
503
|
-
`timezone` is an IANA name; the default is UTC.
|
|
529
|
+
`timezone` is an IANA name; the default is UTC. With `from_` and `until` (at
|
|
530
|
+
most 35 days apart), `spans` lists every run in that window.
|
|
504
531
|
"""
|
|
505
|
-
return CronPreview._from_json(
|
|
532
|
+
return CronPreview._from_json(
|
|
533
|
+
await self._client._send(r.preview_cron(cron, timezone, from_, until))
|
|
534
|
+
)
|
|
506
535
|
|
|
507
536
|
async def trigger(self, name: str) -> Run:
|
|
508
537
|
"""Run the schedule now, even if paused. Doesn't change when it next fires."""
|
|
509
538
|
return Run._from_json(await self._client._send(r.trigger_schedule(name)))
|
|
510
539
|
|
|
511
|
-
|
|
512
|
-
|
|
540
|
+
|
|
541
|
+
class AsyncRuns:
|
|
542
|
+
"""Runs of schedules: one per firing, with its own attempts."""
|
|
543
|
+
|
|
544
|
+
def __init__(self, client: AsyncWorkly) -> None:
|
|
545
|
+
self._client = client
|
|
546
|
+
|
|
547
|
+
async def list(
|
|
548
|
+
self,
|
|
549
|
+
*,
|
|
550
|
+
schedule: str | None = None,
|
|
551
|
+
state: RunState | None = None,
|
|
552
|
+
page_size: int | None = None,
|
|
513
553
|
) -> AsyncIterator[Run]:
|
|
514
|
-
"""
|
|
554
|
+
"""Runs of every schedule, or of one, newest first, fetching pages as needed."""
|
|
515
555
|
cursor: str | None = None
|
|
516
556
|
while True:
|
|
517
|
-
page = await self.
|
|
557
|
+
page = await self.list_page(
|
|
558
|
+
schedule=schedule, state=state, cursor=cursor, limit=page_size
|
|
559
|
+
)
|
|
518
560
|
for run in page.runs:
|
|
519
561
|
yield run
|
|
520
562
|
if page.next_cursor is None:
|
|
521
563
|
return
|
|
522
564
|
cursor = page.next_cursor
|
|
523
565
|
|
|
524
|
-
async def
|
|
566
|
+
async def list_page(
|
|
525
567
|
self,
|
|
526
|
-
name: str,
|
|
527
568
|
*,
|
|
569
|
+
schedule: str | None = None,
|
|
528
570
|
state: RunState | None = None,
|
|
529
571
|
cursor: str | None = None,
|
|
530
572
|
limit: int | None = None,
|
|
531
573
|
) -> RunPage:
|
|
532
|
-
"""One page of `
|
|
533
|
-
req = r.list_runs(
|
|
574
|
+
"""One page of `list`. Pass the previous page's `next_cursor` as `cursor`."""
|
|
575
|
+
req = r.list_runs(schedule=schedule, state=state, cursor=cursor, limit=limit)
|
|
534
576
|
return RunPage._from_json(await self._client._send(req))
|
|
535
577
|
|
|
536
|
-
|
|
537
|
-
class AsyncRuns:
|
|
538
|
-
"""Runs of schedules: one per firing, with its own attempts."""
|
|
539
|
-
|
|
540
|
-
def __init__(self, client: AsyncWorkly) -> None:
|
|
541
|
-
self._client = client
|
|
542
|
-
|
|
543
578
|
async def get(self, run_id: str) -> Run:
|
|
544
579
|
return Run._from_json(await self._client._send(r.get_run(run_id)))
|
|
545
580
|
|
|
@@ -588,3 +623,106 @@ class AsyncAlerts:
|
|
|
588
623
|
async def test(self, channel_id: str) -> None:
|
|
589
624
|
"""Send a test alert to the channel now. Raises APIError (alert_failed) if it can't."""
|
|
590
625
|
await self._client._send(r.test_alert_channel(channel_id))
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
class AsyncWorkflows:
|
|
629
|
+
"""Workflows: DAGs of HTTP steps, each delivered as a task once its dependencies succeed.
|
|
630
|
+
|
|
631
|
+
A step's endpoint can hand its completion to a task group by answering
|
|
632
|
+
2xx with `Workly-Wait-For: group/<id>`; the step then finishes when the
|
|
633
|
+
group does. Define workflows in workly.yaml (`workly deploy`) or with `put`.
|
|
634
|
+
"""
|
|
635
|
+
|
|
636
|
+
def __init__(self, client: AsyncWorkly) -> None:
|
|
637
|
+
self._client = client
|
|
638
|
+
|
|
639
|
+
async def list(self) -> list[Workflow]:
|
|
640
|
+
"""All workflows, by name, with their latest run."""
|
|
641
|
+
data = await self._client._send(r.list_workflows())
|
|
642
|
+
return [Workflow._from_json(w) for w in data["workflows"]]
|
|
643
|
+
|
|
644
|
+
async def get(self, name: str) -> Workflow:
|
|
645
|
+
return Workflow._from_json(await self._client._send(r.get_workflow(name)))
|
|
646
|
+
|
|
647
|
+
async def put(
|
|
648
|
+
self,
|
|
649
|
+
name: str,
|
|
650
|
+
*,
|
|
651
|
+
steps: list[r.WorkflowStepSpec],
|
|
652
|
+
max_duration_seconds: float | None = None,
|
|
653
|
+
) -> Workflow:
|
|
654
|
+
"""Create or update a workflow with exactly these steps.
|
|
655
|
+
|
|
656
|
+
Runs already started keep the definition they started with. A run
|
|
657
|
+
still going after `max_duration_seconds` (default a day) fails.
|
|
658
|
+
"""
|
|
659
|
+
req = r.put_workflow(name, steps, max_duration_seconds)
|
|
660
|
+
return Workflow._from_json(await self._client._send(req))
|
|
661
|
+
|
|
662
|
+
async def delete(self, name: str) -> None:
|
|
663
|
+
"""Delete the definition. Its runs are kept, and those going finish."""
|
|
664
|
+
await self._client._send(r.delete_workflow(name))
|
|
665
|
+
|
|
666
|
+
async def trigger(
|
|
667
|
+
self, name: str, *, input: Any = None, idempotency_key: str | None = None
|
|
668
|
+
) -> WorkflowRun:
|
|
669
|
+
"""Start a run. `input` (any JSON) is sent to every step without a body of its own.
|
|
670
|
+
|
|
671
|
+
With an `idempotency_key` already used for a run of this workflow,
|
|
672
|
+
that run is returned and nothing starts.
|
|
673
|
+
"""
|
|
674
|
+
req = r.start_workflow_run(name, input, idempotency_key)
|
|
675
|
+
return WorkflowRun._from_json(await self._client._send(req))
|
|
676
|
+
|
|
677
|
+
|
|
678
|
+
class AsyncWorkflowRuns:
|
|
679
|
+
"""Runs of workflows, with each step's status, task and what it waits for."""
|
|
680
|
+
|
|
681
|
+
def __init__(self, client: AsyncWorkly) -> None:
|
|
682
|
+
self._client = client
|
|
683
|
+
|
|
684
|
+
async def list(
|
|
685
|
+
self,
|
|
686
|
+
*,
|
|
687
|
+
workflow: str | None = None,
|
|
688
|
+
status: WorkflowRunStatus | None = None,
|
|
689
|
+
page_size: int | None = None,
|
|
690
|
+
) -> AsyncIterator[WorkflowRunSummary]:
|
|
691
|
+
"""Runs of every workflow, or of one, newest first, fetching pages as needed."""
|
|
692
|
+
cursor: str | None = None
|
|
693
|
+
while True:
|
|
694
|
+
page = await self.list_page(
|
|
695
|
+
workflow=workflow, status=status, cursor=cursor, limit=page_size
|
|
696
|
+
)
|
|
697
|
+
for run in page.workflow_runs:
|
|
698
|
+
yield run
|
|
699
|
+
if page.next_cursor is None:
|
|
700
|
+
return
|
|
701
|
+
cursor = page.next_cursor
|
|
702
|
+
|
|
703
|
+
async def list_page(
|
|
704
|
+
self,
|
|
705
|
+
*,
|
|
706
|
+
workflow: str | None = None,
|
|
707
|
+
status: WorkflowRunStatus | None = None,
|
|
708
|
+
cursor: str | None = None,
|
|
709
|
+
limit: int | None = None,
|
|
710
|
+
) -> WorkflowRunPage:
|
|
711
|
+
"""One page of `list`. Pass the previous page's `next_cursor` as `cursor`."""
|
|
712
|
+
req = r.list_workflow_runs(workflow=workflow, status=status, cursor=cursor, limit=limit)
|
|
713
|
+
return WorkflowRunPage._from_json(await self._client._send(req))
|
|
714
|
+
|
|
715
|
+
async def get(self, run_id: str) -> WorkflowRun:
|
|
716
|
+
return WorkflowRun._from_json(await self._client._send(r.get_workflow_run(run_id)))
|
|
717
|
+
|
|
718
|
+
async def cancel(self, run_id: str) -> WorkflowRun:
|
|
719
|
+
"""Stop a run: steps not yet started are skipped. Requests in flight finish."""
|
|
720
|
+
return WorkflowRun._from_json(await self._client._send(r.cancel_workflow_run(run_id)))
|
|
721
|
+
|
|
722
|
+
async def retry(self, run_id: str) -> WorkflowRun:
|
|
723
|
+
"""Reopen a failed or cancelled run under the definition it started with.
|
|
724
|
+
|
|
725
|
+
Steps that succeeded stay; the rest run again, each on its old task with a
|
|
726
|
+
fresh set of attempts. A step that was waiting for a group follows the group.
|
|
727
|
+
"""
|
|
728
|
+
return WorkflowRun._from_json(await self._client._send(r.retry_workflow_run(run_id)))
|