workly 0.2.0__tar.gz → 0.2.1__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.0 → workly-0.2.1}/PKG-INFO +6 -3
- {workly-0.2.0 → workly-0.2.1}/README.md +5 -2
- {workly-0.2.0 → workly-0.2.1}/pyproject.toml +1 -1
- {workly-0.2.0 → workly-0.2.1}/pyproject.toml.orig +1 -1
- {workly-0.2.0 → workly-0.2.1}/src/workly/__init__.py +4 -1
- {workly-0.2.0 → workly-0.2.1}/src/workly/_async_client.py +4 -3
- {workly-0.2.0 → workly-0.2.1}/src/workly/_client.py +4 -3
- {workly-0.2.0 → workly-0.2.1}/src/workly/_models.py +27 -10
- {workly-0.2.0 → workly-0.2.1}/src/workly/_requests.py +24 -8
- {workly-0.2.0 → workly-0.2.1}/LICENSE +0 -0
- {workly-0.2.0 → workly-0.2.1}/src/workly/_errors.py +0 -0
- {workly-0.2.0 → workly-0.2.1}/src/workly/_signing.py +0 -0
- {workly-0.2.0 → workly-0.2.1}/src/workly/_transport.py +0 -0
- {workly-0.2.0 → workly-0.2.1}/src/workly/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: workly
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
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
|
|
@@ -184,13 +184,16 @@ 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://docs.workly.run/groups/).
|
|
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://docs.workly.run/groups/). The callback goes to Workly's `_callbacks` queue unless it names a `queue`.
|
|
188
188
|
|
|
189
189
|
```python
|
|
190
|
+
from workly import Callback
|
|
191
|
+
|
|
190
192
|
group = client.groups.create(
|
|
191
193
|
name="daily-stock-prices",
|
|
192
194
|
idempotency_key="stock-prices-2026-10-19", # creating it again returns this group
|
|
193
|
-
|
|
195
|
+
# or a dict: {"url": "https://api.example.com/jobs/refresh-views"}
|
|
196
|
+
on_complete=Callback(url="https://api.example.com/jobs/refresh-views"),
|
|
194
197
|
)
|
|
195
198
|
for company in companies:
|
|
196
199
|
client.tasks.enqueue(
|
|
@@ -164,13 +164,16 @@ 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://docs.workly.run/groups/).
|
|
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://docs.workly.run/groups/). The callback goes to Workly's `_callbacks` queue unless it names a `queue`.
|
|
168
168
|
|
|
169
169
|
```python
|
|
170
|
+
from workly import Callback
|
|
171
|
+
|
|
170
172
|
group = client.groups.create(
|
|
171
173
|
name="daily-stock-prices",
|
|
172
174
|
idempotency_key="stock-prices-2026-10-19", # creating it again returns this group
|
|
173
|
-
|
|
175
|
+
# or a dict: {"url": "https://api.example.com/jobs/refresh-views"}
|
|
176
|
+
on_complete=Callback(url="https://api.example.com/jobs/refresh-views"),
|
|
174
177
|
)
|
|
175
178
|
for company in companies:
|
|
176
179
|
client.tasks.enqueue(
|
|
@@ -32,6 +32,7 @@ from ._models import (
|
|
|
32
32
|
AlertChannelType,
|
|
33
33
|
Attempt,
|
|
34
34
|
AttemptOutcome,
|
|
35
|
+
Callback,
|
|
35
36
|
CronPreview,
|
|
36
37
|
Delivery,
|
|
37
38
|
EnqueueResult,
|
|
@@ -59,7 +60,7 @@ from ._models import (
|
|
|
59
60
|
TaskSort,
|
|
60
61
|
TaskState,
|
|
61
62
|
)
|
|
62
|
-
from ._requests import GroupCallbackSpec, TaskSpec, __version__
|
|
63
|
+
from ._requests import CallbackDict, GroupCallbackSpec, TaskSpec, __version__
|
|
63
64
|
from ._signing import sign, verify_signature
|
|
64
65
|
|
|
65
66
|
__all__ = [
|
|
@@ -83,6 +84,8 @@ __all__ = [
|
|
|
83
84
|
"AttemptOutcome",
|
|
84
85
|
"AuthenticationError",
|
|
85
86
|
"BadRequestError",
|
|
87
|
+
"Callback",
|
|
88
|
+
"CallbackDict",
|
|
86
89
|
"ConflictError",
|
|
87
90
|
"CronPreview",
|
|
88
91
|
"Delivery",
|
|
@@ -17,6 +17,7 @@ from ._models import (
|
|
|
17
17
|
AlertChannelList,
|
|
18
18
|
AlertChannelType,
|
|
19
19
|
Attempt,
|
|
20
|
+
Callback,
|
|
20
21
|
CronPreview,
|
|
21
22
|
Delivery,
|
|
22
23
|
EnqueueResult,
|
|
@@ -370,13 +371,13 @@ class AsyncGroups:
|
|
|
370
371
|
*,
|
|
371
372
|
name: str | None = None,
|
|
372
373
|
idempotency_key: str | None = None,
|
|
373
|
-
on_complete: r.
|
|
374
|
+
on_complete: Callback | r.CallbackDict | None = None,
|
|
374
375
|
) -> Group:
|
|
375
376
|
"""Create an open group.
|
|
376
377
|
|
|
377
378
|
`on_complete` is the task to enqueue when the group completes, e.g.
|
|
378
|
-
`
|
|
379
|
-
POST of the group's summary as JSON. If `idempotency_key` matches an
|
|
379
|
+
`Callback(url="https://api.example.com/done")` or the same as a dict;
|
|
380
|
+
it is a POST of the group's summary as JSON. If `idempotency_key` matches an
|
|
380
381
|
existing group, that group is returned and nothing is created.
|
|
381
382
|
"""
|
|
382
383
|
req = r.create_group(name, idempotency_key, on_complete)
|
|
@@ -17,6 +17,7 @@ from ._models import (
|
|
|
17
17
|
AlertChannelList,
|
|
18
18
|
AlertChannelType,
|
|
19
19
|
Attempt,
|
|
20
|
+
Callback,
|
|
20
21
|
CronPreview,
|
|
21
22
|
Delivery,
|
|
22
23
|
EnqueueResult,
|
|
@@ -369,13 +370,13 @@ class Groups:
|
|
|
369
370
|
*,
|
|
370
371
|
name: str | None = None,
|
|
371
372
|
idempotency_key: str | None = None,
|
|
372
|
-
on_complete: r.
|
|
373
|
+
on_complete: Callback | r.CallbackDict | None = None,
|
|
373
374
|
) -> Group:
|
|
374
375
|
"""Create an open group.
|
|
375
376
|
|
|
376
377
|
`on_complete` is the task to enqueue when the group completes, e.g.
|
|
377
|
-
`
|
|
378
|
-
POST of the group's summary as JSON. If `idempotency_key` matches an
|
|
378
|
+
`Callback(url="https://api.example.com/done")` or the same as a dict;
|
|
379
|
+
it is a POST of the group's summary as JSON. If `idempotency_key` matches an
|
|
379
380
|
existing group, that group is returned and nothing is created.
|
|
380
381
|
"""
|
|
381
382
|
req = r.create_group(name, idempotency_key, on_complete)
|
|
@@ -5,7 +5,8 @@ Unknown fields are ignored, so newer servers don't break older SDKs.
|
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
-
from
|
|
8
|
+
from collections.abc import Mapping
|
|
9
|
+
from dataclasses import dataclass, field
|
|
9
10
|
from datetime import datetime
|
|
10
11
|
from typing import Any, Literal
|
|
11
12
|
|
|
@@ -510,17 +511,33 @@ class AlertChannelList:
|
|
|
510
511
|
)
|
|
511
512
|
|
|
512
513
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
514
|
+
CALLBACK_QUEUE = "_callbacks"
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
518
|
+
class Callback:
|
|
519
|
+
"""A task Workly enqueues when something completes: a POST to `url` saying what completed.
|
|
520
|
+
|
|
521
|
+
A group's callback has the group's summary as its JSON body. It is
|
|
522
|
+
delivered like any task in its queue, with that queue's retries.
|
|
523
|
+
"""
|
|
516
524
|
|
|
517
|
-
queue: str
|
|
518
525
|
url: str
|
|
519
|
-
|
|
526
|
+
queue: str = CALLBACK_QUEUE
|
|
527
|
+
"""The queue to enqueue the callback into. `_callbacks`, the default, is
|
|
528
|
+
created by Workly; configure it like any queue to change its retries."""
|
|
529
|
+
headers: Mapping[str, str] = field(default_factory=dict[str, str])
|
|
530
|
+
"""Headers sent with the callback. `Workly-*` headers are reserved."""
|
|
520
531
|
|
|
521
532
|
@classmethod
|
|
522
|
-
def _from_json(cls, d: dict[str, Any]) ->
|
|
523
|
-
return cls(
|
|
533
|
+
def _from_json(cls, d: dict[str, Any]) -> Callback:
|
|
534
|
+
return cls(
|
|
535
|
+
url=d["url"], queue=d.get("queue") or CALLBACK_QUEUE, headers=d.get("headers") or {}
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
GroupCallback = Callback
|
|
540
|
+
"""Deprecated: use `Callback`."""
|
|
524
541
|
|
|
525
542
|
|
|
526
543
|
@dataclass(frozen=True, slots=True)
|
|
@@ -537,7 +554,7 @@ class Group:
|
|
|
537
554
|
cancelled: int
|
|
538
555
|
remaining: int
|
|
539
556
|
"""Tasks still pending or running."""
|
|
540
|
-
on_complete:
|
|
557
|
+
on_complete: Callback | None
|
|
541
558
|
callback_task_id: str | None
|
|
542
559
|
"""The callback task, once the group has completed."""
|
|
543
560
|
created_at: datetime
|
|
@@ -558,7 +575,7 @@ class Group:
|
|
|
558
575
|
failed=d["failed"],
|
|
559
576
|
cancelled=d["cancelled"],
|
|
560
577
|
remaining=d["remaining"],
|
|
561
|
-
on_complete=None if cb is None else
|
|
578
|
+
on_complete=None if cb is None else Callback._from_json(cb),
|
|
562
579
|
callback_task_id=d.get("callback_task_id"),
|
|
563
580
|
created_at=_time(d["created_at"]),
|
|
564
581
|
closed_at=_opt_time(d.get("closed_at")),
|
|
@@ -15,7 +15,9 @@ from typing import Any, Required, TypedDict
|
|
|
15
15
|
from urllib.parse import quote
|
|
16
16
|
|
|
17
17
|
from ._models import (
|
|
18
|
+
CALLBACK_QUEUE,
|
|
18
19
|
AlertChannelType,
|
|
20
|
+
Callback,
|
|
19
21
|
GroupStatus,
|
|
20
22
|
HTTPMethod,
|
|
21
23
|
RunState,
|
|
@@ -75,14 +77,31 @@ class TaskSpec(TypedDict, total=False):
|
|
|
75
77
|
group_id: str
|
|
76
78
|
|
|
77
79
|
|
|
78
|
-
class
|
|
79
|
-
"""
|
|
80
|
+
class CallbackDict(TypedDict, total=False):
|
|
81
|
+
"""A `Callback` as a dict, with the same fields."""
|
|
80
82
|
|
|
81
|
-
queue: Required[str]
|
|
82
83
|
url: Required[str]
|
|
84
|
+
queue: str
|
|
83
85
|
headers: Mapping[str, str]
|
|
84
86
|
|
|
85
87
|
|
|
88
|
+
GroupCallbackSpec = CallbackDict
|
|
89
|
+
"""Deprecated: use `Callback` or `CallbackDict`."""
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def callback_body(cb: Callback | CallbackDict) -> dict[str, Any]:
|
|
93
|
+
if isinstance(cb, Callback):
|
|
94
|
+
url, queue, headers = cb.url, cb.queue, cb.headers
|
|
95
|
+
else:
|
|
96
|
+
url, queue, headers = cb["url"], cb.get("queue", CALLBACK_QUEUE), cb.get("headers", {})
|
|
97
|
+
body: dict[str, Any] = {"url": url}
|
|
98
|
+
if queue != CALLBACK_QUEUE:
|
|
99
|
+
body["queue"] = queue
|
|
100
|
+
if headers:
|
|
101
|
+
body["headers"] = dict(headers)
|
|
102
|
+
return body
|
|
103
|
+
|
|
104
|
+
|
|
86
105
|
def task_body(spec: Mapping[str, Any]) -> dict[str, Any]:
|
|
87
106
|
body: dict[str, Any] = {}
|
|
88
107
|
for key, value in spec.items():
|
|
@@ -256,7 +275,7 @@ def retry_task(task_id: str) -> Request:
|
|
|
256
275
|
|
|
257
276
|
|
|
258
277
|
def create_group(
|
|
259
|
-
name: str | None, idempotency_key: str | None, on_complete:
|
|
278
|
+
name: str | None, idempotency_key: str | None, on_complete: Callback | CallbackDict | None
|
|
260
279
|
) -> Request:
|
|
261
280
|
body: dict[str, Any] = {}
|
|
262
281
|
if name is not None:
|
|
@@ -264,10 +283,7 @@ def create_group(
|
|
|
264
283
|
if idempotency_key is not None:
|
|
265
284
|
body["idempotency_key"] = idempotency_key
|
|
266
285
|
if on_complete is not None:
|
|
267
|
-
|
|
268
|
-
if "headers" in on_complete:
|
|
269
|
-
callback["headers"] = dict(on_complete["headers"])
|
|
270
|
-
body["on_complete"] = callback
|
|
286
|
+
body["on_complete"] = callback_body(on_complete)
|
|
271
287
|
return Request("POST", "/v1/groups", json=body, idempotent=idempotency_key is not None)
|
|
272
288
|
|
|
273
289
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|