From 97b611c9a785935d2675bd174b1dcbf5840c6699 Mon Sep 17 00:00:00 2001 From: Bram Jans Date: Fri, 29 May 2026 13:21:23 +0200 Subject: [PATCH] fix: ChoicesFromFiles snake_case to camelCase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a bug in the SDK's CLI upload path. Root cause The server's GraphQL ParameterInput type expects the field name choicesFromFile (camelCase). The SDK was sending choices_from_file (snake_case), so the server rejected it — exactly what the error said: "Field 'choices_from_file' is not defined by type 'ParameterInput'. Did you mean 'choicesFromFile'?" Why it only broke now: upload_pipeline (in openhexa/cli/api.py) passes Parameter.to_dict() straight through as the GraphQL input. Every previous parameter field was a single word (code, type, name, required, multiple, directory…), so snake_case and camelCase happened to be identical. choices_from_file, added in the recent dynamic-choices feature (#385), is the first multi-word field — so it's the first mismatch. Fix I added a small boundary serializer that renames the key only when sending to GraphQL, in openhexa/cli/api.py. I deliberately left Parameter.to_dict() untouched — it keeps snake_case as its internal spec convention (the AST-construction code and ~22 tests in tests/test_choices.py rely on that), so the camelCase concern lives only where it belongs: the GraphQL call. Verification - Executed the helper: ChoicesFromFile("regions.csv", column="code") now serializes to {"choicesFromFile": {"format": None, "path": "regions.csv", "column": "code"}}, snake key gone, nested values intact. Parameters without dynamic choices don't get the key. - to_dict() still returns snake_case (internal convention preserved). - tests/test_choices.py → 22 passed; tests/test_cli.py (upload/pipeline/parameter) → 18 passed; ruff check clean. One side note: the SDK's bundled schema (openhexa/graphql/schema.generated.graphql) doesn't yet contain the choicesFromFile field at all — it lags the server. That doesn't block the push (validation is server-side), but it's worth refreshing the bundled schema in a follow-up so the SDK's breaking-change detection stays accurate. Re-run openhexa pipelines push and it should import cleanly now. --- openhexa/cli/api.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/openhexa/cli/api.py b/openhexa/cli/api.py index b00f506e..f1452dd5 100644 --- a/openhexa/cli/api.py +++ b/openhexa/cli/api.py @@ -611,6 +611,20 @@ def generate_zip_file(pipeline_directory_path: str | Path) -> io.BytesIO: return zip_file +def _parameter_to_graphql_input(parameter) -> dict: + """Serialize a pipeline parameter into a GraphQL ``ParameterInput``. + + ``Parameter.to_dict()`` uses snake_case keys for the parameter spec, but the + GraphQL ``ParameterInput`` type expects camelCase field names. Most keys are + single words and match in both conventions; ``choices_from_file`` is the + exception and must be renamed to ``choicesFromFile``. + """ + spec = parameter.to_dict() + if "choices_from_file" in spec: + spec["choicesFromFile"] = spec.pop("choices_from_file") + return spec + + def upload_pipeline( target_pipeline_code: str, pipeline_directory_path: str | Path, @@ -647,7 +661,7 @@ def upload_pipeline( "description": description, "externalLink": link, "zipfile": base64_content, - "parameters": [p.to_dict() for p in pipeline.parameters], + "parameters": [_parameter_to_graphql_input(p) for p in pipeline.parameters], "timeout": pipeline.timeout, }