diff --git a/README.md b/README.md index 171b685..dbde315 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ python_marketo ============== -Python interface to marketo REST api
-Detailed Doc - http://developers.marketo.com/documentation/rest/ +Python interface to Marketo REST api. It covers all of the basic REST API for Lead, List, Activity and Campaign Objects. It also includes some Email, Folder and File APIs. +It does not yet cover Custom Objects, Opportunity, Company and Sales Person Objects.
+ +Full Marketo REST API documentation - http://developers.marketo.com/documentation/rest/ Installation ============ @@ -13,24 +15,234 @@ Usage ===== ```python from pythonmarketo.client import MarketoClient -mc = MarketoClient(host = , - client_id = , - client_secret = ) +mc = MarketoClient(host=, + client_id=, + client_secret=) + +# Example host: "000-AAA-000.mktorest.com" ``` + +Lead, List, Activity and Campaign Objects +========================================= + Get Leads ---------- +--------------------------------- API Ref: http://developers.marketo.com/documentation/rest/get-multiple-leads-by-filter-type/ ```python -#values could be either "v1 v2 v3" or [v1,v2,v3] +mc.execute(method='get_leads', filtr='email', values='test@test.com', fields=['email','firstName','lastName','company','postalCode']) -mc.execute(method = 'get_leads', filtr = 'email', values = 'test@test.com', fields=['email','firstName','lastName','company','postalCode']) +# values could be either "v1 v2 v3" or [v1,v2,v3] ``` -Get Leads from listId ---------------------- +Get Multiple Leads by Filter Type (alternate implementation) +--------------------------------- +API Ref: http://developers.marketo.com/documentation/rest/get-multiple-leads-by-filter-type/ +```python +lead = mc.execute(method='get_multiple_leads_by_filter_type', filterType='email', filterValues='a@b.com,c@d.com', + fields='firstName, middleName, lastName', nextPageToken=None) + +# fields and nextPageToken are optional +# max 100 filterValues +# theoretically, pass in nextPageToken if more than 300 results, however nextPageToken is not returned at this time +``` + +Get Leads by List Id +----------------------------- API Ref: http://developers.marketo.com/documentation/rest/get-multiple-leads-by-list-id/ ```python -mc.execute(method = 'get_leads_by_listId', listId = '676', fields=['email','firstName','lastName','company','postalCode']) +mc.execute(method='get_leads_by_listId', listId='676', fields=['email','firstName','lastName','company','postalCode']) +``` + +Create Lead +------------ +API Ref: http://developers.marketo.com/documentation/rest/createupdate-leads/ +```python +mc.execute(method = 'create_lead', lookupField = 'email', lookupValue = 'test@test.com', values = {'firstName':'Test1', 'lastName':'Test2'}) +``` + +Update Lead +------------ +API Ref: http://developers.marketo.com/documentation/rest/createupdate-leads/ +```python +mc.execute(method = 'update_lead', lookupField = 'email', lookupValue = 'test@test.com', values = {'firstName':'Test1', 'lastName':'Test2'}) +``` + +Create/Update Leads +------------------- +API Ref: http://developers.marketo.com/documentation/rest/createupdate-leads/ +```python +leads = [{"email":"joe@example.com","firstName":"Joe"},{"email":"jill@example.com","firstName":"Jill"}] +lead = mc.execute(method='create_update_leads', leads=leads, lookupField='email', asyncProcessing='false', partitionName='Default') + +# lookupField and asyncProcessing are optional (defaults are 'email' and 'false') +# partitionName is only required if Marketo instance has > 1 Lead Partition +# max batch size is 300 +``` + +Associate Lead +-------------- +API Ref: http://developers.marketo.com/documentation/rest/associate-lead/ +```python +lead = mc.execute(method='associate_lead', id=2234, cookie='id:287-GTJ-838%26token:_mch-marketo.com-1396310362214-46169') +``` + +Merge Lead +---------- +API Ref: http://developers.marketo.com/documentation/rest/merge-lead/ +```python +lead = mc.execute(method='merge_leads', winning_ld=3482183, loosing_leads_list=[3482182], mergeInCRM=False) + +# mergeInCRM is optional +``` + +Get Lead Partitions +------------------- +API Ref: http://developers.marketo.com/documentation/rest/get-lead-partitions/ +```python +lead = mc.execute(method='get_lead_partitions') +``` + +Get List by Id +-------------- +API Ref: http://developers.marketo.com/documentation/rest/get-list-by-id/ +```python +lead = mc.execute(method='get_list_by_id', id=724) +``` + +Get Multiple Lists +------------------ +API Ref: http://developers.marketo.com/documentation/rest/get-multiple-lists/ +```python +lead = mc.execute(method='get_multiple_lists', id=[724,725], name=None, programName=None, workspaceName=None, batchSize=300, nextPageToken=None) + +# all parameters are optional +``` + +Add Leads to List +----------------- +API Ref: http://developers.marketo.com/documentation/rest/add-leads-to-list/ +```python +lead = mc.execute(method='add_to_list', listId=1, leadIds=[1,2,3]) + +# can handle 300 Leads at a time +``` + +Remove Leads from List +---------------------- +API Ref: http://developers.marketo.com/documentation/rest/remove-leads-from-list/ +```python +lead = mc.execute(method = 'remove_from_list', listId = 1, leadIds = [1,2,3]) + +# can handle 300 Leads at a time +``` + +Member of List +-------------- +API Ref: http://developers.marketo.com/documentation/rest/member-of-list/ +```python +lead = mc.execute(method='member_of_list', listId=728, id=[3482093,3482095,3482096]) +``` + +Get Campaign by Id +------------------ +API Ref: http://developers.marketo.com/documentation/rest/get-campaign-by-id/ +```python +lead = mc.execute(method='get_campaign_by_id', id=1170) +``` + +Get Multiple Campaigns +---------------------- +API Ref: http://developers.marketo.com/documentation/rest/get-multiple-campaigns/ +```python +lead = mc.execute(method='get_multiple_campaigns', id=[1170,1262], name=None, programName=None, workspaceName=None, batchSize=None, nextPageToken=None) + +# all parameters are optional +# batchSize defaults to the maximum (300) +# while it's theoretically possible to pass in a nextPageToken, the nextPageToken is currently not returned in 'lead' +``` + +Schedule Campaign +----------------- +API Ref: http://developers.marketo.com/documentation/rest/schedule-campaign/ +```python +# date format: 2015-11-08T15:43:12-08:00 +from datetime import datetime, timezone, timedelta +now = datetime.now(timezone.utc) +now_no_ms = now.replace(microsecond=0) +now_plus_7 = now_no_ms + timedelta(minutes = 7) +time_as_txt = now_plus_7.astimezone().isoformat() +print(time_as_txt) +lead = mc.execute(method='schedule_campaign', id=1878, runAt=time_as_txt, tokens={'Campaign Name': 'new token value'}, cloneToProgramName=None) + +# runAt is optional; default is 5 minutes from now; if specified, it needs to be at least 5 minutes from now +# tokens and cloneToProgramName are optional +# token override only works for tokens without spaces +# returns True or False +``` + +Request Campaign +---------------- +API Ref: http://developers.marketo.com/documentation/rest/request-campaign/ +```python +lead = mc.execute(method='request_campaign', id=1880, leads=[46,38], tokens={'my.increment': '+2'}) + +# tokens is optional; not tested on tokens with spaces +``` + +Import Lead +----------- +API Ref: http://developers.marketo.com/documentation/rest/import-lead/ +```python +lead = mc.execute(method='import_lead', file='test.csv', format='csv', lookupField='email', listId=None, partitionName='Default') + +# lookupField and listId are optional. Email is the default for lookupField. +# partitionName is required when the Marketo instance has more than 1 Lead Partition +``` + +Get Import Lead Status +---------------------- +API Ref: http://developers.marketo.com/documentation/rest/get-import-lead-status/ +```python +lead = mc.execute(method='get_import_lead_status', id=900) + +# specify the batch ID that is returned in 'Import Lead' +``` + +Get Import Failure File +----------------------- +API Ref: http://developers.marketo.com/documentation/rest/get-import-failure-file/ +```python +batch_id = 899 +failed_leads = mc.execute(method='get_import_failure_file', id=batch_id) +file_name = "import-failure-for-batch-" + str(batch_id) + ".csv" +if failed_leads is not '': + f = open(file_name, encoding='utf-8', mode='w') + f.write(failed_leads) + f.close() + +# specify the batch ID that is returned in 'Import Lead' +``` + +Get Import Warning File +----------------------- +API Ref: http://developers.marketo.com/documentation/rest/get-import-warning-file/ +```python +batch_id = 899 +warning_leads = mc.execute(method='get_import_warning_file', id=batch_id) +file_name = "import-warning-for-batch-" + str(batch_id) + ".csv" +if warning_leads is not '': + f = open(file_name, encoding='utf-8', mode='w') + f.write(warning_leads) + f.close() + +# specify the batch ID that is returned in 'Import Lead' +``` + +Describe +-------- +API Ref: http://developers.marketo.com/documentation/rest/describe/ +```python +lead = mc.execute(method='describe') ``` Get Activity Types @@ -49,7 +261,7 @@ API Ref: http://developers.marketo.com/documentation/rest/get-paging-token/ #2014-10-06T13:22-07:00 #2014-10-06 -mc.execute(method = 'get_paging_token', sinceDatetime = '2014-10-06') +mc.execute(method='get_paging_token', sinceDatetime='2014-10-06') ``` Get Lead Activity @@ -61,18 +273,133 @@ API Ref: http://developers.marketo.com/documentation/rest/get-lead-activities/ mc.execute(method = 'get_lead_activity', activityTypeIds = ['23','22'], sinceDatetime = '2014-10-06', batchSize = None, listId = None) ``` -Create Lead ------------- -API Ref: http://developers.marketo.com/documentation/rest/createupdate-leads/ +Get Lead Changes +---------------- +API Ref: http://developers.marketo.com/documentation/rest/get-lead-changes/ ```python -mc.execute(method = 'create_lead', lookupField = 'email', lookupValue = 'test@test.com', values = {'firstName':'Test1', 'lastName':'Test2'}) +lead = mc.execute(method='get_lead_changes', fields='firstName', sinceDatetime='2015-09-01', listId=None) + +# sinceDateTime and listId are optional +# this will potentially return a lot of records: the function loops until it has all activities, then returns them ``` -Update Lead ------------- -API Ref: http://developers.marketo.com/documentation/rest/createupdate-leads/ +Get Daily Usage +--------------- +API Ref: http://developers.marketo.com/documentation/rest/get-daily-usage/ ```python -mc.execute(method = 'update_lead', lookupField = 'email', lookupValue = 'test@test.com', values = {'firstName':'Test1', 'lastName':'Test2'}) +lead = mc.execute(method='get_daily_usage') +``` + +Get Last 7 Days Usage +--------------------- +API Ref: http://developers.marketo.com/documentation/rest/get-last-7-days-usage/ +```python +lead = mc.execute(method='get_last_7_days_usage') +``` + +Get Daily Errors +---------------- +API Ref: http://developers.marketo.com/documentation/rest/get-daily-errors/ +```python +lead = mc.execute(method='get_daily_errors') +``` + +Get Last 7 Days Errors +---------------------- +API Ref: http://developers.marketo.com/documentation/rest/get-last-7-days-errors/ +```python +lead = mc.execute(method='get_last_7_days_errors') +``` + +Delete Lead +----------- +API Ref: http://developers.marketo.com/documentation/rest/delete-lead/ +```python +lead = mc.execute(method='delete_lead', id=[1,2]) + +# max number of leads to pass in is 300 +``` + +Get Deleted Leads +----------------- +API Ref: http://developers.marketo.com/documentation/rest/get-deleted-leads/ +```python +lead = mc.execute(method='get_deleted_leads', sinceDatetime=date.today(), batchSize=None) + +# batchSize is optional; default batchSize is 300 +# function will loop to download all deleted leads since the specified time +# will return first and last name, Marketo ID and time of deletion, but no additional Lead attributes +``` + +Update Leads Partition +---------------------- +API Ref: http://developers.marketo.com/documentation/rest/update-leads-partition/ +```python +idAndPartitionName = [{'id': 3482156, 'partitionName': 'Default'}, {'id': 3482141, 'partitionName': 'Europe'}] +lead = mc.execute(method='update_leads_partition', idAndPartitionName=idAndPartitionName) +``` + + + +Asset Objects +============= + +Create Folder +------------- +API Ref: http://developers.marketo.com/documentation/asset-api/create-folder/ +```python +lead = mc.execute(method='create_folder', name='pytest2', parent=115, description='optional description') + +# description is optional +``` + +Get Folder by Id +---------------- +API Ref: http://developers.marketo.com/documentation/asset-api/get-folder-by-id/ +```python +lead = mc.execute(method='get_folder_by_id', id=3, type='Folder') + +# type is optional (even though the docs say it's required); type is 'Folder' or 'Program' +# returns False when no folder found +``` + +Get Folder by Name +------------------ +API Ref: http://developers.marketo.com/documentation/asset-api/get-folder-by-name/ +```python +lead = mc.execute(method='get_folder_by_name', name='pytest', type='Folder', root=115, workSpace='Europe') + +# type, root and workSpace are optional +# returns False when no folders found +``` + +Browse Folders +-------------- +API Ref: http://developers.marketo.com/documentation/asset-api/browse-folders +```python +lead = mc.execute(method='browse_folders', root=3, maxDepth=5, maxReturn=200, workSpace='Default') + +# maxDepth, maxReturn and workSpace are optional +# the folder ID for 'root' is not always the same as the folder ID you see in the UI of the Marketo app +``` + +Create a File +------------- +API Ref: http://developers.marketo.com/documentation/asset-api/create-a-file/ +```python +lead = mc.execute(method='create_file', name='Marketo-Logo3.jpg', file='Marketo-Logo.jpg', folder=115, description='test file', insertOnly=False) + +# description and insertOnly are optional +# in 'file', specify a path if file is not in the same folder as the Python script +``` + +List Files +---------- +API Ref: http://developers.marketo.com/documentation/asset-api/list-files/ +```python +lead = mc.execute(method='list_files', folder=709, offset=0, maxReturn=200) + +# offset and maxReturn are optional ``` diff --git a/pythonmarketo/client.py b/pythonmarketo/client.py index 1a3a9e7..cc64635 100644 --- a/pythonmarketo/client.py +++ b/pythonmarketo/client.py @@ -1,7 +1,7 @@ from pythonmarketo.helper.http_lib import HttpLib from pythonmarketo.helper.exceptions import MarketoException -import json import time +import requests class MarketoClient: host = None @@ -28,7 +28,7 @@ def __init__(self, host, client_id, client_secret, api_limit=None): def execute(self, method, *args, **kargs): result = None if self.API_LIMIT and self.API_CALLS_MADE >= self.API_LIMIT: - raise Exception({'message':'API Calls exceded the limit : ' + str(self.API_LIMIT), 'code':'416'}) + raise Exception({'message':'API Calls exceeded the limit : ' + str(self.API_LIMIT), 'code':'416'}) ''' max 10 rechecks @@ -38,15 +38,52 @@ def execute(self, method, *args, **kargs): method_map={ 'get_leads':self.get_leads, 'get_leads_by_listId':self.get_leads_by_listId, + 'get_multiple_leads_by_filter_type':self.get_multiple_leads_by_filter_type, 'get_activity_types':self.get_activity_types, 'get_lead_activity':self.get_lead_activity, 'get_paging_token':self.get_paging_token, 'update_lead':self.update_lead, 'create_lead':self.create_lead, + 'create_update_leads':self.create_update_leads, 'get_lead_activity_page':self.get_lead_activity_page, 'get_email_content_by_id':self.get_email_content_by_id, 'get_email_template_content_by_id':self.get_email_template_content_by_id, 'get_email_templates':self.get_email_templates, + 'run_request_campaign':self.run_request_campaign, + 'merge_leads':self.merge_leads, + 'add_to_list':self.add_to_list, + 'remove_from_list':self.remove_from_list, + 'browse_folders':self.browse_folders, + 'create_folder':self.create_folder, + 'create_get_folder':self.create_get_folder, + 'get_folder_by_id':self.get_folder_by_id, + 'get_folder_by_name':self.get_folder_by_name, + 'list_files':self.list_files, + 'create_file':self.create_file, + 'associate_lead':self.associate_lead, + 'get_lead_partitions':self.get_lead_partitions, + 'get_list_by_id':self.get_list_by_id, + 'get_multiple_lists':self.get_multiple_lists, + 'member_of_list':self.member_of_list, + 'get_campaign_by_id':self.get_campaign_by_id, + 'get_multiple_campaigns':self.get_multiple_campaigns, + 'schedule_campaign':self.schedule_campaign, + 'request_campaign':self.request_campaign, + 'import_lead':self.import_lead, + 'get_import_lead_status':self.get_import_lead_status, + 'get_import_failure_file':self.get_import_failure_file, + 'get_import_warning_file':self.get_import_warning_file, + 'describe':self.describe, + 'get_lead_changes':self.get_lead_changes, + 'get_daily_usage':self.get_daily_usage, + 'get_last_7_days_usage':self.get_last_7_days_usage, + 'get_daily_errors':self.get_daily_errors, + 'get_last_7_days_errors':self.get_last_7_days_errors, + 'delete_lead':self.delete_lead, + 'get_deleted_leads':self.get_deleted_leads, + 'update_leads_partition':self.update_leads_partition, + 'get_lead_by_id':self.get_lead_by_id, + 'get_multiple_leads_by_program_id':self.get_multiple_leads_by_program_id } result = method_map[method](*args,**kargs) @@ -161,6 +198,22 @@ def get_leads_by_listId(self, listId = None , batchSize = None, fields = []): args['nextPageToken'] = data['nextPageToken'] return result_list + def get_multiple_leads_by_filter_type(self, filterType, filterValues, fields=None, nextPageToken=None): + self.authenticate() + data=[('filterValues',filterValues),('filterType',filterType)] + if fields is not None: + data.append(('fields',fields)) + args = { + 'access_token' : self.token, + '_method' : 'GET' + } + if nextPageToken is not None: + args['nextPageToken'] = nextPageToken + result = HttpLib().post("https://" + self.host + "/rest/v1/leads.json",args,data,mode='nojsondumps') + if result is None: raise Exception("Empty Response") + if not result['success'] : raise MarketoException(result['errors'][0]) + return result['result'] + def get_activity_types(self): self.authenticate() args = { @@ -236,13 +289,653 @@ def create_lead(self, lookupField, lookupValue, values): ] } return self.post(data) - + + def upsert_lead(self, lookupField, lookupValue, values): + new_lead = dict(list({lookupField : lookupValue}.items()) + list(values.items())) + data = { + 'action' : 'createOrUpdate', + 'lookupField' : lookupField, + 'input' : [ + new_lead + ] + } + return self.post(data) + + def create_update_leads(self, leads, action='createOrUpdate', lookupField=None, asyncProcessing=None, partitionName=None): + # expected format for 'leads': [{"email":"joe@example.com","firstName":"Joe"},{"email":"jill@example.com","firstName":"Jill"}] + data = { + 'action' : action, + 'input' : leads + } + if lookupField is not None: + data['lookupField'] = lookupField + if asyncProcessing is not None: + data['asyncProcessing '] = asyncProcessing + if partitionName is not None: + data['partitionName'] = partitionName + return self.post(data) + def post(self, data): self.authenticate() args = { - 'access_token' : self.token + 'access_token' : self.token } data = HttpLib().post("https://" + self.host + "/rest/v1/leads.json" , args, data) if not data['success'] : raise MarketoException(data['errors'][0]) - return data['result'][0]['status'] - \ No newline at end of file + return data['result'] + + def run_request_campaign(self, campaignID, leadsID, values): + token_list = [{'name':'{{' + k + '}}', 'value':v} for k, v in values.items()] + leads_list = [{'id':items} for items in leadsID] + data={ + 'input': {"leads": + leads_list, + "tokens": + token_list + } + } + + self.authenticate() + args = { + 'access_token' : self.token + } + x="https://" + self.host + "/rest/v1/campaigns/" + str(campaignID)+ "/trigger.json" + result = HttpLib().post("https://" + self.host + "/rest/v1/campaigns/" + str(campaignID)+ "/trigger.json", args, data) + if not result['success'] : raise MarketoException(result['errors'][0]) + return result['success'] + + def merge_leads(self, winning_ld, loosing_leads_list, mergeInCRM=False): + leadstr = str(loosing_leads_list).strip('[]') + leadsing = '&leadIds=' + leadstr + self.authenticate() + args = { + 'access_token' : self.token + } + if len(loosing_leads_list) > 1: + data={ + 'leadIds':leadstr + } + else: + data={ + 'leadld' : leadstr, + 'mergeInCRM' : mergeInCRM + } + data = None + args = None + headers = {'content-type': 'application/json'} + urls = "https://" + self.host + "/rest/v1/leads/" + str(winning_ld) + "/merge.json?access_token=" + self.token + leadsing + result = requests.post(urls, headers = headers) + x = result.json() + if result.status_code != 200: + return False + else: + return x['success'] + + def add_to_list(self, listId, leadIds): + #currently only handles 300 Leads at a time; looping needs to be implemented outside + self.authenticate() + leads_list = [{'id':items} for items in leadIds] + data={ + 'input': leads_list + } + args = { + 'access_token' : self.token + } + result = HttpLib().post("https://" + self.host + "/rest/v1/lists/" + str(listId)+ "/leads.json", args, data) + if result is None: raise Exception("Empty Response") + if not result['success'] : raise MarketoException(result['errors'][0]) + return result + + def remove_from_list(self, listId, leadIds): + self.authenticate() + leads_list = [{'id':items} for items in leadIds] + data={ + 'input': leads_list + } + args = { + 'access_token' : self.token + } + result = HttpLib().delete("https://" + self.host + "/rest/v1/lists/" + str(listId)+ "/leads.json", args, data) + if result is None: raise Exception("Empty Response") + if not result['success'] : raise MarketoException(result['errors'][0]) + return result + + def browse_folders(self, root, offset=None, maxDepth=None, maxReturn=None, workSpace=None): + # this does not loop, so for now limited to 200 folder; will implement looping in the future + self.authenticate() + if root is None: raise ValueError("Invalid argument: required argument root is none.") + args = { + 'access_token' : self.token, + 'root' : root + } + if offset is not None: + args['offset'] = offset + if maxDepth is not None: + args['maxDepth'] = maxDepth + if maxReturn is not None: + args['maxReturn'] = maxReturn + if workSpace is not None: + args['workSpace'] = workSpace + result = HttpLib().get("https://" + self.host + "/rest/asset/v1/folders.json", args) + if result is None: raise Exception("Empty Response") + self.last_request_id = result['requestId'] + if not result['success'] : raise MarketoException(result['errors'][0]) + try: + return result['result'] + except KeyError: + return False + + def get_folder_by_id(self, id, type=None): + self.authenticate() + if id is None: raise ValueError("Invalid argument: required argument id is none.") + args = { + 'access_token' : self.token + } + if type is not None: + args['type'] = type + result = HttpLib().get("https://" + self.host + "/rest/asset/v1/folder/" + str(id) + ".json", args) + if result is None: raise Exception("Empty Response") + self.last_request_id = result['requestId'] + if not result['success'] : raise MarketoException(result['errors'][0]) + try: + return result['result'] + except KeyError: + return False + + def get_folder_by_name(self, name, type=None, root=None, workSpace=None): + self.authenticate() + if name is None: raise ValueError("Invalid argument: required argument name is none.") + args = { + 'access_token' : self.token, + 'name' : name + } + if type is not None: + args['type'] = type + if root is not None: + args['root'] = root + if workSpace is not None: + args['workSpace'] = workSpace + result = HttpLib().get("https://" + self.host + "/rest/asset/v1/folder/byName.json", args) + if result is None: raise Exception("Empty Response") + self.last_request_id = result['requestId'] + if not result['success'] : raise MarketoException(result['errors'][0]) + try: + return result['result'] + except KeyError: + return False + + def create_folder(self, name, parent, description=None): + self.authenticate() + if name is None: raise ValueError("Invalid argument: required argument name is none.") + if parent is None: raise ValueError("Invalid argument: required argument parent is none.") + args = { + 'access_token' : self.token, + 'name' : name, + 'parent' : parent + } + if description is not None: + args['description'] = description + result = HttpLib().post("https://" + self.host + "/rest/asset/v1/folders.json", args) + if result is None: raise Exception("Empty Response") + self.last_request_id = result['requestId'] + if not result['success'] : raise MarketoException(result['errors'][0]) + return result['result'] + + def create_get_folder(self, name, parent, description=None): + self.authenticate() + if name is None: raise ValueError("Invalid argument: required argument name is none.") + if parent is None: raise ValueError("Invalid argument: required argument parent is none.") + args = { + 'access_token' : self.token, + 'name' : name, + 'parent' : parent + } + if description is not None: + args['description'] = description + result = HttpLib().post("https://" + self.host + "/rest/asset/v1/folders.json", args) + if result is None: raise Exception("Empty Response") + self.last_request_id = result['requestId'] + if not result['success']: + if result['errors'][0]['code'] == "709": + get_fldr = self.get_folder_by_name(name, type='Folder', root=parent) + get_fldr[0]['status'] = 'existing' + else: + raise MarketoException(result['errors'][0]) + return get_fldr + else: + result['result'][0]['status'] = 'new' + return result['result'] + + def list_files(self, folder=None, offset=None, maxReturn=None): + # this does not loop, so for now limited to 200 files; will implement looping in the future + self.authenticate() + args = { + 'access_token' : self.token + } + if folder is not None: + args['folder'] = folder + if offset is not None: + args['offset'] = offset + if maxReturn is not None: + args['maxReturn'] = maxReturn + result = HttpLib().get("https://" + self.host + "/rest/asset/v1/files.json", args) + if result is None: raise Exception("Empty Response") + self.last_request_id = result['requestId'] + if not result['success'] : raise MarketoException(result['errors'][0]) + return result + + def create_file(self, name, file, folder, description=None, insertOnly=None): + self.authenticate() + if name is None: raise ValueError("Invalid argument: required argument name is none.") + if file is None: raise ValueError("Invalid argument: required argument file is none.") + if folder is None: raise ValueError("Invalid argument: required argument folder is none.") + args = { + 'access_token' : self.token, + 'name' : name, + 'folder' : folder + } + if description is not None: + args['description'] = description + if insertOnly is not None: + args['insertOnly'] = insertOnly + result = HttpLib().post("https://" + self.host + "/rest/asset/v1/files.json", args, files=file) + if result is None: raise Exception("Empty Response") + self.last_request_id = result['requestId'] + if not result['success'] : raise MarketoException(result['errors'][0]) + return result + + + def associate_lead(self, id, cookie): + self.authenticate() + if id is None: raise ValueError("Invalid argument: required argument id is none.") + if cookie is None: raise ValueError("Invalid argument: required argument cookie is none.") + args = { + 'access_token' : self.token, + 'id' : id, + 'cookie' : cookie + } + result = HttpLib().post("https://" + self.host + "/rest/v1/leads/" + str(id) + "associate.json", args) + if result is None: raise Exception("Empty Response") + self.last_request_id = result['requestId'] + if not result['success'] : raise MarketoException(result['errors'][0]) + return result['result'] + + def get_lead_partitions(self): + self.authenticate() + args = { + 'access_token' : self.token + } + data = HttpLib().get("https://" + self.host + "/rest/v1/leads/partitions.json", args) + if data is None: raise Exception("Empty Response") + if not data['success'] : raise MarketoException(data['errors'][0]) + return data['result'] + + def get_list_by_id(self, id): + self.authenticate() + args = { + 'access_token' : self.token + } + data = HttpLib().get("https://" + self.host + "/rest/v1/lists/" + str(id) + ".json", args) + if data is None: raise Exception("Empty Response") + if not data['success'] : raise MarketoException(data['errors'][0]) + return data['result'] + + def get_multiple_lists(self, id=None, name=None, programName=None, workspaceName=None, batchSize=None, nextPageToken=None): + self.authenticate() + args = { + 'access_token' : self.token + } + if id is not None: + args['id'] = id + if name is not None: + args['name'] = name + if programName is not None: + args['programName'] = programName + if workspaceName is not None: + args['workspaceName'] = workspaceName + if batchSize is not None: + args['batchSize'] = batchSize + if nextPageToken is not None: + args['nextPageToken'] = nextPageToken + result = HttpLib().get("https://" + self.host + "/rest/v1/lists.json", args) + if result is None: raise Exception("Empty Response") + self.last_request_id = result['requestId'] + if not result['success'] : raise MarketoException(result['errors'][0]) + return result['result'] + + def member_of_list(self, listId, id): + # this uses POST so large numbers of leads can be specified + self.authenticate() + leads_list = [{'id':items} for items in id] + data = { + 'input': leads_list + } + args = { + 'access_token' : self.token, + '_method' : 'GET' + } + data = HttpLib().post("https://" + self.host + "/rest/v1/lists/" + str(listId) + "/leads/ismember.json", args, data) + if data is None: raise Exception("Empty Response") + if not data['success'] : raise MarketoException(data['errors'][0]) + return data['result'] + + def get_campaign_by_id(self, id): + self.authenticate() + args = { + 'access_token' : self.token, + 'id' : id + } + data = HttpLib().get("https://" + self.host + "/rest/v1/campaigns/" + str(id) + ".json", args) + if data is None: raise Exception("Empty Response") + if not data['success'] : raise MarketoException(data['errors'][0]) + return data['result'] + + def get_multiple_campaigns(self, id=None, name=None, programName=None, workspaceName=None, batchSize=None, nextPageToken=None): + self.authenticate() + args = { + 'access_token' : self.token, + '_method' : 'GET' + } + if id is not None: + data = [('id',items) for items in id] + #print(data) + if name is not None: + args['name'] = name + if programName is not None: + args['programName'] = programName + if workspaceName is not None: + args['workspaceName'] = workspaceName + if batchSize is not None: + args['batchSize'] = batchSize + if nextPageToken is not None: + args['nextPageToken'] = nextPageToken + if id is not None: + result = HttpLib().post("https://" + self.host + "/rest/v1/campaigns.json", args, data, mode='nojsondumps') + else: + result = HttpLib().post("https://" + self.host + "/rest/v1/campaigns.json", args) + if result is None: raise Exception("Empty Response") + self.last_request_id = result['requestId'] + if not result['success'] : raise MarketoException(result['errors'][0]) + return result['result'] + + def schedule_campaign(self, id, runAt=None, cloneToProgramName=None, tokens=None): + self.authenticate() + if id is None: raise ValueError("Invalid argument: required argument id is none.") + if runAt is not None or cloneToProgramName is not None or tokens is not None: + data={ + 'input': {} + } + if runAt is not None: + data['input']['runAt'] = runAt + print(data) + elif cloneToProgramName is not None: + data['input']['cloneToProgramName'] = cloneToProgramName + elif tokens is not None: + token_list = [{'name':'{{' + k + '}}', 'value':v} for k, v in tokens.items()] + print(token_list) + data['input']['tokens'] = token_list + args = { + 'access_token' : self.token + } + if runAt is not None or cloneToProgramName is not None or tokens is not None: + result = HttpLib().post("https://" + self.host + "/rest/v1/campaigns/" + str(id)+ "/schedule.json", args, data) + else: + result = HttpLib().post("https://" + self.host + "/rest/v1/campaigns/" + str(id)+ "/schedule.json", args) + if not result['success'] : raise MarketoException(result['errors'][0]) + return result['success'] + + def request_campaign(self, id, leads, tokens=None): + self.authenticate() + if id is None: raise ValueError("Invalid argument: required argument id is none.") + if leads is None: raise ValueError("Invalid argument: required argument leads is none.") + leads_list = [{'id':items} for items in leads] + if tokens is not None: + token_list = [{'name':'{{' + k + '}}', 'value':v} for k, v in tokens.items()] + data={ + 'input': {"leads": + leads_list, + "tokens": + token_list + } + } + else: + data={ + 'input': {"leads": + leads_list + } + } + args = { + 'access_token' : self.token + } + result = HttpLib().post("https://" + self.host + "/rest/v1/campaigns/" + str(id)+ "/trigger.json", args, data) + if not result['success'] : raise MarketoException(result['errors'][0]) + return result['success'] + + def import_lead(self, format, file, lookupField=None, listId=None, partitionName=None): + self.authenticate() + if format is None: raise ValueError("Invalid argument: required argument format is none.") + if file is None: raise ValueError("Invalid argument: required argument file is none.") + args = { + 'access_token' : self.token, + 'format' : format + } + if lookupField is not None: + args['lookupField'] = lookupField + if listId is not None: + args['listId'] = listId + if partitionName is not None: + args['partitionName'] = partitionName + result = HttpLib().post("https://" + self.host + "/bulk/v1/leads.json", args, files=file) + if result is None: raise Exception("Empty Response") + self.last_request_id = result['requestId'] + if not result['success'] : raise MarketoException(result['errors'][0]) + return result + + def get_import_lead_status(self, id): + self.authenticate() + if id is None: raise ValueError("Invalid argument: required argument id is none.") + args = { + 'access_token' : self.token + } + data = HttpLib().get("https://" + self.host + "/bulk/v1/leads/batch/" + str(id) + ".json", args) + if data is None: raise Exception("Empty Response") + if not data['success'] : raise MarketoException(data['errors'][0]) + return data['result'] + + def get_import_failure_file(self, id): + self.authenticate() + if id is None: raise ValueError("Invalid argument: required argument id is none.") + args = { + 'access_token' : self.token + } + data = HttpLib().get("https://" + self.host + "/bulk/v1/leads/batch/" + str(id) + "/failures.json", args, mode='nojson') + if data is None: raise Exception("Empty Response") + return data.text + + def get_import_warning_file(self, id): + self.authenticate() + if id is None: raise ValueError("Invalid argument: required argument id is none.") + args = { + 'access_token' : self.token + } + data = HttpLib().get("https://" + self.host + "/bulk/v1/leads/batch/" + str(id) + "/warnings.json", args, mode='nojson') + if data is None: raise Exception("Empty Response") + return data.text + + def describe(self): + self.authenticate() + args = { + 'access_token' : self.token + } + data = HttpLib().get("https://" + self.host + "/rest/v1/leads/describe.json", args) + if data is None: raise Exception("Empty Response") + if not data['success'] : raise MarketoException(data['errors'][0]) + return data['result'] + + def get_lead_changes_page(self, fields, nextPageToken, batchSize = None, listId = None): + self.authenticate() + fields = fields.split() if type(fields) is str else fields + args = { + 'access_token' : self.token, + 'fields' : ",".join(fields), + 'nextPageToken' : nextPageToken + } + if listId: + args['listId'] = listId + if batchSize: + args['batchSize'] = batchSize + data = HttpLib().get("https://" + self.host + "/rest/v1/activities/leadchanges.json", args) + if data is None: raise Exception("Empty Response") + if not data['success'] : raise MarketoException(data['errors'][0]) + return data + + def get_lead_changes(self, fields, sinceDatetime, batchSize = None, listId = None): + if fields is None: raise ValueError("Invalid argument: required argument fields is none.") + if sinceDatetime is None: raise ValueError("Invalid argument: required argument sinceDatetime is none.") + activity_result_list = [] + nextPageToken = self.get_paging_token(sinceDatetime = sinceDatetime) + moreResult = True + while moreResult: + result = self.get_lead_changes_page(fields, nextPageToken, batchSize, listId) + if result is None: + break + moreResult = result['moreResult'] + nextPageToken = result['nextPageToken'] + if 'result' in result: + activity_result_list.extend(result['result']) + + return activity_result_list + + def get_daily_usage(self): + self.authenticate() + args = { + 'access_token' : self.token + } + data = HttpLib().get("https://" + self.host + "/rest/v1/stats/usage.json", args) + if data is None: raise Exception("Empty Response") + if not data['success'] : raise MarketoException(data['errors'][0]) + return data['result'] + + def get_last_7_days_usage(self): + self.authenticate() + args = { + 'access_token' : self.token + } + data = HttpLib().get("https://" + self.host + "/rest/v1/stats/usage/last7days.json", args) + if data is None: raise Exception("Empty Response") + if not data['success'] : raise MarketoException(data['errors'][0]) + return data['result'] + + def get_daily_errors(self): + self.authenticate() + args = { + 'access_token' : self.token + } + data = HttpLib().get("https://" + self.host + "/rest/v1/stats/errors.json", args) + if data is None: raise Exception("Empty Response") + if not data['success'] : raise MarketoException(data['errors'][0]) + return data['result'] + + def get_last_7_days_errors(self): + self.authenticate() + args = { + 'access_token' : self.token + } + data = HttpLib().get("https://" + self.host + "/rest/v1/stats/errors/last7days.json", args) + if data is None: raise Exception("Empty Response") + if not data['success'] : raise MarketoException(data['errors'][0]) + return data['result'] + + def delete_lead(self, id): + self.authenticate() + if id is None: raise ValueError("Invalid argument: required argument id is none.") + leads_list = [{'id':items} for items in id] + data={ + 'input': leads_list + } + args = { + 'access_token' : self.token + } + result = HttpLib().delete("https://" + self.host + "/rest/v1/leads.json", args, data) + if result is None: raise Exception("Empty Response") + if not result['success'] : raise MarketoException(result['errors'][0]) + return result + + def get_deleted_leads_page(self, nextPageToken, batchSize = None): + self.authenticate() + args = { + 'access_token' : self.token, + 'nextPageToken' : nextPageToken + } + if batchSize: + args['batchSize'] = batchSize + data = HttpLib().get("https://" + self.host + "/rest/v1/activities/deletedleads.json", args) + if data is None: raise Exception("Empty Response") + if not data['success'] : raise MarketoException(data['errors'][0]) + return data + + def get_deleted_leads(self, sinceDatetime, batchSize = None): + if sinceDatetime is None: raise ValueError("Invalid argument: required argument sinceDatetime is none.") + deleted_leads_list = [] + nextPageToken = self.get_paging_token(sinceDatetime = sinceDatetime) + moreResult = True + while moreResult: + result = self.get_deleted_leads_page(nextPageToken, batchSize) + if result is None: + break + moreResult = result['moreResult'] + nextPageToken = result['nextPageToken'] + if 'result' in result: + deleted_leads_list.extend(result['result']) + + return deleted_leads_list + + def update_leads_partition(self, idAndPartitionName): + self.authenticate() + if idAndPartitionName is None: raise ValueError("Invalid argument: required argument idAndPartitionName is none.") + data={ + 'input': [] + } + for lead in idAndPartitionName: + data['input'].append(lead) + args = { + 'access_token' : self.token + } + result = HttpLib().post("https://" + self.host + "/rest/v1/leads/partitions.json", args, data) + if not result['success'] : raise MarketoException(result['errors'][0]) + return result + + def get_lead_by_id(self, id, fields=None): + self.authenticate() + if id is None: raise ValueError("Invalid argument: required argument id is none.") + args = { + 'access_token' : self.token + } + if fields is not None: + args['fields'] = fields + data = HttpLib().get("https://" + self.host + "/rest/v1/lead/" + str(id) + ".json", args) + if data is None: raise Exception("Empty Response") + if not data['success'] : raise MarketoException(data['errors'][0]) + return data['result'] + + def get_multiple_leads_by_program_id(self, programId , batchSize = None, fields = []): + self.authenticate() + args = { + 'access_token' : self.token + } + if len(fields) > 0: + args['fields'] = ",".join(fields) + if batchSize: + args['batchSize'] = batchSize + result_list = [] + while True: + data = HttpLib().get("https://" + self.host + "/rest/v1/leads/programs/" + str(programId)+ ".json", args) + if data is None: raise Exception("Empty Response") + self.last_request_id = data['requestId'] + if not data['success'] : raise MarketoException(data['errors'][0]) + result_list.extend(data['result']) + if len(data['result']) == 0 or 'nextPageToken' not in data: + break + args['nextPageToken'] = data['nextPageToken'] + return result_list + diff --git a/pythonmarketo/helper/http_lib.py b/pythonmarketo/helper/http_lib.py index 53293e7..effb331 100644 --- a/pythonmarketo/helper/http_lib.py +++ b/pythonmarketo/helper/http_lib.py @@ -1,41 +1,83 @@ import requests -import urllib import json import time +try: + from urllib.parse import urlencode +except ImportError: + from urllib import urlencode class HttpLib: max_retries = 3 sleep_duration = 3 - - def get(self, endpoint, args = None): + def get(self, endpoint, args = None, mode=None): retries = 0 while True: if retries > self.max_retries: - return None + return None try: url = endpoint - if args: - url = endpoint + "?" + urllib.urlencode(args) - r = requests.get(url) - return r.json() + if args: + r = requests.get(url, params=args) + else: + r = requests.get(url) + if mode is 'nojson': + return r + else: + return r.json() except Exception as e: print("HTTP Get Exception!!! Retrying.....") time.sleep(self.sleep_duration) retries += 1 - - def post(self, endpoint, args, data): + + def post(self, endpoint, args, data=None, files=None, mode=None): + retries = 0 + while True: + if retries > self.max_retries: + return None + try: + url = endpoint + "?" + urlencode(args) + headers = {'Content-type': 'application/json'} + if mode is 'nojsondumps': + #print("mode is nojsondumps") + r = requests.post(url, data=data) + elif data is None and files is None: + #print('only args') + r = requests.post(url, headers=headers) + elif data is not None and files is None: + #print('args plus data') + r = requests.post(url, data=json.dumps(data), headers=headers) + elif data is None and files is not None: + #print('args plus files') + # removed the headers with JSON content type, because the file is not JSON + # in future try to infer the correct content type based on file extension (create separate function) + with open(files,'rb') as f: + files = {'file': f} + r = requests.post(url, files=files) + else: + #print('args plus data plus files') + # removed the headers with JSON content type, because the file is not JSON + with open(files,'rb') as f: + files = {'file': f} + r = requests.post(url, data=json.dumps(data), files=files) + return r.json() + except Exception as e: + print("HTTP Post Exception!!! Retrying....."+ str(e)) + time.sleep(self.sleep_duration) + retries += 1 + + def delete(self, endpoint, args, data): retries = 0 while True: if retries > self.max_retries: - return None + return None try: - url = endpoint + "?" + urllib.urlencode(args) + url = endpoint + "?" + urlencode(args) headers = {'Content-type': 'application/json'} - r = requests.post(url, data=json.dumps(data), headers=headers) + r = requests.delete(url, data=json.dumps(data), headers=headers) return r.json() except Exception as e: - print("HTTP Post Exception!!! Retrying.....") + print("HTTP Delete Exception!!! Retrying....."+ str(e)) time.sleep(self.sleep_duration) retries += 1