From 1e380130b82c6454d94b01c4700582b45a703183 Mon Sep 17 00:00:00 2001 From: aemous Date: Mon, 14 Apr 2025 10:14:35 -0400 Subject: [PATCH 1/2] Update JSON formatter to encode bytes as Base64 strings. --- .../next-release/bugfix-Formatter-25003.json | 5 +++ awscli/utils.py | 8 +++-- tests/unit/output/test_json_output.py | 31 +++++++++++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 .changes/next-release/bugfix-Formatter-25003.json diff --git a/.changes/next-release/bugfix-Formatter-25003.json b/.changes/next-release/bugfix-Formatter-25003.json new file mode 100644 index 000000000000..a1cd65c63421 --- /dev/null +++ b/.changes/next-release/bugfix-Formatter-25003.json @@ -0,0 +1,5 @@ +{ + "type": "bugfix", + "category": "Formatter", + "description": "Update JSON formatter to encode raw bytes as UTF-8." +} diff --git a/awscli/utils.py b/awscli/utils.py index 995b40473de0..68ed4415ca92 100644 --- a/awscli/utils.py +++ b/awscli/utils.py @@ -10,6 +10,7 @@ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. +import base64 import contextlib import csv import datetime @@ -185,11 +186,14 @@ def operation_uses_document_types(operation_model): def json_encoder(obj): - """JSON encoder that formats datetimes as ISO8601 format.""" + """JSON encoder that formats datetimes as ISO8601 format + and encodes bytes to UTF-8 Base64 string.""" if isinstance(obj, datetime.datetime): return obj.isoformat() + elif isinstance(obj, bytes): + return base64.b64encode(obj).decode("utf-8") else: - return obj + raise TypeError('Encountered unrecognized type in JSON encoder.') @contextlib.contextmanager diff --git a/tests/unit/output/test_json_output.py b/tests/unit/output/test_json_output.py index 826380ec3453..0753d7f1bcf9 100644 --- a/tests/unit/output/test_json_output.py +++ b/tests/unit/output/test_json_output.py @@ -11,6 +11,14 @@ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. +import base64 + +import sys + +import contextlib + +import io + from botocore.compat import json import platform from awscli.formatter import JSONFormatter @@ -127,3 +135,26 @@ def test_fully_buffered_handles_io_error(self): # we still should have called the flush() on the # stream. fake_closed_stream.flush.assert_called_with() + + +class TestBinaryData(unittest.TestCase): + def test_binary_data_gets_base64_encoded(self): + args = mock.Mock(query=None) + raw_bytes = b'foo' + response = {'BinaryValue': raw_bytes} + stdout_b = io.BytesIO() + stdout = io.TextIOWrapper(stdout_b, newline='\n') + formatter = JSONFormatter(args) + + with contextlib.redirect_stdout(stdout): + formatter('command-name', response, sys.stdout) + stdout.flush() + + assert ( + stdout_b.getvalue() + == ( + '{\n' + f' "BinaryValue": "{base64.b64encode(raw_bytes).decode("utf-8")}"\n' + '}\n' + ).encode() + ) From 7d69ab8b8494268d9df668fe487c1429454f0e08 Mon Sep 17 00:00:00 2001 From: aemous Date: Mon, 14 Apr 2025 10:23:59 -0400 Subject: [PATCH 2/2] Formatting. --- tests/unit/output/test_json_output.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/tests/unit/output/test_json_output.py b/tests/unit/output/test_json_output.py index 0753d7f1bcf9..8e3f91f46bc4 100644 --- a/tests/unit/output/test_json_output.py +++ b/tests/unit/output/test_json_output.py @@ -12,13 +12,9 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. import base64 - -import sys - import contextlib - import io - +import sys from botocore.compat import json import platform from awscli.formatter import JSONFormatter @@ -151,10 +147,10 @@ def test_binary_data_gets_base64_encoded(self): stdout.flush() assert ( - stdout_b.getvalue() - == ( - '{\n' - f' "BinaryValue": "{base64.b64encode(raw_bytes).decode("utf-8")}"\n' - '}\n' - ).encode() + stdout_b.getvalue() + == ( + '{\n' + f' "BinaryValue": "{base64.b64encode(raw_bytes).decode("utf-8")}"\n' + '}\n' + ).encode() )