def _get_property_value(property_name, contact_properties): property_value = contact_properties[property_name] if isinstance(property_value, bool): property_value = json_serialize(property_value) elif isinstance(property_value, date): property_value = convert_date_to_timestamp_in_milliseconds(property_value) property_value = unicode(property_value) return property_value
def _get_property_value(property_name, contact_properties): property_value = contact_properties[property_name] if isinstance(property_value, bool): property_value = json_serialize(property_value) elif isinstance(property_value, date): property_value = \ convert_date_to_timestamp_in_milliseconds(property_value) property_value = text_type(property_value) return property_value
def _exclude_contacts_pages_after_cutoff_datetime(cls, contacts, cutoff_datetime): if not cutoff_datetime: filtered_contacts = contacts elif cutoff_datetime <= cls.MOST_RECENT_CONTACT_UPDATE_DATETIME: cutoff_timestamp = convert_date_to_timestamp_in_milliseconds(cutoff_datetime) cutoff_index = cls._MOST_RECENT_CONTACT_UPDATE_TIMESTAMP - cutoff_timestamp last_page_number_to_include = ceil((cutoff_index + 1.0) / BATCH_RETRIEVAL_SIZE_LIMIT) first_contact_index_to_exclude = int(last_page_number_to_include * BATCH_RETRIEVAL_SIZE_LIMIT) filtered_contacts = contacts[:first_contact_index_to_exclude] else: filtered_contacts = [] return filtered_contacts
def _get_contacts_from_all_pages_by_recency( contact_list_id, connection, property_names=(), cutoff_datetime=None, ): contacts_data = _get_contacts_data( connection, '/lists/{}/contacts/recent'.format(contact_list_id), ('vid-offset', 'time-offset'), property_names, ) if cutoff_datetime: cutoff_timestamp = \ convert_date_to_timestamp_in_milliseconds(cutoff_datetime) else: cutoff_timestamp = None property_type_by_property_name = \ get_property_type_by_property_name(connection) seen_contact_vids = set() for contact_data in contacts_data: contact = _build_contact_from_data( contact_data, property_type_by_property_name, ) if contact.vid in seen_contact_vids: continue seen_contact_vids.add(contact.vid) if cutoff_timestamp and contact_data['addedAt'] < cutoff_timestamp: raise StopIteration() yield contact
def _exclude_contacts_pages_after_cutoff_datetime( cls, contacts, cutoff_datetime, ): if not cutoff_datetime: filtered_contacts = contacts elif cutoff_datetime <= cls.MOST_RECENT_CONTACT_UPDATE_DATETIME: cutoff_timestamp = \ convert_date_to_timestamp_in_milliseconds(cutoff_datetime) cutoff_index = \ cls._MOST_RECENT_CONTACT_UPDATE_TIMESTAMP - cutoff_timestamp last_page_number_to_include = \ ceil((cutoff_index + 1.0) / BATCH_RETRIEVAL_SIZE_LIMIT) first_contact_index_to_exclude = \ int(last_page_number_to_include * BATCH_RETRIEVAL_SIZE_LIMIT) filtered_contacts = contacts[:first_contact_index_to_exclude] else: filtered_contacts = [] return filtered_contacts
def _get_contact_added_at_timestamp(self, contact): contact_added_at_datetime = \ self.get_contact_added_at_datetime(contact, self._contacts) contact_added_at_timestamp = \ convert_date_to_timestamp_in_milliseconds(contact_added_at_datetime) return contact_added_at_timestamp
def _convert_date_to_datestamp_in_milliseconds(date_or_datetime): if isinstance(date_or_datetime, datetime): date_or_datetime = date_or_datetime.date() return convert_date_to_timestamp_in_milliseconds(date_or_datetime)
class GetAllContactsByLastUpdate(GetAllContacts): """ Simulator for a successful call to :func:`~hubspot.contacts.lists.get_all_contacts_by_last_update`. """ _API_CALL_PATH_INFO = '/lists/recently_updated/contacts/recent' MOST_RECENT_CONTACT_UPDATE_DATETIME = datetime.now() _MOST_RECENT_CONTACT_UPDATE_TIMESTAMP = \ convert_date_to_timestamp_in_milliseconds( MOST_RECENT_CONTACT_UPDATE_DATETIME, ) def __init__( self, contacts, available_properties, property_names=(), cutoff_datetime=None, ): """ :param iterable contacts: :class:`~hubspot.contacts.Contact` instances for all the contacts supposedly in the portal. :param iterable available_properties: :class:`~hubspot.contacts.properties.Property` instances for all the properties supposedly defined in the portal. :param iterable property_names: The names of the properties that :func:`~hubspot.contacts.lists.get_all_contacts` is expected to request. :param datetime.datetime cutoff_datetime: The :class:`~datetime.datetime` that is expected to be passed to :func:`~hubspot.contacts.lists.get_all_contacts_by_last_update`. When ``cutoff_datetime`` is unset, this simulator works exactly like :class:`GetAllContacts` -- Except that it uses a different API end-point. The order of the returned contacts match that of ``contacts``. Given that :func:`~hubspot.contacts.lists.get_all_contacts_by_last_update` relies on the last modified time of each contact to filter out those which were last changed before ``cutoff_datetime``, this simulator assigns a fake, decreasing value to each contact in the response. Therefore, if you need to test the effects of ``cutoff_datetime``, you ought to pre-compute such values with :meth:`get_contact_added_at_datetime`. E.g.:: contacts = [ Contact(vid=1, email_address='*****@*****.**', properties={}), Contact(vid=2, email_address='*****@*****.**', properties={}), Contact(vid=3, email_address='*****@*****.**', properties={}), ] # We expect get_all_contacts_by_last_update to exclude the last # contact: cutoff_datetime = \\ GetAllContactsByLastUpdate.get_contact_added_at_datetime( contacts[1], contacts, ) simulator = GetAllContactsByLastUpdate( contacts, available_properties, cutoff_datetime=cutoff_datetime, ) """ filtered_contacts = self._exclude_contacts_pages_after_cutoff_datetime( contacts, cutoff_datetime, ) super(GetAllContactsByLastUpdate, self).__init__( filtered_contacts, available_properties, property_names=property_names, ) self._contacts = filtered_contacts @classmethod def _exclude_contacts_pages_after_cutoff_datetime( cls, contacts, cutoff_datetime, ): if not cutoff_datetime: filtered_contacts = contacts elif cutoff_datetime <= cls.MOST_RECENT_CONTACT_UPDATE_DATETIME: cutoff_timestamp = \ convert_date_to_timestamp_in_milliseconds(cutoff_datetime) cutoff_index = \ cls._MOST_RECENT_CONTACT_UPDATE_TIMESTAMP - cutoff_timestamp last_page_number_to_include = \ ceil((cutoff_index + 1.0) / BATCH_RETRIEVAL_SIZE_LIMIT) first_contact_index_to_exclude = \ int(last_page_number_to_include * BATCH_RETRIEVAL_SIZE_LIMIT) filtered_contacts = contacts[:first_contact_index_to_exclude] else: filtered_contacts = [] return filtered_contacts def _get_query_string_args_for_page(self, page_number): super_ = super(GetAllContactsByLastUpdate, self) query_string_args_for_page = \ super_._get_query_string_args_for_page(page_number) page_index = page_number - 1 previous_page_contacts = self._objects_by_page[page_index - 1] previous_page_last_contact = previous_page_contacts[-1] query_string_args_for_page['timeOffset'] = \ self._get_contact_added_at_timestamp(previous_page_last_contact) return query_string_args_for_page def _get_response_body_deserialization_for_page(self, page_contacts): super_ = super(GetAllContactsByLastUpdate, self) response_body_deserialization_for_page = \ super_._get_response_body_deserialization_for_page(page_contacts) if page_contacts: page_last_contact = page_contacts[-1] time_offset = \ self._get_contact_added_at_timestamp(page_last_contact) else: time_offset = 0 response_body_deserialization_for_page['time-offset'] = time_offset return response_body_deserialization_for_page def _get_objects_data(self, contacts): contacts_data = \ super(GetAllContactsByLastUpdate, self)._get_objects_data(contacts) for contact, contact_data in zip(contacts, contacts_data): contact_data['addedAt'] = \ self._get_contact_added_at_timestamp(contact) return contacts_data @classmethod def get_contact_added_at_datetime(cls, contact, contacts): """ Compute the fake last modification time that would be assigned to ``contact`` inside ``contacts``. :param hubspot.contacts.Contact contact: A contact in ``contacts`` :param iterable contacts: A collection containing ``contact`` :return: :class:`~datetime.datetime` for the last modification time corresponding to ``contact`` This method refers to the "added at" term, which is a piece of meta-data that HubSpot attaches to each contact returned via the end-point for the recently updated contacts. In spite of its name, this datum is effectively the last modified date, but not exactly the same as the "lastmodifieddate" property. The ``cutoff_datetime`` passed to this simulator is compared against the "added at" value of the contact, not the "lastmodifieddate" property. """ contact_index = contacts.index(contact) contact_added_at_timestamp = \ cls._MOST_RECENT_CONTACT_UPDATE_TIMESTAMP - contact_index contact_added_at_datetime = \ convert_timestamp_in_milliseconds_to_datetime( contact_added_at_timestamp, ) return contact_added_at_datetime def _get_contact_added_at_timestamp(self, contact): contact_added_at_datetime = \ self.get_contact_added_at_datetime(contact, self._contacts) contact_added_at_timestamp = \ convert_date_to_timestamp_in_milliseconds(contact_added_at_datetime) return contact_added_at_timestamp
class GetAllContacts(_PaginatedObjectsRetriever): """ Simulator for a successful call to :func:`~hubspot.contacts.lists.get_all_contacts`. """ _API_CALL_PATH_INFO = '/lists/all/contacts/all' _OBJECT_DATA_KEY = 'contacts' _LAST_MODIFIED_DATE_PROPERTY = DatetimeProperty( 'lastmodifieddate', 'label', 'description', 'group_name', 'text', ) _STUB_LAST_MODIFIED_TIMESTAMP = \ convert_date_to_timestamp_in_milliseconds(STUB_LAST_MODIFIED_DATETIME) def __init__(self, contacts, available_properties, property_names=()): """ :param iterable contacts: :class:`~hubspot.contacts.Contact` instances for all the contacts supposedly in the portal. :param iterable available_properties: :class:`~hubspot.contacts.properties.Property` instances for all the properties supposedly defined in the portal. :param iterable property_names: The names of the properties that :func:`~hubspot.contacts.lists.get_all_contacts` is expected to request. """ super(GetAllContacts, self).__init__(contacts) available_properties += [self._LAST_MODIFIED_DATE_PROPERTY] self._available_properties_simulator = \ GetAllProperties(available_properties) self._property_names = property_names def __call__(self): api_calls = self._available_properties_simulator() api_calls.extend(super(GetAllContacts, self).__call__()) return api_calls def _get_query_string_args(self, page_contacts): query_string_args = \ super(GetAllContacts, self)._get_query_string_args(page_contacts) if self._property_names: query_string_args['property'] = self._property_names return query_string_args def _get_query_string_args_for_page(self, page_number): previous_page_contacts = self._objects_by_page[page_number - 2] previous_page_last_contact = previous_page_contacts[-1] query_string_args_for_page = \ {'vidOffset': previous_page_last_contact.vid} return query_string_args_for_page def _get_response_body_deserialization_for_page(self, page_contacts): page_last_contact = page_contacts[-1] if page_contacts else None page_last_contact_vid = \ page_last_contact.vid if page_last_contact else 0 response_body_deserialization_for_page = { 'vid-offset': page_last_contact_vid, } return response_body_deserialization_for_page def _get_objects_data(self, contacts): contacts_data = [] for contact in contacts: contact_properties_data = \ self._get_contact_properties_data(contact) contact_profiles_data = self._get_contact_profiles_data(contact) contact_data = { 'vid': contact.vid, 'canonical-vid': contact.vid, 'properties': contact_properties_data, 'identity-profiles': contact_profiles_data, } contacts_data.append(contact_data) return contacts_data def _get_contact_properties_data(self, contact): contact_properties = contact.properties stub_last_modified_timestamp_string = \ str(self._STUB_LAST_MODIFIED_TIMESTAMP) stub_lastmodifieddate_property_value_data = \ self._get_contact_property_value_data( stub_last_modified_timestamp_string, ) contact_properties_data = { self._LAST_MODIFIED_DATE_PROPERTY.name: stub_lastmodifieddate_property_value_data, } for property_name in self._property_names: if property_name == 'email' and contact.email_address: if 'email' in contact_properties: assert contact.email_address == contact_properties['email'] property_value = contact.email_address elif property_name not in contact_properties: continue else: property_value = \ self._get_property_value(property_name, contact_properties) contact_properties_data[property_name] = \ self._get_contact_property_value_data(property_value) return contact_properties_data @staticmethod def _get_contact_property_value_data(property_value): property_value_data = {'value': property_value, 'versions': []} return property_value_data @staticmethod def _get_property_value(property_name, contact_properties): property_value = contact_properties[property_name] if isinstance(property_value, bool): property_value = json_serialize(property_value) elif isinstance(property_value, date): property_value = \ convert_date_to_timestamp_in_milliseconds(property_value) property_value = text_type(property_value) return property_value @staticmethod def _get_contact_profiles_data(contact): contact_profile_data = { 'vid': contact.vid, 'identities': [ { 'type': 'LEAD_GUID', 'value': get_uuid4_str() }, { 'type': 'EMAIL', 'value': contact.email_address }, ], } contact_profiles_data = [contact_profile_data] for vid in contact.related_contact_vids: contact_profiles_data.append({'vid': vid, 'identities': []}) return contact_profiles_data