Repository: IAMconsortium/nomenclature

Issue:
CLI to validate a scenario data file
To make it easier for non-expert users to check their data against a DataStructureDefinition, we need a simple CLI that takes an input file (IAMC scenario data) and validates it against a the definitions.

Basically, this should be a reduced version of `cli_run_workflow`, but only take an `input file` (required) and  a `definitions` folder (default to 'definitions').



Discussion:
dc-almeida: Thanks for the hawk-eye as always!
phackstock: > Thanks for the hawk-eye as always!

You're welcome, always easier to spot these things in other people's work :D
phackstock: Before we go ahead with the merge if I understood our discussion in #419 correctly, this PR is still missing a dimensions option so that you can specify which dimension you want to validate, correct @danielhuppmann?
danielhuppmann: I have a suggestion: we merge as is, and then @dc-almeida does a follow-up PR adding the dimensions for this CLI, and also extending the tests for the `validate-scenario` and `validate-project` to make sure that all three cases work as expected:
- explicit `dimensions` argument (this is currently tested in `test_cli_custom_dimensions_runs()`)
- if not given, use `dimensions` argument set from `nomenclature.yaml` (not sure if this is tested)
- if also not given, check all available folders
phackstock: Sounds good to me.

Patch:
diff --git a/nomenclature/cli.py b/nomenclature/cli.py
index 33cdafc..18ef895 100644
--- a/nomenclature/cli.py
+++ b/nomenclature/cli.py
@@ -277,3 +277,29 @@ def cli_run_workflow(
     df = getattr(workflow, workflow_function)(IamDataFrame(input_file))
     if output_file is not None:
         df.to_excel(output_file)
+
+
+@cli.command("validate-scenarios")
+@click.argument("input_file", type=click.Path(exists=True, path_type=Path))
+@click.option(
+    "--definitions",
+    help="Optional name for definitions folder",
+    type=click.Path(exists=True, path_type=Path),
+    default="definitions",
+)
+def cli_validate_scenarios(input_file: Path, definitions: Path):
+    """Validate a scenario file against the codelists of a project
+
+    Parameters
+    ----------
+    input_file : Path
+        Input data file, must be IAMC format, .xlsx or .csv
+    definitions : Path
+        Definitions folder with codelists, by default "definitions"
+
+    Raises
+    ------
+    ValueError
+        If input_file validation fails against specified codelist(s).
+    """
+    DataStructureDefinition(definitions).validate(IamDataFrame(input_file))


Test patch:
diff --git a/tests/test_cli.py b/tests/test_cli.py
index dd1b342..9eaee1f 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -420,3 +420,32 @@ def test_cli_run_workflow(tmp_path, simple_df):
     )
 
     assert_iamframe_equal(simple_df, IamDataFrame(tmp_path / "output.xlsx"))
+
+
+@pytest.mark.parametrize(
+    "status, unit, exit_code", [("valid", "EJ/yr", 0), ("invalid", "EJ", 1)]
+)
+def test_cli_valid_scenarios(status, unit, exit_code, tmp_path):
+    """Check that CLI validates an IAMC dataset according to defined codelist."""
+    IamDataFrame(
+        pd.DataFrame(
+            [
+                ["m_a", "s_a", "World", "Primary Energy", unit, 1, 2],
+            ],
+            columns=IAMC_IDX + [2005, 2010],
+        )
+    ).to_excel(tmp_path / f"{status}_data.xlsx")
+    result_valid = runner.invoke(
+        cli,
+        [
+            "validate-scenarios",
+            str(tmp_path / f"{status}_data.xlsx"),
+            "--definitions",
+            str(
+                MODULE_TEST_DATA_DIR
+                / "structure_validation_no_mappings"
+                / "definitions"
+            ),
+        ],
+    )
+    assert result_valid.exit_code == exit_code