Skip to content

Commit 52b4520

Browse files
committed
Merge remote-tracking branch 'origin/main' into sk/task-id
# Conflicts: # taskbadger/sdk.py
2 parents 4370a68 + 5010e54 commit 52b4520

11 files changed

Lines changed: 260 additions & 18 deletions

taskbadger.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,11 @@ components:
699699
type: string
700700
description: Queue the task is from
701701
maxLength: 255
702+
external_id:
703+
type: string
704+
description: Identifier from the originating system (e.g. Celery task ID)
705+
for correlating with logs
706+
maxLength: 255
702707
status:
703708
allOf:
704709
- $ref: '#/components/schemas/StatusEnum'
@@ -799,6 +804,11 @@ components:
799804
type: string
800805
description: Queue the task is from
801806
maxLength: 255
807+
external_id:
808+
type: string
809+
description: Identifier from the originating system (e.g. Celery task ID)
810+
for correlating with logs
811+
maxLength: 255
802812
status:
803813
allOf:
804814
- $ref: '#/components/schemas/StatusEnum'
@@ -902,6 +912,11 @@ components:
902912
type: string
903913
description: Queue the task is from
904914
maxLength: 255
915+
external_id:
916+
type: string
917+
description: Identifier from the originating system (e.g. Celery task ID)
918+
for correlating with logs
919+
maxLength: 255
905920
status:
906921
allOf:
907922
- $ref: '#/components/schemas/StatusEnum'

taskbadger/celery.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,9 @@ def task_publish_handler(sender=None, headers=None, body=None, **kwargs):
137137
ctask = celery.current_app.tasks.get(sender)
138138

139139
# get kwargs from the task class (set via decorator)
140-
kwargs = getattr(ctask, TB_KWARGS_ARG, {})
140+
# copy: the raw attr is shared across invocations, and we mutate per-publish
141+
# values (external_id, status, name) into it below
142+
kwargs = dict(getattr(ctask, TB_KWARGS_ARG, {}))
141143
for attr in dir(ctask):
142144
if attr.startswith(KWARG_PREFIX) and attr not in IGNORE_ARGS:
143145
kwargs[attr.removeprefix(KWARG_PREFIX)] = getattr(ctask, attr)
@@ -147,6 +149,7 @@ def task_publish_handler(sender=None, headers=None, body=None, **kwargs):
147149
kwargs["status"] = StatusEnum.PENDING
148150
if routing_key and "queue" not in kwargs:
149151
kwargs["queue"] = routing_key
152+
kwargs.setdefault("external_id", headers["id"])
150153
name = kwargs.pop("name", headers["task"])
151154

152155
global_record_task_args = celery_system and celery_system.record_task_args
@@ -247,7 +250,8 @@ def _maybe_create_task(signal_sender):
247250

248251
delivery_info = getattr(signal_sender.request, "delivery_info", None) or {}
249252
queue = delivery_info.get("routing_key")
250-
task = create_task_safe(task_name, status=StatusEnum.PENDING, data=data, queue=queue)
253+
external_id = signal_sender.request.id
254+
task = create_task_safe(task_name, status=StatusEnum.PENDING, data=data, queue=queue, external_id=external_id)
251255
if task:
252256
# Store the task ID in the request so _update_task can find it
253257
signal_sender.request.update({TB_TASK_ID: task.id})

taskbadger/internal/models/patched_task_request.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ class PatchedTaskRequest:
2626
immutable thereafter. If omitted, an ID is generated.
2727
name (str | Unset): Name of the task
2828
queue (str | Unset): Queue the task is from
29+
external_id (str | Unset): Identifier from the originating system (e.g. Celery task ID) for correlating with
30+
logs
2931
status (StatusEnum | Unset): * `pending` - pending
3032
* `pre_processing` - pre_processing
3133
* `processing` - processing
@@ -53,6 +55,7 @@ class PatchedTaskRequest:
5355
id: str | Unset = UNSET
5456
name: str | Unset = UNSET
5557
queue: str | Unset = UNSET
58+
external_id: str | Unset = UNSET
5659
status: StatusEnum | Unset = StatusEnum.PENDING
5760
value: int | None | Unset = UNSET
5861
value_max: int | Unset = UNSET
@@ -74,6 +77,8 @@ def to_dict(self) -> dict[str, Any]:
7477

7578
queue = self.queue
7679

80+
external_id = self.external_id
81+
7782
status: str | Unset = UNSET
7883
if not isinstance(self.status, Unset):
7984
status = self.status.value
@@ -135,6 +140,8 @@ def to_dict(self) -> dict[str, Any]:
135140
field_dict["name"] = name
136141
if queue is not UNSET:
137142
field_dict["queue"] = queue
143+
if external_id is not UNSET:
144+
field_dict["external_id"] = external_id
138145
if status is not UNSET:
139146
field_dict["status"] = status
140147
if value is not UNSET:
@@ -169,6 +176,8 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
169176

170177
queue = d.pop("queue", UNSET)
171178

179+
external_id = d.pop("external_id", UNSET)
180+
172181
_status = d.pop("status", UNSET)
173182
status: StatusEnum | Unset
174183
if isinstance(_status, Unset):
@@ -261,6 +270,7 @@ def _parse_stale_timeout(data: object) -> int | None | Unset:
261270
id=id,
262271
name=name,
263272
queue=queue,
273+
external_id=external_id,
264274
status=status,
265275
value=value,
266276
value_max=value_max,

taskbadger/internal/models/task.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ class Task:
3333
id (str | Unset): Task ID. May be set on creation to a UUID or shortened UUID; it must be unique and is
3434
immutable thereafter. If omitted, an ID is generated.
3535
queue (str | Unset): Queue the task is from
36+
external_id (str | Unset): Identifier from the originating system (e.g. Celery task ID) for correlating with
37+
logs
3638
status (StatusEnum | Unset): * `pending` - pending
3739
* `pre_processing` - pre_processing
3840
* `processing` - processing
@@ -67,6 +69,7 @@ class Task:
6769
public_url: str
6870
id: str | Unset = UNSET
6971
queue: str | Unset = UNSET
72+
external_id: str | Unset = UNSET
7073
status: StatusEnum | Unset = StatusEnum.PENDING
7174
value: int | None | Unset = UNSET
7275
value_max: int | Unset = UNSET
@@ -103,6 +106,8 @@ def to_dict(self) -> dict[str, Any]:
103106

104107
queue = self.queue
105108

109+
external_id = self.external_id
110+
106111
status: str | Unset = UNSET
107112
if not isinstance(self.status, Unset):
108113
status = self.status.value
@@ -173,6 +178,8 @@ def to_dict(self) -> dict[str, Any]:
173178
field_dict["id"] = id
174179
if queue is not UNSET:
175180
field_dict["queue"] = queue
181+
if external_id is not UNSET:
182+
field_dict["external_id"] = external_id
176183
if status is not UNSET:
177184
field_dict["status"] = status
178185
if value is not UNSET:
@@ -226,6 +233,8 @@ def _parse_value_percent(data: object) -> int | None:
226233

227234
queue = d.pop("queue", UNSET)
228235

236+
external_id = d.pop("external_id", UNSET)
237+
229238
_status = d.pop("status", UNSET)
230239
status: StatusEnum | Unset
231240
if isinstance(_status, Unset):
@@ -325,6 +334,7 @@ def _parse_stale_timeout(data: object) -> int | None | Unset:
325334
public_url=public_url,
326335
id=id,
327336
queue=queue,
337+
external_id=external_id,
328338
status=status,
329339
value=value,
330340
value_max=value_max,

taskbadger/internal/models/task_request.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ class TaskRequest:
2626
id (str | Unset): Task ID. May be set on creation to a UUID or shortened UUID; it must be unique and is
2727
immutable thereafter. If omitted, an ID is generated.
2828
queue (str | Unset): Queue the task is from
29+
external_id (str | Unset): Identifier from the originating system (e.g. Celery task ID) for correlating with
30+
logs
2931
status (StatusEnum | Unset): * `pending` - pending
3032
* `pre_processing` - pre_processing
3133
* `processing` - processing
@@ -53,6 +55,7 @@ class TaskRequest:
5355
name: str
5456
id: str | Unset = UNSET
5557
queue: str | Unset = UNSET
58+
external_id: str | Unset = UNSET
5659
status: StatusEnum | Unset = StatusEnum.PENDING
5760
value: int | None | Unset = UNSET
5861
value_max: int | Unset = UNSET
@@ -74,6 +77,8 @@ def to_dict(self) -> dict[str, Any]:
7477

7578
queue = self.queue
7679

80+
external_id = self.external_id
81+
7782
status: str | Unset = UNSET
7883
if not isinstance(self.status, Unset):
7984
status = self.status.value
@@ -137,6 +142,8 @@ def to_dict(self) -> dict[str, Any]:
137142
field_dict["id"] = id
138143
if queue is not UNSET:
139144
field_dict["queue"] = queue
145+
if external_id is not UNSET:
146+
field_dict["external_id"] = external_id
140147
if status is not UNSET:
141148
field_dict["status"] = status
142149
if value is not UNSET:
@@ -171,6 +178,8 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
171178

172179
queue = d.pop("queue", UNSET)
173180

181+
external_id = d.pop("external_id", UNSET)
182+
174183
_status = d.pop("status", UNSET)
175184
status: StatusEnum | Unset
176185
if isinstance(_status, Unset):
@@ -263,6 +272,7 @@ def _parse_stale_timeout(data: object) -> int | None | Unset:
263272
name=name,
264273
id=id,
265274
queue=queue,
275+
external_id=external_id,
266276
status=status,
267277
value=value,
268278
value_max=value_max,

taskbadger/procrastinate.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,12 +149,16 @@ def _wrap_defer(task):
149149
@functools.wraps(original_defer)
150150
def defer(**kwargs):
151151
kwargs = _maybe_create_pending(task, kwargs)
152-
return original_defer(**kwargs)
152+
job_id = original_defer(**kwargs)
153+
_record_external_id(kwargs, job_id)
154+
return job_id
153155

154156
@functools.wraps(original_defer_async)
155157
async def defer_async(**kwargs):
156158
kwargs = _maybe_create_pending(task, kwargs)
157-
return await original_defer_async(**kwargs)
159+
job_id = await original_defer_async(**kwargs)
160+
_record_external_id(kwargs, job_id)
161+
return job_id
158162

159163
task.defer = defer
160164
task.defer_async = defer_async
@@ -214,6 +218,18 @@ def _maybe_create_pending(task, kwargs):
214218
return new_kwargs
215219

216220

221+
def _record_external_id(kwargs, job_id):
222+
"""Record the Procrastinate job id as the TaskBadger task's ``external_id``.
223+
224+
``defer`` only returns the DB-assigned job id once the job is enqueued, so this
225+
runs as a follow-up update after both ids are known. No-op if the defer wasn't
226+
tracked (no injected id) or no job id came back."""
227+
tb_id = kwargs.get(TB_TASK_ID_KWARG)
228+
if tb_id is None or job_id is None:
229+
return
230+
update_task_safe(tb_id, external_id=str(job_id))
231+
232+
217233
def _serialize_kwargs(kwargs):
218234
"""Return a JSON-roundtrippable copy of the defer kwargs.
219235
@@ -296,14 +312,19 @@ def _patch_job_manager(app, system):
296312
@functools.wraps(original)
297313
async def patched(*, job, periodic_id, defer_timestamp):
298314
task = app.tasks.get(job.task_name)
315+
tb_id = None
299316
if task is not None:
300317
tb_task = _create_pending_task(task, job.task_kwargs, queue=job.queue)
301318
if tb_task is not None:
302319
new_kwargs = {**job.task_kwargs, TB_TASK_ID_KWARG: tb_task.id}
303320
job = job.evolve(task_kwargs=new_kwargs)
304-
return await jm._taskbadger_original_defer_periodic_job(
321+
tb_id = tb_task.id
322+
job_id = await jm._taskbadger_original_defer_periodic_job(
305323
job=job, periodic_id=periodic_id, defer_timestamp=defer_timestamp
306324
)
325+
if tb_id is not None and job_id is not None:
326+
update_task_safe(tb_id, external_id=str(job_id))
327+
return job_id
307328

308329
jm.defer_periodic_job = patched
309330

taskbadger/sdk.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ def create_task(
153153
tags: dict[str, str] = None,
154154
queue: str = None,
155155
task_id: str = None,
156+
external_id: str = None,
156157
) -> "Task":
157158
"""Create a Task.
158159
@@ -170,6 +171,7 @@ def create_task(
170171
queue: Name of the queue the task is from.
171172
task_id: ID to assign to the task. May be a UUID or shortened UUID; it must be
172173
unique and is immutable. If omitted, an ID is generated by the server.
174+
external_id: Identifier from the originating system (e.g. Celery task ID) for correlating with logs.
173175
174176
Returns:
175177
Task: The created Task object.
@@ -182,6 +184,8 @@ def create_task(
182184
task_dict["id"] = task_id
183185
if queue is not None:
184186
task_dict["queue"] = queue
187+
if external_id is not None:
188+
task_dict["external_id"] = external_id
185189
if value is not None:
186190
task_dict["value"] = value
187191
if value_max is not None:
@@ -227,6 +231,7 @@ def update_task(
227231
actions: list[Action] = None,
228232
tags: dict[str, str] = None,
229233
queue: str = None,
234+
external_id: str = None,
230235
) -> "Task":
231236
"""Update a task.
232237
Requires only the task ID and fields to update.
@@ -243,6 +248,7 @@ def update_task(
243248
actions: Task actions. **Deprecated:** use project-level actions instead.
244249
tags: Dictionary of namespace -> value tags.
245250
queue: Name of the queue the task is from.
251+
external_id: Identifier from the originating system (e.g. Celery task ID) for correlating with logs.
246252
247253
Returns:
248254
Task: The updated Task object.
@@ -255,6 +261,7 @@ def update_task(
255261
max_runtime = _none_to_unset(max_runtime)
256262
stale_timeout = _none_to_unset(stale_timeout)
257263
queue = _none_to_unset(queue)
264+
external_id = _none_to_unset(external_id)
258265

259266
data = data or UNSET
260267
body = PatchedTaskRequest(
@@ -266,6 +273,7 @@ def update_task(
266273
max_runtime=max_runtime,
267274
stale_timeout=stale_timeout,
268275
queue=queue,
276+
external_id=external_id,
269277
)
270278
if actions:
271279
_warn_actions_deprecated()
@@ -341,6 +349,7 @@ def create(
341349
tags: dict[str, str] = None,
342350
queue: str = None,
343351
task_id: str = None,
352+
external_id: str = None,
344353
) -> "Task":
345354
"""Create a new task
346355
@@ -359,6 +368,7 @@ def create(
359368
tags=tags,
360369
queue=queue,
361370
task_id=task_id,
371+
external_id=external_id,
362372
)
363373

364374
def __init__(self, task):
@@ -444,6 +454,7 @@ def update(
444454
actions: list[Action] = None,
445455
tags: dict[str, str] = None,
446456
queue: str = None,
457+
external_id: str = None,
447458
data_merge_strategy: Any = None,
448459
):
449460
"""Generic update method used to update any of the task fields.
@@ -472,6 +483,7 @@ def update(
472483
actions=actions,
473484
tags=tags,
474485
queue=queue,
486+
external_id=external_id,
475487
)
476488
self._task = task._task
477489

0 commit comments

Comments
 (0)