mirror of
https://github.com/Stone-Red-Code/dvc.git
synced 2026-11-04 07:18:27 +01:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51c9357cd2 | ||
|
|
9bfff1f957 | ||
|
|
41befea210 | ||
|
|
fbd5393a2d | ||
|
|
8817864e7a | ||
|
|
bd28b711f9 | ||
|
|
5563f13273 | ||
|
|
36c4a3a5af | ||
|
|
dc1242a107 | ||
|
|
17c6858ad3 | ||
|
|
dab347f424 | ||
|
|
3287c3f407 | ||
|
|
0ed44cf151 | ||
|
|
2419f94ef3 | ||
|
|
1eee7e3226 | ||
|
|
16570bad03 | ||
|
|
fd2f9119e3 | ||
|
|
cbf24cd279 | ||
|
|
8b35c69a95 | ||
|
|
ec77bfa1af | ||
|
|
bac1393fc2 | ||
|
|
cb8e24237f | ||
|
|
5b548d679c | ||
|
|
005239ade7 | ||
|
|
b67546d648 | ||
|
|
40c84fe4ed | ||
|
|
0d51d10ccf | ||
|
|
9210e66b0c | ||
|
|
d3bb4f85df | ||
|
|
cbce38074e | ||
|
|
1bb101d250 | ||
|
|
1d272f156f | ||
|
|
05bd4d4dc4 | ||
|
|
e22da1e957 | ||
|
|
4f36ce35ad |
@@ -3,6 +3,10 @@ branches:
|
||||
- master
|
||||
- devel
|
||||
|
||||
cache:
|
||||
- C:\ProgramData\chocolatey\bin -> appveyor.yml
|
||||
- C:\ProgramData\chocolatey\lib -> appveyor.yml
|
||||
|
||||
platform: x86
|
||||
|
||||
environment:
|
||||
|
||||
@@ -3,9 +3,9 @@ repos:
|
||||
rev: stable
|
||||
hooks:
|
||||
- id: black
|
||||
language_version: python3.6
|
||||
language_version: python3
|
||||
- repo: https://gitlab.com/pycqa/flake8
|
||||
rev: master
|
||||
hooks:
|
||||
- id: flake8
|
||||
language_version: python3.6
|
||||
language_version: python3
|
||||
|
||||
+1
-5
@@ -62,12 +62,8 @@ matrix:
|
||||
- PYTHON_CONFIGURE_OPTS="--enable-framework"
|
||||
before_install:
|
||||
- bash ./scripts/ci/before_install.sh
|
||||
- if [[ -n "$PYTHON_VER" ]]; then
|
||||
eval "$(pyenv init -)";
|
||||
pyenv install --skip-existing --keep --verbose "$PYTHON_VER";
|
||||
pyenv shell "$PYTHON_VER";
|
||||
fi
|
||||
- source env.sh
|
||||
- python --version
|
||||
install:
|
||||
- bash ./scripts/ci/install.sh
|
||||
script:
|
||||
|
||||
+6
-3
@@ -10,7 +10,7 @@ import os
|
||||
import warnings
|
||||
|
||||
|
||||
VERSION_BASE = "0.25.0"
|
||||
VERSION_BASE = "0.26.0"
|
||||
__version__ = VERSION_BASE
|
||||
|
||||
PACKAGEPATH = os.path.abspath(os.path.dirname(__file__))
|
||||
@@ -20,8 +20,11 @@ VERSIONPATH = os.path.join(PACKAGEPATH, "version.py")
|
||||
|
||||
def _update_version_file():
|
||||
"""Dynamically update version file."""
|
||||
from git import Repo
|
||||
from git.exc import InvalidGitRepositoryError
|
||||
try:
|
||||
from git import Repo
|
||||
from git.exc import InvalidGitRepositoryError
|
||||
except ImportError:
|
||||
return __version__
|
||||
|
||||
try:
|
||||
repo = Repo(HOMEPATH)
|
||||
|
||||
+16
-5
@@ -17,12 +17,14 @@ class CmdRun(CmdBase):
|
||||
self.args.deps,
|
||||
self.args.outs,
|
||||
self.args.outs_no_cache,
|
||||
self.args.metrics,
|
||||
self.args.metrics_no_cache,
|
||||
self.args.command,
|
||||
]
|
||||
): # pragma: no cover
|
||||
logger.error(
|
||||
"too few arguments. Specify at least one: '-d', "
|
||||
"'-o', '-O', 'command'."
|
||||
"'-o', '-O', '-m', '-M', 'command'."
|
||||
)
|
||||
return 1
|
||||
|
||||
@@ -31,6 +33,7 @@ class CmdRun(CmdBase):
|
||||
cmd=self._parsed_cmd(),
|
||||
outs=self.args.outs,
|
||||
outs_no_cache=self.args.outs_no_cache,
|
||||
metrics=self.args.metrics,
|
||||
metrics_no_cache=self.args.metrics_no_cache,
|
||||
deps=self.args.deps,
|
||||
fname=self.args.file,
|
||||
@@ -88,22 +91,30 @@ def add_parser(subparsers, parent_parser):
|
||||
"--outs",
|
||||
action="append",
|
||||
default=[],
|
||||
help="Declare output data file or data directory.",
|
||||
help="Declare output file or directory.",
|
||||
)
|
||||
run_parser.add_argument(
|
||||
"-O",
|
||||
"--outs-no-cache",
|
||||
action="append",
|
||||
default=[],
|
||||
help="Declare output regular file or directory "
|
||||
"(sync to Git, not DVC cache).",
|
||||
help="Declare output file or directory "
|
||||
"(do not put into DVC cache).",
|
||||
)
|
||||
run_parser.add_argument(
|
||||
"-m",
|
||||
"--metrics",
|
||||
action="append",
|
||||
default=[],
|
||||
help="Declare output metric file or directory.",
|
||||
)
|
||||
run_parser.add_argument(
|
||||
"-M",
|
||||
"--metrics-no-cache",
|
||||
action="append",
|
||||
default=[],
|
||||
help="Declare output metric file or " "directory (not cached by DVC).",
|
||||
help="Declare output metric file or directory "
|
||||
"(do not put into DVC cache).",
|
||||
)
|
||||
run_parser.add_argument(
|
||||
"-f",
|
||||
|
||||
@@ -382,6 +382,9 @@ class Config(object): # pylint: disable=too-many-instance-attributes
|
||||
cache[self.PRIVATE_CWD] = os.path.dirname(fname)
|
||||
|
||||
def _resolve_paths(self, config, fname):
|
||||
if fname is None:
|
||||
return
|
||||
|
||||
self._resolve_cache_path(config, fname)
|
||||
for section in config.values():
|
||||
if self.SECTION_REMOTE_URL not in section.keys():
|
||||
|
||||
+4
-1
@@ -9,6 +9,7 @@ from subprocess import Popen
|
||||
|
||||
import dvc.logger as logger
|
||||
from dvc.utils import is_binary, fix_env
|
||||
from dvc.utils.compat import cast_bytes_py2
|
||||
|
||||
|
||||
CREATE_NEW_PROCESS_GROUP = 0x00000200
|
||||
@@ -78,7 +79,9 @@ def daemon(args):
|
||||
|
||||
env = fix_env()
|
||||
file_path = os.path.abspath(inspect.stack()[0][1])
|
||||
env["PYTHONPATH"] = os.path.dirname(os.path.dirname(file_path))
|
||||
env[cast_bytes_py2("PYTHONPATH")] = cast_bytes_py2(
|
||||
os.path.dirname(os.path.dirname(file_path))
|
||||
)
|
||||
|
||||
logger.debug("Trying to spawn '{}' with env '{}'".format(cmd, env))
|
||||
|
||||
|
||||
+2
-2
@@ -11,7 +11,7 @@ class DvcException(Exception):
|
||||
"""Base class for all dvc exceptions.
|
||||
|
||||
Args:
|
||||
msg (str): message for this exception.
|
||||
msg (unicode): message for this exception.
|
||||
cause (Exception): optional cause exception.
|
||||
"""
|
||||
|
||||
@@ -33,7 +33,7 @@ class OutputDuplicationError(DvcException):
|
||||
stage.
|
||||
|
||||
Args:
|
||||
output (str): path to the file/directory.
|
||||
output (unicode): path to the file/directory.
|
||||
stages (list): list of paths to stages.
|
||||
"""
|
||||
|
||||
|
||||
+8
-3
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from dvc.exceptions import DvcException
|
||||
from dvc.utils.compat import str
|
||||
|
||||
import re
|
||||
@@ -63,8 +64,8 @@ def box(message, border_color=None):
|
||||
"""Prints a message in a box.
|
||||
|
||||
Args:
|
||||
message (str): message to print.
|
||||
border_color (str): name of a color to outline the box with.
|
||||
message (unicode): message to print.
|
||||
border_color (unicode): name of a color to outline the box with.
|
||||
"""
|
||||
lines = message.split("\n")
|
||||
max_width = max(_visual_width(line) for line in lines)
|
||||
@@ -230,7 +231,7 @@ def _walk_exc(exc):
|
||||
tb_list.insert(0, str(exc.cause_tb))
|
||||
exc = exc.cause
|
||||
|
||||
return (exc_list, tb_list)
|
||||
return exc_list, tb_list
|
||||
|
||||
|
||||
def _parse_exc():
|
||||
@@ -260,6 +261,10 @@ def _description(message, exception):
|
||||
description = "{exception}"
|
||||
elif message:
|
||||
description = "{message}"
|
||||
else:
|
||||
raise DvcException(
|
||||
"Unexpected error - either exception or message must be provided"
|
||||
)
|
||||
|
||||
return description.format(message=message, exception=exception)
|
||||
|
||||
|
||||
+33
-19
@@ -370,10 +370,11 @@ class Project(object):
|
||||
def run(
|
||||
self,
|
||||
cmd=None,
|
||||
deps=[],
|
||||
outs=[],
|
||||
outs_no_cache=[],
|
||||
metrics_no_cache=[],
|
||||
deps=None,
|
||||
outs=None,
|
||||
outs_no_cache=None,
|
||||
metrics=None,
|
||||
metrics_no_cache=None,
|
||||
fname=None,
|
||||
cwd=os.curdir,
|
||||
no_exec=False,
|
||||
@@ -383,6 +384,17 @@ class Project(object):
|
||||
):
|
||||
from dvc.stage import Stage
|
||||
|
||||
if outs is None:
|
||||
outs = []
|
||||
if deps is None:
|
||||
deps = []
|
||||
if outs_no_cache is None:
|
||||
outs_no_cache = []
|
||||
if metrics is None:
|
||||
metrics = []
|
||||
if metrics_no_cache is None:
|
||||
metrics_no_cache = []
|
||||
|
||||
with self.state:
|
||||
stage = Stage.create(
|
||||
project=self,
|
||||
@@ -391,6 +403,7 @@ class Project(object):
|
||||
cwd=cwd,
|
||||
outs=outs,
|
||||
outs_no_cache=outs_no_cache,
|
||||
metrics=metrics,
|
||||
metrics_no_cache=metrics_no_cache,
|
||||
deps=deps,
|
||||
overwrite=overwrite,
|
||||
@@ -1017,14 +1030,16 @@ class Project(object):
|
||||
)
|
||||
return self._local_status(target, with_deps=with_deps)
|
||||
|
||||
def _read_metric_json(self, fd, json_path):
|
||||
@staticmethod
|
||||
def _read_metric_json(fd, json_path):
|
||||
import json
|
||||
from jsonpath_rw import parse
|
||||
|
||||
parser = parse(json_path)
|
||||
return [x.value for x in parser.find(json.load(fd))]
|
||||
|
||||
def _do_read_metric_xsv(self, reader, row, col):
|
||||
@staticmethod
|
||||
def _do_read_metric_xsv(reader, row, col):
|
||||
if col is not None and row is not None:
|
||||
return [reader[row][col]]
|
||||
elif col is not None:
|
||||
@@ -1033,22 +1048,24 @@ class Project(object):
|
||||
return reader[row]
|
||||
return None
|
||||
|
||||
def _read_metric_hxsv(self, fd, hxsv_path, delimiter):
|
||||
@classmethod
|
||||
def _read_metric_hxsv(cls, fd, hxsv_path, delimiter):
|
||||
import csv
|
||||
|
||||
col, row = hxsv_path.split(",")
|
||||
row = int(row)
|
||||
reader = list(csv.DictReader(fd, delimiter=builtin_str(delimiter)))
|
||||
return self._do_read_metric_xsv(reader, row, col)
|
||||
return cls._do_read_metric_xsv(reader, row, col)
|
||||
|
||||
def _read_metric_xsv(self, fd, xsv_path, delimiter):
|
||||
@classmethod
|
||||
def _read_metric_xsv(cls, fd, xsv_path, delimiter):
|
||||
import csv
|
||||
|
||||
col, row = xsv_path.split(",")
|
||||
row = int(row)
|
||||
col = int(col)
|
||||
reader = list(csv.reader(fd, delimiter=builtin_str(delimiter)))
|
||||
return self._do_read_metric_xsv(reader, row, col)
|
||||
return cls._do_read_metric_xsv(reader, row, col)
|
||||
|
||||
def _read_metric(self, path, typ=None, xpath=None):
|
||||
ret = None
|
||||
@@ -1137,7 +1154,7 @@ class Project(object):
|
||||
if os.path.isdir(path):
|
||||
logger.warning(
|
||||
"Path '{path}' is a directory. "
|
||||
"Consider runnig with '-R'.".format(path=path)
|
||||
"Consider running with '-R'.".format(path=path)
|
||||
)
|
||||
return {}
|
||||
|
||||
@@ -1182,11 +1199,10 @@ class Project(object):
|
||||
return res
|
||||
|
||||
if path and os.path.isdir(path):
|
||||
msg = "file '{}' does not exist".format(path)
|
||||
return res
|
||||
|
||||
if path:
|
||||
msg = "file '{}' does not exist".format(path)
|
||||
msg = "file '{}' does not exist or malformed".format(path)
|
||||
else:
|
||||
msg = (
|
||||
"no metric files in this repository."
|
||||
@@ -1215,10 +1231,6 @@ class Project(object):
|
||||
msg = "output '{}' scheme '{}' is not supported for metrics"
|
||||
raise DvcException(msg.format(out.path, out.path_info["scheme"]))
|
||||
|
||||
if out.use_cache:
|
||||
msg = "cached output '{}' is not supported for metrics"
|
||||
raise DvcException(msg.format(out.rel_path))
|
||||
|
||||
if typ:
|
||||
if not isinstance(out.metric, dict):
|
||||
out.metric = {}
|
||||
@@ -1366,7 +1378,8 @@ class Project(object):
|
||||
stages.extend(list(nx.get_node_attributes(G, "stage").values()))
|
||||
return stages
|
||||
|
||||
def _welcome_message(self):
|
||||
@staticmethod
|
||||
def _welcome_message():
|
||||
import colorama
|
||||
|
||||
logger.box(
|
||||
@@ -1390,7 +1403,8 @@ class Project(object):
|
||||
)
|
||||
)
|
||||
|
||||
def _expand_target_path(self, from_path, to_path):
|
||||
@staticmethod
|
||||
def _expand_target_path(from_path, to_path):
|
||||
if os.path.isdir(to_path) and not os.path.isdir(from_path):
|
||||
return os.path.join(to_path, os.path.basename(from_path))
|
||||
return to_path
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ def confirm(statement):
|
||||
"""Ask the user for confirmation about the specified statement.
|
||||
|
||||
Args:
|
||||
statement (str): statement to ask the user confirmation about.
|
||||
statement (unicode): statement to ask the user confirmation about.
|
||||
|
||||
Returns:
|
||||
bool: whether or not specified statement was confirmed.
|
||||
|
||||
+19
-11
@@ -35,10 +35,18 @@ class DataCloudError(DvcException):
|
||||
super(DataCloudError, self).__init__("Data sync error: {}".format(msg))
|
||||
|
||||
|
||||
class RemoteBaseCmdError(DvcException):
|
||||
def __init__(self, cmd, ret, err):
|
||||
m = "SSH command '{}' finished with non-zero return code '{}': {}"
|
||||
super(RemoteBaseCmdError, self).__init__(m.format(cmd, ret, err))
|
||||
class RemoteCmdError(DvcException):
|
||||
def __init__(self, remote, cmd, ret, err):
|
||||
super(RemoteCmdError, self).__init__(
|
||||
"{remote} command '{cmd}' finished with non-zero return code"
|
||||
" {ret}': {err}".format(remote=remote, cmd=cmd, ret=ret, err=err)
|
||||
)
|
||||
|
||||
|
||||
class RemoteActionNotImplemented(DvcException):
|
||||
def __init__(self, action, scheme):
|
||||
m = "{} is not supported by {} remote".format(action, scheme)
|
||||
super(RemoteActionNotImplemented, self).__init__(m)
|
||||
|
||||
|
||||
class RemoteMissingDepsError(DvcException):
|
||||
@@ -165,7 +173,7 @@ class RemoteBase(object):
|
||||
return False
|
||||
|
||||
def save(self, path_info):
|
||||
raise NotImplementedError
|
||||
raise RemoteActionNotImplemented("save", self.scheme)
|
||||
|
||||
def download(
|
||||
self,
|
||||
@@ -175,19 +183,19 @@ class RemoteBase(object):
|
||||
name=None,
|
||||
resume=False,
|
||||
):
|
||||
raise NotImplementedError
|
||||
raise RemoteActionNotImplemented("download", self.scheme)
|
||||
|
||||
def upload(self, from_infos, to_infos, path_info, names=None):
|
||||
raise NotImplementedError
|
||||
def upload(self, from_infos, to_infos, names=None):
|
||||
raise RemoteActionNotImplemented("upload", self.scheme)
|
||||
|
||||
def remove(self, path_info):
|
||||
raise NotImplementedError
|
||||
raise RemoteActionNotImplemented("remove", self.scheme)
|
||||
|
||||
def move(self, path_info):
|
||||
raise NotImplementedError
|
||||
raise RemoteActionNotImplemented("move", self.scheme)
|
||||
|
||||
def copy(self, from_info, to_info):
|
||||
raise NotImplementedError
|
||||
raise RemoteActionNotImplemented("copy", self.scheme)
|
||||
|
||||
def _makedirs(self, fname):
|
||||
dname = os.path.dirname(fname)
|
||||
|
||||
+4
-8
@@ -8,14 +8,10 @@ from subprocess import Popen, PIPE
|
||||
|
||||
import dvc.logger as logger
|
||||
from dvc.config import Config
|
||||
from dvc.remote.base import RemoteBase, RemoteBaseCmdError
|
||||
from dvc.remote.base import RemoteBase, RemoteCmdError
|
||||
from dvc.utils import fix_env
|
||||
|
||||
|
||||
class RemoteHDFSCmdError(RemoteBaseCmdError):
|
||||
pass
|
||||
|
||||
|
||||
class RemoteHDFS(RemoteBase):
|
||||
scheme = "hdfs"
|
||||
REGEX = r"^hdfs://((?P<user>.*)@)?.*$"
|
||||
@@ -55,7 +51,7 @@ class RemoteHDFS(RemoteBase):
|
||||
)
|
||||
out, err = p.communicate()
|
||||
if p.returncode != 0:
|
||||
raise RemoteHDFSCmdError(cmd, p.returncode, err)
|
||||
raise RemoteCmdError(self.scheme, cmd, p.returncode, err)
|
||||
return out.decode("utf-8")
|
||||
|
||||
@staticmethod
|
||||
@@ -123,7 +119,7 @@ class RemoteHDFS(RemoteBase):
|
||||
try:
|
||||
self.hadoop_fs("test -e {}".format(path_info["path"]))
|
||||
return True
|
||||
except RemoteHDFSCmdError:
|
||||
except RemoteCmdError:
|
||||
return False
|
||||
|
||||
def upload(self, from_infos, to_infos, names=None):
|
||||
@@ -177,7 +173,7 @@ class RemoteHDFS(RemoteBase):
|
||||
def list_cache_paths(self):
|
||||
try:
|
||||
self.hadoop_fs("test -e {}".format(self.prefix))
|
||||
except RemoteHDFSCmdError:
|
||||
except RemoteCmdError:
|
||||
return []
|
||||
|
||||
stdout = self.hadoop_fs("ls -R {}".format(self.prefix))
|
||||
|
||||
+3
-7
@@ -16,7 +16,7 @@ from dvc.config import Config
|
||||
from dvc.progress import progress
|
||||
from dvc.utils.compat import urlparse
|
||||
from dvc.exceptions import DvcException
|
||||
from dvc.remote.base import RemoteBase, RemoteBaseCmdError
|
||||
from dvc.remote.base import RemoteBase, RemoteCmdError
|
||||
|
||||
|
||||
def sizeof_fmt(num, suffix="B"):
|
||||
@@ -43,10 +43,6 @@ def create_cb(name):
|
||||
return lambda cur, tot: percent_cb(name, cur, tot)
|
||||
|
||||
|
||||
class RemoteSSHCmdError(RemoteBaseCmdError):
|
||||
pass
|
||||
|
||||
|
||||
class RemoteSSH(RemoteBase):
|
||||
scheme = "ssh"
|
||||
|
||||
@@ -134,7 +130,7 @@ class RemoteSSH(RemoteBase):
|
||||
try:
|
||||
self._exec(ssh, "test -e {}".format(path_info["path"]))
|
||||
exists = True
|
||||
except RemoteSSHCmdError:
|
||||
except RemoteCmdError:
|
||||
exists = False
|
||||
|
||||
return exists
|
||||
@@ -184,7 +180,7 @@ class RemoteSSH(RemoteBase):
|
||||
ret = stdout.channel.recv_exit_status()
|
||||
if ret != 0:
|
||||
err = b"".join(stderr_chunks).decode("utf-8")
|
||||
raise RemoteSSHCmdError(cmd, ret, err)
|
||||
raise RemoteCmdError(self.scheme, cmd, ret, err)
|
||||
|
||||
return b"".join(stdout_chunks).decode("utf-8")
|
||||
|
||||
|
||||
+35
-13
@@ -116,11 +116,16 @@ class Stage(object):
|
||||
path=None,
|
||||
cmd=None,
|
||||
cwd=os.curdir,
|
||||
deps=[],
|
||||
outs=[],
|
||||
deps=None,
|
||||
outs=None,
|
||||
md5=None,
|
||||
locked=False,
|
||||
):
|
||||
if deps is None:
|
||||
deps = []
|
||||
if outs is None:
|
||||
outs = []
|
||||
|
||||
self.project = project
|
||||
self.path = path
|
||||
self.cmd = cmd
|
||||
@@ -342,10 +347,11 @@ class Stage(object):
|
||||
def create(
|
||||
project=None,
|
||||
cmd=None,
|
||||
deps=[],
|
||||
outs=[],
|
||||
outs_no_cache=[],
|
||||
metrics_no_cache=[],
|
||||
deps=None,
|
||||
outs=None,
|
||||
outs_no_cache=None,
|
||||
metrics=None,
|
||||
metrics_no_cache=None,
|
||||
fname=None,
|
||||
cwd=os.curdir,
|
||||
locked=False,
|
||||
@@ -355,9 +361,23 @@ class Stage(object):
|
||||
remove_outs=False,
|
||||
):
|
||||
|
||||
if outs is None:
|
||||
outs = []
|
||||
if deps is None:
|
||||
deps = []
|
||||
if outs_no_cache is None:
|
||||
outs_no_cache = []
|
||||
if metrics is None:
|
||||
metrics = []
|
||||
if metrics_no_cache is None:
|
||||
metrics_no_cache = []
|
||||
|
||||
stage = Stage(project=project, cwd=cwd, cmd=cmd, locked=locked)
|
||||
|
||||
stage.outs = output.loads_from(stage, outs, use_cache=True)
|
||||
stage.outs += output.loads_from(
|
||||
stage, metrics, use_cache=True, metric=True
|
||||
)
|
||||
stage.outs += output.loads_from(stage, outs_no_cache, use_cache=False)
|
||||
stage.outs += output.loads_from(
|
||||
stage, metrics_no_cache, use_cache=False, metric=True
|
||||
@@ -512,7 +532,8 @@ class Stage(object):
|
||||
if any(missing):
|
||||
raise MissingDep(missing)
|
||||
|
||||
def _warn_if_fish(self, executable): # pragma: no cover
|
||||
@staticmethod
|
||||
def _warn_if_fish(executable): # pragma: no cover
|
||||
if (
|
||||
executable is None
|
||||
or os.path.basename(os.path.realpath(executable)) != "fish"
|
||||
@@ -530,8 +551,8 @@ class Stage(object):
|
||||
def _check_circular_dependency(self):
|
||||
from dvc.exceptions import CircularDependencyError
|
||||
|
||||
circular_dependencies = set(file.path for file in self.deps) & set(
|
||||
file.path for file in self.outs
|
||||
circular_dependencies = set(d.path for d in self.deps) & set(
|
||||
o.path for o in self.outs
|
||||
)
|
||||
|
||||
if circular_dependencies:
|
||||
@@ -541,11 +562,11 @@ class Stage(object):
|
||||
from dvc.exceptions import ArgumentDuplicationError
|
||||
from collections import Counter
|
||||
|
||||
count = Counter(file.path for file in self.deps + self.outs)
|
||||
path_counts = Counter(edge.path for edge in self.deps + self.outs)
|
||||
|
||||
for file, occurrence in count.items():
|
||||
for path, occurrence in path_counts.items():
|
||||
if occurrence > 1:
|
||||
raise ArgumentDuplicationError(file)
|
||||
raise ArgumentDuplicationError(path)
|
||||
|
||||
def _run(self):
|
||||
self._check_missing_deps()
|
||||
@@ -619,7 +640,8 @@ class Stage(object):
|
||||
for out in self.outs:
|
||||
out.checkout(force=force)
|
||||
|
||||
def _status(self, entries, name):
|
||||
@staticmethod
|
||||
def _status(entries, name):
|
||||
ret = {}
|
||||
|
||||
for entry in entries:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Helpers for other modules."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from dvc.utils.compat import str, builtin_str, open
|
||||
from dvc.utils.compat import str, builtin_str, open, cast_bytes_py2
|
||||
|
||||
import os
|
||||
import sys
|
||||
@@ -195,7 +195,8 @@ def fix_env(env=None):
|
||||
lp_key = "LD_LIBRARY_PATH"
|
||||
lp_orig = env.get(lp_key + "_ORIG", None)
|
||||
if lp_orig is not None:
|
||||
env[lp_key] = lp_orig
|
||||
# NOTE: py2 doesn't like unicode strings in environ
|
||||
env[cast_bytes_py2(lp_key)] = cast_bytes_py2(lp_orig)
|
||||
else:
|
||||
env.pop(lp_key, None)
|
||||
|
||||
|
||||
@@ -12,6 +12,27 @@ is_py2 = _ver[0] == 2
|
||||
#: Python 3.x?
|
||||
is_py3 = _ver[0] == 3
|
||||
|
||||
# NOTE: cast_bytes_py2 is taken from https://github.com/ipython/ipython_genutils
|
||||
|
||||
# simplified version of ipython_genutils/encoding.py
|
||||
DEFAULT_ENCODING = sys.getdefaultencoding()
|
||||
|
||||
|
||||
def no_code(x, encoding=None):
|
||||
return x
|
||||
|
||||
|
||||
def encode(u, encoding=None):
|
||||
encoding = encoding or DEFAULT_ENCODING
|
||||
return u.encode(encoding, "replace")
|
||||
|
||||
|
||||
def cast_bytes(s, encoding=None):
|
||||
if not isinstance(s, bytes):
|
||||
return encode(s, encoding)
|
||||
return s
|
||||
|
||||
|
||||
if is_py2:
|
||||
from urlparse import urlparse, urljoin # noqa: F401
|
||||
from StringIO import StringIO # noqa: F401
|
||||
@@ -27,6 +48,7 @@ if is_py2:
|
||||
numeric_types = (int, long, float) # noqa: F821
|
||||
integer_types = (int, long) # noqa: F821
|
||||
input = raw_input # noqa: F821
|
||||
cast_bytes_py2 = cast_bytes
|
||||
|
||||
elif is_py3:
|
||||
from urllib.parse import urlparse, urljoin # noqa: F401
|
||||
@@ -45,3 +67,4 @@ elif is_py3:
|
||||
integer_types = (int,) # noqa: F821
|
||||
input = input # noqa: F821
|
||||
open = open # noqa: F821
|
||||
cast_bytes_py2 = no_code
|
||||
|
||||
@@ -53,7 +53,14 @@ if [ -n "$TRAVIS_OS_NAME" ] && [ "$TRAVIS_OS_NAME" != "osx" ] \
|
||||
bash "$scriptdir/install_hadoop.sh"
|
||||
fi
|
||||
|
||||
if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
|
||||
if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
|
||||
brew install openssl
|
||||
brew cask install google-cloud-sdk
|
||||
fi
|
||||
|
||||
if [[ -n "$PYTHON_VER" ]]; then
|
||||
eval "$(pyenv init -)"
|
||||
pyenv install --skip-existing --keep --verbose "$PYTHON_VER"
|
||||
echo 'eval "$(pyenv init -)"' >> env.sh
|
||||
echo "pyenv shell $PYTHON_VER" >> env.sh
|
||||
fi
|
||||
|
||||
+14
-12
@@ -11,22 +11,24 @@ if [[ "$TRAVIS_PULL_REQUEST" == "false" && \
|
||||
codecov
|
||||
fi
|
||||
|
||||
if [[ ! -z "$TRAVIS_TAG" ]]; then
|
||||
if [[ -n "$TRAVIS_TAG" || "$TRAVIS_EVENT_TYPE" == "cron" ]]; then
|
||||
./scripts/build_package.sh
|
||||
|
||||
# Test version
|
||||
pip uninstall -y dvc
|
||||
if [ -x "$(command -v dvc)" ]; then
|
||||
echo "ERROR: dvc command already exists!!!"
|
||||
exit 1
|
||||
fi
|
||||
if [[ -n "$TRAVIS_TAG" ]]; then
|
||||
# Test version
|
||||
pip uninstall -y dvc
|
||||
if [ -x "$(command -v dvc)" ]; then
|
||||
echo "ERROR: dvc command already exists!!!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
pip install dist/dvc-*.whl
|
||||
if [[ "$(dvc --version)" != "$TRAVIS_TAG" ]]; then
|
||||
echo "ERROR: 'dvc --version'$(dvc -V) doesn't match '$TRAVIS_TAG'"
|
||||
exit 1
|
||||
pip install dist/dvc-*.whl
|
||||
if [[ "$(dvc --version)" != "$TRAVIS_TAG" ]]; then
|
||||
echo "ERROR: 'dvc --version'$(dvc -V) doesn't match '$TRAVIS_TAG'"
|
||||
exit 1
|
||||
fi
|
||||
pip uninstall -y dvc
|
||||
fi
|
||||
pip uninstall -y dvc
|
||||
|
||||
PY_VER=$(python -c 'import sys; print(sys.version_info[0:2])')
|
||||
if [[ "$PY_VER" != '(3, 7)' ]]; then
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# This script generates config.ini for setup.iss script
|
||||
from dvc import VERSION
|
||||
from dvc.utils.compat import ConfigParser, open
|
||||
from dvc.utils.compat import ConfigParser
|
||||
|
||||
config = ConfigParser.ConfigParser()
|
||||
config.add_section("Version")
|
||||
|
||||
+9
-1
@@ -1,6 +1,8 @@
|
||||
import os
|
||||
import sys
|
||||
from subprocess import check_call
|
||||
|
||||
|
||||
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
os.chdir(REPO_ROOT)
|
||||
|
||||
@@ -10,8 +12,14 @@ os.putenv(
|
||||
os.putenv("DVC_HOME", REPO_ROOT)
|
||||
os.putenv("DVC_TEST", "true")
|
||||
|
||||
if len(sys.argv) == 1:
|
||||
scope = "--all-modules"
|
||||
else:
|
||||
scope = " ".join(sys.argv[1:])
|
||||
|
||||
cmd = (
|
||||
"nosetests -v --processes=-1 --process-timeout=500 --cover-inclusive "
|
||||
"--cover-erase --cover-package=dvc --with-coverage --all-modules --with-flaky"
|
||||
"--cover-erase --cover-package=dvc --with-coverage --with-flaky "
|
||||
"{scope} ".format(scope=scope)
|
||||
)
|
||||
check_call(cmd, shell=True)
|
||||
|
||||
+37
-6
@@ -359,7 +359,7 @@ class TestNoMetrics(TestDvc):
|
||||
|
||||
|
||||
class TestCachedMetrics(TestDvc):
|
||||
def _do_write(self, branch):
|
||||
def _do_add(self, branch):
|
||||
self.dvc.scm.checkout(branch)
|
||||
self.dvc.checkout(force=True)
|
||||
|
||||
@@ -369,20 +369,45 @@ class TestCachedMetrics(TestDvc):
|
||||
stages = self.dvc.add("metrics.json")
|
||||
self.assertEqual(len(stages), 1)
|
||||
stage = stages[0]
|
||||
self.assertNotEqual(stage, None)
|
||||
self.assertIsNotNone(stage)
|
||||
|
||||
self.dvc.scm.add([".gitignore", "metrics.json.dvc"])
|
||||
self.dvc.scm.commit(branch)
|
||||
|
||||
def test(self):
|
||||
def _do_run(self, branch):
|
||||
self.dvc.scm.checkout(branch)
|
||||
self.dvc.checkout(force=True)
|
||||
|
||||
with open("code.py", "w+") as fobj:
|
||||
fobj.write("import sys\n")
|
||||
fobj.write("import os\n")
|
||||
fobj.write("import json\n")
|
||||
fobj.write(
|
||||
'print(json.dumps({{"metrics": "{branch}"}}))\n'.format(
|
||||
branch=branch
|
||||
)
|
||||
)
|
||||
|
||||
stage = self.dvc.run(
|
||||
deps=["code.py"],
|
||||
metrics=["metrics.json"],
|
||||
cmd="python code.py metrics.json > metrics.json",
|
||||
)
|
||||
self.assertIsNotNone(stage)
|
||||
self.assertEqual(stage.relpath, "metrics.json.dvc")
|
||||
|
||||
self.dvc.scm.add([".gitignore", "metrics.json.dvc"])
|
||||
self.dvc.scm.commit(branch)
|
||||
|
||||
def _test_metrics(self, func):
|
||||
self.dvc.scm.commit("init")
|
||||
|
||||
self.dvc.scm.branch("one")
|
||||
self.dvc.scm.branch("two")
|
||||
|
||||
self._do_write("master")
|
||||
self._do_write("one")
|
||||
self._do_write("two")
|
||||
func("master")
|
||||
func("one")
|
||||
func("two")
|
||||
|
||||
self.dvc = Project(".")
|
||||
|
||||
@@ -398,3 +423,9 @@ class TestCachedMetrics(TestDvc):
|
||||
"two": {"metrics.json": ["two"]},
|
||||
},
|
||||
)
|
||||
|
||||
def test_add(self):
|
||||
self._test_metrics(self._do_add)
|
||||
|
||||
def test_run(self):
|
||||
self._test_metrics(self._do_run)
|
||||
|
||||
+13
-1
@@ -50,7 +50,7 @@ class TestRun(TestDvc):
|
||||
self.assertTrue(stage.path, fname)
|
||||
|
||||
with self.assertRaises(OutputDuplicationError):
|
||||
stage = self.dvc.run(
|
||||
self.dvc.run(
|
||||
cmd=cmd,
|
||||
deps=deps,
|
||||
outs=outs,
|
||||
@@ -530,6 +530,18 @@ class TestCmdRunOverwrite(TestDvc):
|
||||
self.assertNotEqual(stage_mtime, os.path.getmtime("out.dvc"))
|
||||
|
||||
|
||||
class TestCmdRunCliMetrics(TestDvc):
|
||||
def test_cached(self):
|
||||
ret = main(["run", "-m", "metrics.txt", "echo test > metrics.txt"])
|
||||
self.assertEqual(ret, 0)
|
||||
self.assertEqual(open("metrics.txt", "r").read().rstrip(), "test")
|
||||
|
||||
def test_not_cached(self):
|
||||
ret = main(["run", "-M", "metrics.txt", "echo test > metrics.txt"])
|
||||
self.assertEqual(ret, 0)
|
||||
self.assertEqual(open("metrics.txt", "r").read().rstrip(), "test")
|
||||
|
||||
|
||||
class TestRunDeterministicBase(TestDvc):
|
||||
def setUp(self):
|
||||
super(TestRunDeterministicBase, self).setUp()
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import mock
|
||||
from unittest import TestCase
|
||||
|
||||
from dvc.remote.base import RemoteBase, RemoteMissingDepsError
|
||||
from dvc.remote import REMOTES, RemoteLOCAL
|
||||
from dvc.remote import REMOTES, RemoteLOCAL, RemoteSSH, RemoteHDFS
|
||||
from dvc.remote.base import RemoteBase, RemoteCmdError, RemoteMissingDepsError
|
||||
|
||||
|
||||
class TestMissingDeps(TestCase):
|
||||
@@ -13,3 +13,23 @@ class TestMissingDeps(TestCase):
|
||||
with mock.patch.object(remote_class, "REQUIRES", REQUIRES):
|
||||
with self.assertRaises(RemoteMissingDepsError):
|
||||
remote_class(None, {})
|
||||
|
||||
|
||||
class TestCmdError(TestCase):
|
||||
def test(self):
|
||||
for remote_class in [RemoteSSH, RemoteHDFS]:
|
||||
project = None
|
||||
config = {}
|
||||
|
||||
remote_name = remote_class.scheme
|
||||
cmd = "sed 'hello'"
|
||||
ret = "1"
|
||||
err = "sed: expression #1, char 2: extra characters after command"
|
||||
|
||||
with mock.patch.object(
|
||||
remote_class,
|
||||
"remove",
|
||||
side_effect=RemoteCmdError(remote_name, cmd, ret, err),
|
||||
):
|
||||
with self.assertRaises(RemoteCmdError):
|
||||
remote_class(project, config).remove("file")
|
||||
|
||||
Reference in New Issue
Block a user