Skip to content

Commit ee40290

Browse files
committed
Force unicode before interaction
Handle binary edge case and improve tests
1 parent 9d214c5 commit ee40290

File tree

3 files changed

+61
-1
lines changed

3 files changed

+61
-1
lines changed

awsshell/utils.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
"""Utility module for misc aws shell functions."""
22
from __future__ import print_function
33
import os
4+
import six
45
import contextlib
56
import tempfile
67
import uuid
8+
import logging
79

810
import awscli
911

1012
from awsshell.compat import HTMLParser
1113

1214

15+
LOG = logging.getLogger(__name__)
1316
AWSCLI_VERSION = awscli.__version__
1417

1518

@@ -118,3 +121,24 @@ def file_contents(self, filename, binary=False):
118121

119122
def file_exists(self, filename):
120123
return filename in self._file_mapping
124+
125+
126+
def _attempt_decode(string, encoding):
127+
try:
128+
return string.decode(encoding)
129+
except UnicodeError as error:
130+
LOG.debug('Failed to decode: %s', error)
131+
return string
132+
133+
134+
def force_unicode(obj, encoding='utf8'):
135+
"""Recursively search lists and dicts for strings to encode as unicode."""
136+
if isinstance(obj, dict):
137+
for key in obj:
138+
obj[key] = force_unicode(obj[key])
139+
elif isinstance(obj, list):
140+
obj = [force_unicode(val) for val in obj]
141+
elif isinstance(obj, six.string_types):
142+
if not isinstance(obj, six.text_type):
143+
obj = _attempt_decode(obj, encoding)
144+
return obj

awsshell/wizard.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from botocore.exceptions import BotoCoreError, ClientError
99

1010
from awsshell.resource import index
11+
from awsshell.utils import force_unicode
1112
from awsshell.selectmenu import select_prompt
1213
from awsshell.interaction import InteractionLoader, InteractionException
1314

@@ -275,13 +276,14 @@ def _handle_retrieval(self):
275276
return data
276277

277278
def _handle_interaction(self, data):
279+
278280
# if no interaction step, just forward data
279281
if self.interaction is None:
280282
return data
281283
else:
282284
creator = self._interaction_loader.create
283285
interaction = creator(self.interaction, self.prompt)
284-
return interaction.execute(data)
286+
return interaction.execute(force_unicode(data))
285287

286288
def _handle_resolution(self, data):
287289
if self.resolution:

tests/unit/test_utils.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22
import os
33
import tempfile
44
import shutil
5+
import six
6+
import pytest
57

68
from awsshell.utils import FSLayer
79
from awsshell.utils import InMemoryFSLayer
810
from awsshell.utils import FileReadError
911
from awsshell.utils import temporary_file
12+
from awsshell.utils import force_unicode
1013

1114

1215
class TestFSLayer(unittest.TestCase):
@@ -93,3 +96,34 @@ def test_can_open_in_read(self):
9396
f.seek(0)
9497
assert f.read() == "foobar"
9598
self.assertFalse(os.path.isfile(filename))
99+
100+
101+
strings = ['', u'', 'some text', u'some text', 'yu\u3086', u'yu\u3086']
102+
103+
104+
@pytest.mark.parametrize('text', strings)
105+
def test_force_unicode(text):
106+
unicode_text = force_unicode(text)
107+
assert unicode_text == text
108+
assert isinstance(unicode_text, six.text_type)
109+
110+
111+
def test_force_unicode_binary():
112+
# should fail decoding and remain unchanged
113+
blob = '\x81'
114+
result = force_unicode(blob)
115+
assert result is blob
116+
117+
118+
def test_force_unicode_recursion():
119+
obj = {
120+
'a': ['string'],
121+
'b': {'str': 'yu\u3086'},
122+
'c': '\x81',
123+
}
124+
125+
clean_obj = force_unicode(obj)
126+
assert isinstance(clean_obj['a'][0], six.text_type)
127+
assert isinstance(clean_obj['b']['str'], six.text_type)
128+
assert clean_obj['c'] is obj['c']
129+
assert obj == clean_obj

0 commit comments

Comments
 (0)