Skip to content
Closed
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
eb212b3
Initial commit for DNS error handling changes
devin-ai-integration[bot] Feb 20, 2025
283802d
Update InvalidURL error handling to retry on temporary DNS failures
devin-ai-integration[bot] Feb 20, 2025
b3c0734
Add test for DNS resolution error handling
devin-ai-integration[bot] Feb 20, 2025
984f4eb
Fix import for FailureType in test
devin-ai-integration[bot] Feb 20, 2025
aef78e8
Update DNS error test to match expected pattern
devin-ai-integration[bot] Feb 20, 2025
69620ac
Add InvalidURL import for DNS error test
devin-ai-integration[bot] Feb 20, 2025
42579ca
Fix DNS error test to use correct method
devin-ai-integration[bot] Feb 20, 2025
7a0b67b
Fix DNS error test to use malformed URL
devin-ai-integration[bot] Feb 20, 2025
9bac7fc
Fix DNS error test to use error handler
devin-ai-integration[bot] Feb 20, 2025
492dc08
Add error handler to StubBasicReadHttpStream
devin-ai-integration[bot] Feb 20, 2025
5ade37c
Create dedicated test class for DNS error handling
devin-ai-integration[bot] Feb 20, 2025
e8541be
Fix import sorting
devin-ai-integration[bot] Feb 20, 2025
24d48c1
Fix formatting and lint issues
devin-ai-integration[bot] Feb 20, 2025
888d5a3
Add DNSResolutionError class and update error handling
devin-ai-integration[bot] Feb 20, 2025
8a88e1d
style: Fix formatting with ruff
devin-ai-integration[bot] Feb 20, 2025
8506c83
fix: Add DNSResolutionError import
devin-ai-integration[bot] Feb 20, 2025
57e3578
style: Fix formatting in http_client.py
devin-ai-integration[bot] Feb 20, 2025
9148cd5
test: Add test for DNS resolution error from ConnectionError
devin-ai-integration[bot] Feb 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,14 @@
ErrorResolution,
ResponseAction,
)
from airbyte_cdk.sources.streams.http.exceptions import DNSResolutionError

DEFAULT_ERROR_MAPPING: Mapping[Union[int, str, Type[Exception]], ErrorResolution] = {
DNSResolutionError: ErrorResolution(
response_action=ResponseAction.RETRY,
failure_type=FailureType.transient_error,
error_message="Temporary DNS resolution error occurred. Retrying...",
),
InvalidSchema: ErrorResolution(
response_action=ResponseAction.FAIL,
failure_type=FailureType.config_error,
Expand Down
21 changes: 21 additions & 0 deletions airbyte_cdk/sources/streams/http/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,24 @@ class DefaultBackoffException(BaseBackoffException):

class RateLimitBackoffException(BaseBackoffException):
pass


class DNSResolutionError(BaseBackoffException):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see where this exception class is raised. Can you help me understand this?

Does our code need to raise this exception in order for its handling to trigger? Or is it going to be picked up automatically somehow?

"""
Raised when a DNS resolution error occurs.
This is different from InvalidURL which is raised for malformed URLs.
"""

def __init__(
self,
url: str,
request: requests.PreparedRequest,
response: Optional[Union[requests.Response, Exception]],
error_message: str = "",
):
self.url = url
super().__init__(
request=request,
response=response,
error_message=error_message or f"Failed to resolve DNS for URL: {url}",
)
11 changes: 11 additions & 0 deletions airbyte_cdk/sources/streams/http/http_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
)
from airbyte_cdk.sources.streams.http.exceptions import (
DefaultBackoffException,
DNSResolutionError,
RateLimitBackoffException,
RequestBodyException,
UserDefinedBackoffException,
Expand Down Expand Up @@ -300,6 +301,16 @@ def _send(

try:
response = self._session.send(request, **request_kwargs)
except requests.ConnectionError as e:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we only catch InvalidURL exceptions here? It looks like previously, these failures were raised and custom-handled as InvalidURL exceptions.

if "Name or service not known" in str(e) or "nodename nor servname provided" in str(e):
assert (
request.url is not None
), "Request URL cannot be None for DNS resolution error"
exc = DNSResolutionError(
url=request.url, request=request, response=e, error_message=str(e)
)
else:
exc = e
except requests.RequestException as e:
exc = e

Expand Down
35 changes: 34 additions & 1 deletion unit_tests/sources/streams/http/test_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import pytest
import requests
from requests.exceptions import InvalidURL

from airbyte_cdk.models import AirbyteLogMessage, AirbyteMessage, Level, SyncMode, Type
from airbyte_cdk.sources.streams import CheckpointMixin
Expand All @@ -20,9 +21,13 @@
from airbyte_cdk.sources.streams.core import StreamData
from airbyte_cdk.sources.streams.http import HttpStream, HttpSubStream
from airbyte_cdk.sources.streams.http.error_handlers import ErrorHandler, HttpStatusErrorHandler
from airbyte_cdk.sources.streams.http.error_handlers.response_models import ResponseAction
from airbyte_cdk.sources.streams.http.error_handlers.response_models import (
FailureType,
ResponseAction,
)
from airbyte_cdk.sources.streams.http.exceptions import (
DefaultBackoffException,
DNSResolutionError,
RequestBodyException,
UserDefinedBackoffException,
)
Expand Down Expand Up @@ -331,6 +336,34 @@ def test_raise_on_http_errors(mocker, error):
assert send_mock.call_count == stream.max_retries + 1


class StubHttpStreamWithErrorHandler(StubBasicReadHttpStream):
def get_error_handler(self) -> Optional[ErrorHandler]:
return HttpStatusErrorHandler(logging.getLogger())


def test_dns_resolution_error_retry():
"""Test that DNS resolution errors are retried"""
stream = StubHttpStreamWithErrorHandler()
error_handler = stream.get_error_handler()
request = requests.PreparedRequest()
request.url = "https://example.com"
dns_error = DNSResolutionError(
Comment on lines +344 to +350
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a test that simulates/mocks the error we expect the http api to raise? Specifically, I think we're expecting something like an invalid URL code, except with the noted error text about DNS.

url="https://example.com", request=request, response=Exception("DNS lookup failed")
)
resolution = error_handler.interpret_response(dns_error)
assert resolution.response_action == ResponseAction.RETRY
assert resolution.failure_type == FailureType.transient_error


def test_invalid_url_fails():
"""Test that invalid URLs fail immediately"""
stream = StubHttpStreamWithErrorHandler()
error_handler = stream.get_error_handler()
resolution = error_handler.interpret_response(InvalidURL())
assert resolution.response_action == ResponseAction.FAIL
assert resolution.failure_type == FailureType.config_error


class PostHttpStream(StubBasicReadHttpStream):
http_method = "POST"

Expand Down