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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: workly
3
- Version: 0.2.0
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
- on_complete={"queue": "jobs", "url": "https://api.example.com/jobs/refresh-views"},
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
- on_complete={"queue": "jobs", "url": "https://api.example.com/jobs/refresh-views"},
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(
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "workly"
3
- version = "0.2.0"
3
+ version = "0.2.1"
4
4
  description = "Python SDK for Workly, the open-source HTTP task queue"
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "workly"
3
- version = "0.2.0"
3
+ version = "0.2.1"
4
4
  description = "Python SDK for Workly, the open-source HTTP task queue"
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -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.GroupCallbackSpec | None = None,
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
- `{"queue": "jobs", "url": "https://api.example.com/done"}`; it is a
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.GroupCallbackSpec | None = None,
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
- `{"queue": "jobs", "url": "https://api.example.com/done"}`; it is a
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 dataclasses import dataclass
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
- @dataclass(frozen=True, slots=True)
514
- class GroupCallback:
515
- """The task a group enqueues when it completes: a POST of the group's summary as JSON."""
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
- headers: dict[str, str]
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]) -> GroupCallback:
523
- return cls(queue=d["queue"], url=d["url"], headers=d.get("headers") or {})
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: GroupCallback | None
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 GroupCallback._from_json(cb),
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 GroupCallbackSpec(TypedDict, total=False):
79
- """The `on_complete` callback of `groups.create`: a task to enqueue when the group completes."""
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: GroupCallbackSpec | None
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
- callback: dict[str, Any] = {"queue": on_complete["queue"], "url": on_complete["url"]}
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