diff --git a/hs_api/.env.template b/hs_api/.env.template index 86577e4..0087396 100644 --- a/hs_api/.env.template +++ b/hs_api/.env.template @@ -1,5 +1,5 @@ HUBSPOT_ACCESS_TOKEN= -HUBS_PIPELINE_ID= +HUBSPOT_TEST_PIPELINE_ID= HUBSPOT_TEST_ACCESS_TOKEN= HUBSPOT_TEST_PIPELINE_ID= diff --git a/hs_api/api/hubspot_api.py b/hs_api/api/hubspot_api.py index eeb01e1..3ee9a2e 100644 --- a/hs_api/api/hubspot_api.py +++ b/hs_api/api/hubspot_api.py @@ -1,5 +1,8 @@ import time +from collections.abc import Generator +from datetime import datetime from math import ceil +from typing import Dict, Optional import requests from hubspot import HubSpot @@ -65,6 +68,8 @@ def create_lookup(self): "contact": self._client.crm.contacts.basic_api.create, "company": self._client.crm.companies.basic_api.create, "deal": self._client.crm.deals.basic_api.create, + "ticket": self._client.crm.tickets.basic_api.create, + "email": self._client.crm.objects.emails.basic_api.create, } @property @@ -72,6 +77,7 @@ def search_lookup(self): return { "contact": self._client.crm.contacts.search_api.do_search, "company": self._client.crm.companies.search_api.do_search, + "email": self._client.crm.objects.emails.search_api.do_search, } @property @@ -157,6 +163,33 @@ def find_contact(self, property_name, value): response = self._find("contact", property_name, value, sort) return response.results + def find_contact_iter( + self, property_name: str, value: str, limit: int = 20 + ) -> Generator[Dict, None, None]: + """ + Searches for a contact in Hubspot and returns results as a generator + + :param property_name: The field name from Hubspot + :param value: The value to search in the field property_name + :param limit: The number of results to return per iteration + :return: Dictionary of results + """ + sort = [{"propertyName": "hs_object_id", "direction": "ASCENDING"}] + after = 0 + + while True: + response = self._find( + "contact", property_name, value, sort, limit=limit, after=after + ) + if not response.results: + break + + yield response.results + + if not response.paging: + break + after = response.paging.next.after + def find_company(self, property_name, value): sort = [{"propertyName": "hs_lastmodifieddate", "direction": "DESCENDING"}] @@ -164,6 +197,33 @@ def find_company(self, property_name, value): response = self._find("company", property_name, value, sort) return response.results + def find_company_iter( + self, property_name: str, value: str, limit: int = 20 + ) -> Generator[Dict, None, None]: + """ + Searches for a company in Hubspot and returns results as a generator + + :param property_name: The field name from Hubspot + :param value: The value to search in the field property_name + :param limit: The number of results to return per iteration + :return: Dictionary of results + """ + sort = [{"propertyName": "hs_lastmodifieddate", "direction": "DESCENDING"}] + after = 0 + + while True: + response = self._find( + "company", property_name, value, sort, limit=limit, after=after + ) + if not response.results: + break + + yield response.results + + if not response.paging: + break + after = response.paging.next.after + def find_deal(self, property_name, value): pipeline_filter = Filter( property_name="pipeline", operator="EQ", value=self.pipeline_id @@ -196,6 +256,16 @@ def _find_owner_by_id(self, owner_id): response = self._client.crm.owners.owners_api.get_by_id(owner_id=owner_id) return response + def find_all_owners(self): + after = None + while True: + response = self._client.crm.owners.owners_api.get_page(after=after) + yield response + + if not response.paging: + break + after = response.paging.next.after + def find_owner(self, property_name, value): if property_name not in ("id", "email"): raise NameError( @@ -320,6 +390,9 @@ def find_all_tickets( else: after = None + def find_ticket(self, ticket_id): + return self._client.crm.tickets.basic_api.get_by_id(ticket_id) + def find_all_contacts_in_list(self, contact_list_id: str) -> object: """ This function will return all contacts in a contact list. @@ -510,6 +583,45 @@ def create_deal( ) return response + def create_ticket(self, subject, **properties): + properties = dict(subject=subject, **properties) + response = self._create("ticket", properties) + return response + + def create_email( + self, + hs_timestamp: Optional[datetime] = None, + hs_email_direction: Optional[str] = "EMAIL", + **properties, + ): + """ + See documentation at https://developers.hubspot.com/docs/api/crm/email + + :param hs_timestamp: This field marks the email's time of creation and determines where the email sits on the + record timeline. You can use either a Unix timestamp in milliseconds or UTC format. If not provided, then the + current time is used. + :param hs_email_direction: The direction the email was sent in. Possible values include: + + EMAIL: the email was sent from the CRM or sent and logged to the CRM with the BCC address. + INCOMING_EMAIL: the email was a reply to a logged outgoing email. + + FORWARDED_EMAIL: the email was forwarded to the CRM. + :param properties: Dictionary of properties as documented on hubspot + :return: + """ + if not hs_timestamp: + hs_timestamp = int(datetime.now().timestamp()) + else: + hs_timestamp = int(hs_timestamp.timestamp()) + + properties = dict( + hs_timestamp=hs_timestamp, + hs_email_direction=hs_email_direction, + **properties, + ) + response = self._create("email", properties) + return response + def delete_contact(self, value, property_name=None): try: public_gdpr_delete_input = PublicGdprDeleteInput( @@ -537,6 +649,20 @@ def delete_deal(self, deal_id): except ApiException as e: print(f"Exception when deleting deal: {e}\n") + def delete_ticket(self, ticket_id): + try: + api_response = self._client.crm.tickets.basic_api.archive(ticket_id) + return api_response + except ApiException as e: + print(f"Exception when deleting ticket: {e}\n") + + def delete_email(self, email_id): + try: + api_response = self._client.crm.objects.emails.basic_api.archive(email_id) + return api_response + except ApiException as e: + print(f"Exception when deleting email: {e}\n") + def update_company(self, object_id, **properties): response = self._update("company", object_id, properties) return response diff --git a/requirements.txt b/requirements.txt index e9f6f99..be78bd4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,9 @@ requests -isort==5.10.1 -flake8==4.0.1 -black==22.3.0 -pytest==6.2.5 -python-dotenv==0.19.2 -hubspot-api-client==5.0.1 +isort==5.12.0 +flake8==6.0.0 +black==23.1.0 +pytest==7.2.2 +python-dotenv==1.0.0 +hubspot-api-client==7.5.0 +tenacity==8.2.2 diff --git a/setup.py b/setup.py index 14ae409..c9aa770 100644 --- a/setup.py +++ b/setup.py @@ -8,6 +8,6 @@ description="Superscript Hubspot API", author="Superscript", author_email="paul.lucas@gosuperscript.com", - install_requires=["requests", "python-dotenv==0.19.2", "hubspot-api-client==5.0.1"], + install_requires=["requests", "python-dotenv==0.19.2", "hubspot-api-client==7.5.0"], packages=find_packages(include=["hs_api*"]), )