Examples index¶
Learning path · step 5 of 8 — Curated task index. Prev: Python tutorial. Next: API reference.
examples/{cli,client,mcp,fhir}/is the source of truth for what's on disk; this page is the curated headline view.
One copy of each example, running against DHIS2 v41, v42, and v43 alike. An example that exists for a single major lives under that major's subdirectory — examples/client/v43/ for the v43 schema divergences, examples/client/v41/ for the v41 wire quirks; see Schema diff: v41 -> v42 -> v43 for what they demonstrate.
Each entry below: file path → what it demonstrates → which concept doc explains it. This page is curated — it covers the headline examples per topic; not every single file shows up here. ls examples/{cli,client,mcp,fhir}/ is the source of truth, and each surface directory has a README of its own.
Examples come in four groups — the three surfaces, plus FHIR, which carries four of its own:
- CLI (
examples/cli/*.sh) — bash invocations of thed2wTyper CLI. Run withbash examples/cli/<name>.shwith the venv onPATH(viasource .venv/bin/activateoruv run -- bash ...). - Client (
examples/client/*.py) — Python library usage. Run withuv run python examples/client/<name>.py. - MCP (
examples/mcp/*.py) — FastMCP tool calls through an in-process client. Run withuv run python examples/mcp/<name>.py. - FHIR (
examples/fhir/{cli,client,engine,igs}/) — the FHIR surface, grouped on its own because it is its own product:cli/andclient/are the two shapes of caller ford2w fhir,engine/is the evaluation engine, which has no DHIS2 in it at all, andigs/holds nine complete project trees, one per feature story, which are also the catalogd2w fhir init --templatescaffolds from.
Every example reads the active DHIS2 profile from .dhis2/profiles.toml / ~/.config/dhis2/profiles.toml / DHIS2_PROFILE env (see profiles). Assume a seeded local stack (make dhis2-run) unless stated otherwise.
FHIR examples¶
d2w fhir turns a DHIS2 instance's metadata into a FHIR Implementation Guide, serves the compiled guide as a read-and-capture endpoint, and posts what that endpoint captured back into DHIS2. dhis2w-fhir and dhis2w-fhir-serve are not per-version packages, so this group is one copy that runs on every major. The narrative these sit under is the d2w fhir guide series, which opens at the Introduction.
The headline rows are below. The Python library path has fifty-four examples in all — grouped from "build a response from my own data" through reading a form, converting to DHIS2, sending, evaluating over a served guide, driving the toolchain, and embedding the facade in your own process — catalogued in examples/fhir/client/README.md. The evaluation engine has nine of its own, catalogued in examples/fhir/engine/README.md.
| Example | What it demonstrates | Related docs |
|---|---|---|
fhir/cli/init.sh |
d2w fhir init — scaffold a dockerized SUSHI IG project, offline, with the identity and selection dials |
Set up an IG project |
fhir/cli/init_from_template.sh |
d2w fhir init --list-templates / --template — scaffold from a guide already generated against an instance, so the project compiles and serves without reaching one |
Start from a template |
fhir/cli/init_refresh.sh |
d2w fhir init --refresh — bring an existing project's scaffold-managed files up to date without losing a line |
Set up an IG project |
fhir/cli/generate_full_run.sh |
d2w fhir generate — every target of the IG source off one pass over the instance |
Generate the IG source |
fhir/cli/generate_json_report.sh |
d2w --json fhir generate — the typed GenerateFullReport on stdout, read with jq: a row per target, and the run's distinct notes grouped by the decision each records |
Generate the IG source |
fhir/cli/generate_foundation.sh |
d2w fhir generate foundation — the instance-independent artifacts, with no client opened |
Generate the IG source |
fhir/cli/generate_option_sets.sh |
d2w fhir generate option-sets — one named target alone: the option-set terminology as pre-built JSON |
Generate the IG source |
fhir/cli/generate_stale_compile.sh |
d2w fhir generate — an unchanged regenerate keeps ig/fsh-generated/, one that rewrites a FSH source removes it and says so, offline |
Generate the IG source |
fhir/cli/generate_hostile_names.sh |
d2w fhir generate --substitute-hostile-names — a DHIS2 name carrying < refuses the run, then publishes as the words it stands for |
Generate the IG source |
fhir/cli/generate_spaced_codes.sh |
d2w fhir generate --substitute-hostile-names — a DHIS2 code carrying a space publishes hyphenated, with the DHIS2 code stated beside it as a dhis2-code property |
Generate the IG source |
fhir/cli/validate.sh |
d2w fhir validate — the FHIR-safety gate over an instance's codes and names, exit 1 on errors |
Validate the instance |
fhir/cli/validate_code_source.sh |
d2w fhir validate --code-source code — preview what switching concept codes to DHIS2 codes would cost |
Validate the instance |
fhir/cli/check_artifacts.sh |
d2w fhir check-artifacts — the build refusal read off the artifacts on disk: a clean project, then one stale resource, exit 1 in seconds, offline |
Build and publish the guide |
fhir/cli/serve.sh |
d2w fhir serve — compile the IG, serve it as a FHIR read + capture facade, post a generated load set, read the receipts back (needs the [serve] extra + docker; skipped by default) |
Serve the guide |
fhir/cli/evaluate.sh |
POST /facade/evaluate and POST /$evaluate over a served guide, from curl — the OperationDefinition the contract lives in, FHIRPath over a stored form, a parse failure answered 200 with its line and column, and the Parameters a CQL library comes back as |
FHIRPath |
fhir/cli/serve_auth_postures.sh |
d2w fhir serve --auth - the four postures a facade takes towards a caller: the bind refusal an absent key earns, a static bearer token, the caller's own DHIS2 credentials with the register read as them, and the jwt issuer |
Secure the facade |
fhir/client/read_register_as_yourself.py |
The register read under the caller's own DHIS2 credentials - what the dhis2 posture changes about who sees whom |
Secure the facade |
fhir/cli/registers_many_types.sh |
Many tracked entity types on one instance, two of them published as Device: one register serving the union, _tag narrowing it to one type, and the checklist naming every type nobody typed (creates and removes its own metadata; skipped by default) |
Custom subject types |
fhir/cli/serve_projection_search.sh |
[serve.search] backend = "projection" - a register search answered from the synced copy: _content, the X-DHIS2W-Projection-As-Of header, and the refusal the live backend answers instead |
Serve the guide |
fhir/cli/serve_attribute_filter.sh |
d2-attribute={attributeUid}\|{value} - the register filtered by what a record holds: the attributes each register declares as filterable at /metadata and /facade/uiconfig, the counts a filter narrows to, a prefix matching nobody, two filters ANDed, an undeclared attribute refused by name, and the same records from both search backends |
Serve the guide |
fhir/cli/serve_record.sh |
GET /facade/tracked-entities/{uid}/events - one tracked entity's own record: every event of its enrollments as the response its programme stage's published form describes, how long the record is, one event on its own, the parameters the surface refuses, and the events dial that withholds the record while identity is still served |
Consume the FHIR API |
fhir/cli/serve_data_set_responses.sh |
GET /facade/data-sets/{uid}/responses - what DHIS2 holds for one data set, at one organisation unit, over the periods the request names: each reporting key as the response the data set's published form describes, the cells typed by that form, one reported form on its own, the bounds a read is refused without, the period count one read may name, and the responses dial that withholds the values while the forms stay published |
Consume the FHIR API |
fhir/cli/summary.sh |
$summary against a served guide - one person's International Patient Summary by tracked entity UID, the same document found by identifier, and what the caveat beside it says |
Consume the FHIR API |
fhir/cli/corrections.sh |
[forward] corrections / [forward] withdrawals - a submission marked amended or entered-in-error refused at the capture door with the key named, and received once the project states it |
Forward captures into DHIS2 |
fhir/cli/withdraw.sh |
d2w fhir withdraw - take back from DHIS2 the event a forwarded receipt landed: the dry run, the commit, and the spool's fourth state (binds a port; writes; skipped by default) |
Forward captures into DHIS2 |
fhir/cli/sync.sh |
d2w fhir sync — fill a durable copy of the DHIS2 register as FHIR on disk: dry run, the initial materialization, the incremental cursor run, and --rebuild |
Serve the guide |
fhir/cli/forward_dry_run.sh |
d2w fhir forward — the default: DHIS2 judges every payload under its own validate-only mode, nothing is written, and the queue is as long as it was (needs docker; skipped by default) |
Forward captures into DHIS2 |
fhir/cli/forward_import.sh |
d2w fhir forward --import — the committing drain, and the three states it files a receipt into (needs docker; writes to the instance; skipped by default) |
Forward captures into DHIS2 |
fhir/cli/forward_overwrites.sh |
The two postures a drain takes towards a value already sent - allow names it, refuse queues the response (needs docker; writes; skipped by default) |
Forward captures into DHIS2 |
fhir/cli/forward_completeness.sh |
Data set completeness: what a completed aggregate response registers, and the dial that turns it off (needs docker; writes; skipped by default) |
Forward captures into DHIS2 |
fhir/cli/doctor_probe.sh |
d2w fhir doctor — the whole chain against one instance over a small representative selection, one verdict (skipped by default) |
Check an instance with doctor |
fhir/cli/doctor_all_targets.sh |
d2w fhir doctor --all-targets — the same chain over every data set, every program, and every organisation-unit level (skipped by default) |
Check an instance with doctor |
fhir/cli/doctor_live_oracle.sh |
d2w fhir doctor --live — the oracle: served resources deep-compared against the instance (skipped by default) |
Check an instance with doctor |
fhir/cli/doctor_report.sh |
d2w fhir doctor --workspace — keep the run's workspace and hand over its markdown report (skipped by default) |
Check an instance with doctor |
fhir/cli/doctor_json.sh |
d2w --json fhir doctor — the typed DoctorReport on stdout, for jq and for CI (skipped by default) |
Check an instance with doctor |
fhir/cli/spool.sh |
d2w fhir spool + d2w fhir requeue — read the capture queue and put a refused receipt back in it, with no DHIS2 connection and no profile |
Forward captures into DHIS2 |
fhir/client/generate_ig.py |
Generate a whole IG from Python — load_project + generate_full, and the GenerateFullReport consumed as a model |
dhis2w_fhir API reference |
fhir/client/consume_facade.py |
Plain httpx2 against a running facade — /metadata, search, $generate, POST a capture, read the receipt and /facade/spool |
Consume the FHIR API |
fhir/client/read_metadata_health.py |
GET /facade/metadata-health off a live facade — the validate findings with the DHIS2 field at fault and what each grade costs, plus translation coverage per locale |
Capture in the browser |
fhir/client/send_with_the_client.py |
FacadeClient against a running facade — $generate a draft, submit_response it, read the CaptureReceipt id and warnings, read the receipt back typed |
dhis2w_fhir API reference |
fhir/client/search_with_the_client.py |
The read half of the same client — canonical_resource_types off /metadata, a typed ResourceQuery against the forms and against the register, and resolve turning a canonical into the resource that holds it |
dhis2w_fhir API reference |
fhir/client/evaluate_with_the_client.py |
evaluate with the typed contexts — InlineResourceContext.over a draft that is stored nowhere, StoredResourceContext over a form the facade holds, and the diagnostic an unparseable expression is answered with rather than raised on |
FHIRPath |
fhir/client/authenticate_with_the_client.py |
BearerToken against a facade started with --auth token — the open reads, the 401 with no credential, the 401 with the wrong one, and the receipt the right one earns |
Run a secured facade |
fhir/client/handle_refusals_with_the_client.py |
FacadeError read typed — the 404 a resource nobody holds raises and the 422 a response answering an unpublished form raises, both through status_code, issues, and diagnostics |
dhis2w_fhir API reference |
fhir/client/evaluate_via_facade.py |
Plain httpx2 against a running facade's POST /facade/evaluate — one FHIRPath call, one CQL library, and one expression that will not parse |
Consume the FHIR API |
fhir/client/evaluate_stored_resource.py |
The stored context — FHIRPath one-liners counting a DHIS2 data set's sections, data elements and category-combination cells off the served Questionnaire, and the 404 a resource nobody holds earns |
FHIRPath |
fhir/client/evaluate_registered_person.py |
The registered context — a chart review of one tracked entity read live out of DHIS2, written as CQL from the guide's own published vocabulary |
CQL |
fhir/client/evaluate_as_parameters.py |
The $evaluate operation's Parameters answer — one parameter per define, part entries per value, an OperationOutcome part per refusal |
Consume the FHIR API |
fhir/client/evaluate_operation_contract.py |
The operation discovered rather than documented — /metadata names it, OperationDefinition/serve-evaluate states its parameters and cardinalities, and a request built to that contract answers |
Consume the FHIR API |
fhir/client/evaluate_compiled_library.py |
ELM as the interchange format — one library compiled locally with ELMSerializer and run on the facade as JSON, compared define by define against its own source |
CQL |
fhir/client/forward_spool.py |
Dry-run a drain from Python — forward_responses, and the ForwardReport counts, outcomes, and rejection reasons |
Forward captures into DHIS2 |
fhir/client/minimal_facade.py |
Facade ladder, level one — one route that translates a capture, posts it, and hands back DHIS2's verdict under DHIS2's own status | Build your own facade |
fhir/client/basic_facade.py |
Facade ladder, level two — one client held for the process in a FastAPI lifespan, settings resolved at startup, /health, and a log line per verdict |
Build your own facade |
fhir/client/complex_facade.py |
Facade ladder, level three — the real spool primitives: a durable receipt, 201 before DHIS2 is asked, a background drain, and GET /receipts/{id} |
Build your own facade |
fhir/client/advanced_facade.py |
Facade ladder, level four — tracker routing, the strict/lenient dial as app config, overwrite naming on drains, and a small /metadata that is not a CapabilityStatement |
Build your own facade |
fhir/client/embed_the_facade.py |
The facade as a library — create_app driven over an ASGI transport, reading /metadata and the published forms with no socket bound |
Embed the facade |
fhir/client/capture_headless.py |
A capture with no server — $generate, POST, the receipt read back, and the spool file it left on disk |
Embed the facade |
fhir/client/forward_headless.py |
The drain from inside your own process — the caller's own DHIS2 connection handed in, and every dial stated as an argument | Embed the facade |
fhir/client/projection_local_store.py |
The local storage layer — run_sync into the SQLite projection, then the watermark, a page, an identifier search, and membership through ProjectionStore |
The materialized projection |
fhir/client/embed_in_fastapi.py |
The facade's routers mounted inside a caller's own FastAPI application, behind the caller's own authentication dependency | Embed the facade |
fhir/client/build_aggregate_response.py |
The minimal aggregate capture — a data set's numbers for one period at one organisation unit, built in typed Python | The capture contract |
fhir/client/build_registration_response.py |
Registering a person and enrolling them, minting both DHIS2 UIDs client-side | The capture contract |
fhir/client/identity_nominations.py |
[ips.identity] read over a real person — a nominated name filled, a sex value the gender map does not answer for, and a birth date stating its own absence |
What goes in |
fhir/client/ips_document.py |
One person's tracker record assembled into an IPS-shaped FHIR document Bundle — one mapped section, three required ones stating absence, and the reference check that proves the document closes |
The IPS document |
Evaluate¶
dhis2w-fhir-engine evaluates FHIRPath, CQL, and ELM over FHIR-shaped data, and scores CQL quality measures into a FHIR R4 MeasureReport. It has no DHIS2 dependency and no web framework, so eight of these nine run instantly against an inline Bundle with nothing else installed. The narrative they sit under is the 501 pages of the guide series.
| Example | What it demonstrates | Related docs |
|---|---|---|
fhir/engine/fhirpath_basics.py |
Paths, where(), first(), count(), exists() over one Patient — and why every expression answers with a collection |
FHIRPath |
fhir/engine/fhirpath_over_bundle.py |
ofType() over a whole Bundle: pick a resource type out of the mix, filter it, count what is left |
FHIRPath |
fhir/engine/cql_library_structure.py |
What each CQL header line declares — library, using, include, codesystem, valueset, code, parameter — and that nothing runs until a definition is asked for by name |
CQL |
fhir/engine/cql_retrieves.py |
[Patient] and [Immunization], then the same retrieves under context Patient: one evaluation per person, each seeing only their own records |
CQL |
fhir/engine/cql_terminology.py |
A ValueSet scoping a retrieve, resolved outside the library — plus $validate-code and memberOf asked of the terminology service directly |
CQL |
fhir/engine/cql_intervals.py |
Interval literals, closed and open bounds, the timing vocabulary, and a "Measurement Period" parameter the caller replaces without editing the library |
CQL |
fhir/engine/measure_report.py |
The populations, proportion versus cohort scoring, the per-person decisions, and to_fhir() rendering a FHIR R4 MeasureReport |
Quality measures |
fhir/engine/elm_round_trip.py |
CQL compiled to ELM JSON and run back from the ELM, with every answer compared across both evaluators | dhis2w_fhir_engine API reference |
fhir/engine/e2e_measure_from_dhis2.py |
The whole chain: read a seeded Child Programme cohort, map tracked entities to Patient and weight data values to Observation, score a measure, check the counts against DHIS2's own records (needs make dhis2-run) |
Quality measures |
Full guides¶
Where the scripts above show one command or one library call apiece, examples/fhir/igs/ holds nine complete d2w fhir init project trees — a fhir.toml, a SUSHI skeleton, a Makefile, a Dockerfile — each selecting one feature story out of the seeded local instance. Every tree is committed as its inputs alone; nothing d2w fhir generate or SUSHI writes is. make verify-igs refreshes, validates, generates, and dockerized-SUSHI-compiles all nine on demand.
| Guide | What it demonstrates | Related docs |
|---|---|---|
fhir/igs/aggregate-minimal |
The smallest complete guide: the forms, one district, no category axis published | Set up an IG project |
fhir/igs/aggregate-disaggregated |
The same selection with both category axes on — the category pairs, the per-axis properties, and the ConceptMaps | Terminology and ConceptMaps |
fhir/igs/event-program |
A program without registration, and the two program rules that ride on its Questionnaire | What goes in |
fhir/igs/tracker-registration |
Registration, enrollment, two stage forms, and the person-only form the tracked entity type publishes | The capture contract |
fhir/igs/terminology-strict |
concept_code_source = "code" — concept codes taken from DHIS2 codes, and what that trades |
Generation |
fhir/igs/registry-district |
One district as Organization and Location pairs, boundaries as GeoJSON, plus the organisation-unit code list | What goes in |
fhir/igs/facility-mixed |
The flagship: one of every capture kind at once, six Questionnaires in one guide | The capture contract |
fhir/igs/patient-summary |
The International Patient Summary end to end: who a person is, which recorded values are doses, the published section map, and the document $summary answers with |
What goes in |
fhir/igs/refused-names |
The exhibit: a selection d2w fhir generate refuses, and the publisher failure it stands in for |
Troubleshooting |
CLI examples¶
| Example | What it demonstrates | Related docs |
|---|---|---|
metadata_round_trip.sh |
Cookbook — export → jq transform → diff → dry-run → import → live verify → revert | CLI tutorial / metadata plugin |
whoami.sh |
Simplest invocation — d2w system whoami + d2w system info |
system |
profile_list_verify.sh |
d2w profile list / verify / show |
profiles |
profile_oidc_config.sh |
Populate an OAuth2 profile by discovering DHIS2's OIDC endpoints | auth |
profile_oidc_login.sh |
Full OIDC login flow — profile add → login → verify → whoami | auth |
profile_session.sh |
Bind a profile to an existing browser login via --auth session + DHIS2_SESSION_COOKIE (plus optional DHIS2_SESSION_XSRF for CSRF-enforcing writes) |
auth |
metadata_list.sh |
d2w metadata list — field presets, filters, order, --all |
metadata plugin |
metadata_get.sh |
d2w metadata get <resource> <uid> — one object, narrowed by --fields |
metadata plugin |
metadata_patch.sh |
RFC 6902 JSON Patch — inline --set / --remove and file-based |
metadata plugin |
metadata_export.sh |
d2w metadata export — a narrow slice and the full catalog |
metadata plugin |
metadata_import.sh |
d2w metadata import — dry run, the commit, and --strategy DELETE |
metadata plugin |
metadata_export_filter.sh |
Per-resource filters + dangling-reference warning on export | metadata plugin |
metadata_diff.sh |
d2w metadata diff — bundle-vs-bundle or bundle-vs-live |
metadata plugin |
metadata_search.sh |
Cross-resource UID / code / name search via d2w metadata search <query> |
metadata plugin |
attribute_values.sh |
Cross-resource AttributeValue workflows via d2w metadata attributes |
metadata plugin |
options.sh |
OptionSet + Option workflows — d2w metadata option-sets get / find / sync |
metadata plugin |
legend_sets.sh |
d2w metadata legend-sets — create / get / clone / delete + the seeded LsDoseBand1 coverage dashboards |
legend sets API |
organisation_units.sh |
d2w metadata organisation-units + groups + group-sets + levels — tree walk, per-level rename, group + dimension round-trip |
organisation units API |
data_elements.sh |
d2w metadata data-elements + groups + group-sets — DE create, rename, legend-set attach, group + dimension round-trip |
data elements API |
indicators.sh |
d2w metadata indicators + groups + group-sets — numerator/denominator expression pre-flight, create, group + dimension round-trip |
indicators API |
program_indicators.sh |
d2w metadata program-indicators + groups — program-scoped expression validation + group membership (pair, no group-set) |
program indicators API |
category_options.sh |
d2w metadata category-options + groups + group-sets — validity-window creation + group + dimension round-trip |
category options API |
data_sets.sh |
d2w metadata data-sets + sections — DataSet create, DE attach (with per-set CC override), ordered Section round-trip |
data sets API |
validation_rules.sh |
d2w metadata validation-rules + their groups — the authoring flip side of maintenance validation run |
validation rules + predictors API |
predictors.sh |
d2w metadata predictors + their groups — the authoring flip side of maintenance predictors run |
validation rules + predictors API |
tracker_schema.sh |
d2w metadata tracked-entity-attributes + tracked-entity-types — create a unique-generated TEA + a Person TET and wire them together |
tracker schema API |
tracker_programs.sh |
d2w metadata programs — tracker + event Program authoring with PTEA enrollment form + OU scoping |
tracker schema API |
tracker_program_stages.sh |
d2w metadata program-stages — stage authoring with ordered PSDE list + reorder + per-DE compulsory flag |
tracker schema API |
messaging.sh |
d2w messaging send / get / reply / set-priority / assign / delete — ticket-workflow round-trip |
messaging plugin |
program_rules.sh |
d2w metadata program-rules — vars, validate-expression, where-de-is-used |
metadata plugin |
sql_views.sh |
d2w metadata list sqlViews / get / execute / refresh / adhoc across the VIEW / QUERY / MATERIALIZED_VIEW shapes |
SQL views API |
aggregate_data_values.sh |
Aggregate data values — read / write a single value / bulk file push | aggregate |
tracker_reads.sh |
Tracked entities by type, enrollments, events, bulk import | tracker |
tracker_register_and_followup.sh |
Canonical tracker-program clinic intake — register, log an event, query outstanding enrollments | tracker |
tracker_event_program.sh |
Event-only WITHOUT_REGISTRATION program — standalone events with no enrollment | tracker |
analytics_query.sh |
Aggregated analytics queries + resource-table refresh | analytics |
analytics_outlier_tracked_entities.sh |
Outlier detection + tracked-entity analytics | analytics |
user_administration.sh |
d2w user — list / get / invite / reinvite / reset-password |
user plugin |
user_groups.sh |
d2w user group admin workflows |
user groups + roles |
user_roles.sh |
d2w user role admin workflows |
user groups + roles |
security.sh |
d2w security settings — read-only security posture (password policy, registration, lockout) |
CLI reference |
schema.sh |
d2w schema <type> — describe a generated type's fields (offline; reflects the active version tree) |
CLI reference |
route_register_and_run.sh |
d2w route lifecycle across every upstream auth type |
auth schemes |
customize_login.sh |
Brand the DHIS2 login page + top menu | customize plugin |
doctor.sh |
d2w doctor — probe every BUGS.md gotcha + metadata-health check |
doctor plugin |
maintenance.sh |
d2w maintenance — background tasks, cache, cleanup, integrity |
maintenance plugin |
files.sh |
d2w files — documents (URL + binary round-trip) + fileResource (MESSAGE_ATTACHMENT round-trip) |
files plugin |
visualizations.sh |
d2w metadata visualizations + d2w metadata dashboards — flag-driven viz create, clone, dashboard compose |
visualizations guide |
visualization_screenshot.sh |
d2w browser viz screenshot — PNG capture via the Data Visualizer app (needs [browser] extra) |
visualizations guide |
maps.sh |
d2w metadata maps — thematic choropleth authoring via REST |
maps API |
map_screenshot.sh |
d2w browser map screenshot — PNG capture via the Maps app (needs [browser] extra) |
maps API |
apps.sh |
d2w apps list / hub-list / hub-url / update --dry-run / update --all / reload over /api/apps + /api/appHub |
apps API |
dev_codegen.sh |
d2w dev codegen — regenerate the typed client from a live instance |
codegen |
profile_pat.sh |
d2w profile pat create — provision a PAT as admin |
auth |
dev_sample.sh |
d2w dev sample ... — inject fixtures + verify end-to-end |
manual testing |
dev_uid.sh |
d2w dev uid — mint 11-char DHIS2 UIDs client-side, offline |
CLI reference |
Client examples (Python library)¶
| Example | What it demonstrates | Related docs |
|---|---|---|
bulk_patch_from_csv.py |
Cookbook — apply a CSV of patches concurrently via asyncio.gather + JsonPatchOpAdapter |
metadata plugin |
profile_drift_check.py |
Cookbook — diff metadata between two profiles, exit non-zero on drift (CI template) | metadata plugin |
retry_policy.py |
Cookbook — RetryPolicy for transient 5xx / connection errors on batch workflows |
client library tutorial |
task_await.py |
Cookbook — client.tasks.await_completion blocks on analytics refresh / metadata import / etc. |
client library tutorial |
concurrent_bulk.py |
Cookbook — fan-out patterns (naive gather → bounded semaphore → tuned pool + retries) with live timings | client architecture |
integrity_issues_stream.py |
Cookbook — client.maintenance.iter_integrity_issues streams tagged issues, severity histogram, early-break scan |
maintenance plugin |
system_cache.py |
Cookbook — client.system cache: info() primed on connect, default_category_combo_uid(), per-key setting(), invalidate + refetch |
system API |
files_documents.py |
Cookbook — client.files documents + fileResource round-trip; external URL docs, binary two-step upload, MESSAGE_ATTACHMENT round-trip |
files plugin |
bulk_delete.py |
Cookbook — client.metadata.delete_bulk / delete_bulk_multi for one-call teardown via /api/metadata?importStrategy=DELETE |
metadata accessor |
metadata_patch_bulk.py |
Cookbook — client.metadata.patch_bulk fans out RFC 6902 patches across many UIDs with per-UID failure capture |
metadata accessor |
bulk_save.py |
Cookbook — client.resources.<res>.save_bulk(items) writes a typed list of one resource in one call |
metadata accessor |
metadata_dry_run.py |
Cookbook — client.metadata.dry_run(...) as the pre-commit gate, and the refusal read back off the Dhis2ApiError |
metadata accessor |
stream_data_values.py |
Cookbook — client.data_values.stream four ways: bytes, sync generator, Path (CSV), 1000-row file with timing |
data values streaming |
stream_analytics.py |
Cookbook — client.analytics.stream_to for JSON / CSV / rawData exports to disk with per-format timing |
analytics streaming |
data_values_export.py |
Cookbook — client.data_values.export as a typed DataValueSet, then client.stream of the same export to a Path, a BytesIO, and a chunk callable |
data values |
data_values_import_atomic.py |
Cookbook — the same partly invalid payload imported under atomic_mode="ALL" and "OBJECT", reading the summary from a 200 or a 409 alike |
data values |
complete_data_set_registrations_read.py |
Cookbook — client.complete_data_set_registrations.export lists which forms are reported complete below the root over a date range |
aggregate |
tracker_reads.py |
Cookbook — client.tracker.tracked_entities / enrollments / events page through one program's records with typed row parsing |
tracker reads |
analytics_event_query.py |
Cookbook — client.analytics.event_query / enrollment_query line lists for the seeded Child Programme |
analytics streaming |
analytics_tables_poll_once.py |
Cookbook — client.maintenance.run_analytics_tables followed with client.tasks.poll_once, one poll per tick with a carried cursor |
tasks module |
messaging_with_attachment.py |
Cookbook — client.messaging.send + MESSAGE_ATTACHMENT fileResource pairing; full inbox lifecycle (list, reply, mark-read, delete) |
messaging plugin |
validation_rules.py |
Cookbook — client.validation workflow: expression validation, run analysis, list persisted results |
validation API |
predictors.py |
Cookbook — client.predictors.run_all over a date range with import-count envelope |
validation API |
whoami.py |
Minimal — default profile → call /api/me |
client library tutorial |
library_only_auth.py |
Library-only auth for PyPI consumers without dhis2w-core |
auth |
profile_resolver.py |
Use a DHIS2 profile from Python via dhis2w-core.open_client |
profiles |
profile_crud.py |
Profile CRUD — in-memory Profile(...) + on-disk add/rename/set-default/remove through the profile-plugin service |
profiles |
oidc_login.py |
OAuth 2.1 authorization-code + PKCE flow; auto-dispatches to Playwright when DHIS2_USERNAME+DHIS2_PASSWORD are set |
auth |
oidc_playwright_login.py |
Fully automated end-to-end OIDC via drive_oauth2_login — CLI subprocess + Chromium IdP form + consent screen |
auth / browser |
list_data_elements.py |
Generated typed resource accessors | metadata CRUD |
enum_round_trip.py |
Generated StrEnums — type-safe access to DHIS2 CONSTANT values | typed schemas |
dashboard_item_users.py |
DashboardItem.user -> users (rename + reshape) |
schema diff v41 -> v42 -> v43 / versioning |
tracked_entity_attribute_favorites.py |
TrackedEntityAttribute.favorite -> favorites + 6 new search fields |
schema diff v41 -> v42 -> v43 |
program_set_labels.py |
v43-only — client.programs.set_labels overrides enrollmentsLabel / eventsLabel / programStagesLabel |
schema diff v41 -> v42 -> v43 |
program_set_change_log.py |
v43-only — toggle Program.enableChangeLog (server-side enrollment + event audit) |
schema diff v41 -> v42 -> v43 |
program_set_enrollment_category_combo.py |
v43-only — set the alt enrollmentCategoryCombo reference applied at enrollment time |
schema diff v41 -> v42 -> v43 |
event_visualization_fix_headers.py |
EventChart / EventReport / EventVisualization add fixColumnHeaders / fixRowHeaders / hideEmptyColumns |
schema diff v41 -> v42 -> v43 |
map_basemaps.py |
Map.basemaps v43-only addition + new Basemap model |
schema diff v41 -> v42 -> v43 |
section_user_removed.py |
Section.user removed in v43 (also Section.favorite) |
schema diff v41 -> v42 -> v43 |
removed_resources.py |
pushAnalysis / externalFileResource / dataInputPeriods removed in v43 |
schema diff v41 -> v42 -> v43 |
metadata_filter_order_paging.py |
Full query DSL — filters, rootJunction, order, server-side paging | metadata plugin |
metadata_patch.py |
RFC 6902 JSON Patch — generated accessor + service + adapter paths | metadata plugin |
metadata_export.py |
service.export_metadata — a typed MetadataBundle, walked and narrowed by per-resource filters |
metadata plugin |
metadata_diff.py |
service.diff_bundles + diff_bundle_against_instance |
metadata plugin |
metadata_bulk_import.py |
service.import_metadata — a typed bundle pushed, dry run then committed |
metadata plugin |
metadata_search.py |
Cross-resource UID / code / name search via client.metadata.search |
metadata plugin |
metadata_usage.py |
Reverse-reference lookup "what points at this UID?" via client.metadata.usage |
metadata plugin |
attribute_values.py |
AttributeValuesAccessor across every resource with an attributeValues field |
metadata plugin |
options_integration.py |
Every method on OptionSetsAccessor — list / get / find / sync |
metadata plugin |
legend_sets.py |
client.legend_sets.create_from_spec + VisualizationSpec(legend_set=...) — colour bands attached to a throw-away viz |
legend sets API |
organisation_units.py |
client.organisation_units + groups + group-sets + levels — descendants walk, rename-by-level, membership + dimension round-trip |
organisation units API |
data_elements.py |
client.data_elements + groups + group-sets — keyword-arg create, rename, per-item membership, dimension |
data elements API |
indicators.py |
client.indicators.validate_expression pre-flight + numerator/denominator create + group + dimension round-trip |
indicators API |
program_indicators.py |
client.program_indicators + groups — program-scoped expression validation + EVENT/ENROLLMENT create + group round-trip (pair, no group-set) |
program indicators API |
category_options.py |
client.category_options validity-window helper + groups + group-sets round-trip |
category options API |
data_sets.py |
client.data_sets + client.sections — DataSet create, DE attach, ordered Section round-trip |
data sets API |
validation_rules.py |
client.validation_rules + its groups — CRUD round-trip + group membership |
validation rules + predictors API |
predictors.py |
client.predictors + its groups — CRUD round-trip + group membership |
validation rules + predictors API |
tracker_schema.py |
client.tracked_entity_attributes + client.tracked_entity_types — TEA + TET round-trip with the TETA linkage helpers |
tracker schema API |
tracker_programs.py |
client.programs — tracker Program with PTEA + OU linkage, using the TET + TEA from step 1 |
tracker schema API |
program_rules.py |
Every method on ProgramRulesAccessor — get, vars, validate, where-DE-used |
metadata plugin |
sql_views.py |
SqlViewsAccessor list / get / execute / refresh / adhoc across the three view shapes |
SQL views API |
sql_view_runner.py |
Three ways to use SqlViewRunner — by UID, ad-hoc query, named bindings |
SQL views API |
indicator_crud.py |
Create a typed Indicator with a formula, use it, clean up | metadata CRUD |
data_set_crud.py |
Full CRUD lifecycle for a data set | metadata CRUD |
org_unit_crud.py |
Full CRUD lifecycle for an organisation unit | metadata CRUD |
geojson_org_units.py |
Pull org units as GeoJSON (validated via geojson-pydantic) | metadata CRUD |
push_data_value.py |
Write one aggregate data value against the seeded dataset | aggregate |
tracker_lifecycle.py |
Create a tracked entity with an enrollment + event | tracker |
tracker_clinic_intake.py |
Canonical tracker-program intake via client.tracker.register / add_event / outstanding |
tracker |
tracker_event_program.py |
WITHOUT_REGISTRATION event-only program — one-shot events via add_event without an enrollment |
tracker |
analytics_query.py |
Aggregated analytics + resource-table refresh | analytics |
analytics_events_enrollments.py |
Event + enrollment analytics queries | analytics |
analytics_outlier_tracked_entities.py |
Outlier detection + tracked-entity analytics | analytics |
task_polling.py |
Watch an analytics-refresh job to completion | maintenance plugin |
doctor.py |
Programmatic probe results for CI / monitoring | doctor plugin |
user_administration.py |
User administration via the Python client | user plugin |
user_groups.py |
User groups via the Python client | user groups + roles |
user_roles.py |
User roles via the Python client | user groups + roles |
sharing.py |
Typed sharing helpers — read + replace on any object | sharing |
customize_login.py |
Brand a DHIS2 instance programmatically | customize plugin |
error_handling.py |
Dhis2ApiError, WebMessage conflicts, AuthenticationError |
errors |
bootstrap_zero_to_data.py |
End-to-end — zero org unit to a data value in one script | client library tutorial |
viz_create_basic.py |
Simplest visualization spec → create → verify axes | visualizations guide |
viz_multiline_by_province.py |
Multi-line time-series with analytics-probe sanity check | visualizations guide |
viz_pivot_table.py |
PIVOT_TABLE — organisation units down the side, periods across the top | visualizations guide |
viz_single_value.py |
SINGLE_VALUE — the KPI tile, and why its grid collapses to one cell | visualizations guide |
viz_clone_and_modify.py |
Clone an existing chart, verify independence | visualizations guide |
dashboard_compose.py |
Assemble a dashboard from scratch with typed DashboardSlots |
visualizations guide |
map_create_choropleth.py |
Build a thematic choropleth from a typed MapSpec + MapLayerSpec |
maps API |
apps.py |
client.apps.list_apps / hub_list / get_hub_url — library surface for the apps plugin |
apps API |
MCP examples¶
| Example | What it demonstrates | Related docs |
|---|---|---|
whoami.py |
whoami + system_info MCP tools in-process |
MCP |
profile_tools.py |
Read-only profile MCP tools |
profiles |
metadata_list.py |
metadata_type_list + metadata_list — what an instance holds, paged and filtered |
metadata plugin |
metadata_get.py |
metadata_get — one object by UID, whole or narrowed by fields |
metadata plugin |
metadata_patch.py |
RFC 6902 JSON Patch via the metadata_patch tool |
metadata plugin |
metadata_diff.py |
metadata_diff tool against two bundles |
metadata plugin |
metadata_export.py |
metadata_export — a bundle written to disk, the summary answered back, dangling references named |
metadata plugin |
metadata_import.py |
metadata_import — the dry run an agent should ask for first, then the commit |
metadata plugin |
metadata_search.py |
metadata_search — cross-resource UID / code / name lookup for agents |
metadata plugin |
metadata_usage.py |
metadata_usage — "what references this UID?" reverse lookup for agents |
metadata plugin |
legend_sets.py |
metadata_legend_set_* — list / create / get / delete via MCP |
legend sets API |
organisation_units.py |
metadata_organisation_unit_* — tree walk + levels + group + group-set via MCP |
organisation units API |
data_elements.py |
metadata_data_element_* — DE + group + group-set round-trip via MCP |
data elements API |
indicators.py |
metadata_indicator_* — expression validation + create + group + group-set via MCP |
indicators API |
program_indicators.py |
metadata_program_indicator_* — program-scoped expression + group round-trip via MCP |
program indicators API |
category_options.py |
metadata_category_option_* — validity window + group + group-set via MCP |
category options API |
data_sets.py |
metadata_data_set_* + metadata_section_* — DataSet + Section round-trip via MCP |
data sets API |
validation_rules.py |
metadata_validation_rule_* (plus groups) — CRUD round-trip via MCP |
validation rules + predictors API |
predictors.py |
metadata_predictor_* (plus groups) — CRUD round-trip via MCP |
validation rules + predictors API |
tracker_schema.py |
metadata_tracked_entity_attribute_* + metadata_tracked_entity_type_* — TEA + TET round-trip via MCP |
tracker schema API |
tracker_programs.py |
metadata_program_* — tracker + event Program authoring via MCP |
tracker schema API |
aggregate_data_values.py |
GET / SET / DELETE data values via MCP | aggregate |
tracker_reads.py |
Discover TET types, list entities/events via MCP | tracker |
tracker_workflow.py |
Agent flow — tracker_register, tracker_add_event, tracker_outstanding for a tracker program |
tracker |
analytics_query.py |
Aggregated analytics + refresh via MCP | analytics |
analytics_events_enrollments.py |
Event + enrollment analytics via MCP | analytics |
analytics_outlier_tracked_entities.py |
Outlier detection + tracked-entity analytics via MCP | analytics |
maintenance.py |
Tasks, cache, cleanup, data-integrity via MCP | maintenance plugin |
route_register_and_run.py |
Route CRUD + run via MCP | auth schemes |
doctor.py |
Structured probe results for an agent via doctor_run |
doctor plugin |
user_administration.py |
User plugin MCP tools | user plugin |
user_groups.py |
User groups + their sharing block via MCP (user_group_list, user_group_sharing_get) |
user groups + roles |
user_roles.py |
User roles + their authorities via MCP (user_role_list, user_role_authority_list) |
user groups + roles |
customize_login.py |
Branding via MCP | customize plugin |
apps.py |
apps_list, apps_hub_list (plus apps_install_*, apps_uninstall, apps_update{,_all}, apps_hub_url_{get,set} available) |
apps API |
External plugin example¶
examples/plugin-external/ — a minimal runnable external plugin (own pyproject.toml, entry-point registration, CLI + MCP hooks). See external plugins for the contract.
Running the whole set¶
make verify-examples runs every example on this page against the active profile and prints a per-surface PASS / FAIL / TIMEOUT / SKIP table. Three things decide what a batch pass actually executes:
- Skipped by default. An example that needs Chromium, a human-clicked login, public-internet egress, or a dockerized SUSHI compile carries a stated reason in
SKIP_BY_DEFAULTininfra/scripts/verify_examples.py.--include-browseropts the UI-driven set back in. - Skipped when the environment is missing. An example that reads a real secret or endpoint out of the environment - a personal access token, a caller's own DHIS2 credentials - runs when every variable it names is set and skips naming the missing ones otherwise. An unprovisioned machine is a fact about the machine, not a defect in the example, and
make verify-examplessourcesinfra/home/credentials/.env.authfirst so a seeded checkout provides them all. - One shared FHIR fixture. Every
examples/fhir/client/example stands up what it needs throughclient/_fixture.py- a scaffolded project, a translation context built live off the instance, and ad2w fhir serve --livefacade on a port the operating system picks. Run one by hand and it builds its own; run the suite and the suite builds one before its loop, exportsD2W_FHIR_EXAMPLE_PROJECTandD2W_FHIR_EXAMPLE_FACADE, and stops the facade after the last example, so a batch pass boots one server instead of a dozen. Set either variable yourself and the suite leaves your fixture alone. The two examples about authentication still start a guarded facade apiece: the facade seam answers the open default posture only, and a shared open server cannot show what atokenordhis2posture changes.
make refresh-and-verify is the full cycle: rebuild the seeded stack, seed the tokens, refresh the analytics tables the analytics examples read, then run the suite.
How example coverage maps to features¶
- Every top-level CLI domain has at least one example. See CLI reference for the full command tree.
- Every plugin with a service layer has both a CLI and client example. Pairs are intentional — the CLI shows the user-facing path, the client shows what library callers do.
- Every MCP plugin group has representative examples. The 40-ish v42 MCP scripts can't cover all ~304 tools 1:1, but each plugin group (
metadata_*,analytics_*,data_*,apps_*, ...) gets at least one runnable example showing the expected input shape + how to unpack thestructured_contentreturn.
When adding a new plugin / command / MCP tool: update this file alongside the feature PR so the catalogue stays in sync.