[Python-checkins] r62454 - in python/trunk: Doc/library/getpass.rst Lib/getpass.py

gregory.p.smith python-checkins at python.org
Tue Apr 22 10:08:42 CEST 2008


Author: gregory.p.smith
Date: Tue Apr 22 10:08:41 2008
New Revision: 62454

Log:
Major improvements:
* Default to using /dev/tty for the password prompt and input before
  falling back to sys.stdin and sys.stderr.
* Use sys.stderr instead of sys.stdout.
* print the 'password may be echoed' warning to stream used to display
  the prompt rather than always sys.stderr.
* warn() with GetPassWarning when input may be echoed.


Modified:
   python/trunk/Doc/library/getpass.rst
   python/trunk/Lib/getpass.py

Modified: python/trunk/Doc/library/getpass.rst
==============================================================================
--- python/trunk/Doc/library/getpass.rst	(original)
+++ python/trunk/Doc/library/getpass.rst	Tue Apr 22 10:08:41 2008
@@ -14,13 +14,29 @@
 
    Prompt the user for a password without echoing.  The user is prompted using the
    string *prompt*, which defaults to ``'Password: '``. On Unix, the prompt is
-   written to the file-like object *stream*, which defaults to ``sys.stdout`` (this
-   argument is ignored on Windows).
+   written to the file-like object *stream*.  *stream* defaults to the
+   controlling terminal (/dev/tty) or if that is unavailable to ``sys.stderr``
+   (this argument is ignored on Windows).
+
+   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`.
 
    Availability: Macintosh, Unix, Windows.
 
    .. versionchanged:: 2.5
       The *stream* parameter was added.
+   .. versionchanged:: 2.6
+      On Unix it defaults to using /dev/tty before falling back
+      to ``sys.stdin`` and ``sys.stderr``.
+      .. note::
+         If you call getpass from within idle, the input may be done in the
+         terminal you launched idle from rather than the idle window itself.
+
+
+.. exception:: GetPassWarning
+
+   A :exc:`UserWarning` subclass issued when password input may be echoed.
 
 
 .. function:: getuser()

Modified: python/trunk/Lib/getpass.py
==============================================================================
--- python/trunk/Lib/getpass.py	(original)
+++ python/trunk/Lib/getpass.py	Tue Apr 22 10:08:41 2008
@@ -1,7 +1,10 @@
 """Utilities to get a password and/or the current user name.
 
-getpass(prompt) - prompt for a password, with echo turned off
-getuser() - get the user name from the environment or password database
+getpass(prompt[, stream]) - Prompt for a password, with echo turned off.
+getuser() - Get the user name from the environment or password database.
+
+GetPassWarning - This UserWarning is issued when getpass() cannot prevent
+                 echoing of the password contents while reading.
 
 On Windows, the msvcrt module will be used.
 On the Mac EasyDialogs.AskPassword is used, if available.
@@ -10,38 +13,70 @@
 
 # Authors: Piers Lauder (original)
 #          Guido van Rossum (Windows support and cleanup)
+#          Gregory P. Smith (tty support & GetPassWarning)
 
-import sys
+import os, sys, warnings
 
-__all__ = ["getpass","getuser"]
+__all__ = ["getpass","getuser","GetPassWarning"]
 
-def unix_getpass(prompt='Password: ', stream=None):
-    """Prompt for a password, with echo turned off.
-    The prompt is written on stream, by default stdout.
 
-    Restore terminal settings at end.
-    """
-    if stream is None:
-        stream = sys.stdout
+class GetPassWarning(UserWarning): pass
 
-    if not sys.stdin.isatty():
-        print >>sys.stderr, "Warning: sys.stdin is not a tty."
-        return default_getpass(prompt)
 
-    try:
-        fd = sys.stdin.fileno()
-    except:
-        return default_getpass(prompt)
+def unix_getpass(prompt='Password: ', stream=None):
+    """Prompt for a password, with echo turned off.
 
-    old = termios.tcgetattr(fd)     # a copy to save
-    new = old[:]
+    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.
+    Returns:
+      The seKr3t input.
+    Raises:
+      EOFError: If our input tty or stdin was closed.
+      GetPassWarning: When we were unable to turn echo off on the input.
 
-    new[3] = new[3] & ~termios.ECHO # 3 == 'lflags'
+    Always restores terminal settings before returning.
+    """
+    fd = None
+    tty = None
     try:
-        termios.tcsetattr(fd, termios.TCSADRAIN, new)
-        passwd = _raw_input(prompt, stream)
-    finally:
-        termios.tcsetattr(fd, termios.TCSADRAIN, old)
+        # Always try reading and writing directly on the tty first.
+        fd = os.open('/dev/tty', os.O_RDWR|os.O_NOCTTY)
+        tty = os.fdopen(fd, 'w+', 1)
+        input = tty
+        if not stream:
+            stream = tty
+    except EnvironmentError, e:
+        # If that fails, see if stdin can be controlled.
+        try:
+            fd = sys.stdin.fileno()
+        except:
+            passwd = fallback_getpass(prompt, stream)
+        input = sys.stdin
+        if not stream:
+            stream = sys.stderr
+
+    if fd is not None:
+        passwd = None
+        try:
+            old = termios.tcgetattr(fd)     # a copy to save
+            new = old[:]
+            new[3] &= ~termios.ECHO  # 3 == 'lflags'
+            try:
+                termios.tcsetattr(fd, termios.TCSADRAIN, new)
+                passwd = _raw_input(prompt, stream, input=input)
+            finally:
+                termios.tcsetattr(fd, termios.TCSADRAIN, old)
+        except termios.error, e:
+            if passwd is not None:
+                # _raw_input succeeded.  The final tcsetattr failed.  Reraise
+                # instead of leaving the terminal in an unknown state.
+                raise
+            # We can't control the tty or stdin.  Give up and use normal IO.
+            # fallback_getpass() raises an appropriate warning.
+            del input, tty  # clean up unused file objects before blocking
+            passwd = fallback_getpass(prompt, stream)
 
     stream.write('\n')
     return passwd
@@ -50,7 +85,7 @@
 def win_getpass(prompt='Password: ', stream=None):
     """Prompt for password with echo off, using Windows getch()."""
     if sys.stdin is not sys.__stdin__:
-        return default_getpass(prompt, stream)
+        return fallback_getpass(prompt, stream)
     import msvcrt
     for c in prompt:
         msvcrt.putch(c)
@@ -70,20 +105,27 @@
     return pw
 
 
-def default_getpass(prompt='Password: ', stream=None):
-    print >>sys.stderr, "Warning: Problem with getpass. Passwords may be echoed."
+def fallback_getpass(prompt='Password: ', stream=None):
+    warnings.warn("Can not control echo on the terminal.", GetPassWarning,
+                  stacklevel=2)
+    if not stream:
+        stream = sys.stderr
+    print >>stream, "Warning: Password input may be echoed."
     return _raw_input(prompt, stream)
 
 
-def _raw_input(prompt="", stream=None):
+def _raw_input(prompt="", stream=None, input=None):
     # A raw_input() replacement that doesn't save the string in the
     # GNU readline history.
-    if stream is None:
-        stream = sys.stdout
+    if not stream:
+        stream = sys.stderr
+    if not input:
+        input = sys.stdin
     prompt = str(prompt)
     if prompt:
         stream.write(prompt)
-    line = sys.stdin.readline()
+        stream.flush()
+    line = input.readline()
     if not line:
         raise EOFError
     if line[-1] == '\n':
@@ -123,7 +165,7 @@
         try:
             from EasyDialogs import AskPassword
         except ImportError:
-            getpass = default_getpass
+            getpass = fallback_getpass
         else:
             getpass = AskPassword
     else:


More information about the Python-checkins mailing list