Skip to content

Commit 0dfc82e

Browse files
DimitriKwihanganayolanferyblsqbot
authored
feat: add secret parameter type for pipeline parameters(HEXA-1457 ) (#373)
* feat: add secret field in parameters of a pipeline * fix:type identity * add secret to schema * feat: add secret field type to pipeline parameters * Revert "chore(deps): update dependency python to 3.14 (#363)" This reverts commit 178b0a7. * fix: parameter typing for File (#371) * chore: release v2.19.4 (#370) * fix: type * fix: type test --------- Co-authored-by: blsqbot <83090543+blsqbot@users.noreply.github.com> --------- Co-authored-by: Yolan Fery <yolan@codethefuture.be> Co-authored-by: blsqbot <83090543+blsqbot@users.noreply.github.com>
1 parent 99bbeee commit 0dfc82e

6 files changed

Lines changed: 123 additions & 2 deletions

File tree

.github/workflows/schema-compatibility-cron.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212

1313
- uses: actions/setup-python@v6
1414
with:
15-
python-version: "3.14"
15+
python-version: "3.13"
1616

1717
- run: pip install --upgrade openhexa.sdk
1818

openhexa/graphql/graphql_client/enums.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,7 @@ class ParameterType(str, Enum):
571571
int = "int"
572572
postgresql = "postgresql"
573573
s3 = "s3"
574+
secret = "secret"
574575
str = "str"
575576

576577

openhexa/graphql/schema.generated.graphql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3180,6 +3180,7 @@ enum ParameterType {
31803180
int
31813181
postgresql
31823182
s3
3183+
secret
31833184
str
31843185
}
31853186

openhexa/sdk/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from .datasets import Dataset
44
from .files import File
55
from .pipelines import current_pipeline, current_run, parameter, pipeline
6-
from .pipelines.parameter import DHIS2Widget, IASOWidget
6+
from .pipelines.parameter import DHIS2Widget, IASOWidget, Secret
77
from .utils import OpenHexaClient
88
from .workspaces import workspace
99
from .workspaces.connection import (
@@ -32,4 +32,5 @@
3232
"Dataset",
3333
"OpenHexaClient",
3434
"File",
35+
"Secret",
3536
]

openhexa/sdk/pipelines/parameter.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,11 +389,77 @@ def validate(self, value: typing.Any | None) -> File:
389389
raise ParameterValueError(str(e))
390390

391391

392+
class Secret(str):
393+
"""Marker type for secret/password pipeline parameters.
394+
395+
Use as the ``type`` argument of the ``@parameter`` decorator to indicate that the parameter value is sensitive
396+
and should be hidden in the OpenHEXA web interface. The pipeline function will receive the value as a plain
397+
``str`` at runtime.
398+
399+
Example::
400+
401+
@parameter("iaso_token", type=Secret, name="IASO token", required=True)
402+
@pipeline("my-pipeline")
403+
def my_pipeline(iaso_token: str):
404+
...
405+
"""
406+
407+
pass
408+
409+
410+
class SecretType(ParameterType):
411+
"""Type class for secret/password string parameters. Values are treated as plain strings at runtime."""
412+
413+
@property
414+
def spec_type(self) -> str:
415+
"""Return a type string for the specs that are sent to the backend."""
416+
return "secret"
417+
418+
@property
419+
def expected_type(self) -> type:
420+
"""Returns the python type expected for values."""
421+
return Secret
422+
423+
@property
424+
def accepts_choices(self) -> bool:
425+
"""Secrets don't support choices."""
426+
return False
427+
428+
@property
429+
def accepts_multiple(self) -> bool:
430+
"""Secrets don't support multiple values."""
431+
return False
432+
433+
@staticmethod
434+
def normalize(value: typing.Any) -> Secret | None:
435+
"""Strip whitespace, convert empty strings to None, and wrap as Secret."""
436+
if isinstance(value, str):
437+
normalized_value = value.strip()
438+
else:
439+
normalized_value = value
440+
441+
if normalized_value == "":
442+
return None
443+
444+
if isinstance(normalized_value, str):
445+
return Secret(normalized_value)
446+
447+
return normalized_value
448+
449+
def validate_default(self, value: typing.Any | None):
450+
"""Validate the default value configured for this type."""
451+
if value == "":
452+
raise ParameterValueError("Empty values are not accepted.")
453+
454+
super().validate_default(value)
455+
456+
392457
TYPES_BY_PYTHON_TYPE = {
393458
"str": StringType,
394459
"bool": Boolean,
395460
"int": Integer,
396461
"float": Float,
462+
"Secret": SecretType,
397463
"DHIS2Connection": DHIS2ConnectionType,
398464
"PostgreSQLConnection": PostgreSQLConnectionType,
399465
"IASOConnection": IASOConnectionType,
@@ -438,6 +504,7 @@ def __init__(
438504
| int
439505
| bool
440506
| float
507+
| Secret
441508
| DHIS2Connection
442509
| IASOConnection
443510
| PostgreSQLConnection
@@ -622,6 +689,7 @@ def parameter(
622689
| int
623690
| bool
624691
| float
692+
| Secret
625693
| DHIS2Connection
626694
| IASOConnection
627695
| PostgreSQLConnection

tests/test_parameter.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
ParameterValueError,
3333
PostgreSQLConnectionType,
3434
S3ConnectionType,
35+
Secret,
36+
SecretType,
3537
StringType,
3638
parameter,
3739
)
@@ -92,6 +94,54 @@ def test_parameter_types_validate():
9294
boolean_parameter_type.validate(86)
9395

9496

97+
def test_secret_type_normalize():
98+
"""Check normalization for SecretType."""
99+
secret_type = SecretType()
100+
assert secret_type.normalize("my-token") == "my-token"
101+
assert secret_type.normalize(" my-token ") == "my-token"
102+
assert secret_type.normalize("") is None
103+
assert secret_type.normalize(" ") is None
104+
105+
106+
def test_secret_type_validate():
107+
"""Check validation for SecretType."""
108+
secret_type = SecretType()
109+
assert secret_type.validate(Secret("my-token")) == "my-token"
110+
with pytest.raises(ParameterValueError):
111+
secret_type.validate(123)
112+
113+
114+
def test_secret_type_does_not_accept_choices():
115+
"""Secret parameters don't support choices."""
116+
with pytest.raises(InvalidParameterError):
117+
Parameter("token", type=Secret, choices=["a", "b"])
118+
119+
120+
def test_secret_type_does_not_accept_multiple():
121+
"""Secret parameters don't support multiple values."""
122+
with pytest.raises(InvalidParameterError):
123+
Parameter("token", type=Secret, multiple=True)
124+
125+
126+
def test_secret_parameter_spec_type():
127+
"""Secret parameters serialize with spec_type 'secret'."""
128+
p = Parameter("token", type=Secret)
129+
assert p.to_dict()["type"] == "secret"
130+
131+
132+
def test_secret_parameter_validates_string():
133+
"""Secret parameters validate and return plain strings."""
134+
p = Parameter("token", type=Secret)
135+
assert p.validate("my-secret-token") == "my-secret-token"
136+
137+
138+
def test_secret_parameter_required():
139+
"""Secret parameters respect required constraint."""
140+
p = Parameter("token", type=Secret, required=True)
141+
with pytest.raises(ParameterValueError):
142+
p.validate(None)
143+
144+
95145
def test_validate_postgres_connection():
96146
"""Check PostgreSQL connection validation."""
97147
identifier = "polio-ff3a0d"

0 commit comments

Comments
 (0)