diff --git a/dvc/parsing/__init__.py b/dvc/parsing/__init__.py index 13d7c53c5..c62ce2c00 100644 --- a/dvc/parsing/__init__.py +++ b/dvc/parsing/__init__.py @@ -499,8 +499,26 @@ class MatrixDefinition: iterable = self.context.resolve(self.matrix_data, unwrap=False) except (ContextError, ParseError) as exc: format_and_raise(exc, f"'{self.where}.{self.name}.matrix'", self.relpath) + + # Matrix entries will have `key` and `item` added to the context. + # Warn users if these are already in the context from the global vars. + self._warn_if_overwriting(["key", "item"]) return iterable + def _warn_if_overwriting(self, keys: List[str]): + warn_for = [k for k in keys if k in self.context] + if warn_for: + linking_verb = "is" if len(warn_for) == 1 else "are" + logger.warning( + ( + "%s %s already specified, " + "will be overwritten for stages generated from '%s'" + ), + " and ".join(warn_for), + linking_verb, + self.name, + ) + @cached_property def normalized_iterable(self) -> Dict[str, "DictStrAny"]: """Convert sequence to Mapping with keys normalized.""" @@ -540,7 +558,7 @@ class MatrixDefinition: with reraise(KeyError, EntryNotFound(err_message)): value = self.normalized_iterable[key] - temp_dict = {self.pair.value: value} + temp_dict = {self.pair.key: key, self.pair.value: value} with self.context.set_temporarily(temp_dict, reserve=True): # optimization: item and key can be removed on __exit__() as they # are top-level values, and are not merged recursively. diff --git a/tests/func/parsing/test_matrix.py b/tests/func/parsing/test_matrix.py index eb9a45a42..a35085a6d 100644 --- a/tests/func/parsing/test_matrix.py +++ b/tests/func/parsing/test_matrix.py @@ -50,3 +50,47 @@ def test_matrix_interpolated(tmp_dir, dvc, matrix): "build@linux-3.8-dict1-list0": {"cmd": "echo linux 3.8 --arg2 2 -- out1 out11"}, "build@linux-3.8-dict1-list1": {"cmd": "echo linux 3.8 --arg2 2 -- out2 out22"}, } + + +@pytest.mark.parametrize( + "matrix", + [ + MATRIX_DATA, + { + "os": "${os}", + "pyv": "${pyv}", + "dict": "${dict}", + "list": "${list}", + }, + ], +) +def test_matrix_key_present(tmp_dir, dvc, matrix): + (tmp_dir / "params.yaml").dump(MATRIX_DATA) + resolver = DataResolver(dvc, tmp_dir.fs_path, {}) + data = { + "matrix": matrix, + "cmd": "echo ${key}", + } + definition = MatrixDefinition(resolver, resolver.context, "build", data) + + assert definition.resolve_all() == { + f"build@{key}": {"cmd": f"echo {key}"} + for key in ( + "win-3.7-dict0-list0", + "win-3.7-dict0-list1", + "win-3.7-dict1-list0", + "win-3.7-dict1-list1", + "win-3.8-dict0-list0", + "win-3.8-dict0-list1", + "win-3.8-dict1-list0", + "win-3.8-dict1-list1", + "linux-3.7-dict0-list0", + "linux-3.7-dict0-list1", + "linux-3.7-dict1-list0", + "linux-3.7-dict1-list1", + "linux-3.8-dict0-list0", + "linux-3.8-dict0-list1", + "linux-3.8-dict1-list0", + "linux-3.8-dict1-list1", + ) + }