Skip to content

Commit 5f44b1c

Browse files
committed
wip
1 parent f2a0ea0 commit 5f44b1c

3 files changed

Lines changed: 38 additions & 30 deletions

File tree

openhexa/cli/api.py

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ def _build_pipeline_version_input(
316316
"description": description,
317317
"externalLink": external_link,
318318
"zipfile": base64.b64encode(zip_file.read()).decode("ascii"),
319-
"parameters": [_parameter_to_graphql_input(p) for p in pipeline.parameters],
319+
"parameters": [p.to_dict() for p in pipeline.parameters],
320320
"timeout": pipeline.timeout,
321321
}
322322

@@ -687,20 +687,6 @@ def generate_zip_file(pipeline_directory_path: str | Path) -> io.BytesIO:
687687
return zip_file
688688

689689

690-
def _parameter_to_graphql_input(parameter) -> dict:
691-
"""Serialize a pipeline parameter into a GraphQL ``ParameterInput``.
692-
693-
``Parameter.to_dict()`` uses snake_case keys for the parameter spec, but the
694-
GraphQL ``ParameterInput`` type expects camelCase field names. Most keys are
695-
single words and match in both conventions; ``choices_from_file`` is the
696-
exception and must be renamed to ``choicesFromFile``.
697-
"""
698-
spec = parameter.to_dict()
699-
if "choices_from_file" in spec:
700-
spec["choicesFromFile"] = spec.pop("choices_from_file")
701-
return spec
702-
703-
704690
def upload_pipeline(
705691
target_pipeline_code: str,
706692
pipeline_directory_path: str | Path,

openhexa/sdk/pipelines/parameter/decorator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ def to_dict(self) -> dict[str, typing.Any]:
119119
"directory": self.directory,
120120
}
121121
if isinstance(self.choices, ChoicesFromFile):
122-
d["choices_from_file"] = self.choices.to_dict()
122+
d["choicesFromFile"] = self.choices.to_dict()
123123
return d
124124

125125
def _validate_single(self, value: typing.Any):

tests/test_choices.py

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
"""Tests for ChoicesFromFile dynamic parameter choices."""
22

33
import tempfile
4-
from unittest import TestCase
4+
from pathlib import Path
5+
from unittest import TestCase, mock
56

67
import pytest
78

@@ -168,7 +169,7 @@ def test_ast_string_shorthand_csv(self):
168169
p = get_pipeline(tmpdir)
169170
param_dict = p.to_dict()["parameters"][0]
170171
assert param_dict["choices"] is None
171-
assert param_dict["choices_from_file"] == {"format": None, "path": "districts.csv", "column": None}
172+
assert param_dict["choicesFromFile"] == {"format": None, "path": "districts.csv", "column": None}
172173

173174
def test_ast_string_shorthand_json(self):
174175
with tempfile.TemporaryDirectory() as tmpdir:
@@ -177,7 +178,7 @@ def test_ast_string_shorthand_json(self):
177178
"@parameter('district', type=str, choices='regions.json')",
178179
)
179180
p = get_pipeline(tmpdir)
180-
assert p.to_dict()["parameters"][0]["choices_from_file"]["format"] is None
181+
assert p.to_dict()["parameters"][0]["choicesFromFile"]["format"] is None
181182

182183
def test_ast_string_shorthand_any_extension(self):
183184
with tempfile.TemporaryDirectory() as tmpdir:
@@ -186,7 +187,7 @@ def test_ast_string_shorthand_any_extension(self):
186187
"@parameter('district', type=str, choices='list.yml')",
187188
)
188189
p = get_pipeline(tmpdir)
189-
assert p.to_dict()["parameters"][0]["choices_from_file"]["format"] is None
190+
assert p.to_dict()["parameters"][0]["choicesFromFile"]["format"] is None
190191

191192
def test_ast_string_shorthand_same_output_as_explicit(self):
192193
with tempfile.TemporaryDirectory() as tmpdir:
@@ -229,7 +230,7 @@ def test_ast_static_list_unaffected(self):
229230
p = get_pipeline(tmpdir)
230231
param_dict = p.to_dict()["parameters"][0]
231232
assert param_dict["choices"] == ["UG", "KE"]
232-
assert "choices_from_file" not in param_dict
233+
assert "choicesFromFile" not in param_dict
233234

234235
def test_ast_string_no_extension_accepted(self):
235236
with tempfile.TemporaryDirectory() as tmpdir:
@@ -238,7 +239,7 @@ def test_ast_string_no_extension_accepted(self):
238239
"@parameter('district', type=str, choices='nodot')",
239240
)
240241
p = get_pipeline(tmpdir)
241-
assert p.to_dict()["parameters"][0]["choices_from_file"]["format"] is None
242+
assert p.to_dict()["parameters"][0]["choicesFromFile"]["format"] is None
242243

243244
def test_ast_string_any_extension_accepted(self):
244245
with tempfile.TemporaryDirectory() as tmpdir:
@@ -247,7 +248,7 @@ def test_ast_string_any_extension_accepted(self):
247248
"@parameter('district', type=str, choices='file.xlsx')",
248249
)
249250
p = get_pipeline(tmpdir)
250-
assert p.to_dict()["parameters"][0]["choices_from_file"]["format"] is None
251+
assert p.to_dict()["parameters"][0]["choicesFromFile"]["format"] is None
251252

252253

253254
# ---------------------------------------------------------------------------
@@ -264,13 +265,13 @@ def test_to_dict_emits_file_choices_key(self):
264265
p = Parameter(code="district", type=str, choices=ChoicesFromFile("districts.csv", column="code", format="csv"))
265266
d = p.to_dict()
266267
assert d["choices"] is None
267-
assert d["choices_from_file"] == {"format": "csv", "path": "districts.csv", "column": "code"}
268+
assert d["choicesFromFile"] == {"format": "csv", "path": "districts.csv", "column": "code"}
268269

269270
def test_to_dict_no_file_choices_key_for_static_choices(self):
270271
p = Parameter(code="country", type=str, choices=["UG", "KE"])
271272
d = p.to_dict()
272273
assert d["choices"] == ["UG", "KE"]
273-
assert "choices_from_file" not in d
274+
assert "choicesFromFile" not in d
274275

275276
def test_rejects_file_choices_on_bool_type(self):
276277
with pytest.raises(InvalidParameterError, match="don't accept choices"):
@@ -331,7 +332,7 @@ def test_file_choices_positional_path(self):
331332
p = get_pipeline(tmpdir)
332333
param_dict = p.to_dict()["parameters"][0]
333334
assert param_dict["choices"] is None
334-
assert param_dict["choices_from_file"] == {"format": None, "path": "districts.csv", "column": None}
335+
assert param_dict["choicesFromFile"] == {"format": None, "path": "districts.csv", "column": None}
335336

336337
def test_file_choices_with_column(self):
337338
with tempfile.TemporaryDirectory() as tmpdir:
@@ -341,7 +342,7 @@ def test_file_choices_with_column(self):
341342
)
342343
p = get_pipeline(tmpdir)
343344
param_dict = p.to_dict()["parameters"][0]
344-
assert param_dict["choices_from_file"] == {"format": None, "path": "data/districts.csv", "column": "code"}
345+
assert param_dict["choicesFromFile"] == {"format": None, "path": "data/districts.csv", "column": "code"}
345346

346347
def test_file_choices_with_column_positional(self):
347348
with tempfile.TemporaryDirectory() as tmpdir:
@@ -351,7 +352,7 @@ def test_file_choices_with_column_positional(self):
351352
)
352353
p = get_pipeline(tmpdir)
353354
param_dict = p.to_dict()["parameters"][0]
354-
assert param_dict["choices_from_file"] == {"format": None, "path": "data/districts.csv", "column": "code"}
355+
assert param_dict["choicesFromFile"] == {"format": None, "path": "data/districts.csv", "column": "code"}
355356

356357
def test_file_choices_explicit_format(self):
357358
with tempfile.TemporaryDirectory() as tmpdir:
@@ -361,7 +362,7 @@ def test_file_choices_explicit_format(self):
361362
)
362363
p = get_pipeline(tmpdir)
363364
param_dict = p.to_dict()["parameters"][0]
364-
assert param_dict["choices_from_file"]["format"] == "json"
365+
assert param_dict["choicesFromFile"]["format"] == "json"
365366

366367
def test_file_choices_format_none_by_default(self):
367368
with tempfile.TemporaryDirectory() as tmpdir:
@@ -371,7 +372,28 @@ def test_file_choices_format_none_by_default(self):
371372
)
372373
p = get_pipeline(tmpdir)
373374
param_dict = p.to_dict()["parameters"][0]
374-
assert param_dict["choices_from_file"]["format"] is None
375+
assert param_dict["choicesFromFile"]["format"] is None
376+
377+
def test_upload_pipeline_sends_camel_case_key(self):
378+
# The GraphQL ParameterInput type expects camelCase field names; a
379+
# snake_case choices_from_file key is rejected by the server.
380+
from openhexa.cli.api import upload_pipeline
381+
382+
with tempfile.TemporaryDirectory() as tmpdir:
383+
self._write_pipeline(
384+
tmpdir,
385+
"@parameter('district', type=str, choices=ChoicesFromFile('districts.csv', column='code'))",
386+
)
387+
with mock.patch("openhexa.cli.api.graphql") as mock_graphql:
388+
mock_graphql.return_value = {
389+
"uploadPipeline": {"success": True, "errors": [], "pipelineVersion": {"id": "version-id"}}
390+
}
391+
upload_pipeline("test-pipeline", Path(tmpdir))
392+
393+
input_data = mock_graphql.call_args[0][1]["input"]
394+
(param,) = input_data["parameters"]
395+
assert "choices_from_file" not in param
396+
assert param["choicesFromFile"] == {"format": None, "path": "districts.csv", "column": "code"}
375397

376398
def test_unsupported_call_in_choices_raises(self):
377399
with tempfile.TemporaryDirectory() as tmpdir:

0 commit comments

Comments
 (0)