mirror of
https://github.com/Stone-Red-Code/dvc.git
synced 2026-11-02 15:28:30 +01:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9622157565 | ||
|
|
6ff4342f02 | ||
|
|
5bb72b36cb | ||
|
|
1aed230cd0 | ||
|
|
c50b2e1e71 | ||
|
|
34bc410075 | ||
|
|
47ff40da0c | ||
|
|
ee35ec10a2 |
@@ -22,7 +22,7 @@ repos:
|
||||
- id: sort-simple-yaml
|
||||
- id: trailing-whitespace
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: 'v0.1.5'
|
||||
rev: 'v0.1.6'
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [--fix, --exit-non-zero-on-fix]
|
||||
|
||||
@@ -42,6 +42,7 @@ from dvc.commands import (
|
||||
repro,
|
||||
root,
|
||||
stage,
|
||||
studio,
|
||||
unprotect,
|
||||
update,
|
||||
version,
|
||||
@@ -93,6 +94,7 @@ COMMANDS = [
|
||||
check_ignore,
|
||||
data,
|
||||
artifacts,
|
||||
studio,
|
||||
]
|
||||
|
||||
|
||||
|
||||
+1
-45
@@ -59,49 +59,6 @@ def _show_json(
|
||||
ui.write_json(compact({"errors": all_errors, "data": data}), highlight=False)
|
||||
|
||||
|
||||
def _adjust_vega_renderers(renderers):
|
||||
from dvc.render import REVISION_FIELD, VERSION_FIELD
|
||||
from dvc_render import VegaRenderer
|
||||
|
||||
for r in renderers:
|
||||
if isinstance(r, VegaRenderer):
|
||||
if _data_versions_count(r) > 1:
|
||||
summary = _summarize_version_infos(r)
|
||||
for dp in r.datapoints:
|
||||
vi = dp.pop(VERSION_FIELD, {})
|
||||
keys = list(vi.keys())
|
||||
for key in keys:
|
||||
if not (len(summary.get(key, set())) > 1):
|
||||
vi.pop(key)
|
||||
if vi:
|
||||
dp["rev"] = "::".join(vi.values())
|
||||
else:
|
||||
for dp in r.datapoints:
|
||||
dp[REVISION_FIELD] = dp[VERSION_FIELD]["revision"]
|
||||
dp.pop(VERSION_FIELD, {})
|
||||
|
||||
|
||||
def _summarize_version_infos(renderer):
|
||||
from collections import defaultdict
|
||||
|
||||
from dvc.render import VERSION_FIELD
|
||||
|
||||
result = defaultdict(set)
|
||||
|
||||
for dp in renderer.datapoints:
|
||||
for key, value in dp.get(VERSION_FIELD, {}).items():
|
||||
result[key].add(value)
|
||||
return dict(result)
|
||||
|
||||
|
||||
def _data_versions_count(renderer):
|
||||
from itertools import product
|
||||
|
||||
summary = _summarize_version_infos(renderer)
|
||||
x = product(summary.get("filename", {None}), summary.get("field", {None}))
|
||||
return len(set(x))
|
||||
|
||||
|
||||
class CmdPlots(CmdBase):
|
||||
def _func(self, *args, **kwargs):
|
||||
raise NotImplementedError
|
||||
@@ -175,11 +132,10 @@ class CmdPlots(CmdBase):
|
||||
return 0
|
||||
|
||||
renderers = [r.renderer for r in renderers_with_errors]
|
||||
_adjust_vega_renderers(renderers)
|
||||
if self.args.show_vega:
|
||||
renderer = first(filter(lambda r: r.TYPE == "vega", renderers))
|
||||
if renderer:
|
||||
ui.write_json(renderer.get_filled_template(as_string=False))
|
||||
ui.write_json(renderer.get_filled_template())
|
||||
return 0
|
||||
|
||||
output_file: Path = (Path.cwd() / out).resolve() / "index.html"
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
import argparse
|
||||
import os
|
||||
|
||||
from funcy import get_in
|
||||
|
||||
from dvc.cli.utils import append_doc_link, fix_subparsers
|
||||
from dvc.commands.config import CmdConfig
|
||||
from dvc.log import logger
|
||||
|
||||
logger = logger.getChild(__name__)
|
||||
|
||||
|
||||
class CmdStudioLogin(CmdConfig):
|
||||
def run(self):
|
||||
from dvc_studio_client.auth import StudioAuthError, get_access_token
|
||||
|
||||
from dvc.env import DVC_STUDIO_URL
|
||||
from dvc.ui import ui
|
||||
from dvc.utils.studio import STUDIO_URL
|
||||
|
||||
name = self.args.name
|
||||
hostname = self.args.hostname or os.environ.get(DVC_STUDIO_URL) or STUDIO_URL
|
||||
scopes = self.args.scopes
|
||||
|
||||
try:
|
||||
token_name, access_token = get_access_token(
|
||||
token_name=name,
|
||||
hostname=hostname,
|
||||
scopes=scopes,
|
||||
use_device_code=self.args.no_open,
|
||||
client_name="DVC",
|
||||
)
|
||||
except StudioAuthError as e:
|
||||
ui.error_write(str(e))
|
||||
return 1
|
||||
|
||||
self.save_config(hostname, access_token)
|
||||
ui.write(
|
||||
"Authentication has been successfully completed."
|
||||
"The generated token will now be accessible as"
|
||||
f" {token_name} in the user's Studio profile."
|
||||
)
|
||||
return 0
|
||||
|
||||
def save_config(self, hostname, token):
|
||||
with self.config.edit("global") as conf:
|
||||
conf["studio"]["token"] = token
|
||||
conf["studio"]["url"] = hostname
|
||||
|
||||
|
||||
class CmdStudioLogout(CmdConfig):
|
||||
def run(self):
|
||||
from dvc.ui import ui
|
||||
|
||||
with self.config.edit("global") as conf:
|
||||
if not get_in(conf, ["studio", "token"]):
|
||||
ui.error_write("Not logged in to Studio.")
|
||||
return 1
|
||||
|
||||
del conf["studio"]["token"]
|
||||
|
||||
ui.write("Logged out from Studio")
|
||||
return 0
|
||||
|
||||
|
||||
class CmdStudioToken(CmdConfig):
|
||||
def run(self):
|
||||
from dvc.ui import ui
|
||||
|
||||
conf = self.config.read("global")
|
||||
token = get_in(conf, ["studio", "token"])
|
||||
if not token:
|
||||
ui.error_write("Not logged in to Studio.")
|
||||
return 1
|
||||
|
||||
ui.write(token)
|
||||
return 0
|
||||
|
||||
|
||||
def add_parser(subparsers, parent_parser):
|
||||
STUDIO_HELP = "Commands to authenticate DVC with Iterative Studio"
|
||||
STUDIO_DESCRIPTION = (
|
||||
"Authenticate DVC with Studio and set the token."
|
||||
" Once this token has been properly configured,\n"
|
||||
" DVC will utilize it for seamlessly sharing live experiments\n"
|
||||
" and sending notifications to Studio regarding any experiments"
|
||||
" that have been pushed."
|
||||
)
|
||||
|
||||
studio_parser = subparsers.add_parser(
|
||||
"studio",
|
||||
parents=[parent_parser],
|
||||
description=append_doc_link(STUDIO_DESCRIPTION, "studio"),
|
||||
help=STUDIO_HELP,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
studio_subparser = studio_parser.add_subparsers(
|
||||
dest="cmd",
|
||||
help="Use `DVC studio CMD --help` to display command-specific help.",
|
||||
)
|
||||
fix_subparsers(studio_subparser)
|
||||
|
||||
STUDIO_LOGIN_HELP = "Authenticate DVC with Studio host"
|
||||
STUDIO_LOGIN_DESCRIPTION = (
|
||||
"By default, this command authenticates the DVC with Studio\n"
|
||||
" using default scopes and assigns a random name as the token name."
|
||||
)
|
||||
login_parser = studio_subparser.add_parser(
|
||||
"login",
|
||||
parents=[parent_parser],
|
||||
description=append_doc_link(STUDIO_LOGIN_DESCRIPTION, "studio/login"),
|
||||
help=STUDIO_LOGIN_HELP,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
|
||||
login_parser.add_argument(
|
||||
"-H",
|
||||
"--hostname",
|
||||
action="store",
|
||||
default=None,
|
||||
help="The hostname of the Studio instance to authenticate with.",
|
||||
)
|
||||
login_parser.add_argument(
|
||||
"-s",
|
||||
"--scopes",
|
||||
action="store",
|
||||
default=None,
|
||||
help="The scopes for the authentication token. ",
|
||||
)
|
||||
|
||||
login_parser.add_argument(
|
||||
"-n",
|
||||
"--name",
|
||||
action="store",
|
||||
default=None,
|
||||
help="The name of the authentication token. It will be used to\n"
|
||||
"identify token shown in Studio profile.",
|
||||
)
|
||||
|
||||
login_parser.add_argument(
|
||||
"--no-open",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Use authentication flow based on user code.\n"
|
||||
"You will be presented with user code to enter in browser.\n"
|
||||
"DVC will also use this if it cannot launch browser on your behalf.",
|
||||
)
|
||||
login_parser.set_defaults(func=CmdStudioLogin)
|
||||
|
||||
STUDIO_LOGOUT_HELP = "Logout user from Studio"
|
||||
STUDIO_LOGOUT_DESCRIPTION = (
|
||||
"This removes the studio token from your global config.\n"
|
||||
)
|
||||
|
||||
logout_parser = studio_subparser.add_parser(
|
||||
"logout",
|
||||
parents=[parent_parser],
|
||||
description=append_doc_link(STUDIO_LOGOUT_DESCRIPTION, "studio/logout"),
|
||||
help=STUDIO_LOGOUT_HELP,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
|
||||
logout_parser.set_defaults(func=CmdStudioLogout)
|
||||
|
||||
STUDIO_TOKEN_HELP = "View the token dvc uses to contact Studio" # noqa: S105 # nosec B105
|
||||
|
||||
logout_parser = studio_subparser.add_parser(
|
||||
"token",
|
||||
parents=[parent_parser],
|
||||
description=append_doc_link(STUDIO_TOKEN_HELP, "studio/token"),
|
||||
help=STUDIO_TOKEN_HELP,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
|
||||
logout_parser.set_defaults(func=CmdStudioToken)
|
||||
@@ -1,7 +1,8 @@
|
||||
INDEX_FIELD = "step"
|
||||
REVISION_FIELD = "rev"
|
||||
FILENAME_FIELD = "filename"
|
||||
VERSION_FIELD = "dvc_data_version_info"
|
||||
REVISIONS_KEY = "revisions"
|
||||
INDEX = "step"
|
||||
REVISION = "rev"
|
||||
FILENAME = "filename"
|
||||
FIELD = "field"
|
||||
REVISIONS = "revisions"
|
||||
ANCHOR_DEFINITIONS = "anchor_definitions"
|
||||
TYPE_KEY = "type"
|
||||
SRC_FIELD = "src"
|
||||
SRC = "src"
|
||||
|
||||
+18
-27
@@ -1,7 +1,6 @@
|
||||
from collections import defaultdict
|
||||
from typing import Dict, List, Union
|
||||
|
||||
from dvc.render import REVISION_FIELD, REVISIONS_KEY, SRC_FIELD, TYPE_KEY, VERSION_FIELD
|
||||
from dvc.render import REVISION, REVISIONS, SRC, TYPE_KEY
|
||||
from dvc.render.converter.image import ImageConverter
|
||||
from dvc.render.converter.vega import VegaConverter
|
||||
|
||||
@@ -19,39 +18,31 @@ def _get_converter(
|
||||
raise ValueError(f"Invalid renderer class {renderer_class}")
|
||||
|
||||
|
||||
def _group_by_rev(datapoints):
|
||||
grouped = defaultdict(list)
|
||||
for datapoint in datapoints:
|
||||
rev = datapoint.get(VERSION_FIELD, {}).get("revision")
|
||||
grouped[rev].append(datapoint)
|
||||
return dict(grouped)
|
||||
|
||||
|
||||
def to_json(renderer, split: bool = False) -> List[Dict]:
|
||||
if renderer.TYPE == "vega":
|
||||
grouped = _group_by_rev(renderer.datapoints)
|
||||
if not renderer.datapoints:
|
||||
return []
|
||||
revs = renderer.get_revs()
|
||||
if split:
|
||||
content = renderer.get_filled_template(
|
||||
skip_anchors=["data"], as_string=False
|
||||
)
|
||||
content, split_content = renderer.get_partial_filled_template()
|
||||
else:
|
||||
content = renderer.get_filled_template(as_string=False)
|
||||
if grouped:
|
||||
return [
|
||||
{
|
||||
TYPE_KEY: renderer.TYPE,
|
||||
REVISIONS_KEY: sorted(grouped.keys()),
|
||||
"content": content,
|
||||
"datapoints": grouped,
|
||||
}
|
||||
]
|
||||
return []
|
||||
content = renderer.get_filled_template()
|
||||
split_content = {}
|
||||
|
||||
return [
|
||||
{
|
||||
TYPE_KEY: renderer.TYPE,
|
||||
REVISIONS: revs,
|
||||
"content": content,
|
||||
**split_content,
|
||||
}
|
||||
]
|
||||
if renderer.TYPE == "image":
|
||||
return [
|
||||
{
|
||||
TYPE_KEY: renderer.TYPE,
|
||||
REVISIONS_KEY: [datapoint.get(REVISION_FIELD)],
|
||||
"url": datapoint.get(SRC_FIELD),
|
||||
REVISIONS: [datapoint.get(REVISION)],
|
||||
"url": datapoint.get(SRC),
|
||||
}
|
||||
for datapoint in renderer.datapoints
|
||||
]
|
||||
|
||||
@@ -2,7 +2,7 @@ import base64
|
||||
import os
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Tuple
|
||||
|
||||
from dvc.render import FILENAME_FIELD, REVISION_FIELD, SRC_FIELD
|
||||
from dvc.render import FILENAME, REVISION, SRC
|
||||
|
||||
from . import Converter
|
||||
|
||||
@@ -58,9 +58,9 @@ class ImageConverter(Converter):
|
||||
else:
|
||||
src = self._encode_image(image_data)
|
||||
datapoint = {
|
||||
REVISION_FIELD: revision,
|
||||
FILENAME_FIELD: filename,
|
||||
SRC_FIELD: src,
|
||||
REVISION: revision,
|
||||
FILENAME: filename,
|
||||
SRC: src,
|
||||
}
|
||||
datapoints.append(datapoint)
|
||||
return datapoints, properties
|
||||
|
||||
@@ -4,7 +4,7 @@ from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
|
||||
from funcy import first, last
|
||||
|
||||
from dvc.exceptions import DvcException
|
||||
from dvc.render import FILENAME_FIELD, INDEX_FIELD, VERSION_FIELD
|
||||
from dvc.render import FIELD, FILENAME, INDEX, REVISION
|
||||
|
||||
from . import Converter
|
||||
|
||||
@@ -112,7 +112,6 @@ class VegaConverter(Converter):
|
||||
):
|
||||
super().__init__(plot_id, data, properties)
|
||||
self.plot_id = plot_id
|
||||
self.inferred_properties: Dict = {}
|
||||
|
||||
def _infer_y_from_data(self):
|
||||
if self.plot_id in self.data:
|
||||
@@ -120,34 +119,38 @@ class VegaConverter(Converter):
|
||||
if all(isinstance(item, dict) for item in lst):
|
||||
datapoint = first(lst)
|
||||
field = last(datapoint.keys())
|
||||
self.inferred_properties["y"] = {self.plot_id: field}
|
||||
break
|
||||
return {self.plot_id: field}
|
||||
return None
|
||||
|
||||
def _infer_x_y(self):
|
||||
x = self.properties.get("x", None)
|
||||
y = self.properties.get("y", None)
|
||||
|
||||
inferred_properties: Dict = {}
|
||||
|
||||
# Infer x.
|
||||
if isinstance(x, str):
|
||||
self.inferred_properties["x"] = {}
|
||||
inferred_properties["x"] = {}
|
||||
# If multiple y files, duplicate x for each file.
|
||||
if isinstance(y, dict):
|
||||
for file, fields in y.items():
|
||||
# Duplicate x for each y.
|
||||
if isinstance(fields, list):
|
||||
self.inferred_properties["x"][file] = [x] * len(fields)
|
||||
inferred_properties["x"][file] = [x] * len(fields)
|
||||
else:
|
||||
self.inferred_properties["x"][file] = x
|
||||
inferred_properties["x"][file] = x
|
||||
# Otherwise use plot ID as file.
|
||||
else:
|
||||
self.inferred_properties["x"][self.plot_id] = x
|
||||
inferred_properties["x"][self.plot_id] = x
|
||||
|
||||
# Infer y.
|
||||
if y is None:
|
||||
self._infer_y_from_data()
|
||||
inferred_properties["y"] = self._infer_y_from_data()
|
||||
# If y files not provided, use plot ID as file.
|
||||
elif not isinstance(y, dict):
|
||||
self.inferred_properties["y"] = {self.plot_id: y}
|
||||
inferred_properties["y"] = {self.plot_id: y}
|
||||
|
||||
return inferred_properties
|
||||
|
||||
def _find_datapoints(self):
|
||||
result = {}
|
||||
@@ -182,7 +185,7 @@ class VegaConverter(Converter):
|
||||
|
||||
x = properties.get("x", None)
|
||||
if not isinstance(x, dict):
|
||||
return INDEX_FIELD
|
||||
return INDEX
|
||||
|
||||
fields = {field for _, field in _file_field(x)}
|
||||
if len(fields) == 1:
|
||||
@@ -192,7 +195,7 @@ class VegaConverter(Converter):
|
||||
def flat_datapoints(self, revision): # noqa: C901, PLR0912
|
||||
file2datapoints, properties = self.convert()
|
||||
|
||||
props_update = {}
|
||||
props_update: Dict[str, Union[str, List[Dict[str, str]]]] = {}
|
||||
|
||||
xs = list(_get_xs(properties, file2datapoints))
|
||||
|
||||
@@ -200,15 +203,17 @@ class VegaConverter(Converter):
|
||||
if not xs:
|
||||
x_file, x_field = (
|
||||
None,
|
||||
INDEX_FIELD,
|
||||
INDEX,
|
||||
)
|
||||
else:
|
||||
x_file, x_field = xs[0]
|
||||
props_update["x"] = x_field
|
||||
|
||||
num_xs = len(xs)
|
||||
multiple_x_fields = num_xs > 1 and len({x[1] for x in xs}) > 1
|
||||
props_update["x"] = "dvc_inferred_x_value" if multiple_x_fields else x_field
|
||||
|
||||
ys = list(_get_ys(properties, file2datapoints))
|
||||
|
||||
num_xs = len(xs)
|
||||
num_ys = len(ys)
|
||||
if num_xs > 1 and num_xs != num_ys:
|
||||
raise DvcException(
|
||||
@@ -237,6 +242,14 @@ class VegaConverter(Converter):
|
||||
else:
|
||||
common_prefix_len = 0
|
||||
|
||||
props_update["anchors_y_definitions"] = [
|
||||
{
|
||||
FILENAME: _get_short_y_file(y_file, common_prefix_len),
|
||||
FIELD: y_field,
|
||||
}
|
||||
for y_file, y_field in ys
|
||||
]
|
||||
|
||||
for i, (y_file, y_field) in enumerate(ys):
|
||||
if num_xs > 1:
|
||||
x_file, x_field = xs[i]
|
||||
@@ -249,15 +262,16 @@ class VegaConverter(Converter):
|
||||
source_field=y_field,
|
||||
)
|
||||
|
||||
if x_field == INDEX_FIELD and x_file is None:
|
||||
_update_from_index(datapoints, INDEX_FIELD)
|
||||
if x_field == INDEX and x_file is None:
|
||||
_update_from_index(datapoints, INDEX)
|
||||
else:
|
||||
x_datapoints = file2datapoints.get(x_file, [])
|
||||
try:
|
||||
_update_from_field(
|
||||
datapoints,
|
||||
field=x_field,
|
||||
field="dvc_inferred_x_value" if multiple_x_fields else x_field,
|
||||
source_datapoints=x_datapoints,
|
||||
source_field=x_field,
|
||||
)
|
||||
except IndexError:
|
||||
raise DvcException( # noqa: B904
|
||||
@@ -266,15 +280,12 @@ class VegaConverter(Converter):
|
||||
"They have to have same length."
|
||||
)
|
||||
|
||||
y_file_short = y_file[common_prefix_len:].strip("/\\")
|
||||
_update_all(
|
||||
datapoints,
|
||||
update_dict={
|
||||
VERSION_FIELD: {
|
||||
"revision": revision,
|
||||
FILENAME_FIELD: y_file_short,
|
||||
"field": y_field,
|
||||
}
|
||||
REVISION: revision,
|
||||
FILENAME: _get_short_y_file(y_file, common_prefix_len),
|
||||
FIELD: y_field,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -295,10 +306,10 @@ class VegaConverter(Converter):
|
||||
generated datapoints and updated properties. `x`, `y` values and labels
|
||||
are inferred and always provided.
|
||||
"""
|
||||
self._infer_x_y()
|
||||
inferred_properties = self._infer_x_y()
|
||||
|
||||
datapoints = self._find_datapoints()
|
||||
properties = {**self.properties, **self.inferred_properties}
|
||||
properties = {**self.properties, **inferred_properties}
|
||||
|
||||
properties["y_label"] = self.infer_y_label(properties)
|
||||
properties["x_label"] = self.infer_x_label(properties)
|
||||
@@ -306,6 +317,10 @@ class VegaConverter(Converter):
|
||||
return datapoints, properties
|
||||
|
||||
|
||||
def _get_short_y_file(y_file, common_prefix_len):
|
||||
return y_file[common_prefix_len:].strip("/\\")
|
||||
|
||||
|
||||
def _update_from_field(
|
||||
target_datapoints: List[Dict],
|
||||
field: str,
|
||||
|
||||
+12
-6
@@ -80,7 +80,7 @@ def match_defs_renderers( # noqa: C901, PLR0912
|
||||
for plot_id, group in plots_data.group_definitions().items():
|
||||
plot_datapoints: List[Dict] = []
|
||||
props = _squash_plots_properties(group)
|
||||
final_props: Dict = {}
|
||||
first_props: Dict = {}
|
||||
|
||||
def_errors: Dict[str, Exception] = {}
|
||||
src_errors: DefaultDict[str, Dict[str, Exception]] = defaultdict(dict)
|
||||
@@ -90,6 +90,7 @@ def match_defs_renderers( # noqa: C901, PLR0912
|
||||
if templates_dir is not None:
|
||||
props["template_dir"] = templates_dir
|
||||
|
||||
revs = []
|
||||
for rev, inner_id, plot_definition in group:
|
||||
plot_sources = infer_data_sources(inner_id, plot_definition)
|
||||
definitions_data = plots_data.get_definition_data(plot_sources, rev)
|
||||
@@ -109,19 +110,24 @@ def match_defs_renderers( # noqa: C901, PLR0912
|
||||
|
||||
try:
|
||||
dps, rev_props = converter.flat_datapoints(rev)
|
||||
if dps and rev not in revs:
|
||||
revs.append(rev)
|
||||
except Exception as e: # noqa: BLE001
|
||||
logger.warning("In %r, %s", rev, str(e).lower())
|
||||
def_errors[rev] = e
|
||||
continue
|
||||
|
||||
if not final_props and rev_props:
|
||||
final_props = rev_props
|
||||
if not first_props and rev_props:
|
||||
first_props = rev_props
|
||||
plot_datapoints.extend(dps)
|
||||
|
||||
if "title" not in final_props:
|
||||
final_props["title"] = renderer_id
|
||||
if "title" not in first_props:
|
||||
first_props["title"] = renderer_id
|
||||
|
||||
if revs:
|
||||
first_props["revs_with_datapoints"] = revs
|
||||
|
||||
if renderer_cls is not None:
|
||||
renderer = renderer_cls(plot_datapoints, renderer_id, **final_props)
|
||||
renderer = renderer_cls(plot_datapoints, renderer_id, **first_props)
|
||||
renderers.append(RendererWithErrors(renderer, dict(src_errors), def_errors))
|
||||
return renderers
|
||||
|
||||
+33
-9
@@ -1,4 +1,4 @@
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Set
|
||||
|
||||
from dvc import prompt
|
||||
|
||||
@@ -7,6 +7,7 @@ from .scm_context import scm_context
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import Repo
|
||||
from .index import IndexView
|
||||
|
||||
|
||||
def _prepare_message(stage, changes):
|
||||
@@ -88,14 +89,7 @@ def commit_2_to_3(repo: "Repo", dry: bool = False):
|
||||
outs_filter=lambda o: o.hash_name == "md5-dos2unix",
|
||||
recursive=True,
|
||||
)
|
||||
migrated = set()
|
||||
for out in view.outs:
|
||||
dvcfile = out.stage.dvcfile.relpath
|
||||
if isinstance(out.stage.dvcfile, ProjectFile):
|
||||
lockfile = out.stage.dvcfile._lockfile.relpath
|
||||
migrated.add(f"{dvcfile} ({lockfile})")
|
||||
else:
|
||||
migrated.add(dvcfile)
|
||||
migrated = _migrateable_dvcfiles(view)
|
||||
if not migrated:
|
||||
ui.write("No DVC files in the repo to migrate to the 3.0 format.")
|
||||
return
|
||||
@@ -110,11 +104,41 @@ def commit_2_to_3(repo: "Repo", dry: bool = False):
|
||||
for out in stage.filter_outs(filter_info)
|
||||
if outs_filter is not None and outs_filter(out)
|
||||
}
|
||||
modified = False
|
||||
if outs:
|
||||
for out in outs:
|
||||
out.update_legacy_hash_name(force=True)
|
||||
modified = True
|
||||
deps = {dep for dep in stage.deps if not stage.is_import and dep.is_in_repo}
|
||||
if deps:
|
||||
for dep in deps:
|
||||
dep.update_legacy_hash_name(force=True)
|
||||
modified = True
|
||||
if modified:
|
||||
stage.save(allow_missing=True)
|
||||
stage.commit(allow_missing=True, relink=True)
|
||||
if not isinstance(stage.dvcfile, ProjectFile):
|
||||
ui.write(f"Updating DVC file '{stage.dvcfile.relpath}'")
|
||||
stage.dump(update_pipeline=False)
|
||||
|
||||
|
||||
def _migrateable_dvcfiles(view: "IndexView") -> Set[str]:
|
||||
from dvc.dvcfile import ProjectFile
|
||||
|
||||
migrated = set()
|
||||
for stage, filter_info in view._stage_infos:
|
||||
outs_filter = view._outs_filter
|
||||
dvcfile = stage.dvcfile.relpath
|
||||
assert outs_filter
|
||||
if any(outs_filter(out) for out in stage.filter_outs(filter_info)) or (
|
||||
not stage.is_import
|
||||
and any(
|
||||
dep.is_in_repo and dep.hash_name == "md5-dos2unix" for dep in stage.deps
|
||||
)
|
||||
):
|
||||
if isinstance(stage.dvcfile, ProjectFile):
|
||||
lockfile = stage.dvcfile._lockfile.relpath
|
||||
migrated.add(f"{dvcfile} ({lockfile})")
|
||||
else:
|
||||
migrated.add(dvcfile)
|
||||
return migrated
|
||||
|
||||
+6
-5
@@ -29,15 +29,16 @@ classifiers = [
|
||||
"Programming Language :: Python :: 3.11",
|
||||
]
|
||||
dynamic = ["version"]
|
||||
|
||||
dependencies = [
|
||||
"colorama>=0.3.9",
|
||||
"configobj>=5.0.6",
|
||||
"distro>=1.3",
|
||||
"dpath<3,>=2.1.0",
|
||||
"dvc-data>=2.22.1,<2.23.0",
|
||||
"dvc-data>=2.22.5,<2.23.0",
|
||||
"dvc-http>=2.29.0",
|
||||
"dvc-render>=0.3.1,<1",
|
||||
"dvc-studio-client>=0.13.0,<1",
|
||||
"dvc-render>=1.0.0,<2",
|
||||
"dvc-studio-client>=0.17.1,<1",
|
||||
"dvc-task>=0.3.0,<1",
|
||||
"flatten_dict<1,>=0.4.1",
|
||||
# https://github.com/iterative/dvc/issues/9654
|
||||
@@ -76,7 +77,7 @@ gdrive = ["dvc-gdrive==2.20"]
|
||||
gs = ["dvc-gs==2.22.1"]
|
||||
hdfs = ["dvc-hdfs==2.19"]
|
||||
lint = [
|
||||
"mypy==1.7.0",
|
||||
"mypy==1.7.1",
|
||||
"pandas-stubs",
|
||||
"types-colorama",
|
||||
"types-psutil",
|
||||
@@ -86,7 +87,7 @@ lint = [
|
||||
"types-toml",
|
||||
"types-tqdm",
|
||||
]
|
||||
oss = ["dvc-oss>=2.20.0"]
|
||||
oss = ["dvc-oss>=2.22.0"]
|
||||
s3 = ["dvc-s3==2.23.0"]
|
||||
ssh = ["dvc-ssh>=2.22.1,<3"]
|
||||
ssh_gssapi = ["dvc-ssh[gssapi]>=2.22.1,<3"]
|
||||
|
||||
@@ -165,7 +165,7 @@ def test_default_cache_type(dvc):
|
||||
@pytest.mark.skipif(os.name == "nt", reason="Not supported for Windows.")
|
||||
@pytest.mark.parametrize("group", [False, True])
|
||||
def test_shared_cache(tmp_dir, dvc, group):
|
||||
from dvc.fs import system
|
||||
from dvc_data.hashfile.db.local import umask
|
||||
|
||||
if group:
|
||||
with dvc.config.edit() as conf:
|
||||
@@ -178,7 +178,7 @@ def test_shared_cache(tmp_dir, dvc, group):
|
||||
tmp_dir.dvc_gen({"file": "file content", "dir": {"file2": "file 2 content"}})
|
||||
|
||||
file_mode = oct(0o444)
|
||||
dir_mode = oct(0o2775 if group else (0o777 & ~system.umask))
|
||||
dir_mode = oct(0o2775 if group else (0o777 & ~umask))
|
||||
for root, dnames, fnames in os.walk(cache_dir):
|
||||
for dname in dnames:
|
||||
path = os.path.join(root, dname)
|
||||
|
||||
@@ -11,7 +11,7 @@ from bs4 import BeautifulSoup
|
||||
from funcy import first
|
||||
|
||||
from dvc.cli import main
|
||||
from dvc.render import REVISION_FIELD, VERSION_FIELD
|
||||
from dvc.render import ANCHOR_DEFINITIONS, FILENAME, REVISION
|
||||
|
||||
JSON_OUT = "vis_data"
|
||||
|
||||
@@ -102,6 +102,9 @@ def verify_vega(
|
||||
html_result,
|
||||
json_result,
|
||||
split_json_result,
|
||||
title,
|
||||
x_label,
|
||||
y_label,
|
||||
):
|
||||
if isinstance(versions, str):
|
||||
versions = [versions]
|
||||
@@ -111,27 +114,60 @@ def verify_vega(
|
||||
assert j[0]["type"] == "vega"
|
||||
assert set(j[0]["revisions"]) == set(versions)
|
||||
|
||||
assert json_result[0]["datapoints"] == split_json_result[0]["datapoints"]
|
||||
assert set(versions) == set(json_result[0]["datapoints"].keys())
|
||||
assert (
|
||||
json_result[0]["content"]["data"]["values"]
|
||||
== split_json_result[0][ANCHOR_DEFINITIONS]["<DVC_METRIC_DATA>"]
|
||||
)
|
||||
|
||||
assert set(versions) == set(json_result[0]["revisions"])
|
||||
|
||||
assert json_result[0]["content"]["data"]["values"]
|
||||
assert html_result["data"]["values"]
|
||||
assert split_json_result[0]["content"]["data"]["values"] == "<DVC_METRIC_DATA>"
|
||||
|
||||
def _assert_templates_equal(html_template, filled_template, split_template):
|
||||
# besides data, json and split json should be equal
|
||||
path = ["data", "values"]
|
||||
content_str = json.dumps(split_json_result[0]["content"])
|
||||
assert "<DVC_METRIC_DATA>" in content_str
|
||||
assert "<DVC_METRIC_X_LABEL>" in content_str
|
||||
assert "<DVC_METRIC_Y_LABEL>" in content_str
|
||||
|
||||
def _assert_templates_equal(
|
||||
html_template, filled_template, split_template, title, x_label, y_label
|
||||
):
|
||||
# besides split anchors, json and split json should be equal
|
||||
paths = [["data", "values"], ["encoding", "color"]]
|
||||
tmp1 = deepcopy(html_template)
|
||||
tmp2 = deepcopy(filled_template)
|
||||
tmp3 = deepcopy(split_template)
|
||||
dpath.set(tmp1, path, {})
|
||||
dpath.set(tmp2, path, {})
|
||||
dpath.set(tmp3, path, {})
|
||||
tmp3 = json.loads(
|
||||
json.dumps(split_template)
|
||||
.replace('"<DVC_METRIC_PLOT_HEIGHT>"', "300")
|
||||
.replace('"<DVC_METRIC_PLOT_WIDTH>"', "300")
|
||||
.replace("<DVC_METRIC_TITLE>", title)
|
||||
.replace("<DVC_METRIC_X_LABEL>", x_label)
|
||||
.replace("<DVC_METRIC_Y_LABEL>", y_label)
|
||||
.replace(
|
||||
'"<DVC_METRIC_ZOOM_AND_PAN>"',
|
||||
json.dumps(
|
||||
{
|
||||
"name": "grid",
|
||||
"select": "interval",
|
||||
"bind": "scales",
|
||||
}
|
||||
),
|
||||
)
|
||||
)
|
||||
for path in paths:
|
||||
dpath.set(tmp1, path, {})
|
||||
dpath.set(tmp2, path, {})
|
||||
dpath.set(tmp3, path, {})
|
||||
|
||||
assert tmp1 == tmp2 == tmp3
|
||||
|
||||
_assert_templates_equal(
|
||||
html_result, json_result[0]["content"], split_json_result[0]["content"]
|
||||
html_result,
|
||||
json_result[0]["content"],
|
||||
split_json_result[0]["content"],
|
||||
title,
|
||||
x_label,
|
||||
y_label,
|
||||
)
|
||||
|
||||
|
||||
@@ -193,17 +229,13 @@ def test_repo_with_plots(tmp_dir, scm, dvc, capsys, run_copy_metrics, repo_with_
|
||||
] == _update_datapoints(
|
||||
linear_v1,
|
||||
{
|
||||
VERSION_FIELD: {
|
||||
"revision": "workspace",
|
||||
"filename": "linear.json",
|
||||
"field": "y",
|
||||
},
|
||||
REVISION: "workspace",
|
||||
},
|
||||
)
|
||||
assert html_result["linear.json"]["data"]["values"] == _update_datapoints(
|
||||
linear_v1,
|
||||
{
|
||||
REVISION_FIELD: "workspace",
|
||||
REVISION: "workspace",
|
||||
},
|
||||
)
|
||||
assert json_data["confusion.json"][0]["content"]["data"][
|
||||
@@ -211,27 +243,29 @@ def test_repo_with_plots(tmp_dir, scm, dvc, capsys, run_copy_metrics, repo_with_
|
||||
] == _update_datapoints(
|
||||
confusion_v1,
|
||||
{
|
||||
VERSION_FIELD: {
|
||||
"revision": "workspace",
|
||||
"filename": "confusion.json",
|
||||
"field": "actual",
|
||||
},
|
||||
REVISION: "workspace",
|
||||
},
|
||||
)
|
||||
assert html_result["confusion.json"]["data"]["values"] == _update_datapoints(
|
||||
confusion_v1,
|
||||
{
|
||||
REVISION_FIELD: "workspace",
|
||||
REVISION: "workspace",
|
||||
},
|
||||
)
|
||||
verify_image(tmp_dir, "workspace", "image.png", image_v1, html_path, json_data)
|
||||
|
||||
for plot in ["linear.json", "confusion.json"]:
|
||||
for plot, title, x_label, y_label in [
|
||||
("linear.json", "linear", "x", "y"),
|
||||
("confusion.json", "confusion matrix", "predicted", "actual"),
|
||||
]:
|
||||
verify_vega(
|
||||
"workspace",
|
||||
html_result[plot],
|
||||
json_data[plot],
|
||||
split_json_data[plot],
|
||||
title,
|
||||
x_label,
|
||||
y_label,
|
||||
)
|
||||
|
||||
verify_vega_props("confusion.json", json_data, **confusion_props)
|
||||
@@ -250,12 +284,18 @@ def test_repo_with_plots(tmp_dir, scm, dvc, capsys, run_copy_metrics, repo_with_
|
||||
verify_image(tmp_dir, "workspace", "image.png", image_v2, html_path, json_data)
|
||||
verify_image(tmp_dir, "HEAD", "image.png", image_v1, html_path, json_data)
|
||||
|
||||
for plot in ["linear.json", "confusion.json"]:
|
||||
for plot, title, x_label, y_label in [
|
||||
("linear.json", "linear", "x", "y"),
|
||||
("confusion.json", "confusion matrix", "predicted", "actual"),
|
||||
]:
|
||||
verify_vega(
|
||||
["HEAD", "workspace"],
|
||||
html_result[plot],
|
||||
json_data[plot],
|
||||
split_json_data[plot],
|
||||
title,
|
||||
x_label,
|
||||
y_label,
|
||||
)
|
||||
verify_vega_props("confusion.json", json_data, **confusion_props)
|
||||
path = tmp_dir / "subdir"
|
||||
@@ -277,31 +317,23 @@ def test_repo_with_plots(tmp_dir, scm, dvc, capsys, run_copy_metrics, repo_with_
|
||||
] == _update_datapoints(
|
||||
linear_v2,
|
||||
{
|
||||
VERSION_FIELD: {
|
||||
"revision": "workspace",
|
||||
"filename": "../linear.json",
|
||||
"field": "y",
|
||||
},
|
||||
REVISION: "workspace",
|
||||
},
|
||||
) + _update_datapoints(
|
||||
linear_v1,
|
||||
{
|
||||
VERSION_FIELD: {
|
||||
"revision": "HEAD",
|
||||
"filename": "../linear.json",
|
||||
"field": "y",
|
||||
},
|
||||
REVISION: "HEAD",
|
||||
},
|
||||
)
|
||||
assert html_result["../linear.json"]["data"]["values"] == _update_datapoints(
|
||||
linear_v2,
|
||||
{
|
||||
REVISION_FIELD: "workspace",
|
||||
REVISION: "workspace",
|
||||
},
|
||||
) + _update_datapoints(
|
||||
linear_v1,
|
||||
{
|
||||
REVISION_FIELD: "HEAD",
|
||||
REVISION: "HEAD",
|
||||
},
|
||||
)
|
||||
assert json_data["../confusion.json"][0]["content"]["data"][
|
||||
@@ -309,43 +341,38 @@ def test_repo_with_plots(tmp_dir, scm, dvc, capsys, run_copy_metrics, repo_with_
|
||||
] == _update_datapoints(
|
||||
confusion_v2,
|
||||
{
|
||||
VERSION_FIELD: {
|
||||
"revision": "workspace",
|
||||
"filename": "../confusion.json",
|
||||
"field": "actual",
|
||||
},
|
||||
REVISION: "workspace",
|
||||
},
|
||||
) + _update_datapoints(
|
||||
confusion_v1,
|
||||
{
|
||||
VERSION_FIELD: {
|
||||
"revision": "HEAD",
|
||||
"filename": "../confusion.json",
|
||||
"field": "actual",
|
||||
},
|
||||
REVISION: "HEAD",
|
||||
},
|
||||
)
|
||||
assert html_result["../confusion.json"]["data"]["values"] == _update_datapoints(
|
||||
confusion_v2,
|
||||
{
|
||||
REVISION_FIELD: "workspace",
|
||||
REVISION: "workspace",
|
||||
},
|
||||
) + _update_datapoints(
|
||||
confusion_v1,
|
||||
{
|
||||
REVISION_FIELD: "HEAD",
|
||||
REVISION: "HEAD",
|
||||
},
|
||||
)
|
||||
|
||||
for plot in [
|
||||
"../linear.json",
|
||||
"../confusion.json",
|
||||
for plot, title, x_label, y_label in [
|
||||
("../linear.json", "linear", "x", "y"),
|
||||
("../confusion.json", "confusion matrix", "predicted", "actual"),
|
||||
]:
|
||||
verify_vega(
|
||||
["HEAD", "workspace"],
|
||||
html_result[plot],
|
||||
json_data[plot],
|
||||
split_json_data[plot],
|
||||
title,
|
||||
x_label,
|
||||
y_label,
|
||||
)
|
||||
verify_image(
|
||||
path,
|
||||
@@ -434,7 +461,7 @@ def test_repo_with_config_plots(tmp_dir, capsys, repo_with_config_plots):
|
||||
repo_state = repo_with_config_plots()
|
||||
plots = next(repo_state)
|
||||
|
||||
html_path, _, __ = call(capsys)
|
||||
html_path, _, split_json_result = call(capsys)
|
||||
|
||||
assert os.path.exists(html_path)
|
||||
html_result = extract_vega_specs(
|
||||
@@ -444,20 +471,28 @@ def test_repo_with_config_plots(tmp_dir, capsys, repo_with_config_plots):
|
||||
"confusion_train_vs_test",
|
||||
],
|
||||
)
|
||||
|
||||
ble = _update_datapoints(
|
||||
plots["data"]["linear_train.json"],
|
||||
{
|
||||
REVISION_FIELD: "linear_train.json",
|
||||
REVISION: "workspace",
|
||||
FILENAME: "linear_train.json",
|
||||
},
|
||||
) + _update_datapoints(
|
||||
plots["data"]["linear_test.json"],
|
||||
{
|
||||
REVISION_FIELD: "linear_test.json",
|
||||
REVISION: "workspace",
|
||||
FILENAME: "linear_test.json",
|
||||
},
|
||||
)
|
||||
|
||||
assert html_result["linear_train_vs_test"]["data"]["values"] == ble
|
||||
# TODO check json results once vscode is able to handle flexible plots
|
||||
assert (
|
||||
split_json_result["data"]["linear_train_vs_test"][0][ANCHOR_DEFINITIONS][
|
||||
"<DVC_METRIC_DATA>"
|
||||
]
|
||||
== ble
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.vscode
|
||||
@@ -466,7 +501,7 @@ def test_repo_with_dvclive_plots(tmp_dir, capsys, repo_with_dvclive_plots):
|
||||
|
||||
for s in ("show", "diff"):
|
||||
_, json_result, split_json_result = call(capsys, subcommand=s)
|
||||
expected_result = {
|
||||
expected_result: Dict[str, Dict[str, list[str]]] = {
|
||||
"data": {
|
||||
"dvclive/plots/metrics/metric.tsv": [],
|
||||
},
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
from dvc_studio_client.auth import AuthenticationExpired
|
||||
|
||||
from dvc.cli import main
|
||||
from dvc.utils.studio import STUDIO_URL
|
||||
|
||||
|
||||
def test_studio_login_token_check_failed(mocker):
|
||||
mocker.patch(
|
||||
"dvc_studio_client.auth.get_access_token", side_effect=AuthenticationExpired
|
||||
)
|
||||
|
||||
assert main(["studio", "login"]) == 1
|
||||
|
||||
|
||||
def test_studio_login_success(mocker, dvc):
|
||||
mocker.patch(
|
||||
"dvc_studio_client.auth.get_access_token",
|
||||
return_value=("token_name", "isat_access_token"),
|
||||
)
|
||||
|
||||
assert main(["studio", "login"]) == 0
|
||||
|
||||
config = dvc.config.load_one("global")
|
||||
assert config["studio"]["token"] == "isat_access_token"
|
||||
assert config["studio"]["url"] == STUDIO_URL
|
||||
|
||||
|
||||
def test_studio_login_arguments(mocker):
|
||||
mock = mocker.patch(
|
||||
"dvc_studio_client.auth.get_access_token",
|
||||
return_value=("token_name", "isat_access_token"),
|
||||
)
|
||||
|
||||
assert (
|
||||
main(
|
||||
[
|
||||
"studio",
|
||||
"login",
|
||||
"--name",
|
||||
"token_name",
|
||||
"--hostname",
|
||||
"https://example.com",
|
||||
"--scopes",
|
||||
"experiments",
|
||||
"--no-open",
|
||||
]
|
||||
)
|
||||
== 0
|
||||
)
|
||||
|
||||
mock.assert_called_with(
|
||||
token_name="token_name",
|
||||
hostname="https://example.com",
|
||||
scopes="experiments",
|
||||
use_device_code=True,
|
||||
client_name="DVC",
|
||||
)
|
||||
|
||||
|
||||
def test_studio_logout(dvc):
|
||||
with dvc.config.edit("global") as conf:
|
||||
conf["studio"]["token"] = "isat_access_token"
|
||||
|
||||
assert main(["studio", "logout"]) == 0
|
||||
config = dvc.config.load_one("global")
|
||||
assert "token" not in config["studio"]
|
||||
|
||||
assert main(["studio", "logout"]) == 1
|
||||
|
||||
|
||||
def test_studio_token(dvc, capsys):
|
||||
with dvc.config.edit("global") as conf:
|
||||
conf["studio"]["token"] = "isat_access_token"
|
||||
|
||||
assert main(["studio", "token"]) == 0
|
||||
assert capsys.readouterr().out == "isat_access_token\n"
|
||||
|
||||
with dvc.config.edit("global") as conf:
|
||||
del conf["studio"]["token"]
|
||||
|
||||
assert main(["studio", "token"]) == 1
|
||||
@@ -1,109 +1,87 @@
|
||||
from dvc.render import REVISION_FIELD, REVISIONS_KEY, SRC_FIELD, TYPE_KEY, VERSION_FIELD
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from dvc.render import ANCHOR_DEFINITIONS, FILENAME, REVISION, REVISIONS, SRC, TYPE_KEY
|
||||
from dvc.render.convert import to_json
|
||||
|
||||
|
||||
def test_to_json_vega(mocker):
|
||||
vega_renderer = mocker.MagicMock()
|
||||
vega_renderer.TYPE = "vega"
|
||||
vega_renderer.get_revs.return_value = ["bar", "foo"]
|
||||
vega_renderer.get_filled_template.return_value = {"this": "is vega"}
|
||||
vega_renderer.datapoints = [
|
||||
{
|
||||
"x": 1,
|
||||
"y": 2,
|
||||
VERSION_FIELD: {"revision": "foo"},
|
||||
"filename": "foo.json",
|
||||
},
|
||||
{
|
||||
"x": 2,
|
||||
"y": 1,
|
||||
VERSION_FIELD: {"revision": "bar"},
|
||||
"filename": "foo.json",
|
||||
},
|
||||
]
|
||||
result = to_json(vega_renderer)
|
||||
assert result[0] == {
|
||||
TYPE_KEY: vega_renderer.TYPE,
|
||||
REVISIONS_KEY: ["bar", "foo"],
|
||||
REVISIONS: ["bar", "foo"],
|
||||
"content": {"this": "is vega"},
|
||||
"datapoints": {
|
||||
"foo": [
|
||||
{
|
||||
"x": 1,
|
||||
"y": 2,
|
||||
"filename": "foo.json",
|
||||
VERSION_FIELD: {"revision": "foo"},
|
||||
},
|
||||
],
|
||||
"bar": [
|
||||
{
|
||||
"x": 2,
|
||||
"y": 1,
|
||||
"filename": "foo.json",
|
||||
VERSION_FIELD: {"revision": "bar"},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
vega_renderer.get_filled_template.assert_called()
|
||||
|
||||
|
||||
@pytest.mark.vscode
|
||||
def test_to_json_vega_split(mocker):
|
||||
revs = ["bar", "foo"]
|
||||
content = json.dumps(
|
||||
{
|
||||
"this": "is split vega",
|
||||
"encoding": {"color": "<DVC_METRIC_COLOR>"},
|
||||
"data": {"values": "<DVC_METRIC_DATA>"},
|
||||
}
|
||||
)
|
||||
anchor_definitions = {
|
||||
"<DVC_METRIC_COLOR>": {
|
||||
"field": "rev",
|
||||
"scale": {
|
||||
"domain": revs,
|
||||
"range": ["#ff0000", "#00ff00"],
|
||||
},
|
||||
},
|
||||
"<DVC_METRIC_DATA>": [
|
||||
{
|
||||
"x": 1,
|
||||
"y": 2,
|
||||
REVISION: "foo",
|
||||
FILENAME: "foo.json",
|
||||
},
|
||||
{
|
||||
"x": 2,
|
||||
"y": 1,
|
||||
REVISION: "bar",
|
||||
FILENAME: "foo.json",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
vega_renderer = mocker.MagicMock()
|
||||
vega_renderer.TYPE = "vega"
|
||||
vega_renderer.get_filled_template.return_value = {"this": "is split vega"}
|
||||
vega_renderer.datapoints = [
|
||||
{
|
||||
"x": 1,
|
||||
"y": 2,
|
||||
VERSION_FIELD: {"revision": "foo"},
|
||||
"filename": "foo.json",
|
||||
},
|
||||
{
|
||||
"x": 2,
|
||||
"y": 1,
|
||||
VERSION_FIELD: {"revision": "bar"},
|
||||
"filename": "foo.json",
|
||||
},
|
||||
]
|
||||
vega_renderer.get_partial_filled_template.return_value = (
|
||||
content,
|
||||
{ANCHOR_DEFINITIONS: anchor_definitions},
|
||||
)
|
||||
vega_renderer.get_revs.return_value = ["bar", "foo"]
|
||||
|
||||
result = to_json(vega_renderer, split=True)
|
||||
assert result[0] == {
|
||||
ANCHOR_DEFINITIONS: anchor_definitions,
|
||||
TYPE_KEY: vega_renderer.TYPE,
|
||||
REVISIONS_KEY: ["bar", "foo"],
|
||||
"content": {"this": "is split vega"},
|
||||
"datapoints": {
|
||||
"foo": [
|
||||
{
|
||||
"x": 1,
|
||||
"y": 2,
|
||||
"filename": "foo.json",
|
||||
VERSION_FIELD: {"revision": "foo"},
|
||||
}
|
||||
],
|
||||
"bar": [
|
||||
{
|
||||
"x": 2,
|
||||
"y": 1,
|
||||
"filename": "foo.json",
|
||||
VERSION_FIELD: {"revision": "bar"},
|
||||
}
|
||||
],
|
||||
},
|
||||
REVISIONS: revs,
|
||||
"content": content,
|
||||
}
|
||||
vega_renderer.get_filled_template.assert_called_with(
|
||||
as_string=False, skip_anchors=["data"]
|
||||
)
|
||||
vega_renderer.get_partial_filled_template.assert_called_once()
|
||||
|
||||
|
||||
def test_to_json_image(mocker):
|
||||
image_renderer = mocker.MagicMock()
|
||||
image_renderer.TYPE = "image"
|
||||
image_renderer.datapoints = [
|
||||
{SRC_FIELD: "contentfoo", REVISION_FIELD: "foo"},
|
||||
{SRC_FIELD: "contentbar", REVISION_FIELD: "bar"},
|
||||
{SRC: "contentfoo", REVISION: "foo"},
|
||||
{SRC: "contentbar", REVISION: "bar"},
|
||||
]
|
||||
result = to_json(image_renderer)
|
||||
assert result[0] == {
|
||||
"url": image_renderer.datapoints[0].get(SRC_FIELD),
|
||||
REVISIONS_KEY: [image_renderer.datapoints[0].get(REVISION_FIELD)],
|
||||
"url": image_renderer.datapoints[0].get(SRC),
|
||||
REVISIONS: [image_renderer.datapoints[0].get(REVISION)],
|
||||
TYPE_KEY: image_renderer.TYPE,
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from dvc.render import REVISION_FIELD, SRC_FIELD
|
||||
from dvc.render import FILENAME, REVISION, SRC
|
||||
from dvc.render.converter.image import ImageConverter
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ def test_image_converter_no_out():
|
||||
datapoints, _ = converter.flat_datapoints("r")
|
||||
|
||||
assert datapoints[0] == {
|
||||
REVISION_FIELD: "r",
|
||||
"filename": "image.png",
|
||||
SRC_FIELD: converter._encode_image(b"content"),
|
||||
REVISION: "r",
|
||||
FILENAME: "image.png",
|
||||
SRC: converter._encode_image(b"content"),
|
||||
}
|
||||
|
||||
|
||||
@@ -21,9 +21,9 @@ def test_image_converter_with_out(tmp_dir):
|
||||
datapoints, _ = converter.flat_datapoints("r")
|
||||
|
||||
assert datapoints[0] == {
|
||||
REVISION_FIELD: "r",
|
||||
"filename": "image.png",
|
||||
SRC_FIELD: str(tmp_dir / "foo" / "r_image.png"),
|
||||
REVISION: "r",
|
||||
FILENAME: "image.png",
|
||||
SRC: str(tmp_dir / "foo" / "r_image.png"),
|
||||
}
|
||||
|
||||
assert (tmp_dir / "foo" / "r_image.png").read_bytes() == b"content"
|
||||
@@ -37,9 +37,9 @@ def test_image_converter_with_slash_in_revision(tmp_dir):
|
||||
datapoints, _ = converter.flat_datapoints("feature/r")
|
||||
|
||||
assert datapoints[0] == {
|
||||
REVISION_FIELD: "feature/r",
|
||||
"filename": "image.png",
|
||||
SRC_FIELD: str(tmp_dir / "foo" / "feature_r_image.png"),
|
||||
REVISION: "feature/r",
|
||||
FILENAME: "image.png",
|
||||
SRC: str(tmp_dir / "foo" / "feature_r_image.png"),
|
||||
}
|
||||
|
||||
assert (tmp_dir / "foo" / "feature_r_image.png").read_bytes() == b"content"
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import pytest
|
||||
from funcy import set_in
|
||||
|
||||
from dvc.render import VERSION_FIELD
|
||||
from dvc.render import FIELD, FILENAME, REVISION
|
||||
from dvc.render.converter.vega import VegaConverter
|
||||
from dvc.render.match import PlotsData, _squash_plots_properties, match_defs_renderers
|
||||
from dvc.render.match import (
|
||||
PlotsData,
|
||||
_squash_plots_properties,
|
||||
match_defs_renderers,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -158,25 +162,23 @@ def test_match_renderers(M):
|
||||
renderer = renderer_with_errors[0]
|
||||
assert renderer.datapoints == [
|
||||
{
|
||||
VERSION_FIELD: {
|
||||
"revision": "v1",
|
||||
"filename": "file.json",
|
||||
"field": "y",
|
||||
},
|
||||
REVISION: "v1",
|
||||
FILENAME: "file.json",
|
||||
FIELD: "y",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
},
|
||||
{
|
||||
VERSION_FIELD: {
|
||||
"revision": "v1",
|
||||
"filename": "file.json",
|
||||
"field": "y",
|
||||
},
|
||||
REVISION: "v1",
|
||||
FILENAME: "file.json",
|
||||
FIELD: "y",
|
||||
"x": 2,
|
||||
"y": 2,
|
||||
},
|
||||
]
|
||||
assert renderer.properties == {
|
||||
"anchors_y_definitions": [{FILENAME: "file.json", FIELD: "y"}],
|
||||
"revs_with_datapoints": ["v1"],
|
||||
"title": "plot_id_1",
|
||||
"x": "x",
|
||||
"y": "y",
|
||||
|
||||
@@ -3,7 +3,7 @@ from collections import OrderedDict
|
||||
import pytest
|
||||
|
||||
from dvc.exceptions import DvcException
|
||||
from dvc.render import VERSION_FIELD
|
||||
from dvc.render import FIELD, FILENAME, REVISION
|
||||
from dvc.render.converter.vega import FieldNotFoundError, VegaConverter, _lists
|
||||
|
||||
|
||||
@@ -35,23 +35,25 @@ def test_finding_lists(dictionary, expected_result):
|
||||
{
|
||||
"v": 1,
|
||||
"step": 0,
|
||||
VERSION_FIELD: {
|
||||
"revision": "r",
|
||||
"filename": "f",
|
||||
"field": "v",
|
||||
},
|
||||
REVISION: "r",
|
||||
FILENAME: "f",
|
||||
FIELD: "v",
|
||||
},
|
||||
{
|
||||
"v": 2,
|
||||
"step": 1,
|
||||
VERSION_FIELD: {
|
||||
"revision": "r",
|
||||
"filename": "f",
|
||||
"field": "v",
|
||||
},
|
||||
REVISION: "r",
|
||||
FILENAME: "f",
|
||||
FIELD: "v",
|
||||
},
|
||||
],
|
||||
{"x": "step", "y": "v", "x_label": "step", "y_label": "v"},
|
||||
{
|
||||
"anchors_y_definitions": [{FILENAME: "f", FIELD: "v"}],
|
||||
"x": "step",
|
||||
"y": "v",
|
||||
"x_label": "step",
|
||||
"y_label": "v",
|
||||
},
|
||||
id="default_x_y",
|
||||
),
|
||||
pytest.param(
|
||||
@@ -61,23 +63,25 @@ def test_finding_lists(dictionary, expected_result):
|
||||
{
|
||||
"v": 1,
|
||||
"v2": 0.1,
|
||||
VERSION_FIELD: {
|
||||
"revision": "r",
|
||||
"filename": "f",
|
||||
"field": "v2",
|
||||
},
|
||||
REVISION: "r",
|
||||
FILENAME: "f",
|
||||
FIELD: "v2",
|
||||
},
|
||||
{
|
||||
"v": 2,
|
||||
"v2": 0.2,
|
||||
VERSION_FIELD: {
|
||||
"revision": "r",
|
||||
"filename": "f",
|
||||
"field": "v2",
|
||||
},
|
||||
REVISION: "r",
|
||||
FILENAME: "f",
|
||||
FIELD: "v2",
|
||||
},
|
||||
],
|
||||
{"x": "v", "y": "v2", "x_label": "v", "y_label": "v2"},
|
||||
{
|
||||
"anchors_y_definitions": [{FILENAME: "f", FIELD: "v2"}],
|
||||
"x": "v",
|
||||
"y": "v2",
|
||||
"x_label": "v",
|
||||
"y_label": "v2",
|
||||
},
|
||||
id="choose_x_y",
|
||||
),
|
||||
pytest.param(
|
||||
@@ -99,23 +103,25 @@ def test_finding_lists(dictionary, expected_result):
|
||||
{
|
||||
"v": 1,
|
||||
"v2": 0.1,
|
||||
VERSION_FIELD: {
|
||||
"revision": "r",
|
||||
"filename": "f",
|
||||
"field": "v2",
|
||||
},
|
||||
REVISION: "r",
|
||||
FILENAME: "f",
|
||||
FIELD: "v2",
|
||||
},
|
||||
{
|
||||
"v": 2,
|
||||
"v2": 0.2,
|
||||
VERSION_FIELD: {
|
||||
"revision": "r",
|
||||
"filename": "f",
|
||||
"field": "v2",
|
||||
},
|
||||
REVISION: "r",
|
||||
FILENAME: "f",
|
||||
FIELD: "v2",
|
||||
},
|
||||
],
|
||||
{"x": "v", "y": "v2", "x_label": "x", "y_label": "y"},
|
||||
{
|
||||
"anchors_y_definitions": [{FILENAME: "f", FIELD: "v2"}],
|
||||
"x": "v",
|
||||
"y": "v2",
|
||||
"x_label": "x",
|
||||
"y_label": "y",
|
||||
},
|
||||
id="find_in_nested_structure",
|
||||
),
|
||||
pytest.param(
|
||||
@@ -123,44 +129,36 @@ def test_finding_lists(dictionary, expected_result):
|
||||
{"y": {"f": ["v", "v2"]}},
|
||||
[
|
||||
{
|
||||
VERSION_FIELD: {
|
||||
"revision": "r",
|
||||
"filename": "f",
|
||||
"field": "v",
|
||||
},
|
||||
REVISION: "r",
|
||||
FILENAME: "f",
|
||||
FIELD: "v",
|
||||
"dvc_inferred_y_value": 1,
|
||||
"v": 1,
|
||||
"v2": 0.1,
|
||||
"step": 0,
|
||||
},
|
||||
{
|
||||
VERSION_FIELD: {
|
||||
"revision": "r",
|
||||
"filename": "f",
|
||||
"field": "v",
|
||||
},
|
||||
REVISION: "r",
|
||||
FILENAME: "f",
|
||||
FIELD: "v",
|
||||
"dvc_inferred_y_value": 2,
|
||||
"v": 2,
|
||||
"v2": 0.2,
|
||||
"step": 1,
|
||||
},
|
||||
{
|
||||
VERSION_FIELD: {
|
||||
"revision": "r",
|
||||
"filename": "f",
|
||||
"field": "v2",
|
||||
},
|
||||
REVISION: "r",
|
||||
FILENAME: "f",
|
||||
FIELD: "v2",
|
||||
"dvc_inferred_y_value": 0.1,
|
||||
"v2": 0.1,
|
||||
"v": 1,
|
||||
"step": 0,
|
||||
},
|
||||
{
|
||||
VERSION_FIELD: {
|
||||
"revision": "r",
|
||||
"filename": "f",
|
||||
"field": "v2",
|
||||
},
|
||||
REVISION: "r",
|
||||
FILENAME: "f",
|
||||
FIELD: "v2",
|
||||
"v": 2,
|
||||
"v2": 0.2,
|
||||
"dvc_inferred_y_value": 0.2,
|
||||
@@ -168,6 +166,10 @@ def test_finding_lists(dictionary, expected_result):
|
||||
},
|
||||
],
|
||||
{
|
||||
"anchors_y_definitions": [
|
||||
{FILENAME: "f", FIELD: "v"},
|
||||
{FILENAME: "f", FIELD: "v2"},
|
||||
],
|
||||
"x": "step",
|
||||
"y": "dvc_inferred_y_value",
|
||||
"y_label": "y",
|
||||
@@ -189,47 +191,43 @@ def test_finding_lists(dictionary, expected_result):
|
||||
"z": 3,
|
||||
"v": 1,
|
||||
"step": 0,
|
||||
VERSION_FIELD: {
|
||||
"revision": "r",
|
||||
"filename": "f",
|
||||
"field": "v",
|
||||
},
|
||||
REVISION: "r",
|
||||
FILENAME: "f",
|
||||
FIELD: "v",
|
||||
},
|
||||
{
|
||||
"dvc_inferred_y_value": 2,
|
||||
"z": 4,
|
||||
"step": 1,
|
||||
"v": 2,
|
||||
VERSION_FIELD: {
|
||||
"revision": "r",
|
||||
"filename": "f",
|
||||
"field": "v",
|
||||
},
|
||||
REVISION: "r",
|
||||
FILENAME: "f",
|
||||
FIELD: "v",
|
||||
},
|
||||
{
|
||||
"dvc_inferred_y_value": 3,
|
||||
"v": 1,
|
||||
"z": 3,
|
||||
"step": 0,
|
||||
VERSION_FIELD: {
|
||||
"revision": "r",
|
||||
"filename": "f",
|
||||
"field": "z",
|
||||
},
|
||||
REVISION: "r",
|
||||
FILENAME: "f",
|
||||
FIELD: "z",
|
||||
},
|
||||
{
|
||||
"dvc_inferred_y_value": 4,
|
||||
"v": 2,
|
||||
"z": 4,
|
||||
"step": 1,
|
||||
VERSION_FIELD: {
|
||||
"revision": "r",
|
||||
"filename": "f",
|
||||
"field": "z",
|
||||
},
|
||||
REVISION: "r",
|
||||
FILENAME: "f",
|
||||
FIELD: "z",
|
||||
},
|
||||
],
|
||||
{
|
||||
"anchors_y_definitions": [
|
||||
{FILENAME: "f", FIELD: "v"},
|
||||
{FILENAME: "f", FIELD: "z"},
|
||||
],
|
||||
"x": "step",
|
||||
"y": "dvc_inferred_y_value",
|
||||
"y_label": "y",
|
||||
@@ -247,32 +245,35 @@ def test_finding_lists(dictionary, expected_result):
|
||||
{
|
||||
"v": 1,
|
||||
"v2": 0.1,
|
||||
VERSION_FIELD: {
|
||||
"revision": "r",
|
||||
"filename": "f",
|
||||
"field": "v2",
|
||||
},
|
||||
REVISION: "r",
|
||||
FILENAME: "f",
|
||||
FIELD: "v2",
|
||||
},
|
||||
{
|
||||
"v": 2,
|
||||
"v2": 0.2,
|
||||
VERSION_FIELD: {
|
||||
"revision": "r",
|
||||
"filename": "f",
|
||||
"field": "v2",
|
||||
},
|
||||
REVISION: "r",
|
||||
FILENAME: "f",
|
||||
FIELD: "v2",
|
||||
},
|
||||
{
|
||||
"v": 3,
|
||||
"v2": 0.3,
|
||||
VERSION_FIELD: {
|
||||
"revision": "r",
|
||||
"filename": "f2",
|
||||
"field": "v2",
|
||||
},
|
||||
REVISION: "r",
|
||||
FILENAME: "f2",
|
||||
FIELD: "v2",
|
||||
},
|
||||
],
|
||||
{"x": "v", "y": "v2", "x_label": "v", "y_label": "v2"},
|
||||
{
|
||||
"anchors_y_definitions": [
|
||||
{FILENAME: "f", FIELD: "v2"},
|
||||
{FILENAME: "f2", FIELD: "v2"},
|
||||
],
|
||||
"x": "v",
|
||||
"y": "v2",
|
||||
"x_label": "v",
|
||||
"y_label": "v2",
|
||||
},
|
||||
id="multi_file_json",
|
||||
),
|
||||
pytest.param(
|
||||
@@ -284,47 +285,43 @@ def test_finding_lists(dictionary, expected_result):
|
||||
"v": 1,
|
||||
"v2": 0.1,
|
||||
"step": 0,
|
||||
VERSION_FIELD: {
|
||||
"revision": "r",
|
||||
"filename": "f",
|
||||
"field": "v",
|
||||
},
|
||||
REVISION: "r",
|
||||
FILENAME: "f",
|
||||
FIELD: "v",
|
||||
},
|
||||
{
|
||||
"dvc_inferred_y_value": 2,
|
||||
"v": 2,
|
||||
"v2": 0.2,
|
||||
"step": 1,
|
||||
VERSION_FIELD: {
|
||||
"revision": "r",
|
||||
"filename": "f",
|
||||
"field": "v",
|
||||
},
|
||||
REVISION: "r",
|
||||
FILENAME: "f",
|
||||
FIELD: "v",
|
||||
},
|
||||
{
|
||||
"dvc_inferred_y_value": 0.1,
|
||||
"v": 1,
|
||||
"v2": 0.1,
|
||||
"step": 0,
|
||||
VERSION_FIELD: {
|
||||
"revision": "r",
|
||||
"filename": "f",
|
||||
"field": "v2",
|
||||
},
|
||||
REVISION: "r",
|
||||
FILENAME: "f",
|
||||
FIELD: "v2",
|
||||
},
|
||||
{
|
||||
"dvc_inferred_y_value": 0.2,
|
||||
"v": 2,
|
||||
"v2": 0.2,
|
||||
"step": 1,
|
||||
VERSION_FIELD: {
|
||||
"revision": "r",
|
||||
"filename": "f",
|
||||
"field": "v2",
|
||||
},
|
||||
REVISION: "r",
|
||||
FILENAME: "f",
|
||||
FIELD: "v2",
|
||||
},
|
||||
],
|
||||
{
|
||||
"anchors_y_definitions": [
|
||||
{FILENAME: "f", FIELD: "v"},
|
||||
{FILENAME: "f", FIELD: "v2"},
|
||||
],
|
||||
"x": "step",
|
||||
"y": "dvc_inferred_y_value",
|
||||
"x_label": "step",
|
||||
@@ -344,35 +341,34 @@ def test_finding_lists(dictionary, expected_result):
|
||||
"v": 1,
|
||||
"v2": 0.1,
|
||||
"v3": 0.01,
|
||||
VERSION_FIELD: {
|
||||
"revision": "r",
|
||||
"filename": "f",
|
||||
"field": "v2",
|
||||
},
|
||||
REVISION: "r",
|
||||
FILENAME: "f",
|
||||
FIELD: "v2",
|
||||
},
|
||||
{
|
||||
"dvc_inferred_y_value": 0.01,
|
||||
"v": 1,
|
||||
"v2": 0.1,
|
||||
"v3": 0.01,
|
||||
VERSION_FIELD: {
|
||||
"revision": "r",
|
||||
"filename": "f",
|
||||
"field": "v3",
|
||||
},
|
||||
REVISION: "r",
|
||||
FILENAME: "f",
|
||||
FIELD: "v3",
|
||||
},
|
||||
{
|
||||
"dvc_inferred_y_value": 0.1,
|
||||
"v": 1,
|
||||
"v2": 0.1,
|
||||
VERSION_FIELD: {
|
||||
"revision": "r",
|
||||
"filename": "f2",
|
||||
"field": "v2",
|
||||
},
|
||||
REVISION: "r",
|
||||
FILENAME: "f2",
|
||||
FIELD: "v2",
|
||||
},
|
||||
],
|
||||
{
|
||||
"anchors_y_definitions": [
|
||||
{FILENAME: "f", FIELD: "v2"},
|
||||
{FILENAME: "f", FIELD: "v3"},
|
||||
{FILENAME: "f2", FIELD: "v2"},
|
||||
],
|
||||
"x": "v",
|
||||
"y": "dvc_inferred_y_value",
|
||||
"x_label": "v",
|
||||
@@ -390,23 +386,23 @@ def test_finding_lists(dictionary, expected_result):
|
||||
{
|
||||
"v": 1,
|
||||
"v2": 0.1,
|
||||
VERSION_FIELD: {
|
||||
"revision": "r",
|
||||
"filename": "f",
|
||||
"field": "v2",
|
||||
},
|
||||
REVISION: "r",
|
||||
FILENAME: "f",
|
||||
FIELD: "v2",
|
||||
},
|
||||
{
|
||||
"v": 1,
|
||||
"v2": 0.1,
|
||||
VERSION_FIELD: {
|
||||
"revision": "r",
|
||||
"filename": "f2",
|
||||
"field": "v2",
|
||||
},
|
||||
REVISION: "r",
|
||||
FILENAME: "f2",
|
||||
FIELD: "v2",
|
||||
},
|
||||
],
|
||||
{
|
||||
"anchors_y_definitions": [
|
||||
{FILENAME: "f", FIELD: "v2"},
|
||||
{FILENAME: "f2", FIELD: "v2"},
|
||||
],
|
||||
"x": "v",
|
||||
"y": "v2",
|
||||
"x_label": "v",
|
||||
@@ -414,6 +410,107 @@ def test_finding_lists(dictionary, expected_result):
|
||||
},
|
||||
id="multi_file_y_same_prefix",
|
||||
),
|
||||
pytest.param(
|
||||
{
|
||||
"f": {"metric": [{"x1": 1, "v": 0.1}]},
|
||||
"f2": {"metric": [{"x2": 100, "v": 0.1}]},
|
||||
},
|
||||
{"y": {"f": ["v"], "f2": ["v"]}, "x": {"f": "x1", "f2": "x2"}},
|
||||
[
|
||||
{
|
||||
"x1": 1,
|
||||
"v": 0.1,
|
||||
"dvc_inferred_x_value": 1,
|
||||
REVISION: "r",
|
||||
FILENAME: "f",
|
||||
FIELD: "v",
|
||||
},
|
||||
{
|
||||
"x2": 100,
|
||||
"v": 0.1,
|
||||
"dvc_inferred_x_value": 100,
|
||||
REVISION: "r",
|
||||
FILENAME: "f2",
|
||||
FIELD: "v",
|
||||
},
|
||||
],
|
||||
{
|
||||
"anchors_y_definitions": [
|
||||
{FILENAME: "f", FIELD: "v"},
|
||||
{FILENAME: "f2", FIELD: "v"},
|
||||
],
|
||||
"x": "dvc_inferred_x_value",
|
||||
"y": "v",
|
||||
"x_label": "x",
|
||||
"y_label": "v",
|
||||
},
|
||||
id="multiple_x_fields",
|
||||
),
|
||||
pytest.param(
|
||||
{
|
||||
"f": {
|
||||
"metric": [
|
||||
{"v": 1, "v2": 0.1, "x1": 100},
|
||||
{"v": 2, "v2": 0.2, "x1": 1000},
|
||||
]
|
||||
},
|
||||
"f2": {"metric": [{"x2": -2}, {"x2": -4}]},
|
||||
},
|
||||
{"y": ["v", "v2"], "x": {"f": "x1", "f2": "x2"}},
|
||||
[
|
||||
{
|
||||
"dvc_inferred_x_value": 100,
|
||||
"dvc_inferred_y_value": 1,
|
||||
"v": 1,
|
||||
"v2": 0.1,
|
||||
"x1": 100,
|
||||
REVISION: "r",
|
||||
FILENAME: "f",
|
||||
FIELD: "v",
|
||||
},
|
||||
{
|
||||
"dvc_inferred_x_value": 1000,
|
||||
"dvc_inferred_y_value": 2,
|
||||
"v": 2,
|
||||
"v2": 0.2,
|
||||
"x1": 1000,
|
||||
REVISION: "r",
|
||||
FILENAME: "f",
|
||||
FIELD: "v",
|
||||
},
|
||||
{
|
||||
"dvc_inferred_x_value": -2,
|
||||
"dvc_inferred_y_value": 0.1,
|
||||
"v": 1,
|
||||
"v2": 0.1,
|
||||
"x1": 100,
|
||||
REVISION: "r",
|
||||
FILENAME: "f",
|
||||
FIELD: "v2",
|
||||
},
|
||||
{
|
||||
"dvc_inferred_x_value": -4,
|
||||
"dvc_inferred_y_value": 0.2,
|
||||
"v": 2,
|
||||
"v2": 0.2,
|
||||
"x1": 1000,
|
||||
REVISION: "r",
|
||||
FILENAME: "f",
|
||||
FIELD: "v2",
|
||||
},
|
||||
],
|
||||
{
|
||||
"anchors_y_definitions": [
|
||||
{FILENAME: "f", FIELD: "v"},
|
||||
{FILENAME: "f", FIELD: "v2"},
|
||||
],
|
||||
"x": "dvc_inferred_x_value",
|
||||
"y": "dvc_inferred_y_value",
|
||||
"x_label": "x",
|
||||
"y_label": "y",
|
||||
},
|
||||
id="y_list_x_dict",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_convert(
|
||||
|
||||
Reference in New Issue
Block a user