Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion Doc/library/getpass.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

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

.. function:: getpass(prompt='Password: ', stream=None)
.. function:: getpass(prompt='Password: ', stream=None, *, echochar=None)

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

The *echochar* argument controls how user input is displayed while typing.
If *echochar* is ``None`` (default), input remains hidden. If *echochar* is
a string, each typed character is replaced with the given string.
For example, ``echochar='*'`` 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
issuing a :exc:`GetPassWarning`.
Expand Down
10 changes: 10 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,16 @@ getopt
* Add support for returning intermixed options and non-option arguments in order.
(Contributed by Serhiy Storchaka in :gh:`126390`.)


getpass
-------

* Support keyboard feedback by :func:`getpass.getpass` via the keyword-only
optional argument ``echochar``. Placeholder characters are rendered whenever
a character is entered, and removed when a character is deleted.
(Contributed by Semyon Moroz in :gh:`77065`.)


http
----

Expand Down
43 changes: 39 additions & 4 deletions Lib/getpass.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Utilities to get a password and/or the current user name.

getpass(prompt[, stream]) - Prompt for a password, with echo turned off.
getpass(prompt[, stream[, echochar]]) - Prompt for a password, with echo
turned off and optional keyboard feedback.
getuser() - Get the user name from the environment or password database.

GetPassWarning - This UserWarning is issued when getpass() cannot prevent
Expand All @@ -25,13 +26,15 @@
class GetPassWarning(UserWarning): pass


def unix_getpass(prompt='Password: ', stream=None):
def unix_getpass(prompt='Password: ', stream=None, *, echochar=None):
"""Prompt for a password, with echo turned off.

Args:
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.
echochar: A string used to mask input (e.g., '*'). If None, input is
hidden.
Returns:
The seKr3t input.
Raises:
Expand Down Expand Up @@ -68,12 +71,37 @@ def unix_getpass(prompt='Password: ', stream=None):
old = termios.tcgetattr(fd) # a copy to save
new = old[:]
new[3] &= ~termios.ECHO # 3 == 'lflags'
if echochar:
new[3] &= ~termios.ICANON
tcsetattr_flags = termios.TCSAFLUSH
if hasattr(termios, 'TCSASOFT'):
tcsetattr_flags |= termios.TCSASOFT
try:
termios.tcsetattr(fd, tcsetattr_flags, new)
passwd = _raw_input(prompt, stream, input=input)
if not echochar:
passwd = _raw_input(prompt, stream, input=input)
stream.write('\n')
return passwd

passwd = ""
stream.write(prompt)
stream.flush()
while True:
char = input.read(1)
if char == '\n' or char == '\r':
break
if char == '\x03':
raise KeyboardInterrupt
if char == '\x7f' or char == '\b':
if echochar and passwd:
stream.write("\b \b" * len(echochar))
stream.flush()
passwd = passwd[:-1]
else:
passwd += char
if echochar:
stream.write(echochar)
stream.flush()
finally:
termios.tcsetattr(fd, tcsetattr_flags, old)
stream.flush() # issue7208
Expand All @@ -93,7 +121,7 @@ def unix_getpass(prompt='Password: ', stream=None):
return passwd


def win_getpass(prompt='Password: ', stream=None):
def win_getpass(prompt='Password: ', stream=None, *, echochar=None):
"""Prompt for password with echo off, using Windows getwch()."""
if sys.stdin is not sys.__stdin__:
return fallback_getpass(prompt, stream)
Expand All @@ -108,9 +136,16 @@ def win_getpass(prompt='Password: ', stream=None):
if c == '\003':
raise KeyboardInterrupt
if c == '\b':
if echochar and pw:
for _ in echochar:
msvcrt.putwch('\b')
msvcrt.putwch(' ')
msvcrt.putwch('\b')
pw = pw[:-1]
else:
pw = pw + c
if echochar:
msvcrt.putwch(echochar)
msvcrt.putwch('\r')
msvcrt.putwch('\n')
return pw
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add keyword-only optional argument *echochar* for :meth:`getpass.getpass`.
Patch by Semyon Moroz.
Loading