Skip to content

Commit a4fcd16

Browse files
committed
airbyte-cdk: merge from main
2 parents b241766 + 65ed26f commit a4fcd16

File tree

3 files changed

+77
-3
lines changed

3 files changed

+77
-3
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,13 @@ Installing all extras is required to run the full suite of unit tests.
9393

9494
To see all available scripts, run `poetry run poe`.
9595

96+
#### Formatting the code
97+
98+
- Iterate on the CDK code locally
99+
- Run `poetry run ruff format` to format your changes.
100+
101+
To see all available `ruff` options, run `poetry run ruff`.
102+
96103
##### Autogenerated files
97104

98105
Low-code CDK models are generated from `sources/declarative/declarative_component_schema.yaml`. If

airbyte_cdk/sources/streams/http/http_client.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#
22
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
33
#
4+
45
import logging
56
import os
67
import urllib
@@ -44,6 +45,7 @@
4445
rate_limit_default_backoff_handler,
4546
user_defined_backoff_handler,
4647
)
48+
from airbyte_cdk.sources.utils.types import JsonType
4749
from airbyte_cdk.utils.airbyte_secrets_utils import filter_secrets
4850
from airbyte_cdk.utils.constants import ENV_REQUEST_CACHE_PATH
4951
from airbyte_cdk.utils.stream_status_utils import (
@@ -335,6 +337,29 @@ def _send(
335337

336338
return response # type: ignore # will either return a valid response of type requests.Response or raise an exception
337339

340+
def _get_response_body(self, response: requests.Response) -> Optional[JsonType]:
341+
"""
342+
Extracts and returns the body of an HTTP response.
343+
344+
This method attempts to parse the response body as JSON. If the response
345+
body is not valid JSON, it falls back to decoding the response content
346+
as a UTF-8 string. If both attempts fail, it returns None.
347+
348+
Args:
349+
response (requests.Response): The HTTP response object.
350+
351+
Returns:
352+
Optional[JsonType]: The parsed JSON object as a string, the decoded
353+
response content as a string, or None if both parsing attempts fail.
354+
"""
355+
try:
356+
return str(response.json())
357+
except requests.exceptions.JSONDecodeError:
358+
try:
359+
return response.content.decode("utf-8")
360+
except Exception:
361+
return "The Content of the Response couldn't be decoded."
362+
338363
def _evict_key(self, prepared_request: requests.PreparedRequest) -> None:
339364
"""
340365
Addresses high memory consumption when enabling concurrency in https://github.com/airbytehq/oncall/issues/6821.
@@ -377,13 +402,18 @@ def _handle_error_resolution(
377402

378403
if error_resolution.response_action == ResponseAction.FAIL:
379404
if response is not None:
380-
error_message = f"'{request.method}' request to '{request.url}' failed with status code '{response.status_code}' and error message '{response.content.decode('utf-8', errors='replace')}'"
405+
filtered_response_message = filter_secrets(
406+
f"Request (body): '{str(request.body)}'. Response (body): '{self._get_response_body(response)}'. Response (headers): '{response.headers}'."
407+
)
408+
error_message = f"'{request.method}' request to '{request.url}' failed with status code '{response.status_code}' and error message: '{self._error_message_parser.parse_response_error_message(response)}'. {filtered_response_message}"
381409
else:
382410
error_message = (
383411
f"'{request.method}' request to '{request.url}' failed with exception: '{exc}'"
384412
)
385413

386-
self._logger.warning(filter_secrets(error_message))
414+
# ensure the exception message is emitted before raised
415+
self._logger.error(error_message)
416+
387417
raise MessageRepresentationAirbyteTracedErrors(
388418
internal_message=error_message,
389419
message=error_resolution.error_message or error_message,

unit_tests/sources/streams/http/test_http.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
33
#
44

5-
65
import json
76
import logging
87
from http import HTTPStatus
@@ -29,6 +28,7 @@
2928
)
3029
from airbyte_cdk.sources.streams.http.http_client import MessageRepresentationAirbyteTracedErrors
3130
from airbyte_cdk.sources.streams.http.requests_native_auth import TokenAuthenticator
31+
from airbyte_cdk.utils.airbyte_secrets_utils import update_secrets
3232

3333

3434
class StubBasicReadHttpStream(HttpStream):
@@ -230,6 +230,43 @@ def test_4xx_error_codes_http_stream(mocker, http_code):
230230
list(stream.read_records(SyncMode.full_refresh))
231231

232232

233+
@pytest.mark.parametrize("http_code", [400, 401, 403])
234+
def test_error_codes_http_stream_error_resolution_with_response_secrets_filtered(mocker, http_code):
235+
stream = StubCustomBackoffHttpStream()
236+
237+
# expected assertion values
238+
expected_header_secret_replaced = "'authorisation_header': '__****__'"
239+
expected_content_str_secret_replaced = "this str contains **** secret"
240+
241+
# mocking the response
242+
res = requests.Response()
243+
res.status_code = http_code
244+
res._content = (
245+
b'{"error": "test error message", "secret_info": "this str contains SECRET_VALUE secret"}'
246+
)
247+
res.headers = {
248+
# simple non-secret header
249+
"regular_header": "some_header_value",
250+
# secret header
251+
"authorisation_header": "__SECRET_X_VALUE__",
252+
}
253+
254+
# updating secrets to be filtered
255+
update_secrets(["SECRET_X_VALUE", "SECRET_VALUE"])
256+
257+
# patch the `send` > response
258+
mocker.patch.object(requests.Session, "send", return_value=res)
259+
260+
# proceed
261+
with pytest.raises(MessageRepresentationAirbyteTracedErrors) as err:
262+
list(stream.read_records(SyncMode.full_refresh))
263+
264+
# we expect the header secrets are obscured
265+
assert expected_header_secret_replaced in str(err._excinfo)
266+
# we expect the response body values (any of them) are obscured
267+
assert expected_content_str_secret_replaced in str(err._excinfo)
268+
269+
233270
class AutoFailFalseHttpStream(StubBasicReadHttpStream):
234271
raise_on_http_errors = False
235272
max_retries = 3

0 commit comments

Comments
 (0)