Skip to content
Merged
3 changes: 0 additions & 3 deletions packages/data-designer/src/data_designer/cli/lazy_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,6 @@ def make_context(
) -> click.Context:
return self._resolve().make_context(info_name, args, parent, **extra)

def invoke(self, ctx: click.Context) -> Any:
return self._resolve().invoke(ctx)


def create_lazy_typer_group(
lazy_subcommands: dict[str, dict[str, str]],
Expand Down
2 changes: 2 additions & 0 deletions packages/data-designer/src/data_designer/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import typer

from data_designer.cli.lazy_group import create_lazy_typer_group
from data_designer.cli.runtime import ensure_cli_default_model_settings

_CMD = "data_designer.cli.commands"

Expand Down Expand Up @@ -105,6 +106,7 @@

def main() -> None:
"""Main entry point for the CLI."""
ensure_cli_default_model_settings()
app()
Comment thread
johnnygreco marked this conversation as resolved.


Expand Down
24 changes: 24 additions & 0 deletions packages/data-designer/src/data_designer/cli/runtime.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

from __future__ import annotations

from data_designer.cli.ui import print_warning
from data_designer.config.default_model_settings import resolve_seed_default_model_settings


def ensure_cli_default_model_settings() -> None:
"""Best-effort bootstrap for CLI default model settings.

Repeated calls are safe because ``resolve_seed_default_model_settings()``
only writes missing files/directories.
"""
try:
resolve_seed_default_model_settings()
except Exception as e:
print_warning(
"Could not initialize default model providers and model configs automatically. "
f"The command will continue. Error: {e}. "
"You will need to configure providers and models manually with "
"`data-designer config providers` and `data-designer config models`."
)
Comment thread
johnnygreco marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from urllib.parse import urlparse

from data_designer.config.config_builder import DataDesignerConfigBuilder
from data_designer.config.default_model_settings import resolve_seed_default_model_settings
from data_designer.config.utils.io_helpers import VALID_CONFIG_FILE_EXTENSIONS, is_http_url


Expand All @@ -23,18 +22,6 @@ class ConfigLoadError(Exception):
USER_MODULE_FUNC_NAME = "load_config_builder"


_default_settings_initialized = False


def _ensure_default_model_settings() -> None:
"""Initialize default model/provider files once before loading CLI configs."""
global _default_settings_initialized
if _default_settings_initialized:
return
resolve_seed_default_model_settings()
_default_settings_initialized = True


def load_config_builder(config_source: str) -> DataDesignerConfigBuilder:
"""Load a DataDesignerConfigBuilder from a file path or URL.

Expand All @@ -52,8 +39,6 @@ def load_config_builder(config_source: str) -> DataDesignerConfigBuilder:
Raises:
ConfigLoadError: If the file cannot be loaded or is invalid.
"""
_ensure_default_model_settings()

if is_http_url(config_source):
return _load_from_config_url(config_source)

Expand Down
43 changes: 43 additions & 0 deletions packages/data-designer/tests/cli/test_main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

from __future__ import annotations

from unittest.mock import Mock, call, patch

from typer.testing import CliRunner

from data_designer.cli.main import app, main
from data_designer.config.utils.constants import DEFAULT_NUM_RECORDS

runner = CliRunner()


@patch("data_designer.cli.main.app")
@patch("data_designer.cli.main.ensure_cli_default_model_settings")
def test_main_bootstraps_before_running_app(mock_bootstrap: Mock, mock_app: Mock) -> None:
"""The CLI entrypoint bootstraps defaults before invoking Typer."""
call_order = Mock()
call_order.attach_mock(mock_bootstrap, "bootstrap")
call_order.attach_mock(mock_app, "app")

main()

assert call_order.mock_calls == [call.bootstrap(), call.app()]


@patch("data_designer.cli.commands.create.GenerationController")
def test_app_dispatches_lazy_create_command(mock_controller_cls: Mock) -> None:
"""The Typer app dispatches lazy-loaded commands through the resolved callback."""
mock_controller = Mock()
mock_controller_cls.return_value = mock_controller

result = runner.invoke(app, ["create", "config.yaml"])

assert result.exit_code == 0
mock_controller.run_create.assert_called_once_with(
config_source="config.yaml",
num_records=DEFAULT_NUM_RECORDS,
dataset_name="dataset",
artifact_path=None,
)
41 changes: 41 additions & 0 deletions packages/data-designer/tests/cli/test_runtime.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

from __future__ import annotations

from unittest.mock import patch

import data_designer.cli.runtime as runtime_mod


def test_ensure_cli_default_model_settings_attempts_default_setup() -> None:
"""CLI bootstrap delegates to default setup when the CLI starts."""
with (
patch("data_designer.cli.runtime.print_warning") as mock_print_warning,
patch("data_designer.cli.runtime.resolve_seed_default_model_settings") as mock_resolve,
):
runtime_mod.ensure_cli_default_model_settings()

mock_resolve.assert_called_once_with()
mock_print_warning.assert_not_called()


def test_ensure_cli_default_model_settings_warns_and_continues() -> None:
"""CLI bootstrap prints an actionable warning when setup fails."""
with (
patch("data_designer.cli.runtime.print_warning") as mock_print_warning,
patch(
"data_designer.cli.runtime.resolve_seed_default_model_settings",
side_effect=RuntimeError("boom"),
) as mock_resolve,
):
runtime_mod.ensure_cli_default_model_settings()
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.

(AR) Suggestion: Explicitly document the no-raise contract

What: This test verifies the warning message content when resolve_seed_default_model_settings raises, but never explicitly asserts that ensure_cli_default_model_settings() itself returned normally.

Why: The core behavioral contract is that this function swallows exceptions and continues — the CLI must not crash if bootstrap fails. If someone accidentally narrows the except Exception to a more specific type or removes the try/except, this test would fail with an unhandled exception rather than a clean assertion failure, and the error message wouldn't indicate which contract was broken.

Suggestion: Add a comment making the implicit assertion explicit:

    # Must not raise — the function swallows exceptions and warns instead
    runtime_mod.ensure_cli_default_model_settings()

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 am leaving this one as-is. If ensure_cli_default_model_settings() stops swallowing the exception, this test already fails immediately, so an added comment would not improve coverage or failure detection.


mock_resolve.assert_called_once_with()
mock_print_warning.assert_called_once()
warning = mock_print_warning.call_args[0][0]
assert "Could not initialize default model providers and model configs automatically." in warning
assert "The command will continue." in warning
assert "boom" in warning
assert "data-designer config providers" in warning
assert "data-designer config models" in warning
13 changes: 2 additions & 11 deletions packages/data-designer/tests/cli/utils/test_config_loader.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

from __future__ import annotations

from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest

import data_designer.cli.utils.config_loader as config_loader_mod
from data_designer.cli.utils.config_loader import (
ConfigLoadError,
load_config_builder,
Expand Down Expand Up @@ -288,13 +289,3 @@ def test_load_config_builder_empty_yaml(tmp_path: Path) -> None:

with pytest.raises(ConfigLoadError, match="Failed to load config from"):
load_config_builder(str(yaml_file))


def test_ensure_default_model_settings_runs_once(monkeypatch: pytest.MonkeyPatch) -> None:
"""_ensure_default_model_settings only calls resolve_seed_default_model_settings once."""
monkeypatch.setattr(config_loader_mod, "_default_settings_initialized", False)

with patch("data_designer.cli.utils.config_loader.resolve_seed_default_model_settings") as mock_resolve:
config_loader_mod._ensure_default_model_settings()
config_loader_mod._ensure_default_model_settings()
mock_resolve.assert_called_once()
Loading