Tasks module¶
client.tasks — block on DHIS2 background jobs (analytics refresh, metadata import, predictor runs, etc.). DHIS2's task endpoints (/api/system/tasks/{jobType} and /api/system/taskSummaries/{jobType}) report job progress as a stream of Notification entries; this module polls them, de-dupes, and resolves a typed TaskCompletion when completed=True lands on the feed.
When to reach for it¶
- After kicking off any async DHIS2 endpoint (
POST /api/resourceTables/analytics,POST /api/predictors/run,POST /api/dataAnalysis/validationRules, an/api/metadataimport withasync=true, …). - When you want a Rich progress display in the terminal — call
iter_notificationsinstead ofawait_completionand render each entry as it arrives. - In a test that needs to wait for a real DHIS2 side-effect to land before asserting.
Worked example — kick off + block + branch on completion¶
from dhis2w_client.v43.tasks import TaskTimeoutError
from dhis2w_core.client_context import open_client
from dhis2w_core.profile import profile_from_env
async with open_client(profile_from_env()) as client:
# 1. Kick off an analytics-table build. DHIS2 returns a WebMessage
# envelope; `.task_ref()` extracts the `(job_type, uid)` tuple
# await_completion takes.
envelope = await client.maintenance.run_analytics_tables(last_years=1)
ref = envelope.task_ref()
if ref is None:
print("no task-ref in response — nothing to watch")
return
job_type, task_uid = ref
print(f"kicked off {job_type}/{task_uid}")
# 2. Block until DHIS2 marks the job complete.
try:
completion = await client.tasks.await_completion(
ref,
timeout=300.0,
poll_interval=2.0,
)
except TaskTimeoutError as exc:
# The partial notification list is reachable via the iterator path,
# not this exception — the exception only carries the timeout message.
print(f"timed out: {exc}")
return
last = completion.final
print(f"done in {len(completion.notifications)} notifications")
print(f" last: level={last.level} message={last.message!r}")
Two failure shapes to handle¶
TaskTimeoutError— the polling loop hittimeoutbefore DHIS2 marked the job complete. Passtimeout=Nonefor jobs that can legitimately take hours (full analytics rebuilds on large datasets).completion.final.level == "ERROR"— the job finished but DHIS2 marked it failed. Thenotificationslist shows the trail; the last entry typically carries the actionable message.
if completion.final.level == "ERROR":
raise RuntimeError(f"task {job_type}/{task_uid} failed: {completion.final.message}")
Streaming notifications (Rich progress / SSE bridges)¶
If you want to render each notification as it arrives instead of waiting for the final one, use iter_notifications:
async for notification in client.tasks.iter_notifications(ref, timeout=600):
print(f" [{notification.level}] {notification.message}")
if notification.completed:
break
One poll per tick (external schedulers)¶
await_completion and iter_notifications own the loop. A caller that already has a clock, such as a workflow engine polling once per tick or a UI refreshing on a timer, wants one read and a return. poll_once is that primitive, and the two blocking helpers are built on it:
poll = await client.tasks.poll_once(ref)
for notification in poll.new:
print(f" [{notification.level}] {notification.message}")
if not poll.completed:
later = await client.tasks.poll_once(ref, cursor=poll.cursor)
Each call GETs /api/system/tasks/{job_type}/{uid} once and returns a TaskPoll: new holds the notifications not covered by the cursor, oldest first and ending at DHIS2's terminal row when it has arrived, completed says whether that row has arrived, and cursor is what to pass next time so already-seen rows stay out. relative_notifier_endpoint is the path the poll read. The cursor is a frozenset of notification identifiers, so it survives being stored between ticks.
parse_task_ref(...) converts either a (job_type, uid) tuple or a "JOB_TYPE/uid" string into the canonical tuple form — handy when wiring the awaiter to call sites that get the ref from different shapes.
Related examples¶
examples/client/task_await.py— end-to-end analytics-refresh kick-off + block.examples/client/analytics_tables_poll_once.py— the same job followed withpoll_onceand a carried cursor.
tasks
¶
Client-level task awaiter for DHIS2 background jobs.
Every async DHIS2 operation (analytics refresh, metadata import, data-integrity
run, tracker async push) returns a WebMessageResponse carrying
jobType + task UID. Callers historically had two options: (a) roll
their own polling loop against /api/system/tasks/{type}/{uid}, or (b)
go through the plugin-layer CLI --watch flag. Neither works when you
want to block a library script on job completion.
client.tasks.await_completion(task_ref) is that helper. Polls the
task-status feed on the already-open HTTP connection (no new client per
poll, unlike the profile-based watch_task in dhis2w-core), de-dupes
notifications by their identifier, and returns a typed TaskCompletion
once completed=True lands.
Caveats:
- Poll interval defaults to 2.0s. DHIS2's notification feed updates at best every ~1s, so faster polling just burns request quota.
- Timeout defaults to 600.0s (10 min). Analytics refreshes on large
instances can run longer — pass
timeout=Noneto block forever, ortimeout=3600etc.
Classes¶
TaskTimeoutError
¶
TaskCompletion
¶
Bases: BaseModel
Result of awaiting a DHIS2 background task — every notification + the terminal row.
Source code in packages/dhis2w-client/src/dhis2w_client/v43/tasks.py
TaskPoll
¶
Bases: BaseModel
One non-blocking read of a task's notification feed — the rows new since a cursor.
poll_once returns this so a caller polling on its own schedule (an engine
tick, a UI refresh) reads the feed once and returns instead of blocking:
new holds the notifications not seen under the passed cursor
(chronological, ending at the completing row), completed is True once
DHIS2's terminal notification has arrived, and cursor is what to pass to
the next poll_once so only newer rows come back.
Source code in packages/dhis2w-client/src/dhis2w_client/v43/tasks.py
TaskModule
¶
Accessor bound to a Dhis2Client exposing background-task polling.
Source code in packages/dhis2w-client/src/dhis2w_client/v43/tasks.py
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 | |
Methods:¶
__init__(client)
¶
Bind to the sharing client — reuses its auth + HTTP pool for every poll.
await_completion(task_ref, *, timeout=600.0, poll_interval=2.0)
async
¶
Block until the task completes; return a typed TaskCompletion.
Polls /api/system/tasks/{job_type}/{uid} every poll_interval
seconds until a notification with completed=True arrives. Raises
TaskTimeoutError if timeout elapses first (pass None for no
timeout). De-dupes notifications by uid/id/time so repeated
polls don't surface the same entries twice.
Source code in packages/dhis2w-client/src/dhis2w_client/v43/tasks.py
poll_once(task_ref, *, cursor=None)
async
¶
Read a task's notification feed once and return what's new — never blocks.
GETs /api/system/tasks/{job_type}/{uid} a single time (no loop, no
sleep) and returns a TaskPoll: the notifications not present in
cursor, whether the task has completed, and the next cursor. Pass the
previous poll's cursor back on the next call so only newer rows come
through — this is the primitive an external scheduler polls once per
tick instead of blocking in await_completion / iter_notifications,
which are built on it.
New rows come oldest-first and stop at the first completed=True row
(DHIS2's terminal notification), which sets completed. A row without a
stable identifier surfaces on every poll, matching the blocking path.
Source code in packages/dhis2w-client/src/dhis2w_client/v43/tasks.py
iter_notifications(task_ref, *, timeout=600.0, poll_interval=2.0)
async
¶
Yield each notification as it arrives; stop when completed=True or timeout.
Separate from await_completion so CLI renderers (Rich progress
bars, server-sent-event bridges, etc.) can render each entry
incrementally instead of waiting for the final result.
Source code in packages/dhis2w-client/src/dhis2w_client/v43/tasks.py
Functions:¶
parse_task_ref(task_ref)
¶
Normalise (job_type, uid) or "job_type/uid" into a (job_type, uid) tuple.
Convenience for callers that store task refs as a single string — matches
what the CLI task-watch flag prints. Strings are split on the last /.