Skip to content

Commit 1a1133f

Browse files
authored
Add -y option for bypassing prompts (#112)
* add generic `@yes_option` functionality, and apply the option to profile `delete` commands * add tests and move printed message to does_user_agree arg * change wording in changelog * make all text get printed by does_user_agree on `profile delete` so `-y` doesn't output any prompt text * fix test
1 parent 90695be commit 1a1133f

8 files changed

Lines changed: 77 additions & 27 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ how a consumer would use the library (e.g. adding unit tests, updating documenta
2828
### Added
2929

3030
- Profile can now save multiple alert and file event checkpoints. The name of the checkpoint to be used for a given query should be passed to `-c` (`--use-checkpoint`).
31+
- `-y/--assume-yes` option added to `profile delete` and `profile delete-all` commands to not require interactive prompt.
3132

3233
### Removed
3334

src/code42cli/cmds/profile.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import code42cli.profile as cliprofile
77
from code42cli.errors import Code42CLIError
8+
from code42cli.options import yes_option
89
from code42cli.profile import CREATE_PROFILE_HELP
910
from code42cli.sdk_client import validate_connection
1011
from code42cli.util import does_user_agree
@@ -103,29 +104,29 @@ def use(profile_name):
103104

104105

105106
@profile.command()
107+
@yes_option
106108
@profile_name_arg
107109
def delete(profile_name):
108110
"""Deletes a profile and its stored password (if any)."""
111+
message = "\nDeleting this profile will also delete any stored passwords and checkpoints. Are you sure? (y/n): "
109112
if cliprofile.is_default_profile(profile_name):
110-
echo("\n{} is currently the default profile!".format(profile_name))
111-
if not does_user_agree(
112-
"\nDeleting this profile will also delete any stored passwords and checkpoints. "
113-
"Are you sure? (y/n): "
114-
):
115-
return
116-
cliprofile.delete_profile(profile_name)
117-
echo("Profile '{}' has been deleted.".format(profile_name))
113+
message = "\n'{0}' is currently the default profile!\n{1}".format(profile_name, message)
114+
if does_user_agree(message):
115+
cliprofile.delete_profile(profile_name)
116+
echo("Profile '{}' has been deleted.".format(profile_name))
118117

119118

120119
@profile.command()
120+
@yes_option
121121
def delete_all():
122122
"""Deletes all profiles and saved passwords (if any)."""
123123
existing_profiles = cliprofile.get_all_profiles()
124124
if existing_profiles:
125-
echo("\nAre you sure you want to delete the following profiles?")
126-
for profile in existing_profiles:
127-
echo("\t{}".format(profile.name))
128-
if does_user_agree("\nThis will also delete any stored passwords and checkpoints. (y/n): "):
125+
message = (
126+
"\nAre you sure you want to delete the following profiles?\n\t{}"
127+
"\n\nThis will also delete any stored passwords and checkpoints. (y/n): "
128+
).format("\n\t".join([profile.name for profile in existing_profiles]))
129+
if does_user_agree(message):
129130
for profile in existing_profiles:
130131
cliprofile.delete_profile(profile.name)
131132
echo("Profile '{}' has been deleted.".format(profile.name))

src/code42cli/options.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@
66
from code42cli.profile import get_profile
77
from code42cli.sdk_client import create_sdk
88

9+
yes_option = click.option(
10+
"-y",
11+
"--assume-yes",
12+
is_flag=True,
13+
expose_value=False,
14+
callback=lambda ctx, param, value: ctx.obj.set_assume_yes(value),
15+
help='Assume "yes" as answer to all prompts and run non-interactively.',
16+
)
17+
918

1019
class CLIState(object):
1120
def __init__(self):
@@ -16,7 +25,7 @@ def __init__(self):
1625
self.debug = False
1726
self._sdk = None
1827
self.search_filters = []
19-
self.cursor_class = None
28+
self.assume_yes = False
2029

2130
@property
2231
def profile(self):
@@ -34,6 +43,9 @@ def sdk(self):
3443
self._sdk = create_sdk(self.profile, self.debug)
3544
return self._sdk
3645

46+
def set_assume_yes(self, param):
47+
self.assume_yes = param
48+
3749

3850
def set_profile(ctx, param, value):
3951
"""Sets the profile on the global state object when --profile <name> is passed to commands

src/code42cli/util.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,18 @@
55
from os import path
66
from signal import signal, getsignal, SIGINT
77

8-
from click import echo, style
8+
from click import echo, style, get_current_context
99

1010
_PADDING_SIZE = 3
1111

1212

1313
def does_user_agree(prompt):
14-
"""Prompts the user and checks if they said yes."""
14+
"""Prompts the user and checks if they said yes. If command has the `yes_option` flag, and
15+
`-y/--yes` is passed, this will always return `True`.
16+
"""
17+
ctx = get_current_context()
18+
if ctx.obj.assume_yes:
19+
return True
1520
ans = input(prompt)
1621
ans = ans.strip().lower()
1722
return ans == "y"

tests/cmds/test_profile.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -182,12 +182,10 @@ def test_update_profile_if_user_agrees_and_valid_connection_sets_password(
182182
mock_cliprofile_namespace.set_password.assert_called_once_with("newpassword", mocker.ANY)
183183

184184

185-
def test_delete_profile_warns_if_deleting_default(
186-
runner, user_agreement, mock_cliprofile_namespace
187-
):
185+
def test_delete_profile_warns_if_deleting_default(runner, mock_cliprofile_namespace):
188186
mock_cliprofile_namespace.is_default_profile.return_value = True
189187
result = runner.invoke(cli, ["profile", "delete", "mockdefault"])
190-
assert "mockdefault is currently the default profile!" in result.output
188+
assert "'mockdefault' is currently the default profile!" in result.output
191189

192190

193191
def test_delete_profile_does_nothing_if_user_doesnt_agree(
@@ -202,7 +200,7 @@ def test_delete_profile_outputs_success(runner, mock_cliprofile_namespace, user_
202200
assert "Profile 'mockdefault' has been deleted." in result.output
203201

204202

205-
def test_delete_all_warns_if_profiles_exist(runner, user_agreement, mock_cliprofile_namespace):
203+
def test_delete_all_warns_if_profiles_exist(runner, mock_cliprofile_namespace):
206204
mock_cliprofile_namespace.get_all_profiles.return_value = [
207205
create_mock_profile("test1"),
208206
create_mock_profile("test2"),
@@ -213,6 +211,17 @@ def test_delete_all_warns_if_profiles_exist(runner, user_agreement, mock_cliprof
213211
assert "test2" in result.output
214212

215213

214+
def test_delete_all_does_not_warn_if_assume_yes_flag(runner, mock_cliprofile_namespace):
215+
mock_cliprofile_namespace.get_all_profiles.return_value = [
216+
create_mock_profile("test1"),
217+
create_mock_profile("test2"),
218+
]
219+
result = runner.invoke(cli, ["profile", "delete-all", "-y"])
220+
assert "Are you sure you want to delete the following profiles?" not in result.output
221+
assert "Profile '{}' has been deleted.".format("test1") in result.output
222+
assert "Profile '{}' has been deleted.".format("test2") in result.output
223+
224+
216225
def test_delete_all_profiles_does_nothing_if_user_doesnt_agree(
217226
runner, user_disagreement, mock_cliprofile_namespace
218227
):

tests/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ def cli_state(mocker, sdk, profile):
118118
mock_state._sdk = sdk
119119
mock_state.profile = profile
120120
mock_state.search_filters = []
121+
mock_state.assume_yes = False
121122
return mock_state
122123

123124

tests/test_profile.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
11
import pytest
2-
import logging
3-
4-
from click.testing import CliRunner
5-
from code42cli.main import cli
62

73
import code42cli.profile as cliprofile
84
from code42cli import PRODUCT_NAME

tests/test_util.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,49 @@
66
TEST_HEADER = {u"key1": u"Column 1", u"key2": u"Column 10", u"key3": u"Column 100"}
77

88

9+
@pytest.fixture
10+
def context_with_assume_yes(mocker, cli_state):
11+
ctx = mocker.MagicMock()
12+
ctx.obj = cli_state
13+
cli_state.assume_yes = True
14+
return mocker.patch("code42cli.util.get_current_context", return_value=ctx)
15+
16+
17+
@pytest.fixture
18+
def context_without_assume_yes(mocker, cli_state):
19+
ctx = mocker.MagicMock()
20+
ctx.obj = cli_state
21+
cli_state.assume_yes = False
22+
return mocker.patch("code42cli.util.get_current_context", return_value=ctx)
23+
24+
925
_NAMESPACE = "{}.util".format(PRODUCT_NAME)
1026

1127

12-
def test_does_user_agree_when_user_says_y_returns_true(mocker):
28+
def test_does_user_agree_when_user_says_y_returns_true(mocker, context_without_assume_yes):
1329
mocker.patch("builtins.input", return_value="y")
1430
assert does_user_agree("Test Prompt")
1531

1632

17-
def test_does_user_agree_when_user_says_capital_y_returns_true(mocker):
33+
def test_does_user_agree_when_user_says_capital_y_returns_true(mocker, context_without_assume_yes):
1834
mocker.patch("builtins.input", return_value="Y")
1935
assert does_user_agree("Test Prompt")
2036

2137

22-
def test_does_user_agree_when_user_says_n_returns_false(mocker):
38+
def test_does_user_agree_when_user_says_n_returns_false(mocker, context_without_assume_yes):
2339
mocker.patch("builtins.input", return_value="n")
2440
assert not does_user_agree("Test Prompt")
2541

2642

43+
def test_does_user_agree_when_assume_yes_argument_passed_returns_true_and_does_not_print_prompt(
44+
mocker, context_with_assume_yes, capsys
45+
):
46+
result = does_user_agree("Test Prompt")
47+
output = capsys.readouterr()
48+
assert result
49+
assert output.out == output.err == ""
50+
51+
2752
def test_find_format_width_when_zero_records_sets_width_to_header_length():
2853
_, column_width = find_format_width([], TEST_HEADER)
2954
assert column_width[u"key1"] == len(TEST_HEADER[u"key1"])

0 commit comments

Comments
 (0)