Skip to content
Closed
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
36 changes: 36 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Files

*.pyc
*~
.coverage
.DS_Store
._*
coverage.xml

# Directories

.settings/
.idea/
hubspot_contacts.egg-info/

# Ignore IntelliJ workspace files.
# These contain developer-specific state such as which windows are open
# in IntelliJ so they should never be checked in.
*.iws

# Ignore IntelliJ project and module files.
# Don't want to check these in until I'm happy that the IntelliJ project
# is working properly. May remove these ignores later so I can check them.
*.iml
*.ipr

.settings/org.eclipse.ltk.core.refactoring.prefs

.pydevproject

.project

.\#*
\#*\#
~

33 changes: 32 additions & 1 deletion hubspot/contacts/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
from hubspot.contacts._property_utils import get_property_type_by_property_name
from hubspot.contacts.generic_utils import ipaginate
from hubspot.contacts.request_data_formatters.contacts import \
format_contacts_data_for_saving
format_contact_data_for_saving, format_contacts_data_for_saving, \
format_contact_properties_for_saving


Contact = Record.create_type(
Expand All @@ -37,6 +38,8 @@


_CONTACTS_SAVING_URL_PATH = CONTACTS_API_SCRIPT_NAME + '/contact/batch/'
_CONTACT_CREATING_URL_PATH = CONTACTS_API_SCRIPT_NAME + '/contact/'
_CONTACT_UPDATING_URL_TEMPLATE = CONTACTS_API_SCRIPT_NAME + '/contact/vid/{contact_id}/profile'


def save_contacts(contacts, connection):
Expand Down Expand Up @@ -78,3 +81,31 @@ def save_contacts(contacts, connection):
_CONTACTS_SAVING_URL_PATH,
contacts_batch_data,
)


def create_contact(contact, connection):
property_type_by_property_name = \
get_property_type_by_property_name(connection)
contact_data = format_contact_data_for_saving(
contact,
property_type_by_property_name,
)
response = connection.send_post_request(
_CONTACT_CREATING_URL_PATH,
contact_data,
)
return response["vid"]


def update_contact(contact, connection):
property_type_by_property_name = \
get_property_type_by_property_name(connection)
properties_data = format_contact_properties_for_saving(
contact.properties,
property_type_by_property_name,
)
path = _CONTACT_UPDATING_URL_TEMPLATE.format(contact_id=contact.vid)
connection.send_post_request(
path,
properties_data,
)
14 changes: 12 additions & 2 deletions hubspot/contacts/request_data_formatters/contacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@
def format_contacts_data_for_saving(contacts, property_type_by_property_name):
contacts_data = []
for contact in contacts:
contact_data = _format_contact_data_for_saving(
contact_data = format_contact_data_for_saving(
contact,
property_type_by_property_name,
)
contacts_data.append(contact_data)
return contacts_data


def _format_contact_data_for_saving(contact, property_type_by_property_name):
def format_contact_data_for_saving(contact, property_type_by_property_name):
properties_data = _format_contact_properties_for_saving(
contact.properties,
property_type_by_property_name,
Expand All @@ -51,6 +51,16 @@ def _format_contact_data_for_saving(contact, property_type_by_property_name):
}
return contact_data

def format_contact_properties_for_saving(properties, property_type_by_property_name):
properties_data = _format_contact_properties_for_saving(
properties,
property_type_by_property_name,
)
properties_data = {
'properties': properties_data
}
return properties_data


def _format_contact_properties_for_saving(
contact_properties,
Expand Down
131 changes: 130 additions & 1 deletion hubspot/contacts/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,13 @@
from hubspot.contacts.generic_utils import paginate
from hubspot.contacts.properties import DatetimeProperty
from hubspot.contacts.request_data_formatters.contacts import \
format_contacts_data_for_saving
format_contact_data_for_saving, format_contacts_data_for_saving, \
format_contact_properties_for_saving
from hubspot.contacts.request_data_formatters.properties import \
format_data_for_property
from hubspot.contacts.request_data_formatters.property_groups import \
format_data_for_property_group as format_request_data_for_property_group
import json


STUB_LAST_MODIFIED_DATETIME = datetime.now().replace(microsecond=0)
Expand Down Expand Up @@ -553,6 +555,133 @@ def _get_contact_added_at_timestamp(self, contact):
partial(_UnsucessfulContactRetrievalSimulator, GetAllContactsByLastUpdate)


class CreateContact(object):
"""
Simulator for a successful POST to /contacts/v1/contact
"""

def __init__(self, vid, contact, available_properties):
"""

:param int vid: vid to be returned
:param iterable contact: Contact expected to be saved
:param iterable available_properties:
:class:`~hubspot.contacts.properties.Property` instances for all
the properties supposedly defined in the portal

"""
super(CreateContact, self).__init__()

self._vid = vid
self._contact = contact

self._property_type_by_property_name = \
{p.name: p.__class__ for p in available_properties}
self._available_properties_simulator = \
GetAllProperties(available_properties)

def __call__(self):
api_calls = self._available_properties_simulator()

request_body_deserialization = format_contact_data_for_saving(
self._contact,
self._property_type_by_property_name,
)
api_call = SuccessfulAPICall(
CONTACTS_API_SCRIPT_NAME + '/contact/',
'POST',
request_body_deserialization=request_body_deserialization,
response_body_deserialization={'vid': self._vid },
)
api_calls.append(api_call)

return api_calls


class UnsuccessfulCreateContact(object):
"""
Simulator for an unsuccessful POST to /contacts/v1/contact
"""

def __init__(self, contact, exception, available_properties):
"""

:param iterable contact: Contact expected to be saved
:param int exception: exception to raise
:param iterable available_properties:
:class:`~hubspot.contacts.properties.Property` instances for all
the properties supposedly defined in the portal

"""
super(UnsuccessfulCreateContact, self).__init__()

self._contact = contact
self._exception = exception

self._property_type_by_property_name = \
{p.name: p.__class__ for p in available_properties}
self._available_properties_simulator = \
GetAllProperties(available_properties)

def __call__(self):
api_calls = self._available_properties_simulator()

request_body_deserialization = format_contact_data_for_saving(
self._contact,
self._property_type_by_property_name,
)
api_call = UnsuccessfulAPICall(
CONTACTS_API_SCRIPT_NAME + '/contact/',
'POST',
request_body_deserialization=request_body_deserialization,
exception=self._exception
)
api_calls.append(api_call)

return api_calls


class UpdateContact(object):
"""
Simulator for a successful POST to /contacts/v1/contact/vid/:contact_id/profile
"""

def __init__(self, contact, available_properties):
"""

:param iterable contact: Contact expected to be saved
:param iterable available_properties:
:class:`~hubspot.contacts.properties.Property` instances for all
the properties supposedly defined in the portal

"""
super(UpdateContact, self).__init__()

self._contact = contact

self._property_type_by_property_name = \
{p.name: p.__class__ for p in available_properties}
self._available_properties_simulator = \
GetAllProperties(available_properties)

def __call__(self):
api_calls = self._available_properties_simulator()

request_body_deserialization = format_contact_properties_for_saving(
self._contact.properties,
self._property_type_by_property_name,
)
api_call = SuccessfulAPICall(
CONTACTS_API_SCRIPT_NAME + '/contact/vid/' + str(self._contact.vid) + '/profile',
'POST',
request_body_deserialization=request_body_deserialization,
response_body_deserialization=None,
)
api_calls.append(api_call)

return api_calls


class SaveContacts(object):
"""
Simulator for a successful call to :func:`~hubspot.contacts.save_contacts`.
Expand Down
62 changes: 62 additions & 0 deletions tests/test_contact.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# coding: utf-8

from hubspot.connection.testing import MockPortalConnection
from nose.tools import eq_
from nose.tools import assert_raises

from hubspot.contacts import create_contact, update_contact
from hubspot.contacts.testing import CreateContact, UnsuccessfulCreateContact, \
UpdateContact

from tests._utils import make_contact
from tests.test_properties import STUB_STRING_PROPERTY

from hubspot.connection.exc import HubspotClientError


class TestCreateContact(object):

def test_create_single_contact(self):
contact = make_contact(None)
with self._make_connection_for_contact(1, contact) as connection:
vid = create_contact(contact, connection)

eq_(1, vid)

@staticmethod
def _make_connection_for_contact(vid, contact, available_property=None):
available_property = available_property or STUB_STRING_PROPERTY
simulator = CreateContact(vid, contact, [available_property])
connection = MockPortalConnection(simulator)
return connection

def test_invalid_property_raises_hubspot_client_error(self):
contact = make_contact(None, properties={'is_polite': 'notavalidinput'})
with assert_raises(HubspotClientError) as context:
with self._make_connection_for_contact_with_exception(
contact,
HubspotClientError("Property notavalidinput is invalid", "request-id")) as connection:
create_contact(contact, connection)
eq_(context.exception.message, "Property notavalidinput is invalid")

@staticmethod
def _make_connection_for_contact_with_exception(contact, exception, available_property=None):
available_property = available_property or STUB_STRING_PROPERTY
simulator = UnsuccessfulCreateContact(contact, exception, [available_property])
connection = MockPortalConnection(simulator)
return connection


class TestUpdateContact(object):

def test_update_single_contact(self):
contact = make_contact(1)
with self._make_connection_for_contact(contact) as connection:
update_contact(contact, connection)

@staticmethod
def _make_connection_for_contact(contact, available_property=None):
available_property = available_property or STUB_STRING_PROPERTY
simulator = UpdateContact(contact, [available_property])
connection = MockPortalConnection(simulator)
return connection