From b236d1ebf063b458eb0209195cbdb8e47e57fc98 Mon Sep 17 00:00:00 2001 From: intellisense Date: Wed, 24 May 2017 01:19:54 +0500 Subject: [PATCH 1/6] pep-8 fixes and custom object data create_or_update method added --- icontact/client.py | 151 +++++++++++++++++++++++---------------- icontact/tests/client.py | 65 +++++++---------- setup.py | 32 +++++---- 3 files changed, 130 insertions(+), 118 deletions(-) diff --git a/icontact/client.py b/icontact/client.py index ea6a1f4..053ef0f 100644 --- a/icontact/client.py +++ b/icontact/client.py @@ -11,10 +11,7 @@ # 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 json import httplib import urllib import urllib2 @@ -33,19 +30,23 @@ 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 @@ -63,6 +64,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 +73,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): + max_retry_count=5, account_id=None, client_folder_id=None, + url=ICONTACT_API_URL, api_version='2.2'): """ - api_key: the API Key assigned for the OA iContact client - username: the iContact web site login username @@ -93,7 +96,7 @@ 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 @@ -115,7 +118,7 @@ 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 _do_request(self, call_path, parameters=None, method='get', type='json', force_dict_params=True): """ Performs an API request and returns the resultant json object. If type='xml' is passed in, returns XML document as an @@ -125,19 +128,24 @@ 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. + evaluating the response; and parsing the response 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) + if parameters is None: + parameters = {} + if force_dict_params: + params = dict(parameters) + else: + params = 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) + data = json.dumps(params) self.log.debug(u"Invoking API method %s with URL: %s" % (method, url)) @@ -145,12 +153,12 @@ def _do_request(self, call_path, parameters={}, method='get', type='json'): 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 } + 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 @@ -159,7 +167,7 @@ def _do_request(self, call_path, parameters={}, method='get', type='json'): 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) + 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(),)) @@ -167,7 +175,7 @@ def _do_request(self, call_path, parameters={}, method='get', type='json'): else: # Perform a GET request req = urllib2.Request(url, None, headers) - self.log.debug("GET headers=%s url=%s" % (req.headers,url)) + self.log.debug("GET headers=%s url=%s" % (req.headers, url)) response = urllib2.urlopen(req) response_status = response.code @@ -178,7 +186,7 @@ def _do_request(self, call_path, parameters={}, method='get', type='json'): # type is json jsondata = response.read() self.log.debug(u"json response=\n%s" % (jsondata,)) - result = simplejson.loads(jsondata) + result = json.loads(jsondata) result = json_to_obj(result) if response_status >= 400: @@ -188,9 +196,11 @@ def _do_request(self, call_path, parameters={}, method='get', type='json'): self.retry_count = 0 return result - def _get_query_string(self, params={}): + def _get_query_string(self, params=None): + if params is None: + params = {} if params: - query_string = '?' + '&'.join([k+'='+urllib.quote(str(v)) for (k,v) in params.items()]) + query_string = '?' + '&'.join([k+'='+urllib.quote(str(v)) for (k, v) in params.items()]) else: query_string = '' return query_string @@ -203,7 +213,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 +233,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'), @@ -261,7 +271,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 +282,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. @@ -287,13 +295,12 @@ def search_contacts(self, params=None, account_id=None, client_folder_id=None, * for k in params: if len(p) > 0: p += "&" - p += "%s=%s" % (k,urllib.quote(params[k])) + 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)) return result - def lists(self, params=None, account_id=None, client_folder_id=None, filters=None): """ Returns iContact Lists @@ -304,7 +311,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, + result = self._do_request('a/%s/c/%s/lists/%s' % (account_id, client_folder_id, self._get_query_string(filters))) return result @@ -314,14 +321,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 +347,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 +358,12 @@ 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, + result = self._do_request('a/%s/c/%s/segments/%s' % (account_id, client_folder_id, self._get_query_string(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 +375,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 @@ -409,7 +418,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 +439,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 +448,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 @@ -450,7 +458,7 @@ def contact_history(self, contact_id, account_id=None, client_folder_id=None, fi """ 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))) + contact_id, self._get_query_string(filters))) return result def create_subscription(self, contact_id, list_id, status='normal', account_id=None, client_folder_id=None): @@ -458,7 +466,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,7 +478,7 @@ 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, + result = self._do_request('a/%s/c/%s/subscriptions/%s' % (account_id, client_folder_id, self._get_query_string(filters))) return result @@ -489,14 +497,13 @@ 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))) 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 +511,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 +530,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 +538,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 +550,28 @@ 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): + """ + 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', + force_dict_params=False) + return result + + 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..2763f3e 100644 --- a/icontact/tests/client.py +++ b/icontact/tests/client.py @@ -1,73 +1,56 @@ import unittest + from icontact.client import IContactClient from icontact.tests import settings -import datetime -class ClientTestCase(unittest.TestCase): - 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.client = IContactClient( + settings.ICONTACT_API_KEY, + settings.ICONTACT_USERNAME, + settings.ICONTACT_PASSWORD, + account_id=settings.ICONTACT_ACCOUNT_ID, + client_folder_id=settings.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.create_subscription(contact_id, settings.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) + result = self.client.move_subscriber( + settings.ICONTACT_MAIN_LIST_ID, contact_id, settings.ICONTACT_HOLDING_LIST_ID) + self.assertTrue(result.subscription.listId == str(settings.ICONTACT_HOLDING_LIST_ID)) if __name__ == '__main__': unittest.main() - diff --git a/setup.py b/setup.py index 5fb6afe..8729543 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', + ], + 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'], ) From 0b89ebe4ac7e5f8a5353a5de8d0c09637deefe0f Mon Sep 17 00:00:00 2001 From: intellisense Date: Thu, 25 May 2017 13:46:33 +0500 Subject: [PATCH 2/6] separate tests for iContact Pro API V3 and new methods --- icontact/client.py | 46 ++++++++++++++++---- icontact/tests/client.py | 23 ++++++---- icontact/tests/client_pro_v3.py | 74 +++++++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+), 16 deletions(-) create mode 100644 icontact/tests/client_pro_v3.py diff --git a/icontact/client.py b/icontact/client.py index 053ef0f..56d2e72 100644 --- a/icontact/client.py +++ b/icontact/client.py @@ -290,15 +290,10 @@ def search_contacts(self, params=None, account_id=None, client_folder_id=None, * if params is None: params = {} params.update(kwarg_params) + querystring = urllib.urlencode(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/?%s' % (account_id, client_folder_id, querystring), type='json') + self.log.debug("search_contacts(%s)=%s" % (querystring, result)) return result def lists(self, params=None, account_id=None, client_folder_id=None, filters=None): @@ -409,6 +404,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', + force_dict_params=False) + return result + def create_contact(self, email, account_id=None, client_folder_id=None, **kwargs): """ Creates the contact and returns the contact object. @@ -483,6 +495,22 @@ def subscriptions(self, account_id=None, client_folder_id=None, filters=None): 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', + force_dict_params=False) + 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. @@ -556,7 +584,7 @@ def get_send(self, send_id, account_id=None, client_folder_id=None): def create_or_update_custom_object(self, custom_object_id, account_id=None, client_folder_id=None, data=None): """ - Update the custom object data + 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, diff --git a/icontact/tests/client.py b/icontact/tests/client.py index 2763f3e..a04e493 100644 --- a/icontact/tests/client.py +++ b/icontact/tests/client.py @@ -1,3 +1,4 @@ +import os import unittest from icontact.client import IContactClient @@ -7,12 +8,20 @@ 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( - settings.ICONTACT_API_KEY, - settings.ICONTACT_USERNAME, - settings.ICONTACT_PASSWORD, - account_id=settings.ICONTACT_ACCOUNT_ID, - client_folder_id=settings.ICONTACT_CLIENT_FOLDER_ID, + 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, ) @@ -48,8 +57,8 @@ def test_unsubscribe(self): contacts = self.client.search_contacts({'email': email}) contact_id = contacts.contacts[0].contactId result = self.client.move_subscriber( - settings.ICONTACT_MAIN_LIST_ID, contact_id, settings.ICONTACT_HOLDING_LIST_ID) - self.assertTrue(result.subscription.listId == str(settings.ICONTACT_HOLDING_LIST_ID)) + self.ICONTACT_MAIN_LIST_ID, contact_id, self.ICONTACT_HOLDING_LIST_ID) + self.assertTrue(result.subscription.listId == str(self.ICONTACT_HOLDING_LIST_ID)) if __name__ == '__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() From c7d12d1767217889080e0fea2d4ed108fca01fc8 Mon Sep 17 00:00:00 2001 From: intellisense Date: Fri, 26 May 2017 01:47:24 +0500 Subject: [PATCH 3/6] adding delete_custom_object_data and get_custom_object_data methods --- icontact/client.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/icontact/client.py b/icontact/client.py index 56d2e72..93056cf 100644 --- a/icontact/client.py +++ b/icontact/client.py @@ -599,6 +599,27 @@ def create_or_update_custom_object(self, custom_object_id, account_id=None, clie force_dict_params=False) 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/?%s' % ( + account_id, client_folder_id, custom_object_id, urllib.urlencode(kwargs))) + + return result + class FixedOffset(tzinfo): """ From bf4ac2bc88284c2087196c831b6ef013b416e30a Mon Sep 17 00:00:00 2001 From: intellisense Date: Tue, 30 May 2017 04:20:38 +0500 Subject: [PATCH 4/6] log optional --- icontact/client.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/icontact/client.py b/icontact/client.py index 93056cf..81fa60c 100644 --- a/icontact/client.py +++ b/icontact/client.py @@ -74,7 +74,7 @@ class IContactClient(object): 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, api_version='2.2'): + 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 @@ -100,7 +100,6 @@ def __init__(self, api_key, username, password, auth_handler=None, 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 @@ -110,6 +109,9 @@ def __init__(self, api_key, username, password, auth_handler=None, 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 @@ -147,7 +149,7 @@ def _do_request(self, call_path, parameters=None, method='get', type='json', for url = "%s%s" % (self.url, call_path) data = json.dumps(params) - self.log.debug(u"Invoking API method %s with URL: %s" % (method, url)) + self.log_me(u"Invoking API method %s with URL: %s" % (method, url)) if type == 'xml': type_header = 'text/xml' @@ -164,28 +166,28 @@ def _do_request(self, call_path, parameters=None, method='get', type='json', for if method.lower() != 'get': # Perform a PUT request - self.log.debug(u'%s Request %s body: %s' % (method, url, data)) + self.log_me(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" % + self.log_me("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)) + self.log_me("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),)) + 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,)) + self.log_me(u"json response=\n%s" % (jsondata,)) result = json.loads(jsondata) result = json_to_obj(result) @@ -262,7 +264,7 @@ def clientfolders(self, account_id, filters=None): 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,)) + self.log_me("clientfolders: %s" % (result,)) return result def clientfolder(self, account_id, index=0): @@ -293,7 +295,7 @@ def search_contacts(self, params=None, account_id=None, client_folder_id=None, * querystring = urllib.urlencode(params) result = self._do_request('a/%s/c/%s/contacts/?%s' % (account_id, client_folder_id, querystring), type='json') - self.log.debug("search_contacts(%s)=%s" % (querystring, result)) + self.log_me("search_contacts(%s)=%s" % (querystring, result)) return result def lists(self, params=None, account_id=None, client_folder_id=None, filters=None): @@ -620,6 +622,10 @@ def get_custom_object_data(self, custom_object_id, account_id=None, client_folde return result + def log_me(self, msg): + if self.log_enabled: + self.log.debug(msg) + class FixedOffset(tzinfo): """ From 28c58e8717c07777c65bfb553ba9274268634647 Mon Sep 17 00:00:00 2001 From: intellisense Date: Tue, 30 May 2017 09:15:36 +0500 Subject: [PATCH 5/6] use requests library for request handling and removed retry logic handle yourself by overriding IContactClient._perform_request --- icontact/__init__.py | 2 +- icontact/client.py | 155 +++++++++++++-------------------------- icontact/tests/client.py | 19 +++-- setup.py | 2 +- 4 files changed, 68 insertions(+), 110 deletions(-) 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 81fa60c..e8909cb 100644 --- a/icontact/client.py +++ b/icontact/client.py @@ -11,12 +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. -import json -import httplib -import urllib -import urllib2 -import urlparse import logging +import requests from datetime import tzinfo, timedelta @@ -47,15 +43,6 @@ def __repr__(self): 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): self.http_status = http_status @@ -73,7 +60,7 @@ 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, + 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 @@ -82,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 @@ -100,13 +85,10 @@ def __init__(self, api_key, username, password, auth_handler=None, self.username = username self.password = password self.auth_handler = auth_handler - 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') @@ -120,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=None, method='get', type='json', force_dict_params=True): + def _perform_request(self, method, url, **kwargs): + return requests.request(method.upper(), url, **kwargs) + + def _do_request(self, call_path, parameters=None, method='get', 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 @@ -132,81 +117,53 @@ def _do_request(self, call_path, parameters=None, method='get', type='json', for URL path; adding auth headers; sending the request to iContact; evaluating the response; and parsing the response 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) if parameters is None: parameters = {} - if force_dict_params: - params = dict(parameters) - else: - params = 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 = json.dumps(params) - self.log_me(u"Invoking API method %s with URL: %s" % (method, url)) + url = '%s%s' % (self.url, call_path) + + type_header = 'text/xml' if 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 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_me(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_me("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_me("GET headers=%s url=%s" % (req.headers, url)) - response = urllib2.urlopen(req) - response_status = response.code - - if type == 'xml': - result = ElementTree.fromstring(response.read()) + 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_me(u"json response=\n%s" % (jsondata,)) - result = json.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=None): - if params is None: - 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 @@ -254,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] @@ -263,7 +220,7 @@ 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') + result = self._do_request('a/%s/c/' % account_id, parameters=filters) self.log_me("clientfolders: %s" % (result,)) return result @@ -292,13 +249,11 @@ def search_contacts(self, params=None, account_id=None, client_folder_id=None, * if params is None: params = {} params.update(kwarg_params) - querystring = urllib.urlencode(params) - result = self._do_request('a/%s/c/%s/contacts/?%s' % (account_id, client_folder_id, querystring), type='json') - self.log_me("search_contacts(%s)=%s" % (querystring, 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 @@ -308,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 @@ -355,8 +309,7 @@ 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 @@ -420,7 +373,7 @@ def create_or_update_contact(self, account_id=None, client_folder_id=None, data= (account_id, client_folder_id), parameters=data, method='post', - force_dict_params=False) + params_as_json=True) return result def create_contact(self, email, account_id=None, client_folder_id=None, **kwargs): @@ -471,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): @@ -492,8 +445,7 @@ 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 @@ -510,7 +462,7 @@ def create_or_update_subscription(self, account_id=None, client_folder_id=None, (account_id, client_folder_id), parameters=data, method='post', - force_dict_params=False) + params_as_json=True) return result def create_message(self, subject, message_type, account_id=None, client_folder_id=None, **kwargs): @@ -529,8 +481,7 @@ def create_message(self, subject, message_type, account_id=None, client_folder_i 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, message_id, account_id=None, client_folder_id=None): @@ -598,7 +549,7 @@ def create_or_update_custom_object(self, custom_object_id, account_id=None, clie (account_id, client_folder_id, custom_object_id), parameters=data, method='post', - force_dict_params=False) + params_as_json=True) return result def delete_custom_object_data(self, custom_object_id, custom_object_field_definition_id, @@ -617,8 +568,8 @@ def get_custom_object_data(self, custom_object_id, account_id=None, client_folde 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/?%s' % ( - account_id, client_folder_id, custom_object_id, urllib.urlencode(kwargs))) + result = self._do_request('a/%s/c/%s/customobjects/%s/data/' % ( + account_id, client_folder_id, custom_object_id), parameters=kwargs) return result diff --git a/icontact/tests/client.py b/icontact/tests/client.py index a04e493..267db50 100644 --- a/icontact/tests/client.py +++ b/icontact/tests/client.py @@ -1,8 +1,7 @@ import os import unittest -from icontact.client import IContactClient -from icontact.tests import settings +from icontact.client import IContactClient, IContactServerError class ClientTestCase(unittest.TestCase): @@ -48,7 +47,9 @@ def test_subscribe(self): email = 'name@example.com' contacts = self.client.search_contacts({'email': email}) contact_id = contacts.contacts[0].contactId - result = self.client.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): @@ -56,9 +57,15 @@ def test_unsubscribe(self): email = 'name@example.com' contacts = self.client.search_contacts({'email': email}) contact_id = contacts.contacts[0].contactId - 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)) + 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__': diff --git a/setup.py b/setup.py index 8729543..ea4f1f1 100644 --- a/setup.py +++ b/setup.py @@ -62,7 +62,7 @@ def fullsplit(path, result=None): url='http://code.google.com/p/python-icontact/', packages=packages, install_requires=[ - 'python-dateutil', + 'python-dateutil', 'requests' ], data_files=data_files, classifiers=['Development Status :: 4 - Beta', From 8e2c421c8bc3f26c4c8e7173990684931ed8c6ed Mon Sep 17 00:00:00 2001 From: intellisense Date: Tue, 30 May 2017 09:19:34 +0500 Subject: [PATCH 6/6] avoid built-in func overriding --- icontact/client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/icontact/client.py b/icontact/client.py index e8909cb..0765383 100644 --- a/icontact/client.py +++ b/icontact/client.py @@ -105,7 +105,7 @@ def _get_client_folder_id(self): def _perform_request(self, method, url, **kwargs): return requests.request(method.upper(), url, **kwargs) - def _do_request(self, call_path, parameters=None, method='get', type='json', params_as_json=False): + 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 @@ -122,7 +122,7 @@ def _do_request(self, call_path, parameters=None, method='get', type='json', par url = '%s%s' % (self.url, call_path) - type_header = 'text/xml' if type == 'xml' else 'application/json' + type_header = 'text/xml' if response_type == 'xml' else 'application/json' headers = { 'Accept': type_header, 'Content-Type': type_header, @@ -150,7 +150,7 @@ def _do_request(self, call_path, parameters=None, method='get', type='json', par self.log_me('response.status=%s headers=%s' % (req.status_code, req.headers,)) response_status = req.status_code - if type == 'xml': + if response_type == 'xml': result = ElementTree.fromstring(req.content) self.log_me(u'Response body:\n%s' % (ElementTree.tostring(result),)) else: