@@ -160,17 +160,22 @@ async def defer_async(**kwargs):
160160 task .defer_async = defer_async
161161
162162
163- def _maybe_create_pending (task , kwargs ):
164- """Decide whether to track this defer, and if so create the TaskBadger
165- task and inject its id into ``kwargs``. Always returns the kwargs dict."""
163+ def _create_pending_task (task , task_kwargs ):
164+ """Create a PENDING TaskBadger task for ``task`` if it should be tracked.
165+
166+ Returns the created TaskBadger task, or ``None`` if Badger isn't
167+ configured, the task isn't tracked (neither manual nor auto), or the
168+ create call failed. ``task_kwargs`` is used only for the
169+ ``record_task_args`` data capture.
170+ """
166171 if not Badger .is_configured ():
167- return kwargs
172+ return None
168173
169174 system = getattr (task , "_taskbadger_system" , None )
170175 manual = getattr (task , _MANUAL_ATTR , False )
171176 auto = bool (system ) and system .track_task (task .name )
172177 if not manual and not auto :
173- return kwargs
178+ return None
174179
175180 opts = dict (getattr (task , _OPTS_ATTR , {}) or {})
176181 name = opts .pop ("name" , None ) or task .name
@@ -185,12 +190,18 @@ def _maybe_create_pending(task, kwargs):
185190 if record_args is None :
186191 record_args = bool (system ) and system .record_task_args
187192 if record_args :
188- data ["procrastinate_task_kwargs" ] = _serialize_kwargs (kwargs )
193+ data ["procrastinate_task_kwargs" ] = _serialize_kwargs (task_kwargs )
189194
190195 if data :
191196 create_kwargs ["data" ] = data
192197
193- tb_task = create_task_safe (name , ** create_kwargs )
198+ return create_task_safe (name , ** create_kwargs )
199+
200+
201+ def _maybe_create_pending (task , kwargs ):
202+ """Decide whether to track this defer, and if so create the TaskBadger
203+ task and inject its id into ``kwargs``. Always returns the kwargs dict."""
204+ tb_task = _create_pending_task (task , kwargs )
194205 if tb_task is None :
195206 return kwargs
196207
@@ -262,6 +273,37 @@ def current_task():
262273 return safe_get_task (tb_id )
263274
264275
276+ def _patch_job_manager (app , system ):
277+ """Patch ``app.job_manager.defer_periodic_job`` so periodic tasks are tracked.
278+
279+ Procrastinate's ``PeriodicDeferrer`` enqueues jobs by calling
280+ ``job_manager.defer_periodic_job(job=..., ...)`` directly, bypassing
281+ ``task.defer``/``defer_async``. Without this hook, ``@app.periodic`` tasks
282+ would never get a PENDING TaskBadger task created at enqueue time.
283+
284+ Idempotent: a second call updates the system reference but doesn't
285+ re-wrap.
286+ """
287+ jm = app .job_manager
288+ if not getattr (jm , "_taskbadger_original_defer_periodic_job" , None ):
289+ original = jm .defer_periodic_job
290+ jm ._taskbadger_original_defer_periodic_job = original
291+
292+ @functools .wraps (original )
293+ async def patched (* , job , periodic_id , defer_timestamp ):
294+ task = app .tasks .get (job .task_name )
295+ if task is not None :
296+ tb_task = _create_pending_task (task , job .task_kwargs )
297+ if tb_task is not None :
298+ new_kwargs = {** job .task_kwargs , TB_TASK_ID_KWARG : tb_task .id }
299+ job = job .evolve (task_kwargs = new_kwargs )
300+ return await jm ._taskbadger_original_defer_periodic_job (
301+ job = job , periodic_id = periodic_id , defer_timestamp = defer_timestamp
302+ )
303+
304+ jm .defer_periodic_job = patched
305+
306+
265307def _patch_app_task (app , system ):
266308 """Replace ``app.task`` with a wrapper that instruments newly-registered
267309 tasks under the supplied ``system``. Idempotent — a second call replaces
0 commit comments