Skip to content

Commit c16dd5c

Browse files
committed
Add cmd2.Cmd.read_secret method
This method is intended to read things like passwords without displaying them on the screen.
1 parent 48b7ba1 commit c16dd5c

File tree

4 files changed

+58
-3
lines changed

4 files changed

+58
-3
lines changed

cmd2/cmd2.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3391,6 +3391,24 @@ def read_input(
33913391

33923392
return self._read_raw_input(prompt, temp_session)
33933393

3394+
def read_secret(
3395+
self,
3396+
prompt: str = '',
3397+
) -> str:
3398+
"""Read a secret from stdin without displaying the value on the screen.
3399+
3400+
:param prompt: prompt to display to user
3401+
:return: the secret read from stdin with all trailing new lines removed
3402+
:raises EOFError: if the input stream is closed or the user signals EOF (e.g., Ctrl+D)
3403+
:raises Exception: any other exceptions raised by prompt()
3404+
"""
3405+
temp_session: PromptSession[str] = PromptSession(
3406+
input=self.main_session.input,
3407+
output=self.main_session.output,
3408+
)
3409+
3410+
return self._read_raw_input(prompt, temp_session, is_password=True)
3411+
33943412
def _process_alerts(self) -> None:
33953413
"""Background worker that processes queued alerts and dynamic prompt updates."""
33963414
while True:

examples/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ each:
7777
- Shows how cmd2's built-in `run_pyscript` command can provide advanced Python scripting of cmd2
7878
applications
7979
- [read_input.py](https://github.com/python-cmd2/cmd2/blob/main/examples/read_input.py)
80-
- Demonstrates the various ways to call `cmd2.Cmd.read_input()` for input history and tab
81-
completion
80+
- Demonstrates the various ways to call `cmd2.Cmd.read_input()` and `cmd2.Cmd.read_secret()` for
81+
input history, tab completion, and password masking
8282
- [remove_builtin_commands.py](https://github.com/python-cmd2/cmd2/blob/main/examples/remove_builtin_commands.py)
8383
- Shows how to remove any built-in cmd2 commands you do not want to be present in your cmd2
8484
application

examples/read_input.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/usr/bin/env python
2-
"""A simple example demonstrating the various ways to call cmd2.Cmd.read_input() for input history and tab completion.
2+
"""A simple example demonstrating the various ways to call cmd2.Cmd.read_input() and cmd2.Cmd.read_secret().
33
4+
These methods can be used to read input from stdin with optional history, tab completion, or password masking.
45
It also demonstrates how to use the cmd2.Cmd.select method.
56
"""
67

@@ -97,6 +98,19 @@ def do_custom_parser(self, _) -> None:
9798
else:
9899
self.custom_history.append(input_str)
99100

101+
@cmd2.with_category(EXAMPLE_COMMANDS)
102+
def do_read_password(self, _) -> None:
103+
"""Call read_secret to read a password without displaying it while being typed.
104+
105+
WARNING: Password will be displayed for verification after it is typed.
106+
"""
107+
self.poutput("The input will not be displayed on the screen")
108+
try:
109+
password = self.read_secret("Password: ")
110+
self.poutput(f"You entered: {password}")
111+
except EOFError:
112+
pass
113+
100114
def do_eat(self, arg):
101115
"""Example of using the select method for reading multiple choice input.
102116

tests/test_cmd2.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2236,6 +2236,29 @@ def test_read_input_eof(base_app, monkeypatch) -> None:
22362236
base_app.read_input("Prompt> ")
22372237

22382238

2239+
def test_read_secret(base_app, monkeypatch):
2240+
"""Test read_secret passes is_password=True to _read_raw_input."""
2241+
with mock.patch.object(base_app, '_read_raw_input') as mock_reader:
2242+
mock_reader.return_value = "my_secret"
2243+
2244+
secret = base_app.read_secret("Secret: ")
2245+
2246+
assert secret == "my_secret"
2247+
# Verify it called _read_raw_input with is_password=True
2248+
args, kwargs = mock_reader.call_args
2249+
assert args[0] == "Secret: "
2250+
assert kwargs['is_password'] is True
2251+
2252+
2253+
def test_read_secret_eof(base_app, monkeypatch):
2254+
"""Test that read_secret passes up EOFErrors."""
2255+
read_raw_mock = mock.MagicMock(name='_read_raw_input', side_effect=EOFError)
2256+
monkeypatch.setattr("cmd2.Cmd._read_raw_input", read_raw_mock)
2257+
2258+
with pytest.raises(EOFError):
2259+
base_app.read_secret("Secret: ")
2260+
2261+
22392262
def test_read_input_passes_all_arguments_to_resolver(base_app):
22402263
mock_choices = ["choice1", "choice2"]
22412264
mock_provider = mock.MagicMock(name="provider")

0 commit comments

Comments
 (0)