Repository: opensearch-project/opensearch-py (python)

Issue:
[BUG] additional info missing from TransportError when using async
### What is the bug?
`TransportError`s are missing the parsed json stored on the `info` property when using the library with async.

The cause is the content type is not being passed to `_raise_errors` from the async code.
See the difference between the [sync code](https://github.com/opensearch-project/opensearch-py/blob/8879644ec404b3ab15b739b97775d7bd7e51018b/opensearchpy/connection/http_requests.py#L204-L208) and the [async code](https://github.com/opensearch-project/opensearch-py/blob/8879644ec404b3ab15b739b97775d7bd7e51018b/opensearchpy/_async/http_aiohttp.py#L340)

### How can one reproduce the bug?
Make bad requests (e.g. 400s) when using the async code.

### What is the expected behavior?
`TransportError`s `info` property should contain a dict with the parsed response (as in non-async use of the library). 

### What is your host/environment?
Linux + MacOs

### Do you have any screenshots?
Links to code above.

### Do you have any additional context?
This bug was introduced in 1.1.0 when the content-type header started to be inspected before parsing the json. https://github.com/opensearch-project/opensearch-py/commit/1cd15b08453a860624b5c405eed4d4052e916096


Pull request description:
Include parsed error info in TransportError in async connections
Fixes #225

Signed-off-by: Gordon Govan <gg@kialo.com>

### Description
Parses the error into the `info` field of `TransportException`s in the async connection to match the behaviour of the sync connection.

### Issues Resolved
Closes #225 

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check [here](https://github.com/opensearch-project/OpenSearch/blob/main/CONTRIBUTING.md#developer-certificate-of-origin).


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 479a0ac3..bc0712d7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
 ### Fixed
 - Fixed import cycle when importing async helpers ([#311](https://github.com/opensearch-project/opensearch-py/pull/311))
 - Fixed userguide for async client ([#340](https://github.com/opensearch-project/opensearch-py/pull/340))
+- Include parsed error info in TransportError in async connections (fixes #225) ([#226](https://github.com/opensearch-project/opensearch-py/pull/226)
 ### Security
 - Fixed CVE-2022-23491 reported in opensearch-dsl-py ([#295](https://github.com/opensearch-project/opensearch-py/pull/295))
 - Update ci workflows ([#318](https://github.com/opensearch-project/opensearch-py/pull/318))
diff --git a/opensearchpy/_async/http_aiohttp.py b/opensearchpy/_async/http_aiohttp.py
index 6f96997e..f630a3bd 100644
--- a/opensearchpy/_async/http_aiohttp.py
+++ b/opensearchpy/_async/http_aiohttp.py
@@ -331,7 +331,11 @@ class AIOHttpConnection(AsyncConnection):
                 status_code=response.status,
                 response=raw_data,
             )
-            self._raise_error(response.status, raw_data)
+            self._raise_error(
+                response.status,
+                raw_data,
+                response.headers.get("content-type"),
+            )
 
         self.log_request_success(
             method, str(url), url_path, orig_body, response.status, raw_data, duration


Test patch:
diff --git a/test_opensearchpy/test_async/test_connection.py b/test_opensearchpy/test_async/test_connection.py
index d43b2bb5..1b8cdc58 100644
--- a/test_opensearchpy/test_async/test_connection.py
+++ b/test_opensearchpy/test_async/test_connection.py
@@ -42,7 +42,7 @@ from pytest import raises
 from opensearchpy import AIOHttpConnection, AsyncOpenSearch, __versionstr__, serializer
 from opensearchpy.compat import reraise_exceptions
 from opensearchpy.connection import Connection, async_connections
-from opensearchpy.exceptions import ConnectionError
+from opensearchpy.exceptions import ConnectionError, TransportError
 
 pytestmark = pytest.mark.asyncio
 
@@ -53,7 +53,13 @@ def gzip_decompress(data):
 
 
 class TestAIOHttpConnection:
-    async def _get_mock_connection(self, connection_params={}, response_body=b"{}"):
+    async def _get_mock_connection(
+        self,
+        connection_params={},
+        response_code=200,
+        response_body=b"{}",
+        response_headers={},
+    ):
         con = AIOHttpConnection(**connection_params)
         await con._create_aiohttp_session()
 
@@ -69,8 +75,8 @@ class TestAIOHttpConnection:
                     return response_body.decode("utf-8", "surrogatepass")
 
             dummy_response = DummyResponse()
-            dummy_response.headers = CIMultiDict()
-            dummy_response.status = 200
+            dummy_response.headers = CIMultiDict(**response_headers)
+            dummy_response.status = response_code
             _dummy_request.call_args = (args, kwargs)
             return dummy_response
 
@@ -298,6 +304,20 @@ class TestAIOHttpConnection:
             await conn.perform_request("GET", "/")
         assert str(e.value) == "Wasn't modified!"
 
+    async def test_json_errors_are_parsed(self):
+        con = await self._get_mock_connection(
+            response_code=400,
+            response_body=b'{"error": {"type": "snapshot_in_progress_exception"}}',
+            response_headers={"Content-Type": "application/json;"},
+        )
+        try:
+            with pytest.raises(TransportError) as e:
+                await con.perform_request("POST", "/", body=b'{"some": "json"')
+
+            assert e.value.error == "snapshot_in_progress_exception"
+        finally:
+            await con.close()
+
 
 class TestConnectionHttpbin:
     """Tests the HTTP connection implementations against a live server E2E"""