Skip to content

Commit d2b3f74

Browse files
committed
Add optional argument mask for getpass.getpass
1 parent 0ff1611 commit d2b3f74

File tree

4 files changed

+55
-7
lines changed

4 files changed

+55
-7
lines changed

Doc/library/getpass.rst

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
The :mod:`getpass` module provides two functions:
1818

19-
.. function:: getpass(prompt='Password: ', stream=None)
19+
.. function:: getpass(prompt='Password: ', stream=None, mask=None)
2020

2121
Prompt the user for a password without echoing. The user is prompted using
2222
the string *prompt*, which defaults to ``'Password: '``. On Unix, the
@@ -25,6 +25,11 @@ The :mod:`getpass` module provides two functions:
2525
(:file:`/dev/tty`) or if that is unavailable to ``sys.stderr`` (this
2626
argument is ignored on Windows).
2727

28+
The *mask* argument controls how user input is displayed while typing. If
29+
*mask* is ``None`` (default), input remains hidden. If *mask* is a string,
30+
each typed character is replaced with the given string. For example,
31+
``mask='*'`` will display asterisks instead of the actual input.
32+
2833
If echo free input is unavailable getpass() falls back to printing
2934
a warning message to *stream* and reading from ``sys.stdin`` and
3035
issuing a :exc:`GetPassWarning`.

Doc/whatsnew/3.14.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,14 @@ getopt
558558
* Add support for returning intermixed options and non-option arguments in order.
559559
(Contributed by Serhiy Storchaka in :gh:`126390`.)
560560

561+
562+
getpass
563+
-------
564+
565+
* Add optional argument *mask* for :meth:`getpass.getpass`.
566+
(Contributed by Semyon Moroz in :gh:`77065`.)
567+
568+
561569
http
562570
----
563571

Lib/getpass.py

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Utilities to get a password and/or the current user name.
22
3-
getpass(prompt[, stream]) - Prompt for a password, with echo turned off.
3+
getpass(prompt[, stream[, mask]]) - Prompt for a password, with echo
4+
turned off.
45
getuser() - Get the user name from the environment or password database.
56
67
GetPassWarning - This UserWarning is issued when getpass() cannot prevent
@@ -25,13 +26,14 @@
2526
class GetPassWarning(UserWarning): pass
2627

2728

28-
def unix_getpass(prompt='Password: ', stream=None):
29+
def unix_getpass(prompt='Password: ', stream=None, mask=None):
2930
"""Prompt for a password, with echo turned off.
3031
3132
Args:
3233
prompt: Written on stream to ask for the input. Default: 'Password: '
3334
stream: A writable file object to display the prompt. Defaults to
3435
the tty. If no tty is available defaults to sys.stderr.
36+
mask: A string used to mask input (e.g., '*'). If None, input is hidden.
3537
Returns:
3638
The seKr3t input.
3739
Raises:
@@ -40,7 +42,7 @@ def unix_getpass(prompt='Password: ', stream=None):
4042
4143
Always restores terminal settings before returning.
4244
"""
43-
passwd = None
45+
passwd = ""
4446
with contextlib.ExitStack() as stack:
4547
try:
4648
# Always try reading and writing directly on the tty first.
@@ -68,17 +70,41 @@ def unix_getpass(prompt='Password: ', stream=None):
6870
old = termios.tcgetattr(fd) # a copy to save
6971
new = old[:]
7072
new[3] &= ~termios.ECHO # 3 == 'lflags'
73+
if mask:
74+
new[3] &= ~termios.ICANON
7175
tcsetattr_flags = termios.TCSAFLUSH
7276
if hasattr(termios, 'TCSASOFT'):
7377
tcsetattr_flags |= termios.TCSASOFT
7478
try:
7579
termios.tcsetattr(fd, tcsetattr_flags, new)
76-
passwd = _raw_input(prompt, stream, input=input)
80+
if not mask:
81+
passwd = _raw_input(prompt, stream, input=input)
82+
stream.write('\n')
83+
return passwd
84+
85+
stream.write(prompt)
86+
stream.flush()
87+
while True:
88+
char = input.read(1)
89+
if char == '\n' or char == '\r':
90+
break
91+
if char == '\x03':
92+
raise KeyboardInterrupt
93+
if char == '\x7f' or char == '\b':
94+
if mask and passwd:
95+
stream.write("\b \b" * len(mask))
96+
stream.flush()
97+
passwd = passwd[:-1]
98+
else:
99+
passwd += char
100+
if mask:
101+
stream.write(mask)
102+
stream.flush()
77103
finally:
78104
termios.tcsetattr(fd, tcsetattr_flags, old)
79105
stream.flush() # issue7208
80106
except termios.error:
81-
if passwd is not None:
107+
if passwd:
82108
# _raw_input succeeded. The final tcsetattr failed. Reraise
83109
# instead of leaving the terminal in an unknown state.
84110
raise
@@ -93,7 +119,7 @@ def unix_getpass(prompt='Password: ', stream=None):
93119
return passwd
94120

95121

96-
def win_getpass(prompt='Password: ', stream=None):
122+
def win_getpass(prompt='Password: ', stream=None, mask=None):
97123
"""Prompt for password with echo off, using Windows getwch()."""
98124
if sys.stdin is not sys.__stdin__:
99125
return fallback_getpass(prompt, stream)
@@ -108,9 +134,16 @@ def win_getpass(prompt='Password: ', stream=None):
108134
if c == '\003':
109135
raise KeyboardInterrupt
110136
if c == '\b':
137+
if mask and pw:
138+
for _ in range(len(mask)):
139+
msvcrt.putwch('\b')
140+
msvcrt.putwch(' ')
141+
msvcrt.putwch('\b')
111142
pw = pw[:-1]
112143
else:
113144
pw = pw + c
145+
if mask:
146+
msvcrt.putwch(mask)
114147
msvcrt.putwch('\r')
115148
msvcrt.putwch('\n')
116149
return pw
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Added optional argument *mask* for :meth:`getpass.getpass`. Patch by
2+
Semyon Moroz.

0 commit comments

Comments
 (0)