mirror of
https://github.com/Stone-Red-Code/dvc.git
synced 2026-11-03 15:28:29 +01:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d39c0ce618 | ||
|
|
1a8680de7d | ||
|
|
696c704632 | ||
|
|
3e68cd7105 | ||
|
|
73088fc7b9 | ||
|
|
978d5ac227 | ||
|
|
21585d18ab | ||
|
|
198cdeec06 | ||
|
|
e049c4bab1 | ||
|
|
e2ed671cc9 | ||
|
|
8a97b21b76 | ||
|
|
a113097817 | ||
|
|
fbcc3f389c | ||
|
|
7521e91d05 | ||
|
|
18d147e2f8 | ||
|
|
39d5d36033 | ||
|
|
091c59e663 | ||
|
|
f3010d1d65 | ||
|
|
36a2bf472b | ||
|
|
93a200cf5f | ||
|
|
dae08255d0 | ||
|
|
237e9d4354 | ||
|
|
788b4d3bb5 | ||
|
|
40c61c4f62 | ||
|
|
1a1294848b | ||
|
|
93dab26313 |
@@ -112,7 +112,7 @@ jobs:
|
||||
--cov-report=xml --cov-report=term
|
||||
--all ${{ env.extra_test_args }}
|
||||
- name: upload coverage report
|
||||
uses: codecov/codecov-action@v2.0.3
|
||||
uses: codecov/codecov-action@v2.1.0
|
||||
with:
|
||||
file: ./coverage.xml
|
||||
fail_ci_if_error: false
|
||||
|
||||
+17
-4
@@ -66,7 +66,8 @@ class CmdConfig(CmdBaseNoRepo):
|
||||
)
|
||||
return 1
|
||||
|
||||
levels = [self.args.level] if self.args.level else self.config.LEVELS
|
||||
levels = self._get_appropriate_levels(self.args.level)
|
||||
|
||||
for level in levels:
|
||||
conf = self.config.read(level)
|
||||
prefix = self._config_file_prefix(
|
||||
@@ -81,9 +82,7 @@ class CmdConfig(CmdBaseNoRepo):
|
||||
def _get(self, remote, section, opt):
|
||||
from dvc.config import ConfigError
|
||||
|
||||
levels = (
|
||||
[self.args.level] if self.args.level else self.config.LEVELS[::-1]
|
||||
)
|
||||
levels = self._get_appropriate_levels(self.args.level)[::-1]
|
||||
|
||||
for level in levels:
|
||||
conf = self.config.read(level)
|
||||
@@ -136,6 +135,20 @@ class CmdConfig(CmdBaseNoRepo):
|
||||
f"option '{opt}' doesn't exist in {name} '{section}'"
|
||||
)
|
||||
|
||||
def _get_appropriate_levels(self, levels):
|
||||
if levels:
|
||||
self._validate_level_for_non_repo_operation(levels)
|
||||
return [levels]
|
||||
if self.config.dvc_dir is None:
|
||||
return self.config.SYSTEM_LEVELS
|
||||
return self.config.LEVELS
|
||||
|
||||
def _validate_level_for_non_repo_operation(self, level):
|
||||
from dvc.config import ConfigError
|
||||
|
||||
if self.config.dvc_dir is None and level in self.config.REPO_LEVELS:
|
||||
raise ConfigError("Not inside a DVC repo")
|
||||
|
||||
@staticmethod
|
||||
def _format_config(config, prefix=""):
|
||||
from dvc.utils.flatten import flatten
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
from collections import Counter, OrderedDict, defaultdict
|
||||
from datetime import date, datetime
|
||||
from fnmatch import fnmatch
|
||||
@@ -789,6 +790,62 @@ class CmdExperimentsRemove(CmdBase):
|
||||
return 0
|
||||
|
||||
|
||||
class CmdExperimentsInit(CmdBase):
|
||||
CODE = "src"
|
||||
DATA = "data"
|
||||
MODELS = "models"
|
||||
DEFAULT_METRICS = "metrics.json"
|
||||
DEFAULT_PARAMS = "params.yaml"
|
||||
PLOTS = "plots"
|
||||
DVCLIVE = "dvclive"
|
||||
DEFAULT_NAME = "default"
|
||||
|
||||
def run(self):
|
||||
from dvc.command.stage import parse_cmd
|
||||
|
||||
cmd = parse_cmd(self.args.cmd)
|
||||
if not cmd:
|
||||
raise InvalidArgumentError("command is not specified")
|
||||
if self.args.interactive:
|
||||
raise NotImplementedError(
|
||||
"'-i/--interactive' is not implemented yet."
|
||||
)
|
||||
if self.args.explicit:
|
||||
raise NotImplementedError("'--explicit' is not implemented yet.")
|
||||
if self.args.template:
|
||||
raise NotImplementedError("template is not supported yet.")
|
||||
|
||||
from dvc.utils.serialize import LOADERS
|
||||
|
||||
code = self.args.code or self.CODE
|
||||
data = self.args.data or self.DATA
|
||||
models = self.args.models or self.MODELS
|
||||
metrics = self.args.metrics or self.DEFAULT_METRICS
|
||||
params_path = self.args.params or self.DEFAULT_PARAMS
|
||||
plots = self.args.plots or self.PLOTS
|
||||
dvclive = self.args.live or self.DVCLIVE
|
||||
|
||||
_, ext = os.path.splitext(params_path)
|
||||
params = list(LOADERS[ext](params_path))
|
||||
|
||||
name = self.args.name or self.DEFAULT_NAME
|
||||
stage = self.repo.stage.add(
|
||||
name=name,
|
||||
cmd=cmd,
|
||||
deps=[code, data],
|
||||
outs=[models],
|
||||
params=[{params_path: params}],
|
||||
metrics_no_cache=[metrics],
|
||||
plots_no_cache=[plots],
|
||||
live=dvclive,
|
||||
force=True,
|
||||
)
|
||||
|
||||
if self.args.run:
|
||||
return self.repo.experiments.run(targets=[stage.addressing])
|
||||
return 0
|
||||
|
||||
|
||||
def add_parser(subparsers, parent_parser):
|
||||
EXPERIMENTS_HELP = "Commands to run and compare experiments."
|
||||
|
||||
@@ -1303,6 +1360,68 @@ def add_parser(subparsers, parent_parser):
|
||||
)
|
||||
experiments_remove_parser.set_defaults(func=CmdExperimentsRemove)
|
||||
|
||||
EXPERIMENTS_INIT_HELP = "Initialize experiments."
|
||||
experiments_init_parser = experiments_subparsers.add_parser(
|
||||
"init",
|
||||
parents=[parent_parser],
|
||||
description=append_doc_link(EXPERIMENTS_INIT_HELP, "exp/init"),
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
experiments_init_parser.add_argument(
|
||||
"cmd",
|
||||
nargs=argparse.REMAINDER,
|
||||
help="Command to execute.",
|
||||
metavar="command",
|
||||
)
|
||||
experiments_init_parser.add_argument(
|
||||
"--run",
|
||||
action="store_true",
|
||||
help="Run the experiment after initializing it",
|
||||
)
|
||||
experiments_init_parser.add_argument(
|
||||
"--interactive",
|
||||
"-i",
|
||||
action="store_true",
|
||||
help="Prompt for values that are not provided",
|
||||
)
|
||||
experiments_init_parser.add_argument(
|
||||
"--template", help="Stage template to use to fill with provided values"
|
||||
)
|
||||
experiments_init_parser.add_argument(
|
||||
"--explicit", help="Only use the path values explicitly provided"
|
||||
)
|
||||
experiments_init_parser.add_argument(
|
||||
"--name", "-n", help="Name of the stage to create"
|
||||
)
|
||||
experiments_init_parser.add_argument(
|
||||
"--code",
|
||||
help="Path to the source file or directory "
|
||||
"which your experiments depend",
|
||||
)
|
||||
experiments_init_parser.add_argument(
|
||||
"--data",
|
||||
help="Path to the data file or directory "
|
||||
"which your experiments depend",
|
||||
)
|
||||
experiments_init_parser.add_argument(
|
||||
"--models",
|
||||
help="Path to the model file or directory for your experiments",
|
||||
)
|
||||
experiments_init_parser.add_argument(
|
||||
"--params", help="Path to the parameters file for your experiments"
|
||||
)
|
||||
experiments_init_parser.add_argument(
|
||||
"--metrics", help="Path to the metrics file for your experiments"
|
||||
)
|
||||
experiments_init_parser.add_argument(
|
||||
"--plots",
|
||||
help="Path to the plots file or directory for your experiments",
|
||||
)
|
||||
experiments_init_parser.add_argument(
|
||||
"--live", help="Path to log dvclive outputs for your experiments"
|
||||
)
|
||||
experiments_init_parser.set_defaults(func=CmdExperimentsInit)
|
||||
|
||||
|
||||
def _add_run_common(parser):
|
||||
"""Add common args for 'exp run' and 'exp resume'."""
|
||||
|
||||
@@ -69,6 +69,22 @@ class CmdMachineRemove(CmdMachineConfig):
|
||||
return 0
|
||||
|
||||
|
||||
class CmdMachineList(CmdMachineConfig):
|
||||
def run(self):
|
||||
levels = [self.args.level] if self.args.level else self.config.LEVELS
|
||||
for level in levels:
|
||||
conf = self.config.read(level)["machine"]
|
||||
if self.args.name:
|
||||
conf = conf.get(self.args.name, {})
|
||||
prefix = self._config_file_prefix(
|
||||
self.args.show_origin, self.config, level
|
||||
)
|
||||
configs = list(self._format_config(conf, prefix))
|
||||
if configs:
|
||||
ui.write("\n".join(configs))
|
||||
return 0
|
||||
|
||||
|
||||
class CmdMachineModify(CmdMachineConfig):
|
||||
def run(self):
|
||||
from dvc.config import merge
|
||||
@@ -219,6 +235,27 @@ def add_parser(subparsers, parent_parser):
|
||||
)
|
||||
machine_default_parser.set_defaults(func=CmdMachineDefault)
|
||||
|
||||
machine_LIST_HELP = "List the configuration of one/all machines."
|
||||
machine_list_parser = machine_subparsers.add_parser(
|
||||
"list",
|
||||
parents=[parent_config_parser, parent_parser],
|
||||
description=append_doc_link(machine_LIST_HELP, "machine/list"),
|
||||
help=machine_LIST_HELP,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
machine_list_parser.add_argument(
|
||||
"--show-origin",
|
||||
default=False,
|
||||
action="store_true",
|
||||
help="Show the source file containing each config value.",
|
||||
)
|
||||
machine_list_parser.add_argument(
|
||||
"name",
|
||||
nargs="?",
|
||||
type=str,
|
||||
help="name of machine to specify",
|
||||
)
|
||||
machine_list_parser.set_defaults(func=CmdMachineList)
|
||||
machine_MODIFY_HELP = "Modify the configuration of an machine."
|
||||
machine_modify_parser = machine_subparsers.add_parser(
|
||||
"modify",
|
||||
|
||||
+4
-2
@@ -76,8 +76,10 @@ class Config(dict):
|
||||
APPNAME = "dvc"
|
||||
APPAUTHOR = "iterative"
|
||||
|
||||
SYSTEM_LEVELS = ("system", "global")
|
||||
REPO_LEVELS = ("repo", "local")
|
||||
# In the order they shadow each other
|
||||
LEVELS = ("system", "global", "repo", "local")
|
||||
LEVELS = SYSTEM_LEVELS + REPO_LEVELS
|
||||
|
||||
CONFIG = "config"
|
||||
CONFIG_LOCAL = "config.local"
|
||||
@@ -286,7 +288,7 @@ class Config(dict):
|
||||
def edit(self, level=None, validate=True):
|
||||
# NOTE: we write to repo config by default, same as git config
|
||||
level = level or "repo"
|
||||
if level in {"repo", "local"} and self.dvc_dir is None:
|
||||
if self.dvc_dir is None and level in self.REPO_LEVELS:
|
||||
raise ConfigError("Not inside a DVC repo")
|
||||
|
||||
conf = self.load_one(level)
|
||||
|
||||
@@ -229,7 +229,17 @@ SCHEMA = {
|
||||
"machine": {
|
||||
str: {
|
||||
"cloud": All(Lower, Choices("aws", "azure")),
|
||||
"region": All(
|
||||
Lower, Choices("us-west", "us-east", "eu-west", "eu-north")
|
||||
),
|
||||
"image": str,
|
||||
"name": str,
|
||||
"spot": Bool,
|
||||
"spot_price": Coerce(float),
|
||||
"instance_hdd_size": Coerce(int),
|
||||
"instance_type": Lower,
|
||||
"instance_gpu": Lower,
|
||||
"ssh_private": str,
|
||||
"startup_script": str,
|
||||
},
|
||||
},
|
||||
|
||||
+2
-2
@@ -11,7 +11,7 @@ from dvc.path_info import CloudURLInfo
|
||||
from dvc.scheme import Schemes
|
||||
from dvc.utils import format_link
|
||||
|
||||
from .fsspec_wrapper import ObjectFSWrapper
|
||||
from .fsspec_wrapper import CallbackMixin, ObjectFSWrapper
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
_DEFAULT_CREDS_STEPS = (
|
||||
@@ -39,7 +39,7 @@ def _az_config():
|
||||
|
||||
|
||||
# pylint:disable=abstract-method
|
||||
class AzureFileSystem(ObjectFSWrapper):
|
||||
class AzureFileSystem(CallbackMixin, ObjectFSWrapper):
|
||||
scheme = Schemes.AZURE
|
||||
PATH_CLS = CloudURLInfo
|
||||
PARAM_CHECKSUM = "etag"
|
||||
|
||||
+111
-69
@@ -1,12 +1,18 @@
|
||||
import contextlib
|
||||
import logging
|
||||
import os
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from functools import partial, partialmethod
|
||||
from functools import partialmethod
|
||||
from multiprocessing import cpu_count
|
||||
from typing import Any, ClassVar, Dict, FrozenSet, Optional
|
||||
|
||||
from funcy import cached_property
|
||||
from tqdm.utils import CallbackIOWrapper
|
||||
|
||||
from dvc.exceptions import DvcException
|
||||
from dvc.path_info import URLInfo
|
||||
from dvc.progress import Tqdm
|
||||
from dvc.progress import DEFAULT_CALLBACK, FsspecCallback
|
||||
from dvc.ui import ui
|
||||
from dvc.utils import tmp_fname
|
||||
from dvc.utils.fs import makedirs, move
|
||||
|
||||
@@ -217,134 +223,170 @@ class BaseFileSystem:
|
||||
return False
|
||||
return hash_.endswith(cls.CHECKSUM_DIR_SUFFIX)
|
||||
|
||||
@cached_property
|
||||
def _local_fs(self):
|
||||
from dvc.fs import LocalFileSystem
|
||||
|
||||
return LocalFileSystem()
|
||||
|
||||
def upload(
|
||||
self,
|
||||
from_info,
|
||||
to_info,
|
||||
total=None,
|
||||
desc=None,
|
||||
callback=None,
|
||||
no_progress_bar=False,
|
||||
**pbar_args,
|
||||
):
|
||||
is_file_obj = hasattr(from_info, "read")
|
||||
method = "upload_fobj" if is_file_obj else "_upload"
|
||||
method = "upload_fobj" if is_file_obj else "put_file"
|
||||
if not hasattr(self, method):
|
||||
raise RemoteActionNotImplemented(method, self.scheme)
|
||||
|
||||
if to_info.scheme != self.scheme:
|
||||
raise NotImplementedError
|
||||
|
||||
logger.debug("Uploading '%s' to '%s'", from_info, to_info)
|
||||
if is_file_obj:
|
||||
with Tqdm.wrapattr(
|
||||
from_info,
|
||||
"read",
|
||||
if not is_file_obj:
|
||||
desc = desc or from_info.name
|
||||
|
||||
stack = contextlib.ExitStack()
|
||||
if not callback:
|
||||
pbar = ui.progress(
|
||||
desc=desc,
|
||||
disable=no_progress_bar,
|
||||
bytes=True,
|
||||
total=total,
|
||||
desc=desc,
|
||||
total=total or -1,
|
||||
**pbar_args,
|
||||
) as wrapped:
|
||||
)
|
||||
stack.enter_context(pbar)
|
||||
callback = pbar.as_callback(self._local_fs, from_info)
|
||||
if total:
|
||||
callback.set_size(total)
|
||||
|
||||
with stack:
|
||||
if is_file_obj:
|
||||
wrapped = CallbackIOWrapper(
|
||||
callback.relative_update, from_info
|
||||
)
|
||||
# `size` is used to provide hints to the WebdavFileSystem
|
||||
# for legacy servers.
|
||||
# pylint: disable=no-member
|
||||
return self.upload_fobj(wrapped, to_info, size=total)
|
||||
|
||||
if from_info.scheme != "local":
|
||||
raise NotImplementedError
|
||||
if from_info.scheme != "local":
|
||||
raise NotImplementedError
|
||||
|
||||
name = desc or from_info.name
|
||||
return self._upload( # noqa, pylint: disable=no-member
|
||||
from_info.fspath,
|
||||
to_info,
|
||||
name=name,
|
||||
no_progress_bar=no_progress_bar,
|
||||
**pbar_args,
|
||||
)
|
||||
logger.debug("Uploading '%s' to '%s'", from_info, to_info)
|
||||
# pylint: disable=no-member
|
||||
return self.put_file(
|
||||
os.fspath(from_info), to_info, callback=callback
|
||||
)
|
||||
|
||||
def download(
|
||||
self,
|
||||
from_info,
|
||||
to_info,
|
||||
name=None,
|
||||
callback=None,
|
||||
no_progress_bar=False,
|
||||
jobs=None,
|
||||
_only_file=False,
|
||||
**kwargs,
|
||||
):
|
||||
if not hasattr(self, "_download"):
|
||||
raise RemoteActionNotImplemented("download", self.scheme)
|
||||
if not hasattr(self, "get_file"):
|
||||
raise RemoteActionNotImplemented("get_file", self.scheme)
|
||||
|
||||
if from_info.scheme != self.scheme:
|
||||
raise NotImplementedError
|
||||
|
||||
if to_info.scheme == self.scheme != "local":
|
||||
self.copy(from_info, to_info)
|
||||
return 0
|
||||
|
||||
if to_info.scheme != "local":
|
||||
raise NotImplementedError
|
||||
|
||||
if not _only_file and self.isdir(from_info):
|
||||
return self._download_dir(
|
||||
from_info, to_info, name, no_progress_bar, jobs, **kwargs
|
||||
download_dir = not _only_file and self.isdir(from_info)
|
||||
|
||||
desc = name or to_info.name
|
||||
stack = contextlib.ExitStack()
|
||||
if not callback:
|
||||
pbar_kwargs = {"unit": "Files"} if download_dir else {}
|
||||
pbar = ui.progress(
|
||||
total=-1,
|
||||
desc="Downloading directory" if download_dir else desc,
|
||||
bytes=not download_dir,
|
||||
disable=no_progress_bar,
|
||||
**pbar_kwargs,
|
||||
)
|
||||
return self._download_file(from_info, to_info, name, no_progress_bar)
|
||||
stack.enter_context(pbar)
|
||||
callback = pbar.as_callback(self, from_info)
|
||||
|
||||
with stack:
|
||||
if download_dir:
|
||||
return self._download_dir(
|
||||
from_info, to_info, callback=callback, jobs=jobs, **kwargs
|
||||
)
|
||||
return self._download_file(from_info, to_info, callback=callback)
|
||||
|
||||
download_file = partialmethod(download, _only_file=True)
|
||||
|
||||
def _download_dir(
|
||||
self, from_info, to_info, name, no_progress_bar, jobs, **kwargs
|
||||
self,
|
||||
from_info,
|
||||
to_info,
|
||||
callback=DEFAULT_CALLBACK,
|
||||
jobs=None,
|
||||
**kwargs,
|
||||
):
|
||||
from_infos = list(self.walk_files(from_info, **kwargs))
|
||||
if not from_infos:
|
||||
makedirs(to_info, exist_ok=True)
|
||||
return None
|
||||
|
||||
to_infos = (
|
||||
to_info / info.relative_to(from_info) for info in from_infos
|
||||
)
|
||||
callback.set_size(len(from_infos))
|
||||
|
||||
with Tqdm(
|
||||
total=len(from_infos),
|
||||
desc="Downloading directory",
|
||||
unit="Files",
|
||||
disable=no_progress_bar,
|
||||
) as pbar:
|
||||
download_files = pbar.wrap_fn(
|
||||
partial(self._download_file, name=name, no_progress_bar=True)
|
||||
)
|
||||
max_workers = jobs or self.jobs
|
||||
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
||||
futures = [
|
||||
executor.submit(download_files, from_info, to_info)
|
||||
for from_info, to_info in zip(from_infos, to_infos)
|
||||
]
|
||||
download_files = FsspecCallback.wrap_fn(callback, self._download_file)
|
||||
max_workers = jobs or self.jobs
|
||||
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
||||
futures = [
|
||||
executor.submit(download_files, from_info, to_info)
|
||||
for from_info, to_info in zip(from_infos, to_infos)
|
||||
]
|
||||
|
||||
# NOTE: unlike pulling/fetching cache, where we need to
|
||||
# download everything we can, not raising an error here might
|
||||
# turn very ugly, as the user might think that he has
|
||||
# downloaded a complete directory, while having a partial one,
|
||||
# which might cause unexpected results in his pipeline.
|
||||
for future in as_completed(futures):
|
||||
# NOTE: executor won't let us raise until all futures that
|
||||
# it has are finished, so we need to cancel them ourselves
|
||||
# before re-raising.
|
||||
exc = future.exception()
|
||||
if exc:
|
||||
for entry in futures:
|
||||
entry.cancel()
|
||||
raise exc
|
||||
# NOTE: unlike pulling/fetching cache, where we need to
|
||||
# download everything we can, not raising an error here might
|
||||
# turn very ugly, as the user might think that he has
|
||||
# downloaded a complete directory, while having a partial one,
|
||||
# which might cause unexpected results in his pipeline.
|
||||
for future in as_completed(futures):
|
||||
# NOTE: executor won't let us raise until all futures that
|
||||
# it has are finished, so we need to cancel them ourselves
|
||||
# before re-raising.
|
||||
exc = future.exception()
|
||||
if exc:
|
||||
for entry in futures:
|
||||
entry.cancel()
|
||||
raise exc
|
||||
|
||||
def _download_file(self, from_info, to_info, name, no_progress_bar):
|
||||
def _download_file(
|
||||
self,
|
||||
from_info,
|
||||
to_info,
|
||||
callback=DEFAULT_CALLBACK,
|
||||
):
|
||||
makedirs(to_info.parent, exist_ok=True)
|
||||
|
||||
logger.debug("Downloading '%s' to '%s'", from_info, to_info)
|
||||
name = name or to_info.name
|
||||
|
||||
tmp_file = tmp_fname(to_info)
|
||||
|
||||
self._download( # noqa, pylint: disable=no-member
|
||||
from_info, tmp_file, name=name, no_progress_bar=no_progress_bar
|
||||
)
|
||||
logger.debug("Downloading '%s' to '%s'", from_info, to_info)
|
||||
try:
|
||||
# noqa, pylint: disable=no-member
|
||||
self.get_file(from_info, tmp_file, callback=callback)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
# do we need to rollback makedirs for previously not-existing
|
||||
# directories?
|
||||
with contextlib.suppress(FileNotFoundError):
|
||||
os.unlink(tmp_file)
|
||||
raise
|
||||
|
||||
move(tmp_file, to_info)
|
||||
|
||||
+6
-3
@@ -6,6 +6,7 @@ from dvc.exceptions import OutputNotFoundError
|
||||
from dvc.path_info import PathInfo
|
||||
from dvc.utils import relpath
|
||||
|
||||
from ..progress import DEFAULT_CALLBACK
|
||||
from ._metadata import Metadata
|
||||
from .base import BaseFileSystem
|
||||
|
||||
@@ -256,10 +257,12 @@ class DvcFileSystem(BaseFileSystem): # pylint:disable=abstract-method
|
||||
|
||||
return ret
|
||||
|
||||
def _download(self, from_info, to_file, **kwargs):
|
||||
def get_file(
|
||||
self, from_info, to_file, callback=DEFAULT_CALLBACK, **kwargs
|
||||
):
|
||||
fs, path = self._get_fs_path(from_info)
|
||||
fs._download( # pylint: disable=protected-access
|
||||
path, to_file, **kwargs
|
||||
fs.get_file( # pylint: disable=protected-access
|
||||
path, to_file, callback=callback, **kwargs
|
||||
)
|
||||
|
||||
def checksum(self, path_info):
|
||||
|
||||
+47
-66
@@ -3,8 +3,9 @@ import shutil
|
||||
from functools import lru_cache
|
||||
|
||||
from funcy import cached_property
|
||||
from tqdm.utils import CallbackIOWrapper
|
||||
|
||||
from dvc.progress import Tqdm
|
||||
from dvc.progress import DEFAULT_CALLBACK
|
||||
|
||||
from .base import BaseFileSystem
|
||||
from .local import LocalFileSystem
|
||||
@@ -130,45 +131,26 @@ class FSSpecWrapper(BaseFileSystem):
|
||||
self._with_bucket(path_info), exist_ok=kwargs.pop("exist_ok", True)
|
||||
)
|
||||
|
||||
def put_file(
|
||||
self, from_file, to_info, callback=DEFAULT_CALLBACK, **kwargs
|
||||
):
|
||||
self.fs.put_file(
|
||||
from_file, self._with_bucket(to_info), callback=callback, **kwargs
|
||||
)
|
||||
self.fs.invalidate_cache(self._with_bucket(to_info.parent))
|
||||
|
||||
def get_file(
|
||||
self, from_info, to_info, callback=DEFAULT_CALLBACK, **kwargs
|
||||
):
|
||||
self.fs.get_file(
|
||||
self._with_bucket(from_info), to_info, callback=callback, **kwargs
|
||||
)
|
||||
|
||||
def upload_fobj(self, fobj, to_info, **kwargs):
|
||||
self.makedirs(to_info.parent)
|
||||
with self.open(to_info, "wb") as fdest:
|
||||
shutil.copyfileobj(fobj, fdest, length=fdest.blocksize)
|
||||
|
||||
def _upload(
|
||||
self, from_file, to_info, name=None, no_progress_bar=False, **kwargs
|
||||
):
|
||||
self.makedirs(to_info.parent)
|
||||
size = os.path.getsize(from_file)
|
||||
with open(from_file, "rb") as fobj:
|
||||
with Tqdm.wrapattr(
|
||||
fobj,
|
||||
"read",
|
||||
disable=no_progress_bar,
|
||||
bytes=True,
|
||||
total=size,
|
||||
desc=name,
|
||||
) as wrapped:
|
||||
self.upload_fobj(wrapped, to_info, size=size)
|
||||
self.fs.invalidate_cache(self._with_bucket(to_info.parent))
|
||||
|
||||
def _download(
|
||||
self, from_info, to_file, name=None, no_progress_bar=False, **pbar_args
|
||||
):
|
||||
total = self.getsize(self._with_bucket(from_info))
|
||||
with self.open(from_info, "rb") as fobj:
|
||||
with Tqdm.wrapattr(
|
||||
fobj,
|
||||
"read",
|
||||
desc=name,
|
||||
disable=no_progress_bar,
|
||||
bytes=True,
|
||||
total=total,
|
||||
**pbar_args,
|
||||
) as wrapped:
|
||||
with open(to_file, "wb") as fdest:
|
||||
shutil.copyfileobj(wrapped, fdest, length=fobj.blocksize)
|
||||
|
||||
|
||||
# pylint: disable=abstract-method
|
||||
class ObjectFSWrapper(FSSpecWrapper):
|
||||
@@ -260,38 +242,37 @@ _LOCAL_FS = LocalFileSystem()
|
||||
|
||||
|
||||
class CallbackMixin:
|
||||
"""Use the native ``get_file()``/``put_file()`` APIs
|
||||
if the target filesystem supports callbacks."""
|
||||
"""Provides callback support for the filesystem that don't support yet."""
|
||||
|
||||
def _upload(
|
||||
self, from_file, to_info, name=None, no_progress_bar=False, **pbar_args
|
||||
def put_file(
|
||||
self,
|
||||
from_file,
|
||||
to_info,
|
||||
callback=DEFAULT_CALLBACK,
|
||||
**kwargs,
|
||||
):
|
||||
with Tqdm(
|
||||
desc=name,
|
||||
disable=no_progress_bar,
|
||||
bytes=True,
|
||||
total=-1,
|
||||
**pbar_args,
|
||||
) as pbar:
|
||||
self.fs.put_file(
|
||||
os.fspath(from_file),
|
||||
self._with_bucket(to_info),
|
||||
callback=pbar.as_callback(_LOCAL_FS, from_file),
|
||||
)
|
||||
self.fs.invalidate_cache(self._with_bucket(to_info.parent))
|
||||
"""Add compatibility support for Callback."""
|
||||
# pylint: disable=protected-access
|
||||
self.makedirs(to_info.parent)
|
||||
size = os.path.getsize(from_file)
|
||||
with open(from_file, "rb") as fobj:
|
||||
callback.set_size(size)
|
||||
wrapped = CallbackIOWrapper(callback.relative_update, fobj)
|
||||
self.upload_fobj(wrapped, to_info)
|
||||
self.fs.invalidate_cache(self._with_bucket(to_info.parent))
|
||||
|
||||
def _download(
|
||||
self, from_info, to_file, name=None, no_progress_bar=False, **pbar_args
|
||||
def get_file(
|
||||
self,
|
||||
from_info,
|
||||
to_info,
|
||||
callback=DEFAULT_CALLBACK,
|
||||
**kwargs,
|
||||
):
|
||||
with Tqdm(
|
||||
desc=name,
|
||||
disable=no_progress_bar,
|
||||
bytes=True,
|
||||
total=-1,
|
||||
**pbar_args,
|
||||
) as pbar:
|
||||
self.fs.get_file(
|
||||
self._with_bucket(from_info),
|
||||
os.fspath(to_file),
|
||||
callback=pbar.as_callback(self, from_info),
|
||||
)
|
||||
# pylint: disable=protected-access
|
||||
total: int = self.getsize(from_info)
|
||||
if total:
|
||||
callback.set_size(total)
|
||||
|
||||
with self.open(from_info, "rb") as fobj, open(to_info, "wb") as fdest:
|
||||
wrapped = CallbackIOWrapper(callback.relative_update, fobj)
|
||||
shutil.copyfileobj(wrapped, fdest, length=fobj.blocksize)
|
||||
|
||||
+11
-4
@@ -12,7 +12,7 @@ from dvc.path_info import CloudURLInfo
|
||||
from dvc.scheme import Schemes
|
||||
from dvc.utils import format_link, tmp_fname
|
||||
|
||||
from .fsspec_wrapper import CallbackMixin, FSSpecWrapper
|
||||
from .fsspec_wrapper import FSSpecWrapper
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
FOLDER_MIME_TYPE = "application/vnd.google-apps.folder"
|
||||
@@ -79,9 +79,7 @@ class GDriveURLInfo(CloudURLInfo):
|
||||
self._spath = re.sub("/{2,}", "/", self._spath.rstrip("/"))
|
||||
|
||||
|
||||
class GDriveFileSystem(
|
||||
CallbackMixin, FSSpecWrapper
|
||||
): # pylint:disable=abstract-method
|
||||
class GDriveFileSystem(FSSpecWrapper): # pylint:disable=abstract-method
|
||||
scheme = Schemes.GDRIVE
|
||||
PATH_CLS = GDriveURLInfo
|
||||
PARAM_CHECKSUM = "checksum"
|
||||
@@ -298,6 +296,15 @@ class GDriveFileSystem(
|
||||
|
||||
return super()._with_bucket(path)
|
||||
|
||||
def _strip_bucket(self, entry):
|
||||
try:
|
||||
bucket, path = entry.split("/", 1)
|
||||
except ValueError:
|
||||
# If there is no path attached, only returns
|
||||
# the bucket (top-level).
|
||||
bucket, path = entry, None
|
||||
return path or bucket
|
||||
|
||||
def upload_fobj(self, fobj, to_info, **kwargs):
|
||||
rpath = self._with_bucket(to_info)
|
||||
self.makedirs(os.path.dirname(rpath))
|
||||
|
||||
+14
-9
@@ -1,8 +1,11 @@
|
||||
import errno
|
||||
import os
|
||||
|
||||
from tqdm.utils import CallbackIOWrapper
|
||||
|
||||
from dvc.utils import is_exec, relpath
|
||||
|
||||
from ..progress import DEFAULT_CALLBACK
|
||||
from .base import BaseFileSystem
|
||||
|
||||
|
||||
@@ -116,16 +119,18 @@ class GitFileSystem(BaseFileSystem): # pylint:disable=abstract-method
|
||||
# NOTE: os.path.join is ~5.5 times slower
|
||||
yield f"{root}{os.sep}{file}"
|
||||
|
||||
def _download(
|
||||
self, from_info, to_file, name=None, no_progress_bar=False, **kwargs
|
||||
def get_file(
|
||||
self, from_info, to_file, callback=DEFAULT_CALLBACK, **kwargs
|
||||
):
|
||||
import shutil
|
||||
|
||||
from dvc.progress import Tqdm
|
||||
total = self.getsize(from_info)
|
||||
if total:
|
||||
callback.set_size(total)
|
||||
|
||||
with open(to_file, "wb+") as to_fobj:
|
||||
with Tqdm.wrapattr(
|
||||
to_fobj, "write", desc=name, disable=no_progress_bar
|
||||
) as wrapped:
|
||||
with self.open(from_info, "rb", **kwargs) as from_fobj:
|
||||
shutil.copyfileobj(from_fobj, wrapped)
|
||||
with self.open(from_info, "rb", **kwargs) as from_fobj:
|
||||
with open(to_file, "wb+") as to_fobj:
|
||||
wrapped = CallbackIOWrapper(
|
||||
callback.relative_update, from_fobj
|
||||
)
|
||||
shutil.copyfileobj(wrapped, to_fobj)
|
||||
|
||||
+4
-4
@@ -6,11 +6,11 @@ from funcy import cached_property, wrap_prop
|
||||
from dvc.path_info import CloudURLInfo
|
||||
from dvc.scheme import Schemes
|
||||
|
||||
from .fsspec_wrapper import ObjectFSWrapper
|
||||
|
||||
|
||||
# pylint:disable=abstract-method
|
||||
class GSFileSystem(ObjectFSWrapper):
|
||||
from .fsspec_wrapper import CallbackMixin, ObjectFSWrapper
|
||||
|
||||
|
||||
class GSFileSystem(CallbackMixin, ObjectFSWrapper):
|
||||
scheme = Schemes.GS
|
||||
PATH_CLS = CloudURLInfo
|
||||
REQUIRES = {"gcsfs": "gcsfs"}
|
||||
|
||||
+22
-25
@@ -8,8 +8,10 @@ import subprocess
|
||||
from collections import deque
|
||||
from contextlib import closing, contextmanager
|
||||
|
||||
from tqdm.utils import CallbackIOWrapper
|
||||
|
||||
from dvc.hash_info import HashInfo
|
||||
from dvc.progress import Tqdm
|
||||
from dvc.progress import DEFAULT_CALLBACK
|
||||
from dvc.scheme import Schemes
|
||||
from dvc.utils import fix_env, tmp_fname
|
||||
|
||||
@@ -249,37 +251,32 @@ class HDFSFileSystem(BaseFileSystem):
|
||||
with hdfs.open_output_stream(to_info.path) as fdest:
|
||||
shutil.copyfileobj(fobj, fdest, self.BLOCK_SIZE)
|
||||
|
||||
def _upload(
|
||||
self, from_file, to_info, name=None, no_progress_bar=False, **_kwargs
|
||||
def put_file(
|
||||
self, from_file, to_info, callback=DEFAULT_CALLBACK, **kwargs
|
||||
):
|
||||
with self.hdfs(to_info) as hdfs:
|
||||
hdfs.create_dir(to_info.parent.path)
|
||||
|
||||
tmp_file = tmp_fname(to_info.path)
|
||||
total = os.path.getsize(from_file)
|
||||
total: int = os.path.getsize(from_file)
|
||||
callback.set_size(total)
|
||||
|
||||
with open(from_file, "rb") as fobj:
|
||||
with Tqdm.wrapattr(
|
||||
fobj,
|
||||
"read",
|
||||
desc=name,
|
||||
total=total,
|
||||
disable=no_progress_bar,
|
||||
) as wrapped:
|
||||
with hdfs.open_output_stream(tmp_file) as sobj:
|
||||
shutil.copyfileobj(wrapped, sobj, self.BLOCK_SIZE)
|
||||
wrapped = CallbackIOWrapper(callback.relative_update, fobj)
|
||||
with hdfs.open_output_stream(tmp_file) as sobj:
|
||||
shutil.copyfileobj(wrapped, sobj, self.BLOCK_SIZE)
|
||||
hdfs.move(tmp_file, to_info.path)
|
||||
|
||||
def _download(
|
||||
self, from_info, to_file, name=None, no_progress_bar=False, **_kwargs
|
||||
def get_file(
|
||||
self, from_info, to_file, callback=DEFAULT_CALLBACK, **kwargs
|
||||
):
|
||||
with self.hdfs(from_info) as hdfs:
|
||||
file_info = hdfs.get_file_info(from_info.path)
|
||||
total = file_info.size
|
||||
with open(to_file, "wb+") as fobj:
|
||||
with Tqdm.wrapattr(
|
||||
fobj,
|
||||
"write",
|
||||
desc=name,
|
||||
total=total,
|
||||
disable=no_progress_bar,
|
||||
) as wrapped:
|
||||
with hdfs.open_input_stream(from_info.path) as sobj:
|
||||
shutil.copyfileobj(sobj, wrapped, self.BLOCK_SIZE)
|
||||
if total:
|
||||
callback.set_size(total)
|
||||
|
||||
with hdfs.open_input_stream(from_info.path) as sobj:
|
||||
with open(to_file, "wb+") as fobj:
|
||||
wrapped = CallbackIOWrapper(callback.relative_update, sobj)
|
||||
shutil.copyfileobj(wrapped, fobj, self.BLOCK_SIZE)
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@ from dvc import prompt
|
||||
from dvc.path_info import HTTPURLInfo
|
||||
from dvc.scheme import Schemes
|
||||
|
||||
from .fsspec_wrapper import CallbackMixin, FSSpecWrapper, NoDirectoriesMixin
|
||||
from .fsspec_wrapper import FSSpecWrapper, NoDirectoriesMixin
|
||||
|
||||
|
||||
@wrap_with(threading.Lock())
|
||||
@@ -32,7 +32,7 @@ def make_context(ssl_verify):
|
||||
|
||||
|
||||
# pylint: disable=abstract-method
|
||||
class HTTPFileSystem(CallbackMixin, NoDirectoriesMixin, FSSpecWrapper):
|
||||
class HTTPFileSystem(NoDirectoriesMixin, FSSpecWrapper):
|
||||
scheme = Schemes.HTTP
|
||||
PATH_CLS = HTTPURLInfo
|
||||
PARAM_CHECKSUM = "checksum"
|
||||
|
||||
+7
-12
@@ -7,6 +7,7 @@ from dvc.system import System
|
||||
from dvc.utils import is_exec, tmp_fname
|
||||
from dvc.utils.fs import copy_fobj_to_file, copyfile, makedirs, move, remove
|
||||
|
||||
from ..progress import DEFAULT_CALLBACK
|
||||
from .base import BaseFileSystem
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -158,21 +159,15 @@ class LocalFileSystem(BaseFileSystem):
|
||||
def info(self, path_info):
|
||||
return self.fs.info(path_info)
|
||||
|
||||
def _upload(
|
||||
self, from_file, to_info, name=None, no_progress_bar=False, **_kwargs
|
||||
def put_file(
|
||||
self, from_file, to_info, callback=DEFAULT_CALLBACK, **kwargs
|
||||
):
|
||||
makedirs(to_info.parent, exist_ok=True)
|
||||
|
||||
tmp_file = tmp_fname(to_info)
|
||||
copyfile(
|
||||
from_file, tmp_file, name=name, no_progress_bar=no_progress_bar
|
||||
)
|
||||
copyfile(from_file, tmp_file, callback=callback)
|
||||
os.replace(tmp_file, to_info)
|
||||
|
||||
@staticmethod
|
||||
def _download(
|
||||
from_info, to_file, name=None, no_progress_bar=False, **_kwargs
|
||||
def get_file(
|
||||
self, from_info, to_file, callback=DEFAULT_CALLBACK, **kwargs
|
||||
):
|
||||
copyfile(
|
||||
from_info, to_file, no_progress_bar=no_progress_bar, name=name
|
||||
)
|
||||
copyfile(from_info, to_file, callback=callback)
|
||||
|
||||
+2
-2
@@ -7,13 +7,13 @@ from funcy import cached_property, wrap_prop
|
||||
from dvc.path_info import CloudURLInfo
|
||||
from dvc.scheme import Schemes
|
||||
|
||||
from .fsspec_wrapper import CallbackMixin, ObjectFSWrapper
|
||||
from .fsspec_wrapper import ObjectFSWrapper
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# pylint:disable=abstract-method
|
||||
class OSSFileSystem(CallbackMixin, ObjectFSWrapper):
|
||||
class OSSFileSystem(ObjectFSWrapper):
|
||||
scheme = Schemes.OSS
|
||||
PATH_CLS = CloudURLInfo
|
||||
REQUIRES = {"ossfs": "ossfs"}
|
||||
|
||||
+8
-5
@@ -9,6 +9,7 @@ from funcy import lfilter, wrap_with
|
||||
|
||||
from dvc.path_info import PathInfo
|
||||
|
||||
from ..progress import DEFAULT_CALLBACK
|
||||
from .base import BaseFileSystem
|
||||
from .dvc import DvcFileSystem
|
||||
|
||||
@@ -448,19 +449,21 @@ class RepoFileSystem(BaseFileSystem): # pylint:disable=abstract-method
|
||||
for fname in files:
|
||||
yield PathInfo(root) / fname
|
||||
|
||||
def _download(self, from_info, to_file, **kwargs):
|
||||
def get_file(
|
||||
self, from_info, to_file, callback=DEFAULT_CALLBACK, **kwargs
|
||||
):
|
||||
fs, dvc_fs = self._get_fs_pair(from_info)
|
||||
try:
|
||||
fs._download( # pylint: disable=protected-access
|
||||
from_info, to_file, **kwargs
|
||||
fs.get_file( # pylint: disable=protected-access
|
||||
from_info, to_file, callback=callback, **kwargs
|
||||
)
|
||||
return
|
||||
except FileNotFoundError:
|
||||
if not dvc_fs:
|
||||
raise
|
||||
|
||||
dvc_fs._download( # pylint: disable=protected-access
|
||||
from_info, to_file, **kwargs
|
||||
dvc_fs.get_file( # pylint: disable=protected-access
|
||||
from_info, to_file, callback=callback, **kwargs
|
||||
)
|
||||
|
||||
def metadata(self, path):
|
||||
|
||||
+15
-28
@@ -6,7 +6,7 @@ from collections import defaultdict
|
||||
from funcy import cached_property, wrap_prop
|
||||
|
||||
from dvc.path_info import CloudURLInfo
|
||||
from dvc.progress import Tqdm
|
||||
from dvc.progress import DEFAULT_CALLBACK
|
||||
from dvc.scheme import Schemes
|
||||
|
||||
from .fsspec_wrapper import ObjectFSWrapper
|
||||
@@ -204,36 +204,23 @@ class S3FileSystem(BaseS3FileSystem): # pylint:disable=abstract-method
|
||||
return bucket.Object(path_info.path)
|
||||
|
||||
@_translate_exceptions
|
||||
def _upload(
|
||||
self, from_file, to_info, name=None, no_progress_bar=False, **pbar_args
|
||||
def put_file(
|
||||
self, from_file, to_info, callback=DEFAULT_CALLBACK, **kwargs
|
||||
):
|
||||
total = os.path.getsize(from_file)
|
||||
with Tqdm(
|
||||
disable=no_progress_bar,
|
||||
total=total,
|
||||
bytes=True,
|
||||
desc=name,
|
||||
**pbar_args,
|
||||
) as pbar:
|
||||
obj = self._get_obj(to_info)
|
||||
obj.upload_file(
|
||||
from_file,
|
||||
Callback=pbar.update,
|
||||
ExtraArgs=self.fs_args.get("s3_additional_kwargs"),
|
||||
Config=self._transfer_config,
|
||||
)
|
||||
callback.set_size(os.path.getsize(from_file))
|
||||
obj = self._get_obj(to_info)
|
||||
obj.upload_file(
|
||||
from_file,
|
||||
Callback=callback.relative_update,
|
||||
ExtraArgs=self.fs_args.get("s3_additional_kwargs"),
|
||||
Config=self._transfer_config,
|
||||
)
|
||||
self.fs.invalidate_cache(self._with_bucket(to_info.parent))
|
||||
|
||||
@_translate_exceptions
|
||||
def _download(
|
||||
self, from_info, to_file, name=None, no_progress_bar=False, **pbar_args
|
||||
def get_file(
|
||||
self, from_info, to_info, callback=DEFAULT_CALLBACK, **kwargs
|
||||
):
|
||||
obj = self._get_obj(from_info)
|
||||
with Tqdm(
|
||||
disable=no_progress_bar,
|
||||
total=obj.content_length,
|
||||
bytes=True,
|
||||
desc=name,
|
||||
**pbar_args,
|
||||
) as pbar:
|
||||
obj.download_file(to_file, Callback=pbar.update)
|
||||
callback.set_size(obj.content_length)
|
||||
obj.download_file(to_info, Callback=callback.relative_update)
|
||||
|
||||
+12
-9
@@ -8,7 +8,8 @@ from dvc import prompt
|
||||
from dvc.scheme import Schemes
|
||||
from dvc.utils.fs import as_atomic
|
||||
|
||||
from .fsspec_wrapper import CallbackMixin, FSSpecWrapper
|
||||
from ..progress import DEFAULT_CALLBACK
|
||||
from .fsspec_wrapper import FSSpecWrapper
|
||||
|
||||
_SSH_TIMEOUT = 60 * 30
|
||||
_SSH_CONFIG_FILE = os.path.expanduser(os.path.join("~", ".ssh", "config"))
|
||||
@@ -26,7 +27,7 @@ def ask_password(host, user, port):
|
||||
|
||||
|
||||
# pylint:disable=abstract-method
|
||||
class SSHFileSystem(CallbackMixin, FSSpecWrapper):
|
||||
class SSHFileSystem(FSSpecWrapper):
|
||||
scheme = Schemes.SSH
|
||||
REQUIRES = {"sshfs": "sshfs"}
|
||||
|
||||
@@ -66,6 +67,11 @@ class SSHFileSystem(CallbackMixin, FSSpecWrapper):
|
||||
or self.DEFAULT_PORT
|
||||
)
|
||||
|
||||
if config.get("ask_password") and config.get("password") is None:
|
||||
config["password"] = ask_password(
|
||||
login_info["host"], login_info["username"], login_info["port"]
|
||||
)
|
||||
|
||||
login_info["password"] = config.get("password")
|
||||
login_info["passphrase"] = config.get("password")
|
||||
|
||||
@@ -100,11 +106,6 @@ class SSHFileSystem(CallbackMixin, FSSpecWrapper):
|
||||
login_info["agent_forwarding"] = config.get("agent_forwarding", True)
|
||||
login_info["proxy_command"] = user_ssh_config.get("ProxyCommand")
|
||||
|
||||
if config.get("ask_password") and login_info["password"] is None:
|
||||
login_info["password"] = ask_password(
|
||||
login_info["host"], login_info["username"], login_info["port"]
|
||||
)
|
||||
|
||||
# We are going to automatically add stuff to known_hosts
|
||||
# something like paramiko's AutoAddPolicy()
|
||||
login_info["known_hosts"] = None
|
||||
@@ -124,6 +125,8 @@ class SSHFileSystem(CallbackMixin, FSSpecWrapper):
|
||||
with as_atomic(self, to_info) as tmp_file:
|
||||
super().upload_fobj(fobj, tmp_file, **kwargs)
|
||||
|
||||
def _upload(self, from_file, to_info, *args, **kwargs):
|
||||
def put_file(
|
||||
self, from_file, to_info, callback=DEFAULT_CALLBACK, **kwargs
|
||||
):
|
||||
with as_atomic(self, to_info) as tmp_file:
|
||||
super()._upload(from_file, tmp_file, *args, **kwargs)
|
||||
super().put_file(from_file, tmp_file, callback=callback, **kwargs)
|
||||
|
||||
+29
-20
@@ -9,7 +9,7 @@ from funcy import cached_property, wrap_prop
|
||||
|
||||
from dvc.hash_info import HashInfo
|
||||
from dvc.path_info import CloudURLInfo
|
||||
from dvc.progress import Tqdm
|
||||
from dvc.progress import DEFAULT_CALLBACK
|
||||
from dvc.scheme import Schemes
|
||||
|
||||
from .base import BaseFileSystem
|
||||
@@ -29,6 +29,15 @@ def update_pbar(pbar, total):
|
||||
return update
|
||||
|
||||
|
||||
def update_callback(callback, total):
|
||||
def update(_, bytes_transfered):
|
||||
if bytes_transfered == -1:
|
||||
return callback.absolute_update(total)
|
||||
return callback.relative_update(bytes_transfered)
|
||||
|
||||
return update
|
||||
|
||||
|
||||
class WebHDFSFileSystem(BaseFileSystem): # pylint:disable=abstract-method
|
||||
scheme = Schemes.WEBHDFS
|
||||
PATH_CLS = CloudURLInfo
|
||||
@@ -145,27 +154,27 @@ class WebHDFSFileSystem(BaseFileSystem): # pylint:disable=abstract-method
|
||||
with self.hdfs_client.write(to_info.path) as fdest:
|
||||
shutil.copyfileobj(fobj, fdest)
|
||||
|
||||
def _upload(
|
||||
self, from_file, to_info, name=None, no_progress_bar=False, **_kwargs
|
||||
def put_file(
|
||||
self, from_file, to_info, callback=DEFAULT_CALLBACK, **kwargs
|
||||
):
|
||||
total = os.path.getsize(from_file)
|
||||
with Tqdm(
|
||||
desc=name, total=total, disable=no_progress_bar, bytes=True
|
||||
) as pbar:
|
||||
self.hdfs_client.upload(
|
||||
to_info.path,
|
||||
from_file,
|
||||
overwrite=True,
|
||||
progress=update_pbar(pbar, total),
|
||||
)
|
||||
callback.set_size(total)
|
||||
|
||||
def _download(
|
||||
self, from_info, to_file, name=None, no_progress_bar=False, **_kwargs
|
||||
self.hdfs_client.makedirs(to_info.parent.path)
|
||||
return self.hdfs_client.upload(
|
||||
to_info.path,
|
||||
from_file,
|
||||
overwrite=True,
|
||||
progress=update_callback(callback, total),
|
||||
)
|
||||
|
||||
def get_file(
|
||||
self, from_info, to_file, callback=DEFAULT_CALLBACK, **kwargs
|
||||
):
|
||||
total = self.getsize(from_info)
|
||||
with Tqdm(
|
||||
desc=name, total=total, disable=no_progress_bar, bytes=True
|
||||
) as pbar:
|
||||
self.hdfs_client.download(
|
||||
from_info.path, to_file, progress=update_pbar(pbar, total)
|
||||
)
|
||||
if total:
|
||||
callback.set_size(total)
|
||||
|
||||
self.hdfs_client.download(
|
||||
from_info.path, to_file, progress=update_callback(callback, total)
|
||||
)
|
||||
|
||||
@@ -97,7 +97,6 @@ class ObjectDB:
|
||||
hash_info: "HashInfo",
|
||||
move: bool = True,
|
||||
verify: Optional[bool] = None,
|
||||
**kwargs,
|
||||
):
|
||||
if self.read_only:
|
||||
raise ObjectDBPermissionError("Cannot add to read-only ODB")
|
||||
@@ -121,10 +120,6 @@ class ObjectDB:
|
||||
except (ObjectFormatError, FileNotFoundError):
|
||||
pass
|
||||
|
||||
callback = kwargs.get("download_callback")
|
||||
if callback:
|
||||
callback(1)
|
||||
|
||||
def hash_to_path_info(self, hash_) -> "DvcPath":
|
||||
return self.path_info / hash_[0:2] / hash_[2:]
|
||||
|
||||
|
||||
+3
-2
@@ -849,8 +849,9 @@ class Output:
|
||||
try:
|
||||
objects.check(self.odb, obj)
|
||||
except FileNotFoundError:
|
||||
remote = self.repo.cloud.get_remote_odb(self.remote)
|
||||
self.repo.cloud.pull([obj.hash_info], odb=remote, **kwargs)
|
||||
if self.remote:
|
||||
kwargs["remote"] = self.remote
|
||||
self.repo.cloud.pull([obj.hash_info], **kwargs)
|
||||
|
||||
if self.obj:
|
||||
return self.obj
|
||||
|
||||
+29
-1
@@ -1,5 +1,4 @@
|
||||
"""Manages progress bars for DVC repo."""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
from threading import RLock
|
||||
@@ -182,9 +181,38 @@ class FsspecCallback(fsspec.Callback):
|
||||
size = self.fs.info(self.path_info)["size"]
|
||||
self.progress_bar.total = size
|
||||
self.progress_bar.refresh()
|
||||
super().set_size(size)
|
||||
|
||||
def relative_update(self, inc=1):
|
||||
self.progress_bar.update(inc)
|
||||
super().relative_update(inc)
|
||||
|
||||
def absolute_update(self, value):
|
||||
self.progress_bar.update_to(value)
|
||||
super().absolute_update(value)
|
||||
|
||||
@staticmethod
|
||||
def wrap_fn(cb, fn):
|
||||
def wrapped(*args, **kwargs):
|
||||
res = fn(*args, **kwargs)
|
||||
cb.relative_update()
|
||||
return res
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
def tdqm_or_callback_wrapped(
|
||||
fobj, method, total, callback=None, **pbar_kwargs
|
||||
):
|
||||
if callback:
|
||||
from funcy import nullcontext
|
||||
from tqdm.utils import CallbackIOWrapper
|
||||
|
||||
callback.set_size(total)
|
||||
wrapper = CallbackIOWrapper(callback.relative_update, fobj, method)
|
||||
return nullcontext(wrapper)
|
||||
|
||||
return Tqdm.wrapattr(fobj, method, total=total, bytes=True, **pbar_kwargs)
|
||||
|
||||
|
||||
DEFAULT_CALLBACK = fsspec.callbacks.NoOpCallback()
|
||||
|
||||
+8
-28
@@ -1,48 +1,28 @@
|
||||
import os.path
|
||||
from typing import Dict, List, Set
|
||||
from typing import Dict, List
|
||||
|
||||
import dpath
|
||||
|
||||
|
||||
def get_files(data: Dict) -> Set:
|
||||
def get_files(data: Dict) -> List:
|
||||
files = set()
|
||||
for rev in data.keys():
|
||||
for file in data[rev].get("data", {}).keys():
|
||||
files.add(file)
|
||||
return files
|
||||
sorted_files = sorted(files)
|
||||
return sorted_files
|
||||
|
||||
|
||||
def group_by_filename(plots_data: Dict) -> List[Dict]:
|
||||
# TODO use dpath.util.search once
|
||||
# https://github.com/dpath-maintainers/dpath-python/issues/147 is released
|
||||
# now cannot search when errors are present in data
|
||||
files = get_files(plots_data)
|
||||
grouped = []
|
||||
|
||||
for file in files:
|
||||
tmp: Dict = {}
|
||||
for revision, revision_data in plots_data.items():
|
||||
if file in revision_data.get("data", {}):
|
||||
if "data" not in tmp:
|
||||
tmp[revision] = {"data": {}}
|
||||
tmp[revision]["data"].update(
|
||||
{file: revision_data["data"][file]}
|
||||
)
|
||||
grouped.append(tmp)
|
||||
|
||||
grouped.append(dpath.util.search(plots_data, ["*", "*", file]))
|
||||
return grouped
|
||||
|
||||
|
||||
def find_vega(repo, plots_data, target):
|
||||
# TODO same as group_by_filename
|
||||
grouped = group_by_filename(plots_data)
|
||||
found = None
|
||||
for plot_group in grouped:
|
||||
files = get_files(plot_group)
|
||||
assert len(files) == 1
|
||||
file = files.pop()
|
||||
if file == target:
|
||||
found = plot_group
|
||||
break
|
||||
|
||||
found = dpath.util.search(plots_data, ["*", "*", target])
|
||||
from dvc.render.vega import VegaRenderer
|
||||
|
||||
if found and VegaRenderer.matches(found):
|
||||
|
||||
+15
-9
@@ -193,33 +193,39 @@ def makedirs(path, exist_ok=False, mode=None):
|
||||
logger.trace("failed to chmod '%o' '%s'", mode, path, exc_info=True)
|
||||
|
||||
|
||||
def copyfile(src, dest, no_progress_bar=False, name=None):
|
||||
def copyfile(src, dest, callback=None, no_progress_bar=False, name=None):
|
||||
"""Copy file with progress bar"""
|
||||
from dvc.progress import Tqdm
|
||||
|
||||
name = name if name else os.path.basename(dest)
|
||||
total = os.stat(src).st_size
|
||||
|
||||
if os.path.isdir(dest):
|
||||
dest = os.path.join(dest, os.path.basename(src))
|
||||
|
||||
if callback:
|
||||
callback.set_size(total)
|
||||
|
||||
try:
|
||||
System.reflink(src, dest)
|
||||
except DvcException:
|
||||
from dvc.progress import tdqm_or_callback_wrapped
|
||||
|
||||
with open(src, "rb") as fsrc, open(dest, "wb+") as fdest:
|
||||
with Tqdm.wrapattr(
|
||||
with tdqm_or_callback_wrapped(
|
||||
fdest,
|
||||
"write",
|
||||
desc=name,
|
||||
total,
|
||||
callback=callback,
|
||||
disable=no_progress_bar,
|
||||
total=total,
|
||||
bytes=True,
|
||||
) as fdest_wrapped:
|
||||
desc=name,
|
||||
) as wrapped:
|
||||
while True:
|
||||
buf = fsrc.read(LOCAL_CHUNK_SIZE)
|
||||
if not buf:
|
||||
break
|
||||
fdest_wrapped.write(buf)
|
||||
wrapped.write(buf)
|
||||
|
||||
if callback:
|
||||
callback.absolute_update(total)
|
||||
|
||||
|
||||
def copy_fobj_to_file(fsrc, dest):
|
||||
|
||||
@@ -14,6 +14,13 @@ LOADERS.update(
|
||||
{".toml": load_toml, ".json": load_json, ".py": load_py} # noqa: F405
|
||||
)
|
||||
|
||||
DUMPERS: DefaultDict[str, DumperFn] = defaultdict( # noqa: F405
|
||||
lambda: dump_yaml # noqa: F405
|
||||
)
|
||||
DUMPERS.update(
|
||||
{".toml": dump_toml, ".json": dump_json, ".py": dump_py} # noqa: F405
|
||||
)
|
||||
|
||||
MODIFIERS: DefaultDict[str, ModifierFn] = defaultdict( # noqa: F405
|
||||
lambda: modify_yaml # noqa: F405
|
||||
)
|
||||
|
||||
@@ -63,10 +63,16 @@ def _load_data(path: "AnyPath", parser: ParserFn, fs: "BaseFileSystem" = None):
|
||||
return parser(fd.read(), path)
|
||||
|
||||
|
||||
def _dump_data(path, data: Any, dumper: DumperFn, fs: "BaseFileSystem" = None):
|
||||
def _dump_data(
|
||||
path,
|
||||
data: Any,
|
||||
dumper: DumperFn,
|
||||
fs: "BaseFileSystem" = None,
|
||||
**dumper_args,
|
||||
):
|
||||
open_fn = fs.open if fs else open
|
||||
with open_fn(path, "w+", encoding="utf-8") as fd: # type: ignore
|
||||
dumper(data, fd)
|
||||
dumper(data, fd, **dumper_args)
|
||||
|
||||
|
||||
@contextmanager
|
||||
|
||||
@@ -19,8 +19,8 @@ def parse_json(text, path, **kwargs):
|
||||
return json.loads(text, **kwargs) or {}
|
||||
|
||||
|
||||
def dump_json(path, data, fs=None):
|
||||
return _dump_data(path, data, dumper=json.dump, fs=fs)
|
||||
def dump_json(path, data, fs=None, **kwargs):
|
||||
return _dump_data(path, data, dumper=json.dump, fs=fs, **kwargs)
|
||||
|
||||
|
||||
@contextmanager
|
||||
|
||||
@@ -40,8 +40,8 @@ def _dump(data, stream):
|
||||
return toml.dump(data, stream, encoder=toml.TomlPreserveCommentEncoder())
|
||||
|
||||
|
||||
def dump_toml(path, data, fs=None):
|
||||
return _dump_data(path, data, dumper=_dump, fs=fs)
|
||||
def dump_toml(path, data, fs=None, **kwargs):
|
||||
return _dump_data(path, data, dumper=_dump, fs=fs, **kwargs)
|
||||
|
||||
|
||||
@contextmanager
|
||||
|
||||
@@ -58,8 +58,8 @@ def _dump(data, stream):
|
||||
return yaml.dump(data, stream)
|
||||
|
||||
|
||||
def dump_yaml(path, data, fs=None):
|
||||
return _dump_data(path, data, dumper=_dump, fs=fs)
|
||||
def dump_yaml(path, data, fs=None, **kwargs):
|
||||
return _dump_data(path, data, dumper=_dump, fs=fs, **kwargs)
|
||||
|
||||
|
||||
def loads_yaml(s, typ="safe"):
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
_BASE_VERSION = "2.7.3"
|
||||
_BASE_VERSION = "2.7.4"
|
||||
|
||||
|
||||
def _generate_version(base_version):
|
||||
|
||||
@@ -10,7 +10,8 @@ nanotime>=0.5.2
|
||||
pyasn1>=0.4.1
|
||||
voluptuous>=0.11.7
|
||||
requests>=2.22.0
|
||||
grandalf>=0.6
|
||||
# See https://github.com/bdcht/grandalf/issues/26
|
||||
grandalf==0.6
|
||||
distro>=1.3.0
|
||||
appdirs>=1.4.3
|
||||
ruamel.yaml>=0.17.11
|
||||
@@ -33,7 +34,7 @@ importlib-metadata>=1.4; python_version < '3.8'
|
||||
flatten_dict>=0.4.1,<1
|
||||
tabulate>=0.8.7
|
||||
pygtrie>=2.3.2
|
||||
dpath>=2.0.1,<3
|
||||
dpath>=2.0.2,<3
|
||||
shtab>=1.3.4,<2
|
||||
rich>=10.0.0
|
||||
dictdiffer>=0.8.1
|
||||
@@ -42,4 +43,4 @@ pyparsing==2.4.7
|
||||
typing_extensions>=3.10.0.2
|
||||
fsspec[http]>=2021.8.1
|
||||
aiohttp-retry==2.4.5
|
||||
diskcache>=5.2.1
|
||||
diskcache>=5.2.1
|
||||
|
||||
@@ -1 +1 @@
|
||||
pydrive2[fsspec]>=1.9.2
|
||||
pydrive2[fsspec]>=1.9.4
|
||||
|
||||
@@ -6,13 +6,12 @@ pytest-cov==2.12.1
|
||||
pytest-xdist==2.3.0
|
||||
pytest-mock==3.6.1
|
||||
pytest-lazy-fixture==0.6.3
|
||||
pytest-timeout==1.4.2
|
||||
pytest-docker==0.10.3
|
||||
|
||||
flaky==3.7.0
|
||||
mock==4.0.3
|
||||
rangehttpserver==1.2.0
|
||||
mock-ssh-server==0.9.0
|
||||
mock-ssh-server==0.9.1
|
||||
wget==3.2
|
||||
filelock==3.0.12
|
||||
wsgidav==3.1.1
|
||||
|
||||
@@ -17,8 +17,6 @@ known_first_party=dvc,tests
|
||||
line_length=79
|
||||
|
||||
[tool:pytest]
|
||||
timeout = 200
|
||||
timeout_method = thread
|
||||
log_level = debug
|
||||
addopts = -ra
|
||||
markers =
|
||||
|
||||
@@ -5,6 +5,7 @@ import pytest
|
||||
|
||||
from .dir_helpers import * # noqa, pylint: disable=wildcard-import
|
||||
from .remotes import * # noqa, pylint: disable=wildcard-import
|
||||
from .utils.scriptify import scriptify
|
||||
|
||||
# Prevent updater and analytics from running their processes
|
||||
os.environ["DVC_TEST"] = "true"
|
||||
@@ -164,3 +165,6 @@ def custom_template(tmp_dir, dvc):
|
||||
template = tmp_dir / "custom_template.json"
|
||||
shutil.copy(tmp_dir / ".dvc" / "plots" / "default.json", template)
|
||||
return template
|
||||
|
||||
|
||||
scriptify_fixture = pytest.fixture(lambda: scriptify, name="scriptify")
|
||||
|
||||
@@ -49,12 +49,14 @@ import os
|
||||
import pathlib
|
||||
import sys
|
||||
from contextlib import contextmanager
|
||||
from functools import partialmethod
|
||||
from textwrap import dedent
|
||||
|
||||
import pytest
|
||||
from funcy import lmap, retry
|
||||
|
||||
from dvc.logger import disable_other_loggers
|
||||
from dvc.utils import serialize
|
||||
from dvc.utils.fs import makedirs
|
||||
|
||||
__all__ = [
|
||||
@@ -250,6 +252,22 @@ class TmpDir(pathlib.Path):
|
||||
def hash_to_path_info(self, hash_):
|
||||
return self / hash_[0:2] / hash_[2:]
|
||||
|
||||
def dump(self, *args, **kwargs):
|
||||
return serialize.DUMPERS[self.suffix](self, *args, **kwargs)
|
||||
|
||||
def parse(self, *args, **kwargs):
|
||||
return serialize.LOADERS[self.suffix](self, *args, **kwargs)
|
||||
|
||||
def modify(self, *args, **kwargs):
|
||||
return serialize.MODIFIERS[self.suffix](self, *args, **kwargs)
|
||||
|
||||
load_yaml = partialmethod(serialize.load_yaml)
|
||||
dump_yaml = partialmethod(serialize.dump_yaml)
|
||||
load_json = partialmethod(serialize.load_json)
|
||||
dump_json = partialmethod(serialize.dump_json)
|
||||
load_toml = partialmethod(serialize.load_toml)
|
||||
dump_toml = partialmethod(serialize.dump_toml)
|
||||
|
||||
|
||||
def _coerce_filenames(filenames):
|
||||
if isinstance(filenames, (str, bytes, pathlib.PurePath)):
|
||||
|
||||
@@ -8,7 +8,7 @@ from funcy import first
|
||||
|
||||
from dvc.dvcfile import PIPELINE_FILE
|
||||
from dvc.repo.experiments.utils import exp_refs_by_rev
|
||||
from dvc.utils.serialize import PythonFileCorruptedError, load_yaml
|
||||
from dvc.utils.serialize import PythonFileCorruptedError
|
||||
from tests.func.test_repro_multistage import COPY_SCRIPT
|
||||
|
||||
|
||||
@@ -660,9 +660,9 @@ def test_modified_data_dep(tmp_dir, scm, dvc, workspace, params, target):
|
||||
|
||||
|
||||
def test_exp_run_recursive(tmp_dir, scm, dvc, run_copy_metrics):
|
||||
tmp_dir.dvc_gen("metric_t.json", "foo: 1")
|
||||
tmp_dir.dvc_gen("metric_t.json", '{"foo": 1}')
|
||||
run_copy_metrics(
|
||||
"metric_t.json", "metric.json", metrics=["metric.json"], no_exec=True
|
||||
)
|
||||
assert dvc.experiments.run(".", recursive=True)
|
||||
assert load_yaml(tmp_dir / "metric.json") == {"foo": 1}
|
||||
assert (tmp_dir / "metric.json").parse() == {"foo": 1}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import os
|
||||
|
||||
from dvc.command.experiments import CmdExperimentsInit
|
||||
from dvc.main import main
|
||||
from dvc.utils.serialize import load_yaml
|
||||
|
||||
|
||||
def test_init(tmp_dir, dvc):
|
||||
tmp_dir.gen(
|
||||
{
|
||||
CmdExperimentsInit.CODE: {"copy.py": ""},
|
||||
"data": "data",
|
||||
"params.yaml": '{"foo": 1}',
|
||||
"dvclive": {},
|
||||
"plots": {},
|
||||
}
|
||||
)
|
||||
code_path = os.path.join(CmdExperimentsInit.CODE, "copy.py")
|
||||
script = f"python {code_path}"
|
||||
|
||||
assert main(["exp", "init", script]) == 0
|
||||
assert load_yaml(tmp_dir / "dvc.yaml") == {
|
||||
"stages": {
|
||||
"default": {
|
||||
"cmd": script,
|
||||
"deps": ["data", "src"],
|
||||
"live": {"dvclive": {"html": True, "summary": True}},
|
||||
"metrics": [{"metrics.json": {"cache": False}}],
|
||||
"outs": ["models"],
|
||||
"params": ["foo"],
|
||||
"plots": [{"plots": {"cache": False}}],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ from dvc.repo.experiments.base import EXPS_STASH, ExpRefInfo
|
||||
from dvc.repo.experiments.executor.base import BaseExecutor, ExecutorInfo
|
||||
from dvc.repo.experiments.utils import exp_refs_by_rev
|
||||
from dvc.utils.fs import makedirs
|
||||
from dvc.utils.serialize import YAMLFileCorruptedError, dump_yaml
|
||||
from dvc.utils.serialize import YAMLFileCorruptedError
|
||||
from tests.func.test_repro_multistage import COPY_SCRIPT
|
||||
|
||||
|
||||
@@ -286,7 +286,7 @@ def test_show_filter(
|
||||
"train/bar": 1,
|
||||
"nested": {"foo": 1, "bar": 1},
|
||||
}
|
||||
dump_yaml(params_file, params_data)
|
||||
(tmp_dir / params_file).dump(params_data)
|
||||
|
||||
dvc.run(
|
||||
cmd="python copy.py params.yaml metrics.yaml",
|
||||
@@ -386,7 +386,7 @@ def test_show_running_workspace(tmp_dir, scm, dvc, exp_stage, capsys):
|
||||
makedirs(pid_dir, True)
|
||||
info = ExecutorInfo(None, None, None, BaseExecutor.DEFAULT_LOCATION)
|
||||
pidfile = os.path.join(pid_dir, f"workspace{BaseExecutor.PIDFILE_EXT}")
|
||||
dump_yaml(pidfile, info.to_dict())
|
||||
(tmp_dir / pidfile).dump(info.to_dict())
|
||||
|
||||
assert dvc.experiments.show()["workspace"] == {
|
||||
"baseline": {
|
||||
@@ -417,7 +417,7 @@ def test_show_running_executor(tmp_dir, scm, dvc, exp_stage):
|
||||
makedirs(pid_dir, True)
|
||||
info = ExecutorInfo(None, None, None, BaseExecutor.DEFAULT_LOCATION)
|
||||
pidfile = os.path.join(pid_dir, f"{exp_rev}{BaseExecutor.PIDFILE_EXT}")
|
||||
dump_yaml(pidfile, info.to_dict())
|
||||
(tmp_dir / pidfile).dump(info.to_dict())
|
||||
|
||||
results = dvc.experiments.show()
|
||||
exp_data = get_in(results, [baseline_rev, exp_rev, "data"])
|
||||
@@ -455,7 +455,7 @@ def test_show_running_checkpoint(
|
||||
info = ExecutorInfo(123, "foo.git", baseline_rev, executor)
|
||||
rev = "workspace" if workspace else stash_rev
|
||||
pidfile = os.path.join(pid_dir, f"{rev}{BaseExecutor.PIDFILE_EXT}")
|
||||
dump_yaml(pidfile, info.to_dict())
|
||||
(tmp_dir / pidfile).dump(info.to_dict())
|
||||
|
||||
mocker.patch.object(
|
||||
BaseExecutor, "fetch_exps", return_value=[str(exp_ref)]
|
||||
@@ -495,6 +495,8 @@ def test_show_with_broken_repo(tmp_dir, scm, dvc, exp_stage, caplog):
|
||||
|
||||
|
||||
def test_show_csv(tmp_dir, scm, dvc, exp_stage, capsys):
|
||||
import time
|
||||
|
||||
baseline_rev = scm.get_rev()
|
||||
|
||||
def _get_rev_isotimestamp(rev):
|
||||
@@ -505,6 +507,7 @@ def test_show_csv(tmp_dir, scm, dvc, exp_stage, capsys):
|
||||
result1 = dvc.experiments.run(exp_stage.addressing, params=["foo=2"])
|
||||
rev1 = first(result1)
|
||||
ref_info1 = first(exp_refs_by_rev(scm, rev1))
|
||||
time.sleep(1)
|
||||
result2 = dvc.experiments.run(exp_stage.addressing, params=["foo=3"])
|
||||
rev2 = first(result2)
|
||||
ref_info2 = first(exp_refs_by_rev(scm, rev2))
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
import os
|
||||
import textwrap
|
||||
|
||||
import pytest
|
||||
|
||||
from dvc.main import main
|
||||
|
||||
CONFIG_TEXT = textwrap.dedent(
|
||||
"""\
|
||||
[feature]
|
||||
machine = true
|
||||
['machine \"foo\"']
|
||||
cloud = aws
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"slot,value",
|
||||
[
|
||||
("region", "us-west"),
|
||||
("image", "iterative-cml"),
|
||||
("name", "iterative_test"),
|
||||
("spot", "True"),
|
||||
("spot_price", "1.2345"),
|
||||
("spot_price", "12345"),
|
||||
("instance_hdd_size", "10"),
|
||||
("instance_type", "l"),
|
||||
("instance_gpu", "tesla"),
|
||||
("ssh_private", "secret"),
|
||||
],
|
||||
)
|
||||
def test_machine_modify_susccess(tmp_dir, dvc, slot, value):
|
||||
(tmp_dir / ".dvc" / "config").write_text(CONFIG_TEXT)
|
||||
assert main(["machine", "modify", "foo", slot, value]) == 0
|
||||
assert (
|
||||
tmp_dir / ".dvc" / "config"
|
||||
).read_text() == CONFIG_TEXT + f" {slot} = {value}\n"
|
||||
assert main(["machine", "modify", "--unset", "foo", slot]) == 0
|
||||
assert (tmp_dir / ".dvc" / "config").read_text() == CONFIG_TEXT
|
||||
|
||||
|
||||
def test_machine_modify_startup_script(tmp_dir, dvc):
|
||||
slot, value = "startup_script", "start.sh"
|
||||
(tmp_dir / ".dvc" / "config").write_text(CONFIG_TEXT)
|
||||
assert main(["machine", "modify", "foo", slot, value]) == 0
|
||||
assert (
|
||||
tmp_dir / ".dvc" / "config"
|
||||
).read_text() == CONFIG_TEXT + f" {slot} = ../{value}\n"
|
||||
assert main(["machine", "modify", "--unset", "foo", slot]) == 0
|
||||
assert (tmp_dir / ".dvc" / "config").read_text() == CONFIG_TEXT
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"slot,value,msg",
|
||||
[
|
||||
(
|
||||
"region",
|
||||
"other-west",
|
||||
"expected one of us-west, us-east, eu-west, eu-north",
|
||||
),
|
||||
("spot_price", "NUM", "expected float"),
|
||||
("instance_hdd_size", "BIG", "expected int"),
|
||||
],
|
||||
)
|
||||
def test_machine_modify_fail(tmp_dir, dvc, caplog, slot, value, msg):
|
||||
(tmp_dir / ".dvc" / "config").write_text(CONFIG_TEXT)
|
||||
|
||||
assert main(["machine", "modify", "foo", slot, value]) == 251
|
||||
assert (tmp_dir / ".dvc" / "config").read_text() == CONFIG_TEXT
|
||||
assert msg in caplog.text
|
||||
|
||||
|
||||
FULL_CONFIG_TEXT = textwrap.dedent(
|
||||
"""\
|
||||
[feature]
|
||||
machine = true
|
||||
['machine \"bar\"']
|
||||
cloud = azure
|
||||
['machine \"foo\"']
|
||||
cloud = aws
|
||||
region = us-west
|
||||
image = iterative-cml
|
||||
name = iterative_test
|
||||
spot = True
|
||||
spot_price = 1.2345
|
||||
instance_hdd_size = 10
|
||||
instance_type = l
|
||||
instance_gpu = tesla
|
||||
ssh_private = secret
|
||||
startup_script = {}
|
||||
""".format(
|
||||
os.path.join("..", "start.sh")
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def test_machine_list(tmp_dir, dvc, capsys):
|
||||
(tmp_dir / ".dvc" / "config").write_text(FULL_CONFIG_TEXT)
|
||||
|
||||
assert main(["machine", "list"]) == 0
|
||||
cap = capsys.readouterr()
|
||||
assert "cloud=azure" in cap.out
|
||||
|
||||
assert main(["machine", "list", "foo"]) == 0
|
||||
cap = capsys.readouterr()
|
||||
assert "cloud=azure" not in cap.out
|
||||
assert "cloud=aws" in cap.out
|
||||
assert "region=us-west" in cap.out
|
||||
assert "image=iterative-cml" in cap.out
|
||||
assert "name=iterative_test" in cap.out
|
||||
assert "spot=True" in cap.out
|
||||
assert "spot_price=1.2345" in cap.out
|
||||
assert "instance_hdd_size=10" in cap.out
|
||||
assert "instance_type=l" in cap.out
|
||||
assert "instance_gpu=tesla" in cap.out
|
||||
assert "ssh_private=secret" in cap.out
|
||||
assert (
|
||||
"startup_script={}".format(
|
||||
os.path.join(tmp_dir, ".dvc", "..", "start.sh")
|
||||
)
|
||||
in cap.out
|
||||
)
|
||||
@@ -1,7 +1,6 @@
|
||||
import json
|
||||
|
||||
from dvc.main import main
|
||||
from dvc.utils.serialize import dump_yaml
|
||||
|
||||
|
||||
def test_metrics_diff_simple(tmp_dir, scm, dvc, run_copy_metrics):
|
||||
@@ -22,7 +21,7 @@ def test_metrics_diff_simple(tmp_dir, scm, dvc, run_copy_metrics):
|
||||
def test_metrics_diff_yaml(tmp_dir, scm, dvc, run_copy_metrics):
|
||||
def _gen(val):
|
||||
metrics = {"a": {"b": {"c": val, "d": 1, "e": str(val)}}}
|
||||
dump_yaml("m_temp.yaml", metrics)
|
||||
(tmp_dir / "m_temp.yaml").dump(metrics)
|
||||
run_copy_metrics(
|
||||
"m_temp.yaml", "m.yaml", metrics=["m.yaml"], commit=str(val)
|
||||
)
|
||||
@@ -39,7 +38,7 @@ def test_metrics_diff_yaml(tmp_dir, scm, dvc, run_copy_metrics):
|
||||
def test_metrics_diff_json(tmp_dir, scm, dvc, run_copy_metrics):
|
||||
def _gen(val):
|
||||
metrics = {"a": {"b": {"c": val, "d": 1, "e": str(val)}}}
|
||||
tmp_dir.gen({"m_temp.json": json.dumps(metrics)})
|
||||
(tmp_dir / "m_temp.json").dump(metrics)
|
||||
run_copy_metrics(
|
||||
"m_temp.json", "m.json", metrics=["m.json"], commit=str(val)
|
||||
)
|
||||
@@ -55,7 +54,7 @@ def test_metrics_diff_json(tmp_dir, scm, dvc, run_copy_metrics):
|
||||
def test_metrics_diff_json_unchanged(tmp_dir, scm, dvc, run_copy_metrics):
|
||||
def _gen(val):
|
||||
metrics = {"a": {"b": {"c": val, "d": 1, "e": str(val)}}}
|
||||
tmp_dir.gen({"m_temp.json": json.dumps(metrics)})
|
||||
(tmp_dir / "m_temp.json").dump(metrics)
|
||||
run_copy_metrics(
|
||||
"m_temp.json", "m.json", metrics=["m.json"], commit=str(val)
|
||||
)
|
||||
@@ -69,7 +68,7 @@ def test_metrics_diff_json_unchanged(tmp_dir, scm, dvc, run_copy_metrics):
|
||||
|
||||
def test_metrics_diff_broken_json(tmp_dir, scm, dvc, run_copy_metrics):
|
||||
metrics = {"a": {"b": {"c": 1, "d": 1, "e": "3"}}}
|
||||
tmp_dir.gen({"m_temp.json": json.dumps(metrics)})
|
||||
(tmp_dir / "m_temp.json").dump(metrics)
|
||||
run_copy_metrics(
|
||||
"m_temp.json",
|
||||
"m.json",
|
||||
@@ -94,7 +93,7 @@ def test_metrics_diff_no_metrics(tmp_dir, scm, dvc):
|
||||
|
||||
def test_metrics_diff_new_metric(tmp_dir, scm, dvc, run_copy_metrics):
|
||||
metrics = {"a": {"b": {"c": 1, "d": 1, "e": "3"}}}
|
||||
tmp_dir.gen({"m_temp.json": json.dumps(metrics)})
|
||||
(tmp_dir / "m_temp.json").dump(metrics)
|
||||
run_copy_metrics("m_temp.json", "m.json", metrics_no_cache=["m.json"])
|
||||
|
||||
assert dvc.metrics.diff() == {
|
||||
@@ -107,7 +106,7 @@ def test_metrics_diff_new_metric(tmp_dir, scm, dvc, run_copy_metrics):
|
||||
|
||||
def test_metrics_diff_deleted_metric(tmp_dir, scm, dvc, run_copy_metrics):
|
||||
metrics = {"a": {"b": {"c": 1, "d": 1, "e": "3"}}}
|
||||
tmp_dir.gen({"m_temp.json": json.dumps(metrics)})
|
||||
(tmp_dir / "m_temp.json").dump(metrics)
|
||||
run_copy_metrics(
|
||||
"m_temp.json",
|
||||
"m.json",
|
||||
|
||||
@@ -6,10 +6,9 @@ from funcy import get_in
|
||||
|
||||
from dvc.dvcfile import PIPELINE_FILE
|
||||
from dvc.exceptions import OverlappingOutputPathsError
|
||||
from dvc.path_info import PathInfo
|
||||
from dvc.repo import Repo
|
||||
from dvc.utils.fs import remove
|
||||
from dvc.utils.serialize import YAMLFileCorruptedError, dump_yaml, modify_yaml
|
||||
from dvc.utils.serialize import YAMLFileCorruptedError
|
||||
|
||||
|
||||
def test_show_simple(tmp_dir, dvc, run_copy_metrics):
|
||||
@@ -227,10 +226,10 @@ def test_show_no_metrics_files(tmp_dir, dvc, caplog):
|
||||
def test_metrics_show_overlap(
|
||||
tmp_dir, dvc, run_copy_metrics, clear_before_run
|
||||
):
|
||||
data_dir = PathInfo("data")
|
||||
(tmp_dir / data_dir).mkdir()
|
||||
data_dir = tmp_dir / "data"
|
||||
data_dir.mkdir()
|
||||
|
||||
dump_yaml(data_dir / "m1_temp.yaml", {"a": {"b": {"c": 2, "d": 1}}})
|
||||
(data_dir / "m1_temp.yaml").dump({"a": {"b": {"c": 2, "d": 1}}})
|
||||
run_copy_metrics(
|
||||
str(data_dir / "m1_temp.yaml"),
|
||||
str(data_dir / "m1.yaml"),
|
||||
@@ -239,7 +238,7 @@ def test_metrics_show_overlap(
|
||||
name="cp-m1",
|
||||
metrics=[str(data_dir / "m1.yaml")],
|
||||
)
|
||||
with modify_yaml("dvc.yaml") as d:
|
||||
with (tmp_dir / "dvc.yaml").modify() as d:
|
||||
# trying to make an output overlaps error
|
||||
d["stages"]["corrupted-stage"] = {
|
||||
"cmd": "mkdir data",
|
||||
|
||||
@@ -76,7 +76,7 @@ def test_partial_upload(tmp_dir, dvc, index, mocker):
|
||||
tmp_dir.dvc_gen({"foo": "foo content"})
|
||||
tmp_dir.dvc_gen({"bar": {"baz": "baz content"}})
|
||||
|
||||
original = LocalFileSystem._upload
|
||||
original = LocalFileSystem.upload
|
||||
|
||||
def unreliable_upload(self, from_file, to_info, name=None, **kwargs):
|
||||
if "baz" in name:
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from dvc.utils import relpath
|
||||
from dvc.utils.serialize import dump_yaml
|
||||
|
||||
|
||||
def test_diff_no_params(tmp_dir, scm, dvc):
|
||||
@@ -140,7 +139,7 @@ def test_no_commits(tmp_dir):
|
||||
def test_vars_shows_on_params_diff(tmp_dir, scm, dvc):
|
||||
params_file = tmp_dir / "test_params.yaml"
|
||||
param_data = {"vars": {"model1": {"epoch": 15}, "model2": {"epoch": 35}}}
|
||||
dump_yaml(params_file, param_data)
|
||||
(tmp_dir / params_file).dump(param_data)
|
||||
d = {
|
||||
"vars": ["test_params.yaml"],
|
||||
"stages": {
|
||||
@@ -150,7 +149,7 @@ def test_vars_shows_on_params_diff(tmp_dir, scm, dvc):
|
||||
}
|
||||
},
|
||||
}
|
||||
dump_yaml("dvc.yaml", d)
|
||||
(tmp_dir / "dvc.yaml").dump(d)
|
||||
assert dvc.params.diff() == {
|
||||
"test_params.yaml": {
|
||||
"vars.model1.epoch": {"new": 15, "old": None},
|
||||
@@ -161,7 +160,7 @@ def test_vars_shows_on_params_diff(tmp_dir, scm, dvc):
|
||||
scm.commit("added stages")
|
||||
|
||||
param_data["vars"]["model1"]["epoch"] = 20
|
||||
dump_yaml(params_file, param_data)
|
||||
(tmp_dir / params_file).dump(param_data)
|
||||
assert dvc.params.diff() == {
|
||||
"test_params.yaml": {
|
||||
"vars.model1.epoch": {"new": 20, "old": 15, "diff": 5}
|
||||
|
||||
@@ -10,7 +10,6 @@ from dvc.parsing import ResolveError
|
||||
from dvc.parsing.context import Context
|
||||
from dvc.parsing.interpolate import embrace
|
||||
from dvc.utils.humanize import join
|
||||
from dvc.utils.serialize import dump_yaml
|
||||
|
||||
from . import make_entry_definition, make_foreach_def
|
||||
|
||||
@@ -133,7 +132,7 @@ def test_interpolate_non_string(tmp_dir, dvc):
|
||||
|
||||
|
||||
def test_partial_vars_doesnot_exist(tmp_dir, dvc):
|
||||
dump_yaml("test_params.yaml", {"sub1": "sub1", "sub2": "sub2"})
|
||||
(tmp_dir / "test_params.yaml").dump({"sub1": "sub1", "sub2": "sub2"})
|
||||
|
||||
definition = make_entry_definition(
|
||||
tmp_dir,
|
||||
@@ -281,7 +280,7 @@ def test_item_key_in_generated_stage_vars(tmp_dir, dvc, redefine, from_file):
|
||||
context = Context(foo="bar")
|
||||
vars_ = [redefine]
|
||||
if from_file:
|
||||
dump_yaml("test_params.yaml", redefine)
|
||||
(tmp_dir / "test_params.yaml").dump(redefine)
|
||||
vars_ = ["test_params.yaml"]
|
||||
|
||||
definition = make_foreach_def(
|
||||
|
||||
@@ -5,7 +5,6 @@ import pytest
|
||||
|
||||
from dvc.parsing import DEFAULT_PARAMS_FILE, DataResolver, ForeachDefinition
|
||||
from dvc.parsing.context import Context
|
||||
from dvc.utils.serialize import dump_yaml
|
||||
|
||||
|
||||
def test_with_simple_list_data(tmp_dir, dvc):
|
||||
@@ -111,7 +110,7 @@ def test_foreach_interpolate_with_composite_data(
|
||||
def test_params_file_with_dict_tracked(tmp_dir, dvc):
|
||||
foreach_data = {"model1": {"thresh": "foo"}, "model2": {"thresh": "bar"}}
|
||||
params = {"models": foreach_data}
|
||||
dump_yaml("params.yaml", params)
|
||||
(tmp_dir / "params.yaml").dump(params)
|
||||
|
||||
resolver = DataResolver(dvc, tmp_dir, {})
|
||||
data = {"foreach": "${models}", "do": {"cmd": "echo ${item.thresh}"}}
|
||||
@@ -132,7 +131,7 @@ def test_params_file_with_dict_tracked(tmp_dir, dvc):
|
||||
def test_params_file_tracked_for_composite_list(tmp_dir, dvc):
|
||||
foreach_data = [{"thresh": "foo"}, {"thresh": "bar"}]
|
||||
params = {"models": foreach_data}
|
||||
dump_yaml("params.yaml", params)
|
||||
(tmp_dir / "params.yaml").dump(params)
|
||||
|
||||
resolver = DataResolver(dvc, tmp_dir, {})
|
||||
data = {"foreach": "${models}", "do": {"cmd": "echo ${item.thresh}"}}
|
||||
@@ -178,8 +177,8 @@ def test_foreach_partial_interpolations(tmp_dir, dvc):
|
||||
|
||||
|
||||
def test_mixed_vars_for_foreach_data(tmp_dir, dvc):
|
||||
dump_yaml("params.yaml", {"models": {"model1": "foo"}})
|
||||
dump_yaml("test_params.yaml", {"models": {"model2": "bar"}})
|
||||
(tmp_dir / "params.yaml").dump({"models": {"model1": "foo"}})
|
||||
(tmp_dir / "test_params.yaml").dump({"models": {"model2": "bar"}})
|
||||
|
||||
resolver = DataResolver(dvc, tmp_dir, {"vars": ["test_params.yaml"]})
|
||||
data = {"foreach": "${models}", "do": {"cmd": "echo ${item}"}}
|
||||
@@ -197,12 +196,10 @@ def test_mixed_vars_for_foreach_data(tmp_dir, dvc):
|
||||
|
||||
|
||||
def test_mixed_vars_for_foreach_data_2(tmp_dir, dvc):
|
||||
dump_yaml(
|
||||
"params.yaml",
|
||||
(tmp_dir / "params.yaml").dump(
|
||||
{"models": {"model1": {"thresh": 10}, "model2": {"thresh": 15}}},
|
||||
)
|
||||
dump_yaml(
|
||||
"test_params.yaml",
|
||||
(tmp_dir / "test_params.yaml").dump(
|
||||
{"models": {"model1": {"epochs": 5}, "model2": {"epochs": 10}}},
|
||||
)
|
||||
|
||||
@@ -288,9 +285,9 @@ def test_foreach_with_local_vars(tmp_dir, dvc):
|
||||
],
|
||||
)
|
||||
def test_foreach_with_imported_vars(tmp_dir, dvc, local_import):
|
||||
dump_yaml("params.yaml", {"models": {"model1": {"thresh": "foo"}}})
|
||||
dump_yaml(
|
||||
"test_params.yaml", {"train": {"epochs": 10}, "prepare": {"nums": 25}}
|
||||
(tmp_dir / "params.yaml").dump({"models": {"model1": {"thresh": "foo"}}})
|
||||
(tmp_dir / "test_params.yaml").dump(
|
||||
{"train": {"epochs": 10}, "prepare": {"nums": 25}}
|
||||
)
|
||||
resolver = DataResolver(dvc, tmp_dir, {})
|
||||
foreach_data = ["foo", "bar"]
|
||||
@@ -321,13 +318,12 @@ def test_foreach_with_imported_vars(tmp_dir, dvc, local_import):
|
||||
def test_foreach_with_interpolated_wdir_and_local_vars(
|
||||
tmp_dir, dvc, local_import
|
||||
):
|
||||
dump_yaml("params.yaml", {"models": {"model1": {"thresh": "foo"}}})
|
||||
(tmp_dir / "params.yaml").dump({"models": {"model1": {"thresh": "foo"}}})
|
||||
|
||||
for i in range(5):
|
||||
build_dir = tmp_dir / ("model-" + str(i))
|
||||
build_dir.mkdir()
|
||||
dump_yaml(
|
||||
build_dir / "params.yaml",
|
||||
(build_dir / "params.yaml").dump(
|
||||
{"train": {"epochs": 1 + i}, "prepare": {"nums": 10 * i}},
|
||||
)
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ from dvc.dependency import _merge_params
|
||||
from dvc.parsing import DEFAULT_PARAMS_FILE, DataResolver
|
||||
from dvc.parsing.context import recurse_not_a_node
|
||||
from dvc.path_info import PathInfo
|
||||
from dvc.utils.serialize import dump_json, dump_yaml
|
||||
|
||||
from . import (
|
||||
CONTEXT_DATA,
|
||||
@@ -30,7 +29,7 @@ def assert_stage_equal(d1, d2):
|
||||
|
||||
|
||||
def test_simple(tmp_dir, dvc):
|
||||
dump_yaml(tmp_dir / DEFAULT_PARAMS_FILE, CONTEXT_DATA)
|
||||
(tmp_dir / DEFAULT_PARAMS_FILE).dump(CONTEXT_DATA)
|
||||
resolver = DataResolver(
|
||||
dvc, PathInfo(str(tmp_dir)), deepcopy(TEMPLATED_DVC_YAML_DATA)
|
||||
)
|
||||
@@ -46,7 +45,7 @@ def test_vars_import(tmp_dir, dvc):
|
||||
Test that different file can be loaded using `vars`
|
||||
instead of default params.yaml.
|
||||
"""
|
||||
dump_yaml(tmp_dir / "params2.yaml", CONTEXT_DATA)
|
||||
(tmp_dir / "params2.yaml").dump(CONTEXT_DATA)
|
||||
d = deepcopy(TEMPLATED_DVC_YAML_DATA)
|
||||
d["vars"] = ["params2.yaml"]
|
||||
resolver = DataResolver(dvc, PathInfo(str(tmp_dir)), d)
|
||||
@@ -68,7 +67,7 @@ def test_vars_and_params_import(tmp_dir, dvc):
|
||||
"vars": [DEFAULT_PARAMS_FILE, {"dict": {"foo": "foobar"}}],
|
||||
"stages": {"stage1": {"cmd": "echo ${dict.foo} ${dict.bar}"}},
|
||||
}
|
||||
dump_yaml(tmp_dir / DEFAULT_PARAMS_FILE, {"dict": {"bar": "bar"}})
|
||||
(tmp_dir / DEFAULT_PARAMS_FILE).dump({"dict": {"bar": "bar"}})
|
||||
resolver = DataResolver(dvc, PathInfo(str(tmp_dir)), d)
|
||||
|
||||
assert_stage_equal(
|
||||
@@ -96,8 +95,8 @@ def test_stage_with_wdir(tmp_dir, dvc):
|
||||
|
||||
data_dir = tmp_dir / "data"
|
||||
data_dir.mkdir()
|
||||
dump_yaml(tmp_dir / DEFAULT_PARAMS_FILE, {"dict": {"bar": "bar"}})
|
||||
dump_json(data_dir / DEFAULT_PARAMS_FILE, {"dict": {"foo": "foo"}})
|
||||
(tmp_dir / DEFAULT_PARAMS_FILE).dump({"dict": {"bar": "bar"}})
|
||||
(data_dir / DEFAULT_PARAMS_FILE).dump({"dict": {"foo": "foo"}})
|
||||
resolver = DataResolver(dvc, PathInfo(str(tmp_dir)), d)
|
||||
|
||||
assert_stage_equal(
|
||||
@@ -135,12 +134,12 @@ def test_with_templated_wdir(tmp_dir, dvc):
|
||||
}
|
||||
}
|
||||
}
|
||||
dump_yaml(
|
||||
tmp_dir / DEFAULT_PARAMS_FILE, {"dict": {"bar": "bar", "ws": "data"}}
|
||||
(tmp_dir / DEFAULT_PARAMS_FILE).dump(
|
||||
{"dict": {"bar": "bar", "ws": "data"}}
|
||||
)
|
||||
data_dir = tmp_dir / "data"
|
||||
data_dir.mkdir()
|
||||
dump_json(data_dir / DEFAULT_PARAMS_FILE, {"dict": {"foo": "foo"}})
|
||||
(data_dir / DEFAULT_PARAMS_FILE).dump({"dict": {"foo": "foo"}})
|
||||
resolver = DataResolver(dvc, PathInfo(str(tmp_dir)), d)
|
||||
|
||||
assert_stage_equal(
|
||||
@@ -167,7 +166,7 @@ def test_with_templated_wdir(tmp_dir, dvc):
|
||||
|
||||
def test_resolve_local_tries_to_load_globally_used_files(tmp_dir, dvc):
|
||||
iterable = {"bar": "bar", "foo": "foo"}
|
||||
dump_json(tmp_dir / "params.json", iterable)
|
||||
(tmp_dir / "params.json").dump(iterable)
|
||||
|
||||
d = {
|
||||
"vars": ["params.json"],
|
||||
@@ -196,7 +195,7 @@ def test_resolve_local_tries_to_load_globally_used_files(tmp_dir, dvc):
|
||||
|
||||
def test_resolve_local_tries_to_load_globally_used_params_yaml(tmp_dir, dvc):
|
||||
iterable = {"bar": "bar", "foo": "foo"}
|
||||
dump_yaml(tmp_dir / "params.yaml", iterable)
|
||||
(tmp_dir / "params.yaml").dump(iterable)
|
||||
|
||||
d = {
|
||||
"stages": {
|
||||
@@ -224,7 +223,7 @@ def test_resolve_local_tries_to_load_globally_used_params_yaml(tmp_dir, dvc):
|
||||
|
||||
def test_vars_relpath_overwrite(tmp_dir, dvc):
|
||||
iterable = {"bar": "bar", "foo": "foo"}
|
||||
dump_yaml(tmp_dir / "params.yaml", iterable)
|
||||
(tmp_dir / "params.yaml").dump(iterable)
|
||||
d = {
|
||||
"vars": ["params.yaml"],
|
||||
"stages": {
|
||||
@@ -252,7 +251,7 @@ def test_vars_relpath_overwrite(tmp_dir, dvc):
|
||||
)
|
||||
def test_vars_load_partial(tmp_dir, dvc, local, vars_):
|
||||
iterable = {"bar": "bar", "foo": "foo"}
|
||||
dump_yaml(tmp_dir / "test_params.yaml", iterable)
|
||||
(tmp_dir / "test_params.yaml").dump(iterable)
|
||||
d = {"stages": {"build": {"cmd": "echo ${bar}"}}}
|
||||
if local:
|
||||
d["stages"]["build"]["vars"] = vars_
|
||||
|
||||
@@ -5,7 +5,7 @@ import pytest
|
||||
|
||||
from dvc.parsing import DEFAULT_PARAMS_FILE, DataResolver, ResolveError
|
||||
from dvc.parsing.context import Context
|
||||
from dvc.utils.serialize import dump_json, dump_yaml, dumps_yaml
|
||||
from dvc.utils.serialize import dumps_yaml
|
||||
|
||||
from . import CONTEXT_DATA, RESOLVED_DVC_YAML_DATA, TEMPLATED_DVC_YAML_DATA
|
||||
|
||||
@@ -50,16 +50,16 @@ def test_vars_interpolation_errors(tmp_dir, dvc, vars_):
|
||||
"vars_", [{}, {"vars": []}, {"vars": [DEFAULT_PARAMS_FILE]}]
|
||||
)
|
||||
def test_default_params_file(tmp_dir, dvc, vars_):
|
||||
dump_yaml(DEFAULT_PARAMS_FILE, DATA)
|
||||
(tmp_dir / DEFAULT_PARAMS_FILE).dump(DATA)
|
||||
resolver = DataResolver(dvc, tmp_dir, vars_)
|
||||
assert resolver.context == DATA
|
||||
|
||||
|
||||
def test_load_vars_from_file(tmp_dir, dvc):
|
||||
dump_yaml(DEFAULT_PARAMS_FILE, DATA)
|
||||
(tmp_dir / DEFAULT_PARAMS_FILE).dump(DATA)
|
||||
|
||||
datasets = {"datasets": ["foo", "bar"]}
|
||||
dump_json("params.json", datasets)
|
||||
(tmp_dir / "params.json").dump(datasets)
|
||||
d = {"vars": [DEFAULT_PARAMS_FILE, "params.json"]}
|
||||
resolver = DataResolver(dvc, tmp_dir, d)
|
||||
|
||||
@@ -82,7 +82,7 @@ def test_load_vars_with_relpath(tmp_dir, scm, dvc):
|
||||
|
||||
|
||||
def test_partial_vars_doesnot_exist(tmp_dir, dvc):
|
||||
dump_yaml("test_params.yaml", {"sub1": "sub1"})
|
||||
(tmp_dir / "test_params.yaml").dump({"sub1": "sub1"})
|
||||
|
||||
with pytest.raises(ResolveError) as exc_info:
|
||||
DataResolver(dvc, tmp_dir, {"vars": ["test_params.yaml:sub2"]})
|
||||
@@ -94,8 +94,8 @@ def test_partial_vars_doesnot_exist(tmp_dir, dvc):
|
||||
|
||||
|
||||
def test_global_overwrite_error_on_imports(tmp_dir, dvc):
|
||||
dump_yaml(DEFAULT_PARAMS_FILE, DATA)
|
||||
dump_json("params.json", DATA)
|
||||
(tmp_dir / DEFAULT_PARAMS_FILE).dump(DATA)
|
||||
(tmp_dir / "params.json").dump(DATA)
|
||||
|
||||
d = {"vars": [DEFAULT_PARAMS_FILE, "params.json"]}
|
||||
with pytest.raises(ResolveError) as exc_info:
|
||||
@@ -109,7 +109,7 @@ def test_global_overwrite_error_on_imports(tmp_dir, dvc):
|
||||
|
||||
|
||||
def test_global_overwrite_vars(tmp_dir, dvc):
|
||||
dump_yaml(DEFAULT_PARAMS_FILE, DATA)
|
||||
(tmp_dir / DEFAULT_PARAMS_FILE).dump(DATA)
|
||||
d = {"vars": [DATA]}
|
||||
|
||||
with pytest.raises(ResolveError) as exc_info:
|
||||
@@ -123,7 +123,7 @@ def test_global_overwrite_vars(tmp_dir, dvc):
|
||||
|
||||
|
||||
def test_local_declared_vars_overwrite(tmp_dir, dvc):
|
||||
dump_yaml(DEFAULT_PARAMS_FILE, DATA)
|
||||
(tmp_dir / DEFAULT_PARAMS_FILE).dump(DATA)
|
||||
|
||||
d = {"vars": [DATA["models"], DATA["models"]]}
|
||||
with pytest.raises(ResolveError) as exc_info:
|
||||
@@ -158,7 +158,7 @@ def test_specified_params_file_not_exist(tmp_dir, dvc):
|
||||
)
|
||||
def test_vars_already_loaded_message(tmp_dir, dvc, local, vars_):
|
||||
d = {"stages": {"build": {"cmd": "echo ${sub1} ${sub2}"}}}
|
||||
dump_yaml("test_params.yaml", {"sub1": "sub1", "sub2": "sub2"})
|
||||
(tmp_dir / "test_params.yaml").dump({"sub1": "sub1", "sub2": "sub2"})
|
||||
if not local:
|
||||
d["vars"] = vars_
|
||||
else:
|
||||
@@ -175,8 +175,8 @@ def test_vars_already_loaded_message(tmp_dir, dvc, local, vars_):
|
||||
"vars_, loc", [(DATA, "build.vars[0]"), ("params.json", "params.json")]
|
||||
)
|
||||
def test_local_overwrite_error(tmp_dir, dvc, vars_, loc):
|
||||
dump_yaml(DEFAULT_PARAMS_FILE, DATA)
|
||||
dump_json("params.json", DATA)
|
||||
(tmp_dir / DEFAULT_PARAMS_FILE).dump(DATA)
|
||||
(tmp_dir / "params.json").dump(DATA)
|
||||
|
||||
d = {"stages": {"build": {"cmd": "echo ${models.foo}", "vars": [vars_]}}}
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
from tests.func.plots.utils import _write_json
|
||||
|
||||
|
||||
def test_diff_dirty(tmp_dir, scm, dvc, run_copy_metrics):
|
||||
_write_json(tmp_dir, [{"y": 2}, {"y": 3}], "metric_t.json")
|
||||
(tmp_dir / "metric_t.json").dump([{"y": 2}, {"y": 3}], sort_keys=True)
|
||||
run_copy_metrics(
|
||||
"metric_t.json",
|
||||
"metric.json",
|
||||
@@ -11,7 +8,7 @@ def test_diff_dirty(tmp_dir, scm, dvc, run_copy_metrics):
|
||||
)
|
||||
|
||||
metric_head = [{"y": 3}, {"y": 5}]
|
||||
_write_json(tmp_dir, metric_head, "metric_t.json")
|
||||
(tmp_dir / "metric_t.json").dump_json(metric_head, sort_keys=True)
|
||||
run_copy_metrics(
|
||||
"metric_t.json",
|
||||
"metric.json",
|
||||
@@ -20,7 +17,7 @@ def test_diff_dirty(tmp_dir, scm, dvc, run_copy_metrics):
|
||||
)
|
||||
|
||||
metric_1 = [{"y": 5}, {"y": 6}]
|
||||
_write_json(tmp_dir, metric_1, "metric_t.json")
|
||||
(tmp_dir / "metric_t.json").dump_json(metric_1, sort_keys=True)
|
||||
run_copy_metrics(
|
||||
"metric_t.json", "metric.json", plots_no_cache=["metric.json"]
|
||||
)
|
||||
@@ -35,7 +32,7 @@ def test_diff_dirty(tmp_dir, scm, dvc, run_copy_metrics):
|
||||
},
|
||||
}
|
||||
metric_2 = [{"y": 7}, {"y": 8}]
|
||||
_write_json(tmp_dir, metric_2, "metric.json")
|
||||
(tmp_dir / "metric.json").dump_json(metric_2, sort_keys=True)
|
||||
|
||||
diff_result = dvc.plots.diff(props=props)
|
||||
assert diff_result == {
|
||||
|
||||
@@ -6,14 +6,13 @@ from dvc.dvcfile import PIPELINE_LOCK
|
||||
from dvc.repo.plots import PropsNotFoundError
|
||||
from dvc.repo.plots.template import TemplateNotFoundError
|
||||
from dvc.utils import relpath
|
||||
from tests.func.plots.utils import _write_json
|
||||
|
||||
|
||||
def test_plots_modify_existing_template(
|
||||
tmp_dir, dvc, run_copy_metrics, custom_template
|
||||
):
|
||||
metric = [{"a": 1, "b": 2}, {"a": 2, "b": 3}]
|
||||
_write_json(tmp_dir, metric, "metric_t.json")
|
||||
(tmp_dir / "metric_t.json").dump_json(metric, sort_keys=True)
|
||||
stage = run_copy_metrics(
|
||||
"metric_t.json",
|
||||
"metric.json",
|
||||
@@ -31,7 +30,7 @@ def test_plots_modify_existing_template(
|
||||
def test_plots_modify_should_not_change_lockfile(
|
||||
tmp_dir, dvc, run_copy_metrics, custom_template
|
||||
):
|
||||
_write_json(tmp_dir, [{"a": 1, "b": 2}], "metric_t.json")
|
||||
(tmp_dir / "metric_t.json").dump_json([{"a": 1, "b": 2}], sort_keys=True)
|
||||
run_copy_metrics(
|
||||
"metric_t.json",
|
||||
"metric.json",
|
||||
@@ -56,7 +55,7 @@ def test_plots_modify_not_existing_template(dvc):
|
||||
|
||||
def test_unset_nonexistent(tmp_dir, dvc, run_copy_metrics, custom_template):
|
||||
metric = [{"a": 1, "b": 2}, {"a": 2, "b": 3}]
|
||||
_write_json(tmp_dir, metric, "metric_t.json")
|
||||
(tmp_dir / "metric_t.json").dump_json(metric, sort_keys=True)
|
||||
run_copy_metrics(
|
||||
"metric_t.json",
|
||||
"metric.json",
|
||||
@@ -76,7 +75,7 @@ def test_dir_plots(tmp_dir, dvc, run_copy_metrics):
|
||||
metric = [{"first_val": 100, "val": 2}, {"first_val": 200, "val": 3}]
|
||||
|
||||
fname = "file.json"
|
||||
_write_json(tmp_dir, metric, fname)
|
||||
(tmp_dir / fname).dump_json(metric, sort_keys=True)
|
||||
|
||||
p1 = os.path.join("subdir", "p1.json")
|
||||
p2 = os.path.join("subdir", "p2.json")
|
||||
|
||||
@@ -6,23 +6,16 @@ from funcy import get_in
|
||||
from dvc.dvcfile import PIPELINE_FILE
|
||||
from dvc.exceptions import OverlappingOutputPathsError
|
||||
from dvc.main import main
|
||||
from dvc.path_info import PathInfo
|
||||
from dvc.render.vega import PlotMetricTypeError
|
||||
from dvc.repo import Repo
|
||||
from dvc.utils import onerror_collect
|
||||
from dvc.utils.fs import remove
|
||||
from dvc.utils.serialize import (
|
||||
EncodingError,
|
||||
YAMLFileCorruptedError,
|
||||
dump_yaml,
|
||||
modify_yaml,
|
||||
)
|
||||
from tests.func.plots.utils import _write_json
|
||||
from dvc.utils.serialize import EncodingError, YAMLFileCorruptedError
|
||||
|
||||
|
||||
def test_plot_cache_missing(tmp_dir, scm, dvc, caplog, run_copy_metrics):
|
||||
metric1 = [{"y": 2}, {"y": 3}]
|
||||
_write_json(tmp_dir, metric1, "metric_t.json")
|
||||
(tmp_dir / "metric_t.json").dump_json(metric1, sort_keys=True)
|
||||
run_copy_metrics(
|
||||
"metric_t.json",
|
||||
"metric.json",
|
||||
@@ -33,7 +26,7 @@ def test_plot_cache_missing(tmp_dir, scm, dvc, caplog, run_copy_metrics):
|
||||
|
||||
# Make a different plot and then remove its datafile
|
||||
metric2 = [{"y": 3}, {"y": 4}]
|
||||
_write_json(tmp_dir, metric2, "metric_t.json")
|
||||
(tmp_dir / "metric_t.json").dump_json(metric2, sort_keys=True)
|
||||
stage = run_copy_metrics(
|
||||
"metric_t.json",
|
||||
"metric.json",
|
||||
@@ -71,7 +64,7 @@ def test_plot_wrong_metric_type(tmp_dir, scm, dvc, run_copy_metrics):
|
||||
@pytest.mark.parametrize("use_dvc", [True, False])
|
||||
def test_show_non_plot(tmp_dir, scm, use_dvc):
|
||||
metric = [{"first_val": 100, "val": 2}, {"first_val": 200, "val": 3}]
|
||||
_write_json(tmp_dir, metric, "metric.json")
|
||||
(tmp_dir / "metric.json").dump_json(metric, sort_keys=True)
|
||||
|
||||
if use_dvc:
|
||||
dvc = Repo.init()
|
||||
@@ -87,7 +80,7 @@ def test_show_non_plot_and_plot_with_params(
|
||||
tmp_dir, scm, dvc, run_copy_metrics
|
||||
):
|
||||
metric = [{"first_val": 100, "val": 2}, {"first_val": 200, "val": 3}]
|
||||
_write_json(tmp_dir, metric, "metric.json")
|
||||
(tmp_dir / "metric.json").dump_json(metric, sort_keys=True)
|
||||
run_copy_metrics(
|
||||
"metric.json", "metric2.json", plots_no_cache=["metric2.json"]
|
||||
)
|
||||
@@ -105,7 +98,7 @@ def test_show_from_subdir(tmp_dir, dvc, capsys):
|
||||
|
||||
subdir.mkdir()
|
||||
metric = [{"first_val": 100, "val": 2}, {"first_val": 200, "val": 3}]
|
||||
_write_json(subdir, metric, "metric.json")
|
||||
(subdir / "metric.json").dump_json(metric, sort_keys=True)
|
||||
|
||||
with subdir.chdir():
|
||||
assert main(["plots", "show", "metric.json"]) == 0
|
||||
@@ -127,10 +120,10 @@ def test_plots_show_non_existing(tmp_dir, dvc, caplog):
|
||||
|
||||
@pytest.mark.parametrize("clear_before_run", [True, False])
|
||||
def test_plots_show_overlap(tmp_dir, dvc, run_copy_metrics, clear_before_run):
|
||||
data_dir = PathInfo("data")
|
||||
(tmp_dir / data_dir).mkdir()
|
||||
data_dir = tmp_dir / "data"
|
||||
data_dir.mkdir()
|
||||
|
||||
dump_yaml(data_dir / "m1_temp.yaml", {"a": {"b": {"c": 2, "d": 1}}})
|
||||
(data_dir / "m1_temp.yaml").dump({"a": {"b": {"c": 2, "d": 1}}})
|
||||
run_copy_metrics(
|
||||
str(data_dir / "m1_temp.yaml"),
|
||||
str(data_dir / "m1.yaml"),
|
||||
@@ -139,7 +132,7 @@ def test_plots_show_overlap(tmp_dir, dvc, run_copy_metrics, clear_before_run):
|
||||
name="cp-m1",
|
||||
plots=[str(data_dir / "m1.yaml")],
|
||||
)
|
||||
with modify_yaml("dvc.yaml") as d:
|
||||
with (tmp_dir / "dvc.yaml").modify() as d:
|
||||
# trying to make an output overlaps error
|
||||
d["stages"]["corrupted-stage"] = {
|
||||
"cmd": "mkdir data",
|
||||
@@ -167,7 +160,7 @@ def test_dir_plots(tmp_dir, dvc, run_copy_metrics):
|
||||
metric = [{"first_val": 100, "val": 2}, {"first_val": 200, "val": 3}]
|
||||
|
||||
fname = "file.json"
|
||||
_write_json(tmp_dir, metric, fname)
|
||||
(tmp_dir / fname).dump_json(metric, sort_keys=True)
|
||||
|
||||
p1 = os.path.join("subdir", "p1.json")
|
||||
p2 = os.path.join("subdir", "p2.json")
|
||||
@@ -213,7 +206,7 @@ def test_log_errors(
|
||||
tmp_dir, scm, dvc, run_copy_metrics, file, error_path, capsys
|
||||
):
|
||||
metric = [{"val": 2}, {"val": 3}]
|
||||
dump_yaml("metric_t.yaml", metric)
|
||||
(tmp_dir / "metric_t.yaml").dump(metric)
|
||||
run_copy_metrics(
|
||||
"metric_t.yaml",
|
||||
"plot.yaml",
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import json
|
||||
|
||||
from tests.utils import dump_sv
|
||||
|
||||
|
||||
def _write_csv(metric, filename, header=True):
|
||||
with open(filename, "w", newline="") as csvobj:
|
||||
dump_sv(csvobj, metric, delimiter=",", header=header)
|
||||
|
||||
|
||||
def _write_json(tmp_dir, metric, filename):
|
||||
tmp_dir.gen(filename, json.dumps(metric, sort_keys=True))
|
||||
@@ -55,7 +55,7 @@ def test_add(tmp_dir, dvc):
|
||||
assert stage.outs[0].hash_info == HashInfo("md5", md5)
|
||||
assert stage.md5 is None
|
||||
|
||||
assert load_yaml("foo.dvc") == {
|
||||
assert (tmp_dir / "foo.dvc").parse() == {
|
||||
"outs": [
|
||||
{
|
||||
"md5": "acbd18db4cc2f85cedef654fccc4a4d8",
|
||||
@@ -73,7 +73,7 @@ def test_add_executable(tmp_dir, dvc):
|
||||
os.chmod("foo", st.st_mode | stat.S_IEXEC)
|
||||
dvc.add("foo")
|
||||
|
||||
assert load_yaml("foo.dvc") == {
|
||||
assert (tmp_dir / "foo.dvc").parse() == {
|
||||
"outs": [
|
||||
{
|
||||
"md5": "acbd18db4cc2f85cedef654fccc4a4d8",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import os
|
||||
from unittest import mock
|
||||
from unittest.mock import call
|
||||
|
||||
import pytest
|
||||
@@ -9,17 +8,17 @@ from dvc.main import main
|
||||
from dvc.repo import Repo
|
||||
|
||||
|
||||
@mock.patch("dvc.analytics.send")
|
||||
def test_daemon_analytics(mock_send, tmp_path):
|
||||
def test_daemon_analytics(mocker, tmp_path):
|
||||
mock_send = mocker.patch("dvc.analytics.send")
|
||||
report = os.fspath(tmp_path)
|
||||
assert 0 == main(["daemon", "analytics", report])
|
||||
|
||||
mock_send.assert_called_with(report)
|
||||
|
||||
|
||||
@mock.patch("dvc.analytics.collect_and_send_report")
|
||||
@mock.patch("dvc.analytics.is_enabled", return_value=True)
|
||||
def test_main_analytics(mock_is_enabled, mock_report, tmp_dir, dvc):
|
||||
def test_main_analytics(mocker, tmp_dir, dvc):
|
||||
mock_is_enabled = mocker.patch("dvc.analytics.collect_and_send_report")
|
||||
mock_report = mocker.patch("dvc.analytics.is_enabled", return_value=True)
|
||||
tmp_dir.gen("foo", "text")
|
||||
assert 0 == main(["add", "foo"])
|
||||
assert mock_is_enabled.called
|
||||
@@ -46,8 +45,8 @@ class ANY:
|
||||
return isinstance(other, self.expected_type)
|
||||
|
||||
|
||||
@mock.patch("requests.post")
|
||||
def test_collect_and_send_report(mock_post, dvc, mock_daemon):
|
||||
def test_collect_and_send_report(mocker, dvc, mock_daemon):
|
||||
mock_post = mocker.patch("requests.post")
|
||||
collect_and_send_report()
|
||||
|
||||
assert mock_daemon.call_count == 1
|
||||
|
||||
@@ -867,9 +867,9 @@ def test_checkout_external_modified_file(tmp_dir, dvc, scm, mocker, workspace):
|
||||
def test_checkout_executable(tmp_dir, dvc):
|
||||
tmp_dir.dvc_gen("foo", "foo")
|
||||
|
||||
contents = load_yaml("foo.dvc")
|
||||
contents = (tmp_dir / "foo.dvc").parse()
|
||||
contents["outs"][0]["isexec"] = True
|
||||
dump_yaml("foo.dvc", contents)
|
||||
(tmp_dir / "foo.dvc").dump(contents)
|
||||
|
||||
dvc.checkout("foo")
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ from dvc.dependency.base import DependencyDoesNotExistError
|
||||
from dvc.dvcfile import PIPELINE_FILE
|
||||
from dvc.output import OutputDoesNotExistError
|
||||
from dvc.stage.exceptions import StageCommitError
|
||||
from dvc.utils.serialize import dump_yaml, load_yaml
|
||||
from tests.utils import clean_staging
|
||||
|
||||
|
||||
@@ -65,9 +64,9 @@ def test_commit_changed_md5(tmp_dir, dvc):
|
||||
tmp_dir.gen({"file": "file content"})
|
||||
(stage,) = dvc.add("file", no_commit=True)
|
||||
|
||||
stage_file_content = load_yaml(stage.path)
|
||||
stage_file_content = (tmp_dir / stage.path).parse()
|
||||
stage_file_content["md5"] = "1111111111"
|
||||
dump_yaml(stage.path, stage_file_content)
|
||||
(tmp_dir / stage.path).dump(stage_file_content)
|
||||
|
||||
clean_staging()
|
||||
|
||||
@@ -75,7 +74,7 @@ def test_commit_changed_md5(tmp_dir, dvc):
|
||||
dvc.commit(stage.path)
|
||||
|
||||
dvc.commit(stage.path, force=True)
|
||||
assert "md5" not in load_yaml(stage.path)
|
||||
assert "md5" not in (tmp_dir / stage.path).parse()
|
||||
|
||||
|
||||
def test_commit_no_exec(tmp_dir, dvc):
|
||||
|
||||
@@ -77,6 +77,12 @@ def test_config_set_local(tmp_dir, dvc):
|
||||
assert (tmp_dir / ".dvc" / "config.local").read_text() == "\n"
|
||||
|
||||
|
||||
def test_config_set_in_non_dvc_repo(tmp_dir, caplog):
|
||||
assert main(["config", "core.analytics", "true"]) != 0
|
||||
out = caplog.text
|
||||
assert "Not inside a DVC repo" in out
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"args, ret, msg",
|
||||
[
|
||||
@@ -128,6 +134,21 @@ def test_config_get(tmp_dir, dvc, capsys, caplog, args, ret, msg):
|
||||
assert msg in text
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"args, ret",
|
||||
[
|
||||
(["--local", "core.remote"], 251),
|
||||
(["--project", "core.remote"], 251),
|
||||
(["core.remote"], 0),
|
||||
],
|
||||
)
|
||||
def test_config_get_in_non_dvc_repo(tmp_dir, caplog, args, ret):
|
||||
assert main(["config"] + args) == ret
|
||||
if ret != 0:
|
||||
out = caplog.text
|
||||
assert "Not inside a DVC repo" in out
|
||||
|
||||
|
||||
def test_config_list(tmp_dir, dvc, capsys):
|
||||
(tmp_dir / ".dvc" / "config").write_text(
|
||||
textwrap.dedent(
|
||||
@@ -169,6 +190,21 @@ def test_config_list(tmp_dir, dvc, capsys):
|
||||
assert "core.remote=myremote" in out
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"args, ret",
|
||||
[
|
||||
(["--list", "--local"], 251),
|
||||
(["--list", "--project"], 251),
|
||||
(["--list"], 0),
|
||||
],
|
||||
)
|
||||
def test_config_list_in_non_dvc_repo(tmp_dir, caplog, args, ret):
|
||||
assert main(["config"] + args) == ret
|
||||
if ret != 0:
|
||||
out = caplog.text
|
||||
assert "Not inside a DVC repo" in out
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"args", [["core.analytics"], ["core.analytics", "false"], ["--unset"]]
|
||||
)
|
||||
|
||||
@@ -10,7 +10,6 @@ from dvc.external_repo import clean_repos
|
||||
from dvc.main import main
|
||||
from dvc.stage.exceptions import StageNotFound
|
||||
from dvc.utils.fs import move, remove
|
||||
from dvc.utils.serialize import dump_yaml, load_yaml
|
||||
|
||||
all_clouds = [
|
||||
pytest.lazy_fixture(cloud)
|
||||
@@ -204,9 +203,9 @@ def test_warn_on_outdated_stage(tmp_dir, dvc, local_remote, caplog):
|
||||
assert main(["push"]) == 0
|
||||
|
||||
stage_file_path = stage.relpath
|
||||
content = load_yaml(stage_file_path)
|
||||
content = (tmp_dir / stage_file_path).parse()
|
||||
del content["outs"][0]["md5"]
|
||||
dump_yaml(stage_file_path, content)
|
||||
(tmp_dir / stage_file_path).dump(content)
|
||||
|
||||
with caplog.at_level(logging.WARNING, logger="dvc"):
|
||||
caplog.clear()
|
||||
@@ -612,8 +611,6 @@ def test_pull_no_00_prefix(tmp_dir, dvc, remote, monkeypatch):
|
||||
|
||||
|
||||
def test_output_remote(tmp_dir, dvc, make_remote):
|
||||
from dvc.utils.serialize import modify_yaml
|
||||
|
||||
make_remote("default", default=True)
|
||||
make_remote("for_foo", default=False)
|
||||
make_remote("for_data", default=False)
|
||||
@@ -622,10 +619,10 @@ def test_output_remote(tmp_dir, dvc, make_remote):
|
||||
tmp_dir.dvc_gen("bar", "bar")
|
||||
tmp_dir.dvc_gen("data", {"one": "one", "two": "two"})
|
||||
|
||||
with modify_yaml("foo.dvc") as d:
|
||||
with (tmp_dir / "foo.dvc").modify() as d:
|
||||
d["outs"][0]["remote"] = "for_foo"
|
||||
|
||||
with modify_yaml("data.dvc") as d:
|
||||
with (tmp_dir / "data.dvc").modify() as d:
|
||||
d["outs"][0]["remote"] = "for_data"
|
||||
|
||||
dvc.push()
|
||||
@@ -653,3 +650,35 @@ def test_output_remote(tmp_dir, dvc, make_remote):
|
||||
"6b18131dc289fd37006705affe961ef8.dir",
|
||||
"b8a9f715dbb64fd5c56e7783c6820a61",
|
||||
}
|
||||
|
||||
|
||||
def test_target_remote(tmp_dir, dvc, make_remote):
|
||||
make_remote("default", default=True)
|
||||
make_remote("myremote", default=False)
|
||||
|
||||
tmp_dir.dvc_gen("foo", "foo")
|
||||
tmp_dir.dvc_gen("data", {"one": "one", "two": "two"})
|
||||
|
||||
dvc.push(remote="myremote")
|
||||
|
||||
default = dvc.cloud.get_remote_odb("default")
|
||||
myremote = dvc.cloud.get_remote_odb("myremote")
|
||||
|
||||
assert set(default.all()) == set()
|
||||
assert set(myremote.all()) == {
|
||||
"acbd18db4cc2f85cedef654fccc4a4d8",
|
||||
"f97c5d29941bfb1b2fdab0874906ab82",
|
||||
"6b18131dc289fd37006705affe961ef8.dir",
|
||||
"b8a9f715dbb64fd5c56e7783c6820a61",
|
||||
}
|
||||
|
||||
clean(["foo", "data"], dvc)
|
||||
|
||||
dvc.pull(remote="myremote")
|
||||
|
||||
assert set(dvc.odb.local.all()) == {
|
||||
"acbd18db4cc2f85cedef654fccc4a4d8",
|
||||
"f97c5d29941bfb1b2fdab0874906ab82",
|
||||
"6b18131dc289fd37006705affe961ef8.dir",
|
||||
"b8a9f715dbb64fd5c56e7783c6820a61",
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ from dvc.stage.exceptions import (
|
||||
StageFileFormatError,
|
||||
)
|
||||
from dvc.stage.loader import StageNotFound
|
||||
from dvc.utils.serialize import dump_yaml
|
||||
|
||||
|
||||
def test_run_load_one_for_multistage(tmp_dir, dvc):
|
||||
@@ -245,14 +244,14 @@ def test_remove_stage_on_lockfile_format_error(tmp_dir, dvc, run_copy):
|
||||
lock_data = lock_file.load()
|
||||
lock_data["gibberish"] = True
|
||||
data["gibberish"] = True
|
||||
dump_yaml(lock_file.relpath, lock_data)
|
||||
(tmp_dir / lock_file.relpath).dump(lock_data)
|
||||
with pytest.raises(StageFileFormatError):
|
||||
dvc_file.remove_stage(stage)
|
||||
|
||||
lock_file.remove()
|
||||
dvc_file.dump(stage, update_pipeline=False)
|
||||
|
||||
dump_yaml(dvc_file.relpath, data)
|
||||
(tmp_dir / dvc_file.relpath).dump(data)
|
||||
with pytest.raises(StageFileFormatError):
|
||||
dvc_file.remove_stage(stage)
|
||||
|
||||
@@ -335,7 +334,7 @@ def test_dvcfile_dump_preserves_desc(tmp_dir, dvc, run_copy):
|
||||
|
||||
data = dvcfile._load()[0]
|
||||
data["stages"]["run_copy"]["outs"][0] = {"bar": {"desc": out_desc}}
|
||||
dump_yaml(dvcfile.path, data)
|
||||
(tmp_dir / dvcfile.path).dump(data)
|
||||
|
||||
assert stage.desc == stage_desc
|
||||
stage.outs[0].desc = out_desc
|
||||
@@ -376,7 +375,7 @@ def test_dvcfile_dump_preserves_comments(tmp_dir, dvc):
|
||||
],
|
||||
)
|
||||
def test_dvcfile_try_dumping_parametrized_stage(tmp_dir, dvc, data, name):
|
||||
dump_yaml("dvc.yaml", {"stages": data, "vars": [{"foo": "foobar"}]})
|
||||
(tmp_dir / "dvc.yaml").dump({"stages": data, "vars": [{"foo": "foobar"}]})
|
||||
|
||||
stage = dvc.stage.load_one(name=name)
|
||||
dvcfile = stage.dvcfile
|
||||
|
||||
@@ -13,7 +13,7 @@ from tests.unit.fs.test_repo import make_subrepo
|
||||
from tests.utils import clean_staging
|
||||
|
||||
|
||||
def test_external_repo(erepo_dir):
|
||||
def test_external_repo(erepo_dir, mocker):
|
||||
with erepo_dir.chdir():
|
||||
with erepo_dir.branch("branch", new=True):
|
||||
erepo_dir.dvc_gen("file", "branch", commit="create file on branch")
|
||||
@@ -21,16 +21,17 @@ def test_external_repo(erepo_dir):
|
||||
|
||||
url = os.fspath(erepo_dir)
|
||||
|
||||
with patch.object(Git, "clone", wraps=Git.clone) as mock:
|
||||
with external_repo(url) as repo:
|
||||
with repo.open_by_relpath("file") as fd:
|
||||
assert fd.read() == "master"
|
||||
mock = mocker.patch.object(Git, "clone", wraps=Git.clone)
|
||||
|
||||
with external_repo(url, rev="branch") as repo:
|
||||
with repo.open_by_relpath("file") as fd:
|
||||
assert fd.read() == "branch"
|
||||
with external_repo(url) as repo:
|
||||
with repo.open_by_relpath("file") as fd:
|
||||
assert fd.read() == "master"
|
||||
|
||||
assert mock.call_count == 1
|
||||
with external_repo(url, rev="branch") as repo:
|
||||
with repo.open_by_relpath("file") as fd:
|
||||
assert fd.read() == "branch"
|
||||
|
||||
assert mock.call_count == 1
|
||||
|
||||
|
||||
def test_source_change(erepo_dir):
|
||||
|
||||
@@ -3,6 +3,7 @@ import os
|
||||
from operator import itemgetter
|
||||
from os.path import join
|
||||
|
||||
import fsspec
|
||||
import pytest
|
||||
|
||||
from dvc.fs import get_cloud_fs
|
||||
@@ -413,3 +414,154 @@ def test_fs_makedirs_on_upload_and_copy(dvc, cloud):
|
||||
fs.copy(cloud / "dir" / "foo", cloud / "dir2" / "foo")
|
||||
assert fs.isdir(cloud / "dir2")
|
||||
assert fs.exists(cloud / "dir2" / "foo")
|
||||
|
||||
|
||||
@pytest.mark.needs_internet
|
||||
@pytest.mark.parametrize(
|
||||
"cloud",
|
||||
[
|
||||
pytest.lazy_fixture("azure"),
|
||||
pytest.lazy_fixture("gs"),
|
||||
pytest.lazy_fixture("gdrive"),
|
||||
pytest.lazy_fixture("hdfs"),
|
||||
pytest.lazy_fixture("local_cloud"),
|
||||
pytest.lazy_fixture("oss"),
|
||||
pytest.lazy_fixture("s3"),
|
||||
pytest.lazy_fixture("ssh"),
|
||||
pytest.lazy_fixture("webhdfs"),
|
||||
pytest.lazy_fixture("webdav"),
|
||||
pytest.lazy_fixture("http"),
|
||||
],
|
||||
)
|
||||
def test_upload_callback(tmp_dir, dvc, cloud):
|
||||
tmp_dir.gen("foo", "foo")
|
||||
cls, config, _ = get_cloud_fs(dvc, **cloud.config)
|
||||
fs = cls(**config)
|
||||
expected_size = os.path.getsize(tmp_dir / "foo")
|
||||
|
||||
callback = fsspec.Callback()
|
||||
fs.upload(tmp_dir / "foo", cloud / "foo", callback=callback)
|
||||
|
||||
assert callback.size == expected_size
|
||||
assert callback.value == expected_size
|
||||
|
||||
|
||||
@pytest.mark.needs_internet
|
||||
@pytest.mark.parametrize(
|
||||
"cloud",
|
||||
[
|
||||
pytest.lazy_fixture("azure"),
|
||||
pytest.lazy_fixture("gs"),
|
||||
pytest.lazy_fixture("gdrive"),
|
||||
pytest.lazy_fixture("hdfs"),
|
||||
pytest.lazy_fixture("local_cloud"),
|
||||
pytest.lazy_fixture("oss"),
|
||||
pytest.lazy_fixture("s3"),
|
||||
pytest.lazy_fixture("ssh"),
|
||||
pytest.lazy_fixture("webhdfs"),
|
||||
pytest.lazy_fixture("webdav"),
|
||||
pytest.lazy_fixture("http"),
|
||||
],
|
||||
)
|
||||
def test_download_callback(tmp_dir, dvc, cloud, local_cloud):
|
||||
cls, config, _ = get_cloud_fs(dvc, **cloud.config)
|
||||
fs = cls(**config)
|
||||
|
||||
(tmp_dir / "to_upload").write_text("foo")
|
||||
fs.upload(tmp_dir / "to_upload", cloud / "foo")
|
||||
expected_size = fs.getsize(cloud / "foo")
|
||||
|
||||
callback = fsspec.Callback()
|
||||
fs.download_file(cloud / "foo", tmp_dir / "foo", callback=callback)
|
||||
|
||||
assert callback.size == expected_size
|
||||
assert callback.value == expected_size
|
||||
assert (tmp_dir / "foo").read_text() == "foo"
|
||||
|
||||
|
||||
@pytest.mark.needs_internet
|
||||
@pytest.mark.parametrize(
|
||||
"cloud",
|
||||
[
|
||||
pytest.lazy_fixture("azure"),
|
||||
pytest.lazy_fixture("gs"),
|
||||
pytest.lazy_fixture("hdfs"),
|
||||
pytest.lazy_fixture("local_cloud"),
|
||||
pytest.lazy_fixture("s3"),
|
||||
pytest.param(
|
||||
pytest.lazy_fixture("ssh"),
|
||||
marks=pytest.mark.skipif(
|
||||
os.name == "nt", reason="unsupported on Windows."
|
||||
),
|
||||
),
|
||||
pytest.lazy_fixture("gdrive"),
|
||||
pytest.lazy_fixture("webdav"),
|
||||
],
|
||||
)
|
||||
def test_download_dir_callback(tmp_dir, dvc, cloud):
|
||||
cls, config, _ = get_cloud_fs(dvc, **cloud.config)
|
||||
fs = cls(**config)
|
||||
cloud.gen({"dir": {"foo": "foo", "bar": "bar"}})
|
||||
|
||||
callback = fsspec.Callback()
|
||||
fs.download(cloud / "dir", tmp_dir / "dir", callback=callback)
|
||||
|
||||
assert callback.size == 2
|
||||
assert callback.value == 2
|
||||
assert (tmp_dir / "dir").read_text() == {"foo": "foo", "bar": "bar"}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fs_type", ["git", "dvc"])
|
||||
def test_download_callbacks_on_dvc_git_fs(tmp_dir, dvc, scm, fs_type):
|
||||
gen = tmp_dir.scm_gen if fs_type == "git" else tmp_dir.dvc_gen
|
||||
gen({"dir": {"foo": "foo", "bar": "bar"}, "file": "file"}, commit="gen")
|
||||
|
||||
fs = dvc.dvcfs if fs_type == "dvc" else scm.get_fs("HEAD")
|
||||
|
||||
callback = fsspec.Callback()
|
||||
fs.download(tmp_dir / "file", tmp_dir / "file2", callback=callback)
|
||||
|
||||
size = os.path.getsize(tmp_dir / "file")
|
||||
assert (tmp_dir / "file2").read_text() == "file"
|
||||
assert callback.size == size
|
||||
assert callback.value == size
|
||||
|
||||
if fs_type == "git":
|
||||
pytest.skip("gitfs does not support download_dir")
|
||||
|
||||
callback = fsspec.Callback()
|
||||
fs.download(tmp_dir / "dir", tmp_dir / "dir2", callback=callback)
|
||||
|
||||
assert (tmp_dir / "dir2").read_text() == {"foo": "foo", "bar": "bar"}
|
||||
assert callback.size == 2
|
||||
assert callback.value == 2
|
||||
|
||||
|
||||
def test_callback_on_repo_fs(tmp_dir, dvc, scm):
|
||||
tmp_dir.dvc_gen({"dir": {"bar": "bar"}}, commit="dvc")
|
||||
tmp_dir.scm_gen({"dir": {"foo": "foo"}}, commit="git")
|
||||
|
||||
fs = dvc.repo_fs
|
||||
|
||||
callback = fsspec.Callback()
|
||||
fs.download(tmp_dir / "dir", tmp_dir / "dir2", callback=callback)
|
||||
|
||||
assert (tmp_dir / "dir2").read_text() == {"foo": "foo", "bar": "bar"}
|
||||
assert callback.size == 2
|
||||
assert callback.value == 2
|
||||
|
||||
callback = fsspec.Callback()
|
||||
fs.download(tmp_dir / "dir" / "foo", tmp_dir / "foo", callback=callback)
|
||||
|
||||
size = os.path.getsize(tmp_dir / "dir" / "foo")
|
||||
assert (tmp_dir / "foo").read_text() == "foo"
|
||||
assert callback.size == size
|
||||
assert callback.value == size
|
||||
|
||||
callback = fsspec.Callback()
|
||||
fs.download(tmp_dir / "dir" / "bar", tmp_dir / "bar", callback=callback)
|
||||
|
||||
size = os.path.getsize(tmp_dir / "dir" / "bar")
|
||||
assert (tmp_dir / "bar").read_text() == "bar"
|
||||
assert callback.size == size
|
||||
assert callback.value == size
|
||||
|
||||
@@ -9,12 +9,7 @@ from dvc.dvcfile import PIPELINE_LOCK, Lockfile, LockfileCorruptedError
|
||||
from dvc.hash_info import HashInfo
|
||||
from dvc.stage.utils import split_params_deps
|
||||
from dvc.utils.fs import remove
|
||||
from dvc.utils.serialize import (
|
||||
dump_yaml,
|
||||
dumps_yaml,
|
||||
load_yaml,
|
||||
parse_yaml_for_update,
|
||||
)
|
||||
from dvc.utils.serialize import dumps_yaml, parse_yaml_for_update
|
||||
from tests.func.test_run_multistage import supported_params
|
||||
|
||||
FS_STRUCTURE = {
|
||||
@@ -183,7 +178,7 @@ def v1_repo_lock(tmp_dir, dvc):
|
||||
}
|
||||
dvc.run(cmd="echo foo", name="foo", no_exec=True)
|
||||
dvc.run(cmd="echo bar>bar.txt", outs=["bar.txt"], name="bar", no_exec=True)
|
||||
dump_yaml(tmp_dir / "dvc.lock", v1_lockdata)
|
||||
(tmp_dir / "dvc.lock").dump(v1_lockdata)
|
||||
yield v1_lockdata
|
||||
|
||||
|
||||
@@ -202,7 +197,7 @@ def test_migrates_v1_lockfile_to_v2_during_dump(
|
||||
assert dvc.reproduce()
|
||||
|
||||
assert "Migrating lock file 'dvc.lock' from v1 to v2" in caplog.messages
|
||||
d = load_yaml(tmp_dir / "dvc.lock")
|
||||
d = (tmp_dir / "dvc.lock").parse()
|
||||
assert d == {"stages": v1_repo_lock, "schema": "2.0"}
|
||||
|
||||
|
||||
@@ -211,7 +206,7 @@ def test_migrates_v1_lockfile_to_v2_during_dump(
|
||||
)
|
||||
def test_lockfile_invalid_versions(tmp_dir, dvc, version_info):
|
||||
lockdata = {**version_info, "stages": {"foo": {"cmd": "echo foo"}}}
|
||||
dump_yaml("dvc.lock", lockdata)
|
||||
(tmp_dir / "dvc.lock").dump(lockdata)
|
||||
with pytest.raises(LockfileCorruptedError) as exc_info:
|
||||
Lockfile(dvc, tmp_dir / "dvc.lock").load()
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import pytest
|
||||
from dvc.dvcfile import DVC_FILE_SUFFIX
|
||||
from dvc.exceptions import DvcException, MoveNotDataSourceError
|
||||
from dvc.main import main
|
||||
from dvc.utils.serialize import dump_yaml, load_yaml
|
||||
from dvc.utils.serialize import load_yaml
|
||||
from tests.basic_env import TestDvc, TestDvcGit
|
||||
from tests.func.test_repro import TestRepro
|
||||
from tests.utils import cd
|
||||
@@ -249,9 +249,9 @@ def test_move_output_overlap(tmp_dir, dvc):
|
||||
|
||||
def test_move_meta(tmp_dir, dvc):
|
||||
(stage,) = tmp_dir.dvc_gen("foo", "foo")
|
||||
data = load_yaml(stage.path)
|
||||
data = (tmp_dir / stage.path).parse()
|
||||
data["meta"] = {"custom_key": 42}
|
||||
dump_yaml(stage.path, data)
|
||||
(tmp_dir / stage.path).dump(data)
|
||||
|
||||
dvc.move("foo", "bar")
|
||||
res = (tmp_dir / "bar.dvc").read_text()
|
||||
|
||||
@@ -7,7 +7,6 @@ from pygtrie import Trie
|
||||
from dvc.repo.index import Index
|
||||
from dvc.stage import PipelineStage, Stage
|
||||
from dvc.utils import relpath
|
||||
from tests.func.plots.utils import _write_json
|
||||
|
||||
|
||||
def test_index(tmp_dir, scm, dvc, run_copy):
|
||||
@@ -103,7 +102,9 @@ def test_deps_outs_getters(tmp_dir, dvc, run_copy_metrics):
|
||||
params=["param"],
|
||||
name="copy-metrics",
|
||||
)
|
||||
_write_json(tmp_dir, [{"a": 1, "b": 2}, {"a": 2, "b": 3}], "metric_t.json")
|
||||
(tmp_dir / "metric_t.json").dump_json(
|
||||
[{"a": 1, "b": 2}, {"a": 2, "b": 3}], sort_keys=True
|
||||
)
|
||||
run_stage2 = run_copy_metrics(
|
||||
"metric_t.json",
|
||||
"metric.json",
|
||||
|
||||
@@ -1244,7 +1244,7 @@ def test_repro_when_cmd_changes(tmp_dir, dvc, run_copy, mocker):
|
||||
|
||||
data = SingleStageFile(dvc, stage.path)._load()[0]
|
||||
data["cmd"] = " ".join(stage.cmd.split()) # change cmd spacing by two
|
||||
dump_yaml(stage.path, data)
|
||||
(tmp_dir / stage.path).dump(data)
|
||||
|
||||
assert dvc.status([stage.addressing]) == {
|
||||
stage.addressing: ["changed checksum"]
|
||||
|
||||
@@ -9,17 +9,17 @@ from dvc.dvcfile import PIPELINE_FILE, PIPELINE_LOCK
|
||||
from dvc.exceptions import CyclicGraphError, ReproductionError
|
||||
from dvc.main import main
|
||||
from dvc.stage import PipelineStage
|
||||
from dvc.utils.serialize import dump_yaml, load_yaml
|
||||
from tests.func import test_repro
|
||||
from tests.utils.scriptify import scriptify
|
||||
|
||||
COPY_SCRIPT_FORMAT = dedent(
|
||||
"""\
|
||||
import sys
|
||||
|
||||
def copy_script(src, dest):
|
||||
import shutil
|
||||
shutil.copyfile({}, {})
|
||||
"""
|
||||
)
|
||||
COPY_SCRIPT = COPY_SCRIPT_FORMAT.format("sys.argv[1]", "sys.argv[2]")
|
||||
|
||||
shutil.copyfile(src, dest)
|
||||
|
||||
|
||||
COPY_SCRIPT, _ = scriptify(copy_script)
|
||||
|
||||
|
||||
class MultiStageRun:
|
||||
@@ -317,7 +317,7 @@ def test_repro_when_new_deps_is_added_in_dvcfile(tmp_dir, dvc, run_copy):
|
||||
dvcfile = Dvcfile(dvc, stage.path)
|
||||
data, _ = dvcfile._load()
|
||||
data["stages"]["copy-file"]["deps"] += ["copy.py"]
|
||||
dump_yaml(stage.path, data)
|
||||
(tmp_dir / stage.path).dump(data)
|
||||
|
||||
assert dvc.reproduce(target)[0] == stage
|
||||
|
||||
@@ -339,7 +339,7 @@ def test_repro_when_new_outs_is_added_in_dvcfile(tmp_dir, dvc):
|
||||
dvcfile = Dvcfile(dvc, stage.path)
|
||||
data, _ = dvcfile._load()
|
||||
data["stages"]["copy-file"]["outs"] = ["foobar"]
|
||||
dump_yaml(stage.path, data)
|
||||
(tmp_dir / stage.path).dump(data)
|
||||
|
||||
assert dvc.reproduce(target)[0] == stage
|
||||
|
||||
@@ -358,7 +358,10 @@ def test_repro_when_new_deps_is_moved(tmp_dir, dvc):
|
||||
target = ":copy-file"
|
||||
assert not dvc.reproduce(target)
|
||||
|
||||
tmp_dir.gen("copy.py", COPY_SCRIPT_FORMAT.format("'bar'", "'foobar'"))
|
||||
# hardcode values in source code, ignore sys.argv
|
||||
tmp_dir.gen(
|
||||
"copy.py", COPY_SCRIPT.replace("func(*argv)", 'func("bar", "foobar")')
|
||||
)
|
||||
from shutil import move
|
||||
|
||||
move("foo", "bar")
|
||||
@@ -366,7 +369,7 @@ def test_repro_when_new_deps_is_moved(tmp_dir, dvc):
|
||||
dvcfile = Dvcfile(dvc, stage.path)
|
||||
data, _ = dvcfile._load()
|
||||
data["stages"]["copy-file"]["deps"] = ["bar"]
|
||||
dump_yaml(stage.path, data)
|
||||
(tmp_dir / stage.path).dump(data)
|
||||
|
||||
assert dvc.reproduce(target)[0] == stage
|
||||
|
||||
@@ -376,8 +379,7 @@ def test_repro_when_new_out_overlaps_others_stage_outs(tmp_dir, dvc):
|
||||
|
||||
tmp_dir.gen({"dir": {"file1": "file1"}, "foo": "foo"})
|
||||
dvc.add("dir")
|
||||
dump_yaml(
|
||||
PIPELINE_FILE,
|
||||
(tmp_dir / PIPELINE_FILE).dump(
|
||||
{
|
||||
"stages": {
|
||||
"run-copy": {
|
||||
@@ -395,8 +397,7 @@ def test_repro_when_new_out_overlaps_others_stage_outs(tmp_dir, dvc):
|
||||
def test_repro_when_new_deps_added_does_not_exist(tmp_dir, dvc):
|
||||
tmp_dir.gen("copy.py", COPY_SCRIPT)
|
||||
tmp_dir.gen("foo", "foo")
|
||||
dump_yaml(
|
||||
PIPELINE_FILE,
|
||||
(tmp_dir / PIPELINE_FILE).dump(
|
||||
{
|
||||
"stages": {
|
||||
"run-copy": {
|
||||
@@ -414,8 +415,7 @@ def test_repro_when_new_deps_added_does_not_exist(tmp_dir, dvc):
|
||||
def test_repro_when_new_outs_added_does_not_exist(tmp_dir, dvc):
|
||||
tmp_dir.gen("copy.py", COPY_SCRIPT)
|
||||
tmp_dir.gen("foo", "foo")
|
||||
dump_yaml(
|
||||
PIPELINE_FILE,
|
||||
(tmp_dir / PIPELINE_FILE).dump(
|
||||
{
|
||||
"stages": {
|
||||
"run-copy": {
|
||||
@@ -433,8 +433,7 @@ def test_repro_when_new_outs_added_does_not_exist(tmp_dir, dvc):
|
||||
def test_repro_when_lockfile_gets_deleted(tmp_dir, dvc):
|
||||
tmp_dir.gen("copy.py", COPY_SCRIPT)
|
||||
tmp_dir.gen("foo", "foo")
|
||||
dump_yaml(
|
||||
PIPELINE_FILE,
|
||||
(tmp_dir / PIPELINE_FILE).dump(
|
||||
{
|
||||
"stages": {
|
||||
"run-copy": {
|
||||
@@ -464,13 +463,13 @@ def test_cyclic_graph_error(tmp_dir, dvc, run_copy):
|
||||
run_copy("bar", "baz", name="copy-bar-baz")
|
||||
run_copy("baz", "foobar", name="copy-baz-foobar")
|
||||
|
||||
data = load_yaml(PIPELINE_FILE)
|
||||
data = (tmp_dir / PIPELINE_FILE).parse()
|
||||
data["stages"]["copy-baz-foo"] = {
|
||||
"cmd": "echo baz > foo",
|
||||
"deps": ["baz"],
|
||||
"outs": ["foo"],
|
||||
}
|
||||
dump_yaml(PIPELINE_FILE, data)
|
||||
(tmp_dir / PIPELINE_FILE).dump(data)
|
||||
with pytest.raises(CyclicGraphError):
|
||||
dvc.reproduce(":copy-baz-foo")
|
||||
|
||||
@@ -479,8 +478,8 @@ def test_repro_multiple_params(tmp_dir, dvc):
|
||||
from dvc.stage.utils import split_params_deps
|
||||
from tests.func.test_run_multistage import supported_params
|
||||
|
||||
dump_yaml(tmp_dir / "params2.yaml", supported_params)
|
||||
dump_yaml(tmp_dir / "params.yaml", supported_params)
|
||||
(tmp_dir / "params2.yaml").dump(supported_params)
|
||||
(tmp_dir / "params.yaml").dump(supported_params)
|
||||
|
||||
(tmp_dir / "foo").write_text("foo")
|
||||
stage = dvc.run(
|
||||
@@ -522,7 +521,7 @@ def test_repro_multiple_params(tmp_dir, dvc):
|
||||
assert not dvc.reproduce(stage.addressing)
|
||||
params = deepcopy(supported_params)
|
||||
params["answer"] = 43
|
||||
dump_yaml(tmp_dir / "params.yaml", params)
|
||||
(tmp_dir / "params.yaml").dump(params)
|
||||
|
||||
assert dvc.reproduce(stage.addressing) == [stage]
|
||||
|
||||
@@ -533,7 +532,7 @@ def test_repro_list_of_commands_in_order(tmp_dir, dvc, multiline):
|
||||
if multiline:
|
||||
cmd = "\n".join(cmd)
|
||||
|
||||
dump_yaml("dvc.yaml", {"stages": {"multi": {"cmd": cmd}}})
|
||||
(tmp_dir / "dvc.yaml").dump({"stages": {"multi": {"cmd": cmd}}})
|
||||
|
||||
(tmp_dir / "dvc.yaml").write_text(
|
||||
dedent(
|
||||
@@ -559,7 +558,7 @@ def test_repro_list_of_commands_raise_and_stops_after_failure(
|
||||
if multiline:
|
||||
cmd = "\n".join(cmd)
|
||||
|
||||
dump_yaml("dvc.yaml", {"stages": {"multi": {"cmd": cmd}}})
|
||||
(tmp_dir / "dvc.yaml").dump({"stages": {"multi": {"cmd": cmd}}})
|
||||
|
||||
with pytest.raises(ReproductionError):
|
||||
dvc.reproduce(targets=["multi"])
|
||||
|
||||
@@ -5,7 +5,6 @@ import pytest
|
||||
|
||||
from dvc.exceptions import InvalidArgumentError
|
||||
from dvc.stage.exceptions import DuplicateStageName, InvalidStageName
|
||||
from dvc.utils.serialize import dump_yaml, parse_yaml_for_update
|
||||
|
||||
|
||||
def test_run_with_name(tmp_dir, dvc, run_copy):
|
||||
@@ -232,7 +231,7 @@ supported_params = {
|
||||
def test_run_params_default(tmp_dir, dvc):
|
||||
from dvc.dependency import ParamsDependency
|
||||
|
||||
dump_yaml(tmp_dir / "params.yaml", supported_params)
|
||||
(tmp_dir / "params.yaml").dump(supported_params)
|
||||
stage = dvc.run(
|
||||
name="read_params",
|
||||
params=["nested.nested1.nested2"],
|
||||
@@ -255,7 +254,7 @@ def test_run_params_default(tmp_dir, dvc):
|
||||
def test_run_params_custom_file(tmp_dir, dvc):
|
||||
from dvc.dependency import ParamsDependency
|
||||
|
||||
dump_yaml(tmp_dir / "params2.yaml", supported_params)
|
||||
(tmp_dir / "params2.yaml").dump(supported_params)
|
||||
stage = dvc.run(
|
||||
name="read_params",
|
||||
params=["params2.yaml:lists"],
|
||||
@@ -278,7 +277,7 @@ def test_run_params_custom_file(tmp_dir, dvc):
|
||||
def test_run_params_no_exec(tmp_dir, dvc):
|
||||
from dvc.dependency import ParamsDependency
|
||||
|
||||
dump_yaml(tmp_dir / "params2.yaml", supported_params)
|
||||
(tmp_dir / "params2.yaml").dump(supported_params)
|
||||
stage = dvc.run(
|
||||
name="read_params",
|
||||
params=["params2.yaml:lists"],
|
||||
@@ -318,9 +317,7 @@ def test_run_overwrite_order(tmp_dir, dvc, run_copy):
|
||||
|
||||
run_copy("foo1", "bar1", name="copy-foo-bar", force=True)
|
||||
|
||||
data = parse_yaml_for_update(
|
||||
(tmp_dir / PIPELINE_FILE).read_text(), PIPELINE_FILE
|
||||
)
|
||||
data = (tmp_dir / PIPELINE_FILE).parse()
|
||||
assert list(data["stages"].keys()) == ["copy-foo-bar", "copy-bar-foobar"]
|
||||
|
||||
|
||||
|
||||
@@ -168,34 +168,34 @@ def test_meta_is_preserved(tmp_dir, dvc):
|
||||
(stage,) = tmp_dir.dvc_gen("foo", "foo content")
|
||||
|
||||
# Add meta to DVC-file
|
||||
data = load_yaml(stage.path)
|
||||
data = (tmp_dir / stage.path).parse()
|
||||
data["meta"] = {"custom_key": 42}
|
||||
dump_yaml(stage.path, data)
|
||||
(tmp_dir / stage.path).dump(data)
|
||||
|
||||
# Loading and dumping to test that it works and meta is retained
|
||||
dvcfile = SingleStageFile(dvc, stage.path)
|
||||
new_stage = dvcfile.stage
|
||||
dvcfile.dump(new_stage)
|
||||
|
||||
new_data = load_yaml(stage.path)
|
||||
new_data = (tmp_dir / stage.path).parse()
|
||||
assert new_data["meta"] == data["meta"]
|
||||
|
||||
|
||||
def test_desc_is_preserved(tmp_dir, dvc):
|
||||
(stage,) = tmp_dir.dvc_gen("foo", "foo content")
|
||||
|
||||
data = load_yaml(stage.path)
|
||||
data = (tmp_dir / stage.path).parse()
|
||||
stage_desc = "test stage description"
|
||||
out_desc = "test out description"
|
||||
data["desc"] = stage_desc
|
||||
data["outs"][0]["desc"] = out_desc
|
||||
dump_yaml(stage.path, data)
|
||||
(tmp_dir / stage.path).dump(data)
|
||||
|
||||
dvcfile = SingleStageFile(dvc, stage.path)
|
||||
new_stage = dvcfile.stage
|
||||
dvcfile.dump(new_stage)
|
||||
|
||||
new_data = load_yaml(stage.path)
|
||||
new_data = (tmp_dir / stage.path).parse()
|
||||
assert new_data["desc"] == stage_desc
|
||||
assert new_data["outs"][0]["desc"] == out_desc
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ from dvc.stage.exceptions import (
|
||||
)
|
||||
from dvc.utils import relpath
|
||||
from dvc.utils.fs import remove
|
||||
from dvc.utils.serialize import dump_yaml
|
||||
|
||||
|
||||
def test_collect(tmp_dir, scm, dvc, run_copy):
|
||||
@@ -149,7 +148,7 @@ def test_collect_generated(tmp_dir, dvc):
|
||||
"build": {"foreach": "${vars}", "do": {"cmd": "echo ${item}"}}
|
||||
},
|
||||
}
|
||||
dump_yaml("dvc.yaml", d)
|
||||
(tmp_dir / "dvc.yaml").dump(d)
|
||||
|
||||
all_stages = set(dvc.index.stages)
|
||||
assert len(all_stages) == 5
|
||||
@@ -443,7 +442,7 @@ def test_collect_repo_callback(tmp_dir, dvc, mocker):
|
||||
dvc.stage_collection_error_handler = mock
|
||||
|
||||
(stage,) = tmp_dir.dvc_gen("foo", "foo")
|
||||
dump_yaml(tmp_dir / PIPELINE_FILE, {"stages": {"cmd": "echo hello world"}})
|
||||
(tmp_dir / PIPELINE_FILE).dump({"stages": {"cmd": "echo hello world"}})
|
||||
|
||||
dvc._reset()
|
||||
assert dvc.index.stages == [stage]
|
||||
|
||||
@@ -7,8 +7,7 @@ from functools import partialmethod
|
||||
import pytest
|
||||
from funcy import cached_property, retry
|
||||
|
||||
from dvc.fs.gdrive import GDriveFileSystem
|
||||
from dvc.path_info import CloudURLInfo
|
||||
from dvc.fs.gdrive import GDriveFileSystem, GDriveURLInfo
|
||||
from dvc.utils import tmp_fname
|
||||
|
||||
from .base import Base
|
||||
@@ -44,7 +43,7 @@ def _gdrive_retry(func):
|
||||
)(func)
|
||||
|
||||
|
||||
class GDrive(Base, CloudURLInfo):
|
||||
class GDrive(Base, GDriveURLInfo):
|
||||
@staticmethod
|
||||
def should_test():
|
||||
return bool(os.getenv(GDriveFileSystem.GDRIVE_CREDENTIALS_DATA))
|
||||
|
||||
@@ -6,13 +6,13 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from dvc.path_info import URLInfo
|
||||
from dvc.path_info import CloudURLInfo
|
||||
|
||||
from .base import Base
|
||||
from .hdfs import _hdfs_root, md5md5crc32c
|
||||
|
||||
|
||||
class WebHDFS(Base, URLInfo): # pylint: disable=abstract-method
|
||||
class WebHDFS(Base, CloudURLInfo): # pylint: disable=abstract-method
|
||||
@contextmanager
|
||||
def _webhdfs(self):
|
||||
from hdfs import InsecureClient
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import collections
|
||||
import os
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -191,14 +190,14 @@ def test_show_json_hide_missing(mocker, capsys):
|
||||
|
||||
|
||||
@pytest.mark.parametrize("show_hash", [None, True, False])
|
||||
@mock.patch("dvc.command.diff._show_md")
|
||||
def test_diff_show_md_and_hash(mock_show_md, mocker, show_hash):
|
||||
def test_diff_show_md_and_hash(mocker, show_hash):
|
||||
options = ["diff", "--show-md"] + (["--show-hash"] if show_hash else [])
|
||||
args = parse_args(options)
|
||||
cmd = args.func(args)
|
||||
|
||||
diff = {}
|
||||
show_hash = show_hash if show_hash else False
|
||||
mock_show_md = mocker.patch("dvc.command.diff._show_md")
|
||||
mocker.patch("dvc.repo.Repo.diff", return_value=diff.copy())
|
||||
|
||||
assert 0 == cmd.run()
|
||||
|
||||
@@ -5,10 +5,23 @@ from dvc.command.machine import (
|
||||
CmdMachineAdd,
|
||||
CmdMachineCreate,
|
||||
CmdMachineDestroy,
|
||||
CmdMachineList,
|
||||
CmdMachineModify,
|
||||
CmdMachineRemove,
|
||||
CmdMachineSsh,
|
||||
)
|
||||
|
||||
DATA = {
|
||||
".dvc": {
|
||||
"config": (
|
||||
"[feature]\n"
|
||||
" machine = true\n"
|
||||
"['machine \"foo\"']\n"
|
||||
" cloud = aws"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def test_add(tmp_dir):
|
||||
tmp_dir.gen({".dvc": {"config": "[feature]\n machine = true"}})
|
||||
@@ -21,18 +34,7 @@ def test_add(tmp_dir):
|
||||
|
||||
|
||||
def test_remove(tmp_dir):
|
||||
tmp_dir.gen(
|
||||
{
|
||||
".dvc": {
|
||||
"config": (
|
||||
"[feature]\n"
|
||||
" machine = true\n"
|
||||
"['machine \"foo\"']\n"
|
||||
" cloud = aws"
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
tmp_dir.gen(DATA)
|
||||
cli_args = parse_args(["machine", "remove", "foo"])
|
||||
assert cli_args.func == CmdMachineRemove
|
||||
cmd = cli_args.func(cli_args)
|
||||
@@ -78,3 +80,25 @@ def test_ssh(tmp_dir, dvc, mocker):
|
||||
|
||||
assert cmd.run() == 0
|
||||
m.assert_called_once_with("foo")
|
||||
|
||||
|
||||
def test_list(tmp_dir, mocker):
|
||||
from dvc.ui import ui
|
||||
|
||||
tmp_dir.gen(DATA)
|
||||
cli_args = parse_args(["machine", "list", "foo"])
|
||||
assert cli_args.func == CmdMachineList
|
||||
cmd = cli_args.func(cli_args)
|
||||
m = mocker.patch.object(ui, "write", autospec=True)
|
||||
assert cmd.run() == 0
|
||||
m.assert_called_once_with("cloud=aws")
|
||||
|
||||
|
||||
def test_modified(tmp_dir):
|
||||
tmp_dir.gen(DATA)
|
||||
cli_args = parse_args(["machine", "modify", "foo", "cloud", "azure"])
|
||||
assert cli_args.func == CmdMachineModify
|
||||
cmd = cli_args.func(cli_args)
|
||||
assert cmd.run() == 0
|
||||
config = configobj.ConfigObj(str(tmp_dir / ".dvc" / "config"))
|
||||
assert config['machine "foo"']["cloud"] == "azure"
|
||||
|
||||
@@ -336,7 +336,7 @@ def make_subrepo(dir_, scm, config=None):
|
||||
dir_.add_remote(config=config)
|
||||
|
||||
|
||||
def test_subrepos(tmp_dir, scm, dvc):
|
||||
def test_subrepos(tmp_dir, scm, dvc, mocker):
|
||||
tmp_dir.scm_gen(
|
||||
{"dir": {"repo.txt": "file to confuse RepoFileSystem"}},
|
||||
commit="dir/repo.txt",
|
||||
|
||||
@@ -43,6 +43,12 @@ def test_init():
|
||||
assert fs.fs_args["password"] == fs.fs_args["passphrase"] == "xxx"
|
||||
|
||||
|
||||
def test_ssh_ask_password(mocker):
|
||||
mocker.patch("dvc.fs.ssh.ask_password", return_value="fish")
|
||||
fs = SSHFileSystem(user="test", host="2.2.2.2", ask_password=True)
|
||||
assert fs.fs_args["password"] == fs.fs_args["passphrase"] == "fish"
|
||||
|
||||
|
||||
mock_ssh_config = """
|
||||
Host example.com
|
||||
User ubuntu
|
||||
|
||||
@@ -6,11 +6,11 @@ from mock import patch
|
||||
from dvc.fs.http import HTTPFileSystem
|
||||
|
||||
|
||||
def test_download_fails_on_error_code(dvc, http):
|
||||
def test_download_fails_on_error_code(tmp_dir, http):
|
||||
fs = HTTPFileSystem(**http.config)
|
||||
|
||||
with pytest.raises(FileNotFoundError):
|
||||
fs._download(http / "missing.txt", "missing.txt")
|
||||
fs.download_file(http / "missing.txt", tmp_dir / "missing.txt")
|
||||
|
||||
|
||||
def test_public_auth_method(dvc):
|
||||
|
||||
@@ -91,8 +91,8 @@ def test_walk_files(remote):
|
||||
def test_copy_preserve_etag_across_buckets(cloud, dvc):
|
||||
cloud.gen(FILE_WITH_CONTENTS)
|
||||
rem = _get_odb(dvc, cloud.config)
|
||||
s3 = rem.fs
|
||||
s3.fs.mkdir("another/")
|
||||
s3 = rem.fs.s3
|
||||
s3.create_bucket(Bucket="another")
|
||||
|
||||
config = cloud.config.copy()
|
||||
config["url"] = "s3://another"
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
import dvc.objects.db.slow_link_detection
|
||||
@@ -14,9 +12,9 @@ def timeout_immediately(monkeypatch):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def make_remote():
|
||||
def make_remote(mocker):
|
||||
def _make_remote(cache_type=None, should_warn=True):
|
||||
remote = mock.Mock()
|
||||
remote = mocker.Mock()
|
||||
remote.cache_types = cache_type
|
||||
remote.slow_link_warning = should_warn
|
||||
return remote
|
||||
|
||||
@@ -305,7 +305,7 @@ def test_raise_on_no_template(tmp_dir, dvc):
|
||||
|
||||
def test_bad_template(tmp_dir, dvc):
|
||||
metric = [{"val": 2}, {"val": 3}]
|
||||
tmp_dir.gen("template.json", json.dumps({"a": "b", "c": "d"}))
|
||||
(tmp_dir / "template.json").dump({"a": "b", "c": "d"})
|
||||
props = {"template": "template.json"}
|
||||
data = {
|
||||
"workspace": {"data": {"file.json": {"data": metric, "props": props}}}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
from unittest import mock
|
||||
|
||||
|
||||
@mock.patch("dvc.repo.reproduce._reproduce_stage", returns=[])
|
||||
def test_number_reproduces(reproduce_stage_mock, tmp_dir, dvc):
|
||||
def test_number_reproduces(tmp_dir, dvc, mocker):
|
||||
reproduce_stage_mock = mocker.patch(
|
||||
"dvc.repo.reproduce._reproduce_stage", returns=[]
|
||||
)
|
||||
tmp_dir.dvc_gen({"pre-foo": "pre-foo"})
|
||||
|
||||
dvc.run(
|
||||
|
||||
@@ -2,7 +2,6 @@ import os
|
||||
import signal
|
||||
import subprocess
|
||||
import threading
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -18,35 +17,35 @@ TEST_STAGE_DICT = {
|
||||
}
|
||||
|
||||
|
||||
def test_stage_checksum():
|
||||
def test_stage_checksum(mocker):
|
||||
stage = Stage(None, "path", cmd="mycmd")
|
||||
|
||||
with mock.patch.object(stage, "dumpd", return_value=TEST_STAGE_DICT):
|
||||
assert stage.compute_md5() == "e9521a22111493406ea64a88cda63e0b"
|
||||
mocker.patch.object(stage, "dumpd", return_value=TEST_STAGE_DICT)
|
||||
assert stage.compute_md5() == "e9521a22111493406ea64a88cda63e0b"
|
||||
|
||||
|
||||
def test_wdir_default_ignored():
|
||||
def test_wdir_default_ignored(mocker):
|
||||
stage = Stage(None, "path", cmd="mycmd")
|
||||
d = dict(TEST_STAGE_DICT, wdir=".")
|
||||
|
||||
with mock.patch.object(stage, "dumpd", return_value=d):
|
||||
assert stage.compute_md5() == "e9521a22111493406ea64a88cda63e0b"
|
||||
mocker.patch.object(stage, "dumpd", return_value=d)
|
||||
assert stage.compute_md5() == "e9521a22111493406ea64a88cda63e0b"
|
||||
|
||||
|
||||
def test_wdir_non_default_is_not_ignored():
|
||||
def test_wdir_non_default_is_not_ignored(mocker):
|
||||
stage = Stage(None, "path", cmd="mycmd")
|
||||
d = dict(TEST_STAGE_DICT, wdir="..")
|
||||
|
||||
with mock.patch.object(stage, "dumpd", return_value=d):
|
||||
assert stage.compute_md5() == "2ceba15e87f6848aa756502c1e6d24e9"
|
||||
mocker.patch.object(stage, "dumpd", return_value=d)
|
||||
assert stage.compute_md5() == "2ceba15e87f6848aa756502c1e6d24e9"
|
||||
|
||||
|
||||
def test_meta_ignored():
|
||||
def test_meta_ignored(mocker):
|
||||
stage = Stage(None, "path", cmd="mycmd")
|
||||
d = dict(TEST_STAGE_DICT, meta={"author": "Suor"})
|
||||
|
||||
with mock.patch.object(stage, "dumpd", return_value=d):
|
||||
assert stage.compute_md5() == "e9521a22111493406ea64a88cda63e0b"
|
||||
mocker.patch.object(stage, "dumpd", return_value=d)
|
||||
assert stage.compute_md5() == "e9521a22111493406ea64a88cda63e0b"
|
||||
|
||||
|
||||
def test_path_conversion(dvc):
|
||||
@@ -124,21 +123,21 @@ def test_external_outs(tmp_path_factory, dvc):
|
||||
create_stage(Stage, dvc, "path.dvc", outs=[os.fspath(foo)], external=True)
|
||||
|
||||
|
||||
def test_env(dvc):
|
||||
def test_env(dvc, mocker):
|
||||
from dvc.env import DVC_ROOT
|
||||
from dvc.exceptions import DvcException
|
||||
from dvc.stage import create_stage
|
||||
|
||||
stage = create_stage(Stage, dvc, "path.dvc", outs=["foo", "bar"])
|
||||
|
||||
with mock.patch.object(stage, "_read_env", return_value={"foo": "foo"}):
|
||||
env = stage.env()
|
||||
assert env == {DVC_ROOT: dvc.root_dir, "foo": "foo"}
|
||||
mocker.patch.object(stage, "_read_env", return_value={"foo": "foo"})
|
||||
env = stage.env()
|
||||
assert env == {DVC_ROOT: dvc.root_dir, "foo": "foo"}
|
||||
|
||||
def mock_read_env(out, **kwargs):
|
||||
return {"foo": str(out)}
|
||||
|
||||
with pytest.raises(DvcException) as exc:
|
||||
with mock.patch.object(stage, "_read_env", mock_read_env):
|
||||
env = stage.env()
|
||||
mocker.patch.object(stage, "_read_env", mock_read_env)
|
||||
_ = stage.env()
|
||||
assert exc.value == "Conflicting values for env variable"
|
||||
|
||||
@@ -10,17 +10,16 @@ from dvc.cli import parse_args
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tmp_global_dir(tmp_path):
|
||||
def tmp_global_dir(mocker, tmp_path):
|
||||
"""
|
||||
Fixture to prevent modifying the actual global config
|
||||
"""
|
||||
with mock.patch("dvc.config.Config.get_dir", return_value=str(tmp_path)):
|
||||
yield
|
||||
mocker.patch("dvc.config.Config.get_dir", return_value=str(tmp_path))
|
||||
|
||||
|
||||
@mock.patch("dvc.daemon._spawn")
|
||||
@mock.patch("json.dump")
|
||||
def test_collect_and_send_report(mock_json, mock_daemon, tmp_global_dir):
|
||||
def test_collect_and_send_report(mocker, tmp_global_dir):
|
||||
mock_json = mocker.patch("json.dump")
|
||||
mock_daemon = mocker.patch("dvc.daemon._spawn")
|
||||
analytics.collect_and_send_report()
|
||||
report = mock_json.call_args[0][0]
|
||||
|
||||
@@ -67,7 +66,7 @@ def test_send(mock_post, tmp_path):
|
||||
|
||||
analytics.send(str(report_file))
|
||||
assert mock_post.called
|
||||
assert mock_post.call_args.args[0] == url
|
||||
assert mock_post.call_args[0][0] == url
|
||||
assert not report_file.exists()
|
||||
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ from dvc.parsing.context import (
|
||||
recurse_not_a_node,
|
||||
)
|
||||
from dvc.utils import relpath
|
||||
from dvc.utils.serialize import dump_yaml, dumps_yaml
|
||||
from dvc.utils.serialize import dumps_yaml
|
||||
|
||||
|
||||
def test_context():
|
||||
@@ -290,7 +290,7 @@ def test_track(tmp_dir):
|
||||
}
|
||||
fs = LocalFileSystem()
|
||||
path = tmp_dir / "params.yaml"
|
||||
dump_yaml(path, d, fs)
|
||||
path.dump(d, fs=fs)
|
||||
|
||||
context = Context.load_from(fs, path)
|
||||
|
||||
@@ -326,8 +326,8 @@ def test_track_from_multiple_files(tmp_dir):
|
||||
fs = LocalFileSystem()
|
||||
path1 = tmp_dir / "params.yaml"
|
||||
path2 = tmp_dir / "params2.yaml"
|
||||
dump_yaml(path1, d1, fs)
|
||||
dump_yaml(path2, d2, fs)
|
||||
path1.dump(d1, fs=fs)
|
||||
path2.dump(d2, fs=fs)
|
||||
|
||||
context = Context.load_from(fs, path1)
|
||||
c = Context.load_from(fs, path2)
|
||||
|
||||
@@ -15,7 +15,6 @@ from dvc.stage.exceptions import (
|
||||
StageFileIsNotDvcFileError,
|
||||
)
|
||||
from dvc.utils.fs import remove
|
||||
from dvc.utils.serialize import dump_yaml
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -66,7 +65,7 @@ def test_stage_load_on_non_file(tmp_dir, dvc, file):
|
||||
@pytest.mark.parametrize("file", ["stage.dvc", "dvc.yaml"])
|
||||
def test_stage_load_on_invalid_data(tmp_dir, dvc, file):
|
||||
data = {"is_this_a_valid_dvcfile": False}
|
||||
dump_yaml(file, data)
|
||||
(tmp_dir / file).dump(data)
|
||||
dvcfile = Dvcfile(dvc, file)
|
||||
with pytest.raises(StageFileFormatError):
|
||||
assert dvcfile.stages
|
||||
|
||||
@@ -2,7 +2,6 @@ import pytest
|
||||
|
||||
from dvc.dvcfile import FileIsGitIgnored, Lockfile, LockfileCorruptedError
|
||||
from dvc.stage import PipelineStage
|
||||
from dvc.utils.serialize import dump_yaml
|
||||
|
||||
|
||||
def test_stage_dump_no_outs_deps(tmp_dir, dvc):
|
||||
@@ -17,7 +16,7 @@ def test_stage_dump_no_outs_deps(tmp_dir, dvc):
|
||||
|
||||
def test_stage_dump_when_already_exists(tmp_dir, dvc):
|
||||
data = {"s1": {"cmd": "command", "deps": [], "outs": []}}
|
||||
dump_yaml("path.lock", data)
|
||||
(tmp_dir / "path.lock").dump(data)
|
||||
stage = PipelineStage(name="s2", repo=dvc, path="path", cmd="command2")
|
||||
lockfile = Lockfile(dvc, "path.lock")
|
||||
lockfile.dump(stage)
|
||||
@@ -35,7 +34,7 @@ def test_stage_dump_with_deps_and_outs(tmp_dir, dvc):
|
||||
"outs": [{"md5": "2.txt", "path": "checksum"}],
|
||||
}
|
||||
}
|
||||
dump_yaml("path.lock", data)
|
||||
(tmp_dir / "path.lock").dump(data)
|
||||
lockfile = Lockfile(dvc, "path.lock")
|
||||
stage = PipelineStage(name="s2", repo=dvc, path="path", cmd="command2")
|
||||
lockfile.dump(stage)
|
||||
@@ -78,7 +77,7 @@ def test_load_when_lockfile_does_not_exist(tmp_dir, dvc):
|
||||
],
|
||||
)
|
||||
def test_load_when_lockfile_is_corrupted(tmp_dir, dvc, corrupt_data):
|
||||
dump_yaml("Dvcfile.lock", corrupt_data)
|
||||
(tmp_dir / "Dvcfile.lock").dump(corrupt_data)
|
||||
lockfile = Lockfile(dvc, "Dvcfile.lock")
|
||||
with pytest.raises(LockfileCorruptedError) as exc_info:
|
||||
lockfile.load()
|
||||
|
||||
@@ -28,9 +28,9 @@ def test_plots_order(tmp_dir, dvc):
|
||||
name="stage2",
|
||||
)
|
||||
|
||||
assert get_files(dvc.plots.show()) == {
|
||||
assert get_files(dvc.plots.show()) == [
|
||||
"p.json",
|
||||
os.path.join("sub", "p4.json"),
|
||||
"p1.json",
|
||||
os.path.join("sub", "p3.json"),
|
||||
}
|
||||
os.path.join("sub", "p4.json"),
|
||||
]
|
||||
|
||||
+17
-18
@@ -1,6 +1,4 @@
|
||||
import logging
|
||||
import sys
|
||||
from unittest import mock
|
||||
|
||||
from dvc.progress import Tqdm
|
||||
from dvc.utils import env2bool
|
||||
@@ -15,15 +13,15 @@ def test_quiet_logging(caplog, capsys):
|
||||
assert out_err.err == ""
|
||||
|
||||
|
||||
def test_quiet_logging_disable_false(caplog, capsys):
|
||||
def test_quiet_logging_disable_false(caplog, capsys, mocker):
|
||||
# simulate interactive terminal
|
||||
mocker.patch("sys.stdout.isatty", return_value=True)
|
||||
with caplog.at_level(logging.CRITICAL, logger="dvc"):
|
||||
# simulate interactive terminal
|
||||
with mock.patch.object(sys.stderr, "isatty", return_value=True):
|
||||
for _ in Tqdm(range(10), disable=False):
|
||||
pass
|
||||
out_err = capsys.readouterr()
|
||||
assert out_err.out == ""
|
||||
assert out_err.err == ""
|
||||
for _ in Tqdm(range(10), disable=False):
|
||||
pass
|
||||
out_err = capsys.readouterr()
|
||||
assert out_err.out == ""
|
||||
assert out_err.err == ""
|
||||
|
||||
|
||||
def test_quiet_notty(caplog, capsys):
|
||||
@@ -38,12 +36,13 @@ def test_quiet_notty(caplog, capsys):
|
||||
assert out_err.err == ""
|
||||
|
||||
|
||||
def test_default(caplog, capsys):
|
||||
def test_default(caplog, capsys, mocker):
|
||||
# simulate interactive terminal
|
||||
mocker.patch("sys.stdout.isatty", return_value=True)
|
||||
with caplog.at_level(logging.INFO, logger="dvc"):
|
||||
# simulate interactive terminal
|
||||
with mock.patch.object(sys.stderr, "isatty", return_value=True):
|
||||
for _ in Tqdm(range(10)):
|
||||
pass
|
||||
out_err = capsys.readouterr()
|
||||
assert out_err.out == ""
|
||||
assert "0/10" in out_err.err
|
||||
for _ in Tqdm(range(10)):
|
||||
pass
|
||||
|
||||
out_err = capsys.readouterr()
|
||||
assert out_err.out == ""
|
||||
assert "0/10" in out_err.err
|
||||
|
||||
@@ -2,7 +2,6 @@ import json
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -12,12 +11,11 @@ from tests.func.parsing.test_errors import escape_ansi
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tmp_global_dir(tmp_path):
|
||||
def tmp_global_dir(mocker, tmp_path):
|
||||
"""
|
||||
Fixture to prevent modifying the actual global config
|
||||
"""
|
||||
with mock.patch("dvc.config.Config.get_dir", return_value=str(tmp_path)):
|
||||
yield
|
||||
mocker.patch("dvc.config.Config.get_dir", return_value=str(tmp_path))
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
@@ -36,8 +34,8 @@ def mock_tty(mocker):
|
||||
return mocker.patch("sys.stdout.isatty", return_value=True)
|
||||
|
||||
|
||||
@mock.patch("requests.get")
|
||||
def test_fetch(mock_get, updater):
|
||||
def test_fetch(mocker, updater):
|
||||
mock_get = mocker.patch("requests.get")
|
||||
mock_get.return_value.status_code = 200
|
||||
mock_get.return_value.json.return_value = {"version": __version__}
|
||||
|
||||
@@ -70,8 +68,8 @@ def test_is_enabled(dvc, updater, config, result):
|
||||
|
||||
|
||||
@pytest.mark.parametrize("result", [True, False])
|
||||
@mock.patch("dvc.updater.Updater._check")
|
||||
def test_check_update_respect_config(mock_check, result, updater, mocker):
|
||||
def test_check_update_respect_config(result, updater, mocker):
|
||||
mock_check = mocker.patch("dvc.updater.Updater._check")
|
||||
mocker.patch.object(updater, "is_enabled", return_value=result)
|
||||
updater.check()
|
||||
assert result == mock_check.called
|
||||
@@ -139,8 +137,8 @@ def test_check_fetches_on_invalid_data_format(
|
||||
fetch.assert_called_once()
|
||||
|
||||
|
||||
@mock.patch("dvc.updater.Updater._check")
|
||||
def test_check(mock_check, updater):
|
||||
def test_check(mocker, updater):
|
||||
mock_check = mocker.patch("dvc.updater.Updater._check")
|
||||
updater.check()
|
||||
updater.check()
|
||||
updater.check()
|
||||
|
||||
+13
-13
@@ -1,20 +1,20 @@
|
||||
import subprocess
|
||||
from unittest import mock
|
||||
|
||||
import dvc.version
|
||||
|
||||
|
||||
def test_is_release():
|
||||
with mock.patch.object(dvc.version.subprocess, "check_output") as m:
|
||||
m.side_effect = subprocess.CalledProcessError(1, "cmd")
|
||||
ret = dvc.version._is_release(None, dvc.version._BASE_VERSION)
|
||||
assert ret is False
|
||||
def test_is_release(mocker):
|
||||
m = mocker.patch.object(dvc.version.subprocess, "check_output")
|
||||
|
||||
m.side_effect = None
|
||||
m.return_value = dvc.version._BASE_VERSION.encode("ascii")
|
||||
ret = dvc.version._is_release(None, dvc.version._BASE_VERSION)
|
||||
assert ret
|
||||
m.side_effect = subprocess.CalledProcessError(1, "cmd")
|
||||
ret = dvc.version._is_release(None, dvc.version._BASE_VERSION)
|
||||
assert ret is False
|
||||
|
||||
m.return_value = b"630d1741c2d5dd89a3176bd15b63121b905d35c9"
|
||||
ret = dvc.version._is_release(None, dvc.version._BASE_VERSION)
|
||||
assert ret is False
|
||||
m.side_effect = None
|
||||
m.return_value = dvc.version._BASE_VERSION.encode("ascii")
|
||||
ret = dvc.version._is_release(None, dvc.version._BASE_VERSION)
|
||||
assert ret
|
||||
|
||||
m.return_value = b"630d1741c2d5dd89a3176bd15b63121b905d35c9"
|
||||
ret = dvc.version._is_release(None, dvc.version._BASE_VERSION)
|
||||
assert ret is False
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import inspect
|
||||
import sys
|
||||
import textwrap
|
||||
from pathlib import Path
|
||||
|
||||
SCRIPT_TEMPLATE = """\
|
||||
#! {executable}
|
||||
# AUTOGENERATED USING SCRIPTIFY FOR USE IN TESTS FOR DVC
|
||||
|
||||
|
||||
{code}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
_, *argv = sys.argv
|
||||
func = {func!s}
|
||||
func(*argv)
|
||||
"""
|
||||
|
||||
|
||||
def scriptify(func, path=None):
|
||||
# you can use `breakpoint()` inside functions too
|
||||
# just remember to run the test in `-s` mode.
|
||||
code = textwrap.dedent(inspect.getsource(func))
|
||||
executable = sys.executable
|
||||
source = SCRIPT_TEMPLATE.format(
|
||||
executable=executable, code=code, func=func.__name__
|
||||
)
|
||||
|
||||
if not path:
|
||||
return source, None
|
||||
|
||||
Path(path).write_text(source, encoding="utf-8")
|
||||
return source, f"{executable} {path}"
|
||||
Reference in New Issue
Block a user