Repository: iterative/dvc

Issue:
Unable to track metrics on state file name not ending with "dvc"
Platform: Ubuntu 16.04
Installation method: pip
DVC version: 0.18.3

After running `dvc run ... -M metricFilename -f state ... `
 and commiting, `dvc metrics show` displays:

> Error: Failed to show metrics: No metric files in this repository. Use 'dvc metrics add' to add a metric file to track.

Further experiments show that running `dvc run ... -M metricFilename -f state.dvc ...`  solves problem.
Is this intended behaviour? I did not have problem with repro-ing from `state` file


Discussion:
Hi @pared !

Yes, it is an expected behaviour. `-f` is used to specify filename for the dvcfile and in order for dvc to find it later it should be either `Dvcfile` or end with `.dvc` suffix. So when you use `-f state` instead of `-f state.dvc` dvc is simply not finding it. This is definitely something we should prevent when running `dvc run`. I will prepare a patch shortly. Thank you!
>  I did not have problem with repro-ing from state file

Ah, looks like we should check it there too, since it only works if you explicitly specify `dvc repro state` and will not be discovered automatically by dvc. Great catch!
Glad I could be of use, if there is no one to patch this up i can try to do it. 
I am new to open source, so I probably would need some guidance.
Hi @pared !

Thank you so much for offering help! I'm ready to send a patch to fix this particular issue, but if you still wish to help out in a different task we do have some and are happy to help tackle them :slightly_smiling_face:  Full list is available [here](https://github.com/iterative/dvc/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22). For example here are two simple tasks related to our visualization feature `dvc pipeline show` that are perfect to start with:

1) [`--dot`](https://github.com/iterative/dvc/issues/1051) -- basically you will need to add `--dot file.dot` option for `dvc pipeline show` that will output DAG of the pipeline in graphviz dot format that user will be then able to render using graphviz itself. We already store our DAG using networkx, so [this](https://networkx.github.io/documentation/stable/auto_examples/pygraphviz/write_dotfile.html) should be useful;

2) [`--tree`](https://github.com/iterative/dvc/issues/1038) -- this one is similar to our `--ascii` option, but will actually unroll all the dependencies down the graph into a dependency tree. Something like [asciitree](https://pypi.org/project/asciitree/) should be helpful;

Examples for both tasks are located at `dvc/commands/pipeline.py`. And our CLI parser is in `dvc/cli.py`. You might also find our [contrib](https://dvc.org/doc/user-guide/contributing) doc useful.

Please feel free to ping me if you need anything, I will be happy to help. If you decide to tackle some task please leave a heads up here or in a comments to it, so that I will be able to assign you to it, so no one else takes it.

Thanks,
Ruslan

Resolving patch:
diff --git a/dvc/cli.py b/dvc/cli.py
--- a/dvc/cli.py
+++ b/dvc/cli.py
@@ -217,7 +217,12 @@ def parse_args(argv=None):
     run_parser.add_argument(
                         '-f',
                         '--file',
-                        help='Specify name of the state file.')
+                        help="Specify name of the stage file. It should be "
+                             "either 'Dvcfile' or have a '.dvc' suffix (e.g. "
+                             "'prepare.dvc', 'clean.dvc', etc) in order for "
+                             "dvc to be able to find it later. By default "
+                             "the first output basename + .dvc is used as "
+                             "a stage filename.")
     run_parser.add_argument(
                         '-c',
                         '--cwd',
diff --git a/dvc/stage.py b/dvc/stage.py
--- a/dvc/stage.py
+++ b/dvc/stage.py
@@ -36,6 +36,13 @@ def __init__(self, fname):
         super(StageFileIsNotDvcFileError, self).__init__(msg)
 
 
+class StageFileBadNameError(DvcException):
+    def __init__(self, fname):
+        msg = "Bad stage filename '{}'. Stage files should be named " \
+              "'Dvcfile' or have a '.dvc' suffix(e.g. '{}.dvc')."
+        super(StageFileBadNameError, self).__init__(msg.format(fname, fname))
+
+
 class MissingDataSource(DvcException):
     def __init__(self, missing_files):
         assert len(missing_files) > 0
@@ -93,16 +100,20 @@ def is_data_source(self):
         return self.cmd is None
 
     @staticmethod
-    def is_stage_file(path):
-        if not os.path.isfile(path):
-            return False
-
+    def is_stage_filename(path):
         if not path.endswith(Stage.STAGE_FILE_SUFFIX) \
            and os.path.basename(path) != Stage.STAGE_FILE:
             return False
 
         return True
 
+    @staticmethod
+    def is_stage_file(path):
+        if not os.path.isfile(path):
+            return False
+
+        return Stage.is_stage_filename(path)
+
     def changed_md5(self):
         md5 = self.dumpd().get(self.PARAM_MD5, None)
 
@@ -276,6 +287,11 @@ def loads(project=None,
 
         return stage
 
+    @staticmethod
+    def _check_dvc_filename(fname):
+        if not Stage.is_stage_filename(fname):
+            raise StageFileBadNameError(os.path.relpath(fname))
+
     @staticmethod
     def _check_dvc_file(fname):
         sname = fname + Stage.STAGE_FILE_SUFFIX
@@ -288,6 +304,8 @@ def load(project, fname):
             Stage._check_dvc_file(fname)
             raise StageFileDoesNotExistError(fname)
 
+        Stage._check_dvc_filename(fname)
+
         if not Stage.is_stage_file(fname):
             Stage._check_dvc_file(fname)
             raise StageFileIsNotDvcFileError(fname)
@@ -320,6 +338,8 @@ def dump(self, fname=None):
         if not fname:
             fname = self.path
 
+        self._check_dvc_filename(fname)
+
         msg = "Saving information to '{}'.".format(os.path.relpath(fname))
         Logger.info(msg)
 


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
+from dvc.stage import Stage, StageFileBadNameError
 from dvc.command.run import CmdRun
 from dvc.exceptions import OutputDuplicationError
 
@@ -55,6 +55,17 @@ def test(self):
                      cwd=os.curdir)
 
 
+class TestRunBadStageFilename(TestDvc):
+    def test(self):
+        with self.assertRaises(StageFileBadNameError):
+            self.dvc.run(cmd='',
+                         deps=[],
+                         outs=[],
+                         outs_no_cache=[],
+                         fname='empty',
+                         cwd=os.curdir)
+
+
 class TestRunNoExec(TestDvc):
     def test(self):
         self.dvc.run(cmd='python {} {} {}'.format(self.CODE, self.FOO, 'out'),