diff --git a/icontact/__init__.py b/icontact/__init__.py index 5a9a127..259ca00 100644 --- a/icontact/__init__.py +++ b/icontact/__init__.py @@ -1 +1 @@ -VERSION = (2, 0, 'beta') +VERSION = (2, 1, 'beta') diff --git a/icontact/client.py b/icontact/client.py index ea6a1f4..0765383 100644 --- a/icontact/client.py +++ b/icontact/client.py @@ -11,15 +11,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -try: - from django.utils import simplejson -except ImportError: - import simplejson -import httplib -import urllib -import urllib2 -import urlparse import logging +import requests from datetime import tzinfo, timedelta @@ -33,27 +26,22 @@ from dateutil.parser import parse -def json_to_obj(json): - if isinstance(json, list): - json = [json_to_obj(x) for x in json] - if not isinstance(json, dict): - return json + +def json_to_obj(json_data): + if isinstance(json_data, list): + json_data = [json_to_obj(x) for x in json_data] + if not isinstance(json_data, dict): + return json_data + class Object(object): def __repr__(self): return 'icontact.client.Object(%s)' % repr(self.__dict__) + o = Object() - for k in json: - o.__dict__[k] = json_to_obj(json[k]) + for k in json_data: + o.__dict__[k] = json_to_obj(json_data[k]) return o -class ExcessiveRetriesException(Exception): - """ - A standard exception that represents a potentially transient fault - where an an iContact API client fails to perform an operation more - than `self.max_retry_count` times. - """ - pass - class IContactServerError(Exception): def __init__(self, http_status, errors): @@ -63,6 +51,7 @@ def __init__(self, http_status, errors): def __str__(self): return '%s: %s' % (self.http_status, '\n'.join(self.errors)) + class IContactClient(object): """Perform operations on the iContact API.""" @@ -71,7 +60,8 @@ class IContactClient(object): NAMESPACE = 'http://www.w3.org/1999/xlink' def __init__(self, api_key, username, password, auth_handler=None, - max_retry_count=5, account_id=None, client_folder_id=None, url=ICONTACT_API_URL): + account_id=None, client_folder_id=None, + url=ICONTACT_API_URL, api_version='2.2', log_enabled=False): """ - api_key: the API Key assigned for the OA iContact client - username: the iContact web site login username @@ -79,8 +69,6 @@ def __init__(self, api_key, username, password, auth_handler=None, This is the password registered for the API client, also known as the "API Application Password". It is *not* the standard web site login password. - - max_retry_count: (Optional) Retry limit for logins or - rate-limited operations. - auth_handler: (Optional) An object that implements two callback methods that this client will invoke when it generates, or requires, authentication credentials. The authentication handler @@ -93,20 +81,19 @@ def __init__(self, api_key, username, password, auth_handler=None, set_credentials(token,sequence) """ self.api_key = api_key - self.api_version = "2.2" + self.api_version = api_version self.username = username self.password = password self.auth_handler = auth_handler - self.log = logging.getLogger('icontact') - self.max_retry_count = max_retry_count self.account_id = account_id self.client_folder_id = client_folder_id - # Track number of retries we have performed - self.retry_count = 0 self.url = url + self.log = logging.getLogger('icontact') + self.log_enabled = log_enabled + def _get_account_id(self): self.account_id = self.account().accountId return self.account_id @@ -115,7 +102,10 @@ def _get_client_folder_id(self): self.client_folder_id = self.clientfolder(self.account_id).clientFolderId return self.client_folder_id - def _do_request(self, call_path, parameters={}, method='get', type='json'): + def _perform_request(self, method, url, **kwargs): + return requests.request(method.upper(), url, **kwargs) + + def _do_request(self, call_path, parameters=None, method='get', response_type='json', params_as_json=False): """ Performs an API request and returns the resultant json object. If type='xml' is passed in, returns XML document as an @@ -125,76 +115,55 @@ def _do_request(self, call_path, parameters={}, method='get', type='json'): This method does all the hard work for API operations: building the URL path; adding auth headers; sending the request to iContact; - evaluating the response; and parsing the respones to an XML node. - """ - # Check whether this method call was a retry that exceeds the retry limit - if self.retry_count > self.max_retry_count: - raise ExcessiveRetriesException("Exceeded maximum retry count (%d)" % self.max_retry_count) - params = dict(parameters) - data = None - - if method.lower() == 'get' and len(params) > 0: - url = "%s%s?%s" % (self.url, call_path, urllib.urlencode(params)) - else: - url = "%s%s" % (self.url, call_path) - data = simplejson.dumps(params) - - self.log.debug(u"Invoking API method %s with URL: %s" % (method, url)) - - if type == 'xml': - type_header = 'text/xml' - else: - type_header = 'application/json' - headers = {'Accept':type_header, - 'Content-Type':type_header, - 'Api-Version':self.api_version, - 'Api-AppId':self.api_key, - 'Api-Username':self.username, - 'API-Password':self.password } - - # TODO: try request for urllib2.HTTPError for 503 to do rate limit retry - - if method.lower() != 'get': - # Perform a PUT request - self.log.debug(u'%s Request %s body: %s' % (method, url, data)) - scheme, host, path, params, query, fragment = urlparse.urlparse(url) - conn = httplib.HTTPSConnection(host, 443) - conn.request(method.upper(), path , data, headers) - response = conn.getresponse() - self.log.debug("response.status=%s msg=%s headers=%s" % - (response.status, response.msg, response.getheaders(),)) - response_status = response.status - else: - # Perform a GET request - req = urllib2.Request(url, None, headers) - self.log.debug("GET headers=%s url=%s" % (req.headers,url)) - response = urllib2.urlopen(req) - response_status = response.code - - if type == 'xml': - result = ElementTree.fromstring(response.read()) - self.log.debug(u'Response body:\n%s' % (ElementTree.tostring(result),)) + evaluating the response; and parsing the response to an XML node. + """ + if parameters is None: + parameters = {} + + url = '%s%s' % (self.url, call_path) + + type_header = 'text/xml' if response_type == 'xml' else 'application/json' + headers = { + 'Accept': type_header, + 'Content-Type': type_header, + 'Api-Version': self.api_version, + 'Api-AppId': self.api_key, + 'Api-Username': self.username, + 'API-Password': self.password, + } + + req_params = { + 'headers': headers, + } + + if parameters: + if method.lower() == 'get': + req_params['params'] = parameters + else: + if params_as_json or method.lower() == 'put': + req_params['json'] = parameters + else: + req_params['data'] = parameters + + self.log_me(u'Invoking API method %s with URL: %s' % (method, url)) + req = self._perform_request(method, url, **req_params) + self.log_me('response.status=%s headers=%s' % (req.status_code, req.headers,)) + response_status = req.status_code + + if response_type == 'xml': + result = ElementTree.fromstring(req.content) + self.log_me(u'Response body:\n%s' % (ElementTree.tostring(result),)) else: # type is json - jsondata = response.read() - self.log.debug(u"json response=\n%s" % (jsondata,)) - result = simplejson.loads(jsondata) + result = req.json() + self.log_me(u'json response=\n%s' % (result,)) result = json_to_obj(result) if response_status >= 400: raise IContactServerError(response_status, result.errors) - # Reset retry count to 0 since we have a successful response - self.retry_count = 0 return result - def _get_query_string(self, params={}): - if params: - query_string = '?' + '&'.join([k+'='+urllib.quote(str(v)) for (k,v) in params.items()]) - else: - query_string = '' - return query_string - def _parse_stats(self, node): """ Parses statistics information from a 'stats' XML node that will @@ -203,7 +172,7 @@ def _parse_stats(self, node): information is returned as a dictionary of dictionaries. """ def summary_to_dict(stats_node): - if stats_node == None: + if stats_node is None: return None summary = dict( count=int(stats_node.get('count') or '0'), @@ -223,7 +192,7 @@ def summary_to_dict(stats_node): comments=summary_to_dict(node.find('comments')), complaints=summary_to_dict(node.find('complaintss')) ) - contacts=[] + contacts = [] for c in node.findall('*/contact'): contact = dict( email=c.get('email'), @@ -242,7 +211,7 @@ def account(self, index=0): Returns the first account object in the accounts dictionary. Url: /icp/a/ """ - accountobj = self._do_request('a', type='json') + accountobj = self._do_request('a') return accountobj.accounts[index] @@ -251,8 +220,8 @@ def clientfolders(self, account_id, filters=None): Returns the clientfolders object. Url: /icp/a/{accountId}/c """ - result = self._do_request('a/%s/c%s' % (account_id, self._get_query_string(filters)), type='json') - self.log.debug("clientfolders: %s" % (result,)) + result = self._do_request('a/%s/c/' % account_id, parameters=filters) + self.log_me("clientfolders: %s" % (result,)) return result def clientfolder(self, account_id, index=0): @@ -261,7 +230,6 @@ def clientfolder(self, account_id, index=0): """ return self.clientfolders(account_id).clientfolders[index] - def _required_values(self, account_id, client_folder_id): if account_id is None: if self.account_id is None: @@ -273,7 +241,6 @@ def _required_values(self, account_id, client_folder_id): client_folder_id = self.client_folder_id return account_id, client_folder_id - def search_contacts(self, params=None, account_id=None, client_folder_id=None, **kwarg_params): """ If account_id or client_folder_id is None, then use the default (first) one. @@ -283,18 +250,10 @@ def search_contacts(self, params=None, account_id=None, client_folder_id=None, * params = {} params.update(kwarg_params) - p = "" - for k in params: - if len(p) > 0: - p += "&" - p += "%s=%s" % (k,urllib.quote(params[k])) - - result = self._do_request('a/%s/c/%s/contacts/?%s' % (account_id, client_folder_id, p), type='json') - self.log.debug("search_contacts(%s)=%s" % (p, result)) + result = self._do_request('a/%s/c/%s/contacts/' % (account_id, client_folder_id), parameters=params) return result - - def lists(self, params=None, account_id=None, client_folder_id=None, filters=None): + def lists(self, account_id=None, client_folder_id=None, filters=None): """ Returns iContact Lists params is a dictionary @@ -304,8 +263,7 @@ def lists(self, params=None, account_id=None, client_folder_id=None, filters=Non """ account_id, client_folder_id = self._required_values(account_id, client_folder_id) - result = self._do_request('a/%s/c/%s/lists/%s' % (account_id,client_folder_id, - self._get_query_string(filters))) + result = self._do_request('a/%s/c/%s/lists/' % (account_id, client_folder_id), parameters=filters) return result @@ -314,14 +272,16 @@ def list(self, list_id, account_id=None, client_folder_id=None): Returns an object representing the iContact List identified by the given id number. In the json returned below, and object is created with attributes for each key. Example: - {'list':{'listId':'123123', 'name':'name', 'description':'', 'emailOwnerOnChange':'','welcomeOnManualAdd':'','welcomeOnSignupAdd':'','welcomeMessageId':'123123'}} + {'list':{'listId':'123123', 'name':'name', 'description':'', 'emailOwnerOnChange':'', + 'welcomeOnManualAdd':'', 'welcomeOnSignupAdd':'', 'welcomeMessageId':'123123'}} + >>> client = IContactClient() >>> mylist = client.list(123123) >>> mylist.list.listId u'123123' """ account_id, client_folder_id = self._required_values(account_id, client_folder_id) - result = self._do_request('a/%s/c/%s/lists/%s/' % (account_id,client_folder_id, list_id)) + result = self._do_request('a/%s/c/%s/lists/%s/' % (account_id, client_folder_id, list_id)) return result @@ -338,7 +298,7 @@ def create_list(self, name, email_owner_on_change, welcome_on_manual_add, if description: params['description'] = description - result = self._do_request('a/%s/c/%s/lists/' % (account_id,client_folder_id), + result = self._do_request('a/%s/c/%s/lists/' % (account_id, client_folder_id), parameters=params, method='post') return result @@ -349,12 +309,11 @@ def segments(self, account_id=None, client_folder_id=None, filters=None): """ account_id, client_folder_id = self._required_values(account_id, client_folder_id) - result = self._do_request('a/%s/c/%s/segments/%s' % (account_id,client_folder_id, - self._get_query_string(filters))) + result = self._do_request('a/%s/c/%s/segments/' % (account_id, client_folder_id), parameters=filters) return result - def create_segment(self, name, listId, description=None, account_id=None, + def create_segment(self, name, list_id, description=None, account_id=None, client_folder_id=None): """Creates segment""" @@ -366,24 +325,24 @@ def create_segment(self, name, listId, description=None, account_id=None, account_id, client_folder_id = self._required_values(account_id, client_folder_id) - params = dict(name=name, listId=listId) + params = dict(name=name, listId=list_id) if description: params['description'] = description - result = self._do_request('a/%s/c/%s/segments/' % (account_id,client_folder_id), + result = self._do_request('a/%s/c/%s/segments/' % (account_id, client_folder_id), parameters=params, method='post') return result - def create_criterion(self, segmentId, fieldName, operator, values, + def create_criterion(self, segment_id, field_name, operator, values, account_id=None, client_folder_id=None): """Creates single criterion for a given segment""" account_id, client_folder_id = self._required_values(account_id, client_folder_id) - params = dict(fieldName=fieldName, operator=operator, values=values) + params = dict(fieldName=field_name, operator=operator, values=values) result = self._do_request('a/%s/c/%s/segments/%s/criteria/' % ( - account_id, client_folder_id, segmentId), + account_id, client_folder_id, segment_id), parameters=params, method='post') return result @@ -400,6 +359,23 @@ def move_subscriber(self, old_list, contact_id, new_list, account_id=None, clien return result + def create_or_update_contact(self, account_id=None, client_folder_id=None, data=None): + """ + Create or Update the contact + :param data: List of dicts holding multiple contacts data + """ + account_id, client_folder_id = self._required_values(account_id, + client_folder_id) + if data and type(data) != list: + data = [data] + + result = self._do_request('a/%s/c/%s/contacts/' % + (account_id, client_folder_id), + parameters=data, + method='post', + params_as_json=True) + return result + def create_contact(self, email, account_id=None, client_folder_id=None, **kwargs): """ Creates the contact and returns the contact object. @@ -409,7 +385,7 @@ def create_contact(self, email, account_id=None, client_folder_id=None, **kwargs """ account_id, client_folder_id = self._required_values(account_id, client_folder_id) params = dict(contact=kwargs) - params['contact']['email']=email + params['contact']['email'] = email if 'status' not in params['contact']: params['contact']['status'] = 'normal' @@ -430,9 +406,8 @@ def update_contact(self, contact_id, account_id=None, client_folder_id=None, **k params = dict(contact=kwargs) params['contact']['contactId'] = contact_id return self._do_request('a/%s/c/%s/contacts/' % (account_id, client_folder_id), - parameters=params, - method='post') - + parameters=params, + method='post') def delete_contact(self, contact_id, account_id=None, client_folder_id=None): """ @@ -440,7 +415,7 @@ def delete_contact(self, contact_id, account_id=None, client_folder_id=None): """ account_id, client_folder_id = self._required_values(account_id, client_folder_id) result = self._do_request('a/%s/c/%s/contacts/%s' % (account_id, client_folder_id, - contact_id), method='delete') + contact_id), method='delete') return result @@ -449,8 +424,8 @@ def contact_history(self, contact_id, account_id=None, client_folder_id=None, fi Returns action history for a contact """ account_id, client_folder_id = self._required_values(account_id, client_folder_id) - result = self._do_request('a/%s/c/%s/contacts/%s/actions/%s' % (account_id, client_folder_id, - contact_id, self._get_query_string(filters))) + result = self._do_request('a/%s/c/%s/contacts/%s/actions/' % (account_id, client_folder_id, + contact_id), parameters=filters) return result def create_subscription(self, contact_id, list_id, status='normal', account_id=None, client_folder_id=None): @@ -458,7 +433,7 @@ def create_subscription(self, contact_id, list_id, status='normal', account_id=N Creates the subscription for the contact. """ account_id, client_folder_id = self._required_values(account_id, client_folder_id) - data = dict(subscription=dict(contactId=contact_id,listId=list_id, status=status)) + data = dict(subscription=dict(contactId=contact_id, listId=list_id, status=status)) result = self._do_request('a/%s/c/%s/subscriptions/' % (account_id, client_folder_id), parameters=data, method='post') @@ -470,11 +445,26 @@ def subscriptions(self, account_id=None, client_folder_id=None, filters=None): """ account_id, client_folder_id = self._required_values(account_id, client_folder_id) - result = self._do_request('a/%s/c/%s/subscriptions/%s' % (account_id,client_folder_id, - self._get_query_string(filters))) + result = self._do_request('a/%s/c/%s/subscriptions/' % (account_id, client_folder_id), parameters=filters) return result + def create_or_update_subscription(self, account_id=None, client_folder_id=None, data=None): + """ + Create or Update the subscription for the contact. + """ + account_id, client_folder_id = self._required_values(account_id, client_folder_id) + + if data and type(data) != list: + data = [data] + + result = self._do_request('a/%s/c/%s/subscriptions/' % + (account_id, client_folder_id), + parameters=data, + method='post', + params_as_json=True) + return result + def create_message(self, subject, message_type, account_id=None, client_folder_id=None, **kwargs): """ Creates a message. Note, the campaignId is required. @@ -489,14 +479,12 @@ def create_message(self, subject, message_type, account_id=None, client_folder_i method='post') return result - def messages(self, account_id=None, client_folder_id=None, filters=None): account_id, client_folder_id = self._required_values(account_id, client_folder_id) - result = self._do_request('a/%s/c/%s/messages/%s' % (account_id, client_folder_id, - self._get_query_string(filters))) + result = self._do_request('a/%s/c/%s/messages/' % (account_id, client_folder_id), parameters=filters) return result - def get_message(self, messageId, account_id=None, client_folder_id=None): + def get_message(self, message_id, account_id=None, client_folder_id=None): """ Gets message. """ @@ -504,17 +492,17 @@ def get_message(self, messageId, account_id=None, client_folder_id=None): client_folder_id) result = self._do_request('a/%s/c/%s/messages/%s' % - (account_id, client_folder_id, messageId), + (account_id, client_folder_id, message_id), method='get') return result - def create_send(self, messageId, includeListIds, account_id=None, - client_folder_id=None, **kwargs): + def create_send(self, message_id, include_list_ids, account_id=None, + client_folder_id=None, **kwargs): """ Creates a send. """ account_id, client_folder_id = self._required_values(account_id, client_folder_id) - alert = dict(messageId=messageId, includeListIds=','.join(includeListIds)) + alert = dict(messageId=message_id, includeListIds=','.join(include_list_ids)) alert.update(kwargs) data = dict(send=alert) @@ -523,7 +511,7 @@ def create_send(self, messageId, includeListIds, account_id=None, method='post') return result - def delete_send(self, sendId, account_id=None, client_folder_id=None): + def delete_send(self, send_id, account_id=None, client_folder_id=None): """ Deletes send. """ @@ -531,11 +519,11 @@ def delete_send(self, sendId, account_id=None, client_folder_id=None): client_folder_id) result = self._do_request('a/%s/c/%s/sends/%s' % - (account_id, client_folder_id, sendId), + (account_id, client_folder_id, send_id), method='delete') return result - def get_send(self, sendId, account_id=None, client_folder_id=None): + def get_send(self, send_id, account_id=None, client_folder_id=None): """ Gets send. """ @@ -543,10 +531,53 @@ def get_send(self, sendId, account_id=None, client_folder_id=None): client_folder_id) result = self._do_request('a/%s/c/%s/sends/%s' % - (account_id, client_folder_id, sendId), + (account_id, client_folder_id, send_id), method='get') return result + def create_or_update_custom_object(self, custom_object_id, account_id=None, client_folder_id=None, data=None): + """ + Create or Update the custom object data + :param data: List of dicts holding multiple custom objects data + """ + account_id, client_folder_id = self._required_values(account_id, + client_folder_id) + if data and type(data) != list: + data = [data] + + result = self._do_request('a/%s/c/%s/customobjects/%s/data/' % + (account_id, client_folder_id, custom_object_id), + parameters=data, + method='post', + params_as_json=True) + return result + + def delete_custom_object_data(self, custom_object_id, custom_object_field_definition_id, + account_id=None, client_folder_id=None): + """ + Deletes the custom object data record for custom object specified via `custom_object_id` + """ + account_id, client_folder_id = self._required_values(account_id, client_folder_id) + result = self._do_request('a/%s/c/%s/customobjects/%s/data/%s/' % ( + account_id, client_folder_id, custom_object_id, custom_object_field_definition_id), method='delete') + + return result + + def get_custom_object_data(self, custom_object_id, account_id=None, client_folder_id=None, **kwargs): + """ + Get all records of a custom object defined by `custom_object_id` + """ + account_id, client_folder_id = self._required_values(account_id, client_folder_id) + result = self._do_request('a/%s/c/%s/customobjects/%s/data/' % ( + account_id, client_folder_id, custom_object_id), parameters=kwargs) + + return result + + def log_me(self, msg): + if self.log_enabled: + self.log.debug(msg) + + class FixedOffset(tzinfo): """ Fixed offset value that extends the `datetime.tzinfo` object to diff --git a/icontact/tests/client.py b/icontact/tests/client.py index 76738c1..267db50 100644 --- a/icontact/tests/client.py +++ b/icontact/tests/client.py @@ -1,73 +1,72 @@ +import os import unittest -from icontact.client import IContactClient -from icontact.tests import settings -import datetime -class ClientTestCase(unittest.TestCase): +from icontact.client import IContactClient, IContactServerError + - def get_client(self): - client = IContactClient(settings.ICONTACT_API_KEY, settings.ICONTACT_USERNAME, - settings.ICONTACT_PASSWORD) - return client +class ClientTestCase(unittest.TestCase): def setUp(self): - IContactClient.ICONTACT_API_URL = IContactClient.ICONTACT_SANDBOX_API_URL + self.ICONTACT_API_KEY = os.environ['ICONTACT_API_KEY'] + self.ICONTACT_USERNAME = os.environ['ICONTACT_USERNAME'] + self.ICONTACT_PASSWORD = os.environ['ICONTACT_PASSWORD'] + self.ICONTACT_ACCOUNT_ID = os.environ.get('ICONTACT_ACCOUNT_ID', None) + self.ICONTACT_CLIENT_FOLDER_ID = os.environ.get('ICONTACT_CLIENT_FOLDER_ID', None) + self.ICONTACT_MAIN_LIST_ID = os.environ.get('ICONTACT_MAIN_LIST_ID', None) + self.ICONTACT_HOLDING_LIST_ID = os.environ.get('ICONTACT_HOLDING_LIST_ID', None) + self.client = IContactClient( + self.ICONTACT_API_KEY, + self.ICONTACT_USERNAME, + self.ICONTACT_PASSWORD, + account_id=self.ICONTACT_ACCOUNT_ID, + client_folder_id=self.ICONTACT_CLIENT_FOLDER_ID, + url=IContactClient.ICONTACT_SANDBOX_API_URL, + ) def test_account(self): - s = self.get_client() - account = s.account() - self.assertTrue(not account is None, "Did not get account object") + account = self.client.account() + self.assertIsNotNone(account, "Did not get account object") self.assertTrue(long(account.accountId) > 0, "Did not get valid accountId") def test_folder(self): - s = IContactClient(settings.ICONTACT_API_KEY, settings.ICONTACT_USERNAME, - settings.ICONTACT_PASSWORD) - account = s.account() - folder = s.clientfolder(account.accountId) - self.assertTrue(not folder.clientFolderId is None, "Did not get clientFolderId") + account = self.client.account() + folder = self.client.clientfolder(account.accountId) + self.assertIsNotNone(folder.clientFolderId, "Did not get clientFolderId") def test_find_or_create_contact(self): - s = IContactClient(settings.ICONTACT_API_KEY, settings.ICONTACT_USERNAME, - settings.ICONTACT_PASSWORD) email = 'name@example.com' - contacts = s.search_contacts({'email':email}) + contacts = self.client.search_contacts({'email': email}) if contacts.total == 0: - contacts = s.create_contact(email, firstName='Firstname', lastName='Lastname') + contacts = self.client.create_contact(email, firstName='Firstname', lastName='Lastname') self.assertTrue(contacts.contacts[0].email == email, "Contacts=%s" % (contacts,)) else: self.assertTrue(contacts.contacts[0].email == email) def test_subscribe(self): - s = IContactClient(settings.ICONTACT_API_KEY, settings.ICONTACT_USERNAME, - settings.ICONTACT_PASSWORD) email = 'name@example.com' - contacts = s.search_contacts({'email':email}) + contacts = self.client.search_contacts({'email': email}) contact_id = contacts.contacts[0].contactId - result = s.create_subscription(contact_id, settings.ICONTACT_MAIN_LIST_ID) + result = self.client.subscriptions(filters={'contactId': contact_id}) + if result.total == 0: + result = self.client.create_subscription(contact_id, self.ICONTACT_MAIN_LIST_ID) self.assertTrue(len(result.subscriptions) == 1) - def test_unsubscribe(self): - # note, you can't unsubscribe, you can only move them to a holding list - s = IContactClient(settings.ICONTACT_API_KEY, settings.ICONTACT_USERNAME, - settings.ICONTACT_PASSWORD) + # note, you can't un-subscribe, you can only move them to a holding list email = 'name@example.com' - contacts = s.search_contacts({'email':email}) + contacts = self.client.search_contacts({'email': email}) contact_id = contacts.contacts[0].contactId - result = s.move_subscriber(settings.ICONTACT_MAIN_LIST_ID, contact_id, settings.ICONTACT_HOLDING_LIST_ID) - self.assertTrue(result.subscription.listId == settings.ICONTACT_HOLDING_LIST_ID) - - def test_create_list(self): - name = "test_list_%s" % (datetime.datetime.now(),) - client = self.get_client() - subject = 'Welcome for %s' % (name,) - textBody = 'Welcome to list %s' % (name,) - # TODO: need campaign_id - #campaign_id = - #message_id = client.create_message(subject, 'welcome', textBody=textBody, campaignId=campaign_id) + try: + result = self.client.move_subscriber( + self.ICONTACT_MAIN_LIST_ID, contact_id, self.ICONTACT_HOLDING_LIST_ID) + self.assertTrue(result.subscription.listId == str(self.ICONTACT_HOLDING_LIST_ID)) + except IContactServerError, e: + if e.http_status == 400 and 'No Changes Made' in e.errors: + pass + else: + raise e if __name__ == '__main__': unittest.main() - diff --git a/icontact/tests/client_pro_v3.py b/icontact/tests/client_pro_v3.py new file mode 100644 index 0000000..d5d5abc --- /dev/null +++ b/icontact/tests/client_pro_v3.py @@ -0,0 +1,74 @@ +import os +import unittest + +from icontact.client import IContactClient + + +class ClientTestCase(unittest.TestCase): + + def setUp(self): + self.ICONTACT_API_KEY = os.environ['ICONTACT_API_KEY'] + self.ICONTACT_USERNAME = os.environ['ICONTACT_USERNAME'] + self.ICONTACT_PASSWORD = os.environ['ICONTACT_PASSWORD'] + self.ICONTACT_ACCOUNT_ID = os.environ.get('ICONTACT_ACCOUNT_ID', None) + self.ICONTACT_CLIENT_FOLDER_ID = os.environ.get('ICONTACT_CLIENT_FOLDER_ID', None) + self.ICONTACT_MAIN_LIST_ID = os.environ.get('ICONTACT_MAIN_LIST_ID', None) + self.ICONTACT_HOLDING_LIST_ID = os.environ.get('ICONTACT_HOLDING_LIST_ID', None) + + self.client = IContactClient( + self.ICONTACT_API_KEY, + self.ICONTACT_USERNAME, + self.ICONTACT_PASSWORD, + account_id=self.ICONTACT_ACCOUNT_ID, + client_folder_id=self.ICONTACT_CLIENT_FOLDER_ID, + url='https://api.icpro.co/icp/', + api_version='2.3', + ) + + def test_account(self): + account = self.client.account() + self.assertIsNotNone(account, "Did not get account object") + self.assertTrue(long(account.accountId) > 0, "Did not get valid accountId") + + def test_folder(self): + account = self.client.account() + folder = self.client.clientfolder(account.accountId) + self.assertIsNotNone(folder.clientFolderId, "Did not get clientFolderId") + + def TEST_CONTACT(self): + email = 'name5@example.com' + contact = {'email': email, 'firstName': 'Firstname', 'lastName': 'Lastname'} + contacts = self.client.create_or_update_contact(data=[contact]) + self.assertTrue(contacts.contacts[0].email == email, "Contacts=%s" % (contacts,)) + + def TEST_SUBSCRIPTION(self): + email = 'name5@example.com' + contacts = self.client.search_contacts({'email': email}) + contact_id = contacts.contacts[0].contactId + subscription = {'contactId': contact_id, 'listId': self.ICONTACT_MAIN_LIST_ID, 'status': 'normal'} + + subscriptions = self.client.subscriptions(filters={'contactId': contact_id}) + if subscriptions.total == 0: + # test `create_or_update_subscription` + result = self.client.create_or_update_subscription(data=[subscription]) + self.assertTrue(len(result.subscriptions) == 1, "Subscriptions=%s" % (result,)) + else: + self.assertTrue(subscriptions.subscriptions[0].contactId == contact_id) + + ''' + # @TODO: fix me, API returns error as "No changes detected" + # test `move_subscriber` + result = self.client.move_subscriber( + self.ICONTACT_MAIN_LIST_ID, contact_id, self.ICONTACT_HOLDING_LIST_ID) + self.assertTrue(result.subscription.listId == str(self.ICONTACT_HOLDING_LIST_ID)) + ''' + + # test delete contact + result = self.client.delete_contact(contact_id) + + def test_contact_and_subscription(self): + self.TEST_CONTACT() + self.TEST_SUBSCRIPTION() + +if __name__ == '__main__': + unittest.main() diff --git a/setup.py b/setup.py index 5fb6afe..ea4f1f1 100644 --- a/setup.py +++ b/setup.py @@ -5,6 +5,7 @@ from distutils.command.install import INSTALL_SCHEMES from distutils.core import setup + def fullsplit(path, result=None): """ Split a pathname into components (the opposite of os.path.join) in a @@ -53,18 +54,21 @@ def fullsplit(path, result=None): version = "%d.%d" % version_tuple[:2] setup( - name = 'python-icontact', - version = version, - description = 'iContact API client library', - author = 'James Murty', - author_email = 'jmurty@gmail.com', - url = 'http://code.google.com/p/python-icontact/', - packages = packages, - data_files = data_files, - classifiers = ['Development Status :: 4 - Beta', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: Apache License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Topic :: Software Development :: Libraries :: Python Modules'], + name='python-icontact', + version=version, + description='iContact API client library', + author='James Murty', + author_email='jmurty@gmail.com', + url='http://code.google.com/p/python-icontact/', + packages=packages, + install_requires=[ + 'python-dateutil', 'requests' + ], + data_files=data_files, + classifiers=['Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Topic :: Software Development :: Libraries :: Python Modules'], )