Merge pull request #933 from efiop/master

metrics: specify xpath in stage files
This commit is contained in:
Ruslan Kuprieiev
2018-07-23 00:45:12 +03:00
committed by GitHub
6 changed files with 189 additions and 51 deletions
+32 -2
View File
@@ -19,8 +19,10 @@ from dvc.command.add import CmdAdd
from dvc.command.imp import CmdImport
from dvc.command.config import CmdConfig
from dvc.command.checkout import CmdCheckout
from dvc.command.remote import CmdRemoteAdd, CmdRemoteRemove, CmdRemoteModify, CmdRemoteList
from dvc.command.metrics import CmdMetricsShow, CmdMetricsAdd, CmdMetricsRemove
from dvc.command.remote import CmdRemoteAdd, CmdRemoteRemove
from dvc.command.remote import CmdRemoteModify, CmdRemoteList
from dvc.command.metrics import CmdMetricsShow, CmdMetricsAdd
from dvc.command.metrics import CmdMetricsRemove, CmdMetricsModify
from dvc.command.install import CmdInstall
from dvc.command.root import CmdRoot
from dvc.command.lock import CmdLock, CmdUnlock
@@ -477,6 +479,12 @@ def parse_args(argv=None):
'path',
nargs='?',
help='Path to metrics file')
metrics_show_parser.add_argument('-t',
'--type',
help='Type of metrics(RAW/JSON/TSV/HTSV/CSV/HCSV)')
metrics_show_parser.add_argument('-x',
'--xpath',
help='JSON/TSV/HTSV/CSV/HCSV path')
metrics_show_parser_group = metrics_show_parser.add_mutually_exclusive_group()
metrics_show_parser_group.add_argument(
'--json-path',
@@ -505,12 +513,34 @@ def parse_args(argv=None):
'add',
parents=[parent_parser],
help='Add metrics')
metrics_add_parser.add_argument('-t',
'--type',
help='Type of metrics(RAW/JSON/TSV/HTSV/CSV/HCSV)')
metrics_add_parser.add_argument('-x',
'--xpath',
help='JSON/TSV/HTSV/CSV/HCSV path')
metrics_add_parser.add_argument(
'path',
help='Path to metrics file')
metrics_add_parser.set_defaults(func=CmdMetricsAdd)
metrics_modify_parser = metrics_subparsers.add_parser(
'modify',
parents=[parent_parser],
help='Modify metrics')
metrics_modify_parser.add_argument('-t',
'--type',
help='Type of metrics(RAW/JSON/TSV/HTSV/CSV/HCSV)')
metrics_modify_parser.add_argument('-x',
'--xpath',
help='JSON/TSV/HTSV/CSV/HCSV path')
metrics_modify_parser.add_argument(
'path',
help='Metrics file')
metrics_modify_parser.set_defaults(func=CmdMetricsModify)
metrics_remove_parser = metrics_subparsers.add_parser(
'remove',
parents=[parent_parser],
+38 -6
View File
@@ -5,12 +5,29 @@ from dvc.exceptions import DvcException
class CmdMetricsShow(CmdBase):
def run(self):
try:
# backward compatibility
if self.args.json_path:
typ = 'json'
xpath = self.args.json_path
elif self.args.tsv_path:
typ = 'tsv'
xpath = self.args.tsv_path
elif self.args.htsv_path:
typ = 'htsv'
xpath = self.args.htsv_path
elif self.args.csv_path:
typ = 'csv'
xpath = self.args.csv_path
elif self.args.hcsv_path:
typ = 'hcsv'
xpath = self.args.hcsv_path
else:
typ = self.args.type
xpath = self.args.xpath
self.project.metrics_show(self.args.path,
json_path=self.args.json_path,
tsv_path=self.args.tsv_path,
htsv_path=self.args.htsv_path,
csv_path=self.args.csv_path,
hcsv_path=self.args.hcsv_path,
typ=typ,
xpath=xpath,
all_branches=self.args.all_branches)
except DvcException as exc:
self.project.logger.error('Failed to show metrics', exc)
@@ -19,10 +36,25 @@ class CmdMetricsShow(CmdBase):
return 0
class CmdMetricsModify(CmdBase):
def run(self):
try:
self.project.metrics_modify(self.args.path,
typ=self.args.type,
xpath=self.args.xpath)
except DvcException as exc:
self.project.logger.error('Failed to modify metrics', exc)
return 1
return 0
class CmdMetricsAdd(CmdBase):
def run(self):
try:
self.project.metrics_add(self.args.path)
self.project.metrics_add(self.args.path,
self.args.type,
self.args.xpath)
except DvcException as exc:
msg = 'Failed to add metric file \'{}\''.format(self.args.path)
self.project.logger.error(msg, exc)
+1 -1
View File
@@ -23,7 +23,7 @@ OUTS_MAP = {'hdfs': OutputHDFS,
'': OutputLOCAL}
SCHEMA[schema.Optional(OutputLOCAL.PARAM_CACHE)] = bool
SCHEMA[schema.Optional(OutputLOCAL.PARAM_METRIC)] = bool
SCHEMA[schema.Optional(OutputLOCAL.PARAM_METRIC)] = OutputLOCAL.METRIC_SCHEMA
def _get(stage, p, info, cache, metric):
+15 -1
View File
@@ -14,6 +14,12 @@ from dvc.istextfile import istextfile
class OutputLOCAL(DependencyLOCAL):
PARAM_CACHE = 'cache'
PARAM_METRIC = 'metric'
PARAM_METRIC_TYPE = 'type'
PARAM_METRIC_XPATH = 'xpath'
METRIC_SCHEMA = schema.Or(None, bool,
{schema.Optional(PARAM_METRIC_TYPE): schema.Or(str, None),
schema.Optional(PARAM_METRIC_XPATH): schema.Or(str, None)})
DoesNotExistError = OutputDoesNotExistError
IsNotFileOrDirError = OutputIsNotFileOrDirError
@@ -38,7 +44,15 @@ class OutputLOCAL(DependencyLOCAL):
def dumpd(self):
ret = super(OutputLOCAL, self).dumpd()
ret[self.PARAM_CACHE] = self.use_cache
ret[self.PARAM_METRIC] = self.metric
if isinstance(self.metric, dict):
if self.PARAM_METRIC_XPATH in self.metric and \
not self.metric[self.PARAM_METRIC_XPATH]:
del self.metric[self.PARAM_METRIC_XPATH]
if self.metric:
ret[self.PARAM_METRIC] = self.metric
return ret
def changed(self):
+52 -34
View File
@@ -426,11 +426,7 @@ class Project(object):
reader = list(csv.reader(fd, delimiter=delimiter))
return self._do_read_metric_xsv(reader, row, col)
def _read_metric(self, path, json_path=None,
tsv_path=None,
htsv_path=None,
csv_path=None,
hcsv_path=None):
def _read_metric(self, path, typ=None, xpath=None):
ret = None
if not os.path.exists(path):
@@ -438,16 +434,16 @@ class Project(object):
try:
with open(path, 'r') as fd:
if json_path:
ret = self._read_metric_json(fd, json_path)
elif csv_path:
ret = self._read_metric_xsv(fd, csv_path, ',')
elif tsv_path:
ret = self._read_metric_xsv(fd, tsv_path, '\t')
elif hcsv_path:
ret = self._read_metric_hxsv(fd, hcsv_path, ',')
elif htsv_path:
ret = self._read_metric_hxsv(fd, htsv_path, '\t')
if typ == 'json':
ret = self._read_metric_json(fd, xpath)
elif typ == 'csv':
ret = self._read_metric_xsv(fd, xpath, ',')
elif typ == 'tsv':
ret = self._read_metric_xsv(fd, xpath, '\t')
elif typ == 'hcsv':
ret = self._read_metric_hxsv(fd, xpath, ',')
elif typ == 'htsv':
ret = self._read_metric_hxsv(fd, xpath, '\t')
else:
ret = fd.read()
except Exception as exc:
@@ -470,12 +466,7 @@ class Project(object):
return matched[0] if matched else None
def metrics_show(self, path=None, json_path=None,
tsv_path=None,
htsv_path=None,
csv_path=None,
hcsv_path=None,
all_branches=False):
def metrics_show(self, path=None, typ=None, xpath=None, all_branches=False):
res = {}
for branch in self.scm.brancher(all_branches=all_branches):
outs = [out for stage in self.active_stages() for out in stage.outs]
@@ -483,23 +474,33 @@ class Project(object):
if path:
out = self._find_output_by_path(path, outs=outs)
stage = out.stage.path if out else None
fnames = [path]
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))]
else:
entries = [(path, typ, xpath)]
else:
metrics = filter(lambda o: o.metric, outs)
stage = None
fnames = map(lambda o: o.path, metrics)
entries = []
for o in metrics:
if not typ and isinstance(o.metric, dict):
t = o.metric.get(o.PARAM_METRIC_TYPE, None)
x = o.metric.get(o.PARAM_METRIC_XPATH, None)
else:
t = typ
x = xpath
entries.append((o.path, t, x))
for fname in fnames:
for fname, t, x in entries:
if stage:
self.checkout(stage)
rel = os.path.relpath(fname)
metric = self._read_metric(fname,
json_path=json_path,
tsv_path=tsv_path,
htsv_path=htsv_path,
csv_path=csv_path,
hcsv_path=hcsv_path)
typ=t,
xpath=x)
if not metric:
continue
@@ -524,7 +525,7 @@ class Project(object):
'Use \'dvc metrics add\' to add a metric file to track.'
raise DvcException(msg)
def _metrics_modify(self, path, val):
def _metrics_modify(self, path, typ=None, xpath=None, delete=False):
found = False
out = self._find_output_by_path(path)
if not out:
@@ -539,16 +540,33 @@ class Project(object):
msg = 'Cached output \'{}\' is not supported for metrics'
raise DvcException(msg.format(out.rel_path))
out.metric = val
if typ:
if not isinstance(out.metric, dict):
out.metric = {}
out.metric[out.PARAM_METRIC_TYPE] = typ
if xpath:
if not isinstance(out.metric, dict):
out.metric = {}
out.metric[out.PARAM_METRIC_XPATH] = xpath
if delete:
out.metric = None
out._verify_metric()
out.stage.dump()
def metrics_add(self, path):
self._metrics_modify(path, True)
def metrics_modify(self, path=None, typ=None, xpath=None):
self._metrics_modify(path, typ, xpath)
def metrics_add(self, path, typ=None, xpath=None):
if not typ:
typ = 'raw'
self._metrics_modify(path, typ, xpath)
def metrics_remove(self, path):
self._metrics_modify(path, False)
self._metrics_modify(path, delete=True)
def graph(self):
import networkx as nx
+51 -7
View File
@@ -47,31 +47,46 @@ class TestMetrics(TestDvc):
self.assertTrue(ret['bar']['metric'] == 'bar')
self.assertTrue(ret['baz']['metric'] == 'baz')
ret = self.dvc.metrics_show('metric_json', json_path='branch', all_branches=True)
ret = self.dvc.metrics_show('metric_json',
typ='json',
xpath='branch',
all_branches=True)
self.assertEqual(len(ret), 3)
self.assertTrue(ret['foo']['metric_json'] == ['foo'])
self.assertTrue(ret['bar']['metric_json'] == ['bar'])
self.assertTrue(ret['baz']['metric_json'] == ['baz'])
ret = self.dvc.metrics_show('metric_tsv', tsv_path='0,0', all_branches=True)
ret = self.dvc.metrics_show('metric_tsv',
typ='tsv',
xpath='0,0',
all_branches=True)
self.assertEqual(len(ret), 3)
self.assertTrue(ret['foo']['metric_tsv'] == ['foo'])
self.assertTrue(ret['bar']['metric_tsv'] == ['bar'])
self.assertTrue(ret['baz']['metric_tsv'] == ['baz'])
ret = self.dvc.metrics_show('metric_htsv', htsv_path='branch,0', all_branches=True)
ret = self.dvc.metrics_show('metric_htsv',
typ='htsv',
xpath='branch,0',
all_branches=True)
self.assertEqual(len(ret), 3)
self.assertTrue(ret['foo']['metric_htsv'] == ['foo'])
self.assertTrue(ret['bar']['metric_htsv'] == ['bar'])
self.assertTrue(ret['baz']['metric_htsv'] == ['baz'])
ret = self.dvc.metrics_show('metric_csv', csv_path='0,0', all_branches=True)
ret = self.dvc.metrics_show('metric_csv',
typ='csv',
xpath='0,0',
all_branches=True)
self.assertEqual(len(ret), 3)
self.assertTrue(ret['foo']['metric_csv'] == ['foo'])
self.assertTrue(ret['bar']['metric_csv'] == ['bar'])
self.assertTrue(ret['baz']['metric_csv'] == ['baz'])
ret = self.dvc.metrics_show('metric_hcsv', hcsv_path='branch,0', all_branches=True)
ret = self.dvc.metrics_show('metric_hcsv',
typ='hcsv',
xpath='branch,0',
all_branches=True)
self.assertEqual(len(ret), 3)
self.assertTrue(ret['foo']['metric_hcsv'] == ['foo'])
self.assertTrue(ret['bar']['metric_hcsv'] == ['bar'])
@@ -112,7 +127,7 @@ class TestMetricsReproCLI(TestDvc):
self.dvc.run(metrics_no_cache=['metrics_bin'])
class TestMetricsCLI(TestMetrics):
class TestMetricsCLICompat(TestMetrics):
def test(self):
#FIXME check output
ret = main(['metrics', 'show', '--all-branches', 'metric', '-v'])
@@ -133,6 +148,34 @@ class TestMetricsCLI(TestMetrics):
ret = main(['metrics', 'show', '--all-branches', 'metric_hcsv', '--hcsv-path', 'branch,0'])
self.assertEqual(ret, 0)
class TestMetricsCLI(TestMetrics):
def test(self):
#FIXME check output
ret = main(['metrics', 'show', '-a', 'metric', '-v'])
self.assertEqual(ret, 0)
ret = main(['metrics', 'show', '-a', 'metric_json', '-t', 'json',
'-x', 'branch'])
self.assertEqual(ret, 0)
ret = main(['metrics', 'show', '-a', 'metric_tsv', '-t', 'tsv',
'-x', '0,0'])
self.assertEqual(ret, 0)
ret = main(['metrics', 'show', '-a', 'metric_htsv', '-t', 'htsv',
'-x', 'branch,0'])
self.assertEqual(ret, 0)
ret = main(['metrics', 'show', '-a', 'metric_csv', '-t', 'csv',
'-x', '0,0'])
self.assertEqual(ret, 0)
ret = main(['metrics', 'show', '-a', 'metric_hcsv', '-t', 'hcsv',
'-x', 'branch,0'])
self.assertEqual(ret, 0)
def test_dir(self):
os.mkdir('metrics_dir')
@@ -190,7 +233,8 @@ class TestCachedMetrics(TestDvc):
res = self.dvc.metrics_show('metrics.json',
all_branches=True,
json_path='metrics')
typ='json',
xpath='metrics')
self.assertEqual(res, {"master": {"metrics.json": ["master"]},
"one": {"metrics.json": ["one"]},