mirror of
https://github.com/Stone-Red-Code/dvc.git
synced 2026-11-11 15:28:27 +01:00
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:
co-authored by
Ruslan Kuprieiev
parent
ce946d1160
commit
666e203bbb
@@ -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)
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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
@@ -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):
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 |"""
|
||||
)
|
||||
|
||||
@@ -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 |"""
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user