Repository: iterative/dvc

Issue:
Bug: Add error message when one depency is not found
**Ubuntu 16.04, pip install in Anaconda 3.6.4, dvc version 0.18.13**

I have noticed that:

`dvc run -d {my_dep} -o {my_output} {my_cmd}` will run (i.e `{my_cmd}` will be executed) even if one or more dependency is not found (e.g. a path spelling error in the depencies). However, no `.dvc` file is created.

I think this behaviour is a bit confusing. It would probably be better to check for dependencies before running the command.


Discussion:
Hi @tdeboissiere !

You are right, dvc should first check for dependencies and make sure that they exist and then run the command. Will look into it soon.

Thanks,
Ruslan

Resolving patch:
diff --git a/dvc/dependency/base.py b/dvc/dependency/base.py
--- a/dvc/dependency/base.py
+++ b/dvc/dependency/base.py
@@ -54,7 +54,7 @@ def sep(self):
 
     @property
     def exists(self):
-        return self.remote.exists([self.path_info])
+        return self.remote.exists([self.path_info])[0]
 
     def changed(self):
         raise NotImplementedError
diff --git a/dvc/stage.py b/dvc/stage.py
--- a/dvc/stage.py
+++ b/dvc/stage.py
@@ -42,6 +42,19 @@ def __init__(self, fname):
         super(StageFileBadNameError, self).__init__(msg.format(fname, fname))
 
 
+class MissingDep(DvcException):
+    def __init__(self, deps):
+        assert len(deps) > 0
+
+        if len(deps) > 1:
+            dep = 'dependencies'
+        else:
+            dep = 'dependency'
+
+        msg = u'missing {}: {}'.format(dep, ', '.join(map(str, deps)))
+        super(MissingDep, self).__init__(msg)
+
+
 class MissingDataSource(DvcException):
     def __init__(self, missing_files):
         assert len(missing_files) > 0
@@ -354,7 +367,18 @@ def save(self):
         for out in self.outs:
             out.save()
 
+    def _check_missing_deps(self):
+        missing = []
+        for dep in self.deps:
+            if not dep.exists:
+                missing.append(dep)
+
+        if len(missing) > 0:
+            raise MissingDep(missing)
+
     def run(self, dry=False):
+        self._check_missing_deps()
+
         if self.locked:
             msg = u'Verifying outputs in locked stage \'{}\''
             self.project.logger.info(msg.format(self.relpath))


Test patch:
diff --git a/tests/test_run.py b/tests/test_run.py
--- a/tests/test_run.py
+++ b/tests/test_run.py
@@ -3,7 +3,7 @@
 
 from dvc.main import main
 from dvc.utils import file_md5
-from dvc.stage import Stage, StageFileBadNameError
+from dvc.stage import Stage, StageFileBadNameError, MissingDep
 from dvc.command.run import CmdRun
 from dvc.exceptions import OutputDuplicationError
 
@@ -55,6 +55,17 @@ def test(self):
                      cwd=os.curdir)
 
 
+class TestRunMissingDep(TestDvc):
+    def test(self):
+        with self.assertRaises(MissingDep):
+            self.dvc.run(cmd='',
+                         deps=['non-existing-dep'],
+                         outs=[],
+                         outs_no_cache=[],
+                         fname='empty.dvc',
+                         cwd=os.curdir)
+
+
 class TestRunBadStageFilename(TestDvc):
     def test(self):
         with self.assertRaises(StageFileBadNameError):