Skip to content

Commit 45944dd

Browse files
authored
metrics: rework metrics implementation (#3658)
Dropping support for all types except json and yaml. No more xpath support. Fixes #3572
1 parent 52369bd commit 45944dd

File tree

17 files changed

+376
-1436
lines changed

17 files changed

+376
-1436
lines changed

dvc/command/metrics.py

Lines changed: 38 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,9 @@
1414
def show_metrics(
1515
metrics, all_branches=False, all_tags=False, all_commits=False
1616
):
17-
"""
18-
Args:
19-
metrics (list): Where each element is either a `list`
20-
if an xpath was specified, otherwise a `str`
21-
"""
17+
from flatten_json import flatten
18+
from dvc.utils.diff import format_dict
19+
2220
# When `metrics` contains a `None` key, it means that some files
2321
# specified as `targets` in `repo.metrics.show` didn't contain any metrics.
2422
missing = metrics.pop(None, None)
@@ -28,21 +26,13 @@ def show_metrics(
2826
logger.info("{branch}:".format(branch=branch))
2927

3028
for fname, metric in val.items():
31-
if isinstance(metric, dict):
32-
lines = list(metric.values())
33-
elif isinstance(metric, list):
34-
lines = metric
35-
else:
36-
lines = metric.splitlines()
29+
if not isinstance(metric, dict):
30+
logger.info("\t{}: {}".format(fname, str(metric)))
31+
continue
3732

38-
if len(lines) > 1:
39-
logger.info("\t{fname}:".format(fname=fname))
40-
41-
for line in lines:
42-
logger.info("\t\t{content}".format(content=line))
43-
44-
else:
45-
logger.info("\t{}: {}".format(fname, metric))
33+
logger.info("\t{}:".format(fname))
34+
for key, value in flatten(format_dict(metric), ".").items():
35+
logger.info("\t\t{}: {}".format(key, value))
4636

4737
if missing:
4838
raise BadMetricError(missing)
@@ -53,35 +43,25 @@ def run(self):
5343
try:
5444
metrics = self.repo.metrics.show(
5545
self.args.targets,
56-
typ=self.args.type,
57-
xpath=self.args.xpath,
5846
all_branches=self.args.all_branches,
5947
all_tags=self.args.all_tags,
6048
all_commits=self.args.all_commits,
6149
recursive=self.args.recursive,
6250
)
6351

64-
show_metrics(
65-
metrics,
66-
self.args.all_branches,
67-
self.args.all_tags,
68-
self.args.all_commits,
69-
)
70-
except DvcException:
71-
logger.exception("failed to show metrics")
72-
return 1
73-
74-
return 0
75-
52+
if self.args.show_json:
53+
import json
7654

77-
class CmdMetricsModify(CmdBase):
78-
def run(self):
79-
try:
80-
self.repo.metrics.modify(
81-
self.args.path, typ=self.args.type, xpath=self.args.xpath
82-
)
55+
logger.info(json.dumps(metrics))
56+
else:
57+
show_metrics(
58+
metrics,
59+
self.args.all_branches,
60+
self.args.all_tags,
61+
self.args.all_commits,
62+
)
8363
except DvcException:
84-
logger.exception("failed to modify metric file settings")
64+
logger.exception("failed to show metrics")
8565
return 1
8666

8767
return 0
@@ -90,9 +70,7 @@ def run(self):
9070
class CmdMetricsAdd(CmdBase):
9171
def run(self):
9272
try:
93-
self.repo.metrics.add(
94-
self.args.path, self.args.type, self.args.xpath
95-
)
73+
self.repo.metrics.add(self.args.path)
9674
except DvcException:
9775
msg = "failed to add metric file '{}'".format(self.args.path)
9876
logger.exception(msg)
@@ -114,11 +92,14 @@ def run(self):
11492

11593

11694
def _show_diff(diff):
95+
from collections import OrderedDict
96+
11797
from dvc.utils.diff import table
11898

11999
rows = []
120100
for fname, mdiff in diff.items():
121-
for metric, change in mdiff.items():
101+
sorted_mdiff = OrderedDict(sorted(mdiff.items()))
102+
for metric, change in sorted_mdiff.items():
122103
rows.append(
123104
[
124105
fname,
@@ -138,9 +119,8 @@ def run(self):
138119
a_rev=self.args.a_rev,
139120
b_rev=self.args.b_rev,
140121
targets=self.args.targets,
141-
typ=self.args.type,
142-
xpath=self.args.xpath,
143122
recursive=self.args.recursive,
123+
all=self.args.all,
144124
)
145125

146126
if self.args.show_json:
@@ -185,32 +165,9 @@ def add_parser(subparsers, parent_parser):
185165
help=METRICS_ADD_HELP,
186166
formatter_class=argparse.RawDescriptionHelpFormatter,
187167
)
188-
metrics_add_parser.add_argument(
189-
"-t", "--type", help="Type of metrics (json/yaml).", metavar="<type>",
190-
)
191-
metrics_add_parser.add_argument(
192-
"-x", "--xpath", help="json/yaml path.", metavar="<path>",
193-
)
194168
metrics_add_parser.add_argument("path", help="Path to a metric file.")
195169
metrics_add_parser.set_defaults(func=CmdMetricsAdd)
196170

197-
METRICS_MODIFY_HELP = "Modify metric default formatting."
198-
metrics_modify_parser = metrics_subparsers.add_parser(
199-
"modify",
200-
parents=[parent_parser],
201-
description=append_doc_link(METRICS_MODIFY_HELP, "metrics/modify"),
202-
help=METRICS_MODIFY_HELP,
203-
formatter_class=argparse.RawDescriptionHelpFormatter,
204-
)
205-
metrics_modify_parser.add_argument(
206-
"-t", "--type", help="Type of metrics (json/yaml).", metavar="<type>",
207-
)
208-
metrics_modify_parser.add_argument(
209-
"-x", "--xpath", help="json/yaml path.", metavar="<path>",
210-
)
211-
metrics_modify_parser.add_argument("path", help="Path to a metric file.")
212-
metrics_modify_parser.set_defaults(func=CmdMetricsModify)
213-
214171
METRICS_SHOW_HELP = "Print metrics, with optional formatting."
215172
metrics_show_parser = metrics_subparsers.add_parser(
216173
"show",
@@ -224,19 +181,6 @@ def add_parser(subparsers, parent_parser):
224181
nargs="*",
225182
help="Metric files or directories (see -R) to show",
226183
)
227-
metrics_show_parser.add_argument(
228-
"-t",
229-
"--type",
230-
help=(
231-
"Type of metrics (json/yaml). "
232-
"It can be detected by the file extension automatically. "
233-
"Unsupported types will be treated as raw."
234-
),
235-
metavar="<type>",
236-
)
237-
metrics_show_parser.add_argument(
238-
"-x", "--xpath", help="json/yaml path.", metavar="<path>",
239-
)
240184
metrics_show_parser.add_argument(
241185
"-a",
242186
"--all-branches",
@@ -267,6 +211,12 @@ def add_parser(subparsers, parent_parser):
267211
"metric files."
268212
),
269213
)
214+
metrics_show_parser.add_argument(
215+
"--show-json",
216+
action="store_true",
217+
default=False,
218+
help="Show output in JSON format.",
219+
)
270220
metrics_show_parser.set_defaults(func=CmdMetricsShow)
271221

272222
METRICS_DIFF_HELP = "Show changes in metrics between commits"
@@ -295,19 +245,6 @@ def add_parser(subparsers, parent_parser):
295245
),
296246
metavar="<paths>",
297247
)
298-
metrics_diff_parser.add_argument(
299-
"-t",
300-
"--type",
301-
help=(
302-
"Type of metrics (json/yaml). "
303-
"It can be detected by the file extension automatically. "
304-
"Unsupported types will be treated as raw."
305-
),
306-
metavar="<type>",
307-
)
308-
metrics_diff_parser.add_argument(
309-
"-x", "--xpath", help="json/yaml path.", metavar="<path>",
310-
)
311248
metrics_diff_parser.add_argument(
312249
"-R",
313250
"--recursive",
@@ -318,6 +255,12 @@ def add_parser(subparsers, parent_parser):
318255
"metric files."
319256
),
320257
)
258+
metrics_diff_parser.add_argument(
259+
"--all",
260+
action="store_true",
261+
default=False,
262+
help="Show unchanged metrics as well.",
263+
)
321264
metrics_diff_parser.add_argument(
322265
"--show-json",
323266
action="store_true",

dvc/remote/local.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ def get(self, md5):
8787
return self.checksum_to_path_info(md5).url
8888

8989
def exists(self, path_info):
90+
assert is_working_tree(self.repo.tree)
9091
assert path_info.scheme == "local"
9192
return self.repo.tree.exists(fspath_py35(path_info))
9293

dvc/repo/metrics/__init__.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,6 @@ def add(self, *args, **kwargs):
77

88
return add(self.repo, *args, **kwargs)
99

10-
def modify(self, *args, **kwargs):
11-
from dvc.repo.metrics.modify import modify
12-
13-
return modify(self.repo, *args, **kwargs)
14-
1510
def show(self, *args, **kwargs):
1611
from dvc.repo.metrics.show import show
1712

dvc/repo/metrics/add.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
from dvc.repo.metrics.modify import modify
22

33

4-
def add(repo, path, typ=None, xpath=None):
5-
if not typ:
6-
typ = "raw"
7-
modify(repo, path, typ, xpath)
4+
def add(repo, path):
5+
modify(repo, path)

dvc/repo/metrics/diff.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import dvc.utils.diff
1+
from dvc.utils.diff import format_dict, diff as _diff
22
from dvc.exceptions import NoMetricsError
33

44

@@ -13,7 +13,11 @@ def _get_metrics(repo, *args, rev=None, **kwargs):
1313

1414

1515
def diff(repo, *args, a_rev=None, b_rev=None, **kwargs):
16+
with_unchanged = kwargs.pop("all", False)
17+
1618
old = _get_metrics(repo, *args, **kwargs, rev=(a_rev or "HEAD"))
1719
new = _get_metrics(repo, *args, **kwargs, rev=b_rev)
1820

19-
return dvc.utils.diff.diff(old, new)
21+
return _diff(
22+
format_dict(old), format_dict(new), with_unchanged=with_unchanged
23+
)

dvc/repo/metrics/modify.py

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44

55

66
@locked
7-
def modify(repo, path, typ=None, xpath=None, delete=False):
8-
supported_types = ["raw", "json", "csv", "tsv", "hcsv", "htsv"]
7+
def modify(repo, path, delete=False):
98
outs = repo.find_outs_by_path(path)
109
assert len(outs) == 1
1110
out = outs[0]
@@ -14,25 +13,6 @@ def modify(repo, path, typ=None, xpath=None, delete=False):
1413
msg = "output '{}' scheme '{}' is not supported for metrics"
1514
raise DvcException(msg.format(out.path, out.path_info.scheme))
1615

17-
if typ is not None:
18-
typ = typ.lower().strip()
19-
if typ not in ["raw", "json", "csv", "tsv", "hcsv", "htsv"]:
20-
msg = (
21-
"metric type '{typ}' is not supported, "
22-
"must be one of [{types}]"
23-
)
24-
raise DvcException(
25-
msg.format(typ=typ, types=", ".join(supported_types))
26-
)
27-
if not isinstance(out.metric, dict):
28-
out.metric = {}
29-
out.metric[out.PARAM_METRIC_TYPE] = typ
30-
31-
if xpath is not None:
32-
if not isinstance(out.metric, dict):
33-
out.metric = {}
34-
out.metric[out.PARAM_METRIC_XPATH] = xpath
35-
3616
if delete:
3717
out.metric = None
3818

0 commit comments

Comments
 (0)