The *echo_char* argument controls how user input is displayed while typing.
If *echo_char* is ``None`` (default), input remains hidden. Otherwise,
- *echo_char* must be a printable ASCII string and each typed character
- is replaced by it. For example, ``echo_char='*'`` will display
- asterisks instead of the actual input.
+ *echo_char* must be a single printable ASCII character and each
+ typed character is replaced by it. For example, ``echo_char='*'`` will
+ display asterisks instead of the actual input.
If echo free input is unavailable getpass() falls back to printing
a warning message to *stream* and reading from ``sys.stdin`` and
prompt: Written on stream to ask for the input. Default: 'Password: '
stream: A writable file object to display the prompt. Defaults to
the tty. If no tty is available defaults to sys.stderr.
- echo_char: A string used to mask input (e.g., '*'). If None, input is
- hidden.
+ echo_char: A single ASCII character to mask input (e.g., '*').
+ If None, input is hidden.
Returns:
The seKr3t input.
Raises:
def _check_echo_char(echo_char):
- # ASCII excluding control characters
- if echo_char and not (echo_char.isprintable() and echo_char.isascii()):
- raise ValueError("'echo_char' must be a printable ASCII string, "
- f"got: {echo_char!r}")
+ # Single-character ASCII excluding control characters
+ if echo_char is None:
+ return
+ if not isinstance(echo_char, str):
+ raise TypeError("'echo_char' must be a str or None, not "
+ f"{type(echo_char).__name__}")
+ if not (
+ len(echo_char) == 1
+ and echo_char.isprintable()
+ and echo_char.isascii()
+ ):
+ raise ValueError("'echo_char' must be a single printable ASCII "
+ f"character, got: {echo_char!r}")
def _raw_input(prompt="", stream=None, input=None, echo_char=None):
self.assertEqual('Password: *******\x08 \x08', mock_output.getvalue())
+class GetpassEchoCharTest(unittest.TestCase):
+
+ def test_accept_none(self):
+ getpass._check_echo_char(None)
+
+ @support.subTests('echo_char', ["*", "A", " "])
+ def test_accept_single_printable_ascii(self, echo_char):
+ getpass._check_echo_char(echo_char)
+
+ def test_reject_empty_string(self):
+ self.assertRaises(ValueError, getpass.getpass, echo_char="")
+
+ @support.subTests('echo_char', ["***", "AA", "aA*!"])
+ def test_reject_multi_character_strings(self, echo_char):
+ self.assertRaises(ValueError, getpass.getpass, echo_char=echo_char)
+
+ @support.subTests('echo_char', [
+ '\N{LATIN CAPITAL LETTER AE}', # non-ASCII single character
+ '\N{HEAVY BLACK HEART}', # non-ASCII multibyte character
+ ])
+ def test_reject_non_ascii(self, echo_char):
+ self.assertRaises(ValueError, getpass.getpass, echo_char=echo_char)
+
+ @support.subTests('echo_char', [
+ ch for ch in map(chr, range(0, 128))
+ if not ch.isprintable()
+ ])
+ def test_reject_non_printable_characters(self, echo_char):
+ self.assertRaises(ValueError, getpass.getpass, echo_char=echo_char)
+
+ # TypeError Rejection
+ @support.subTests('echo_char', [b"*", 0, 0.0, [], {}])
+ def test_reject_non_string(self, echo_char):
+ self.assertRaises(TypeError, getpass.getpass, echo_char=echo_char)
+
+
if __name__ == "__main__":
unittest.main()