dvc find command

This commit is contained in:
Dmitry
2017-11-16 01:11:07 -08:00
parent 76a6757fa1
commit 86a933ea71
5 changed files with 142 additions and 22 deletions
+28
View File
@@ -4,6 +4,7 @@ import sys
import argparse
from multiprocessing import cpu_count
from dvc.command.find import CmdFind
from dvc.command.init import CmdInit
from dvc.command.remove import CmdRemove
from dvc.command.run import CmdRun
@@ -420,6 +421,33 @@ def parse_args(argv=None):
help='Checkout')
checkout_parser.set_defaults(func=CmdCheckout)
# Find
find_parser = subparsers.add_parser(
'find',
parents=[parent_parser],
help='Find branch name')
find_parser.set_defaults(func=CmdFind)
find_parser.add_argument(
'target',
nargs='?',
help='Target metric file.')
find_parser.add_argument(
'-r',
'--branch-regex',
help='Branch name regexp.')
find_parser.add_argument(
'-c',
'--criteria',
help='Search criteria. By default it finds max.',
choices=['max', 'min', 'all'],
default='max')
find_parser.add_argument(
'-s',
'--show-value',
action='store_true',
default=False,
help='Show metrics value.')
if isinstance(argv, str):
argv = argv.split()
+62
View File
@@ -0,0 +1,62 @@
from dvc import utils
from dvc.command.base import CmdBase, DvcLock
from dvc.executor import ExecutorError
from dvc.logger import Logger
class CmdFind(CmdBase):
def __init__(self, settings):
super(CmdFind, self).__init__(settings)
def run(self):
with DvcLock(self.is_locker, self.git):
fname = self.parsed_args.target
regex = self.parsed_args.branch_regex
branches = self.git.branches(regex)
metrics = [self.read_metrics(fname, b) for b in branches]
zipped = zip(metrics, branches)
res = self.filter(zipped, self.parsed_args.criteria)
res = filter(lambda x: x[0] is not None, res)
for (val, branch) in res:
if self.parsed_args.show_value:
print('{}: {}'.format(branch, val))
else:
print(branch)
return 0
@staticmethod
def filter(zipped, criteria):
if criteria == 'max':
res = [max(zipped, key=lambda t: t[0])]
elif criteria == 'min':
res = [min(zipped, key=lambda t: t[0])]
else:
res = zipped
return res
def read_metrics(self, fname, branch):
try:
lines = self.git.get_file_content(fname, branch).split('\n')
except ExecutorError as ex:
msg = 'Unable to read metrics file from branch {}: {}'
data_item = self.settings.path_factory.data_item(fname)
try:
self.git.get_file_content(data_item.state.relative, branch)
Logger.error(msg.format(branch, 'this is data file, not metric file'))
except ExecutorError:
Logger.error(msg.format(branch, 'file does not exist in this branch'))
return None
return None
metric = utils.parse_target_metric(lines)
if not metric:
msg = 'Unable to parse metrics from the first line of file {} in branch {}'
Logger.error(msg.format(fname, branch))
return None
return metric
+21
View File
@@ -444,3 +444,24 @@ class GitWrapper(GitWrapperI):
git_cmd = u'git ls-tree --name-only -r {}'.format(commit)
lines = Executor.exec_cmd_only_success(git_cmd.split()).split('\n')
return filter(lambda s: s.startswith(ConfigI.STATE_DIR), lines)
@staticmethod
def branches(regexp):
cmd = 'git branch --format "%(refname)"'
lines = Executor.exec_cmd_only_success(cmd.split()).split('\n')
lines = map(lambda l: l.strip('"'), lines)
if not regexp or '/' not in regexp:
# remove remotes like 'refs/heads'
lines = map(lambda s: s.split('/')[-1], lines)
if not regexp:
return lines
reg = re.compile(regexp)
return filter(reg.search, lines)
@staticmethod
def get_file_content(fname, branch):
cmd = 'git show {}:{}'.format(branch, fname)
return Executor.exec_cmd_only_success(cmd.split())
+5 -22
View File
@@ -4,6 +4,7 @@ import sys
import json
import re
from dvc import utils
from dvc.exceptions import DvcException
from dvc.path.data_item import DataDirError
from dvc.system import System
@@ -18,8 +19,6 @@ class StateFile(object):
MAGIC = 'DVC-State'
VERSION = '0.1'
FLOATS_FROM_STRING = re.compile(r'[-+]?(?:(?:\d*\.\d+)|(?:\d+\.?))(?:[Ee][+-]?\d+)?')
DVC_PYTHON_FILE_NAME = 'dvc.py'
DVC_COMMAND = 'dvc'
@@ -110,29 +109,13 @@ class StateFile(object):
if target_metric:
self.target_metrics[StateFile.TARGET_METRICS_SINGLE_METRIC] = target_metric
@staticmethod
def parse_target_metric(file_name):
fd = open(file_name, 'r')
try:
lines = fd.readlines(2)
except Exception as e:
raise StateFileError('Unable to parse target file')
if len(lines) != 1:
raise StateFileError('file contains more then one line')
# Extract float from string. I.e. from 'AUC: 0.596182'
nums = StateFile.FLOATS_FROM_STRING.findall(lines[0])
if len(nums) < 1:
raise StateFileError("Unable to parse metrics from the first line")
return float(nums[0])
@staticmethod
def try_parse_target_metric(file_name):
try:
return StateFile.parse_target_metric(file_name)
metric = utils.parse_target_metric_file(file_name)
if not metric:
raise StateFileError('Unable to parse metrics from the first line of file {}'.format(file_name))
return metric
except StateFileError:
return None
+26
View File
@@ -2,6 +2,7 @@
Helpers for other modules.
"""
import os
import re
import stat
import shutil
@@ -13,6 +14,7 @@ from dvc.logger import Logger
LOCAL_CHUNK_SIZE = 1024*1024*1024
def cached_property(func):
'''A decorator for caching properties in classes.'''
def get(self):
@@ -82,3 +84,27 @@ def map_progress(func, targets, n_threads):
progress.finish()
return zip(targets, ret)
def parse_target_metric_file(file_name):
with open(file_name, 'r') as fd:
try:
lines = fd.readlines(2)
except Exception:
return None
return parse_target_metric(lines)
FLOATS_FROM_STRING = re.compile(r'[-+]?(?:(?:\d*\.\d+)|(?:\d+\.?))(?:[Ee][+-]?\d+)?')
def parse_target_metric(lines):
if len(lines) != 1:
return None
# Extract float from string. I.e. from 'AUC: 0.596182'
nums = FLOATS_FROM_STRING.findall(lines[0])
if len(nums) < 1:
return None
return float(nums[0])