params/metrics: diff: implement tabulated markdown output (--show-md) (#3757)

* params/metrics: diff: implement tabulated markdown output

Via `--show-md` on `diff`s.

* table: show None on missing val

* tests: use `dedent`, add tests for markdown

* diff: add --show-md

Co-authored-by: Ruslan Kuprieiev <[email protected]>
This commit is contained in:
Saugat Pachhai
2020-05-10 18:39:51 +03:00
committed by GitHub
co-authored by Ruslan Kuprieiev
parent ce946d1160
commit 666e203bbb
8 changed files with 206 additions and 64 deletions
+23
View File
@@ -11,6 +11,21 @@ from dvc.exceptions import DvcException
logger = logging.getLogger(__name__)
def _show_md(diff):
from dvc.utils.diff import table
rows = []
for status in ["added", "deleted", "modified"]:
entries = diff.get(status, [])
if not entries:
continue
paths = sorted([entry["path"] for entry in entries])
for path in paths:
rows.append([status, path])
return table(["Status", "Path"], rows, True)
class CmdDiff(CmdBase):
@staticmethod
def _format(diff):
@@ -103,6 +118,8 @@ class CmdDiff(CmdBase):
if self.args.show_json:
logger.info(json.dumps(diff))
elif self.args.show_md:
logger.info(_show_md(diff))
elif diff:
logger.info(self._format(diff))
@@ -147,4 +164,10 @@ def add_parser(subparsers, parent_parser):
action="store_true",
default=False,
)
diff_parser.add_argument(
"--show-md",
help="Show tabulated output in the Markdown format (GFM).",
action="store_true",
default=False,
)
diff_parser.set_defaults(func=CmdDiff)
+9 -3
View File
@@ -87,7 +87,7 @@ class CmdMetricsRemove(CmdBase):
return 0
def _show_diff(diff):
def _show_diff(diff, markdown=False):
from collections import OrderedDict
from dvc.utils.diff import table
@@ -105,7 +105,7 @@ def _show_diff(diff):
]
)
return table(["Path", "Metric", "Value", "Change"], rows)
return table(["Path", "Metric", "Value", "Change"], rows, markdown)
class CmdMetricsDiff(CmdBase):
@@ -124,7 +124,7 @@ class CmdMetricsDiff(CmdBase):
logger.info(json.dumps(diff))
else:
table = _show_diff(diff)
table = _show_diff(diff, self.args.show_md)
if table:
logger.info(table)
@@ -263,6 +263,12 @@ def add_parser(subparsers, parent_parser):
default=False,
help="Show output in JSON format.",
)
metrics_diff_parser.add_argument(
"--show-md",
action="store_true",
default=False,
help="Show tabulated output in the Markdown format (GFM).",
)
metrics_diff_parser.set_defaults(func=CmdMetricsDiff)
METRICS_REMOVE_HELP = "Remove metric mark on a DVC-tracked file."
+9 -3
View File
@@ -8,7 +8,7 @@ from dvc.exceptions import DvcException
logger = logging.getLogger(__name__)
def _show_diff(diff):
def _show_diff(diff, markdown=False):
from dvc.utils.diff import table
rows = []
@@ -17,7 +17,7 @@ def _show_diff(diff):
for param, change in sorted_pdiff.items():
rows.append([fname, param, change["old"], change["new"]])
return table(["Path", "Param", "Old", "New"], rows)
return table(["Path", "Param", "Old", "New"], rows, markdown)
class CmdParamsDiff(CmdBase):
@@ -34,7 +34,7 @@ class CmdParamsDiff(CmdBase):
logger.info(json.dumps(diff))
else:
table = _show_diff(diff)
table = _show_diff(diff, self.args.show_md)
if table:
logger.info(table)
@@ -94,4 +94,10 @@ def add_parser(subparsers, parent_parser):
default=False,
help="Show output in JSON format.",
)
params_diff_parser.add_argument(
"--show-md",
action="store_true",
default=False,
help="Show tabulated output in the Markdown format (GFM).",
)
params_diff_parser.set_defaults(func=CmdParamsDiff)
+11 -15
View File
@@ -85,24 +85,20 @@ def diff(old, new, with_unchanged=False):
return dict(res)
def table(header, rows):
from texttable import Texttable
def table(header, rows, markdown=False):
from tabulate import tabulate
if not rows:
if not rows and not markdown:
return ""
t = Texttable()
# disable automatic formatting
t.set_cols_dtype(["t"] * len(header))
# remove borders to make it easier for users to copy stuff
t.set_chars([""] * len(header))
t.set_deco(0)
t.add_rows([header] + rows)
return t.draw()
return tabulate(
rows,
header,
tablefmt="github" if markdown else "plain",
disable_numparse=True,
# None will be shown as "" by default, overriding
missingval="None",
)
def format_dict(d):
+1 -1
View File
@@ -78,7 +78,7 @@ install_requires = [
"pydot>=1.2.4",
"speedcopy>=2.0.1; python_version < '3.8' and sys_platform == 'win32'",
"flatten_json>=0.1.6",
"texttable>=0.5.2",
"tabulate>=0.8.7",
"pygtrie==2.3.2",
"dpath>=2.0.1,<3",
]
+30
View File
@@ -3,6 +3,7 @@ import logging
import os
from dvc.cli import parse_args
from dvc.command.diff import _show_md
def test_default(mocker, caplog):
@@ -110,3 +111,32 @@ def test_no_changes(mocker, caplog):
cmd = args.func(args)
assert 0 == cmd.run()
assert not info()
def test_show_md_empty():
assert _show_md({}) == ("| Status | Path |\n" "|----------|--------|")
def test_show_md():
diff = {
"deleted": [
{"path": "zoo", "hash": "22222"},
{"path": os.path.join("data", ""), "hash": "XXXXXXXX.dir"},
{"path": os.path.join("data", "foo"), "hash": "11111111"},
{"path": os.path.join("data", "bar"), "hash": "00000000"},
],
"modified": [
{"path": "file", "hash": {"old": "AAAAAAAA", "new": "BBBBBBBB"}}
],
"added": [{"path": "file", "hash": "00000000"}],
}
assert _show_md(diff) == (
"| Status | Path |\n"
"|----------|----------|\n"
"| added | file |\n"
"| deleted | data{sep} |\n"
"| deleted | data{sep}bar |\n"
"| deleted | data{sep}foo |\n"
"| deleted | zoo |\n"
"| modified | file |"
).format(sep=os.path.sep)
+62 -23
View File
@@ -1,3 +1,5 @@
import textwrap
from dvc.cli import parse_args
from dvc.command.metrics import CmdMetricsDiff, CmdMetricsShow, _show_diff
@@ -37,25 +39,30 @@ def test_metrics_diff(dvc, mocker):
def test_metrics_show_json_diff():
assert _show_diff(
{"metrics.json": {"a.b.c": {"old": 1, "new": 2, "diff": 3}}}
) == (
" Path Metric Value Change\n"
"metrics.json a.b.c 2 3 "
) == textwrap.dedent(
"""\
Path Metric Value Change
metrics.json a.b.c 2 3"""
)
def test_metrics_show_raw_diff():
assert _show_diff({"metrics": {"": {"old": "1", "new": "2"}}}) == (
" Path Metric Value Change \n"
"metrics 2 diff not supported"
assert _show_diff(
{"metrics": {"": {"old": "1", "new": "2"}}}
) == textwrap.dedent(
"""\
Path Metric Value Change
metrics 2 diff not supported"""
)
def test_metrics_diff_no_diff():
assert _show_diff(
{"other.json": {"a.b.d": {"old": "old", "new": "new"}}}
) == (
" Path Metric Value Change \n"
"other.json a.b.d new diff not supported"
) == textwrap.dedent(
"""\
Path Metric Value Change
other.json a.b.d new diff not supported"""
)
@@ -66,18 +73,20 @@ def test_metrics_diff_no_changes():
def test_metrics_diff_new_metric():
assert _show_diff(
{"other.json": {"a.b.d": {"old": None, "new": "new"}}}
) == (
" Path Metric Value Change \n"
"other.json a.b.d new diff not supported"
) == textwrap.dedent(
"""\
Path Metric Value Change
other.json a.b.d new diff not supported"""
)
def test_metrics_diff_deleted_metric():
assert _show_diff(
{"other.json": {"a.b.d": {"old": "old", "new": None}}}
) == (
" Path Metric Value Change \n"
"other.json a.b.d None diff not supported"
) == textwrap.dedent(
"""\
Path Metric Value Change
other.json a.b.d None diff not supported"""
)
@@ -114,9 +123,10 @@ def test_metrics_show(dvc, mocker):
def test_metrics_diff_prec():
assert _show_diff(
{"other.json": {"a.b": {"old": 0.0042, "new": 0.0043, "diff": 0.0001}}}
) == (
" Path Metric Value Change\n"
"other.json a.b 0.0043 0.0001"
) == textwrap.dedent(
"""\
Path Metric Value Change
other.json a.b 0.0043 0.0001"""
)
@@ -129,9 +139,38 @@ def test_metrics_diff_sorted():
"a.b.c": {"old": 1, "new": 2, "diff": 1},
}
}
) == (
" Path Metric Value Change\n"
"metrics.yaml a.b.c 2 1 \n"
"metrics.yaml a.d.e 4 1 \n"
"metrics.yaml x.b 6 1 "
) == textwrap.dedent(
"""\
Path Metric Value Change
metrics.yaml a.b.c 2 1
metrics.yaml a.d.e 4 1
metrics.yaml x.b 6 1"""
)
def test_metrics_diff_markdown_empty():
assert _show_diff({}, markdown=True) == textwrap.dedent(
"""\
| Path | Metric | Value | Change |
|--------|----------|---------|----------|"""
)
def test_metrics_diff_markdown():
assert _show_diff(
{
"metrics.yaml": {
"x.b": {"old": 5, "new": 6},
"a.d.e": {"old": 3, "new": 4, "diff": 1},
"a.b.c": {"old": 1, "new": 2, "diff": 1},
}
},
markdown=True,
) == textwrap.dedent(
"""\
| Path | Metric | Value | Change |
|--------------|----------|---------|--------------------|
| metrics.yaml | a.b.c | 2 | 1 |
| metrics.yaml | a.d.e | 4 | 1 |
| metrics.yaml | x.b | 6 | diff not supported |"""
)
+61 -19
View File
@@ -1,4 +1,5 @@
import logging
import textwrap
from dvc.cli import parse_args
from dvc.command.params import CmdParamsDiff, _show_diff
@@ -21,25 +22,32 @@ def test_params_diff(dvc, mocker):
def test_params_diff_changed():
assert _show_diff({"params.yaml": {"a.b.c": {"old": 1, "new": 2}}}) == (
" Path Param Old New\n" "params.yaml a.b.c 1 2 "
assert _show_diff(
{"params.yaml": {"a.b.c": {"old": 1, "new": 2}}}
) == textwrap.dedent(
"""\
Path Param Old New
params.yaml a.b.c 1 2"""
)
def test_params_diff_list():
assert _show_diff(
{"params.yaml": {"a.b.c": {"old": 1, "new": [2, 3]}}}
) == (
" Path Param Old New \n"
"params.yaml a.b.c 1 [2, 3]"
) == textwrap.dedent(
"""\
Path Param Old New
params.yaml a.b.c 1 [2, 3]"""
)
def test_params_diff_unchanged():
assert _show_diff(
{"params.yaml": {"a.b.d": {"old": "old", "new": "new"}}}
) == (
" Path Param Old New\n" "params.yaml a.b.d old new"
) == textwrap.dedent(
"""\
Path Param Old New
params.yaml a.b.d old new"""
)
@@ -50,25 +58,30 @@ def test_params_diff_no_changes():
def test_params_diff_new():
assert _show_diff(
{"params.yaml": {"a.b.d": {"old": None, "new": "new"}}}
) == (
" Path Param Old New\n" "params.yaml a.b.d None new"
) == textwrap.dedent(
"""\
Path Param Old New
params.yaml a.b.d None new"""
)
def test_params_diff_deleted():
assert _show_diff(
{"params.yaml": {"a.b.d": {"old": "old", "new": None}}}
) == (
" Path Param Old New \n" "params.yaml a.b.d old None"
) == textwrap.dedent(
"""\
Path Param Old New
params.yaml a.b.d old None"""
)
def test_params_diff_prec():
assert _show_diff(
{"params.yaml": {"train.lr": {"old": 0.0042, "new": 0.0043}}}
) == (
" Path Param Old New \n"
"params.yaml train.lr 0.0042 0.0043"
) == textwrap.dedent(
"""\
Path Param Old New
params.yaml train.lr 0.0042 0.0043"""
)
@@ -94,9 +107,38 @@ def test_params_diff_sorted():
"a.b.c": {"old": 1, "new": 2},
}
}
) == (
" Path Param Old New\n"
"params.yaml a.b.c 1 2 \n"
"params.yaml a.d.e 3 4 \n"
"params.yaml x.b 5 6 "
) == textwrap.dedent(
"""\
Path Param Old New
params.yaml a.b.c 1 2
params.yaml a.d.e 3 4
params.yaml x.b 5 6"""
)
def test_params_diff_markdown_empty():
assert _show_diff({}, markdown=True) == textwrap.dedent(
"""\
| Path | Param | Old | New |
|--------|---------|-------|-------|"""
)
def test_params_diff_markdown():
assert _show_diff(
{
"params.yaml": {
"x.b": {"old": 5, "new": 6},
"a.d.e": {"old": None, "new": 4},
"a.b.c": {"old": 1, "new": None},
}
},
markdown=True,
) == textwrap.dedent(
"""\
| Path | Param | Old | New |
|-------------|---------|-------|-------|
| params.yaml | a.b.c | 1 | None |
| params.yaml | a.d.e | None | 4 |
| params.yaml | x.b | 5 | 6 |"""
)