repro: introduce --dry option

Fixes #972
Related to #894

Signed-off-by: Ruslan Kuprieiev <[email protected]>
This commit is contained in:
Ruslan Kuprieiev
2018-08-05 00:21:35 +03:00
parent 7baf368367
commit ed8052c229
5 changed files with 53 additions and 25 deletions
+6
View File
@@ -324,6 +324,12 @@ def parse_args(argv=None):
action='store_true',
default=False,
help='Show metrics after reproduction')
repro_parser.add_argument(
'--dry',
action='store_true',
default=False,
help='Only print the commands that would be executed '
'without actually executing.')
repro_parser.set_defaults(func=CmdRepro)
# Remove
+2 -1
View File
@@ -17,7 +17,8 @@ class CmdRepro(CmdBase):
try:
self.project.reproduce(target,
recursive=recursive,
force=self.args.force)
force=self.args.force,
dry=self.args.dry)
if self.args.metrics:
self.project.metrics_show()
except ReproductionError as ex:
+11 -8
View File
@@ -235,7 +235,7 @@ class Project(object):
stage.dump()
return stage
def _reproduce_stage(self, stages, node, force):
def _reproduce_stage(self, stages, node, force, dry):
stage = stages[node]
if stage.locked:
@@ -243,13 +243,16 @@ class Project(object):
'going to be reproduced.'
self.logger.warn(msg.format(stage.relpath))
stage = stage.reproduce(force=force)
stage = stage.reproduce(force=force, dry=dry)
if not stage:
return []
stage.dump()
if not dry:
stage.dump()
return [stage]
def reproduce(self, target, recursive=True, force=False):
def reproduce(self, target, recursive=True, force=False, dry=False):
import networkx as nx
G = self.graph()[1]
@@ -259,17 +262,17 @@ class Project(object):
raise NotDvcFileError(target)
if recursive:
return self._reproduce_stages(G, stages, node, force)
return self._reproduce_stages(G, stages, node, force, dry)
return self._reproduce_stage(stages, node, force)
return self._reproduce_stage(stages, node, force, dry)
def _reproduce_stages(self, G, stages, node, force):
def _reproduce_stages(self, G, stages, node, force, dry):
import networkx as nx
result = []
for n in nx.dfs_postorder_nodes(G, node):
try:
result += self._reproduce_stage(stages, n, force)
result += self._reproduce_stage(stages, n, force, dry)
except Exception as ex:
raise ReproductionError(stages[n].relpath, ex)
return result
+21 -16
View File
@@ -150,17 +150,17 @@ class Stage(object):
self.remove_outs(ignore_remove=True)
os.unlink(self.path)
def reproduce(self, force=False):
def reproduce(self, force=False, dry=False):
if not self.changed() and not force:
return None
if (self.cmd or self.is_import) and not self.locked:
if (self.cmd or self.is_import) and not self.locked and not dry:
# Removing outputs only if we actually have command to reproduce
self.remove_outs(ignore_remove=False)
self.project.logger.info(u'Reproducing \'{}\''.format(self.relpath))
self.run()
self.run(dry=dry)
msg = u'\'{}\' was reproduced'.format(self.relpath)
self.project.logger.debug(msg)
@@ -297,35 +297,40 @@ class Stage(object):
for out in self.outs:
out.save()
def run(self):
def run(self, dry=False):
if self.locked:
msg = u'Verifying outputs in locked stage \'{}\''
self.project.logger.info(msg.format(self.relpath))
self.check_missing_outputs()
if not dry:
self.check_missing_outputs()
elif self.is_import:
msg = u'Importing \'{}\' -> \'{}\''
self.project.logger.info(msg.format(self.deps[0].path,
self.outs[0].path))
self.deps[0].download(self.outs[0].path_info)
if not dry:
self.deps[0].download(self.outs[0].path_info)
elif self.is_data_source:
msg = u'Verifying data sources in \'{}\''.format(self.relpath)
self.project.logger.info(msg)
self.check_missing_outputs()
if not dry:
self.check_missing_outputs()
else:
msg = u'Running command:\n\t{}'.format(self.cmd)
self.project.logger.info(msg)
p = subprocess.Popen(self.cmd,
cwd=self.cwd,
shell=True,
env=os.environ,
executable=os.getenv('SHELL'))
p.communicate()
if p.returncode != 0:
raise StageCmdFailedError(self)
if not dry:
p = subprocess.Popen(self.cmd,
cwd=self.cwd,
shell=True,
env=os.environ,
executable=os.getenv('SHELL'))
p.communicate()
if p.returncode != 0:
raise StageCmdFailedError(self)
self.save()
if not dry:
self.save()
def check_missing_outputs(self):
outs = [out for out in self.outs if not out.exists]
+13
View File
@@ -125,6 +125,19 @@ class TestReproChangedData(TestRepro):
shutil.copyfile(self.BAR, self.FOO)
class TestReproDry(TestReproChangedData):
def test(self):
self.swap_foo_with_bar()
stages = self.dvc.reproduce(self.file1_stage, dry=True)
self.assertTrue(len(stages), 2)
self.assertFalse(filecmp.cmp(self.file1, self.BAR))
ret = main(['repro', '--dry'])
self.assertFalse(filecmp.cmp(self.file1, self.BAR))
class TestReproChangedDeepData(TestReproChangedData):
def test(self):
file2 = 'file2'