Skip to content

Commit 698f543

Browse files
authored
Source FB Marketing: Handle errors without API Error Code/Messages (#37771)
1 parent a330088 commit 698f543

File tree

5 files changed

+63
-5
lines changed

5 files changed

+63
-5
lines changed

airbyte-integrations/connectors/source-facebook-marketing/metadata.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ data:
1010
connectorSubtype: api
1111
connectorType: source
1212
definitionId: e7778cfc-e97c-4458-9ecb-b4f2bba8946c
13-
dockerImageTag: 2.1.7
13+
dockerImageTag: 2.1.8
1414
dockerRepository: airbyte/source-facebook-marketing
1515
documentationUrl: https://docs.airbyte.com/integrations/sources/facebook-marketing
1616
githubIssueLabel: source-facebook-marketing

airbyte-integrations/connectors/source-facebook-marketing/pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ requires = [ "poetry-core>=1.0.0",]
33
build-backend = "poetry.core.masonry.api"
44

55
[tool.poetry]
6-
version = "2.1.7"
6+
version = "2.1.8"
77
name = "source-facebook-marketing"
88
description = "Source implementation for Facebook Marketing."
99
authors = [ "Airbyte <[email protected]>",]

airbyte-integrations/connectors/source-facebook-marketing/source_facebook_marketing/streams/common.py

+13-2
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ def should_retry_api_error(exc):
103103
unknown_error = exc.api_error_subcode() == FACEBOOK_UNKNOWN_ERROR_CODE
104104
connection_reset_error = exc.api_error_code() == FACEBOOK_CONNECTION_RESET_ERROR_CODE
105105
server_error = exc.http_status() == http.client.INTERNAL_SERVER_ERROR
106+
service_unavailable_error = exc.http_status() == http.client.SERVICE_UNAVAILABLE
106107
return any(
107108
(
108109
exc.api_transient_error(),
@@ -113,6 +114,7 @@ def should_retry_api_error(exc):
113114
connection_reset_error,
114115
temporary_oauth_error,
115116
server_error,
117+
service_unavailable_error,
116118
)
117119
)
118120
return True
@@ -150,7 +152,7 @@ def traced_exception(fb_exception: FacebookRequestError):
150152
Please see ../unit_tests/test_errors.py for full error examples
151153
Please add new errors to the tests
152154
"""
153-
msg = fb_exception.api_error_message()
155+
msg = fb_exception.api_error_message() or fb_exception.get_message()
154156

155157
if "Error validating access token" in msg:
156158
failure_type = FailureType.config_error
@@ -188,9 +190,18 @@ def traced_exception(fb_exception: FacebookRequestError):
188190
exception=fb_exception,
189191
)
190192

193+
elif fb_exception.http_status() == 503:
194+
return AirbyteTracedException(
195+
message="The Facebook API service is temporarily unavailable. This issue should resolve itself, and does not require further action.",
196+
internal_message=str(fb_exception),
197+
failure_type=FailureType.transient_error,
198+
exception=fb_exception,
199+
)
200+
191201
else:
192202
failure_type = FailureType.system_error
193-
friendly_msg = f"Error: {fb_exception.api_error_code()}, {fb_exception.api_error_message()}."
203+
error_code = fb_exception.api_error_code() if fb_exception.api_error_code() else fb_exception.http_status()
204+
friendly_msg = f"Error code {error_code}: {msg}."
194205

195206
return AirbyteTracedException(
196207
message=friendly_msg or msg,

airbyte-integrations/connectors/source-facebook-marketing/unit_tests/test_errors.py

+46
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44

55
import json
66
from datetime import datetime
7+
from unittest.mock import MagicMock
78

89
import pytest
910
from airbyte_cdk.models import FailureType, SyncMode
1011
from airbyte_cdk.utils.traced_exception import AirbyteTracedException
1112
from facebook_business import FacebookAdsApi, FacebookSession
13+
from facebook_business.exceptions import FacebookRequestError
1214
from source_facebook_marketing.api import API
1315
from source_facebook_marketing.streams import AdAccount, AdCreatives, AdsInsights
16+
from source_facebook_marketing.streams.common import traced_exception
1417

1518
FB_API_VERSION = FacebookAdsApi.API_VERSION
1619

@@ -300,6 +303,17 @@ class TestRealErrors:
300303
# Potentially could be caused by some particular field (list of requested fields is constant).
301304
# But since sync was successful on next attempt, then conclusion is that this is a temporal problem.
302305
),
306+
(
307+
"error_503_service_unavailable",
308+
{
309+
"json": {
310+
"error": {
311+
"message": "Call was not successful",
312+
}
313+
},
314+
"status_code": 503,
315+
},
316+
),
303317
],
304318
)
305319
def test_retryable_error(self, some_config, requests_mock, name, retryable_error_response):
@@ -510,3 +524,35 @@ def test_adaccount_list_objects_retry(self, requests_mock, failure_response):
510524
stream_state={},
511525
)
512526
assert list(record_gen) == [{"account_id": "unknown_account", "id": "act_unknown_account"}]
527+
528+
def test_traced_exception_with_api_error():
529+
error = FacebookRequestError(
530+
message="Some error occurred",
531+
request_context={},
532+
http_status=400,
533+
http_headers={},
534+
body='{"error": {"message": "Error validating access token", "code": 190}}'
535+
)
536+
error.api_error_message = MagicMock(return_value="Error validating access token")
537+
538+
result = traced_exception(error)
539+
540+
assert isinstance(result, AirbyteTracedException)
541+
assert result.message == "Invalid access token. Re-authenticate if FB oauth is used or refresh access token with all required permissions"
542+
assert result.failure_type == FailureType.config_error
543+
544+
def test_traced_exception_without_api_error():
545+
error = FacebookRequestError(
546+
message="Call was unsuccessful. The Facebook API has imploded",
547+
request_context={},
548+
http_status=408,
549+
http_headers={},
550+
body='{}'
551+
)
552+
error.api_error_message = MagicMock(return_value=None)
553+
554+
result = traced_exception(error)
555+
556+
assert isinstance(result, AirbyteTracedException)
557+
assert result.message == "Error code 408: Call was unsuccessful. The Facebook API has imploded."
558+
assert result.failure_type == FailureType.system_error

docs/integrations/sources/facebook-marketing.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,8 @@ The Facebook Marketing connector uses the `lookback_window` parameter to repeate
201201
## Changelog
202202

203203
| Version | Date | Pull Request | Subject |
204-
| :------ | :--------- | :------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
204+
|:--------|:-----------|:---------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
205+
| 2.1.8 | 2024-05-07 | [37771](https://github.com/airbytehq/airbyte/pull/37771) | Handle errors without API error codes/messages |
205206
| 2.1.7 | 2024-04-24 | [36634](https://github.com/airbytehq/airbyte/pull/36634) | Update to CDK 0.80.0 |
206207
| 2.1.6 | 2024-04-24 | [36634](https://github.com/airbytehq/airbyte/pull/36634) | Schema descriptions |
207208
| 2.1.5 | 2024-04-17 | [37341](https://github.com/airbytehq/airbyte/pull/37341) | Move rate limit errors to transient errors. |

0 commit comments

Comments
 (0)