Skip to content

Commit e2bf235

Browse files
committed
update search endpoint to include associations
1 parent 3bfc440 commit e2bf235

File tree

3 files changed

+65
-38
lines changed

3 files changed

+65
-38
lines changed

sources/hubspot/__init__.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@
5050
from .settings import (
5151
ALL_OBJECTS,
5252
ARCHIVED_PARAM,
53+
CRM_OBJECT_ASSOCIATIONS,
5354
CRM_OBJECT_ENDPOINTS,
5455
CRM_PIPELINES_ENDPOINT,
55-
CRM_SEARCH_OBJECT_ENDPOINTS,
5656
ENTITY_PROPERTIES,
5757
LAST_MODIFIED_PROPERTY,
5858
HUBSPOT_CREATION_DATE,
@@ -95,7 +95,13 @@ def fetch_data_for_properties(
9595
logger.info(f"Fetching data for {object_type}.")
9696
# The Hubspot API expects a comma separated string as properties
9797
joined_props = ",".join(sorted(props))
98-
params: Dict[str, Any] = {"properties": joined_props, "limit": 100}
98+
associations = CRM_OBJECT_ASSOCIATIONS[object_type]
99+
joined_associations = ",".join(associations)
100+
params: Dict[str, Any] = {
101+
"associations": joined_associations,
102+
"properties": joined_props,
103+
"limit": 100,
104+
}
99105
context: Optional[Dict[str, Any]] = (
100106
{SOFT_DELETE_KEY: False} if soft_delete else None
101107
)
@@ -120,8 +126,9 @@ def fetch_data_for_properties(
120126

121127
try:
122128
yield from search_data(
123-
CRM_SEARCH_OBJECT_ENDPOINTS[object_type],
129+
CRM_OBJECT_ENDPOINTS[object_type],
124130
api_key,
131+
associations=associations,
125132
params=search_params,
126133
context=context,
127134
)
@@ -228,7 +235,7 @@ def crm_object_history(
228235
# This is especially relevant for columns of type "number" in Hubspot
229236
# that are returned as strings by the API
230237
for batch in fetch_property_history(
231-
CRM_SEARCH_OBJECT_ENDPOINTS[object_type],
238+
CRM_OBJECT_ENDPOINTS[object_type],
232239
api_key,
233240
",".join(sorted(props_to_type.keys())),
234241
):

sources/hubspot/helpers.py

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@
88
from dlt.common.schema.typing import TColumnSchema
99
from dlt.sources.helpers import requests
1010

11-
from .settings import OBJECT_TYPE_PLURAL, HS_TO_DLT_TYPE
11+
from .settings import (
12+
CRM_ASSOCIATIONS_ENDPOINT,
13+
CRM_SEARCH_ENDPOINT,
14+
OBJECT_TYPE_PLURAL,
15+
HS_TO_DLT_TYPE,
16+
)
1217

1318
BASE_URL = "https://api.hubapi.com/"
1419

@@ -62,7 +67,7 @@ def search_pagination(
6267
if _next:
6368
after = _next["after"]
6469
# Get the next page response
65-
r = requests.post(url, headers=headers, json={**params, after: after})
70+
r = requests.post(url, headers=headers, json={**params, "after": after})
6671
return r.json() # type: ignore
6772
else:
6873
return None
@@ -149,11 +154,12 @@ def fetch_property_history(
149154
def search_data(
150155
endpoint: str,
151156
api_key: str,
157+
associations: Optional[List[str]] = None,
152158
params: Optional[Dict[str, Any]] = None,
153159
context: Optional[Dict[str, Any]] = None,
154160
) -> Iterator[List[Dict[str, Any]]]:
155161
# Construct the URL and headers for the API request
156-
url = get_url(endpoint)
162+
url = get_url(CRM_SEARCH_ENDPOINT.format(crm_endpoint=endpoint))
157163
headers = _get_headers(api_key)
158164

159165
# Make the API request
@@ -168,7 +174,9 @@ def search_data(
168174
# Yield the properties of each result in the API response
169175
while _data is not None:
170176
if "results" in _data:
171-
yield _data_to_objects(_data, headers, context)
177+
yield _data_to_objects(
178+
_data, endpoint, headers, associations=associations, context=context
179+
)
172180

173181
# Follow pagination links if they exist
174182
_data = search_pagination(url, _data, headers, params)
@@ -220,15 +228,17 @@ def fetch_data(
220228
# Yield the properties of each result in the API response
221229
while _data is not None:
222230
if "results" in _data:
223-
yield _data_to_objects(_data, headers, context)
231+
yield _data_to_objects(_data, endpoint, headers, context=context)
224232

225233
# Follow pagination links if they exist
226234
_data = pagination(_data, headers)
227235

228236

229237
def _data_to_objects(
230238
data: Any,
239+
endpoint: str,
231240
headers: Dict[str, str],
241+
associations: Optional[List[str]] = None,
232242
context: Optional[Dict[str, Any]] = None,
233243
) -> List[Dict[str, Any]]:
234244
_objects: List[Dict[str, Any]] = []
@@ -240,19 +250,36 @@ def _data_to_objects(
240250
if "associations" in _result:
241251
for association in _result["associations"]:
242252
__data = _result["associations"][association]
243-
244-
__values = extract_association_data(_obj, __data, association, headers)
245-
246-
# remove duplicates from list of dicts
247-
__values = [dict(t) for t in {tuple(d.items()) for d in __values}]
248-
249-
_obj[association] = __values
253+
_add_association_data(__data, association, headers, _obj)
254+
elif associations is not None:
255+
for association in associations:
256+
__endpoint = get_url(
257+
CRM_ASSOCIATIONS_ENDPOINT.format(
258+
crm_endpoint=endpoint,
259+
object_id=_result["id"],
260+
association=association,
261+
)
262+
)
263+
r = requests.get(__endpoint, headers=headers, params={"limit": 500})
264+
__data = r.json()
265+
_add_association_data(__data, association, headers, _obj)
250266
if context:
251267
_obj.update(context)
252268
_objects.append(_obj)
253269
return _objects
254270

255271

272+
def _add_association_data(
273+
data: Any, association: str, headers: Dict[str, str], obj: Any
274+
) -> None:
275+
__values = extract_association_data(obj, data, association, headers)
276+
277+
# remove duplicates from list of dicts
278+
__values = [dict(t) for t in {tuple(d.items()) for d in __values}]
279+
280+
obj[association] = __values
281+
282+
256283
def _get_property_names_types(
257284
api_key: str, object_type: str
258285
) -> Dict[str, Union[str, None]]:

sources/hubspot/settings.py

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,8 @@
66
HUBSPOT_CREATION_DATE = pendulum.datetime(year=2006, month=6, day=1)
77
STARTDATE = pendulum.datetime(year=2024, month=2, day=10)
88

9-
CRM_CONTACTS_SEARCH_ENDPOINT = "/crm/v3/objects/contacts/search"
10-
CRM_COMPANIES_SEARCH_ENDPOINT = "/crm/v3/objects/companies/search"
11-
CRM_DEALS_SEARCH_ENDPOINT = "/crm/v3/objects/deals/search"
12-
CRM_PRODUCTS_SEARCH_ENDPOINT = "/crm/v3/objects/products/search"
13-
CRM_TICKETS_SEARCH_ENDPOINT = "/crm/v3/objects/tickets/search"
14-
CRM_QUOTES_SEARCH_ENDPOINT = "/crm/v3/objects/quotes/search"
15-
16-
CRM_SEARCH_OBJECT_ENDPOINTS = {
17-
"contact": CRM_CONTACTS_SEARCH_ENDPOINT,
18-
"company": CRM_COMPANIES_SEARCH_ENDPOINT,
19-
"deal": CRM_DEALS_SEARCH_ENDPOINT,
20-
"product": CRM_PRODUCTS_SEARCH_ENDPOINT,
21-
"ticket": CRM_TICKETS_SEARCH_ENDPOINT,
22-
"quote": CRM_QUOTES_SEARCH_ENDPOINT,
23-
}
24-
25-
CRM_CONTACTS_ENDPOINT = (
26-
"/crm/v3/objects/contacts?associations=deals,products,tickets,quotes"
27-
)
28-
CRM_COMPANIES_ENDPOINT = (
29-
"/crm/v3/objects/companies?associations=contacts,deals,products,tickets,quotes"
30-
)
9+
CRM_CONTACTS_ENDPOINT = "/crm/v3/objects/contacts"
10+
CRM_COMPANIES_ENDPOINT = "/crm/v3/objects/companies"
3111
CRM_DEALS_ENDPOINT = "/crm/v3/objects/deals"
3212
CRM_PRODUCTS_ENDPOINT = "/crm/v3/objects/products"
3313
CRM_TICKETS_ENDPOINT = "/crm/v3/objects/tickets"
@@ -36,6 +16,9 @@
3616
CRM_PROPERTIES_ENDPOINT = "/crm/v3/properties/{objectType}/{property_name}"
3717
CRM_PIPELINES_ENDPOINT = "/crm/v3/pipelines/{objectType}"
3818

19+
CRM_SEARCH_ENDPOINT = "{crm_endpoint}/search"
20+
CRM_ASSOCIATIONS_ENDPOINT = "{crm_endpoint}/{object_id}/associations/{association}"
21+
3922
CRM_OBJECT_ENDPOINTS = {
4023
"contact": CRM_CONTACTS_ENDPOINT,
4124
"company": CRM_COMPANIES_ENDPOINT,
@@ -46,6 +29,16 @@
4629
"owner": CRM_OWNERS_ENDPOINT,
4730
}
4831

32+
CRM_OBJECT_ASSOCIATIONS = {
33+
"contact": ["deals", "products", "tickets", "quotes"],
34+
"company": ["contacts", "deals", "products", "tickets", "quotes"],
35+
"deal": [],
36+
"product": [],
37+
"ticket": [],
38+
"quote": [],
39+
"owner": [],
40+
}
41+
4942
WEB_ANALYTICS_EVENTS_ENDPOINT = "/events/v3/events?objectType={objectType}&objectId={objectId}&occurredAfter={occurredAfter}&occurredBefore={occurredBefore}&sort=-occurredAt"
5043

5144
OBJECT_TYPE_SINGULAR = {

0 commit comments

Comments
 (0)