Repository: instructlab/instructlab

Issue:
config file ignored
As of https://github.com/instructlab/instructlab/pull/1749

if the version isn't present, the config file fails to load with no warning I could see)
and a default  is instead loaded

we should version be defaulted or inform users to update their config file


Discussion:
fixed by #1834 



Patch:
diff --git a/src/instructlab/configuration.py b/src/instructlab/configuration.py
index b95ca2bdd1..160e1eef44 100644
--- a/src/instructlab/configuration.py
+++ b/src/instructlab/configuration.py
@@ -599,7 +599,7 @@ def ensure_config(self, ctx: click.Context) -> None:
         `ilab config init`. First level subcommand functions call this
         method when they need a config for one of their subcommands.
         """
-        if self.config is None:
+        if self.error_msg is not None:
             ctx.fail(self.error_msg)
 
 


Test patch:
diff --git a/tests/test_lab.py b/tests/test_lab.py
index 12f8e1ffef..59b51cce4d 100644
--- a/tests/test_lab.py
+++ b/tests/test_lab.py
@@ -55,27 +55,47 @@ def test_ilab_cli_imports(testdata_path: pathlib.Path):
     subprocess.check_call([sys.executable, str(script)], text=True)
 
 
-subcommands = [
+class Command(typing.NamedTuple):
+    args: tuple[str, ...]
+    extra_args: tuple[str, ...] = ()
+    needs_config: bool = True
+
+    def get_args(self, *extra, default_config: bool = True) -> list[str]:
+        args = []
+        if self.needs_config and default_config:
+            args.extend(["--config", "DEFAULT"])
+        args.extend(self.args)
+        args.extend(self.extra_args)
+        args.extend(extra)
+        return args
+
+    @property
+    def has_debug_params(self) -> bool:
+        # only second level subcommands have a --debug-params option
+        return len(self.args) > 1
+
+
+subcommands: list[Command] = [
     # split first and second level for nicer pytest output
     # first, second, extra args
-    (None, None, ()),
-    ("config", None, ()),
-    ("config", "init", ()),
-    ("config", "show", ()),
-    ("model", None, ()),
-    ("model", "chat", ()),
-    ("model", "convert", ("--model-dir", "test")),
-    ("model", "download", ()),
-    ("model", "evaluate", ("--benchmark", "mmlu")),
-    ("model", "serve", ()),
-    ("model", "test", ()),
-    ("model", "train", ()),
-    ("data", None, ()),
-    ("data", "generate", ()),
-    ("system", None, ()),
-    ("system", "info", ()),
-    ("taxonomy", None, ()),
-    ("taxonomy", "diff", ()),
+    Command((), needs_config=False),
+    Command(("config",), needs_config=False),
+    Command(("config", "init"), needs_config=False),
+    Command(("config", "show")),
+    Command(("model",), needs_config=False),
+    Command(("model", "chat")),
+    Command(("model", "convert"), ("--model-dir", "test")),
+    Command(("model", "download")),
+    Command(("model", "evaluate"), ("--benchmark", "mmlu")),
+    Command(("model", "serve")),
+    Command(("model", "test")),
+    Command(("model", "train")),
+    Command(("data",), needs_config=False),
+    Command(("data", "generate")),
+    Command(("system",), needs_config=False),
+    Command(("system", "info"), needs_config=False),
+    Command(("taxonomy",), needs_config=False),
+    Command(("taxonomy", "diff")),
 ]
 
 aliases = [
@@ -93,14 +113,10 @@ def test_ilab_cli_imports(testdata_path: pathlib.Path):
 ]
 
 
-@pytest.mark.parametrize("first,second", [sc[:2] for sc in subcommands])
-def test_ilab_cli_help(first: str | None, second: str | None, cli_runner):
-    cmd = ["--config", "DEFAULT"]
-    if first is not None:
-        cmd.append(first)
-    if second is not None:
-        cmd.append(second)
-    cmd.append("--help")
+@pytest.mark.parametrize("command", subcommands, ids=lambda sc: repr(sc.args))
+def test_ilab_cli_help(command: Command, cli_runner: CliRunner):
+    cmd = command.get_args("--help")
+    assert "--help" in cmd
     result = cli_runner.invoke(lab.ilab, cmd)
     assert result.exit_code == 0, result.stdout
 
@@ -114,20 +130,15 @@ def test_ilab_cli_deprecated_help(alias: str, cli_runner):
 
 
 @pytest.mark.parametrize(
-    "first,second,extra",
-    # only second level subcommands have a --debug-params option
-    [sc for sc in subcommands if sc[1] is not None],
+    "command",
+    [sc for sc in subcommands if sc.has_debug_params],
+    ids=lambda sc: repr(sc.args),
 )
-def test_ilab_cli_debug_params(
-    first: str, second: str, extra: typing.Sequence[str], cli_runner: CliRunner
-):
-    cmd = ["--config", "DEFAULT", first, second]
-    cmd.extend(extra)
-
-    result = cli_runner.invoke(lab.ilab, cmd + ["--debug-params"])
+def test_ilab_cli_debug_params(command: Command, cli_runner: CliRunner):
+    result = cli_runner.invoke(lab.ilab, command.get_args("--debug-params"))
     assert result.exit_code == 0, result.stdout
 
-    result = cli_runner.invoke(lab.ilab, cmd + ["--debug-params-json"])
+    result = cli_runner.invoke(lab.ilab, command.get_args("--debug-params-json"))
     assert result.exit_code == 0, result.stdout
     j = json.loads(result.stdout)
     assert isinstance(j, dict)
@@ -144,11 +155,33 @@ def test_ilab_commands_tested():
             sub.add(secondary.name)
 
     tested = {None: set([""])}
-    for primary, secondary, _ in subcommands:
-        sub = tested.setdefault(primary, set())
-        sub.add(secondary or "")
+    for command in subcommands:
+        if not command.args:
+            continue
+        sub = tested.setdefault(command.args[0], set())
+        if len(command.args) == 2:
+            sub.add(command.args[1])
+        else:
+            sub.add("")
 
     assert ilab_commands == tested
 
     ep_aliases = metadata.entry_points(group="instructlab.command.alias")
     assert set(aliases) == ep_aliases.names
+
+
+@pytest.mark.parametrize(
+    "command",
+    subcommands,
+    ids=lambda sc: repr(sc.args),
+)
+def test_ilab_missing_config(command: Command, cli_runner: CliRunner) -> None:
+    cmd = command.get_args(default_config=False)
+    assert "--config" not in cmd
+    result = cli_runner.invoke(lab.ilab, cmd)
+
+    if command.needs_config:
+        assert result.exit_code == 2, result
+        assert "does not exist or is not a readable file" in result.stdout
+    else:
+        assert result.exit_code == 0, result