Skip to content

Commit d88a81c

Browse files
🐛 Source HubSpot: fix property scopes (#18624)
* 🐛 Source HubSpot: fix property scopes * 🐛 Source HubSpot: bump version * 🐛 Source Hubspot: fix properties for auth * 🐛 Source Hubspot: fix log messages * auto-bump connector version Co-authored-by: Octavia Squidington III <[email protected]>
1 parent 0944f5e commit d88a81c

File tree

5 files changed

+26
-6
lines changed

5 files changed

+26
-6
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
@@ -572,7 +572,7 @@
572572
- name: HubSpot
573573
sourceDefinitionId: 36c891d9-4bd9-43ac-bad2-10e12756272c
574574
dockerRepository: airbyte/source-hubspot
575-
dockerImageTag: 0.2.2
575+
dockerImageTag: 0.2.3
576576
documentationUrl: https://docs.airbyte.com/integrations/sources/hubspot
577577
icon: hubspot.svg
578578
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
@@ -5324,7 +5324,7 @@
53245324
supportsNormalization: false
53255325
supportsDBT: false
53265326
supported_destination_sync_modes: []
5327-
- dockerImage: "airbyte/source-hubspot:0.2.2"
5327+
- dockerImage: "airbyte/source-hubspot:0.2.3"
53285328
spec:
53295329
documentationUrl: "https://docs.airbyte.com/integrations/sources/hubspot"
53305330
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.2.2
37+
LABEL io.airbyte.version=0.2.3
3838
LABEL io.airbyte.name=airbyte/source-hubspot

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#
44

55
import logging
6+
from itertools import chain
67
from typing import Any, Iterator, List, Mapping, MutableMapping, Optional, Tuple, Union
78

89
import requests
@@ -130,6 +131,12 @@ def streams(self, config: Mapping[str, Any]) -> List[Stream]:
130131
available_streams = [stream for stream in streams if stream.scope_is_granted(granted_scopes)]
131132
unavailable_streams = [stream for stream in streams if not stream.scope_is_granted(granted_scopes)]
132133
self.logger.info(f"The following streams are unavailable: {[s.name for s in unavailable_streams]}")
134+
partially_available_streams = [stream for stream in streams if not stream.properties_scope_is_granted()]
135+
required_scoped = set(chain(*[x.properties_scopes for x in partially_available_streams]))
136+
self.logger.info(
137+
f"The following streams are partially available: {[s.name for s in partially_available_streams]}, "
138+
f"add the following scopes to download all available data: {required_scoped}"
139+
)
133140
else:
134141
self.logger.info("No scopes to grant when authenticating with API key.")
135142
available_streams = streams

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

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -209,13 +209,16 @@ class Stream(HttpStream, ABC):
209209
filter_old_records: bool = True
210210
denormalize_records: bool = False # one record from API response can result in multiple records emitted
211211
raise_on_http_errors: bool = True
212+
granted_scopes: Set = None
213+
properties_scopes: Set = None
212214

213215
@property
214216
@abstractmethod
215217
def scopes(self) -> Set[str]:
216218
"""Set of required scopes. Users need to grant at least one of the scopes for the stream to be avaialble to them"""
217219

218220
def scope_is_granted(self, granted_scopes: Set[str]) -> bool:
221+
self.granted_scopes = set(granted_scopes)
219222
if not self.scopes:
220223
return True
221224
else:
@@ -631,16 +634,24 @@ def _get_field_props(field_type: str) -> Mapping[str, List[str]]:
631634
@lru_cache()
632635
def properties(self) -> Mapping[str, Any]:
633636
"""Some entities has dynamic set of properties, so we trying to resolve those at runtime"""
634-
if not self.entity:
635-
return {}
636-
637637
props = {}
638+
if not self.entity:
639+
return props
640+
if not self.properties_scope_is_granted():
641+
logger.warning(
642+
f"Check your API key has the following permissions granted: {self.properties_scopes}, "
643+
f"to be able to fetch all properties available."
644+
)
645+
return props
638646
data, response = self._api.get(f"/properties/v2/{self.entity}/properties")
639647
for row in data:
640648
props[row["name"]] = self._get_field_props(row["type"])
641649

642650
return props
643651

652+
def properties_scope_is_granted(self):
653+
return not self.properties_scopes - self.granted_scopes if self.properties_scopes and self.granted_scopes else True
654+
644655
def _flat_associations(self, records: Iterable[MutableMapping]) -> Iterable[MutableMapping]:
645656
"""When result has associations we prefer to have it flat, so we transform this:
646657
@@ -1127,6 +1138,7 @@ class ContactsListMemberships(Stream):
11271138
page_field = "vid-offset"
11281139
primary_key = "canonical-vid"
11291140
scopes = {"crm.objects.contacts.read"}
1141+
properties_scopes = {"crm.schemas.contacts.read"}
11301142

11311143
def _transform(self, records: Iterable) -> Iterable:
11321144
"""Extracting list membership records from contacts
@@ -1421,6 +1433,7 @@ class PropertyHistory(Stream):
14211433
limit_field = "count"
14221434
limit = 100
14231435
scopes = {"crm.objects.contacts.read"}
1436+
properties_scopes = {"crm.schemas.contacts.read"}
14241437

14251438
def request_params(
14261439
self,

0 commit comments

Comments
 (0)