Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/schema-compatibility-cron.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:

- uses: actions/setup-python@v6
with:
python-version: "3.14"
python-version: "3.13"

- run: pip install --upgrade openhexa.sdk

Expand Down
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "2.19.3"
".": "2.19.4"
}
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

## [2.19.4](https://github.com/BLSQ/openhexa-sdk-python/compare/v2.19.3...v2.19.4) (2026-04-01)


### Bug Fixes

* parameter typing for File ([#371](https://github.com/BLSQ/openhexa-sdk-python/issues/371)) ([c19a404](https://github.com/BLSQ/openhexa-sdk-python/commit/c19a4048d840a849e7df0669ea100417521417e1))


### Miscellaneous

* **deps:** update dependency python to 3.14 ([#363](https://github.com/BLSQ/openhexa-sdk-python/issues/363)) ([178b0a7](https://github.com/BLSQ/openhexa-sdk-python/commit/178b0a763455393be282ca9602db25f6182b6471))

## [2.19.3](https://github.com/BLSQ/openhexa-sdk-python/compare/v2.19.2...v2.19.3) (2026-03-16)


Expand Down
1 change: 1 addition & 0 deletions openhexa/graphql/graphql_client/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,7 @@ class ParameterType(str, Enum):
int = "int"
postgresql = "postgresql"
s3 = "s3"
secret = "secret"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To modify the files under graphql/graphql_client/ you need to follow https://github.com/BLSQ/openhexa-sdk-python/blob/0f795c065543956e8487d3b1a126e65a3a09ea1a/README.md#codegen-from-the-graphql-schema

Those files are auto generated based on schema.generated.graphql

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know I'm off today :) but felt to address the comments. It slipped my mind that files under graphql/graphql_client/ are auto-generated. I've added secret to schema.generated.graphql and re-ran ariadne-codegen to regenerate enums.py instead of editing it manually.

str = "str"


Expand Down
1 change: 1 addition & 0 deletions openhexa/graphql/schema.generated.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -3180,6 +3180,7 @@ enum ParameterType {
int
postgresql
s3
secret
str
}

Expand Down
3 changes: 2 additions & 1 deletion openhexa/sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from .datasets import Dataset
from .files import File
from .pipelines import current_pipeline, current_run, parameter, pipeline
from .pipelines.parameter import DHIS2Widget, IASOWidget
from .pipelines.parameter import DHIS2Widget, IASOWidget, Secret
from .utils import OpenHexaClient
from .workspaces import workspace
from .workspaces.connection import (
Expand Down Expand Up @@ -32,4 +32,5 @@
"Dataset",
"OpenHexaClient",
"File",
"Secret",
]
74 changes: 72 additions & 2 deletions openhexa/sdk/pipelines/parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,11 +389,77 @@ def validate(self, value: typing.Any | None) -> File:
raise ParameterValueError(str(e))


class Secret(str):
"""Marker type for secret/password pipeline parameters.

Use as the ``type`` argument of the ``@parameter`` decorator to indicate that the parameter value is sensitive
and should be hidden in the OpenHEXA web interface. The pipeline function will receive the value as a plain
``str`` at runtime.

Example::

@parameter("iaso_token", type=Secret, name="IASO token", required=True)
@pipeline("my-pipeline")
def my_pipeline(iaso_token: str):
...
"""

pass


class SecretType(ParameterType):
"""Type class for secret/password string parameters. Values are treated as plain strings at runtime."""

@property
def spec_type(self) -> str:
"""Return a type string for the specs that are sent to the backend."""
return "secret"

@property
def expected_type(self) -> type:
"""Returns the python type expected for values."""
return Secret

@property
def accepts_choices(self) -> bool:
"""Secrets don't support choices."""
return False

@property
def accepts_multiple(self) -> bool:
"""Secrets don't support multiple values."""
return False

@staticmethod
def normalize(value: typing.Any) -> Secret | None:
"""Strip whitespace, convert empty strings to None, and wrap as Secret."""
if isinstance(value, str):
normalized_value = value.strip()
else:
normalized_value = value

if normalized_value == "":
return None

if isinstance(normalized_value, str):
return Secret(normalized_value)

return normalized_value

def validate_default(self, value: typing.Any | None):
"""Validate the default value configured for this type."""
if value == "":
raise ParameterValueError("Empty values are not accepted.")

super().validate_default(value)


TYPES_BY_PYTHON_TYPE = {
"str": StringType,
"bool": Boolean,
"int": Integer,
"float": Float,
"Secret": SecretType,
"DHIS2Connection": DHIS2ConnectionType,
"PostgreSQLConnection": PostgreSQLConnectionType,
"IASOConnection": IASOConnectionType,
Expand Down Expand Up @@ -438,13 +504,15 @@ def __init__(
| int
| bool
| float
| Secret
| DHIS2Connection
| IASOConnection
| PostgreSQLConnection
| GCSConnection
| S3Connection
| CustomConnection
| Dataset
| File
],
name: str | None = None,
choices: typing.Sequence | None = None,
Expand Down Expand Up @@ -621,13 +689,15 @@ def parameter(
| int
| bool
| float
| Secret
| DHIS2Connection
| IASOConnection
| PostgreSQLConnection
| GCSConnection
| S3Connection
| CustomConnection
| Dataset
| File
],
name: str | None = None,
choices: typing.Sequence | None = None,
Expand All @@ -647,7 +717,7 @@ def parameter(
----------
code : str
The parameter identifier (must be unique for a given pipeline)
type : {str, int, bool, float, DHIS2Connection, IASOConnection, PostgreSQLConnection, GCSConnection, S3Connection}
type : {str, int, bool, float, DHIS2Connection, IASOConnection, PostgreSQLConnection, GCSConnection, S3Connection, CustomConnection, Dataset, File}
The parameter Python type
name : str, optional
A name for the parameter (will be used instead of the code in the web interface)
Expand All @@ -668,7 +738,7 @@ def parameter(
Whether this parameter should be provided multiple values (if True, the value must be provided as a list of
values of the chosen type)
directory : str, optional
An optional parameter to force file selection to specific directory (only used for parater type File). If the directory does not exist, it will be ignored.
An optional parameter to force file selection to specific directory (only used for parameter type File). If the directory does not exist, it will be ignored.

Returns
-------
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "openhexa.sdk"
version = "2.19.3"
version = "2.19.4"
description = "OpenHEXA SDK"

authors = [{ name = "Bluesquare", email = "dev@bluesquarehub.com" }]
Expand All @@ -20,7 +20,7 @@ requires-python = ">=3.11,<3.15" # the main constraint for supported Python vers
dependencies = [
"urllib3<3",
"multiprocess~=0.70.15",
"requests>=2.31,<2.33",
"requests>=2.31,<2.34",
"PyYAML~=6.0",
"click~=8.1.3",
"jinja2>3,<4",
Expand Down
50 changes: 50 additions & 0 deletions tests/test_parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
ParameterValueError,
PostgreSQLConnectionType,
S3ConnectionType,
Secret,
SecretType,
StringType,
parameter,
)
Expand Down Expand Up @@ -92,6 +94,54 @@ def test_parameter_types_validate():
boolean_parameter_type.validate(86)


def test_secret_type_normalize():
"""Check normalization for SecretType."""
secret_type = SecretType()
assert secret_type.normalize("my-token") == "my-token"
assert secret_type.normalize(" my-token ") == "my-token"
assert secret_type.normalize("") is None
assert secret_type.normalize(" ") is None


def test_secret_type_validate():
"""Check validation for SecretType."""
secret_type = SecretType()
assert secret_type.validate(Secret("my-token")) == "my-token"
with pytest.raises(ParameterValueError):
secret_type.validate(123)


def test_secret_type_does_not_accept_choices():
"""Secret parameters don't support choices."""
with pytest.raises(InvalidParameterError):
Parameter("token", type=Secret, choices=["a", "b"])


def test_secret_type_does_not_accept_multiple():
"""Secret parameters don't support multiple values."""
with pytest.raises(InvalidParameterError):
Parameter("token", type=Secret, multiple=True)


def test_secret_parameter_spec_type():
"""Secret parameters serialize with spec_type 'secret'."""
p = Parameter("token", type=Secret)
assert p.to_dict()["type"] == "secret"


def test_secret_parameter_validates_string():
"""Secret parameters validate and return plain strings."""
p = Parameter("token", type=Secret)
assert p.validate("my-secret-token") == "my-secret-token"


def test_secret_parameter_required():
"""Secret parameters respect required constraint."""
p = Parameter("token", type=Secret, required=True)
with pytest.raises(ParameterValueError):
p.validate(None)


def test_validate_postgres_connection():
"""Check PostgreSQL connection validation."""
identifier = "polio-ff3a0d"
Expand Down