From 948021f5b0b7dc53b3a42a45e45f3fa48b0fb293 Mon Sep 17 00:00:00 2001 From: "Mr. Outis" Date: Fri, 14 Dec 2018 10:47:17 -0600 Subject: [PATCH] rfc: Prompt as functions instead of a class --- dvc/command/destroy.py | 4 ++-- dvc/command/gc.py | 3 ++- dvc/command/remove.py | 5 ++--- dvc/project.py | 5 ++--- dvc/prompt.py | 42 +++++++++++++++++++----------------------- dvc/remote/local.py | 5 ++--- dvc/remote/ssh.py | 3 ++- dvc/stage.py | 8 +++++--- tests/test_checkout.py | 8 ++------ tests/test_remove.py | 4 +--- 10 files changed, 39 insertions(+), 48 deletions(-) diff --git a/dvc/command/destroy.py b/dvc/command/destroy.py index e7f0643e1..c0305b950 100644 --- a/dvc/command/destroy.py +++ b/dvc/command/destroy.py @@ -1,3 +1,4 @@ +import dvc.prompt as prompt from dvc.command.base import CmdBase from dvc.exceptions import DvcException @@ -9,8 +10,7 @@ class CmdDestroy(CmdBase): u'all data files, as well as cache in .dvc/cache.\n' \ u'Are you sure you want to continue?' - if not self.args.force \ - and not self.project.prompt.prompt(msg, False): + if not self.args.force and not prompt.confirm(msg): msg = u'Cannot destroy without a confirmation from the ' \ u'user. Use \'-f\' to force.' raise DvcException(msg) diff --git a/dvc/command/gc.py b/dvc/command/gc.py index a802bf08c..19fd24b43 100644 --- a/dvc/command/gc.py +++ b/dvc/command/gc.py @@ -1,4 +1,5 @@ import os +import dvc.prompt as prompt from dvc.command.base import CmdBase @@ -26,7 +27,7 @@ class CmdGC(CmdBase): self.project.logger.warn(msg) msg = 'Are you sure you want to proceed?' - if not self.args.force and not self.project.prompt.prompt(msg): + if not self.args.force and not prompt.confirm(msg): return 1 self.project.gc(all_branches=self.args.all_branches, diff --git a/dvc/command/remove.py b/dvc/command/remove.py index df3d35dad..b3d491c15 100644 --- a/dvc/command/remove.py +++ b/dvc/command/remove.py @@ -1,3 +1,4 @@ +import dvc.prompt as prompt from dvc.exceptions import DvcException from dvc.command.base import CmdBase from dvc.logger import logger @@ -16,9 +17,7 @@ class CmdRemove(CmdBase): .format(target) ) - confirmed = self.project.prompt.prompt(msg) - - if confirmed: + if prompt.confirm(msg): return False raise DvcException( diff --git a/dvc/project.py b/dvc/project.py index fbd7fa9a0..65cacc712 100644 --- a/dvc/project.py +++ b/dvc/project.py @@ -1,5 +1,6 @@ import collections import os +import dvc.prompt as prompt from dvc.exceptions import DvcException, MoveNotDataSourceError from dvc.exceptions import NotDvcProjectError @@ -29,7 +30,6 @@ class Project(object): from dvc.cache import Cache from dvc.data_cloud import DataCloud from dvc.updater import Updater - from dvc.prompt import Prompt root_dir = self._find_root(root_dir) @@ -51,7 +51,6 @@ class Project(object): self.cache = Cache(self) self.cloud = DataCloud(self, config=self.config._config) self.updater = Updater(self.dvc_dir) - self.prompt = Prompt() self._files_to_git_add = [] @@ -619,7 +618,7 @@ class Project(object): msg = "Missing cache for directory '{}'. " \ "Cache for files inside will be lost. " \ "Would you like to continue? Use '-f' to force. " - if not (force or self.prompt.prompt(msg, False)): + if not force and not prompt.confirm(msg): raise DvcException("Unable to fully collect " "used cache without cache " "for directory '{}'".format(out)) diff --git a/dvc/prompt.py b/dvc/prompt.py index 827aeff2d..24ef7f216 100644 --- a/dvc/prompt.py +++ b/dvc/prompt.py @@ -1,6 +1,5 @@ import sys - -from dvc.progress import progress +from getpass import getpass try: # NOTE: in Python3 raw_input() was renamed to input() @@ -9,32 +8,29 @@ except NameError: pass -class Prompt(object): - def __init__(self): - self.default = None +def ask(prompt, limited_to=None): + if not sys.stdout.isatty(): + return - def prompt(self, msg, default=False): # pragma: no cover - if self.default is not None: - return self.default + while True: + answer = input(prompt + ' ').lower() - if not sys.stdout.isatty(): - return default + if not limited_to: + return answer - answer = input(msg + u' (y/n)\n').lower() - while answer not in ['yes', 'no', 'y', 'n']: - answer = input('Enter \'yes\' or \'no\'.\n').lower() + if answer in limited_to: + return answer - return answer[0] == "y" + print("Your response must be one of: {options}. Please try again." + .format(options=limited_to)) - def prompt_password(self, msg): # pragma: no cover - import getpass - if not sys.stdout.isatty(): - return None +def confirm(statement): + prompt = '{statement} [y/n]'.format(statement=statement) + answer = ask(prompt, limited_to=['yes', 'no', 'y', 'n']) + return answer and answer.startswith('y') - msg = '{}:\n'.format(msg) - if not progress.is_finished: - msg = u'\n' + msg - - return getpass.getpass(msg) +def password(statement): + prompt = '{statement}: '.format(statement=statement) + return getpass(prompt) diff --git a/dvc/remote/local.py b/dvc/remote/local.py index 3274ce9c1..e054550ca 100644 --- a/dvc/remote/local.py +++ b/dvc/remote/local.py @@ -7,6 +7,7 @@ import shutil import posixpath from operator import itemgetter +import dvc.prompt as prompt from dvc.system import System from dvc.remote.base import RemoteBase, STATUS_MAP from dvc.logger import logger @@ -372,9 +373,7 @@ class RemoteLOCAL(RemoteBase): .format(file) ) - confirmed = self.project.prompt.prompt(msg, False) - - if not confirmed: + if not prompt.confirm(msg): raise DvcException('Unable to remove {} without a confirmation' " from the user. Use '-f' to force." .format(file)) diff --git a/dvc/remote/ssh.py b/dvc/remote/ssh.py index 46652388e..755468728 100644 --- a/dvc/remote/ssh.py +++ b/dvc/remote/ssh.py @@ -7,6 +7,7 @@ try: except ImportError: paramiko = None +import dvc.prompt as prompt from dvc.logger import logger from dvc.progress import progress from dvc.remote.base import RemoteBase, RemoteBaseCmdError @@ -93,7 +94,7 @@ class RemoteSSH(RemoteBase): if self.ask_password and self.password is None: msg = ("Enter a private key passphrase or a password for " "host '{}' port '{}' user '{}'").format(host, port, user) - self.password = self.project.prompt.prompt_password(msg) + self.password = prompt.password(msg) ssh.connect(host, username=user, diff --git a/dvc/stage.py b/dvc/stage.py index 5751c1f87..e9a85341b 100644 --- a/dvc/stage.py +++ b/dvc/stage.py @@ -4,6 +4,7 @@ import posixpath import subprocess from schema import Schema, SchemaError, Optional, Or, And +import dvc.prompt as prompt import dvc.dependency as dependency import dvc.output as output from dvc.exceptions import DvcException @@ -251,8 +252,8 @@ class Stage(object): msg = "Going to reproduce '{}'. Are you sure you want to continue?" msg = msg.format(self.relpath) - if interactive \ - and not self.project.prompt.prompt(msg): + + if interactive and not prompt.confirm(msg): raise DvcException('Reproduction aborted by the user') self.project.logger.info(u'Reproducing \'{}\''.format(self.relpath)) @@ -412,7 +413,8 @@ class Stage(object): msg = "'{}' already exists. Do you wish to run the command and " \ "overwrite it?".format(stage.relpath) - if not overwrite and not project.prompt.prompt(msg, False): + + if not overwrite and not prompt.confirm(msg): raise StageFileAlreadyExistsError(stage.relpath) return stage diff --git a/tests/test_checkout.py b/tests/test_checkout.py index 8c8cac816..f4f2cc763 100644 --- a/tests/test_checkout.py +++ b/tests/test_checkout.py @@ -163,12 +163,10 @@ class TestRemoveFilesWhenCheckout(CheckoutBase): class TestCheckoutCleanWorkingDir(CheckoutBase): - @patch('dvc.prompt.Prompt.prompt') + @patch('dvc.prompt.confirm', return_value=True) def test(self, mock_prompt): from tests.test_data_cloud import sleep - mock_prompt.return_value = True - stages = self.dvc.add(self.DATA_DIR) stage = stages[0] @@ -184,12 +182,10 @@ class TestCheckoutCleanWorkingDir(CheckoutBase): self.assertEqual(ret, 0) self.assertFalse(os.path.exists(working_dir_change)) - @patch('dvc.prompt.Prompt.prompt') + @patch('dvc.prompt.confirm', return_value=False) def test_force(self, mock_prompt): from tests.test_data_cloud import sleep - mock_prompt.return_value = False - stages = self.dvc.add(self.DATA_DIR) self.assertEqual(len(stages), 1) stage = stages[0] diff --git a/tests/test_remove.py b/tests/test_remove.py index 9b17c3544..7e2b7bf15 100644 --- a/tests/test_remove.py +++ b/tests/test_remove.py @@ -69,10 +69,8 @@ class TestRemovePurge(TestDvc): self.assertFalse(os.path.exists(self.FOO)) self.assertFalse(os.path.exists(dvcfile)) - @patch('dvc.prompt.Prompt.prompt') + @patch('dvc.prompt.confirm', return_value=False) def test_force(self, mock_prompt): - mock_prompt.return_value = False - dvcfile = self.dvc.add(self.FOO)[0].path ret = main(['remove', '--purge', dvcfile])