exp show: Allow for wildcard patterns in include and exclude params/m… (#5720)

* exp show: Allow for wildcard patterns in include and exclude params/metrics

Fixes #5642

* Add test case for nested metrics/params

* match full string in _filter_name

* pre-commit

* Update dvc/command/experiments.py

Co-authored-by: Peter Rowlands (변기호) <[email protected]>

Co-authored-by: Peter Rowlands (변기호) <[email protected]>
This commit is contained in:
David de la Iglesia Castro
2021-04-05 20:52:29 +09:00
committed by GitHub
co-authored by Peter Rowlands
parent 1a995eafce
commit c634577463
2 changed files with 155 additions and 53 deletions
+11 -21
View File
@@ -3,7 +3,7 @@ import logging
from collections import Counter, OrderedDict, defaultdict
from collections.abc import Mapping
from datetime import date, datetime
from itertools import groupby
from fnmatch import fnmatch
from typing import Dict, Iterable, Optional
import dvc.prompt as prompt
@@ -27,32 +27,23 @@ def _filter_name(names, label, filter_strs):
for filter_s in filter_strs:
path, _, name = filter_s.rpartition(":")
path_filters[path].append(tuple(name.split(".")))
path_filters[path].append(name)
for path, filters in path_filters.items():
if path:
match_paths = [path]
else:
match_paths = names.keys()
for length, groups in groupby(filters, len):
for group in groups:
for match_path in match_paths:
possible_names = [
tuple(name.split(".")) for name in names[match_path]
]
matches = [
name
for name in possible_names
if name[:length] == group
]
if not matches:
name = ".".join(group)
raise InvalidArgumentError(
f"'{name}' does not match any known {label}"
)
ret[match_path].update(
{".".join(match): None for match in matches}
for match_path in match_paths:
for f in filters:
matches = [
name for name in names[match_path] if fnmatch(name, f)
]
if not matches:
raise InvalidArgumentError(
f"'{f}' does not match any known {label}"
)
ret[match_path].update({match: None for match in matches})
return ret
@@ -103,7 +94,6 @@ def _collect_names(all_experiments, **kwargs):
for exp in experiments.values():
_update_names(metric_names, exp.get("metrics", {}).items())
_update_names(param_names, exp.get("params", {}).items())
metric_names = _filter_names(
metric_names,
"metrics",
+144 -32
View File
@@ -5,8 +5,11 @@ from datetime import datetime
import pytest
from funcy import first
from dvc.exceptions import InvalidArgumentError
from dvc.main import main
from dvc.repo.experiments.base import ExpRefInfo
from dvc.utils.serialize import dump_yaml
from tests.func.test_repro_multistage import COPY_SCRIPT
def test_show_simple(tmp_dir, scm, dvc, exp_stage):
@@ -157,47 +160,156 @@ def test_show_checkpoint_branch(
assert f"({branch_rev[:7]})" in cap.out
def test_show_filter(tmp_dir, scm, dvc, exp_stage, capsys):
@pytest.mark.parametrize(
"i_metrics,i_params,e_metrics,e_params,included,excluded",
[
(
"foo",
"foo",
None,
None,
["foo"],
["bar", "train/foo", "nested.foo"],
),
(
None,
None,
"foo",
"foo",
["bar", "train/foo", "nested.foo"],
["foo"],
),
(
"foo,bar",
"foo,bar",
None,
None,
["foo", "bar"],
["train/foo", "train/bar", "nested.foo", "nested.bar"],
),
(
"metrics.yaml:foo,bar",
"params.yaml:foo,bar",
None,
None,
["foo", "bar"],
["train/foo", "train/bar", "nested.foo", "nested.bar"],
),
(
"train/*",
"train/*",
None,
None,
["train/foo", "train/bar"],
["foo", "bar", "nested.foo", "nested.bar"],
),
(
None,
None,
"train/*",
"train/*",
["foo", "bar", "nested.foo", "nested.bar"],
["train/foo", "train/bar"],
),
(
"train/*",
"train/*",
"*foo",
"*foo",
["train/bar"],
["train/foo", "foo", "bar", "nested.foo", "nested.bar"],
),
(
"nested.*",
"nested.*",
None,
None,
["nested.foo", "nested.bar"],
["foo", "bar", "train/foo", "train/bar"],
),
(
None,
None,
"nested.*",
"nested.*",
["foo", "bar", "train/foo", "train/bar"],
["nested.foo", "nested.bar"],
),
(
"*.*",
"*.*",
"*.bar",
"*.bar",
["nested.foo"],
["foo", "bar", "nested.bar", "train/foo", "train/bar"],
),
],
)
def test_show_filter(
tmp_dir,
scm,
dvc,
capsys,
i_metrics,
i_params,
e_metrics,
e_params,
included,
excluded,
):
capsys.readouterr()
assert (
main(
[
"exp",
"show",
"--no-pager",
"--no-timestamp",
"--include-metrics=foo",
"--include-params=foo",
]
)
== 0
)
cap = capsys.readouterr()
div = "│" if os.name == "nt" else "┃"
assert f"{div} foo {div} foo {div}" in cap.out
assert (
main(
[
"exp",
"show",
"--no-pager",
"--no-timestamp",
"--exclude-metrics=foo",
"--exclude-params=foo",
]
)
== 0
tmp_dir.gen("copy.py", COPY_SCRIPT)
params_file = tmp_dir / "params.yaml"
params_data = {
"foo": 1,
"bar": 1,
"train/foo": 1,
"train/bar": 1,
"nested": {"foo": 1, "bar": 1},
}
dump_yaml(params_file, params_data)
dvc.run(
cmd="python copy.py params.yaml metrics.yaml",
metrics_no_cache=["metrics.yaml"],
params=["foo"],
name="copy-file",
deps=["copy.py"],
)
scm.add(
[
"dvc.yaml",
"dvc.lock",
"copy.py",
"params.yaml",
"metrics.yaml",
".gitignore",
]
)
scm.commit("init")
command = ["exp", "show", "--no-pager", "--no-timestamp"]
if i_metrics is not None:
command.append(f"--include-metrics={i_metrics}")
if i_params is not None:
command.append(f"--include-params={i_params}")
if e_metrics is not None:
command.append(f"--exclude-metrics={e_metrics}")
if e_params is not None:
command.append(f"--exclude-params={e_params}")
assert main(command) == 0
cap = capsys.readouterr()
assert f"{div} foo {div}" not in cap.out
for i in included:
assert f"{div} {i} {div}" in cap.out
for e in excluded:
assert f"{div} {e} {div}" not in cap.out
def test_show_multiple_commits(tmp_dir, scm, dvc, exp_stage):
from dvc.exceptions import InvalidArgumentError
init_rev = scm.get_rev()
tmp_dir.scm_gen("file", "file", "commit")
next_rev = scm.get_rev()