mirror of
https://github.com/Stone-Red-Code/dvc.git
synced 2026-11-10 15:28:34 +01:00
Merge pull request #777 from efiop/master
dvc: support external local deps
This commit is contained in:
@@ -11,11 +11,6 @@ class DependencyError(DvcException):
|
||||
super(DependencyError, self).__init__('Dependency \'{}\' error: {}'.format(path, msg))
|
||||
|
||||
|
||||
class DependencyOutsideOfRepoError(DependencyError):
|
||||
def __init__(self, path):
|
||||
super(DependencyOutsideOfRepoError, self).__init__(path, 'outside of repository')
|
||||
|
||||
|
||||
class DependencyDoesNotExistError(DependencyError):
|
||||
def __init__(self, path):
|
||||
super(DependencyDoesNotExistError, self).__init__(path, 'does not exist')
|
||||
|
||||
+25
-4
@@ -3,6 +3,11 @@ import schema
|
||||
import posixpath
|
||||
import ntpath
|
||||
|
||||
try:
|
||||
from urlparse import urlparse
|
||||
except ImportError:
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from dvc.dependency.base import *
|
||||
from dvc.logger import Logger
|
||||
from dvc.utils import remove
|
||||
@@ -20,12 +25,17 @@ class DependencyLOCAL(DependencyBase):
|
||||
def __init__(self, stage, path, info=None, remote=None):
|
||||
self.stage = stage
|
||||
self.project = stage.project
|
||||
if not os.path.isabs(path):
|
||||
path = self.unixpath(path)
|
||||
path = os.path.join(stage.cwd, path)
|
||||
self.path = os.path.normpath(path)
|
||||
self.info = info
|
||||
self.remote = remote if remote != None else RemoteLOCAL(stage.project, {})
|
||||
|
||||
if remote:
|
||||
path = os.path.join(remote.prefix, urlparse(path).path.lstrip('/'))
|
||||
|
||||
if not os.path.isabs(path):
|
||||
path = self.ospath(path)
|
||||
path = os.path.join(stage.cwd, path)
|
||||
self.path = os.path.normpath(path)
|
||||
|
||||
self.path_info = {'scheme': 'local',
|
||||
'path': self.path}
|
||||
|
||||
@@ -54,12 +64,23 @@ class DependencyLOCAL(DependencyBase):
|
||||
|
||||
self.info = self.remote.save_info(self.path_info)
|
||||
|
||||
def ospath(self, path):
|
||||
if os.name == 'nt':
|
||||
return self.ntpath(path)
|
||||
return self.unixpath(path)
|
||||
|
||||
@staticmethod
|
||||
def unixpath(path):
|
||||
assert not ntpath.isabs(path)
|
||||
assert not posixpath.isabs(path)
|
||||
return path.replace('\\', '/')
|
||||
|
||||
@staticmethod
|
||||
def ntpath(path):
|
||||
assert not ntpath.isabs(path)
|
||||
assert not posixpath.isabs(path)
|
||||
return path.replace('/', '\\')
|
||||
|
||||
def dumpd(self):
|
||||
if self.path.startswith(self.stage.project.root_dir):
|
||||
path = self.unixpath(os.path.relpath(self.path, self.stage.cwd))
|
||||
|
||||
+9
-3
@@ -66,10 +66,16 @@ class OutputLOCAL(DependencyLOCAL):
|
||||
self.project.logger.debug(msg.format(self.rel_path))
|
||||
return
|
||||
|
||||
if self.project.scm.is_tracked(self.path):
|
||||
raise OutputAlreadyTrackedError(self.rel_path)
|
||||
if self.path.startswith(self.project.root_dir):
|
||||
if self.project.scm.is_tracked(self.path):
|
||||
raise OutputAlreadyTrackedError(self.rel_path)
|
||||
|
||||
if self.use_cache:
|
||||
self.project.scm.ignore(self.path)
|
||||
|
||||
self.info = self.project.cache.local.save(self.path_info)
|
||||
|
||||
def remove(self):
|
||||
def remove(self, ignore_remove=False):
|
||||
self.remote.remove(self.path_info)
|
||||
if ignore_remove and self.use_cache:
|
||||
self.project.scm.ignore_remove(self.path)
|
||||
|
||||
@@ -35,6 +35,10 @@ class RemoteLOCAL(RemoteBase):
|
||||
|
||||
self.state = State(self.cache_dir)
|
||||
|
||||
@property
|
||||
def prefix(self):
|
||||
return self.cache_dir
|
||||
|
||||
def all(self):
|
||||
clist = []
|
||||
for entry in os.listdir(self.cache_dir):
|
||||
|
||||
+4
-8
@@ -115,14 +115,12 @@ class Stage(object):
|
||||
|
||||
return ret
|
||||
|
||||
def remove_outs(self):
|
||||
def remove_outs(self, ignore_remove=False):
|
||||
for out in self.outs:
|
||||
out.remove()
|
||||
if out.use_cache:
|
||||
self.project.scm.ignore_remove(out.path)
|
||||
out.remove(ignore_remove=ignore_remove)
|
||||
|
||||
def remove(self):
|
||||
self.remove_outs()
|
||||
self.remove_outs(ignore_remove=True)
|
||||
os.unlink(self.path)
|
||||
|
||||
def reproduce(self, force=False):
|
||||
@@ -131,7 +129,7 @@ class Stage(object):
|
||||
|
||||
if self.cmd:
|
||||
# Removing outputs only if we actually have command to reproduce
|
||||
self.remove_outs()
|
||||
self.remove_outs(ignore_remove=False)
|
||||
|
||||
self.run()
|
||||
|
||||
@@ -223,8 +221,6 @@ class Stage(object):
|
||||
|
||||
for out in self.outs:
|
||||
out.save()
|
||||
if out.use_cache and out.path_info['scheme'] == 'local':
|
||||
self.project.scm.ignore(out.path)
|
||||
|
||||
def run(self):
|
||||
if not self.is_data_source:
|
||||
|
||||
+69
-7
@@ -3,6 +3,7 @@ import yaml
|
||||
import stat
|
||||
import shutil
|
||||
import filecmp
|
||||
import tempfile
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
import boto3
|
||||
@@ -222,31 +223,47 @@ class TestReproExternalBase(TestDvc):
|
||||
def should_test(self):
|
||||
return False
|
||||
|
||||
@property
|
||||
def cache_scheme(self):
|
||||
return self.scheme
|
||||
|
||||
@property
|
||||
def scheme(self):
|
||||
return None
|
||||
|
||||
@property
|
||||
def scheme_sep(self):
|
||||
return '://'
|
||||
|
||||
@property
|
||||
def sep(self):
|
||||
return '/'
|
||||
|
||||
def test(self):
|
||||
if not self.should_test():
|
||||
return
|
||||
|
||||
cache = self.scheme + '://' + self.bucket + '/' + str(uuid.uuid4())
|
||||
cache = self.scheme + self.scheme_sep + self.bucket + self.sep + str(uuid.uuid4())
|
||||
|
||||
ret = main(['config', 'cache.' + self.scheme, 'myrepo'])
|
||||
ret = main(['config', 'cache.' + self.cache_scheme, 'myrepo'])
|
||||
self.assertEqual(ret, 0)
|
||||
ret = main(['remote', 'add', 'myrepo', cache])
|
||||
self.assertEqual(ret, 0)
|
||||
|
||||
remote_name = 'myremote'
|
||||
remote_key = str(uuid.uuid4())
|
||||
remote = self.scheme + '://' + self.bucket + '/' + remote_key
|
||||
remote = self.scheme + self.scheme_sep + self.bucket + self.sep + remote_key
|
||||
|
||||
ret = main(['remote', 'add', remote_name, remote])
|
||||
self.assertEqual(ret, 0)
|
||||
|
||||
self.dvc = Project('.')
|
||||
|
||||
foo_key = remote_key + '/' + self.FOO
|
||||
bar_key = remote_key + '/' + self.BAR
|
||||
foo_key = remote_key + self.sep + self.FOO
|
||||
bar_key = remote_key + self.sep + self.BAR
|
||||
|
||||
foo_path = self.scheme + '://' + self.bucket + '/' + foo_key
|
||||
bar_path = self.scheme + '://' + self.bucket + '/' + bar_key
|
||||
foo_path = self.scheme + self.scheme_sep + self.bucket + self.sep + foo_key
|
||||
bar_path = self.scheme + self.scheme_sep + self.bucket + self.sep + bar_key
|
||||
|
||||
# Using both plain and remote notation
|
||||
out_foo_path = 'remote://' + remote_name + '/' + self.FOO
|
||||
@@ -351,6 +368,51 @@ class TestReproExternalHDFS(TestReproExternalBase):
|
||||
self.assertEqual(p.returncode, 0)
|
||||
|
||||
|
||||
class TestReproExternalLOCAL(TestReproExternalBase):
|
||||
def setUp(self):
|
||||
super(TestReproExternalLOCAL, self).setUp()
|
||||
self.tmpdir = tempfile.mkdtemp()
|
||||
|
||||
def should_test(self):
|
||||
return True
|
||||
|
||||
@property
|
||||
def cache_scheme(self):
|
||||
return 'local'
|
||||
|
||||
@property
|
||||
def scheme(self):
|
||||
return ''
|
||||
|
||||
@property
|
||||
def scheme_sep(self):
|
||||
return ''
|
||||
|
||||
@property
|
||||
def sep(self):
|
||||
return os.sep
|
||||
|
||||
@property
|
||||
def bucket(self):
|
||||
return self.tmpdir
|
||||
|
||||
@property
|
||||
def cmd(self):
|
||||
if os.name == 'nt':
|
||||
return 'copy'
|
||||
return 'cp'
|
||||
|
||||
def write(self, bucket, key, body):
|
||||
path = os.path.join(bucket, key)
|
||||
dname = os.path.dirname(path)
|
||||
|
||||
if not os.path.exists(dname):
|
||||
os.makedirs(dname)
|
||||
|
||||
with open(path, 'w+') as fd:
|
||||
fd.write(body)
|
||||
|
||||
|
||||
class TestReproShell(TestDvc):
|
||||
def test(self):
|
||||
if os.name == 'nt':
|
||||
|
||||
Reference in New Issue
Block a user