Skip to content

fix: ChoicesFromFiles snake_case to camelCase#393

Open
bramj wants to merge 1 commit into
mainfrom
fix-choices-from-file-issue
Open

fix: ChoicesFromFiles snake_case to camelCase#393
bramj wants to merge 1 commit into
mainfrom
fix-choices-from-file-issue

Conversation

@bramj

@bramj bramj commented May 29, 2026

Copy link
Copy Markdown
Contributor

Issue: Pushing a pipeline with dynamic parameters with the CLI fails.

image (22)

Sorry for the raw Claude fix, didn't review the code, but I tested and it does fix the issue

Claude message:

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.

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.
@bramj bramj requested a review from mrivar May 29, 2026 11:25

@mrivar mrivar left a comment

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.

That's interesting, it is a bit annoying having that double format of snake_case + camelCase being different.
Thanks for the fix!

@bramj

bramj commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

Preparing review...

1 similar comment
@bramj

bramj commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

Preparing review...

@bramj

bramj commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

PR Reviewer Guide 🔍

(Review updated until commit 97b611c)

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis ✅

385 - PR Code Verified

Compliant requirements:

  • Pipeline with dynamic parameters can now be pushed via CLI without GraphQL field name error (this PR fixes the broken CLI upload path)

Requires further human verification:

  • Verify that ChoicesFromFile descriptor and string shorthand work end-to-end on the platform (dynamic dropdown rendered correctly)
  • Verify static list choices still work as before after this fix
⏱️ Estimated effort to review: 1 🔵⚪⚪⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Incomplete camelCase conversion

The _parameter_to_graphql_input function only handles the choices_from_filechoicesFromFile rename. If future multi-word snake_case fields are added to Parameter.to_dict() (e.g., external_link, functional_type), they will silently be sent as snake_case to GraphQL and fail. A more robust approach would be a general snake_case-to-camelCase conversion, or at minimum a comment listing all known single-word fields that are safe to pass through unchanged.

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

@bramj

bramj commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

Persistent review updated to latest commit 97b611c

1 similar comment
@bramj

bramj commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

Persistent review updated to latest commit 97b611c

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants