Skip to content

winterop-com/dhis2w-utils

Repository files navigation

dhis2w

CI PyPI Python DHIS2 License

A Python toolkit for DHIS2 — pure client library, CLI, MCP server, Playwright browser automation, and a shared plugin runtime, all in one uv workspace. Targets DHIS2 v41, v42, and v43.

The repo lives at winterop-com/dhis2w-utils; PyPI ships the six publishable members under the dhis2w-* prefix. Not affiliated with DHIS2.

Learning path · step 1 of 8 — You are here. Quick install + profile + first CLI / Python call below. Next: the contributor walkthrough for the local docker stack, or jump to a surface-specific tutorial — CLI, Python, MCP.

Why this toolkit?

DHIS2 already has a lightweight, official Python client that returns plain JSON dictionaries — ideal when you want a thin wrapper and a few lines in a notebook. dhis2w is built for a different need: a typed, multi-surface toolkit you can depend on across instances and versions.

  • Typed, not stringly-typed. Every response is a Pydantic model generated from DHIS2's own OpenAPI spec, so your editor autocompletes fields and the type checker catches a misspelled key before you run. No guessing dictionary keys against the docs.
  • One core, four surfaces. The same typed client powers a Python library, a dhis2 CLI, an MCP server, and Playwright browser automation — all sharing one service.py per domain, so behaviour never drifts between them.
  • Built for AI agents. The MCP server exposes ~304 typed tools, one per CLI command, so any MCP host (Claude, Cursor) can drive a DHIS2 instance directly.
  • Version-aware by design. Detects v41 / v42 / v43 on connect and binds the matching hand-written tree, so one codebase works across instances instead of branching on the wire shape yourself.
  • Real auth. Basic, PAT, and OAuth2/OIDC with PKCE, behind a pluggable AuthProvider protocol, with a profile system for juggling multiple instances.
  • Production posture. Strict ruff + mypy + pyright, ~1,150 tests, an mkdocs-material site, and runnable examples for every supported version.

Reach for the official client when you want the smallest possible dependency and raw JSON. Reach for dhis2w when you want types, a CLI, agent tooling, and version coverage in one place. Note that dhis2w is third-party and pre-1.0.

Workspace members

Package PyPI Purpose
dhis2w-client uv add dhis2w-client Pure async httpx + pydantic DHIS2 client with pluggable auth (Basic, PAT, OAuth2/OIDC). Typed models from both /api/schemas and /api/openapi.json codegen.
dhis2w-core uv add dhis2w-core Shared runtime: profile discovery, plugin registry, auth factory, token store, first-party plugins.
dhis2w-cli uv tool install dhis2w-cli Typer console script dhis2.
dhis2w-mcp uv tool install dhis2w-mcp FastMCP server dhis2w-mcp.
dhis2w-mcp-bridge uv tool install dhis2w-mcp-bridge FastMCP server dhis2w-mcp-bridge — exposes the whole dhis2 CLI as a single dhis2_cli tool for small local models.
dhis2w-browser uv add dhis2w-browser Playwright helpers for DHIS2 UI automation — PAT minting, Playwright-driven OIDC login + consent, dashboard / viz / map screenshot capture. Mounted under dhis2 browser when the [browser] extra is installed on dhis2w-cli.
dhis2w-codegen workspace-only Generator that emits pydantic models + StrEnums + CRUD accessors into dhis2w_client.generated.v{N}/. Two source-of-truth paths: /api/schemas for metadata resources, /api/openapi.json for instance-side shapes (tracker writes, envelopes, auth schemes).

All six publishable packages release together (lockstep versioning); see docs/releasing.md.

Install

Use the CLI

The CLI command is named dhis2 but the PyPI distribution is dhis2w-cli — that's why every install command spells out the package name explicitly.

# Install once, run forever — drops `dhis2` on $PATH
uv tool install dhis2w-cli

# With Playwright UI automation (browser screenshots, OIDC login, PAT minting)
uv tool install 'dhis2w-cli[browser]'
playwright install chromium    # one-time, after the install above

# Update to the latest release
uv tool upgrade dhis2w-cli

# Force a re-install (handy after PyPI publish issues / cache problems)
uv tool install --reinstall dhis2w-cli

# Check what's installed
uv tool list

# Remove
uv tool uninstall dhis2w-cli

After uv tool install dhis2w-cli, run the CLI directly:

dhis2 --help
dhis2 --version  # also: -V — shows package version + active plugin tree
dhis2 system info --url https://play.im.dhis2.org/dev-2-43 --username admin --password district

dhis2 --version surfaces which plugin tree (v41 / v42 / v43) the CLI booted with and where that came from in the resolution chain (profile.versionDHIS2_VERSION env → default v42). Helps debug "which DHIS2 major is this CLI talking to" without reading the profile by hand.

One-shot runs without installing — uvx

uvx is uv's "run-and-forget" runner — it fetches the package into a cache and runs the binary, with no permanent install:

# uvx <command>           # works when the binary name == the package name
# uvx --from <pkg> <cmd>  # required when they differ — that's our case

uvx --from dhis2w-cli dhis2 --help
uvx --from dhis2w-cli dhis2 system info --url https://play.im.dhis2.org/dev-2-43 --username admin --password district

# With the browser extra
uvx --from 'dhis2w-cli[browser]' dhis2 browser pat --url ...

# Force a cache refresh — pulls the latest published version
uvx --refresh --from dhis2w-cli dhis2 --help

uv tool install keeps the install in its own dedicated venv (separate from any project venv), so the dhis2 binary on your $PATH can't be perturbed by a uv sync somewhere else.

Use the client library in your own project

# Inside a uv-managed project
uv add dhis2w-client
from dhis2w_client import BasicAuth, Dhis2Client

async with Dhis2Client(
    base_url="https://play.im.dhis2.org/dev-2-43",
    auth=BasicAuth(username="admin", password="district"),
) as client:
    me = await client.system.me()
    print(me.username)

dhis2w-client is standalone — no dependency on dhis2w-core or the profile system. PyPI users who want the typed async client + generated metadata models stop here.

Use the MCP server

dhis2w-mcp exposes ~304 typed tools (one per CLI command) over the MCP stdio transport when connected to a DHIS2 v42 instance; v43 adds a handful more for the v43-only schema fields. Connect any MCP host — Claude Desktop, Claude Code, Cursor, or anything that speaks stdio MCP.

The PyPI distribution name is the binary name here (dhis2w-mcp), so the --from dance isn't needed:

# Install once — drops `dhis2w-mcp` on $PATH
uv tool install dhis2w-mcp

# Update later
uv tool upgrade dhis2w-mcp

# Or run on demand without installing
uvx dhis2w-mcp

# Force a fresh fetch (after a new PyPI release)
uvx --refresh dhis2w-mcp

Claude Desktop — edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):

{
  "mcpServers": {
    "dhis2": {
      "command": "uvx",
      "args": ["dhis2w-mcp"],
      "env": {
        "DHIS2_URL": "https://play.im.dhis2.org/dev-2-43",
        "DHIS2_USERNAME": "admin",
        "DHIS2_PASSWORD": "district"
      }
    }
  }
}

Restart Claude Desktop. PAT auth works the same way — replace the username/password pair with "DHIS2_PAT": "d2p_...".

Claude Code — register from any shell:

claude mcp add dhis2 -s user \
  -e DHIS2_URL=https://play.im.dhis2.org/dev-2-43 \
  -e DHIS2_PAT=d2p_... \
  -- uvx dhis2w-mcp

-s user makes the server available across every project. Tools land in-session as mcp__dhis2__system_whoami, mcp__dhis2__metadata_data_element_list, etc.

Cursor — edit ~/.cursor/mcp.json with the same JSON shape as Claude Desktop and reload.

The full per-client setup, profile-based auth (.dhis2/profiles.toml for OAuth2 / OIDC), tool-naming convention, and troubleshooting are in packages/dhis2w-mcp/README.md.

Use the MCP bridge (small local models)

For a small model running on-box (LM Studio / Ollama / llama.cpp) against data that can't leave the machine, dhis2w-mcp-bridge exposes the whole CLI as a single tool, dhis2_cli, that the model drives by progressive discovery — ~one tool schema instead of ~304. (Why one tool, not many: Bridge design. Use the full dhis2w-mcp server above for capable cloud models.)

uv tool install dhis2w-mcp-bridge          # or run on demand: uvx dhis2w-mcp-bridge

LM Studio (native MCP client) — ~/.lmstudio/mcp.json:

{
  "mcpServers": {
    "dhis2": {
      "command": "dhis2w-mcp-bridge",
      "env": { "DHIS2_PROFILE": "local_basic", "DHIS2_MCP_READONLY": "1" }
    }
  }
}

The model then drives the CLI like a terminal, pulling help on demand:

dhis2_cli(["--help"])                                        # discover command groups
dhis2_cli(["metadata", "list", "dataElements", "--count"])   # {"resource":"dataElements","total":1037}
dhis2_cli(["schema", "dataElement"])                         # the type's fields (+ enum values)

--json is injected automatically; DHIS2_MCP_READONLY=1 refuses writes (fail-closed). Full usage + read-only details: the bridge guide.

Use the profile layer (env / TOML config)

The dhis2w-cli and dhis2w-mcp packages share a profile system that walks DHIS2_PROFILE env → ./.dhis2/profiles.toml~/.config/dhis2/profiles.toml:

# One-shot bootstrap: prompts for URL + auth, saves a profile
dhis2 profile bootstrap mywork

# List what's known
dhis2 profile list

# Switch the default
dhis2 profile default mywork
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:
    me = await client.system.me()
    print(me.username)

PyPI consumers who want the library without the profile layer can construct Dhis2Client(url, auth=BasicAuth(...)) directly — see examples/v42/client/library_only_auth.py.

CLI surface

Eighteen top-level domains; every plugin shares a service.py between the CLI and MCP sides so one typed call answers both surfaces.

Command What it covers
dhis2 profile Manage DHIS2 profiles (Basic / PAT / OAuth2) + the default precedence chain
dhis2 system /api/system/info, /api/me, minted UIDs
dhis2 metadata List / get / export / import any metadata resource, with DHIS2's full filter + fields selector
dhis2 data Aggregate data values + tracker reads + pushes
dhis2 analytics Aggregated, event, enrollment, outlier-detection, and tracked-entity analytics + table rebuild
dhis2 user List / get / me / invite / reinvite / reset-password
dhis2 user-group / dhis2 user-role Membership + authority administration
dhis2 route Integration routes (/api/routes) — register, run, inspect
dhis2 maintenance Background tasks, cache clear, data-integrity, soft-delete cleanup, validation-rule runs, predictor runs, analytics-table refresh
dhis2 files /api/documents + /api/fileResources — upload / download / list binary attachments
dhis2 messaging /api/messageConversations — send, reply, list, mark read/unread
dhis2 apps /api/apps + /api/appHub — install / uninstall / update installed apps, browse the App Hub catalog, point DHIS2 at a custom App Hub
dhis2 doctor One-command preflight — ~100 metadata-health + integrity checks against a live instance
dhis2 browser Playwright-driven UI automation (PAT minting, dashboard / viz / map screenshot capture, automated OIDC login) — only registers when the [browser] extra is installed
dhis2 dev Codegen, UID gen, PAT / OAuth2 seed helpers, branding (dev customize), sample data

Full per-command reference: dhis2 --help (or uvx --from dhis2w-cli dhis2 --help — the package is dhis2w-cli but the binary is dhis2, so uvx --from is required).

Working on the workspace itself

git clone git@github.com:winterop-com/dhis2w-utils.git
cd dhis2w-utils

make install      # sync workspace deps (uv sync --all-packages --all-extras)
make lint         # ruff + mypy + pyright
make test         # pytest across all members
make docs-serve   # local mkdocs-material

# Bring up a fully-seeded DHIS2 v43 on :8080 (Flyway-bootstraps; v42 still has a seeded e2e dump)
make dhis2-run

# Refresh codegen against the public play instances (no docker needed)
make dhis2-codegen-play

Connecting to a DHIS2 instance

See docs/guides/connecting-to-dhis2.md for the full end-to-end walkthrough covering Basic, PAT, and OAuth2/OIDC — including the dhis.conf keys the OAuth2 path needs on the DHIS2 server, manual OAuth2 client registration without the seed script, the openId user field, and a troubleshooting matrix of every failure mode.

Documentation + examples

  • Architecture + plugin walkthroughs: docs/architecture/
  • API reference (mkdocstrings-rendered): docs/api/
  • Releasing: docs/releasing.md
  • Roadmap: docs/roadmap.md
  • Upstream DHIS2 quirks we've tripped over: BUGS.md
  • Runnable examples: three trees (examples/v41/, examples/v42/, examples/v43/), each with cli/, client/, and mcp/ subfolders. examples/v43/client/ carries seven divergence-focused examples that exist only on v43 (removed_resources.py, section_user_removed.py, category_combo_coc_regen.py, event_visualization_fix_headers.py, etc.) — see docs/architecture/schema-diff-v41-v42-v43.md for the underlying schema drift. examples/v41/client/ carries v41-only quirks (oauth2_cid_field.py, grid_rows_wire_shape.py, apps_display_name.py).

Hard requirements, conventions, and the plugin / auth / workspace model are documented in CLAUDE.md and the docs/ site.

Packages

 
 
 

Contributors

Languages