Implement -R option for dvc metrics show (#1543)

This commit is contained in:
Thibault de Boissiere
2019-02-02 05:18:06 +02:00
committed by Ruslan Kuprieiev
parent 413b938160
commit 70a16b2dec
3 changed files with 134 additions and 25 deletions
+11 -1
View File
@@ -34,6 +34,7 @@ class CmdMetricsShow(CmdBase):
xpath=xpath,
all_branches=self.args.all_branches,
all_tags=self.args.all_tags,
recursive=self.args.recursive,
)
except DvcException:
logger.error("failed to show metrics")
@@ -98,7 +99,9 @@ def add_parser(subparsers, parent_parser):
description=METRICS_SHOW_HELP,
help=METRICS_SHOW_HELP,
)
metrics_show_parser.add_argument("path", nargs="?", help="Path to metrics file.")
metrics_show_parser.add_argument(
"path", nargs="?", help="Path to metrics file or directory"
)
metrics_show_parser.add_argument(
"-t", "--type", help="Type of metrics(RAW/JSON/TSV/HTSV/CSV/HCSV)."
)
@@ -133,6 +136,13 @@ def add_parser(subparsers, parent_parser):
default=False,
help="Show metrics for all tags.",
)
metrics_show_parser.add_argument(
"-R",
"--recursive",
action="store_true",
default=False,
help="If path is a directory, recursively search and process metric files in path.",
)
metrics_show_parser.set_defaults(func=CmdMetricsShow)
METRICS_ADD_HELP = "Add metrics."
+64 -24
View File
@@ -1020,13 +1020,13 @@ class Project(object):
elif typ == "htsv":
ret = self._read_metric_hxsv(fd, xpath, "\t")
else:
ret = fd.read()
ret = fd.read().strip()
except Exception:
logger.error("unable to read metric in '{}'".format(path))
return ret
def _find_output_by_path(self, path, outs=None):
def _find_output_by_path(self, path, outs=None, recursive=False):
from dvc.exceptions import OutputDuplicationError
if not outs:
@@ -1034,37 +1034,62 @@ class Project(object):
outs = [out for stage in astages for out in stage.outs]
abs_path = os.path.abspath(path)
matched = [out for out in outs if out.path == abs_path]
stages = [out.stage.relpath for out in matched]
if len(stages) > 1:
raise OutputDuplicationError(path, stages)
if os.path.isdir(abs_path) and recursive:
matched = [
out for out in outs if os.path.abspath(out.path).startswith(abs_path)
]
else:
matched = [out for out in outs if out.path == abs_path]
stages = [out.stage.relpath for out in matched]
if len(stages) > 1:
raise OutputDuplicationError(path, stages)
return matched[0] if matched else None
return matched if matched else []
def metrics_show(
self, path=None, typ=None, xpath=None, all_branches=False, all_tags=False
self,
path=None,
typ=None,
xpath=None,
all_branches=False,
all_tags=False,
recursive=False,
):
res = {}
for branch in self.scm.brancher(all_branches=all_branches, all_tags=all_tags):
astages = self.active_stages()
outs = [out for stage in astages for out in stage.outs]
if path:
out = self._find_output_by_path(path, outs=outs)
stage = out.stage.path if out else None
if out and all([out.metric, not typ, isinstance(out.metric, dict)]):
entries = [
(
path,
out.metric.get(out.PARAM_METRIC_TYPE, None),
out.metric.get(out.PARAM_METRIC_XPATH, None),
outs = self._find_output_by_path(path, outs=outs, recursive=recursive)
stages = [out.stage.path for out in outs]
entries = []
for out in outs:
if all([out.metric, not typ, isinstance(out.metric, dict)]):
entries += [
(
out.path,
out.metric.get(out.PARAM_METRIC_TYPE, None),
out.metric.get(out.PARAM_METRIC_XPATH, None),
)
]
else:
entries += [(out.path, typ, xpath)]
if not entries:
if os.path.isdir(path):
logger.warning(
"Path '{path}' is a directory. "
"Consider runnig with '-R'.".format(path=path)
)
]
else:
entries = [(path, typ, xpath)]
return {}
else:
entries += [(path, typ, xpath)]
else:
metrics = filter(lambda o: o.metric, outs)
stage = None
stages = None
entries = []
for o in metrics:
if not typ and isinstance(o.metric, dict):
@@ -1076,8 +1101,9 @@ class Project(object):
entries.append((o.path, t, x))
for fname, t, x in entries:
if stage:
self.checkout(stage, force=True)
if stages:
for stage in stages:
self.checkout(stage, force=True)
rel = os.path.relpath(fname)
metric = self._read_metric(fname, typ=t, xpath=x)
@@ -1098,6 +1124,10 @@ class Project(object):
if res:
return res
if path and os.path.isdir(path):
msg = "file '{}' does not exist".format(path)
return res
if path:
msg = "file '{}' does not exist".format(path)
else:
@@ -1109,11 +1139,21 @@ class Project(object):
raise DvcException(msg)
def _metrics_modify(self, path, typ=None, xpath=None, delete=False):
out = self._find_output_by_path(path)
if not out:
outs = self._find_output_by_path(path)
if not outs:
msg = "unable to find file '{}' in the pipeline".format(path)
raise DvcException(msg)
if len(outs) != 1:
msg = (
"-R not yet supported for metrics modify. "
"Make sure only one metric is referred to by '{}'".format(path)
)
raise DvcException(msg)
out = outs[0]
if out.scheme != "local":
msg = "output '{}' scheme '{}' is not supported for metrics"
raise DvcException(msg.format(out.path, out.path_info["scheme"]))
+59
View File
@@ -97,6 +97,65 @@ class TestMetrics(TestDvc):
self.assertTrue(ret["baz"]["metric_hcsv"] == ["baz"])
class TestMetricsRecursive(TestDvc):
def setUp(self):
super(TestMetricsRecursive, self).setUp()
self.dvc.scm.commit("init")
self.dvc.scm.checkout("nested", create_new=True)
os.mkdir("nested")
os.mkdir(os.path.join("nested", "subnested"))
ret = main(
[
"run",
"-M",
os.path.join("nested", "metric_nested"),
"echo",
"nested",
">>",
os.path.join("nested", "metric_nested"),
]
)
self.assertEqual(ret, 0)
ret = main(
[
"run",
"-M",
os.path.join("nested", "subnested", "metric_subnested"),
"echo",
"subnested",
">>",
os.path.join("nested", "subnested", "metric_subnested"),
]
)
self.assertEqual(ret, 0)
self.dvc.scm.add(["nested"])
self.dvc.scm.commit("nested metrics")
self.dvc.scm.checkout("master")
def test(self):
ret = self.dvc.metrics_show("nested", all_branches=True, recursive=False)
self.assertEqual(len(ret), 0)
ret = self.dvc.metrics_show("nested", all_branches=True, recursive=True)
self.assertEqual(len(ret), 1)
self.assertEqual(
ret["nested"][os.path.join("nested", "subnested", "metric_subnested")],
"subnested",
)
self.assertEqual(
ret["nested"][os.path.join("nested", "metric_nested")], "nested"
)
class TestMetricsReproCLI(TestDvc):
def test(self):
stage = self.dvc.run(