Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

source-hubspot-native: Expand HubSpot Engagement associations #2505

Merged
merged 1 commit into from
Mar 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions source-hubspot-native/acmeCo/engagements.schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,90 @@ properties:
default: {}
title: Associations
type: object
contacts:
default: []
items:
type: integer
title: Contacts
type: array
companies:
default: []
items:
type: integer
title: Companies
type: array
deals:
default: []
items:
type: integer
title: Deals
type: array
tickets:
default: []
items:
type: integer
title: Tickets
type: array
content:
default: []
items:
type: integer
title: Content
type: array
quotes:
default: []
items:
type: integer
title: Quotes
type: array
orders:
default: []
items:
type: integer
title: Orders
type: array
emails:
default: []
items:
type: integer
title: Emails
type: array
meetings:
default: []
items:
type: integer
title: Meetings
type: array
notes:
default: []
items:
type: integer
title: Notes
type: array
tasks:
default: []
items:
type: integer
title: Tasks
type: array
carts:
default: []
items:
type: integer
title: Carts
type: array
partner_clients:
default: []
items:
type: integer
title: Partner Clients
type: array
marketing_event:
default: []
items:
type: integer
title: Marketing Event
type: array
required:
- id
- createdAt
Expand Down
91 changes: 65 additions & 26 deletions source-hubspot-native/source_hubspot_native/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,20 @@ class Names(StrEnum):
quotes = auto()
subscriptions = auto()
tickets = auto()
emails = auto()
meetings = auto()
notes = auto()
tasks = auto()
content = auto()
orders = auto()
carts = auto()
partner_clients = auto()
marketing_event = auto()


# A Property is a HubSpot or HubSpot-user defined attribute that's
# attached to a HubSpot CRM object.
class Property(BaseDocument, extra="allow"):

name: str = ""
calculated: bool = False
hubspotObject: str = "unknown" # Added by us.
Expand All @@ -126,7 +134,6 @@ class Properties(BaseDocument, extra="forbid"):


class DealPipeline(BaseDocument, extra="allow"):

createdAt: AwareDatetime | None
updatedAt: AwareDatetime | None

Expand All @@ -136,7 +143,6 @@ class DealPipelines(BaseDocument, extra="forbid"):


class Owner(BaseDocument, extra="allow"):

createdAt: AwareDatetime | None
updatedAt: AwareDatetime | None

Expand Down Expand Up @@ -185,7 +191,6 @@ def _post_init(self) -> Self:
k: v for k, v in self.propertiesWithHistory.items() if len(v)
}


# If the model has attached inline associations,
# hoist them to corresponding arrays. Then clear associations.
for ae in self.ASSOCIATED_ENTITIES:
Expand Down Expand Up @@ -222,9 +227,40 @@ class Deal(BaseCRMObject):


class Engagement(BaseCRMObject):
ASSOCIATED_ENTITIES = [Names.deals]
ASSOCIATED_ENTITIES = [
Names.contacts,
Names.companies,
Names.deals,
Names.tickets,
Names.quotes,
Names.orders,
Names.emails,
Names.meetings,
Names.notes,
Names.tasks,
Names.content,
Names.orders,
Names.carts,
Names.partner_clients,
Names.marketing_event,
]

contacts: list[int] = []
companies: list[int] = []
deals: list[int] = []
tickets: list[int] = []
content: list[int] = []
quotes: list[int] = []
orders: list[int] = []
emails: list[int] = []
meetings: list[int] = []
notes: list[int] = []
tasks: list[int] = []
content: list[int] = []
orders: list[int] = []
carts: list[int] = []
partner_clients: list[int] = []
marketing_event: list[int] = []


class Ticket(BaseCRMObject):
Expand All @@ -241,7 +277,14 @@ class Product(BaseCRMObject):


class LineItem(BaseCRMObject):
ASSOCIATED_ENTITIES = [Names.commerce_payments, Names.products, Names.deals, Names.invoices, Names.quotes, Names.subscriptions]
ASSOCIATED_ENTITIES = [
Names.commerce_payments,
Names.products,
Names.deals,
Names.invoices,
Names.quotes,
Names.subscriptions,
]

commerce_payments: list[int] = []
products: list[int] = []
Expand All @@ -253,7 +296,6 @@ class LineItem(BaseCRMObject):

# An Association, as returned by the v4 associations API.
class Association(BaseModel, extra="forbid"):

class Type(BaseModel, extra="forbid"):
category: Literal["HUBSPOT_DEFINED", "USER_DEFINED"]
# Type IDs are defined here: https://developers.hubspot.com/docs/api/crm/associations
Expand Down Expand Up @@ -296,7 +338,6 @@ class Paging(BaseModel, extra="forbid"):
# Common shape of a v3 API paged listing for a GET request to the objects endpoint for a particular
# object.
class PageResult(BaseModel, Generic[Item], extra="forbid"):

class Cursor(BaseModel, extra="forbid"):
after: str
link: str
Expand All @@ -311,7 +352,6 @@ class Paging(BaseModel, extra="forbid"):
# Common shape of a v3 search API listing, which is the same as PageResult but includes a field for
# the total number of records returned, and doesn't have a "link" in the paging.next object.
class SearchPageResult(BaseModel, Generic[Item], extra="forbid"):

class Cursor(BaseModel, extra="forbid"):
after: str

Expand All @@ -325,7 +365,6 @@ class Paging(BaseModel, extra="forbid"):

# Common shape of a v3 API batch read.
class BatchResult(BaseModel, Generic[Item], extra="forbid"):

class Error(BaseModel, extra="forbid"):
status: Literal["error"]
category: Literal["OBJECT_NOT_FOUND"]
Expand All @@ -349,7 +388,6 @@ class Error(BaseModel, extra="forbid"):


class OldRecentCompanies(BaseModel):

class Item(BaseModel):
class Properties(BaseModel):
class Timestamp(BaseModel):
Expand All @@ -367,7 +405,6 @@ class Timestamp(BaseModel):


class OldRecentContacts(BaseModel):

class Item(BaseModel):
class Properties(BaseModel):
class Timestamp(BaseModel):
Expand All @@ -379,13 +416,12 @@ class Timestamp(BaseModel):
properties: Properties

contacts: list[Item]
has_more: bool = Field(alias="has-more") # type: ignore
time_offset: int = Field(alias="time-offset") # type: ignore
vid_offset: int = Field(alias="vid-offset") # type: ignore
has_more: bool = Field(alias="has-more") # type: ignore
time_offset: int = Field(alias="time-offset") # type: ignore
vid_offset: int = Field(alias="vid-offset") # type: ignore


class OldRecentDeals(BaseModel):

class Item(BaseModel):
class Properties(BaseModel):
class Timestamp(BaseModel):
Expand All @@ -403,7 +439,6 @@ class Timestamp(BaseModel):


class OldRecentEngagements(BaseModel):

class Item(BaseModel):
class Engagement(BaseModel):
id: int
Expand All @@ -425,6 +460,7 @@ class OldRecentTicket(BaseModel):
# EmailEvent and EmailEventsResponse represent an email event and the shape of the email events API
# response, respectively.


class EmailEvent(BaseDocument, extra="allow"):
id: str
created: AwareDatetime
Expand All @@ -442,12 +478,12 @@ class EmailEvent(BaseDocument, extra="allow"):
"STATUSCHANGE",
"SPAMREPORT",
"SUPPRESSED",
"SUPPRESSION", # "SUPPRESSION" is documented in HubSpot's docs, but "SUPPRESSED" isn't. We've seen "SUPPRESSED" events, so "SUPPRESSION" events might not actually occur.
"UNBOUNCE", # This is not actually a type reported by HubSpot, but the absence of the "type" field means its an UNBOUNCE type.
"SUPPRESSION", # "SUPPRESSION" is documented in HubSpot's docs, but "SUPPRESSED" isn't. We've seen "SUPPRESSED" events, so "SUPPRESSION" events might not actually occur.
"UNBOUNCE", # This is not actually a type reported by HubSpot, but the absence of the "type" field means its an UNBOUNCE type.
] = Field(
default="UNBOUNCE",
# Don't schematize the default value.
json_schema_extra=lambda x: x.pop('default'), # type: ignore
# Don't schematize the default value.
json_schema_extra=lambda x: x.pop("default"), # type: ignore
)


Expand Down Expand Up @@ -478,7 +514,6 @@ class CustomObjectSchema(BaseDocument, extra="allow"):
# This is the shape of a response from the V3 search API for custom objects. As above, we are
# modeling only the minimum needed to get the IDs and modification time.
class CustomObjectSearchResult(BaseModel):

class Properties(BaseModel):
hs_lastmodifieddate: AwareDatetime

Expand All @@ -488,10 +523,14 @@ def set_lastmodifieddate(cls, values):

# The "Contacts" object uses `lastmodifieddate`, while everything
# else uses `hs_lastmodifieddate`.
if 'lastmodifieddate' in values:
values['hs_lastmodifieddate'] = datetime.fromisoformat(values.pop('lastmodifieddate'))
elif 'hs_lastmodifieddate' in values:
values['hs_lastmodifieddate'] = datetime.fromisoformat(values.pop('hs_lastmodifieddate'))
if "lastmodifieddate" in values:
values["hs_lastmodifieddate"] = datetime.fromisoformat(
values.pop("lastmodifieddate")
)
elif "hs_lastmodifieddate" in values:
values["hs_lastmodifieddate"] = datetime.fromisoformat(
values.pop("hs_lastmodifieddate")
)
return values

id: int
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4993,6 +4993,9 @@
"uuid": "DocUUIDPlaceholder-329Bb50aa48EAa9ef"
},
"archived": false,
"companies": [
19015593502
],
"createdAt": "2024-02-15T17:25:55.752000Z",
"id": 47494434080,
"properties": {
Expand Down
Loading
Loading