Skip to content

feat(cli): support Python workflow args#799

Merged
eric-tramel merged 10 commits into
mainfrom
codex/issue-507
Jul 7, 2026
Merged

feat(cli): support Python workflow args#799
eric-tramel merged 10 commits into
mainfrom
codex/issue-507

Conversation

@eric-tramel

@eric-tramel eric-tramel commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

📋 Summary

Support workflow-specific arguments for local Python config modules used by data-designer create, preview, validate, and check-models. Arguments after -- are exposed through a public DataDesignerScriptParams object while existing zero-argument workflows remain compatible.

The loader rejects forwarded arguments for serialized and remote configs, validates Python workflow signatures, and keeps unknown Data Designer options before -- strict.

🔗 Related Issue

Fixes #507

🔄 Changes

  • Add the lazily exported DataDesignerScriptParams(argv=...) config API.
  • Forward raw script arguments through all four config-loading CLI commands and the controller.
  • Validate params-aware and legacy Python workflow signatures at the config-loader boundary, including positional and sole keyword-only parameters.
  • Reject script arguments for YAML, JSON, and remote config sources without printing their values.
  • Document the optional params pattern and supported commands in the Fern landing page.
  • Cover lazy CLI dispatch, option strictness, controller threading, loader compatibility, and rejection paths.

💻 Usage

import argparse

import data_designer.config as dd


def load_config_builder(
    params: dd.DataDesignerScriptParams | None = None,
) -> dd.DataDesignerConfigBuilder:
    parser = argparse.ArgumentParser()
    parser.add_argument("--seed-path", required=True)
    args = parser.parse_args(list(params.argv if params else ()))

    builder = dd.DataDesignerConfigBuilder()
    builder.with_seed_dataset(dd.LocalFileSeedSource(path=args.seed_path))
    return builder
data-designer create workflow.py --num-records 32 -- --seed-path seeds.parquet
data-designer preview workflow.py -n 3 -- --seed-path sample.parquet
data-designer validate workflow.py -- --seed-path seeds.parquet
data-designer check-models workflow.py -- --seed-path seeds.parquet

🧪 Testing

The full repository suite previously passed with 3,805 tests and 1 skip. After merging the latest main and addressing review feedback, the full CLI suite passed with 813 tests and 1 skip; Ruff passed on all changed Python files; Fern validation completed with zero errors.

  • Unit tests added/updated
  • CLI integration paths covered
  • Fern documentation validated

✅ Checklist

  • Follows commit message conventions
  • Commits are signed off (DCO)
  • Public usage documentation updated

Description updated with AI

Forward arguments after the CLI separator to params-aware local Python config modules while preserving legacy zero-argument workflows. Reject script arguments for serialized and remote configs, and document the public params protocol.

Fixes #507

Signed-off-by: Eric W. Tramel <eric.tramel@gmail.com>
@eric-tramel
eric-tramel requested a review from a team as a code owner July 7, 2026 14:39
@eric-tramel

Copy link
Copy Markdown
Contributor Author

/nvskills-ci

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Fern preview: https://nvidia-preview-pr-799.docs.buildwithfern.com/nemo/datadesigner

Fern previews include the docs-website version archive with PR changes synced into latest. Notebook tutorials are rendered without execution outputs in previews.

@greptile-apps

greptile-apps Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces DataDesignerScriptParams, a frozen dataclass that carries raw CLI arguments forwarded after -- to local Python workflow modules. All four generation commands (create, preview, validate, check-models) are updated to collect these arguments and thread them through the controller into the config loader.

  • New DataDesignerScriptParams type lazily exported from data_designer.config, holding an immutable argv tuple passed to Python workflows; YAML/JSON/remote sources reject non-empty argv with a clear error that avoids echoing sensitive values.
  • Signature introspection in _load_from_python_module handles zero-arg (legacy), positional/positional-or-keyword, and keyword-only single-parameter signatures; *args, **kwargs, and multi-parameter functions raise a descriptive ConfigLoadError.
  • _is_version_request fix in main.py truncates the args list at -- before checking for --version, preventing a forwarded --version from bypassing bootstrap.

Confidence Score: 5/5

Safe to merge — changes are additive, backward-compatible with existing zero-argument workflows, and well-guarded against misuse.

The controller always wraps script_args into a DataDesignerScriptParams before calling the loader, so the empty-argv path is exercised for every existing caller without any behavior change. The loader's rejection check fires only when argv is non-empty and the source is not a local .py file, keeping YAML/JSON/remote configs unaffected. Signature introspection is exhaustive: the zero-arg legacy case, the positional case, the keyword-only case, and the error cases (VAR_POSITIONAL, VAR_KEYWORD, multi-parameter) are all explicitly handled and tested. The _is_version_request fix is a single-line truncation that does not mutate sys.argv and is covered by a dedicated test.

No files require special attention.

Important Files Changed

Filename Overview
packages/data-designer-config/src/data_designer/config/script_params.py New frozen dataclass DataDesignerScriptParams with an immutable argv tuple; minimal and correct.
packages/data-designer/src/data_designer/cli/utils/config_loader.py Core loader change: inspects the Python workflow signature (0-arg, positional, or keyword-only), rejects argv for non-Python sources, and dispatches correctly; all branches are covered by tests.
packages/data-designer/src/data_designer/cli/controllers/generation_controller.py _load_config always wraps script_args into DataDesignerScriptParams (empty when None) and passes it through; the empty-argv path preserves backward compatibility with YAML/JSON configs.
packages/data-designer/src/data_designer/cli/main.py Fixes _is_version_request to ignore args after the -- separator; correctly rebinds the local variable without mutating sys.argv.
packages/data-designer/tests/cli/utils/test_config_loader.py Comprehensive new tests covering: param forwarding, keyword-only parameters, legacy signature rejection, unsupported signatures (*args/**kwargs/multi-param), serialized config rejection, remote URL rejection, and sensitive-value leak prevention in error messages.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant User
    participant CLI as CLI (create/preview/validate/check-models)
    participant Controller as GenerationController
    participant Loader as load_config_builder
    participant Module as Python Workflow Module

    User->>CLI: data-designer create workflow.py -- --seed-path seeds.parquet
    CLI->>Controller: run_create(config_source, script_args)
    Controller->>Controller: "DataDesignerScriptParams(argv=(...))"
    Controller->>Loader: load_config_builder(config_source, script_params)
    Loader->>Loader: Check: is_remote or non-.py? reject if argv non-empty
    Loader->>Loader: inspect.signature(load_config_builder)
    alt 0 parameters
        Loader->>Module: func()
    else 1 positional parameter
        Loader->>Module: func(script_params)
    else 1 keyword-only parameter
        Loader->>Module: "func(param_name=script_params)"
    else unsupported signature
        Loader-->>Controller: ConfigLoadError
    end
    Module-->>Loader: DataDesignerConfigBuilder
    Loader-->>Controller: DataDesignerConfigBuilder
    Controller->>Controller: Run generation workflow
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant User
    participant CLI as CLI (create/preview/validate/check-models)
    participant Controller as GenerationController
    participant Loader as load_config_builder
    participant Module as Python Workflow Module

    User->>CLI: data-designer create workflow.py -- --seed-path seeds.parquet
    CLI->>Controller: run_create(config_source, script_args)
    Controller->>Controller: "DataDesignerScriptParams(argv=(...))"
    Controller->>Loader: load_config_builder(config_source, script_params)
    Loader->>Loader: Check: is_remote or non-.py? reject if argv non-empty
    Loader->>Loader: inspect.signature(load_config_builder)
    alt 0 parameters
        Loader->>Module: func()
    else 1 positional parameter
        Loader->>Module: func(script_params)
    else 1 keyword-only parameter
        Loader->>Module: "func(param_name=script_params)"
    else unsupported signature
        Loader-->>Controller: ConfigLoadError
    end
    Module-->>Loader: DataDesignerConfigBuilder
    Loader-->>Controller: DataDesignerConfigBuilder
    Controller->>Controller: Run generation workflow
Loading

Reviews (9): Last reviewed commit: "Merge branch 'main' into codex/issue-507" | Re-trigger Greptile

Signed-off-by: Eric W. Tramel <eric.tramel@gmail.com>
@eric-tramel

Copy link
Copy Markdown
Contributor Author

/nvskills-ci

1 similar comment
@eric-tramel

Copy link
Copy Markdown
Contributor Author

/nvskills-ci

Signed-off-by: Eric W. Tramel <eric.tramel@gmail.com>
andreatnvidia
andreatnvidia previously approved these changes Jul 7, 2026

@nabinchha nabinchha 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.

Nice work on this one, @eric-tramel — the layering and test coverage here are really clean.

Summary

This PR lets local Python config modules accept workflow-specific CLI arguments passed after --, surfaced through a new lazily-exported DataDesignerScriptParams. The implementation matches the stated intent: args are threaded through create/preview/validate → controller → loader, the loader validates the workflow signature, and non-Python/remote sources reject forwarded args without echoing their (potentially sensitive) values. The signature-handling and rejection logic is well thought out and thoroughly tested.

Findings

Warnings — Worth addressing

packages/data-designer/src/data_designer/cli/controllers/generation_controller.py:119check-models can't parameterize a workflow

  • What: create, preview, and validate all gained script_args, but run_check_models still calls self._load_config(config_source) with no args. A workflow whose load_config_builder(params) requires argv (e.g. an argparse required=True --seed-path) will fail under check-models, because it always receives an empty DataDesignerScriptParams().
  • Why: check-models is the natural pre-flight companion to validate, so a user who parameterizes their workflow will reasonably expect check-models workflow.py -- --seed-path seeds.parquet to work too. Today it either errors (required arg missing) or silently runs against defaults, which is surprising given the other three commands support it.
  • Suggestion: Either extend check-models (command + run_check_models) with the same script_args plumbing, or, if excluding it is intentional, add a one-line note in the docs/PR description so the asymmetry is a documented decision rather than a gap.

Suggestions — Take it or leave it

packages/data-designer/src/data_designer/cli/utils/config_loader.py:191-198 — Keyword-only param name is hard-coded to params

  • What: A single positional-or-keyword parameter is accepted under any name, but a keyword-only parameter is only accepted when it is literally named params (def load_config_builder(*, config) is rejected, def load_config_builder(config) is fine).
  • Why: The asymmetry is a little surprising — the name only matters in the keyword-only branch. A user who copies the documented positional pattern is fine, but anyone experimenting with keyword-only signatures could hit a confusing rejection.
  • Suggestion: Since we already know it's the sole parameter, we can bind by name generically for the keyword-only case (func(**{parameter.name: params})) and drop the == "params" constraint — or, if the constraint is deliberate, mention the expected name in signature_error.

packages/data-designer/src/data_designer/cli/main.py:52 — Dense one-liner in _is_version_request

  • What: return "--version" in args[: args.index("--")] if "--" in args else "--version" in args packs the separator-aware logic into a single nested ternary.
  • Why: It's correct (and nicely tested), but takes a second read to parse the slice-before--- intent.
  • Suggestion: Optional readability tweak, e.g.
def _is_version_request(args: list[str]) -> bool:
    if "--" in args:
        args = args[: args.index("--")]
    return "--version" in args

packages/data-designer/src/data_designer/cli/commands/create.py:74 — Variadic script_args also captures bare positionals without --

  • What: script_args is a variadic typer.Argument, so data-designer create a.py b.py silently binds b.py as a forwarded arg, even though the help text says "after --". (Unknown options before -- are correctly rejected — nice — but stray positionals aren't.)
  • Why: Minor foot-gun: a typo'd second path produces a downstream "does not accept script arguments" error rather than an "unexpected argument" one. Not a regression in the common path, just slightly less obvious.
  • Suggestion: Leave as-is if you consider it acceptable; otherwise a short note in the arg help ("captured after --") or a guard could make the intent clearer.

What Looks Good

  • Signature validation is genuinely careful — zero-arg, positional, keyword-only, and the *args/**kwargs/multi-arg rejection paths are all handled and parametrized in tests.
  • Security-conscious rejection — refusing forwarded args for YAML/JSON/remote sources without printing their values, plus the explicit "sensitive-value" not in str(exc_info.value) assertion, is a thoughtful touch.
  • Clean layeringDataDesignerScriptParams lands in config as a frozen/slots dataclass and is lazily exported, respecting the interface → engine → config direction, and the Fern docs were updated in the same PR.

Verdict

Needs changes — only the check-models asymmetry is worth resolving (fix or document) before merge; everything else is optional polish. Linter (ruff check + ruff format --check) passes on all changed files.


This review was generated by an AI assistant.

Signed-off-by: Eric W. Tramel <eric.tramel@gmail.com>

# Conflicts:
#	packages/data-designer/src/data_designer/cli/controllers/generation_controller.py
#	packages/data-designer/src/data_designer/cli/utils/config_loader.py
#	packages/data-designer/tests/cli/controllers/test_generation_controller.py
#	packages/data-designer/tests/cli/test_main.py
Signed-off-by: Eric W. Tramel <eric.tramel@gmail.com>
- Forward workflow arguments through check-models.
- Bind sole keyword-only loader parameters by their declared name.
- Clarify separator-aware version detection.

Signed-off-by: Eric W. Tramel <eric.tramel@gmail.com>
@eric-tramel

Copy link
Copy Markdown
Contributor Author

@nabinchha Thanks for the thorough review. I addressed the feedback in 4bbd409a:

  • Added script_args support to check-models end-to-end, including CLI/controller tests and the Fern usage example.
  • Changed sole keyword-only loader parameters to bind by their declared name instead of requiring params.
  • Expanded _is_version_request to make the separator handling easier to read.
  • Kept bare positional handling unchanged: Click removes the -- separator before callback execution, so strict enforcement would require brittle raw-argv inspection. The CLI help and docs continue to state that workflow arguments belong after --.

Validation is green: 813 CLI tests passed with 1 skip, Ruff passed, and Fern validation reported 0 errors. I also merged the latest main and completed a clean review pass.

@nabinchha nabinchha 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.

Thanks for the quick turnaround, @eric-tramel — the new fix(cli): complete workflow argument handling commit resolves all three actionable findings cleanly, with matching test and doc updates.

Addressed

  • check-models now forwards workflow argscheck_models_command gained script_args, run_check_models threads it through _load_config, docs list the new check-models workflow.py -- --seed-path ... example, and both the command and controller tests are updated. This closes the asymmetry that was the reason for requesting changes.
  • Keyword-only param no longer name-locked — dropped the parameter.name == "params" constraint and binds generically via func(**{parameters[0].name: params}); the test now uses def load_config_builder(*, config) and "*, other" was correctly removed from the unsupported-signature cases.
  • _is_version_request readability — rewritten to the early-return form; clear and still separator-aware.

Remaining (optional, non-blocking)

  • The variadic script_args still captures bare positionals without -- (e.g. create a.py b.py). Fine to leave as-is — noted only for awareness.

Verdict

Ship it (with nits) — all requested changes are in, and ruff check + ruff format --check pass on the touched files. Approving.


This review was generated by an AI assistant.

@eric-tramel
eric-tramel merged commit bc0d03e into main Jul 7, 2026
62 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: support custom CLI args for Python config modules

3 participants