Skip to content

Commit 4be0046

Browse files
authored
feat HEXA-1149 dhis2 client improvements (#75)
* feat: update client, orgunits, datasets * feat: extends all calls with feilds * fixes test, adds exception * refactors api * add test for connection initialisation
1 parent cf6b793 commit 4be0046

File tree

3 files changed

+107
-42
lines changed

3 files changed

+107
-42
lines changed

openhexa/toolbox/dhis2/api.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,7 @@ class DHIS2Connection(Protocol):
2525

2626

2727
class Api:
28-
def __init__(self, connection: DHIS2Connection, cache_dir: Optional[Union[Path, str]] = None):
29-
self.url = self.parse_api_url(connection.url)
30-
28+
def __init__(self, connection: DHIS2Connection = None, cache_dir: Optional[Union[Path, str]] = None, **kwargs):
3129
self.session = requests.Session()
3230
adapter = HTTPAdapter(
3331
max_retries=Retry(
@@ -40,7 +38,15 @@ def __init__(self, connection: DHIS2Connection, cache_dir: Optional[Union[Path,
4038
self.session.mount("https://", adapter)
4139
self.session.mount("http://", adapter)
4240

43-
self.session = self.authenticate(connection.username, connection.password)
41+
if connection is None and ("url" not in kwargs or "username" not in kwargs or "password" not in kwargs):
42+
raise DHIS2Error("Connection or url, username and password must be provided")
43+
44+
if connection:
45+
self.url = self.parse_api_url(connection.url)
46+
self.session = self.authenticate(connection.username, connection.password)
47+
else:
48+
self.url = self.parse_api_url(kwargs["url"])
49+
self.session = self.authenticate(kwargs["username"], kwargs["password"])
4450

4551
self.cache = None
4652
if cache_dir:

openhexa/toolbox/dhis2/dhis2.py

Lines changed: 70 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,22 @@
1414

1515

1616
class DHIS2:
17-
def __init__(self, connection: DHIS2Connection, cache_dir: Union[str, Path] = None):
17+
def __init__(self, connection: DHIS2Connection = None, cache_dir: Union[str, Path] = None, **kwargs):
1818
"""Initialize a new DHIS2 instance.
1919
2020
Parameters
2121
----------
22-
connection : openhexa DHIS2Connection
22+
connection : openhexa DHIS2Connection, optional
2323
An initialized openhexa dhis2 connection
24+
kwargs:
25+
Additional arguments to pass to initialize openhexa dhis2 connection, such as `url`, `username`, `password`
2426
cache_dir : str, optional
2527
Cache directory. Actual cache data will be stored under a sub-directory
2628
named after the DHIS2 instance domain.
2729
"""
2830
if isinstance(cache_dir, str):
2931
cache_dir = Path(cache_dir)
30-
self.api = Api(connection, cache_dir)
32+
self.api = Api(connection, cache_dir, **kwargs)
3133
self.meta = Metadata(self)
3234
self.version = self.meta.system_info().get("version")
3335
self.data_value_sets = DataValueSets(self)
@@ -69,11 +71,13 @@ def organisation_unit_levels(self) -> List[dict]:
6971
)
7072
return levels
7173

72-
def organisation_units(self, filter: str = None) -> List[dict]:
74+
def organisation_units(self, fields: str = "id,name,level,path,geometry", filter: str = None) -> List[dict]:
7375
"""Get organisation units metadata.
7476
7577
Parameters
7678
----------
79+
fields: str, optional
80+
DHIS2 fields to include in the response, where default value is "id,name,level,path,geometry"
7781
filter: str, optional
7882
DHIS2 query filter
7983
@@ -82,7 +86,7 @@ def organisation_units(self, filter: str = None) -> List[dict]:
8286
list of dict
8387
Id, name, level, path and geometry of all org units.
8488
"""
85-
params = {"fields": "id,name,level,path,geometry"}
89+
params = {"fields": fields}
8690
if filter:
8791
params["filter"] = filter
8892
org_units = []
@@ -93,18 +97,22 @@ def organisation_units(self, filter: str = None) -> List[dict]:
9397
for ou in page["organisationUnits"]:
9498
org_units.append(
9599
{
96-
"id": ou.get("id"),
97-
"name": ou.get("name"),
98-
"level": ou.get("level"),
99-
"path": ou.get("path"),
100-
"geometry": json.dumps(ou.get("geometry")) if ou.get("geometry") else None,
100+
key: ou.get(key)
101+
if key != "geometry"
102+
else json.dumps(ou.get("geometry"))
103+
if ou.get("geometry")
104+
else None
105+
for key in fields.split(",")
101106
}
102107
)
103108
return org_units
104109

105-
def organisation_unit_groups(self) -> List[dict]:
110+
def organisation_unit_groups(self, fields: str = "id,name,organisationUnits") -> List[dict]:
106111
"""Get organisation unit groups metadata.
107-
112+
Parameters
113+
----------
114+
fields: str, optional
115+
DHIS2 fields to include in the response, where default value is "id,name,organisationUnits"
108116
Return
109117
------
110118
list of dict
@@ -113,23 +121,29 @@ def organisation_unit_groups(self) -> List[dict]:
113121
org_unit_groups = []
114122
for page in self.client.api.get_paged(
115123
"organisationUnitGroups",
116-
params={"fields": "id,name,organisationUnits"},
124+
params={"fields": fields},
117125
):
118126
groups = []
119127
for group in page.get("organisationUnitGroups"):
120128
groups.append(
121129
{
122-
"id": group.get("id"),
123-
"name": group.get("name"),
124-
"organisation_units": [ou.get("id") for ou in group["organisationUnits"]],
130+
key: group.get(key)
131+
if key != "organisationUnits"
132+
else [ou.get("id") for ou in group["organisationUnits"]]
133+
for key in fields.split(",")
125134
}
126135
)
127136
org_unit_groups += groups
128137
return groups
129138

130-
def datasets(self) -> List[dict]:
139+
def datasets(self, fields: str = "id,name,dataSetElements,indicators,organisationUnits") -> List[dict]:
131140
"""Get datasets metadata.
132141
142+
Parameters
143+
----------
144+
fields: str, optional
145+
DHIS2 fields to include in the response, where default value is
146+
"id,name,dataSetElements,indicators,organisationUnits"
133147
Return
134148
------
135149
list of dict
@@ -139,23 +153,35 @@ def datasets(self) -> List[dict]:
139153
for page in self.client.api.get_paged(
140154
"dataSets",
141155
params={
142-
"fields": "id,name,dataSetElements,indicators,organisationUnits",
156+
"fields": fields,
143157
"pageSize": 10,
144158
},
145159
):
146160
for ds in page["dataSets"]:
147-
row = {"id": ds.get("id"), "name": ds.get("name")}
148-
row["data_elements"] = [dx["dataElement"]["id"] for dx in ds["dataSetElements"]]
149-
row["indicators"] = [indicator["id"] for indicator in ds["indicators"]]
150-
row["organisation_units"] = [ou["id"] for ou in ds["organisationUnits"]]
161+
fields_list = fields.split(",")
162+
row = {}
163+
if "data_elements" in fields_list:
164+
row["data_elements"] = [dx["dataElement"]["id"] for dx in ds["dataSetElements"]]
165+
fields_list.remove("data_elements")
166+
if "indicators" in fields_list:
167+
row["indicators"] = [indicator["id"] for indicator in ds["indicators"]]
168+
fields_list.remove("indicators")
169+
if "organisation_units" in fields_list:
170+
row["organisation_units"] = [ou["id"] for ou in ds["organisationUnits"]]
171+
fields_list.remove("organisation_units")
172+
row.update({key: ds.get(key) for key in fields_list})
151173
datasets.append(row)
152174
return datasets
153175

154-
def data_elements(self, filter: str = None) -> List[dict]:
176+
def data_elements(
177+
self, fields: str = "id,name,aggregationType,zeroIsSignificant", filter: str = None
178+
) -> List[dict]:
155179
"""Get data elements metadata.
156180
157181
Parameters
158182
----------
183+
fields: str, optional
184+
DHIS2 fields to include in the response, where default value is "id,name,aggregationType,zeroIsSignificant"
159185
filter: str, optional
160186
DHIS2 query filter
161187
@@ -164,20 +190,24 @@ def data_elements(self, filter: str = None) -> List[dict]:
164190
list of dict
165191
Id, name, and aggregation type of all data elements.
166192
"""
167-
params = {"fields": "id,name,aggregationType,zeroIsSignificant"}
193+
params = {"fields": fields}
168194
if filter:
169195
params["filter"] = filter
170196
elements = []
171197
for page in self.client.api.get_paged(
172198
"dataElements",
173199
params=params,
174200
):
175-
elements += page["dataElements"]
201+
for element in page["dataElements"]:
202+
elements.append({key: element.get(key) for key in params["fields"].split(",")})
176203
return elements
177204

178-
def data_element_groups(self) -> List[dict]:
205+
def data_element_groups(self, fields: str = "id,name,dataElements") -> List[dict]:
179206
"""Get data element groups metadata.
180-
207+
Parameters
208+
----------
209+
fields: str, optional
210+
DHIS2 fields to include in the response, where default value is "id,name,dataElements"
181211
Return
182212
------
183213
list of dict
@@ -186,15 +216,14 @@ def data_element_groups(self) -> List[dict]:
186216
de_groups = []
187217
for page in self.client.api.get_paged(
188218
"dataElementGroups",
189-
params={"fields": "id,name,dataElements"},
219+
params={"fields": fields},
190220
):
191221
groups = []
192222
for group in page.get("dataElementGroups"):
193223
groups.append(
194224
{
195-
"id": group.get("id"),
196-
"name": group.get("name"),
197-
"data_elements": [ou.get("id") for ou in group["dataElements"]],
225+
key: group.get(key) if key != "dataElements" else [de.get("id") for de in group["dataElements"]]
226+
for key in fields.split(",")
198227
}
199228
)
200229
de_groups += groups
@@ -213,7 +242,7 @@ def category_option_combos(self) -> List[dict]:
213242
combos += page.get("categoryOptionCombos")
214243
return combos
215244

216-
def indicators(self, filter: str = None) -> List[dict]:
245+
def indicators(self, fields: str = "id,name,numerator,denominator", filter: str = None) -> List[dict]:
217246
"""Get indicators metadata.
218247
219248
Parameters
@@ -226,18 +255,20 @@ def indicators(self, filter: str = None) -> List[dict]:
226255
list of dict
227256
Id, name, numerator and denominator of all indicators.
228257
"""
229-
params = {"fields": "id,name,numerator,denominator"}
258+
params = {"fields": fields}
230259
if filter:
231260
params["filter"] = filter
232261
indicators = []
233262
for page in self.client.api.get_paged(
234263
"indicators",
235264
params=params,
236265
):
266+
for indicator in page["indicators"]:
267+
indicators.append({key: indicator.get(key) for key in fields.split(",")})
237268
indicators += page["indicators"]
238269
return indicators
239270

240-
def indicator_groups(self) -> List[dict]:
271+
def indicator_groups(self, fields: str = "id,name,indicators") -> List[dict]:
241272
"""Get indicator groups metadata.
242273
243274
Return
@@ -248,15 +279,16 @@ def indicator_groups(self) -> List[dict]:
248279
ind_groups = []
249280
for page in self.client.api.get_paged(
250281
"indicatorGroups",
251-
params={"fields": "id,name,indicators"},
282+
params={"fields": fields},
252283
):
253284
groups = []
254285
for group in page.get("indicatorGroups"):
255286
groups.append(
256287
{
257-
"id": group.get("id"),
258-
"name": group.get("name"),
259-
"indicators": [ou.get("id") for ou in group["indicators"]],
288+
key: group.get(key)
289+
if key != "indicators"
290+
else [indicator.get("id") for indicator in group["indicators"]]
291+
for key in fields.split(",")
260292
}
261293
)
262294
ind_groups += groups

tests/dhis2/test_dhis2.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,33 @@ def con():
1717
return Connection("http://localhost:8080", "admin", "district")
1818

1919

20+
@responses.activate
21+
@pytest.mark.parametrize("version", VERSIONS)
22+
def test_connection_from_object(version, con):
23+
responses_dir = Path("tests", "dhis2", "responses", version)
24+
responses._add_from_file(Path(responses_dir, "dhis2_init.yaml"))
25+
api = DHIS2(con, cache_dir=None)
26+
assert api is not None
27+
28+
29+
@responses.activate
30+
@pytest.mark.parametrize("version", VERSIONS)
31+
def test_connection_from_kwargs(version):
32+
responses_dir = Path("tests", "dhis2", "responses", version)
33+
responses._add_from_file(Path(responses_dir, "dhis2_init.yaml"))
34+
api = DHIS2(url="http://localhost:8080", username="admin", password="district", cache_dir=None)
35+
assert api is not None
36+
37+
38+
@responses.activate
39+
@pytest.mark.parametrize("version", VERSIONS)
40+
def test_connection_from_kwargs_fails(version):
41+
responses_dir = Path("tests", "dhis2", "responses", version)
42+
responses._add_from_file(Path(responses_dir, "dhis2_init.yaml"))
43+
with pytest.raises(DHIS2Error):
44+
DHIS2(url="http://localhost:8080", cache_dir=None)
45+
46+
2047
@responses.activate
2148
@pytest.mark.parametrize("version", VERSIONS)
2249
def test_data_elements(version, con):

0 commit comments

Comments
 (0)