diff --git a/src/qtoolkit/io/base.py b/src/qtoolkit/io/base.py index b935949..c48e627 100644 --- a/src/qtoolkit/io/base.py +++ b/src/qtoolkit/io/base.py @@ -1,6 +1,7 @@ from __future__ import annotations import abc +import difflib import shlex from dataclasses import fields from pathlib import Path @@ -82,9 +83,22 @@ def generate_header(self, options: dict | QResources | None) -> str: # check that all the options are present in the template keys = set(options.keys()) - extra = keys.difference(template.get_identifiers()) + all_identifiers = template.get_identifiers() + extra = keys.difference(all_identifiers) if extra: - msg = f"The following keys are not present in the template: {', '.join(sorted(extra))}" + close_matches = {} + for extra_val in extra: + m = difflib.get_close_matches(extra_val, all_identifiers, n=1) + if m: + close_matches[extra_val] = m[0] + msg = ( + f"The following keys are not present in the template: {', '.join(sorted(extra))}. " + f"Check the template in {type(self).__module__}.{type(self).__qualname__}.header_template" + ) + if close_matches: + msg += "Possible replacements:" + for extra_val, match in close_matches.items(): + msg += f" {match} instead of {extra_val}." raise ValueError(msg) unclean_header = template.safe_substitute(options) diff --git a/tests/io/test_base.py b/tests/io/test_base.py index 09aa6a5..1d50541 100644 --- a/tests/io/test_base.py +++ b/tests/io/test_base.py @@ -146,7 +146,7 @@ def test_generate_header(self, scheduler): with pytest.raises( ValueError, - match=r"The following keys are not present in the template: tata, titi", + match=r"The following keys are not present in the template: tata, titi. Check the template in .*MyScheduler.header_template", ): res = QResources( nodes=4, @@ -155,6 +155,18 @@ def test_generate_header(self, scheduler): ) scheduler.generate_header(res) + with pytest.raises( + ValueError, + match=r"The following keys are not present in the template: option32, processes-per-node. " + r"Check the template in .*MyScheduler.header_template.*option3 instead of option32. processes_per_node instead of processes-per-node", + ): + res = QResources( + nodes=4, + processes_per_node=16, + scheduler_kwargs={"option32": "xxx", "processes-per-node": "yyy"}, + ) + scheduler.generate_header(res) + def test_generate_ids_list(self, scheduler): ids_list = scheduler.generate_ids_list( [QJob(job_id=4), QJob(job_id="job_id_abc1"), 215, "job12345"]