plots/metrics: fix some communication disrepancies (#4837)

* plots/metrics: unified parsing errors communication

* plots/metrics: unify no metrics/plots messaging

* fixup

* fixup
This commit is contained in:
Paweł Redzyński
2020-11-07 02:56:14 +02:00
committed by GitHub
parent 4531aa34ca
commit f4ee0e16b4
9 changed files with 104 additions and 63 deletions
+1 -1
View File
@@ -72,7 +72,7 @@ class CmdPlots(CmdBase):
logger.info(f"file://{path}")
except DvcException:
logger.exception("")
logger.exception("failed to show plots")
return 1
return 0
+13 -4
View File
@@ -174,11 +174,20 @@ class NoMetricsError(DvcException):
pass
class NoPlotsError(DvcException):
def __init__(self):
class NoMetricsParsedError(NoMetricsError):
def __init__(self, command):
super().__init__(
"no plots in this repository. Use `--plots/--plots-no-cache` "
"options for `dvc run` to mark stage outputs as plots."
f"Could not parse {command} files. Use `-v` option to see more "
"details."
)
class NoMetricsFoundError(NoMetricsError):
def __init__(self, command, run_options):
super().__init__(
f"No {command} files in this repository. "
f"Use `{run_options}` options for "
f"`dvc run` to mark stage outputs as {command}."
)
+3 -10
View File
@@ -1,6 +1,6 @@
import logging
from dvc.exceptions import NoMetricsError
from dvc.exceptions import NoMetricsFoundError, NoMetricsParsedError
from dvc.repo import locked
from dvc.repo.collect import collect
from dvc.scm.base import SCMError
@@ -104,16 +104,9 @@ def show(
if not res:
if metrics_found:
msg = (
"Could not parse metrics files. Use `-v` option to see more "
"details."
)
raise NoMetricsParsedError("metrics")
else:
msg = (
"no metrics files in this repository. Use `-m/-M` options for "
"`dvc run` to mark stage outputs as metrics."
)
raise NoMetricsError(msg)
raise NoMetricsFoundError("metrics", "-m/-M")
# Hide workspace metrics if they are the same as in the active branch
try:
+25 -6
View File
@@ -2,8 +2,13 @@ import logging
from funcy import cached_property, first, project
from dvc.exceptions import DvcException, NoPlotsError
from dvc.exceptions import (
DvcException,
NoMetricsFoundError,
NoMetricsParsedError,
)
from dvc.repo.collect import collect
from dvc.repo.plots.data import PlotParsingError
from dvc.schema import PLOT_PROPS
from dvc.tree.repo import RepoTree
from dvc.utils import relpath
@@ -71,10 +76,24 @@ class Plots:
# Merge data by plot file and apply overriding props
plots = _prepare_plots(data, revs, props)
return {
datafile: _render(datafile, desc["data"], desc["props"], templates)
for datafile, desc in plots.items()
}
result = {}
for datafile, desc in plots.items():
try:
result[datafile] = _render(
datafile, desc["data"], desc["props"], templates
)
except PlotParsingError as e:
logger.debug(
"failed to read '%s' on '%s'",
e.path,
e.revision,
exc_info=True,
)
if not any(result.values()):
raise NoMetricsParsedError("plots")
return result
def show(self, targets=None, revs=None, props=None, templates=None):
from .data import NoMetricInHistoryError
@@ -90,7 +109,7 @@ class Plots:
# No data at all is a special error with a special message
if not data:
raise NoPlotsError()
raise NoMetricsFoundError("plots", "--plots/--plots-no-cache")
if templates is None:
templates = self.templates
+19 -6
View File
@@ -1,14 +1,13 @@
import csv
import io
import json
import os
from collections import OrderedDict
from copy import copy
from funcy import first
from funcy import first, reraise
from dvc.exceptions import DvcException
from dvc.utils.serialize import loads_yaml
from dvc.utils.serialize import ParseError, parse_json, parse_yaml
class PlotMetricTypeError(DvcException):
@@ -32,6 +31,14 @@ class NoMetricInHistoryError(DvcException):
super().__init__(f"Could not find '{path}'.")
class PlotParsingError(ParseError):
def __init__(self, path, revision):
self.path = path
self.revision = revision
super().__init__(path, f"revision: {revision}")
def plot_data(filename, revision, content):
_, extension = os.path.splitext(filename.lower())
if extension == ".json":
@@ -159,7 +166,11 @@ class PlotData:
return [_filter_fields, _append_index, _append_revision]
def to_datapoints(self, **kwargs):
data = self.raw(**kwargs)
with reraise(
[ParseError, csv.Error],
PlotParsingError(self.filename, self.revision),
):
data = self.raw(**kwargs)
for data_proc in self._processors():
data = data_proc(
@@ -170,7 +181,9 @@ class PlotData:
class JSONPlotData(PlotData):
def raw(self, **kwargs):
return json.loads(self.content, object_pairs_hook=OrderedDict)
return parse_json(
self.content, self.filename, object_pairs_hook=OrderedDict
)
def _processors(self):
parent_processors = super()._processors()
@@ -207,7 +220,7 @@ class CSVPlotData(PlotData):
class YAMLPlotData(PlotData):
def raw(self, **kwargs):
return loads_yaml(self.content, typ="rt")
return parse_yaml(self.content, self.filename, typ="rt")
def _processors(self):
parent_processors = super()._processors()
+2 -2
View File
@@ -14,9 +14,9 @@ def load_json(path, tree=None):
return _load_data(path, parser=parse_json, tree=tree)
def parse_json(text, path):
def parse_json(text, path, **kwargs):
with reraise(json.JSONDecodeError, JSONFileCorruptedError(path)):
return json.loads(text) or {}
return json.loads(text, **kwargs) or {}
def dump_json(path, data, tree=None):
-15
View File
@@ -3,15 +3,9 @@ import os
import pytest
from dvc.repo import Repo
from dvc.repo.metrics.show import NoMetricsError
from dvc.utils.fs import remove
def test_show_empty(dvc):
with pytest.raises(NoMetricsError):
dvc.metrics.show()
def test_show_simple(tmp_dir, dvc, run_copy_metrics):
tmp_dir.gen("metrics_t.yaml", "1.1")
run_copy_metrics(
@@ -36,15 +30,6 @@ def test_show_multiple(tmp_dir, dvc, run_copy_metrics):
assert dvc.metrics.show() == {"": {"foo": {"foo": 1}, "baz": {"baz": 2}}}
def test_show_invalid_metric(tmp_dir, dvc, run_copy_metrics):
tmp_dir.gen("metrics_temp.yaml", "foo:\n- bar\n- baz\nxyz: string")
run_copy_metrics(
"metrics_temp.yaml", "metrics.yaml", metrics=["metrics.yaml"]
)
with pytest.raises(NoMetricsError):
dvc.metrics.show()
def test_show_branch(tmp_dir, scm, dvc, run_copy_metrics):
tmp_dir.gen("metrics_temp.yaml", "foo: 1")
run_copy_metrics(
-7
View File
@@ -437,13 +437,6 @@ def _replace(path, src, dst):
path.write_text(path.read_text().replace(src, dst))
def test_no_plots(tmp_dir, dvc):
from dvc.exceptions import NoPlotsError
with pytest.raises(NoPlotsError):
dvc.plots.show()
def test_should_raise_on_no_template(tmp_dir, dvc, run_copy_metrics):
metric = [{"val": 2}, {"val": 3}]
_write_json(tmp_dir, metric, "metric_t.json")
+41 -12
View File
@@ -2,28 +2,57 @@ import logging
import pytest
from dvc.main import main
from tests.func.metrics.utils import _write_json
def metrics_diff(dvc, filename, revision):
dvc.metrics.diff(targets=[filename], a_rev=revision)
def plots_diff(dvc, filename, revision):
dvc.plots.diff(targets=[filename], revs=[revision])
@pytest.mark.parametrize(
"diff_fun, metric_value",
((metrics_diff, {"m": 1}), (plots_diff, [{"m": 1}, {"m": 2}])),
"command, metric_value",
(("metrics", {"m": 1}), ("plots", [{"m": 1}, {"m": 2}])),
)
def test_diff_no_file_on_target_rev(
tmp_dir, scm, dvc, caplog, diff_fun, metric_value
tmp_dir, scm, dvc, caplog, command, metric_value
):
with tmp_dir.branch("new_branch", new=True):
_write_json(tmp_dir, metric_value, "metric.json")
with caplog.at_level(logging.WARNING, "dvc"):
diff_fun(dvc, "metric.json", "master")
assert (
main([command, "diff", "master", "--targets", "metric.json"])
== 0
)
assert "'metric.json' was not found at: 'master'." in caplog.text
@pytest.mark.parametrize(
"command, malformed_metric",
(("metrics", '{"m": 1'), ("plots", '[{"m": 1}, {"m": 2}'),),
)
def test_show_malformed_metric(
tmp_dir, scm, dvc, caplog, command, malformed_metric
):
tmp_dir.gen("metric.json", malformed_metric)
with caplog.at_level(logging.ERROR, "dvc"):
assert main([command, "show", "metric.json"]) == 1
assert (
f"Could not parse {command} files. "
"Use `-v` option to see more details."
) in caplog.text
@pytest.mark.parametrize(
"command, run_options",
(("metrics", "-m/-M"), ("plots", "--plots/--plots-no-cache"),),
)
def test_show_no_metrics_files(tmp_dir, dvc, caplog, command, run_options):
with caplog.at_level(logging.ERROR, "dvc"):
assert main([command, "show"]) == 1
assert (
f"No {command} files in this repository. "
f"Use `{run_options}` options for "
f"`dvc run` to mark stage outputs as {command}."
) in caplog.text