exp show: handle renaming of metrics/params.

_extend_row did not considered partially empty `items`, which can occur when metris or params are renamed between commits.

Fixes #7070
This commit is contained in:
David de la Iglesia Castro
2022-02-09 21:40:15 +01:00
parent b6fa1178e7
commit d89555cd31
2 changed files with 106 additions and 37 deletions
+35 -27
View File
@@ -65,6 +65,9 @@ experiment_types = {
def _collect_rows(
base_rev,
experiments,
all_headers,
metric_headers,
param_headers,
metric_names,
param_names,
deps_names,
@@ -87,14 +90,18 @@ def _collect_rows(
new_checkpoint = True
for i, (rev, results) in enumerate(experiments.items()):
fill_value = FILL_VALUE_ERRORED if results.get("error") else fill_value
row_dict = {k: fill_value for k in all_headers}
exp = results.get("data", {})
if exp.get("running"):
state = "Running"
elif exp.get("queued"):
state = "Queued"
else:
state = fill_value
executor = exp.get("executor", fill_value)
is_baseline = rev == "baseline"
if is_baseline:
@@ -102,9 +109,7 @@ def _collect_rows(
else:
name_rev = rev[:7]
exp_name = exp.get("name", "")
tip = exp.get("checkpoint_tip")
parent_rev = exp.get("checkpoint_parent", "")
parent_exp = experiments.get(parent_rev, {}).get("data", {})
parent_tip = parent_exp.get("checkpoint_tip")
@@ -130,26 +135,28 @@ def _collect_rows(
if not is_baseline:
new_checkpoint = not (tip and tip == parent_tip)
row = [
exp_name,
name_rev,
typ,
_format_time(exp.get("timestamp"), fill_value, iso),
parent,
state,
executor,
]
fill_value = FILL_VALUE_ERRORED if results.get("error") else fill_value
row_dict["Experiment"] = exp.get("name", "")
row_dict["rev"] = name_rev
row_dict["typ"] = typ
row_dict["Created"] = _format_time(
exp.get("timestamp"), fill_value, iso
)
row_dict["parent"] = parent
row_dict["State"] = state
row_dict["Executor"] = exp.get("executor", fill_value)
_extend_row(
row,
row_dict,
metric_names,
metric_headers,
exp.get("metrics", {}).items(),
precision,
fill_value=fill_value,
)
_extend_row(
row,
row_dict,
param_names,
param_headers,
exp.get("params", {}).items(),
precision,
fill_value=fill_value,
@@ -158,8 +165,8 @@ def _collect_rows(
hash_info = exp.get("deps", {}).get(dep, {}).get("hash")
if hash_info is not None:
hash_info = hash_info[:7]
row.append(hash_info or fill_value)
yield row
row_dict[dep] = hash_info
yield list(row_dict.values())
def _sort_column(sort_by, metric_names, param_names):
@@ -225,13 +232,9 @@ def _format_time(datetime_obj, fill_value=FILL_VALUE, iso=False):
return datetime_obj.strftime(fmt)
def _extend_row(row, names, items, precision, fill_value=FILL_VALUE):
def _extend_row(row, names, headers, items, precision, fill_value=FILL_VALUE):
from dvc.compare import _format_field, with_value
if not items:
row.extend(fill_value for keys in names.values() for _ in keys)
return
for fname, data in items:
item = data.get("data", {})
item = flatten(item) if isinstance(item, dict) else {fname: item}
@@ -243,7 +246,11 @@ def _extend_row(row, names, items, precision, fill_value=FILL_VALUE):
# wrap field data in ui.rich_text, otherwise rich may
# interpret unescaped braces from list/dict types as rich
# markup tags
row.append(ui.rich_text(str(_format_field(value, precision))))
value = ui.rich_text(str(_format_field(value, precision)))
if name in headers:
row[name] = value
else:
row[f"{fname}:{name}"] = value
def experiments_table(
@@ -264,14 +271,15 @@ def experiments_table(
from dvc.compare import TabularData
td = TabularData(
lconcat(headers, metric_headers, param_headers, deps_names),
fill_value=fill_value,
)
all_headers = lconcat(headers, metric_headers, param_headers, deps_names)
td = TabularData(all_headers, fill_value=fill_value)
for base_rev, experiments in all_experiments.items():
rows = _collect_rows(
base_rev,
experiments,
all_headers,
metric_headers,
param_headers,
metric_names,
param_names,
deps_names,
+71 -10
View File
@@ -640,16 +640,7 @@ def test_show_outs(tmp_dir, dvc, scm):
deps=["copy.py"],
outs=["out"],
)
scm.add(
[
"dvc.yaml",
"dvc.lock",
"copy.py",
"params.yaml",
"metrics.yaml",
".gitignore",
]
)
scm.commit("init")
outs = dvc.experiments.show()["workspace"]["baseline"]["data"]["outs"]
@@ -662,6 +653,76 @@ def test_show_outs(tmp_dir, dvc, scm):
}
def test_metrics_renaming(tmp_dir, dvc, scm, capsys):
tmp_dir.gen("copy.py", COPY_SCRIPT)
params_file = tmp_dir / "params.yaml"
params_data = {
"foo": 1,
}
(tmp_dir / params_file).dump(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("metrics.yaml")
metrics_rev = scm.get_rev()
dvc.run(
cmd="python copy.py params.yaml scores.yaml",
metrics_no_cache=["scores.yaml"],
params=["foo"],
name="copy-file",
deps=["copy.py"],
)
scm.add(
[
"dvc.yaml",
"dvc.lock",
"params.yaml",
"scores.yaml",
]
)
scm.commit("scores.yaml")
scores_rev = scm.get_rev()
capsys.readouterr()
assert main(["exp", "show", "--csv", "-A"]) == 0
cap = capsys.readouterr()
def _get_rev_isotimestamp(rev):
return datetime.fromtimestamp(
scm.gitpython.repo.rev_parse(rev).committed_date
).isoformat()
assert (
"master,{},baseline,{},,1,,1".format(
scores_rev[:7], _get_rev_isotimestamp(scores_rev)
)
in cap.out
)
assert (
",{},baseline,{},,,1,1".format(
metrics_rev[:7], _get_rev_isotimestamp(metrics_rev)
)
in cap.out
)
def test_show_sorted_deps(tmp_dir, dvc, scm, capsys):
tmp_dir.gen("a", "a")
tmp_dir.gen("b", "b")