Skip to content

Commit fd7fa00

Browse files
Source google-analytics-data-api: upgrade cdk 3 (#42841)
1 parent 223717a commit fd7fa00

File tree

13 files changed

+435
-235
lines changed

13 files changed

+435
-235
lines changed

airbyte-integrations/connectors/source-google-analytics-data-api/metadata.yaml

+6-5
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ data:
1212
connectorSubtype: api
1313
connectorType: source
1414
definitionId: 3cc2eafd-84aa-4dca-93af-322d9dfeec1a
15-
dockerImageTag: 2.4.14
15+
dockerImageTag: 2.5.0
1616
dockerRepository: airbyte/source-google-analytics-data-api
1717
documentationUrl: https://docs.airbyte.com/integrations/sources/google-analytics-data-api
1818
githubIssueLabel: source-google-analytics-data-api
@@ -51,10 +51,11 @@ data:
5151
- language:python
5252
- cdk:python
5353
connectorTestSuitesOptions:
54-
- suite: liveTests
55-
testConnections:
56-
- name: google-analytics-data-api_config_dev_null
57-
id: 6cee88ef-3893-4498-9812-8b80dbfcab0e
54+
# Running regression and acceptance at the same time can cause quota consumption problems
55+
# - suite: liveTests
56+
# testConnections:
57+
# - name: google-analytics-data-api_config_dev_null
58+
# id: 6cee88ef-3893-4498-9812-8b80dbfcab0e
5859
- suite: unitTests
5960
- suite: acceptanceTests
6061
testSecrets:

airbyte-integrations/connectors/source-google-analytics-data-api/poetry.lock

+188-101
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

airbyte-integrations/connectors/source-google-analytics-data-api/pyproject.toml

+4-4
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.4.14"
6+
version = "2.5.0"
77
name = "source-google-analytics-data-api"
88
description = "Source implementation for Google Analytics Data Api."
99
authors = [ "Airbyte <[email protected]>",]
@@ -17,10 +17,10 @@ include = "source_google_analytics_data_api"
1717

1818
[tool.poetry.dependencies]
1919
python = "^3.9,<3.12"
20-
cryptography = "==37.0.4"
20+
cryptography = "==42.0.5"
2121
requests = "==2.31.0"
22-
airbyte-cdk = "^0"
23-
PyJWT = "==2.4.0"
22+
airbyte-cdk = "^3"
23+
PyJWT = "==2.8.0"
2424
pandas = "==2.2.0"
2525

2626
[tool.poetry.scripts]

airbyte-integrations/connectors/source-google-analytics-data-api/source_google_analytics_data_api/api_quota.py

+16-24
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from typing import Any, Iterable, Mapping, Optional
99

1010
import requests
11+
from airbyte_cdk.sources.streams.http.error_handlers.response_models import ResponseAction
1112
from requests.exceptions import JSONDecodeError
1213

1314
from .utils import API_LIMIT_PER_HOUR
@@ -22,9 +23,8 @@ class GoogleAnalyticsApiQuotaBase:
2223
# setting the scenario values for attrs prior to the 429 error
2324
treshold: float = 0.1
2425
# base attrs
25-
should_retry: Optional[bool] = True
26+
response_action: ResponseAction = ResponseAction.RETRY
2627
backoff_time: Optional[int] = None
27-
raise_on_http_errors: bool = True
2828
# stop making new slices globally
2929
stop_iter: bool = False
3030
error_message = None
@@ -33,23 +33,20 @@ class GoogleAnalyticsApiQuotaBase:
3333
"concurrentRequests": {
3434
"error_pattern": "Exhausted concurrent requests quota.",
3535
"backoff": 30,
36-
"should_retry": True,
37-
"raise_on_http_errors": False,
36+
"response_action": ResponseAction.RETRY,
3837
"stop_iter": False,
3938
},
4039
"tokensPerProjectPerHour": {
4140
"error_pattern": "Exhausted property tokens for a project per hour.",
4241
"backoff": 1800,
43-
"should_retry": True,
44-
"raise_on_http_errors": False,
42+
"response_action": ResponseAction.RETRY,
4543
"stop_iter": False,
4644
"error_message": API_LIMIT_PER_HOUR,
4745
},
4846
"potentiallyThresholdedRequestsPerHour": {
4947
"error_pattern": "Exhausted potentially thresholded requests quota.",
5048
"backoff": 1800,
51-
"should_retry": True,
52-
"raise_on_http_errors": False,
49+
"response_action": ResponseAction.RETRY,
5350
"stop_iter": False,
5451
"error_message": API_LIMIT_PER_HOUR,
5552
},
@@ -61,22 +58,19 @@ class GoogleAnalyticsApiQuotaBase:
6158
# 'tokensPerDay': {
6259
# 'error_pattern': "___",
6360
# "backoff": None,
64-
# "should_retry": False,
65-
# "raise_on_http_errors": False,
61+
# "response_action": ResponseAction.FAIL,
6662
# "stop_iter": True,
6763
# },
6864
# 'tokensPerHour': {
6965
# 'error_pattern': "___",
7066
# "backoff": 1800,
71-
# "should_retry": True,
72-
# "raise_on_http_errors": False,
67+
# "response_action": ResponseAction.RETRY,
7368
# "stop_iter": False,
7469
# },
7570
# 'serverErrorsPerProjectPerHour': {
7671
# 'error_pattern': "___",
7772
# "backoff": 3600,
78-
# "should_retry": True,
79-
# "raise_on_http_errors": False,
73+
# "response_action": ResponseAction.RETRY,
8074
# "stop_iter": False,
8175
# },
8276
}
@@ -105,16 +99,14 @@ def _get_known_quota_from_response(self, property_quota: Mapping[str, Any]) -> M
10599
def _set_retry_attrs_for_quota(self, quota_name: str) -> None:
106100
quota = self.quota_mapping.get(quota_name, {})
107101
if quota:
108-
self.should_retry = quota.get("should_retry")
109-
self.raise_on_http_errors = quota.get("raise_on_http_errors")
102+
self.response_action = quota.get("response_action")
110103
self.stop_iter = quota.get("stop_iter")
111104
self.backoff_time = quota.get("backoff")
112-
self.error_message = quota.get("error_message")
105+
self.error_message = quota.get("error_message", quota.get("error_pattern"))
113106

114-
def _set_default_retry_attrs(self) -> None:
115-
self.should_retry = True
107+
def _set_default_handle_error_attrs(self) -> None:
108+
self.response_action = ResponseAction.RETRY
116109
self.backoff_time = None
117-
self.raise_on_http_errors = True
118110
self.stop_iter = False
119111

120112
def _set_initial_quota(self, current_quota: Optional[Mapping[str, Any]] = None) -> None:
@@ -137,7 +129,7 @@ def _check_remaining_quota(self, current_quota: Mapping[str, Any]) -> None:
137129
def _check_for_errors(self, response: requests.Response) -> None:
138130
try:
139131
# revert to default values after successul retry
140-
self._set_default_retry_attrs()
132+
self._set_default_handle_error_attrs()
141133
error = response.json().get("error")
142134
if error:
143135
quota_name = self._get_quota_name_from_error_message(error.get("message"))
@@ -161,7 +153,7 @@ def _check_quota(self, response: requests.Response):
161153
try:
162154
parsed_response = response.json()
163155
except (AttributeError, JSONDecodeError) as e:
164-
self.logger.warn(
156+
self.logger.warning(
165157
f"`GoogleAnalyticsApiQuota._check_quota`: Received non JSON response from the API. Full error: {e}. Bypassing."
166158
)
167159
parsed_response = {}
@@ -170,7 +162,7 @@ def _check_quota(self, response: requests.Response):
170162
if property_quota:
171163
# return default attrs values once successfully retried
172164
# or until another 429 error is hit
173-
self._set_default_retry_attrs()
165+
self._set_default_handle_error_attrs()
174166
# reduce quota list to known kinds only
175167
current_quota = self._get_known_quota_from_response(property_quota)
176168
if current_quota:
@@ -183,7 +175,7 @@ def _check_quota(self, response: requests.Response):
183175

184176
def handle_quota(self) -> None:
185177
"""
186-
The function decorator is used to integrate with the `should_retry` method,
178+
The function decorator is used to integrate with the `interpret_response` method,
187179
or any other method that provides early access to the `response` object.
188180
"""
189181

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#
2+
# Copyright (c) 2024 Airbyte, Inc., all rights reserved.
3+
#
4+
from typing import Mapping, Type, Union
5+
6+
from airbyte_cdk.models import FailureType
7+
from airbyte_cdk.sources.streams.http.error_handlers.default_error_mapping import DEFAULT_ERROR_MAPPING
8+
from airbyte_cdk.sources.streams.http.error_handlers.response_models import ErrorResolution
9+
from source_google_analytics_data_api.utils import WRONG_CUSTOM_REPORT_CONFIG
10+
11+
12+
def get_google_analytics_data_api_base_error_mapping(report_name) -> Mapping[Union[int, str, Type[Exception]], ErrorResolution]:
13+
"""
14+
Updating base default error messages friendly config error message that includes the steam report name
15+
"""
16+
stream_error_mapping = {}
17+
for error_key, base_error_resolution in DEFAULT_ERROR_MAPPING.items():
18+
if base_error_resolution.failure_type in (FailureType.config_error, FailureType.system_error):
19+
stream_error_mapping[error_key] = ErrorResolution(
20+
response_action=base_error_resolution.response_action,
21+
failure_type=FailureType.config_error,
22+
error_message=WRONG_CUSTOM_REPORT_CONFIG.format(report=report_name),
23+
)
24+
else:
25+
stream_error_mapping[error_key] = base_error_resolution
26+
return stream_error_mapping
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#
2+
# Copyright (c) 2024 Airbyte, Inc., all rights reserved.
3+
#
4+
from typing import Mapping, Type, Union
5+
6+
from airbyte_cdk.models import FailureType
7+
from airbyte_cdk.sources.streams.http.error_handlers.default_error_mapping import DEFAULT_ERROR_MAPPING
8+
from airbyte_cdk.sources.streams.http.error_handlers.response_models import ErrorResolution, ResponseAction
9+
10+
PROPERTY_ID_DOCS_URL = "https://developers.google.com/analytics/devguides/reporting/data/v1/property-id#what_is_my_property_id"
11+
MESSAGE = "Incorrect Property ID: {property_id}. Access was denied to the property ID entered. Check your access to the Property ID or use Google Analytics {property_id_docs_url} to find your Property ID."
12+
13+
14+
def get_google_analytics_data_api_metadata_error_mapping(property_id) -> Mapping[Union[int, str, Type[Exception]], ErrorResolution]:
15+
"""
16+
Adding friendly messages to bad request and forbidden responses that includes the property id and the documentation guidance.
17+
"""
18+
return DEFAULT_ERROR_MAPPING | {
19+
403: ErrorResolution(
20+
response_action=ResponseAction.FAIL,
21+
failure_type=FailureType.config_error,
22+
error_message=MESSAGE.format(property_id=property_id, property_id_docs_url=PROPERTY_ID_DOCS_URL),
23+
),
24+
400: ErrorResolution(
25+
response_action=ResponseAction.FAIL,
26+
failure_type=FailureType.config_error,
27+
error_message=MESSAGE.format(property_id=property_id, property_id_docs_url=PROPERTY_ID_DOCS_URL),
28+
),
29+
}

0 commit comments

Comments
 (0)