Skip to content

Commit 1d51228

Browse files
authored
feat(pipeline): add IASO widget implementation (#255)
* feat(pipeline): adds iaso widget * linting
1 parent 82975ef commit 1d51228

File tree

5 files changed

+118
-13
lines changed

5 files changed

+118
-13
lines changed

examples/pipelines/iaso/pipeline.py

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""Example pipeline to get data elements from IASO."""
2+
3+
from openhexa.sdk import current_run, parameter, pipeline
4+
from openhexa.sdk.pipelines.parameter import IASOWidget
5+
from openhexa.sdk.workspaces.connection import IASOConnection
6+
7+
8+
@pipeline(name="IASO Data Elements")
9+
@parameter("iaso_con", type=IASOConnection, required=True)
10+
@parameter(
11+
"forms",
12+
type=str,
13+
required=True,
14+
widget=IASOWidget.FORMS,
15+
multiple=True,
16+
connection="iaso_con",
17+
)
18+
def iaso(iaso_con, forms):
19+
"""Get forms from IASO."""
20+
print_forms(iaso_con, forms)
21+
22+
23+
@iaso.task
24+
def print_forms(iaso_con, forms):
25+
"""Print forms."""
26+
current_run.log_info("Printing forms")
27+
28+
current_run.log_info(f"Forms: {forms}")
29+
30+
31+
if __name__ == "__main__":
32+
iaso()

openhexa/sdk/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from .datasets import Dataset
44
from .pipelines import current_run, parameter, pipeline
5-
from .pipelines.parameter import DHIS2Widget
5+
from .pipelines.parameter import DHIS2Widget, IASOWidget
66
from .workspaces import workspace
77
from .workspaces.connection import (
88
CustomConnection,
@@ -21,6 +21,7 @@
2121
"DHIS2Connection",
2222
"DHIS2Widget",
2323
"IASOConnection",
24+
"IASOWidget",
2425
"PostgreSQLConnection",
2526
"GCSConnection",
2627
"S3Connection",

openhexa/sdk/pipelines/parameter.py

+22-10
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,14 @@ def validate(self, value: typing.Any | None) -> Dataset:
359359
}
360360

361361

362+
class IASOWidget(StrEnum):
363+
"""Enum for IASO widgets."""
364+
365+
FORMS = "IASO_FORMS"
366+
ORG_UNITS = "IASO_ORG_UNITS"
367+
PROJECTS = "IASO_PROJECTS"
368+
369+
362370
class DHIS2Widget(StrEnum):
363371
"""Enum for DHIS2 widgets."""
364372

@@ -393,7 +401,7 @@ def __init__(
393401
choices: typing.Sequence | None = None,
394402
help: str | None = None,
395403
default: typing.Any | None = None,
396-
widget: DHIS2Widget | None = None,
404+
widget: DHIS2Widget | IASOWidget | None = None,
397405
connection: str | None = None,
398406
required: bool = True,
399407
multiple: bool = False,
@@ -531,18 +539,23 @@ def _validate_default(self, default: typing.Any, multiple: bool):
531539

532540
def validate_parameters(parameters: list[Parameter]):
533541
"""Validate the provided connection parameters if they relate to existing connection parameter."""
534-
supported_connection_types = {DHIS2ConnectionType}
542+
supported_connection_types = {DHIS2ConnectionType, IASOConnectionType}
535543
connection_parameters = {p.code for p in parameters if type(p.type) in supported_connection_types}
536544

537545
for parameter in parameters:
538546
if parameter.connection and parameter.connection not in connection_parameters:
539547
raise InvalidParameterError(
540548
f"Connection field '{parameter.code}' references a non-existing connection parameter '{parameter.connection}'"
541549
)
542-
if parameter.widget and parameter.widget in DHIS2Widget and not parameter.connection:
550+
if (
551+
parameter.widget
552+
and (parameter.widget in DHIS2Widget or parameter.widget in IASOWidget)
553+
and not parameter.connection
554+
):
543555
raise InvalidParameterError(
544-
f"DHIS2 widgets require a connection parameter. Please provide a connection parameter for {parameter.code}. "
545-
f"Example: @parameter('{parameter.code}', type=str, widget=DHIS2Widget.{parameter.widget}, connection='my_connection')"
556+
f"Widgets require a connection parameter. Please provide a connection parameter for {parameter.code}. "
557+
f"Example: @parameter('my_connection', ...)"
558+
f"Example: @parameter('{parameter.code}', widget = ..., connection='my_connection')"
546559
)
547560

548561

@@ -563,7 +576,7 @@ def parameter(
563576
name: str | None = None,
564577
choices: typing.Sequence | None = None,
565578
help: str | None = None,
566-
widget: DHIS2Widget | None = None,
579+
widget: DHIS2Widget | IASOWidget | None = None,
567580
connection: str | None = None,
568581
default: typing.Any | None = None,
569582
required: bool = True,
@@ -586,11 +599,10 @@ def parameter(
586599
interface)
587600
help : str, optional
588601
An optional help text to be displayed in the web interface
589-
widget : DHIS2Widget, optional
590-
An optional widget type for the parameter (only used if the parameter type is DHIS2Connection)
602+
widget : DHIS2Widget|IASOWidget, optional
603+
An optional widget type for the parameter (only used if the parameter type is DHIS2Connection, IASOConnection)
591604
connection : str, optional
592-
An optional connection parameter that will be used to fetch the list of choices for the parameter (only used
593-
if the parameter type is DHIS2Connection)
605+
An optional connection parameter that will be used to link widget to the connection.
594606
default : any, optional
595607
An optional default value for the parameter (should be of the type defined by the type parameter)
596608
required : bool, default=True

openhexa/sdk/pipelines/runtime.py

+3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from openhexa.sdk.pipelines.parameter import (
1818
TYPES_BY_PYTHON_TYPE,
1919
DHIS2Widget,
20+
IASOWidget,
2021
Parameter,
2122
validate_parameters,
2223
)
@@ -170,6 +171,8 @@ def _get_decorator_arg_value(decorator: ast.Call, arg: Argument, index: int) ->
170171
elif isinstance(keyword.value, ast.Attribute):
171172
if keyword.value.attr in DHIS2Widget.__members__:
172173
return getattr(DHIS2Widget, keyword.value.attr), True
174+
elif keyword.value.attr in IASOWidget.__members__:
175+
return getattr(IASOWidget, keyword.value.attr), True
173176
else:
174177
raise ValueError(f"Unsupported widget: {keyword.value.attr}")
175178

tests/test_ast.py

+59-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from unittest.mock import patch
77

88
from openhexa.sdk.pipelines.exceptions import InvalidParameterError, PipelineNotFound
9-
from openhexa.sdk.pipelines.parameter import DHIS2Widget
9+
from openhexa.sdk.pipelines.parameter import DHIS2Widget, IASOWidget
1010
from openhexa.sdk.pipelines.runtime import get_pipeline
1111

1212

@@ -428,7 +428,7 @@ def test_pipeline_with_unsupported_parameter(self):
428428
with self.assertRaises(InvalidParameterError):
429429
get_pipeline(tmpdirname)
430430

431-
def test_pipeline_with_connection_parameter(self):
431+
def test_pipeline_with_connection_parameter_for_dhis2(self):
432432
"""The file contains a @pipeline decorator and a @parameter decorator with a connection type."""
433433
with tempfile.TemporaryDirectory() as tmpdirname:
434434
with open(f"{tmpdirname}/pipeline.py", "w") as f:
@@ -485,6 +485,63 @@ def test_pipeline_with_connection_parameter(self):
485485
},
486486
)
487487

488+
def test_pipeline_with_connection_parameter_for_iaso(self):
489+
"""The file contains a @pipeline decorator and a @parameter decorator with a connection type."""
490+
with tempfile.TemporaryDirectory() as tmpdirname:
491+
with open(f"{tmpdirname}/pipeline.py", "w") as f:
492+
f.write(
493+
"\n".join(
494+
[
495+
"from openhexa.sdk.pipelines import pipeline, parameter",
496+
"from openhexa.sdk.pipelines.widgets import IASOWidget",
497+
"",
498+
"@parameter('iaso_con', name='IASO Connection', type=IASOConnection, required=True)",
499+
"@parameter('forms', name='Forms', type=str, widget=IASOWidget.FORMS, connection='iaso_con', required=True)",
500+
"@pipeline('Test pipeline')",
501+
"def test_pipeline():",
502+
" pass",
503+
"",
504+
]
505+
)
506+
)
507+
pipeline = get_pipeline(tmpdirname)
508+
self.maxDiff = None
509+
self.assertEqual(
510+
pipeline.to_dict(),
511+
{
512+
"name": "Test pipeline",
513+
"function": None,
514+
"tasks": [],
515+
"parameters": [
516+
{
517+
"code": "iaso_con",
518+
"type": "iaso",
519+
"name": "IASO Connection",
520+
"default": None,
521+
"multiple": False,
522+
"choices": None,
523+
"widget": None,
524+
"connection": None,
525+
"help": None,
526+
"required": True,
527+
},
528+
{
529+
"code": "forms",
530+
"type": "str",
531+
"name": "Forms",
532+
"widget": IASOWidget.FORMS.value,
533+
"connection": "iaso_con",
534+
"default": None,
535+
"multiple": False,
536+
"choices": None,
537+
"help": None,
538+
"required": True,
539+
},
540+
],
541+
"timeout": None,
542+
},
543+
)
544+
488545
def test_pipeline_wit_wrong_connection_parameter(self):
489546
"""The file contains a @pipeline decorator and a @parameter decorator with a non-existing connection type."""
490547
with tempfile.TemporaryDirectory() as tmpdirname:

0 commit comments

Comments
 (0)