Data elements
Three accessors on Dhis2Client for the DataElement triple, matching the canonical DHIS2 resource names:
| Accessor |
API path |
Purpose |
client.data_elements |
/api/dataElements |
Atoms of aggregate + tracker data capture. CRUD + rename + legend-set edits. |
client.data_element_groups |
/api/dataElementGroups |
Thematic groupings of DEs (vaccines, ANC indicators, HIV indicators). Per-item membership add/remove. |
client.data_element_group_sets |
/api/dataElementGroupSets |
Analytics dimensions collecting groups ("Vaccine stock", "HIV programme axis"). |
Generic CRUD is still available on the generated accessors (client.resources.data_elements + friends). The hand-written accessors layer the keyword-arg creation shapes, partial-update renames, and per-item membership shortcuts that production flows reach for every day.
No *Spec builder
Same design decision as the organisation-unit surface: keyword args on the accessor rather than a spec-over-model hop. client.data_elements.create(name=..., value_type=..., ...) dumps a plain dict at the HTTP boundary. The DataElement wire shape doesn't need transformation work (no chart-type-aware placement, no enum fan-out, no inline children with synthesised UIDs), so kwargs are the right call here — see the Legend sets doc for the rule on when reaching for a *Spec is the right shape.
Worked example
from dhis2w_client.generated.v42.enums import DataElementDomain, ValueType
async with Dhis2Client(...) as client:
de = await client.data_elements.create(
name="BCG doses given (<1y)",
short_name="BCG <1y",
value_type=ValueType.NUMBER,
domain_type=DataElementDomain.AGGREGATE,
legend_set_uids=["LsDoseBand1"],
)
group = await client.data_element_groups.create(
name="Immunization indicators",
short_name="Immun Ind",
)
await client.data_element_groups.add_members(group.id, data_element_uids=[de.id])
dimension = await client.data_element_group_sets.create(
name="Programme area",
short_name="Prog Area",
)
await client.data_element_group_sets.add_groups(dimension.id, group_uids=[group.id])
create defaults the categoryCombo to the instance default (client.system.default_category_combo_uid()); override via category_combo_uid= when you have a disaggregation.
CLI
dhis2 metadata data-elements list --domain-type AGGREGATE
dhis2 metadata data-elements create --name "BCG doses" --short-name "BCG" --value-type NUMBER --legend-set LsDoseBand1
dhis2 metadata data-element-groups create --name "Vaccines" --short-name "Vacc"
dhis2 metadata data-element-groups add-members <GROUP_UID> --data-element <DE_UID>
dhis2 metadata data-element-group-sets create --name "Programme area" --short-name "ProgArea"
dhis2 metadata data-element-group-sets add-groups <SET_UID> --group <GROUP_UID>
Every list has an ls alias; every destructive verb accepts --yes / -y.
MCP
Eighteen tools: metadata_data_element_{list,get,create,rename,set_legend_sets,delete}, metadata_data_element_group_{list,get,members,create,add_members,remove_members,delete}, metadata_data_element_group_set_{list,get,create,add_groups,remove_groups,delete}.
data_elements
DataElement authoring — Dhis2Client.data_elements.
DHIS2 DataElements are the atoms of aggregate + tracker data
capture; every cell of a DataValueSet and every eventDataValue on
tracker events points at one. Generic CRUD lives on the generated
accessor (client.resources.data_elements); this module adds the
authoring primitives integration + admin flows need:
create(...) — named kwargs covering the minimal required subset
(name, short_name, value_type, domain_type, aggregation_type)
plus the optional references (category_combo, option_set,
legend_set) most real DEs carry, so callers don't hand-craft the
reference payload shape.
update(de) — PUT with an existing typed DataElement model.
rename(uid, ...) — partial-update shortcut for the common case
of "fix the label / short name / description" without round-tripping
every field.
delete(uid) — DHIS2 rejects deletes on DEs with saved values.
No *Spec builder — continues the spec-audit data point from the
organisation-unit accessors. Callers hand the accessor keyword args;
the accessor dumps a plain dict at the HTTP boundary.
Classes
DataElement
Bases: BaseModel
Generated model for DHIS2 DataElement.
DHIS2 Data Element - persisted metadata (generated from /api/schemas at DHIS2 v42).
API endpoint: /api/dataElements.
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/data_element.py
| class DataElement(BaseModel):
"""Generated model for DHIS2 `DataElement`.
DHIS2 Data Element - persisted metadata (generated from /api/schemas at DHIS2 v42).
API endpoint: /api/dataElements.
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).")
aggregationLevels: list[Any] | None = Field(default=None, description="Collection of Integer.")
aggregationType: AggregationType | None = None
attributeValues: Any | None = Field(default=None, description="Reference to AttributeValues. Length/value max=255.")
categoryCombo: Reference | None = Field(default=None, description="Reference to CategoryCombo.")
code: str | None = Field(default=None, description="Unique. Length/value max=50.")
commentOptionSet: Reference | None = Field(default=None, description="Reference to OptionSet.")
created: datetime | None = None
createdBy: Reference | None = Field(default=None, description="Reference to User.")
dataElementGroups: list[Any] | None = Field(
default=None, description="Collection of DataElementGroup. Read-only (inverse side)."
)
dataSetElements: list[DataSetElement] | None = Field(
default=None, description="Collection of DataSetElement. Read-only (inverse side)."
)
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
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.")
displayShortName: str | None = Field(default=None, description="Read-only.")
domainType: DataElementDomain | None = None
favorite: bool | None = Field(default=None, description="Read-only.")
favorites: list[Any] | None = Field(default=None, description="Collection of String. Read-only (inverse side).")
fieldMask: str | None = Field(default=None, description="Length/value max=255.")
formName: str | None = Field(default=None, description="Length/value min=2, max=230.")
href: str | None = None
id: str | None = Field(default=None, description="Unique. Length/value min=11, max=11.")
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="Unique. Length/value min=1, max=230.")
optionSet: Reference | None = Field(default=None, description="Reference to OptionSet.")
optionSetValue: bool | None = Field(default=None, description="Read-only.")
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="Unique. 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).")
valueType: ValueType | None = None
valueTypeOptions: Any | None = Field(
default=None, description="Reference to ValueTypeOptions. Length/value max=255."
)
zeroIsSignificant: bool | None = None
|
DataElementsAccessor
Dhis2Client.data_elements — CRUD + renaming helpers over /api/dataElements.
Source code in packages/dhis2w-client/src/dhis2w_client/data_elements.py
| class DataElementsAccessor:
"""`Dhis2Client.data_elements` — CRUD + renaming helpers over `/api/dataElements`."""
def __init__(self, client: Dhis2Client) -> None:
"""Bind to the sharing client."""
self._client = client
async def list_all(
self,
*,
domain_type: DataElementDomain | str | None = None,
page: int = 1,
page_size: int = 50,
) -> list[DataElement]:
"""Page through DataElements, optionally filtered to one domain.
`domain_type=DataElementDomain.AGGREGATE` narrows to aggregate
DEs; `TRACKER` for tracker-only. Server-side paged — loop `page`
until the returned list is shorter than `page_size` for the full
catalog.
"""
filters: list[str] | None = None
if domain_type is not None:
value = domain_type.value if isinstance(domain_type, DataElementDomain) else domain_type
filters = [f"domainType:eq:{value}"]
return cast(
list[DataElement],
await self._client.resources.data_elements.list(
fields=_DE_FIELDS,
filters=filters,
page=page,
page_size=page_size,
),
)
async def get(self, uid: str) -> DataElement:
"""Fetch one DataElement by UID with its references resolved inline."""
return await self._client.get(f"/api/dataElements/{uid}", model=DataElement, params={"fields": _DE_FIELDS})
async def create(
self,
*,
name: str,
short_name: str,
value_type: ValueType | str,
domain_type: DataElementDomain | str = DataElementDomain.AGGREGATE,
aggregation_type: AggregationType | str = AggregationType.SUM,
category_combo_uid: str | None = None,
option_set_uid: str | None = None,
legend_set_uids: list[str] | None = None,
code: str | None = None,
form_name: str | None = None,
description: str | None = None,
uid: str | None = None,
zero_is_significant: bool = False,
) -> DataElement:
"""Create an aggregate or tracker DataElement.
DHIS2 rejects DEs without a `categoryCombo` — omit
`category_combo_uid` to fall back to the default combo
(`client.system.default_category_combo_uid()`). `legend_set_uids`
wires colour-legend sets for rendering in the Data Visualizer.
`aggregation_type` defaults to `SUM`; pass `AVERAGE_SUM_ORG_UNIT`
/ etc. via the `AggregationType` StrEnum for other modes.
"""
default_combo = category_combo_uid or await self._client.system.default_category_combo_uid()
payload: dict[str, Any] = {
"name": name,
"shortName": short_name,
"valueType": value_type.value if isinstance(value_type, ValueType) else value_type,
"domainType": domain_type.value if isinstance(domain_type, DataElementDomain) else domain_type,
"aggregationType": (
aggregation_type.value if isinstance(aggregation_type, AggregationType) else aggregation_type
),
"zeroIsSignificant": zero_is_significant,
"categoryCombo": {"id": default_combo},
}
if option_set_uid:
payload["optionSet"] = {"id": option_set_uid}
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 form_name:
payload["formName"] = form_name
if description:
payload["description"] = description
envelope = await self._client.post("/api/dataElements", payload, model=WebMessageResponse)
created_uid = envelope.created_uid or uid
if not created_uid:
raise RuntimeError("data-element create did not return a uid")
return await self.get(created_uid)
async def update(self, data_element: DataElement) -> DataElement:
"""PUT an edited DataElement back. `data_element.id` must be set."""
if not data_element.id:
raise ValueError("update requires data_element.id to be set")
body = data_element.model_dump(by_alias=True, exclude_none=True, mode="json")
await self._client.put_raw(f"/api/dataElements/{data_element.id}", body=body)
return await self.get(data_element.id)
async def rename(
self,
uid: str,
*,
name: str | None = None,
short_name: str | None = None,
form_name: str | None = None,
description: str | None = None,
) -> DataElement:
"""Partial-update shortcut — read, mutate the label fields, PUT."""
if name is None and short_name is None and form_name is None and description is None:
raise ValueError("rename requires at least one of name / short_name / form_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 form_name is not None:
current.formName = form_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]) -> DataElement:
"""Replace the legend-set refs on a DE — used to roll out threshold colouring."""
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 delete(self, uid: str) -> None:
"""Delete a DataElement — DHIS2 rejects deletes on DEs with saved values."""
if not uid:
raise ValueError("delete requires a non-empty uid")
await self._client.resources.data_elements.delete(uid)
|
Functions
__init__(client)
Bind to the sharing client.
Source code in packages/dhis2w-client/src/dhis2w_client/data_elements.py
| def __init__(self, client: Dhis2Client) -> None:
"""Bind to the sharing client."""
self._client = client
|
list_all(*, domain_type=None, page=1, page_size=50)
async
Page through DataElements, optionally filtered to one domain.
domain_type=DataElementDomain.AGGREGATE narrows to aggregate
DEs; TRACKER for tracker-only. Server-side paged — loop page
until the returned list is shorter than page_size for the full
catalog.
Source code in packages/dhis2w-client/src/dhis2w_client/data_elements.py
| async def list_all(
self,
*,
domain_type: DataElementDomain | str | None = None,
page: int = 1,
page_size: int = 50,
) -> list[DataElement]:
"""Page through DataElements, optionally filtered to one domain.
`domain_type=DataElementDomain.AGGREGATE` narrows to aggregate
DEs; `TRACKER` for tracker-only. Server-side paged — loop `page`
until the returned list is shorter than `page_size` for the full
catalog.
"""
filters: list[str] | None = None
if domain_type is not None:
value = domain_type.value if isinstance(domain_type, DataElementDomain) else domain_type
filters = [f"domainType:eq:{value}"]
return cast(
list[DataElement],
await self._client.resources.data_elements.list(
fields=_DE_FIELDS,
filters=filters,
page=page,
page_size=page_size,
),
)
|
get(uid)
async
Fetch one DataElement by UID with its references resolved inline.
Source code in packages/dhis2w-client/src/dhis2w_client/data_elements.py
| async def get(self, uid: str) -> DataElement:
"""Fetch one DataElement by UID with its references resolved inline."""
return await self._client.get(f"/api/dataElements/{uid}", model=DataElement, params={"fields": _DE_FIELDS})
|
create(*, name, short_name, value_type, domain_type=DataElementDomain.AGGREGATE, aggregation_type=AggregationType.SUM, category_combo_uid=None, option_set_uid=None, legend_set_uids=None, code=None, form_name=None, description=None, uid=None, zero_is_significant=False)
async
Create an aggregate or tracker DataElement.
DHIS2 rejects DEs without a categoryCombo — omit
category_combo_uid to fall back to the default combo
(client.system.default_category_combo_uid()). legend_set_uids
wires colour-legend sets for rendering in the Data Visualizer.
aggregation_type defaults to SUM; pass AVERAGE_SUM_ORG_UNIT
/ etc. via the AggregationType StrEnum for other modes.
Source code in packages/dhis2w-client/src/dhis2w_client/data_elements.py
| async def create(
self,
*,
name: str,
short_name: str,
value_type: ValueType | str,
domain_type: DataElementDomain | str = DataElementDomain.AGGREGATE,
aggregation_type: AggregationType | str = AggregationType.SUM,
category_combo_uid: str | None = None,
option_set_uid: str | None = None,
legend_set_uids: list[str] | None = None,
code: str | None = None,
form_name: str | None = None,
description: str | None = None,
uid: str | None = None,
zero_is_significant: bool = False,
) -> DataElement:
"""Create an aggregate or tracker DataElement.
DHIS2 rejects DEs without a `categoryCombo` — omit
`category_combo_uid` to fall back to the default combo
(`client.system.default_category_combo_uid()`). `legend_set_uids`
wires colour-legend sets for rendering in the Data Visualizer.
`aggregation_type` defaults to `SUM`; pass `AVERAGE_SUM_ORG_UNIT`
/ etc. via the `AggregationType` StrEnum for other modes.
"""
default_combo = category_combo_uid or await self._client.system.default_category_combo_uid()
payload: dict[str, Any] = {
"name": name,
"shortName": short_name,
"valueType": value_type.value if isinstance(value_type, ValueType) else value_type,
"domainType": domain_type.value if isinstance(domain_type, DataElementDomain) else domain_type,
"aggregationType": (
aggregation_type.value if isinstance(aggregation_type, AggregationType) else aggregation_type
),
"zeroIsSignificant": zero_is_significant,
"categoryCombo": {"id": default_combo},
}
if option_set_uid:
payload["optionSet"] = {"id": option_set_uid}
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 form_name:
payload["formName"] = form_name
if description:
payload["description"] = description
envelope = await self._client.post("/api/dataElements", payload, model=WebMessageResponse)
created_uid = envelope.created_uid or uid
if not created_uid:
raise RuntimeError("data-element create did not return a uid")
return await self.get(created_uid)
|
update(data_element)
async
PUT an edited DataElement back. data_element.id must be set.
Source code in packages/dhis2w-client/src/dhis2w_client/data_elements.py
| async def update(self, data_element: DataElement) -> DataElement:
"""PUT an edited DataElement back. `data_element.id` must be set."""
if not data_element.id:
raise ValueError("update requires data_element.id to be set")
body = data_element.model_dump(by_alias=True, exclude_none=True, mode="json")
await self._client.put_raw(f"/api/dataElements/{data_element.id}", body=body)
return await self.get(data_element.id)
|
rename(uid, *, name=None, short_name=None, form_name=None, description=None)
async
Partial-update shortcut — read, mutate the label fields, PUT.
Source code in packages/dhis2w-client/src/dhis2w_client/data_elements.py
| async def rename(
self,
uid: str,
*,
name: str | None = None,
short_name: str | None = None,
form_name: str | None = None,
description: str | None = None,
) -> DataElement:
"""Partial-update shortcut — read, mutate the label fields, PUT."""
if name is None and short_name is None and form_name is None and description is None:
raise ValueError("rename requires at least one of name / short_name / form_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 form_name is not None:
current.formName = form_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 a DE — used to roll out threshold colouring.
Source code in packages/dhis2w-client/src/dhis2w_client/data_elements.py
| async def set_legend_sets(self, uid: str, *, legend_set_uids: list[str]) -> DataElement:
"""Replace the legend-set refs on a DE — used to roll out threshold colouring."""
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)
|
delete(uid)
async
Delete a DataElement — DHIS2 rejects deletes on DEs with saved values.
Source code in packages/dhis2w-client/src/dhis2w_client/data_elements.py
| async def delete(self, uid: str) -> None:
"""Delete a DataElement — DHIS2 rejects deletes on DEs with saved values."""
if not uid:
raise ValueError("delete requires a non-empty uid")
await self._client.resources.data_elements.delete(uid)
|
data_element_groups
DataElementGroup authoring — Dhis2Client.data_element_groups.
DHIS2 DataElementGroups collect data elements by thematic axis
(vaccines, antenatal indicators, HIV indicators, …) so dashboards,
pivot tables, and bulk metadata operations can target a coherent
subset in one ref. This accessor mirrors
OrganisationUnitGroupsAccessor: CRUD + per-item membership add/remove
via the DHIS2 collection-item shortcut routes.
Classes
DataElementGroup
Bases: BaseModel
Generated model for DHIS2 DataElementGroup.
DHIS2 Data Element Group - persisted metadata (generated from /api/schemas at DHIS2 v42).
API endpoint: /api/dataElementGroups.
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/data_element_group.py
| class DataElementGroup(BaseModel):
"""Generated model for DHIS2 `DataElementGroup`.
DHIS2 Data Element Group - persisted metadata (generated from /api/schemas at DHIS2 v42).
API endpoint: /api/dataElementGroups.
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).")
aggregationType: AggregationType | 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.")
dataElements: list[Any] | None = Field(default=None, description="Collection of DataElement.")
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
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.")
displayShortName: 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).")
formName: str | None = Field(default=None, description="Length/value max=2147483647.")
groupSets: list[Any] | None = Field(
default=None, description="Collection of DataElementGroupSet. Read-only (inverse side)."
)
href: str | None = None
id: str | None = Field(default=None, description="Unique. Length/value min=11, max=11.")
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. Read-only (inverse side).")
name: str | None = Field(default=None, description="Unique. Length/value min=1, max=230.")
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="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).")
|
DataElementGroupsAccessor
Dhis2Client.data_element_groups — CRUD + membership helpers.
Source code in packages/dhis2w-client/src/dhis2w_client/data_element_groups.py
| class DataElementGroupsAccessor:
"""`Dhis2Client.data_element_groups` — CRUD + membership helpers."""
def __init__(self, client: Dhis2Client) -> None:
"""Bind to the sharing client."""
self._client = client
async def list_all(self) -> list[DataElementGroup]:
"""Return every DataElementGroup with its member refs inline."""
return cast(
list[DataElementGroup],
await self._client.resources.data_element_groups.list(
fields=_DE_GROUP_FIELDS,
paging=False,
),
)
async def get(self, uid: str) -> DataElementGroup:
"""Fetch one group by UID with `dataElements` + `groupSets` populated."""
return await self._client.get(
f"/api/dataElementGroups/{uid}", model=DataElementGroup, params={"fields": _DE_GROUP_FIELDS}
)
async def list_members(
self,
uid: str,
*,
page: int = 1,
page_size: int = 50,
) -> list[DataElement]:
"""Page through DataElements belonging to one group."""
return cast(
list[DataElement],
await self._client.resources.data_elements.list(
fields=_MEMBER_FIELDS,
filters=[f"dataElementGroups.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,
) -> DataElementGroup:
"""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/dataElementGroups", payload, model=WebMessageResponse)
created_uid = envelope.created_uid or uid
if not created_uid:
raise RuntimeError("data-element-group create did not return a uid")
return await self.get(created_uid)
async def update(self, group: DataElementGroup) -> DataElementGroup:
"""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/dataElementGroups/{group.id}", body=body)
return await self.get(group.id)
async def add_members(self, uid: str, *, data_element_uids: list[str]) -> DataElementGroup:
"""Add DataElements to the group via the per-item POST shortcut."""
for de_uid in data_element_uids:
await self._client.resources.data_element_groups.add_collection_item(uid, "dataElements", de_uid)
return await self.get(uid)
async def remove_members(self, uid: str, *, data_element_uids: list[str]) -> DataElementGroup:
"""Drop DataElements from the group via the per-item DELETE shortcut."""
for de_uid in data_element_uids:
await self._client.resources.data_element_groups.remove_collection_item(uid, "dataElements", de_uid)
return await self.get(uid)
async def delete(self, uid: str) -> None:
"""Delete the grouping row — member DEs stay."""
if not uid:
raise ValueError("delete requires a non-empty uid")
await self._client.resources.data_element_groups.delete(uid)
|
Functions
__init__(client)
Bind to the sharing client.
Source code in packages/dhis2w-client/src/dhis2w_client/data_element_groups.py
| def __init__(self, client: Dhis2Client) -> None:
"""Bind to the sharing client."""
self._client = client
|
list_all()
async
Return every DataElementGroup with its member refs inline.
Source code in packages/dhis2w-client/src/dhis2w_client/data_element_groups.py
| async def list_all(self) -> list[DataElementGroup]:
"""Return every DataElementGroup with its member refs inline."""
return cast(
list[DataElementGroup],
await self._client.resources.data_element_groups.list(
fields=_DE_GROUP_FIELDS,
paging=False,
),
)
|
get(uid)
async
Fetch one group by UID with dataElements + groupSets populated.
Source code in packages/dhis2w-client/src/dhis2w_client/data_element_groups.py
| async def get(self, uid: str) -> DataElementGroup:
"""Fetch one group by UID with `dataElements` + `groupSets` populated."""
return await self._client.get(
f"/api/dataElementGroups/{uid}", model=DataElementGroup, params={"fields": _DE_GROUP_FIELDS}
)
|
list_members(uid, *, page=1, page_size=50)
async
Page through DataElements belonging to one group.
Source code in packages/dhis2w-client/src/dhis2w_client/data_element_groups.py
| async def list_members(
self,
uid: str,
*,
page: int = 1,
page_size: int = 50,
) -> list[DataElement]:
"""Page through DataElements belonging to one group."""
return cast(
list[DataElement],
await self._client.resources.data_elements.list(
fields=_MEMBER_FIELDS,
filters=[f"dataElementGroups.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/data_element_groups.py
| async def create(
self,
*,
name: str,
short_name: str,
uid: str | None = None,
code: str | None = None,
description: str | None = None,
) -> DataElementGroup:
"""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/dataElementGroups", payload, model=WebMessageResponse)
created_uid = envelope.created_uid or uid
if not created_uid:
raise RuntimeError("data-element-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/data_element_groups.py
| async def update(self, group: DataElementGroup) -> DataElementGroup:
"""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/dataElementGroups/{group.id}", body=body)
return await self.get(group.id)
|
add_members(uid, *, data_element_uids)
async
Add DataElements to the group via the per-item POST shortcut.
Source code in packages/dhis2w-client/src/dhis2w_client/data_element_groups.py
| async def add_members(self, uid: str, *, data_element_uids: list[str]) -> DataElementGroup:
"""Add DataElements to the group via the per-item POST shortcut."""
for de_uid in data_element_uids:
await self._client.resources.data_element_groups.add_collection_item(uid, "dataElements", de_uid)
return await self.get(uid)
|
remove_members(uid, *, data_element_uids)
async
Drop DataElements from the group via the per-item DELETE shortcut.
Source code in packages/dhis2w-client/src/dhis2w_client/data_element_groups.py
| async def remove_members(self, uid: str, *, data_element_uids: list[str]) -> DataElementGroup:
"""Drop DataElements from the group via the per-item DELETE shortcut."""
for de_uid in data_element_uids:
await self._client.resources.data_element_groups.remove_collection_item(uid, "dataElements", de_uid)
return await self.get(uid)
|
delete(uid)
async
Delete the grouping row — member DEs stay.
Source code in packages/dhis2w-client/src/dhis2w_client/data_element_groups.py
| async def delete(self, uid: str) -> None:
"""Delete the grouping row — member DEs stay."""
if not uid:
raise ValueError("delete requires a non-empty uid")
await self._client.resources.data_element_groups.delete(uid)
|
data_element_group_sets
DataElementGroupSet authoring — Dhis2Client.data_element_group_sets.
A DataElementGroupSet is the analytics dimension that collects
DataElementGroups — e.g. "Vaccine stock" carries groups for each
antigen, "HIV" carries groups for testing / treatment / care
indicators. Mirrors the OU-group-set surface: CRUD + per-item
add_groups / remove_groups via the DHIS2 collection-item shortcut
routes.
Classes
DataElementGroupSet
Bases: BaseModel
Generated model for DHIS2 DataElementGroupSet.
DHIS2 Data Element Group Set - persisted metadata (generated from /api/schemas at DHIS2 v42).
API endpoint: /api/dataElementGroupSets.
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/data_element_group_set.py
| class DataElementGroupSet(BaseModel):
"""Generated model for DHIS2 `DataElementGroupSet`.
DHIS2 Data Element Group Set - persisted metadata (generated from /api/schemas at DHIS2 v42).
API endpoint: /api/dataElementGroupSets.
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).")
aggregationType: AggregationType | None = None
allItems: 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.")
compulsory: bool | None = None
created: datetime | None = None
createdBy: Reference | None = Field(default=None, description="Reference to User.")
dataDimension: bool | None = None
dataDimensionType: DataDimensionType | None = None
dataElementGroups: list[Any] | None = Field(default=None, description="Collection of DataElementGroup.")
description: str | None = Field(default=None, description="Length/value min=1, max=2147483647.")
dimension: str | None = Field(default=None, description="Length/value max=2147483647.")
dimensionItemKeywords: Any | None = Field(
default=None, description="Reference to DimensionItemKeywords. Read-only (inverse side)."
)
dimensionType: DimensionType | None = None
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.")
displayShortName: 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).")
filter: str | None = Field(default=None, description="Length/value max=2147483647.")
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.")
items: list[Any] | None = Field(
default=None, description="Collection of DimensionalItemObject. Read-only (inverse side)."
)
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).")
name: str | None = Field(default=None, description="Unique. Length/value min=1, max=230.")
optionSet: Reference | None = Field(default=None, description="Reference to OptionSet. Read-only (inverse side).")
program: Reference | None = Field(default=None, description="Reference to Program. Read-only (inverse side).")
programStage: Reference | None = Field(
default=None, description="Reference to ProgramStage. Read-only (inverse side)."
)
repetition: Any | None = Field(default=None, description="Reference to EventRepetition. 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="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).")
valueType: ValueType | None = Field(default=None, description="Read-only.")
|
DataElementGroupSetsAccessor
Dhis2Client.data_element_group_sets — CRUD + group-membership helpers.
Source code in packages/dhis2w-client/src/dhis2w_client/data_element_group_sets.py
| class DataElementGroupSetsAccessor:
"""`Dhis2Client.data_element_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[DataElementGroupSet]:
"""Return every DataElementGroupSet with its groups inline."""
return cast(
list[DataElementGroupSet],
await self._client.resources.data_element_group_sets.list(
fields=_DE_GROUP_SET_FIELDS,
paging=False,
),
)
async def get(self, uid: str) -> DataElementGroupSet:
"""Fetch one group set by UID with its `dataElementGroups` populated."""
return await self._client.get(
f"/api/dataElementGroupSets/{uid}", model=DataElementGroupSet, params={"fields": _DE_GROUP_SET_FIELDS}
)
async def list_groups(self, uid: str) -> list[DataElementGroup]:
"""Return the groups in the set in definition order."""
group_set = await self.get(uid)
groups = group_set.dataElementGroups or []
return [DataElementGroup.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,
data_dimension: bool = True,
) -> DataElementGroupSet:
"""Create an empty group set; wire groups into it via `add_groups`.
`data_dimension=True` (default) exposes the set as an analytics
axis (pivot tables, visualisations). `compulsory=True` requires
every member DE to land in exactly one group of the set.
"""
payload: dict[str, Any] = {
"name": name,
"shortName": short_name,
"compulsory": compulsory,
"dataDimension": data_dimension,
}
if uid:
payload["id"] = uid
if code:
payload["code"] = code
if description:
payload["description"] = description
envelope = await self._client.post("/api/dataElementGroupSets", payload, model=WebMessageResponse)
created_uid = envelope.created_uid or uid
if not created_uid:
raise RuntimeError("data-element-group-set create did not return a uid")
return await self.get(created_uid)
async def update(self, group_set: DataElementGroupSet) -> DataElementGroupSet:
"""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/dataElementGroupSets/{group_set.id}", body=body)
return await self.get(group_set.id)
async def add_groups(self, uid: str, *, group_uids: list[str]) -> DataElementGroupSet:
"""Add `group_uids` to the set via the per-item POST shortcut."""
for group_uid in group_uids:
await self._client.resources.data_element_group_sets.add_collection_item(
uid, "dataElementGroups", group_uid
)
return await self.get(uid)
async def remove_groups(self, uid: str, *, group_uids: list[str]) -> DataElementGroupSet:
"""Drop `group_uids` from the set via the per-item DELETE shortcut."""
for group_uid in group_uids:
await self._client.resources.data_element_group_sets.remove_collection_item(
uid, "dataElementGroups", group_uid
)
return await self.get(uid)
async def delete(self, uid: str) -> None:
"""Delete a group set — groups stay, only the dimension row is removed."""
if not uid:
raise ValueError("delete requires a non-empty uid")
await self._client.resources.data_element_group_sets.delete(uid)
|
Functions
__init__(client)
Bind to the sharing client.
Source code in packages/dhis2w-client/src/dhis2w_client/data_element_group_sets.py
| def __init__(self, client: Dhis2Client) -> None:
"""Bind to the sharing client."""
self._client = client
|
list_all()
async
Return every DataElementGroupSet with its groups inline.
Source code in packages/dhis2w-client/src/dhis2w_client/data_element_group_sets.py
| async def list_all(self) -> list[DataElementGroupSet]:
"""Return every DataElementGroupSet with its groups inline."""
return cast(
list[DataElementGroupSet],
await self._client.resources.data_element_group_sets.list(
fields=_DE_GROUP_SET_FIELDS,
paging=False,
),
)
|
get(uid)
async
Fetch one group set by UID with its dataElementGroups populated.
Source code in packages/dhis2w-client/src/dhis2w_client/data_element_group_sets.py
| async def get(self, uid: str) -> DataElementGroupSet:
"""Fetch one group set by UID with its `dataElementGroups` populated."""
return await self._client.get(
f"/api/dataElementGroupSets/{uid}", model=DataElementGroupSet, params={"fields": _DE_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/data_element_group_sets.py
| async def list_groups(self, uid: str) -> list[DataElementGroup]:
"""Return the groups in the set in definition order."""
group_set = await self.get(uid)
groups = group_set.dataElementGroups or []
return [DataElementGroup.model_validate(g) for g in groups if isinstance(g, dict)]
|
create(*, name, short_name, uid=None, code=None, description=None, compulsory=False, data_dimension=True)
async
Create an empty group set; wire groups into it via add_groups.
data_dimension=True (default) exposes the set as an analytics
axis (pivot tables, visualisations). compulsory=True requires
every member DE to land in exactly one group of the set.
Source code in packages/dhis2w-client/src/dhis2w_client/data_element_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,
data_dimension: bool = True,
) -> DataElementGroupSet:
"""Create an empty group set; wire groups into it via `add_groups`.
`data_dimension=True` (default) exposes the set as an analytics
axis (pivot tables, visualisations). `compulsory=True` requires
every member DE to land in exactly one group of the set.
"""
payload: dict[str, Any] = {
"name": name,
"shortName": short_name,
"compulsory": compulsory,
"dataDimension": data_dimension,
}
if uid:
payload["id"] = uid
if code:
payload["code"] = code
if description:
payload["description"] = description
envelope = await self._client.post("/api/dataElementGroupSets", payload, model=WebMessageResponse)
created_uid = envelope.created_uid or uid
if not created_uid:
raise RuntimeError("data-element-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/data_element_group_sets.py
| async def update(self, group_set: DataElementGroupSet) -> DataElementGroupSet:
"""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/dataElementGroupSets/{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/data_element_group_sets.py
| async def add_groups(self, uid: str, *, group_uids: list[str]) -> DataElementGroupSet:
"""Add `group_uids` to the set via the per-item POST shortcut."""
for group_uid in group_uids:
await self._client.resources.data_element_group_sets.add_collection_item(
uid, "dataElementGroups", 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/data_element_group_sets.py
| async def remove_groups(self, uid: str, *, group_uids: list[str]) -> DataElementGroupSet:
"""Drop `group_uids` from the set via the per-item DELETE shortcut."""
for group_uid in group_uids:
await self._client.resources.data_element_group_sets.remove_collection_item(
uid, "dataElementGroups", group_uid
)
return await self.get(uid)
|
delete(uid)
async
Delete a group set — groups stay, only the dimension row is removed.
Source code in packages/dhis2w-client/src/dhis2w_client/data_element_group_sets.py
| async def delete(self, uid: str) -> None:
"""Delete a group set — groups stay, only the dimension row is removed."""
if not uid:
raise ValueError("delete requires a non-empty uid")
await self._client.resources.data_element_group_sets.delete(uid)
|