Repository: aws/aws-cli

Issue:
Not properly outputting in MINGW
System: Windows 7 64bit

steps to reproduce:
1. Open mingw64 (git bash) 
2. Run `aws configure`
3. Observe no output
4. Hit enter 4 times
5. Observe output

It looks as if AWS CLI is thinking it is outputting text however it actually isn't.

![image](https://cloud.githubusercontent.com/assets/855184/14644114/86f4eb30-0616-11e6-835c-21cb94db9eb4.png)



Discussion:
Looks like the issue here is that MINGW doesn't refresh until stdout is flushed explicitly, whereas most terminal emulators will opt to display text as soon as possible. This shouldn't be too hard to fix.

In the meantime, you should be able to work around the issue by providing the environment variable `PYTHONUNBUFFERED=1`.

@JordonPhillips I put in my environment variables `PYTHONUNBUFFERED=1` and even tried exporting it `export PYTHONUNBUFFERED=1`. Checked with `echo $PYTHONUNBUFFERED` and got output of `1`. 

Still same issue. Note that my mingw64 was installed via GIT as the Git Bash terminal.


Resolving patch:
diff --git a/awscli/compat.py b/awscli/compat.py
--- a/awscli/compat.py
+++ b/awscli/compat.py
@@ -105,3 +105,20 @@ def compat_open(filename, mode='r', encoding=None):
         if 'b' not in mode:
             encoding = locale.getpreferredencoding()
         return io.open(filename, mode, encoding=encoding)
+
+
+def compat_input(prompt):
+    """
+    Cygwin's pty's are based on pipes. Therefore, when it interacts with a Win32
+    program (such as Win32 python), what that program sees is a pipe instead of
+    a console. This is important because python buffers pipes, and so on a
+    pty-based terminal, text will not necessarily appear immediately. In most
+    cases, this isn't a big deal. But when we're doing an interactive prompt,
+    the result is that the prompts won't display until we fill the buffer. Since
+    raw_input does not flush the prompt, we need to manually write and flush it.
+
+    See https://github.com/mintty/mintty/issues/56 for more details.
+    """
+    sys.stdout.write(prompt)
+    sys.stdout.flush()
+    return raw_input()
diff --git a/awscli/customizations/configure/configure.py b/awscli/customizations/configure/configure.py
--- a/awscli/customizations/configure/configure.py
+++ b/awscli/customizations/configure/configure.py
@@ -15,7 +15,7 @@
 
 from botocore.exceptions import ProfileNotFound
 
-from awscli.compat import raw_input
+from awscli.compat import compat_input
 from awscli.customizations.commands import BasicCommand
 from awscli.customizations.configure.addmodel import AddModelCommand
 from awscli.customizations.configure.set import ConfigureSetCommand
@@ -39,7 +39,7 @@ class InteractivePrompter(object):
     def get_value(self, current_value, config_name, prompt_text=''):
         if config_name in ('aws_access_key_id', 'aws_secret_access_key'):
             current_value = mask_value(current_value)
-        response = raw_input("%s [%s]: " % (prompt_text, current_value))
+        response = compat_input("%s [%s]: " % (prompt_text, current_value))
         if not response:
             # If the user hits enter, we return a value of None
             # instead of an empty string.  That way we can determine


Test patch:
diff --git a/tests/unit/customizations/configure/test_configure.py b/tests/unit/customizations/configure/test_configure.py
--- a/tests/unit/customizations/configure/test_configure.py
+++ b/tests/unit/customizations/configure/test_configure.py
@@ -15,6 +15,7 @@
 
 from awscli.customizations.configure import configure, ConfigValue, NOT_SET
 from awscli.testutils import unittest
+from awscli.compat import six
 
 from . import FakeSession
 
@@ -132,12 +133,15 @@ def test_session_says_profile_does_not_exist(self):
 class TestInteractivePrompter(unittest.TestCase):
 
     def setUp(self):
-        self.patch = mock.patch(
-            'awscli.customizations.configure.configure.raw_input')
-        self.mock_raw_input = self.patch.start()
+        self.input_patch = mock.patch('awscli.compat.raw_input')
+        self.mock_raw_input = self.input_patch.start()
+        self.stdout = six.StringIO()
+        self.stdout_patch = mock.patch('sys.stdout', self.stdout)
+        self.stdout_patch.start()
 
     def tearDown(self):
-        self.patch.stop()
+        self.input_patch.stop()
+        self.stdout_patch.stop()
 
     def test_access_key_is_masked(self):
         self.mock_raw_input.return_value = 'foo'
@@ -148,7 +152,7 @@ def test_access_key_is_masked(self):
         # First we should return the value from raw_input.
         self.assertEqual(response, 'foo')
         # We should also not display the entire access key.
-        prompt_text = self.mock_raw_input.call_args[0][0]
+        prompt_text = self.stdout.getvalue()
         self.assertNotIn('myaccesskey', prompt_text)
         self.assertRegexpMatches(prompt_text, r'\[\*\*\*\*.*\]')
 
@@ -160,7 +164,7 @@ def test_access_key_not_masked_when_none(self):
             prompt_text='Access key')
         # First we should return the value from raw_input.
         self.assertEqual(response, 'foo')
-        prompt_text = self.mock_raw_input.call_args[0][0]
+        prompt_text = self.stdout.getvalue()
         self.assertIn('[None]', prompt_text)
 
     def test_secret_key_is_masked(self):
@@ -170,7 +174,7 @@ def test_secret_key_is_masked(self):
             config_name='aws_secret_access_key',
             prompt_text='Secret Key')
         # We should also not display the entire secret key.
-        prompt_text = self.mock_raw_input.call_args[0][0]
+        prompt_text = self.stdout.getvalue()
         self.assertNotIn('mysupersecretkey', prompt_text)
         self.assertRegexpMatches(prompt_text, r'\[\*\*\*\*.*\]')
 
@@ -180,7 +184,7 @@ def test_non_secret_keys_are_not_masked(self):
             current_value='mycurrentvalue', config_name='not_a_secret_key',
             prompt_text='Enter value')
         # We should also not display the entire secret key.
-        prompt_text = self.mock_raw_input.call_args[0][0]
+        prompt_text = self.stdout.getvalue()
         self.assertIn('mycurrentvalue', prompt_text)
         self.assertRegexpMatches(prompt_text, r'\[mycurrentvalue\]')
 
@@ -196,6 +200,27 @@ def test_user_hits_enter_returns_none(self):
         # was no input.
         self.assertIsNone(response)
 
+    def test_compat_input_flushes_after_each_prompt(self):
+        # Clear out the default patch
+        self.stdout_patch.stop()
+
+        # Create a mock stdout to record flush calls and replace stdout_patch
+        self.stdout = mock.Mock()
+        self.stdout_patch = mock.patch('sys.stdout', self.stdout)
+        self.stdout_patch.start()
+
+        # Make sure flush called at least once
+        prompter = configure.InteractivePrompter()
+        prompter.get_value(current_value='foo', config_name='bar',
+                           prompt_text='baz')
+        self.assertTrue(self.stdout.flush.called)
+
+        # Make sure flush is called after *every* prompt
+        self.stdout.reset_mock()
+        prompter.get_value(current_value='foo2', config_name='bar2',
+                           prompt_text='baz2')
+        self.assertTrue(self.stdout.flush.called)
+
 
 class TestConfigValueMasking(unittest.TestCase):