Maintenance¶
MaintenanceAccessor on Dhis2Client.maintenance — the data-integrity reader (get_integrity_report, iter_integrity_issues), the on-demand CategoryOptionCombo matrix regeneration (update_category_option_combos), and the analytics-table build trigger (run_analytics_tables). Predictor and validation runs live on their own accessors; see Validation + predictors.
When to reach for it¶
- Run DHIS2's built-in data-integrity scan (81 checks) and pull the typed report.
- Stream tagged integrity issues as they're emitted (large instances can have thousands;
iter_integrity_issuesis the streaming consumer). - Trigger COC matrix regeneration after a
CategoryCombosave on v43 (update_category_option_combos) — see alsocategory_combos.wait_for_coc_generationfor the polling helper that pairs with it. - Build the analytics tables after a data push (
run_analytics_tables) and follow the job throughclient.tasks.
Worked example — stream integrity issues¶
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:
# `iter_integrity_issues` is an async iterator — one `IntegrityIssueRow`
# per row as DHIS2 emits it; safe for instances with thousands of issues.
# Each row carries the owning check's metadata (`check_name`,
# `check_display_name`, `severity`) plus the typed issue itself
# (`row.issue.name`, `row.issue.id`, `row.issue.comment`).
severity_counts: dict[str, int] = {}
async for row in client.maintenance.iter_integrity_issues():
sev = row.severity or "UNKNOWN"
severity_counts[sev] = severity_counts.get(sev, 0) + 1
if severity_counts[sev] <= 3:
print(f" [{sev}] {row.check_display_name or row.check_name}: {row.issue.name} ({row.issue.id})")
print(f"total by severity: {severity_counts}")
Worked example — full integrity report (snapshot, not stream)¶
async with open_client(profile_from_env()) as client:
# Returns a typed `DataIntegrityReport` carrying `.results`, a
# `dict[str, DataIntegrityResult]` keyed by check name. Each result
# has `.name`, `.severity`, `.count`, and `.issues` (list of
# DataIntegrityIssue with `.name`, `.id`, `.comment`).
report = await client.maintenance.get_integrity_report()
print(f"{len(report.results)} checks ran")
for check_key, result in list(report.results.items())[:5]:
print(f" {check_key} severity={result.severity} issues={len(result.issues)}")
Worked example — build the analytics tables¶
run_analytics_tables POSTs /api/resourceTables/analytics and returns the job-kickoff WebMessageResponse. last_years caps how many years the tables cover; each skip_* flag drops one build phase and is only sent when set. The envelope's task_ref() is what client.tasks takes, and notifier_endpoint() is the /api/system/tasks/{jobType}/{uid} path verbatim.
async with open_client(profile_from_env()) as client:
envelope = await client.maintenance.run_analytics_tables(last_years=1, skip_outliers=True)
ref = envelope.task_ref()
if ref is None:
raise RuntimeError(f"no job scheduled: {envelope.message}")
print(f"feed at {envelope.notifier_endpoint()}")
# Block until DHIS2 posts the terminal notification ...
completion = await client.tasks.await_completion(ref, timeout=None)
print(f"{completion.level} {completion.message}")
... or read the feed one poll at a time with client.tasks.poll_once when the caller has its own clock; see Tasks module. The CLI equivalent is d2w maintenance refresh analytics --watch, and the MCP tool is maintenance_refresh_analytics; both call the plugin service, which wraps this accessor.
Related examples¶
examples/client/analytics_tables_poll_once.py—run_analytics_tablesfollowed withclient.tasks.poll_once, one poll per tick.examples/client/task_await.py—run_analytics_tablesblocked on withclient.tasks.await_completion.examples/client/integrity_issues_stream.py—iter_integrity_issues+ severity histogram + early-break scan.
maintenance
¶
Typed models + client accessor for DHIS2 maintenance + data-integrity + task-notification APIs.
DataIntegrityCheck and DataIntegrityIssue come from
dhis2w_client.generated.v43.oas. DataIntegrityResult and
DataIntegrityReport stay hand-written — OpenAPI splits the result into
separate DataIntegrityDetails / DataIntegritySummary shapes, but this
module's callers want the merged view + the client-side {check_name: result}
map. Notification re-exports the OAS type so callers get typed
category: JobType, dataType: NotificationDataType, level: NotificationLevel
enums + time: datetime.
MaintenanceAccessor (bound to Dhis2Client.maintenance) exposes the
data-integrity read paths. iter_integrity_issues is the ergonomic
entry point for large runs — yields one issue at a time tagged with its
owning check's metadata, so callers don't have to walk the
{check_name: {issues: [...]}} two-level shape themselves.
Classes¶
DataIntegrityCheck
¶
Bases: BaseModel
OpenAPI schema DataIntegrityCheck.
Source code in packages/dhis2w-client/src/dhis2w_client/generated/v43/oas/data_integrity_check.py
DataIntegrityIssue
¶
Bases: BaseModel
OpenAPI schema DataIntegrityIssue.
Source code in packages/dhis2w-client/src/dhis2w_client/generated/v43/oas/data_integrity_issue.py
Notification
¶
Bases: BaseModel
OpenAPI schema Notification.
Source code in packages/dhis2w-client/src/dhis2w_client/generated/v43/oas/notification.py
JobType
¶
Bases: StrEnum
JobType.
Source code in packages/dhis2w-client/src/dhis2w_client/generated/v43/oas/_enums.py
NotificationDataType
¶
NotificationLevel
¶
Bases: StrEnum
NotificationLevel.
Source code in packages/dhis2w-client/src/dhis2w_client/generated/v43/oas/_enums.py
DataIntegrityResult
¶
Bases: BaseModel
Result of one check — populated after the async job completes.
Merges OpenAPI's DataIntegrityDetails (has issues[]) and
DataIntegritySummary (has count) into one caller-friendly shape.
Both modes populate startTime / finishedTime once the job has run;
an unrun check returns the definition block alone.
Source code in packages/dhis2w-client/src/dhis2w_client/v43/maintenance.py
DataIntegrityReport
¶
Bases: BaseModel
/api/dataIntegrity/summary or /details response — keyed by check name.
Hand-written: DHIS2 returns {check_name: result} — a client-side convenience
shape not in OpenAPI. The from_api classmethod hides the raw-dict detail.
Source code in packages/dhis2w-client/src/dhis2w_client/v43/maintenance.py
IntegrityIssueRow
¶
Bases: BaseModel
One issue from a data-integrity run, tagged with its owning check's metadata.
iter_integrity_issues yields these as a flat stream so callers can
filter / transform without walking the two-level
{check_name: {issues: [...]}} shape themselves.
Source code in packages/dhis2w-client/src/dhis2w_client/v43/maintenance.py
MaintenanceAccessor
¶
Dhis2Client.maintenance — read paths for the data-integrity surface.
Writes (kicking off a run, clearing cache) stay on the plugin-layer
service in dhis2w_core for now — those need a Profile for OAuth2
token-store keying, which the raw client doesn't know about.
Source code in packages/dhis2w-client/src/dhis2w_client/v43/maintenance.py
91 92 93 94 95 96 97 98 99 100 101 102 103 104 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 220 221 222 223 224 225 | |
Methods:¶
__init__(client)
¶
Bind to the sharing client — reuses its auth + HTTP pool for every request.
get_integrity_report(*, checks=None, details=True)
async
¶
Fetch the full /api/dataIntegrity/{details|summary} report as a typed model.
details=True (the default) populates issues[] on each result;
details=False hits the cheaper /summary endpoint which returns
just counts + timing. Pass checks to narrow to specific check
names (from list_dataintegrity_checks); omit for every check
the last run produced.
Source code in packages/dhis2w-client/src/dhis2w_client/v43/maintenance.py
iter_integrity_issues(*, checks=None)
async
¶
Stream every issue from /api/dataIntegrity/details one at a time.
DHIS2's endpoint returns the whole {check_name: {issues: [...]}}
structure in one response (no server-side pagination). This helper
still buys you:
- A flat stream —
async for row in ...instead of nested loops. - Tagged rows — each yielded
IntegrityIssueRowcarries the owning check's name + display name + severity, so the caller knows the provenance without a second lookup. - Early break — stop iteration mid-stream without building the full list in memory on the Python side.
Issues yield in the order DHIS2 returns checks, then the order
of that check's issues[] list — stable across runs.
Source code in packages/dhis2w-client/src/dhis2w_client/v43/maintenance.py
run_analytics_tables(*, last_years=None, skip_resource_tables=False, skip_aggregate=False, skip_events=False, skip_enrollment=False, skip_org_unit_ownership=False, skip_outliers=False, skip_tracked_entities=False, skip_validation_result=False)
async
¶
Kick off an analytics-table build; return the job-kickoff envelope.
POSTs /api/resourceTables/analytics. DHIS2 schedules an
ANALYTICS_TABLE job and answers with a job-kickoff
WebMessageResponse — call .task_ref() on the result for
(job_type, task_uid) and .notifier_endpoint() for the
relativeNotifierEndpoint path. Feed the task ref to
client.tasks.poll_once to read notifications one engine tick at a
time, or client.tasks.await_completion to block until the build
finishes.
Each skip_* flag drops one build phase; last_years caps how many
years of data the tables cover (omit for DHIS2's default). A flag left
at its False default is not sent — DHIS2 already defaults it to
False.
Source code in packages/dhis2w-client/src/dhis2w_client/v43/maintenance.py
update_category_option_combos()
async
¶
Trigger DHIS2 to (re)generate the CategoryOptionCombo matrix.
DHIS2 v42 auto-generated COCs whenever a CategoryCombo was saved, so callers rarely needed this. v43 changed the behavior — saving a CategoryCombo no longer triggers regeneration; the matrix stays empty until this maintenance task runs.
client.category_combos.wait_for_coc_generation calls this
helper internally before polling so the helper "just works" on
both versions. Call it directly when you need to ensure the COC
matrix is up to date for an existing combo (e.g. after appending
a category via add_category).
The endpoint is synchronous — DHIS2 walks every persisted combo, adds missing COCs, removes orphaned ones, and returns when done.