Skip to content

Commit 4f4496c

Browse files
snopokeclaude
andcommitted
Let tracked Procrastinate tasks inherit system options
A `@track`ed task kept `_taskbadger_system = None` when the integration was constructed after it was registered, so it never picked up the system's options. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent a23ada4 commit 4f4496c

2 files changed

Lines changed: 33 additions & 3 deletions

File tree

‎taskbadger/procrastinate.py‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
_INSTRUMENTED_ATTR = "_taskbadger_instrumented"
4343
_MANUAL_ATTR = "_taskbadger_manual"
4444
_OPTS_ATTR = "_taskbadger_opts"
45+
_SYSTEM_ATTR = "_taskbadger_system"
4546

4647
_current_tb_task_id: ContextVar[str | None] = ContextVar("_current_tb_task_id", default=None)
4748

@@ -62,6 +63,12 @@ def _instrument_task(task, system=None, manual=False, opts=None):
6263
if manual:
6364
setattr(task, _MANUAL_ATTR, True)
6465

66+
# Set before the idempotency check: a task already instrumented by ``@track``
67+
# when the system integration is constructed still has to inherit the
68+
# system's options (heartbeat_interval, stale_timeout, record_task_args).
69+
if system is not None or not hasattr(task, _SYSTEM_ATTR):
70+
setattr(task, _SYSTEM_ATTR, system)
71+
6572
if getattr(task, _INSTRUMENTED_ATTR, False):
6673
return
6774

@@ -116,7 +123,6 @@ def wrapped(*args, **kwargs):
116123
_wrap_defer(task)
117124
task.func = wrapped
118125
setattr(task, _INSTRUMENTED_ATTR, True)
119-
setattr(task, "_taskbadger_system", system)
120126

121127

122128
def _update_status(tb_id, status, exception=None):
@@ -150,7 +156,7 @@ def _update_status(tb_id, status, exception=None):
150156
def _heartbeat_interval(task):
151157
"""Seconds between pings while ``task`` runs, or ``None`` for no heartbeat."""
152158
opts = getattr(task, _OPTS_ATTR, {}) or {}
153-
system = getattr(task, "_taskbadger_system", None)
159+
system = getattr(task, _SYSTEM_ATTR, None)
154160
interval, _ = resolve_heartbeat_options(opts.get("heartbeat_interval"), None, system)
155161
return interval
156162

@@ -195,7 +201,7 @@ def _create_pending_task(task, task_kwargs, queue=None):
195201
if not Badger.is_configured():
196202
return None
197203

198-
system = getattr(task, "_taskbadger_system", None)
204+
system = getattr(task, _SYSTEM_ATTR, None)
199205
manual = getattr(task, _MANUAL_ATTR, False)
200206
auto = bool(system) and system.track_task(task.name)
201207
if not manual and not auto:

‎tests/test_procrastinate_heartbeat.py‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,30 @@ def auto_heartbeat(a):
150150
heartbeat.stop.assert_called_once_with("tb-5")
151151

152152

153+
@pytest.mark.usefixtures("_bind_settings")
154+
def test_system_integration_heartbeat_for_tracked_task(app, heartbeat):
155+
"""A ``@track``ed task still inherits the system's options when the
156+
integration is constructed after the task was registered."""
157+
158+
@track()
159+
@app.task(name="tracked_auto_heartbeat")
160+
def tracked_auto_heartbeat(a):
161+
return a
162+
163+
ProcrastinateSystemIntegration(app=app, heartbeat_interval=45)
164+
165+
with (
166+
mock.patch("taskbadger.procrastinate.create_task_safe", return_value=task_for_test(id="tb-6")) as create,
167+
mock.patch("taskbadger.procrastinate.update_task_safe"),
168+
mock.patch("taskbadger.sdk.get_task", return_value=task_for_test(status=StatusEnum.PROCESSING)),
169+
):
170+
tracked_auto_heartbeat.defer(a=1)
171+
tracked_auto_heartbeat.func(a=1, **{TB_TASK_ID_KWARG: "tb-6"})
172+
173+
assert create.call_args.kwargs["stale_timeout"] == 90
174+
heartbeat.start.assert_called_once_with("tb-6", 45)
175+
176+
153177
def test_track_rejects_bad_interval(app):
154178
with pytest.raises(ConfigurationError):
155179
track(heartbeat_interval=0)

0 commit comments

Comments
 (0)