Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitbf8bbe9

Browse files
donBarbospicnixz
andauthored
gh-77065: Add optional keyword-only argumentecho_char forgetpass.getpass (#130496)
Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
1 parent53e6d76 commitbf8bbe9

File tree

5 files changed

+119
-6
lines changed

5 files changed

+119
-6
lines changed

‎Doc/library/getpass.rst

Lines changed: 10 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, *, echo_char=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,12 @@ 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 *echo_char* argument controls how user input is displayed while typing.
29+
If *echo_char* is ``None`` (default), input remains hidden. Otherwise,
30+
*echo_char* must be a printable ASCII string and each typed character
31+
is replaced by it. For example, ``echo_char='*'`` will display
32+
asterisks instead of the actual input.
33+
2834
If echo free input is unavailable getpass() falls back to printing
2935
a warning message to *stream* and reading from ``sys.stdin`` and
3036
issuing a:exc:`GetPassWarning`.
@@ -33,6 +39,9 @@ The :mod:`getpass` module provides two functions:
3339
If you call getpass from within IDLE, the input may be done in the
3440
terminal you launched IDLE from rather than the idle window itself.
3541

42+
..versionchanged::next
43+
Added the *echo_char* parameter for keyboard feedback.
44+
3645
..exception::GetPassWarning
3746

3847
A:exc:`UserWarning` subclass issued when password input may be echoed.

‎Doc/whatsnew/3.14.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,6 +1195,15 @@ getopt
11951195
(Contributed by Serhiy Storchaka in:gh:`126390`.)
11961196

11971197

1198+
getpass
1199+
-------
1200+
1201+
* Support keyboard feedback by:func:`getpass.getpass` via the keyword-only
1202+
optional argument ``echo_char``. Placeholder characters are rendered whenever
1203+
a character is entered, and removed when a character is deleted.
1204+
(Contributed by Semyon Moroz in:gh:`77065`.)
1205+
1206+
11981207
graphlib
11991208
--------
12001209

‎Lib/getpass.py

Lines changed: 59 additions & 5 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[, echo_char]]) - Prompt for a password, with echo
4+
turned off and optional keyboard feedback.
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,15 @@
2526
classGetPassWarning(UserWarning):pass
2627

2728

28-
defunix_getpass(prompt='Password: ',stream=None):
29+
defunix_getpass(prompt='Password: ',stream=None,*,echo_char=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+
echo_char: A string used to mask input (e.g., '*'). If None, input is
37+
hidden.
3538
Returns:
3639
The seKr3t input.
3740
Raises:
@@ -40,6 +43,8 @@ def unix_getpass(prompt='Password: ', stream=None):
4043
4144
Always restores terminal settings before returning.
4245
"""
46+
_check_echo_char(echo_char)
47+
4348
passwd=None
4449
withcontextlib.ExitStack()asstack:
4550
try:
@@ -68,12 +73,16 @@ def unix_getpass(prompt='Password: ', stream=None):
6873
old=termios.tcgetattr(fd)# a copy to save
6974
new=old[:]
7075
new[3]&=~termios.ECHO# 3 == 'lflags'
76+
ifecho_char:
77+
new[3]&=~termios.ICANON
7178
tcsetattr_flags=termios.TCSAFLUSH
7279
ifhasattr(termios,'TCSASOFT'):
7380
tcsetattr_flags|=termios.TCSASOFT
7481
try:
7582
termios.tcsetattr(fd,tcsetattr_flags,new)
76-
passwd=_raw_input(prompt,stream,input=input)
83+
passwd=_raw_input(prompt,stream,input=input,
84+
echo_char=echo_char)
85+
7786
finally:
7887
termios.tcsetattr(fd,tcsetattr_flags,old)
7988
stream.flush()# issue7208
@@ -93,10 +102,11 @@ def unix_getpass(prompt='Password: ', stream=None):
93102
returnpasswd
94103

95104

96-
defwin_getpass(prompt='Password: ',stream=None):
105+
defwin_getpass(prompt='Password: ',stream=None,*,echo_char=None):
97106
"""Prompt for password with echo off, using Windows getwch()."""
98107
ifsys.stdinisnotsys.__stdin__:
99108
returnfallback_getpass(prompt,stream)
109+
_check_echo_char(echo_char)
100110

101111
forcinprompt:
102112
msvcrt.putwch(c)
@@ -108,9 +118,15 @@ def win_getpass(prompt='Password: ', stream=None):
108118
ifc=='\003':
109119
raiseKeyboardInterrupt
110120
ifc=='\b':
121+
ifecho_charandpw:
122+
msvcrt.putch('\b')
123+
msvcrt.putch(' ')
124+
msvcrt.putch('\b')
111125
pw=pw[:-1]
112126
else:
113127
pw=pw+c
128+
ifecho_char:
129+
msvcrt.putwch(echo_char)
114130
msvcrt.putwch('\r')
115131
msvcrt.putwch('\n')
116132
returnpw
@@ -126,7 +142,14 @@ def fallback_getpass(prompt='Password: ', stream=None):
126142
return_raw_input(prompt,stream)
127143

128144

129-
def_raw_input(prompt="",stream=None,input=None):
145+
def_check_echo_char(echo_char):
146+
# ASCII excluding control characters
147+
ifecho_charandnot (echo_char.isprintable()andecho_char.isascii()):
148+
raiseValueError("'echo_char' must be a printable ASCII string, "
149+
f"got:{echo_char!r}")
150+
151+
152+
def_raw_input(prompt="",stream=None,input=None,echo_char=None):
130153
# This doesn't save the string in the GNU readline history.
131154
ifnotstream:
132155
stream=sys.stderr
@@ -143,6 +166,8 @@ def _raw_input(prompt="", stream=None, input=None):
143166
stream.write(prompt)
144167
stream.flush()
145168
# NOTE: The Python C API calls flockfile() (and unlock) during readline.
169+
ifecho_char:
170+
return_readline_with_echo_char(stream,input,echo_char)
146171
line=input.readline()
147172
ifnotline:
148173
raiseEOFError
@@ -151,6 +176,35 @@ def _raw_input(prompt="", stream=None, input=None):
151176
returnline
152177

153178

179+
def_readline_with_echo_char(stream,input,echo_char):
180+
passwd=""
181+
eof_pressed=False
182+
whileTrue:
183+
char=input.read(1)
184+
ifchar=='\n'orchar=='\r':
185+
break
186+
elifchar=='\x03':
187+
raiseKeyboardInterrupt
188+
elifchar=='\x7f'orchar=='\b':
189+
ifpasswd:
190+
stream.write("\b\b")
191+
stream.flush()
192+
passwd=passwd[:-1]
193+
elifchar=='\x04':
194+
ifeof_pressed:
195+
break
196+
else:
197+
eof_pressed=True
198+
elifchar=='\x00':
199+
continue
200+
else:
201+
passwd+=char
202+
stream.write(echo_char)
203+
stream.flush()
204+
eof_pressed=False
205+
returnpasswd
206+
207+
154208
defgetuser():
155209
"""Get the username from the environment or password database.
156210

‎Lib/test/test_getpass.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,45 @@ def test_falls_back_to_stdin(self):
161161
self.assertIn('Warning',stderr.getvalue())
162162
self.assertIn('Password:',stderr.getvalue())
163163

164+
deftest_echo_char_replaces_input_with_asterisks(self):
165+
mock_result='*************'
166+
withmock.patch('os.open')asos_open, \
167+
mock.patch('io.FileIO'), \
168+
mock.patch('io.TextIOWrapper')astextio, \
169+
mock.patch('termios.tcgetattr'), \
170+
mock.patch('termios.tcsetattr'), \
171+
mock.patch('getpass._raw_input')asmock_input:
172+
os_open.return_value=3
173+
mock_input.return_value=mock_result
174+
175+
result=getpass.unix_getpass(echo_char='*')
176+
mock_input.assert_called_once_with('Password: ',textio(),
177+
input=textio(),echo_char='*')
178+
self.assertEqual(result,mock_result)
179+
180+
deftest_raw_input_with_echo_char(self):
181+
passwd='my1pa$$word!'
182+
mock_input=StringIO(f'{passwd}\n')
183+
mock_output=StringIO()
184+
withmock.patch('sys.stdin',mock_input), \
185+
mock.patch('sys.stdout',mock_output):
186+
result=getpass._raw_input('Password: ',mock_output,mock_input,
187+
'*')
188+
self.assertEqual(result,passwd)
189+
self.assertEqual('Password: ************',mock_output.getvalue())
190+
191+
deftest_control_chars_with_echo_char(self):
192+
passwd='pass\twd\b'
193+
expect_result='pass\tw'
194+
mock_input=StringIO(f'{passwd}\n')
195+
mock_output=StringIO()
196+
withmock.patch('sys.stdin',mock_input), \
197+
mock.patch('sys.stdout',mock_output):
198+
result=getpass._raw_input('Password: ',mock_output,mock_input,
199+
'*')
200+
self.assertEqual(result,expect_result)
201+
self.assertEqual('Password: *******\x08\x08',mock_output.getvalue())
202+
164203

165204
if__name__=="__main__":
166205
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add keyword-only optional argument *echo_char* for:meth:`getpass.getpass`
2+
for optional visual keyboard feedback support. Patch by Semyon Moroz.

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp