Skip to content

Commit e2100c4

Browse files
authored
Source Hubspot: turn on default HttpAvailabilityStrategy (#22479)
Signed-off-by: Sergey Chvalyuk <[email protected]>
1 parent 17c77fc commit e2100c4

File tree

9 files changed

+29
-117
lines changed

9 files changed

+29
-117
lines changed

airbyte-config/init/src/main/resources/seed/source_definitions.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -799,7 +799,7 @@
799799
- name: HubSpot
800800
sourceDefinitionId: 36c891d9-4bd9-43ac-bad2-10e12756272c
801801
dockerRepository: airbyte/source-hubspot
802-
dockerImageTag: 0.3.1
802+
dockerImageTag: 0.3.2
803803
documentationUrl: https://docs.airbyte.com/integrations/sources/hubspot
804804
icon: hubspot.svg
805805
sourceType: api

airbyte-config/init/src/main/resources/seed/source_specs.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6521,7 +6521,7 @@
65216521
supportsNormalization: false
65226522
supportsDBT: false
65236523
supported_destination_sync_modes: []
6524-
- dockerImage: "airbyte/source-hubspot:0.3.1"
6524+
- dockerImage: "airbyte/source-hubspot:0.3.2"
65256525
spec:
65266526
documentationUrl: "https://docs.airbyte.com/integrations/sources/hubspot"
65276527
connectionSpecification:

airbyte-integrations/connectors/source-hubspot/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,5 @@ COPY source_hubspot ./source_hubspot
3434
ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py"
3535
ENTRYPOINT ["python", "/airbyte/integration_code/main.py"]
3636

37-
LABEL io.airbyte.version=0.3.1
37+
LABEL io.airbyte.version=0.3.2
3838
LABEL io.airbyte.name=airbyte/source-hubspot

airbyte-integrations/connectors/source-hubspot/integration_tests/expected_records.jsonl

Lines changed: 2 additions & 2 deletions
Large diffs are not rendered by default.

airbyte-integrations/connectors/source-hubspot/source_hubspot/source.py

Lines changed: 1 addition & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,12 @@
44

55
import logging
66
from itertools import chain
7-
from typing import Any, Iterator, List, Mapping, MutableMapping, Optional, Tuple, Union
7+
from typing import Any, List, Mapping, Optional, Tuple
88

99
import requests
1010
from airbyte_cdk.logger import AirbyteLogger
11-
from airbyte_cdk.models import AirbyteMessage, AirbyteStateMessage, ConfiguredAirbyteCatalog
1211
from airbyte_cdk.sources import AbstractSource
13-
from airbyte_cdk.sources.connector_state_manager import ConnectorStateManager
1412
from airbyte_cdk.sources.streams import Stream
15-
from airbyte_cdk.sources.utils.schema_helpers import split_config
16-
from airbyte_cdk.utils.event_timing import create_timer
17-
from airbyte_cdk.utils.traced_exception import AirbyteTracedException
1813
from requests import HTTPError
1914
from source_hubspot.streams import (
2015
API,
@@ -136,51 +131,3 @@ def streams(self, config: Mapping[str, Any]) -> List[Stream]:
136131
available_streams = streams
137132

138133
return available_streams
139-
140-
def read(
141-
self,
142-
logger: logging.Logger,
143-
config: Mapping[str, Any],
144-
catalog: ConfiguredAirbyteCatalog,
145-
state: Union[List[AirbyteStateMessage], MutableMapping[str, Any]] = None,
146-
) -> Iterator[AirbyteMessage]:
147-
"""
148-
This method is overridden to check whether the stream `quotes` exists in the source, if not skip reading that stream.
149-
"""
150-
logger.info(f"Starting syncing {self.name}")
151-
config, internal_config = split_config(config)
152-
# TODO assert all streams exist in the connector
153-
# get the streams once in case the connector needs to make any queries to generate them
154-
stream_instances = {s.name: s for s in self.streams(config)}
155-
state_manager = ConnectorStateManager(stream_instance_map=stream_instances, state=state)
156-
self._stream_to_instance_map = stream_instances
157-
with create_timer(self.name) as timer:
158-
for configured_stream in catalog.streams:
159-
stream_instance = stream_instances.get(configured_stream.stream.name)
160-
if not stream_instance and configured_stream.stream.name == "quotes":
161-
logger.warning("Stream `quotes` does not exist in the source. Skip reading `quotes` stream.")
162-
continue
163-
if not stream_instance:
164-
raise KeyError(
165-
f"The requested stream {configured_stream.stream.name} was not found in the source. Available streams: {stream_instances.keys()}"
166-
)
167-
168-
try:
169-
yield from self._read_stream(
170-
logger=logger,
171-
stream_instance=stream_instance,
172-
configured_stream=configured_stream,
173-
state_manager=state_manager,
174-
internal_config=internal_config,
175-
)
176-
except Exception as e:
177-
logger.exception(f"Encountered an exception while reading stream {configured_stream.stream.name}")
178-
display_message = stream_instance.get_error_display_message(e)
179-
if display_message:
180-
raise AirbyteTracedException.from_exception(e, message=display_message) from e
181-
raise e
182-
finally:
183-
logger.info(f"Finished syncing {self.name}")
184-
logger.info(timer.report())
185-
186-
logger.info(f"Finished syncing {self.name}")

airbyte-integrations/connectors/source-hubspot/source_hubspot/streams.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
import requests
1616
from airbyte_cdk.entrypoint import logger
1717
from airbyte_cdk.models import SyncMode
18-
from airbyte_cdk.sources.streams.availability_strategy import AvailabilityStrategy
1918
from airbyte_cdk.sources.streams.http import HttpStream
2019
from airbyte_cdk.sources.streams.http.requests_native_auth import Oauth2Authenticator, TokenAuthenticator
2120
from airbyte_cdk.sources.utils.transform import TransformConfig, TypeTransformer
@@ -206,14 +205,9 @@ class Stream(HttpStream, ABC):
206205
primary_key = None
207206
filter_old_records: bool = True
208207
denormalize_records: bool = False # one record from API response can result in multiple records emitted
209-
raise_on_http_errors: bool = True
210208
granted_scopes: Set = None
211209
properties_scopes: Set = None
212210

213-
@property
214-
def availability_strategy(self) -> Optional["AvailabilityStrategy"]:
215-
return None
216-
217211
@property
218212
@abstractmethod
219213
def scopes(self) -> Set[str]:
@@ -263,12 +257,6 @@ def __init__(self, api: API, start_date: Union[str, pendulum.datetime], credenti
263257
if creds_title in (OAUTH_CREDENTIALS, PRIVATE_APP_CREDENTIALS):
264258
self._authenticator = api.get_authenticator()
265259

266-
def should_retry(self, response: requests.Response) -> bool:
267-
if response.status_code == HTTPStatus.FORBIDDEN:
268-
setattr(self, "raise_on_http_errors", False)
269-
logger.warning("You have not permission to API for this stream. " "Please check your scopes for Hubspot account.")
270-
return super().should_retry(response)
271-
272260
def backoff_time(self, response: requests.Response) -> Optional[float]:
273261
if response.status_code == codes.too_many_requests:
274262
return float(response.headers.get("Retry-After", 3))

airbyte-integrations/connectors/source-hubspot/unit_tests/conftest.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,6 @@ def some_credentials_fixture():
4242
return {"credentials_title": "Private App Credentials", "access_token": "wrong token"}
4343

4444

45-
@pytest.fixture(name="creds_with_wrong_permissions")
46-
def creds_with_wrong_permissions():
47-
return {"credentials_title": "Private App Credentials", "access_token": "THIS-IS-THE-ACCESS_TOKEN"}
48-
49-
5045
@pytest.fixture(name="fake_properties_list")
5146
def fake_properties_list():
5247
return [f"property_number_{i}" for i in range(NUMBER_OF_PROPERTIES)]

airbyte-integrations/connectors/source-hubspot/unit_tests/test_source.py

Lines changed: 20 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from source_hubspot.errors import HubspotRateLimited
1414
from source_hubspot.helpers import APIv3Property
1515
from source_hubspot.source import SourceHubspot
16-
from source_hubspot.streams import API, Companies, Deals, Engagements, Products, Stream, Workflows
16+
from source_hubspot.streams import API, Companies, Deals, Engagements, Products, Stream
1717

1818
from .utils import read_full_refresh, read_incremental
1919

@@ -134,38 +134,30 @@ def test_check_connection_backoff_on_server_error(requests_mock, config):
134134
assert not error
135135

136136

137-
def test_wrong_permissions_api_key(requests_mock, creds_with_wrong_permissions, common_params, caplog):
138-
"""
139-
Error with API Key Permissions to particular stream,
140-
typically this issue raises along with calling `workflows` stream with API Key
141-
that doesn't have required permissions to read the stream.
142-
"""
143-
144-
# Mapping tipical response for mocker
137+
def test_stream_forbidden(requests_mock, config, caplog):
145138
json = {
146139
"status": "error",
147-
"message": f'This hapikey ({creds_with_wrong_permissions.get("api_key")}) does not have proper permissions! (requires any of [automation-access])',
148-
"correlationId": "2fe0a9af-3609-45c9-a4d7-83a1774121aa",
149-
}
150-
151-
# We expect something like this
152-
expected_warining_message = {
153-
"type": "LOG",
154-
"log": {
155-
"level": "WARN",
156-
"message": f'Stream `workflows` cannot be procced. This hapikey ({creds_with_wrong_permissions.get("api_key")}) does not have proper permissions! (requires any of [automation-access])',
157-
},
140+
"message": "This access_token does not have proper permissions!",
158141
}
142+
requests_mock.get("https://api.hubapi.com/automation/v3/workflows", json=json, status_code=403)
159143

160-
# Create test_stream instance
161-
test_stream = Workflows(**common_params)
162-
163-
# Mocking Request
164-
requests_mock.register_uri("GET", test_stream.url, json=json, status_code=403)
165-
records = list(test_stream.read_records(sync_mode=SyncMode.full_refresh))
144+
catalog = ConfiguredAirbyteCatalog.parse_obj({
145+
"streams": [
146+
{
147+
"stream": {
148+
"name": "workflows",
149+
"json_schema": {},
150+
"supported_sync_modes": ["full_refresh"],
151+
},
152+
"sync_mode": "full_refresh",
153+
"destination_sync_mode": "overwrite"
154+
}
155+
]
156+
})
166157

167-
# match logged expected logged warning message with output given from preudo-output
168-
assert expected_warining_message["log"]["message"] in caplog.text
158+
records = list(SourceHubspot().read(logger, config, catalog, {}))
159+
assert json["message"] in caplog.text
160+
records = [r for r in records if r.type == Type.RECORD]
169161
assert not records
170162

171163

@@ -328,17 +320,6 @@ def configured_catalog_fixture():
328320
return ConfiguredAirbyteCatalog.parse_obj(configured_catalog)
329321

330322

331-
def test_it_should_not_read_quotes_stream_if_it_does_not_exist_in_client(oauth_config, configured_catalog):
332-
"""
333-
If 'quotes' stream is not in the client, it should skip it.
334-
"""
335-
source = SourceHubspot()
336-
337-
all_records = list(source.read(logger, config=oauth_config, catalog=configured_catalog, state=None))
338-
records = [record for record in all_records if record.type == Type.RECORD]
339-
assert not records
340-
341-
342323
def test_search_based_stream_should_not_attempt_to_get_more_than_10k_records(requests_mock, common_params, fake_properties_list):
343324
"""
344325
If there are more than 10,000 records that would be returned by the Hubspot search endpoint,

docs/integrations/sources/hubspot.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,9 @@ Now that you have set up the Hubspot source connector, check out the following H
126126

127127
| Version | Date | Pull Request | Subject |
128128
|:--------|:-----------|:---------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------|
129-
| 0.3.1 | 2023-01-27 | [22009](https://github.com/airbytehq/airbyte/pull/22009) | Set `AvailabilityStrategy` for streams explicitly to `None` |
130-
| 0.3.0 | 2022-10-27 | [18546](https://github.com/airbytehq/airbyte/pull/18546) | Sunsetting API Key authentication. `Quotes` stream is no longer available |
129+
| 0.3.2 | 2023-02-07 | [22479](https://github.com/airbytehq/airbyte/pull/22479) | Turn on default HttpAvailabilityStrategy |
130+
| 0.3.1 | 2023-01-27 | [22009](https://github.com/airbytehq/airbyte/pull/22009) | Set `AvailabilityStrategy` for streams explicitly to `None` |
131+
| 0.3.0 | 2022-10-27 | [18546](https://github.com/airbytehq/airbyte/pull/18546) | Sunsetting API Key authentication. `Quotes` stream is no longer available |
131132
| 0.2.2 | 2022-10-03 | [16914](https://github.com/airbytehq/airbyte/pull/16914) | Fix 403 forbidden error validation |
132133
| 0.2.1 | 2022-09-26 | [17120](https://github.com/airbytehq/airbyte/pull/17120) | Migrate to per-stream state. |
133134
| 0.2.0 | 2022-09-13 | [16632](https://github.com/airbytehq/airbyte/pull/16632) | Remove Feedback Submissions stream as the one using unstable (beta) API. |

0 commit comments

Comments
 (0)