diff --git a/src/meridian/suites/loader.py b/src/meridian/suites/loader.py index 4655f7c..33cd4e3 100644 --- a/src/meridian/suites/loader.py +++ b/src/meridian/suites/loader.py @@ -23,6 +23,16 @@ SUITE_FILE = "suite.yaml" TASKS_DIR = "tasks" +HARBOR_TASK_FILE = "task.toml" + + +def _looks_like_harbor_tasks(root: Path) -> bool: + """Harbor keeps task.toml at the dataset root, one level down, or two.""" + return any( + candidate.is_file() + for pattern in (HARBOR_TASK_FILE, f"*/{HARBOR_TASK_FILE}", f"*/*/{HARBOR_TASK_FILE}") + for candidate in root.glob(pattern) + ) def _read_yaml( @@ -73,12 +83,15 @@ def load_suite(path: str | Path) -> Suite: suite_file = root / SUITE_FILE if not suite_file.is_file(): + message = f"no {SUITE_FILE} in {root}" + if _looks_like_harbor_tasks(root): + message += "; this looks like Harbor tasks — run `meridian import-harbor` first" raise SuiteValidationError( [ ValidationIssue( code="missing_suite_file", file=SUITE_FILE, - message=f"no {SUITE_FILE} in {root}", + message=message, ) ] ) diff --git a/tests/unit/test_suite_validation.py b/tests/unit/test_suite_validation.py index f9dd90f..3c8ba8f 100644 --- a/tests/unit/test_suite_validation.py +++ b/tests/unit/test_suite_validation.py @@ -79,6 +79,26 @@ def test_missing_suite_file_is_its_own_code(tmp_path: Path) -> None: assert excinfo.value.codes() == ["missing_suite_file"] +@pytest.mark.unit +def test_a_harbor_dataset_suggests_importing_it_first(tmp_path: Path) -> None: + """Pointing `run` at a Harbor dataset should name the command that converts it.""" + task = tmp_path / "some-harbor-task" + task.mkdir() + (task / "task.toml").write_text("", encoding="utf-8") + with pytest.raises(SuiteValidationError) as excinfo: + load_suite(tmp_path) + assert excinfo.value.codes() == ["missing_suite_file"] + assert "import-harbor" in str(excinfo.value) + + +@pytest.mark.unit +def test_a_plain_directory_gets_no_harbor_hint(tmp_path: Path) -> None: + (tmp_path / "tasks").mkdir() + with pytest.raises(SuiteValidationError) as excinfo: + load_suite(tmp_path) + assert "import-harbor" not in str(excinfo.value) + + @pytest.mark.unit def test_malformed_yaml_is_reported_as_such(tmp_path: Path) -> None: shutil.copytree(FIXTURES / "_valid-control", tmp_path / "suite")