Indicators
Three accessors on Dhis2Client for the Indicator triple, matching the canonical DHIS2 resource names:
| Accessor |
API path |
Purpose |
client.indicators |
/api/indicators |
Computed ratios / counts / percentages over DataElements. CRUD + rename + expression validation. |
client.indicator_groups |
/api/indicatorGroups |
Thematic groupings (coverage, quality, mortality, …). Per-item membership. |
client.indicator_group_sets |
/api/indicatorGroupSets |
Analytics dimensions collecting groups. |
Generic CRUD stays on the generated accessors (client.resources.indicators, …). The hand-written accessors add keyword-arg create shapes, partial-update rename, per-item membership shortcuts, and — unique to indicators — an expression_validate(context="indicator") pre-flight that catches bad DE references before the create round-trip.
No *Spec builder
Same design call as the DataElement + organisation-unit surfaces: keyword args on the accessor rather than a spec-over-model hop. The Indicator wire shape doesn't need the kind of transformation work that motivates a spec — see the Legend sets doc for the rule on when reaching for a *Spec is the right shape.
Worked example
async with Dhis2Client(...) as client:
# Pre-flight the expression so create doesn't fail on a typo.
desc = await client.indicators.validate_expression("#{s46m5MS0hxu}")
assert desc.status == "OK", desc.message
indicator = await client.indicators.create(
name="BCG coverage",
short_name="BCG cov",
indicator_type_uid="JkWynlWMjJR", # "Number (Factor 1)"
numerator="#{s46m5MS0hxu}", # BCG doses given
denominator="1",
numerator_description="BCG doses given",
legend_set_uids=["LsDoseBand1"],
)
group = await client.indicator_groups.create(
name="Immunization coverage",
short_name="Immun cov",
)
await client.indicator_groups.add_members(group.id, indicator_uids=[indicator.id])
dimension = await client.indicator_group_sets.create(
name="Programme area",
short_name="ProgArea",
)
await client.indicator_group_sets.add_groups(dimension.id, group_uids=[group.id])
Every create defaults annualized=False; flip to True for rate-per-year indicators that should be scaled by period length on aggregation.
CLI
dhis2 metadata indicators list
dhis2 metadata indicators validate-expression "#{s46m5MS0hxu}"
dhis2 metadata indicators create \
--name "BCG coverage" --short-name "BCG cov" \
--indicator-type JkWynlWMjJR \
--numerator "#{s46m5MS0hxu}" --denominator "1"
dhis2 metadata indicator-groups create --name "Immunization" --short-name "Immun"
dhis2 metadata indicator-groups add-members <GROUP_UID> --indicator <IND_UID>
dhis2 metadata indicator-group-sets create --name "ProgArea" --short-name "ProgArea"
dhis2 metadata indicator-group-sets add-groups <SET_UID> --group <GROUP_UID>
Every list has an ls alias; every destructive verb accepts --yes / -y.
MCP
Seventeen tools: metadata_indicator_{list,get,create,rename,validate_expression,set_legend_sets,delete}, metadata_indicator_group_{list,get,members,create,add_members,remove_members,delete}, metadata_indicator_group_set_{list,get,create,add_groups,remove_groups,delete}.
indicators
Indicator authoring — Dhis2Client.indicators.
DHIS2 Indicators are computed ratios / counts / percentages over
DataElement values, identified by a numerator + denominator
expression pair (each referencing DE UIDs via #{<uid>} + optional
Category-Option-Combo refs via .{<coc_uid>}). Every indicator also
carries an IndicatorType reference that pins the output scaling
factor (COUNT / PERCENT / PER_100_PEOPLE / etc).
Generic CRUD lives on the generated accessor (client.resources.indicators);
this module adds the authoring + validation primitives typical
workflows reach for:
create(...) — named kwargs for the required subset (name,
short_name, indicator_type_uid, numerator, denominator) plus the
optional expression descriptions + legend sets.
update(indicator) — PUT with an existing typed model.
rename(uid, ...) — partial-update shortcut for label fields.
validate_expression(expr) — pre-flight wrapper around
client.validation.describe_expression(..., context="indicator")
so callers can catch bad refs before attempting a create.
Classes
Indicator
Bases: BaseModel
Generated model for DHIS2 Indicator.
DHIS2 Indicator - persisted metadata (generated from /api/schemas at DHIS2 v42).
API endpoint: /api/indicators.
Field Field(description=...) entries flag DHIS2 semantics the bare
type can't capture: which side of a relationship owns the link
(writable) vs the inverse side (ignored by the API), uniqueness
constraints, and length bounds.
Source code in packages/dhis2w-client/src/dhis2w_client/generated/v42/schemas/indicator.py
| class Indicator(BaseModel):
"""Generated model for DHIS2 `Indicator`.
DHIS2 Indicator - persisted metadata (generated from /api/schemas at DHIS2 v42).
API endpoint: /api/indicators.
Field `Field(description=...)` entries flag DHIS2 semantics the bare
type can't capture: which side of a relationship owns the link
(writable) vs the inverse side (ignored by the API), uniqueness
constraints, and length bounds.
"""
model_config = ConfigDict(extra="allow", populate_by_name=True)
access: Any | None = Field(default=None, description="Reference to Access. Read-only (inverse side).")
aggregateExportAttributeOptionCombo: str | None = Field(default=None, description="Length/value max=255.")
aggregateExportCategoryOptionCombo: str | None = Field(default=None, description="Length/value max=255.")
aggregationType: AggregationType | None = None
annualized: bool | None = None
attributeValues: Any | None = Field(default=None, description="Reference to AttributeValues. Length/value max=255.")
code: str | None = Field(default=None, description="Unique. Length/value max=50.")
created: datetime | None = None
createdBy: Reference | None = Field(default=None, description="Reference to User.")
dataSets: list[Any] | None = Field(default=None, description="Collection of DataSet. Read-only (inverse side).")
decimals: int | None = Field(default=None, description="Length/value max=2147483647.")
denominator: str | None = Field(default=None, description="Length/value max=2147483647.")
denominatorDescription: str | None = Field(default=None, description="Length/value max=2147483647.")
description: str | None = Field(default=None, description="Length/value min=1, max=2147483647.")
dimensionItem: str | None = Field(default=None, description="Read-only.")
dimensionItemType: DimensionItemType | None = None
displayDenominatorDescription: str | None = Field(default=None, description="Read-only.")
displayDescription: str | None = Field(default=None, description="Read-only.")
displayFormName: str | None = Field(default=None, description="Read-only.")
displayName: str | None = Field(default=None, description="Read-only.")
displayNumeratorDescription: str | None = Field(default=None, description="Read-only.")
displayShortName: str | None = Field(default=None, description="Read-only.")
explodedDenominator: str | None = Field(default=None, description="Length/value max=2147483647.")
explodedNumerator: str | None = Field(default=None, description="Length/value max=2147483647.")
favorite: bool | None = Field(default=None, description="Read-only.")
favorites: list[Any] | None = Field(default=None, description="Collection of String. Read-only (inverse side).")
formName: str | None = Field(default=None, description="Length/value max=2147483647.")
href: str | None = None
id: str | None = Field(default=None, description="Unique. Length/value min=11, max=11.")
indicatorGroups: list[Any] | None = Field(
default=None, description="Collection of IndicatorGroup. Read-only (inverse side)."
)
indicatorType: Reference | None = Field(default=None, description="Reference to IndicatorType.")
lastUpdated: datetime | None = None
lastUpdatedBy: Reference | None = Field(default=None, description="Reference to User.")
legendSet: Reference | None = Field(default=None, description="Reference to LegendSet. Read-only (inverse side).")
legendSets: list[Any] | None = Field(default=None, description="Collection of LegendSet.")
name: str | None = Field(default=None, description="Length/value min=1, max=230.")
numerator: str | None = Field(default=None, description="Length/value max=2147483647.")
numeratorDescription: str | None = Field(default=None, description="Length/value max=2147483647.")
queryMods: Any | None = Field(default=None, description="Reference to QueryModifiers. Read-only (inverse side).")
sharing: Any | None = Field(default=None, description="Reference to Sharing. Length/value max=255.")
shortName: str | None = Field(default=None, description="Length/value min=1, max=50.")
style: Any | None = Field(default=None, description="Reference to ObjectStyle. Length/value max=255.")
translations: list[Any] | None = Field(default=None, description="Collection of Translation. Length/value max=255.")
url: str | None = Field(default=None, description="Length/value max=255.")
user: Reference | None = Field(default=None, description="Reference to User. Read-only (inverse side).")
|
IndicatorsAccessor
Dhis2Client.indicators — CRUD + rename + expression validation.
Source code in packages/dhis2w-client/src/dhis2w_client/indicators.py
| class IndicatorsAccessor:
"""`Dhis2Client.indicators` — CRUD + rename + expression validation."""
def __init__(self, client: Dhis2Client) -> None:
"""Bind to the sharing client."""
self._client = client
async def list_all(
self,
*,
page: int = 1,
page_size: int = 50,
) -> list[Indicator]:
"""Page through Indicators with type + expressions resolved inline."""
raw = await self._client.get_raw(
"/api/indicators",
params={
"fields": _INDICATOR_FIELDS,
"page": str(page),
"pageSize": str(page_size),
},
)
rows = raw.get("indicators") or []
return [Indicator.model_validate(row) for row in rows if isinstance(row, dict)]
async def get(self, uid: str) -> Indicator:
"""Fetch one Indicator by UID."""
return await self._client.get(f"/api/indicators/{uid}", model=Indicator, params={"fields": _INDICATOR_FIELDS})
async def create(
self,
*,
name: str,
short_name: str,
indicator_type_uid: str,
numerator: str,
denominator: str,
numerator_description: str | None = None,
denominator_description: str | None = None,
legend_set_uids: list[str] | None = None,
annualized: bool = False,
decimals: int | None = None,
code: str | None = None,
description: str | None = None,
uid: str | None = None,
) -> Indicator:
"""Create an Indicator.
`indicator_type_uid` pins the output scale (percent / count /
etc.) via an `IndicatorType` reference. `numerator` and
`denominator` are DHIS2 expressions — `#{<de_uid>}` for a DE,
`#{<de_uid>.<coc_uid>}` for a DE × CategoryOptionCombo cell,
arithmetic operators allowed. Call `validate_expression(expr)`
first to catch typos before a failed create.
"""
payload: dict[str, Any] = {
"name": name,
"shortName": short_name,
"indicatorType": {"id": indicator_type_uid},
"numerator": numerator,
"denominator": denominator,
"annualized": annualized,
}
if numerator_description:
payload["numeratorDescription"] = numerator_description
if denominator_description:
payload["denominatorDescription"] = denominator_description
if decimals is not None:
payload["decimals"] = decimals
if legend_set_uids:
payload["legendSets"] = [{"id": uid_} for uid_ in legend_set_uids]
if uid:
payload["id"] = uid
if code:
payload["code"] = code
if description:
payload["description"] = description
envelope = await self._client.post("/api/indicators", payload, model=WebMessageResponse)
created_uid = envelope.created_uid or uid
if not created_uid:
raise RuntimeError("indicator create did not return a uid")
return await self.get(created_uid)
async def update(self, indicator: Indicator) -> Indicator:
"""PUT an edited Indicator back. `indicator.id` must be set."""
if not indicator.id:
raise ValueError("update requires indicator.id to be set")
body = indicator.model_dump(by_alias=True, exclude_none=True, mode="json")
await self._client.put_raw(f"/api/indicators/{indicator.id}", body=body)
return await self.get(indicator.id)
async def rename(
self,
uid: str,
*,
name: str | None = None,
short_name: str | None = None,
description: str | None = None,
) -> Indicator:
"""Partial-update the label fields — read, mutate, PUT."""
if name is None and short_name is None and description is None:
raise ValueError("rename requires at least one of name / short_name / description")
current = await self.get(uid)
if name is not None:
current.name = name
if short_name is not None:
current.shortName = short_name
if description is not None:
current.description = description
return await self.update(current)
async def set_legend_sets(self, uid: str, *, legend_set_uids: list[str]) -> Indicator:
"""Replace the legend-set refs on one Indicator."""
current = await self.get(uid)
current.legendSets = [Reference(id=ref).model_dump(by_alias=True, exclude_none=True) for ref in legend_set_uids]
return await self.update(current)
async def validate_expression(self, expression: str) -> ExpressionDescription:
"""Parse-check a numerator / denominator expression via DHIS2's validator.
Wraps `client.validation.describe_expression(expression,
context="indicator")`. Returns the typed
`ExpressionDescription` — `.status == "OK"` on success,
`.message` names the failing reference when DHIS2 rejects.
Cheap pre-flight for create flows that want early error
feedback instead of a full-object POST rejection.
"""
return await self._client.validation.describe_expression(expression, context="indicator")
async def delete(self, uid: str) -> None:
"""Delete an Indicator — DHIS2 rejects deletes on indicators used in viz / dashboards."""
if not uid:
raise ValueError("delete requires a non-empty uid")
await self._client.resources.indicators.delete(uid)
|
Functions
__init__(client)
Bind to the sharing client.
Source code in packages/dhis2w-client/src/dhis2w_client/indicators.py
| def __init__(self, client: Dhis2Client) -> None:
"""Bind to the sharing client."""
self._client = client
|
list_all(*, page=1, page_size=50)
async
Page through Indicators with type + expressions resolved inline.
Source code in packages/dhis2w-client/src/dhis2w_client/indicators.py
| async def list_all(
self,
*,
page: int = 1,
page_size: int = 50,
) -> list[Indicator]:
"""Page through Indicators with type + expressions resolved inline."""
raw = await self._client.get_raw(
"/api/indicators",
params={
"fields": _INDICATOR_FIELDS,
"page": str(page),
"pageSize": str(page_size),
},
)
rows = raw.get("indicators") or []
return [Indicator.model_validate(row) for row in rows if isinstance(row, dict)]
|
get(uid)
async
Fetch one Indicator by UID.
Source code in packages/dhis2w-client/src/dhis2w_client/indicators.py
| async def get(self, uid: str) -> Indicator:
"""Fetch one Indicator by UID."""
return await self._client.get(f"/api/indicators/{uid}", model=Indicator, params={"fields": _INDICATOR_FIELDS})
|
create(*, name, short_name, indicator_type_uid, numerator, denominator, numerator_description=None, denominator_description=None, legend_set_uids=None, annualized=False, decimals=None, code=None, description=None, uid=None)
async
Create an Indicator.
indicator_type_uid pins the output scale (percent / count /
etc.) via an IndicatorType reference. numerator and
denominator are DHIS2 expressions — #{<de_uid>} for a DE,
#{<de_uid>.<coc_uid>} for a DE × CategoryOptionCombo cell,
arithmetic operators allowed. Call validate_expression(expr)
first to catch typos before a failed create.
Source code in packages/dhis2w-client/src/dhis2w_client/indicators.py
| async def create(
self,
*,
name: str,
short_name: str,
indicator_type_uid: str,
numerator: str,
denominator: str,
numerator_description: str | None = None,
denominator_description: str | None = None,
legend_set_uids: list[str] | None = None,
annualized: bool = False,
decimals: int | None = None,
code: str | None = None,
description: str | None = None,
uid: str | None = None,
) -> Indicator:
"""Create an Indicator.
`indicator_type_uid` pins the output scale (percent / count /
etc.) via an `IndicatorType` reference. `numerator` and
`denominator` are DHIS2 expressions — `#{<de_uid>}` for a DE,
`#{<de_uid>.<coc_uid>}` for a DE × CategoryOptionCombo cell,
arithmetic operators allowed. Call `validate_expression(expr)`
first to catch typos before a failed create.
"""
payload: dict[str, Any] = {
"name": name,
"shortName": short_name,
"indicatorType": {"id": indicator_type_uid},
"numerator": numerator,
"denominator": denominator,
"annualized": annualized,
}
if numerator_description:
payload["numeratorDescription"] = numerator_description
if denominator_description:
payload["denominatorDescription"] = denominator_description
if decimals is not None:
payload["decimals"] = decimals
if legend_set_uids:
payload["legendSets"] = [{"id": uid_} for uid_ in legend_set_uids]
if uid:
payload["id"] = uid
if code:
payload["code"] = code
if description:
payload["description"] = description
envelope = await self._client.post("/api/indicators", payload, model=WebMessageResponse)
created_uid = envelope.created_uid or uid
if not created_uid:
raise RuntimeError("indicator create did not return a uid")
return await self.get(created_uid)
|
update(indicator)
async
PUT an edited Indicator back. indicator.id must be set.
Source code in packages/dhis2w-client/src/dhis2w_client/indicators.py
| async def update(self, indicator: Indicator) -> Indicator:
"""PUT an edited Indicator back. `indicator.id` must be set."""
if not indicator.id:
raise ValueError("update requires indicator.id to be set")
body = indicator.model_dump(by_alias=True, exclude_none=True, mode="json")
await self._client.put_raw(f"/api/indicators/{indicator.id}", body=body)
return await self.get(indicator.id)
|
rename(uid, *, name=None, short_name=None, description=None)
async
Partial-update the label fields — read, mutate, PUT.
Source code in packages/dhis2w-client/src/dhis2w_client/indicators.py
| async def rename(
self,
uid: str,
*,
name: str | None = None,
short_name: str | None = None,
description: str | None = None,
) -> Indicator:
"""Partial-update the label fields — read, mutate, PUT."""
if name is None and short_name is None and description is None:
raise ValueError("rename requires at least one of name / short_name / description")
current = await self.get(uid)
if name is not None:
current.name = name
if short_name is not None:
current.shortName = short_name
if description is not None:
current.description = description
return await self.update(current)
|
set_legend_sets(uid, *, legend_set_uids)
async
Replace the legend-set refs on one Indicator.
Source code in packages/dhis2w-client/src/dhis2w_client/indicators.py
| async def set_legend_sets(self, uid: str, *, legend_set_uids: list[str]) -> Indicator:
"""Replace the legend-set refs on one Indicator."""
current = await self.get(uid)
current.legendSets = [Reference(id=ref).model_dump(by_alias=True, exclude_none=True) for ref in legend_set_uids]
return await self.update(current)
|
validate_expression(expression)
async
Parse-check a numerator / denominator expression via DHIS2's validator.
Wraps client.validation.describe_expression(expression,
context="indicator"). Returns the typed
ExpressionDescription — .status == "OK" on success,
.message names the failing reference when DHIS2 rejects.
Cheap pre-flight for create flows that want early error
feedback instead of a full-object POST rejection.
Source code in packages/dhis2w-client/src/dhis2w_client/indicators.py
| async def validate_expression(self, expression: str) -> ExpressionDescription:
"""Parse-check a numerator / denominator expression via DHIS2's validator.
Wraps `client.validation.describe_expression(expression,
context="indicator")`. Returns the typed
`ExpressionDescription` — `.status == "OK"` on success,
`.message` names the failing reference when DHIS2 rejects.
Cheap pre-flight for create flows that want early error
feedback instead of a full-object POST rejection.
"""
return await self._client.validation.describe_expression(expression, context="indicator")
|
delete(uid)
async
Delete an Indicator — DHIS2 rejects deletes on indicators used in viz / dashboards.
Source code in packages/dhis2w-client/src/dhis2w_client/indicators.py
| async def delete(self, uid: str) -> None:
"""Delete an Indicator — DHIS2 rejects deletes on indicators used in viz / dashboards."""
if not uid:
raise ValueError("delete requires a non-empty uid")
await self._client.resources.indicators.delete(uid)
|
indicator_groups
IndicatorGroup authoring — Dhis2Client.indicator_groups.
IndicatorGroups collect indicators by thematic axis (coverage,
quality, mortality, …) so dashboards and analytics can address a
coherent subset in one ref. Mirrors DataElementGroupsAccessor.
Classes
IndicatorGroup
Bases: BaseModel
Generated model for DHIS2 IndicatorGroup.
DHIS2 Indicator Group - persisted metadata (generated from /api/schemas at DHIS2 v42).
API endpoint: /api/indicatorGroups.
Field Field(description=...) entries flag DHIS2 semantics the bare
type can't capture: which side of a relationship owns the link
(writable) vs the inverse side (ignored by the API), uniqueness
constraints, and length bounds.
Source code in packages/dhis2w-client/src/dhis2w_client/generated/v42/schemas/indicator_group.py
| class IndicatorGroup(BaseModel):
"""Generated model for DHIS2 `IndicatorGroup`.
DHIS2 Indicator Group - persisted metadata (generated from /api/schemas at DHIS2 v42).
API endpoint: /api/indicatorGroups.
Field `Field(description=...)` entries flag DHIS2 semantics the bare
type can't capture: which side of a relationship owns the link
(writable) vs the inverse side (ignored by the API), uniqueness
constraints, and length bounds.
"""
model_config = ConfigDict(extra="allow", populate_by_name=True)
access: Any | None = Field(default=None, description="Reference to Access. Read-only (inverse side).")
attributeValues: Any | None = Field(default=None, description="Reference to AttributeValues. Length/value max=255.")
code: str | None = Field(default=None, description="Unique. Length/value max=50.")
created: datetime | None = None
createdBy: Reference | None = Field(default=None, description="Reference to User.")
description: str | None = Field(default=None, description="Length/value min=2, max=2147483647.")
displayName: str | None = Field(default=None, description="Read-only.")
favorite: bool | None = Field(default=None, description="Read-only.")
favorites: list[Any] | None = Field(default=None, description="Collection of String. Read-only (inverse side).")
groupSets: list[Any] | None = Field(
default=None, description="Collection of IndicatorGroupSet. Read-only (inverse side)."
)
href: str | None = None
id: str | None = Field(default=None, description="Unique. Length/value min=11, max=11.")
indicatorGroupSet: Reference | None = Field(
default=None, description="Reference to IndicatorGroupSet. Read-only (inverse side)."
)
indicators: list[Any] | None = Field(default=None, description="Collection of Indicator.")
lastUpdated: datetime | None = None
lastUpdatedBy: Reference | None = Field(default=None, description="Reference to User.")
name: str | None = Field(default=None, description="Length/value min=1, max=230.")
sharing: Any | None = Field(default=None, description="Reference to Sharing. Length/value max=255.")
translations: list[Any] | None = Field(default=None, description="Collection of Translation. Length/value max=255.")
user: Reference | None = Field(default=None, description="Reference to User. Read-only (inverse side).")
|
IndicatorGroupsAccessor
Dhis2Client.indicator_groups — CRUD + membership helpers.
Source code in packages/dhis2w-client/src/dhis2w_client/indicator_groups.py
| class IndicatorGroupsAccessor:
"""`Dhis2Client.indicator_groups` — CRUD + membership helpers."""
def __init__(self, client: Dhis2Client) -> None:
"""Bind to the sharing client."""
self._client = client
async def list_all(self) -> list[IndicatorGroup]:
"""Return every IndicatorGroup."""
return cast(
list[IndicatorGroup],
await self._client.resources.indicator_groups.list(
fields=_INDICATOR_GROUP_FIELDS,
paging=False,
),
)
async def get(self, uid: str) -> IndicatorGroup:
"""Fetch one group by UID with `indicators` + `groupSets` populated."""
return await self._client.get(
f"/api/indicatorGroups/{uid}", model=IndicatorGroup, params={"fields": _INDICATOR_GROUP_FIELDS}
)
async def list_members(
self,
uid: str,
*,
page: int = 1,
page_size: int = 50,
) -> list[Indicator]:
"""Page through Indicators belonging to one group."""
return cast(
list[Indicator],
await self._client.resources.indicators.list(
fields=_MEMBER_FIELDS,
filters=[f"indicatorGroups.id:eq:{uid}"],
order=["name:asc"],
page=page,
page_size=page_size,
),
)
async def create(
self,
*,
name: str,
short_name: str,
uid: str | None = None,
code: str | None = None,
description: str | None = None,
) -> IndicatorGroup:
"""Create an empty group; add members afterwards via `add_members`."""
payload: dict[str, Any] = {"name": name, "shortName": short_name}
if uid:
payload["id"] = uid
if code:
payload["code"] = code
if description:
payload["description"] = description
envelope = await self._client.post("/api/indicatorGroups", payload, model=WebMessageResponse)
created_uid = envelope.created_uid or uid
if not created_uid:
raise RuntimeError("indicator-group create did not return a uid")
return await self.get(created_uid)
async def update(self, group: IndicatorGroup) -> IndicatorGroup:
"""PUT an edited group back. `group.id` must be set."""
if not group.id:
raise ValueError("update requires group.id to be set")
body = group.model_dump(by_alias=True, exclude_none=True, mode="json")
await self._client.put_raw(f"/api/indicatorGroups/{group.id}", body=body)
return await self.get(group.id)
async def add_members(self, uid: str, *, indicator_uids: list[str]) -> IndicatorGroup:
"""Add Indicators to the group via the generated per-item POST shortcut."""
for ind_uid in indicator_uids:
await self._client.resources.indicator_groups.add_collection_item(uid, "indicators", ind_uid)
return await self.get(uid)
async def remove_members(self, uid: str, *, indicator_uids: list[str]) -> IndicatorGroup:
"""Drop Indicators from the group via the generated per-item DELETE shortcut."""
for ind_uid in indicator_uids:
await self._client.resources.indicator_groups.remove_collection_item(uid, "indicators", ind_uid)
return await self.get(uid)
async def delete(self, uid: str) -> None:
"""Delete the grouping row — member indicators stay."""
if not uid:
raise ValueError("delete requires a non-empty uid")
await self._client.resources.indicator_groups.delete(uid)
|
Functions
__init__(client)
Bind to the sharing client.
Source code in packages/dhis2w-client/src/dhis2w_client/indicator_groups.py
| def __init__(self, client: Dhis2Client) -> None:
"""Bind to the sharing client."""
self._client = client
|
list_all()
async
Return every IndicatorGroup.
Source code in packages/dhis2w-client/src/dhis2w_client/indicator_groups.py
| async def list_all(self) -> list[IndicatorGroup]:
"""Return every IndicatorGroup."""
return cast(
list[IndicatorGroup],
await self._client.resources.indicator_groups.list(
fields=_INDICATOR_GROUP_FIELDS,
paging=False,
),
)
|
get(uid)
async
Fetch one group by UID with indicators + groupSets populated.
Source code in packages/dhis2w-client/src/dhis2w_client/indicator_groups.py
| async def get(self, uid: str) -> IndicatorGroup:
"""Fetch one group by UID with `indicators` + `groupSets` populated."""
return await self._client.get(
f"/api/indicatorGroups/{uid}", model=IndicatorGroup, params={"fields": _INDICATOR_GROUP_FIELDS}
)
|
list_members(uid, *, page=1, page_size=50)
async
Page through Indicators belonging to one group.
Source code in packages/dhis2w-client/src/dhis2w_client/indicator_groups.py
| async def list_members(
self,
uid: str,
*,
page: int = 1,
page_size: int = 50,
) -> list[Indicator]:
"""Page through Indicators belonging to one group."""
return cast(
list[Indicator],
await self._client.resources.indicators.list(
fields=_MEMBER_FIELDS,
filters=[f"indicatorGroups.id:eq:{uid}"],
order=["name:asc"],
page=page,
page_size=page_size,
),
)
|
create(*, name, short_name, uid=None, code=None, description=None)
async
Create an empty group; add members afterwards via add_members.
Source code in packages/dhis2w-client/src/dhis2w_client/indicator_groups.py
| async def create(
self,
*,
name: str,
short_name: str,
uid: str | None = None,
code: str | None = None,
description: str | None = None,
) -> IndicatorGroup:
"""Create an empty group; add members afterwards via `add_members`."""
payload: dict[str, Any] = {"name": name, "shortName": short_name}
if uid:
payload["id"] = uid
if code:
payload["code"] = code
if description:
payload["description"] = description
envelope = await self._client.post("/api/indicatorGroups", payload, model=WebMessageResponse)
created_uid = envelope.created_uid or uid
if not created_uid:
raise RuntimeError("indicator-group create did not return a uid")
return await self.get(created_uid)
|
update(group)
async
PUT an edited group back. group.id must be set.
Source code in packages/dhis2w-client/src/dhis2w_client/indicator_groups.py
| async def update(self, group: IndicatorGroup) -> IndicatorGroup:
"""PUT an edited group back. `group.id` must be set."""
if not group.id:
raise ValueError("update requires group.id to be set")
body = group.model_dump(by_alias=True, exclude_none=True, mode="json")
await self._client.put_raw(f"/api/indicatorGroups/{group.id}", body=body)
return await self.get(group.id)
|
add_members(uid, *, indicator_uids)
async
Add Indicators to the group via the generated per-item POST shortcut.
Source code in packages/dhis2w-client/src/dhis2w_client/indicator_groups.py
| async def add_members(self, uid: str, *, indicator_uids: list[str]) -> IndicatorGroup:
"""Add Indicators to the group via the generated per-item POST shortcut."""
for ind_uid in indicator_uids:
await self._client.resources.indicator_groups.add_collection_item(uid, "indicators", ind_uid)
return await self.get(uid)
|
remove_members(uid, *, indicator_uids)
async
Drop Indicators from the group via the generated per-item DELETE shortcut.
Source code in packages/dhis2w-client/src/dhis2w_client/indicator_groups.py
| async def remove_members(self, uid: str, *, indicator_uids: list[str]) -> IndicatorGroup:
"""Drop Indicators from the group via the generated per-item DELETE shortcut."""
for ind_uid in indicator_uids:
await self._client.resources.indicator_groups.remove_collection_item(uid, "indicators", ind_uid)
return await self.get(uid)
|
delete(uid)
async
Delete the grouping row — member indicators stay.
Source code in packages/dhis2w-client/src/dhis2w_client/indicator_groups.py
| async def delete(self, uid: str) -> None:
"""Delete the grouping row — member indicators stay."""
if not uid:
raise ValueError("delete requires a non-empty uid")
await self._client.resources.indicator_groups.delete(uid)
|
indicator_group_sets
IndicatorGroupSet authoring — Dhis2Client.indicator_group_sets.
An IndicatorGroupSet is the analytics dimension that collects
IndicatorGroups — e.g. "Programme area" carries groups for each
disease programme; "Reporting quality" carries timeliness /
completeness groups. Mirrors DataElementGroupSetsAccessor.
Classes
IndicatorGroupSet
Bases: BaseModel
Generated model for DHIS2 IndicatorGroupSet.
DHIS2 Indicator Group Set - persisted metadata (generated from /api/schemas at DHIS2 v42).
API endpoint: /api/indicatorGroupSets.
Field Field(description=...) entries flag DHIS2 semantics the bare
type can't capture: which side of a relationship owns the link
(writable) vs the inverse side (ignored by the API), uniqueness
constraints, and length bounds.
Source code in packages/dhis2w-client/src/dhis2w_client/generated/v42/schemas/indicator_group_set.py
| class IndicatorGroupSet(BaseModel):
"""Generated model for DHIS2 `IndicatorGroupSet`.
DHIS2 Indicator Group Set - persisted metadata (generated from /api/schemas at DHIS2 v42).
API endpoint: /api/indicatorGroupSets.
Field `Field(description=...)` entries flag DHIS2 semantics the bare
type can't capture: which side of a relationship owns the link
(writable) vs the inverse side (ignored by the API), uniqueness
constraints, and length bounds.
"""
model_config = ConfigDict(extra="allow", populate_by_name=True)
access: Any | None = Field(default=None, description="Reference to Access. Read-only (inverse side).")
attributeValues: Any | None = Field(
default=None, description="Reference to AttributeValues. Read-only (inverse side)."
)
code: str | None = Field(default=None, description="Unique. Length/value max=50.")
compulsory: bool | None = None
created: datetime | None = None
createdBy: Reference | None = Field(default=None, description="Reference to User.")
description: str | None = Field(default=None, description="Length/value min=2, max=2147483647.")
displayName: str | None = Field(default=None, description="Read-only.")
favorite: bool | None = Field(default=None, description="Read-only.")
favorites: list[Any] | None = Field(default=None, description="Collection of String. Read-only (inverse side).")
href: str | None = None
id: str | None = Field(default=None, description="Unique. Length/value min=11, max=11.")
indicatorGroups: list[Any] | None = Field(default=None, description="Collection of IndicatorGroup.")
lastUpdated: datetime | None = None
lastUpdatedBy: Reference | None = Field(default=None, description="Reference to User.")
name: str | None = Field(default=None, description="Length/value min=1, max=230.")
sharing: Any | None = Field(default=None, description="Reference to Sharing. Length/value max=255.")
shortName: str | None = Field(default=None, description="Unique. Length/value min=1, max=50.")
translations: list[Any] | None = Field(default=None, description="Collection of Translation. Length/value max=255.")
user: Reference | None = Field(default=None, description="Reference to User. Read-only (inverse side).")
|
IndicatorGroupSetsAccessor
Dhis2Client.indicator_group_sets — CRUD + group-membership helpers.
Source code in packages/dhis2w-client/src/dhis2w_client/indicator_group_sets.py
| class IndicatorGroupSetsAccessor:
"""`Dhis2Client.indicator_group_sets` — CRUD + group-membership helpers."""
def __init__(self, client: Dhis2Client) -> None:
"""Bind to the sharing client."""
self._client = client
async def list_all(self) -> list[IndicatorGroupSet]:
"""Return every IndicatorGroupSet with its groups inline."""
return cast(
list[IndicatorGroupSet],
await self._client.resources.indicator_group_sets.list(
fields=_INDICATOR_GROUP_SET_FIELDS,
paging=False,
),
)
async def get(self, uid: str) -> IndicatorGroupSet:
"""Fetch one group set by UID with its `indicatorGroups` populated."""
return await self._client.get(
f"/api/indicatorGroupSets/{uid}", model=IndicatorGroupSet, params={"fields": _INDICATOR_GROUP_SET_FIELDS}
)
async def list_groups(self, uid: str) -> list[IndicatorGroup]:
"""Return the groups in the set in definition order."""
group_set = await self.get(uid)
groups = group_set.indicatorGroups or []
return [IndicatorGroup.model_validate(g) for g in groups if isinstance(g, dict)]
async def create(
self,
*,
name: str,
short_name: str,
uid: str | None = None,
code: str | None = None,
description: str | None = None,
compulsory: bool = False,
) -> IndicatorGroupSet:
"""Create an empty group set; wire groups in via `add_groups`."""
payload: dict[str, Any] = {
"name": name,
"shortName": short_name,
"compulsory": compulsory,
}
if uid:
payload["id"] = uid
if code:
payload["code"] = code
if description:
payload["description"] = description
envelope = await self._client.post("/api/indicatorGroupSets", payload, model=WebMessageResponse)
created_uid = envelope.created_uid or uid
if not created_uid:
raise RuntimeError("indicator-group-set create did not return a uid")
return await self.get(created_uid)
async def update(self, group_set: IndicatorGroupSet) -> IndicatorGroupSet:
"""PUT an edited group set back. `group_set.id` must be set."""
if not group_set.id:
raise ValueError("update requires group_set.id to be set")
body = group_set.model_dump(by_alias=True, exclude_none=True, mode="json")
await self._client.put_raw(f"/api/indicatorGroupSets/{group_set.id}", body=body)
return await self.get(group_set.id)
async def add_groups(self, uid: str, *, group_uids: list[str]) -> IndicatorGroupSet:
"""Add `group_uids` to the set via the per-item POST shortcut."""
for group_uid in group_uids:
await self._client.resources.indicator_group_sets.add_collection_item(uid, "indicatorGroups", group_uid)
return await self.get(uid)
async def remove_groups(self, uid: str, *, group_uids: list[str]) -> IndicatorGroupSet:
"""Drop `group_uids` from the set via the per-item DELETE shortcut."""
for group_uid in group_uids:
await self._client.resources.indicator_group_sets.remove_collection_item(uid, "indicatorGroups", group_uid)
return await self.get(uid)
async def delete(self, uid: str) -> None:
"""Delete a group set — groups stay."""
if not uid:
raise ValueError("delete requires a non-empty uid")
await self._client.resources.indicator_group_sets.delete(uid)
|
Functions
__init__(client)
Bind to the sharing client.
Source code in packages/dhis2w-client/src/dhis2w_client/indicator_group_sets.py
| def __init__(self, client: Dhis2Client) -> None:
"""Bind to the sharing client."""
self._client = client
|
list_all()
async
Return every IndicatorGroupSet with its groups inline.
Source code in packages/dhis2w-client/src/dhis2w_client/indicator_group_sets.py
| async def list_all(self) -> list[IndicatorGroupSet]:
"""Return every IndicatorGroupSet with its groups inline."""
return cast(
list[IndicatorGroupSet],
await self._client.resources.indicator_group_sets.list(
fields=_INDICATOR_GROUP_SET_FIELDS,
paging=False,
),
)
|
get(uid)
async
Fetch one group set by UID with its indicatorGroups populated.
Source code in packages/dhis2w-client/src/dhis2w_client/indicator_group_sets.py
| async def get(self, uid: str) -> IndicatorGroupSet:
"""Fetch one group set by UID with its `indicatorGroups` populated."""
return await self._client.get(
f"/api/indicatorGroupSets/{uid}", model=IndicatorGroupSet, params={"fields": _INDICATOR_GROUP_SET_FIELDS}
)
|
list_groups(uid)
async
Return the groups in the set in definition order.
Source code in packages/dhis2w-client/src/dhis2w_client/indicator_group_sets.py
| async def list_groups(self, uid: str) -> list[IndicatorGroup]:
"""Return the groups in the set in definition order."""
group_set = await self.get(uid)
groups = group_set.indicatorGroups or []
return [IndicatorGroup.model_validate(g) for g in groups if isinstance(g, dict)]
|
create(*, name, short_name, uid=None, code=None, description=None, compulsory=False)
async
Create an empty group set; wire groups in via add_groups.
Source code in packages/dhis2w-client/src/dhis2w_client/indicator_group_sets.py
| async def create(
self,
*,
name: str,
short_name: str,
uid: str | None = None,
code: str | None = None,
description: str | None = None,
compulsory: bool = False,
) -> IndicatorGroupSet:
"""Create an empty group set; wire groups in via `add_groups`."""
payload: dict[str, Any] = {
"name": name,
"shortName": short_name,
"compulsory": compulsory,
}
if uid:
payload["id"] = uid
if code:
payload["code"] = code
if description:
payload["description"] = description
envelope = await self._client.post("/api/indicatorGroupSets", payload, model=WebMessageResponse)
created_uid = envelope.created_uid or uid
if not created_uid:
raise RuntimeError("indicator-group-set create did not return a uid")
return await self.get(created_uid)
|
update(group_set)
async
PUT an edited group set back. group_set.id must be set.
Source code in packages/dhis2w-client/src/dhis2w_client/indicator_group_sets.py
| async def update(self, group_set: IndicatorGroupSet) -> IndicatorGroupSet:
"""PUT an edited group set back. `group_set.id` must be set."""
if not group_set.id:
raise ValueError("update requires group_set.id to be set")
body = group_set.model_dump(by_alias=True, exclude_none=True, mode="json")
await self._client.put_raw(f"/api/indicatorGroupSets/{group_set.id}", body=body)
return await self.get(group_set.id)
|
add_groups(uid, *, group_uids)
async
Add group_uids to the set via the per-item POST shortcut.
Source code in packages/dhis2w-client/src/dhis2w_client/indicator_group_sets.py
| async def add_groups(self, uid: str, *, group_uids: list[str]) -> IndicatorGroupSet:
"""Add `group_uids` to the set via the per-item POST shortcut."""
for group_uid in group_uids:
await self._client.resources.indicator_group_sets.add_collection_item(uid, "indicatorGroups", group_uid)
return await self.get(uid)
|
remove_groups(uid, *, group_uids)
async
Drop group_uids from the set via the per-item DELETE shortcut.
Source code in packages/dhis2w-client/src/dhis2w_client/indicator_group_sets.py
| async def remove_groups(self, uid: str, *, group_uids: list[str]) -> IndicatorGroupSet:
"""Drop `group_uids` from the set via the per-item DELETE shortcut."""
for group_uid in group_uids:
await self._client.resources.indicator_group_sets.remove_collection_item(uid, "indicatorGroups", group_uid)
return await self.get(uid)
|
delete(uid)
async
Delete a group set — groups stay.
Source code in packages/dhis2w-client/src/dhis2w_client/indicator_group_sets.py
| async def delete(self, uid: str) -> None:
"""Delete a group set — groups stay."""
if not uid:
raise ValueError("delete requires a non-empty uid")
await self._client.resources.indicator_group_sets.delete(uid)
|