From 14f2a2d68f79c6f61067b472a41ded07cff3967d Mon Sep 17 00:00:00 2001 From: akshara08 Date: Tue, 18 Apr 2023 10:46:30 -0700 Subject: [PATCH 1/3] Support moto by allowing non-awaitable content and tests --- aiobotocore/endpoint.py | 11 ++++- tests/test_response.py | 89 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 2 deletions(-) diff --git a/aiobotocore/endpoint.py b/aiobotocore/endpoint.py index 81965b54..d77cde69 100644 --- a/aiobotocore/endpoint.py +++ b/aiobotocore/endpoint.py @@ -1,4 +1,5 @@ import asyncio +from inspect import isawaitable from botocore.endpoint import ( DEFAULT_TIMEOUT, @@ -53,14 +54,20 @@ async def convert_to_response_dict(http_response, operation_model): }, } if response_dict['status_code'] >= 300: - response_dict['body'] = await http_response.content + if isawaitable(http_response.content): + response_dict['body'] = await http_response.content + else: + response_dict['body'] = http_response.content elif operation_model.has_event_stream_output: response_dict['body'] = http_response.raw elif operation_model.has_streaming_output: length = response_dict['headers'].get('content-length') response_dict['body'] = StreamingBody(http_response.raw, length) else: - response_dict['body'] = await http_response.content + if isawaitable(http_response.content): + response_dict['body'] = await http_response.content + else: + response_dict['body'] = http_response.content return response_dict diff --git a/tests/test_response.py b/tests/test_response.py index 5a3bae26..4fa5bfe1 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -1,9 +1,13 @@ import io +from unittest.mock import patch import pytest +from botocore.awsrequest import AWSResponse from botocore.exceptions import IncompleteReadError +from moto.core.botocore_stubber import MockRawResponse from aiobotocore import response +from aiobotocore.endpoint import convert_to_response_dict # https://github.com/boto/botocore/blob/develop/tests/unit/test_response.py @@ -187,3 +191,88 @@ async def test_streaming_line_empty_body(): content_length=0, ) await assert_lines(stream.iter_lines(), []) + + +@pytest.mark.moto +@pytest.mark.asyncio +async def test_convert_to_response_dict_non_awaitable_ok(): + class MockOperationalModel: + def name(self): + return 'test' + + @property + def has_streaming_output(self): + return False + + @property + def has_event_stream_output(self): + return False + + url = 'https://testbucket.s3.amazonaws.com/' + status = 200 + headers = { + 'x-amzn-requestid': '0n32brAiyTp2t9rdLgFtTmvlh4ZoPpIf62mizOK0W9Nt9lZr5XRL' + } + body = ( + b'' + b'testbucket' + b'' + ) + + raw = MockRawResponse(body) + encoded_headers = [ + ( + str(header).encode(encoding='utf-8'), + str(value).encode(encoding='utf-8'), + ) + for header, value in headers.items() + ] + raw.raw_headers = encoded_headers + + operational_model = MockOperationalModel() + response = AWSResponse(url, status, headers, raw) + + response = await convert_to_response_dict(response, operational_model) + assert response['body'] == body + + +@patch('aiobotocore.endpoint.isawaitable', return_value=True) +@pytest.mark.moto +@pytest.mark.asyncio +async def test_convert_to_response_dict_non_awaitable_fail(mock_awaitable): + class MockOperationalModel: + def name(self): + return 'test' + + @property + def has_streaming_output(self): + return False + + @property + def has_event_stream_output(self): + return False + + url = 'https://testbucket.s3.amazonaws.com/' + status = 200 + headers = { + 'x-amzn-requestid': '0n32brAiyTp2t9rdLgFtTmvlh4ZoPpIf62mizOK0W9Nt9lZr5XRL' + } + body = ( + b'' + b'testbucket' + b'' + ) + raw = MockRawResponse(body) + encoded_headers = [ + ( + str(header).encode(encoding='utf-8'), + str(value).encode(encoding='utf-8'), + ) + for header, value in headers.items() + ] + raw.raw_headers = encoded_headers + operational_model = MockOperationalModel() + response = AWSResponse(url, status, headers, raw) + with pytest.raises(TypeError) as e: + await convert_to_response_dict(response, operational_model) + assert "can't be used in 'await' expression" in str(e) From e3f8977a87e3dcde7d7cfcd94ab68ee1b2dc0fa1 Mon Sep 17 00:00:00 2001 From: akshara08 Date: Tue, 18 Apr 2023 10:50:08 -0700 Subject: [PATCH 2/3] Version change in CHANGES.rst --- CHANGES.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index e40529bd..205398ee 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,5 +1,9 @@ Changes ------- +2.5.1 (2023-04-05) +^^^^^^^^^^^^^^^^^^ +* Add support for moto by allowing non-awaitable content + 2.5.0 (2023-03-06) ^^^^^^^^^^^^^^^^^^ * bump botocore to 1.29.76 (thanks @jakob-keller #999) From 3d16aadc4daff85553a2f23d385dcca4851d2a3b Mon Sep 17 00:00:00 2001 From: akshara08 Date: Tue, 18 Apr 2023 14:55:11 -0700 Subject: [PATCH 3/3] Clean up using helper method --- aiobotocore/endpoint.py | 13 ++++--------- tests/test_response.py | 2 +- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/aiobotocore/endpoint.py b/aiobotocore/endpoint.py index d77cde69..1f37489f 100644 --- a/aiobotocore/endpoint.py +++ b/aiobotocore/endpoint.py @@ -1,5 +1,4 @@ import asyncio -from inspect import isawaitable from botocore.endpoint import ( DEFAULT_TIMEOUT, @@ -20,6 +19,8 @@ from aiobotocore.httpsession import AIOHTTPSession from aiobotocore.response import StreamingBody +from ._helpers import resolve_awaitable + async def convert_to_response_dict(http_response, operation_model): """Convert an HTTP response object to a request dict. @@ -54,20 +55,14 @@ async def convert_to_response_dict(http_response, operation_model): }, } if response_dict['status_code'] >= 300: - if isawaitable(http_response.content): - response_dict['body'] = await http_response.content - else: - response_dict['body'] = http_response.content + response_dict['body'] = await resolve_awaitable(http_response.content) elif operation_model.has_event_stream_output: response_dict['body'] = http_response.raw elif operation_model.has_streaming_output: length = response_dict['headers'].get('content-length') response_dict['body'] = StreamingBody(http_response.raw, length) else: - if isawaitable(http_response.content): - response_dict['body'] = await http_response.content - else: - response_dict['body'] = http_response.content + response_dict['body'] = await resolve_awaitable(http_response.content) return response_dict diff --git a/tests/test_response.py b/tests/test_response.py index 4fa5bfe1..55c82e83 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -236,7 +236,7 @@ def has_event_stream_output(self): assert response['body'] == body -@patch('aiobotocore.endpoint.isawaitable', return_value=True) +@patch('aiobotocore._helpers.inspect.isawaitable', return_value=True) @pytest.mark.moto @pytest.mark.asyncio async def test_convert_to_response_dict_non_awaitable_fail(mock_awaitable):