続 PYTHON 2.7 の GETPASS.PY

前の記事では Windows でしか試さなかったのだけれど、気になったので Unix 側(_raw_inputを通るケースのみ)でもやってみたら、一つ措置が抜けていた。

 1 from __future__ import unicode_literals
 2 from __future__ import print_function
 3 
 4 # Authors: Piers Lauder (original)
 5 #          Guido van Rossum (Windows support and cleanup)
 6 #          Gregory P. Smith (tty support & GetPassWarning)
 7 
 8 import os, sys, warnings
 9 if sys.version < '3':
10     str = unicode

ので、全体ではこうなる:

py333_getpass.py
  1 """Utilities to get a password and/or the current user name.
  2 
  3 getpass(prompt[, stream]) - Prompt for a password, with echo turned off.
  4 getuser() - Get the user name from the environment or password database.
  5 
  6 GetPassWarning - This UserWarning is issued when getpass() cannot prevent
  7                  echoing of the password contents while reading.
  8 
  9 On Windows, the msvcrt module will be used.
 10 On the Mac EasyDialogs.AskPassword is used, if available.
 11 
 12 """
 13 from __future__ import unicode_literals
 14 from __future__ import print_function
 15 
 16 # Authors: Piers Lauder (original)
 17 #          Guido van Rossum (Windows support and cleanup)
 18 #          Gregory P. Smith (tty support & GetPassWarning)
 19 
 20 import os, sys, warnings
 21 if sys.version < '3':
 22     str = unicode
 23 
 24 __all__ = ["getpass","getuser","GetPassWarning"]
 25 
 26 
 27 class GetPassWarning(UserWarning): pass
 28 
 29 
 30 def unix_getpass(prompt='Password: ', stream=None):
 31     """Prompt for a password, with echo turned off.
 32 
 33     Args:
 34       prompt: Written on stream to ask for the input.  Default: 'Password: '
 35       stream: A writable file object to display the prompt.  Defaults to
 36               the tty.  If no tty is available defaults to sys.stderr.
 37     Returns:
 38       The seKr3t input.
 39     Raises:
 40       EOFError: If our input tty or stdin was closed.
 41       GetPassWarning: When we were unable to turn echo off on the input.
 42 
 43     Always restores terminal settings before returning.
 44     """
 45     fd = None
 46     tty = None
 47     try:
 48         # Always try reading and writing directly on the tty first.
 49         fd = os.open('/dev/tty', os.O_RDWR|os.O_NOCTTY)
 50         tty = os.fdopen(fd, 'w+', 1)
 51         input = tty
 52         if not stream:
 53             stream = tty
 54     except EnvironmentError as e:
 55         # If that fails, see if stdin can be controlled.
 56         try:
 57             fd = sys.stdin.fileno()
 58         except (AttributeError, ValueError):
 59             passwd = fallback_getpass(prompt, stream)
 60         input = sys.stdin
 61         if not stream:
 62             stream = sys.stderr
 63 
 64     if fd is not None:
 65         passwd = None
 66         try:
 67             old = termios.tcgetattr(fd)     # a copy to save
 68             new = old[:]
 69             new[3] &= ~termios.ECHO  # 3 == 'lflags'
 70             tcsetattr_flags = termios.TCSAFLUSH
 71             if hasattr(termios, 'TCSASOFT'):
 72                 tcsetattr_flags |= termios.TCSASOFT
 73             try:
 74                 termios.tcsetattr(fd, tcsetattr_flags, new)
 75                 passwd = _raw_input(prompt, stream, input=input)
 76             finally:
 77                 termios.tcsetattr(fd, tcsetattr_flags, old)
 78                 stream.flush()  # issue7208
 79         except termios.error:
 80             if passwd is not None:
 81                 # _raw_input succeeded.  The final tcsetattr failed.  Reraise
 82                 # instead of leaving the terminal in an unknown state.
 83                 raise
 84             # We can't control the tty or stdin.  Give up and use normal IO.
 85             # fallback_getpass() raises an appropriate warning.
 86             del input, tty  # clean up unused file objects before blocking
 87             passwd = fallback_getpass(prompt, stream)
 88 
 89     stream.write('\n')
 90     return passwd
 91 
 92 
 93 def win_getpass(prompt='Password: ', stream=None):
 94     """Prompt for password with echo off, using Windows getch()."""
 95     if sys.stdin is not sys.__stdin__:
 96         return fallback_getpass(prompt, stream)
 97     import msvcrt
 98     for c in prompt:
 99         msvcrt.putwch(c)
100     pw = ""
101     while 1:
102         c = msvcrt.getwch()
103         if c == '\r' or c == '\n':
104             break
105         if c == '\003':
106             raise KeyboardInterrupt
107         if c == '\b':
108             pw = pw[:-1]
109         else:
110             pw = pw + c
111     msvcrt.putwch('\r')
112     msvcrt.putwch('\n')
113     return pw
114 
115 
116 def fallback_getpass(prompt='Password: ', stream=None):
117     warnings.warn("Can not control echo on the terminal.", GetPassWarning,
118                   stacklevel=2)
119     if not stream:
120         stream = sys.stderr
121     print("Warning: Password input may be echoed.", file=stream)
122     return _raw_input(prompt, stream)
123 
124 
125 def _raw_input(prompt="", stream=None, input=None):
126     # This doesn't save the string in the GNU readline history.
127     if not stream:
128         stream = sys.stderr
129     if not input:
130         input = sys.stdin
131     prompt = str(prompt)
132     if prompt:
133         stream.write(prompt)
134         stream.flush()
135     # NOTE: The Python C API calls flockfile() (and unlock) during readline.
136     line = input.readline()
137     if not line:
138         raise EOFError
139     if line[-1] == '\n':
140         line = line[:-1]
141     return line
142 
143 
144 def getuser():
145     """Get the username from the environment or password database.
146 
147     First try various environment variables, then the password
148     database.  This works on Windows as long as USERNAME is set.
149 
150     """
151 
152     for name in ('LOGNAME', 'USER', 'LNAME', 'USERNAME'):
153         user = os.environ.get(name)
154         if user:
155             return user
156 
157     # If this fails, the exception will "explain" why
158     import pwd
159     return pwd.getpwuid(os.getuid())[0]
160 
161 # Bind the name getpass to the appropriate function
162 try:
163     import termios
164     # it's possible there is an incompatible termios from the
165     # McMillan Installer, make sure we have a UNIX-compatible termios
166     termios.tcgetattr, termios.tcsetattr
167 except (ImportError, AttributeError):
168     try:
169         import msvcrt
170     except ImportError:
171         getpass = fallback_getpass
172     else:
173         getpass = win_getpass
174 else:
175     getpass = unix_getpass

ただ、クライアントコード側は一工夫必要(Windowsでも同じコードでOK)。

 1 # -*- coding: utf-8 -*-
 2 from __future__ import unicode_literals
 3 import sys
 4 import codecs
 5 
 6 import py333_getpass
 7 s = py333_getpass.getpass("pw? ")
 8 print(s)
 9 s = py333_getpass.getpass('ぱわ? ', stream=codecs.getwriter('utf-8')(sys.stdout))
10 print(s)