From 3b1177a37575695a22d01952e99aac06b8fe0648 Mon Sep 17 00:00:00 2001 From: Sandip Sinha Date: Fri, 8 May 2015 15:43:02 -0700 Subject: [PATCH 01/28] Added run_request_campaign function to handle Request Campaign API in Marketo --- pythonmarketo/client.py | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/pythonmarketo/client.py b/pythonmarketo/client.py index 1a3a9e7..128e934 100644 --- a/pythonmarketo/client.py +++ b/pythonmarketo/client.py @@ -47,6 +47,7 @@ def execute(self, method, *args, **kargs): '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, + 'request_campaign':self.run_request_campaign, } result = method_map[method](*args,**kargs) @@ -236,7 +237,43 @@ def create_lead(self, lookupField, lookupValue, values): ] } return self.post(data) + + def run_request_campaign(self,campaignID,leadsID,values): + def add_curly(k): + return '{{'+k+'}}' + append_array={add_curly(k):v for k, v in values.items()} + leads_list=[] + token_dict={} + token_list=[] + leads_dict={} + for k,v in append_array.items(): + token_dict['name']=k + token_dict['value']=v + token_list.append(token_dict) + for rows in leadsID: + leads_dict['id']=rows + leads_list.append(leads_dict) + camp_dict={} + camp_dict['tokens']= token_list + 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(data['errors'][0]) + return result['success'] + + def post(self, data): self.authenticate() args = { @@ -245,4 +282,4 @@ def post(self, data): 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 + From d469feadbf8493be71e649cfbf5b49e51580cf9c Mon Sep 17 00:00:00 2001 From: Sandip Sinha Date: Thu, 14 May 2015 10:57:47 -0700 Subject: [PATCH 02/28] Made corrections suggested by the moderator --- pythonmarketo/client.py | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/pythonmarketo/client.py b/pythonmarketo/client.py index 128e934..16f9d5c 100644 --- a/pythonmarketo/client.py +++ b/pythonmarketo/client.py @@ -238,21 +238,9 @@ def create_lead(self, lookupField, lookupValue, values): } return self.post(data) - def run_request_campaign(self,campaignID,leadsID,values): - def add_curly(k): - return '{{'+k+'}}' - append_array={add_curly(k):v for k, v in values.items()} - leads_list=[] - token_dict={} - token_list=[] - leads_dict={} - for k,v in append_array.items(): - token_dict['name']=k - token_dict['value']=v - token_list.append(token_dict) - for rows in leadsID: - leads_dict['id']=rows - leads_list.append(leads_dict) + 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 leads_list] camp_dict={} camp_dict['tokens']= token_list data={ From ab6e21de5fcc38d4e9cdd677f4c641a8198fdb30 Mon Sep 17 00:00:00 2001 From: Sandip Sinha Date: Thu, 14 May 2015 11:33:23 -0700 Subject: [PATCH 03/28] Made corrections suggested by the moderator --- pythonmarketo/client.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pythonmarketo/client.py b/pythonmarketo/client.py index 16f9d5c..1679f33 100644 --- a/pythonmarketo/client.py +++ b/pythonmarketo/client.py @@ -241,8 +241,6 @@ def create_lead(self, lookupField, lookupValue, values): 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 leads_list] - camp_dict={} - camp_dict['tokens']= token_list data={ 'input': {"leads": leads_list From 3a92f3a4295a89fb8c88f055309e6cc262c54152 Mon Sep 17 00:00:00 2001 From: Sandip Sinha Date: Fri, 15 May 2015 14:58:45 -0700 Subject: [PATCH 04/28] Minor updates --- pythonmarketo/client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pythonmarketo/client.py b/pythonmarketo/client.py index 1679f33..27ffaea 100644 --- a/pythonmarketo/client.py +++ b/pythonmarketo/client.py @@ -243,8 +243,8 @@ def run_request_campaign(self, campaignID, leadsID, values): leads_list = [{'id':items} for items in leads_list] data={ 'input': {"leads": - leads_list - ,"tokens": + leads_list, + "tokens": token_list } } @@ -254,7 +254,7 @@ def run_request_campaign(self, campaignID, leadsID, values): '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) + result = HttpLib().post("https://" + self.host + "/rest/v1/campaigns/" + str(campaignID)+ "/trigger.json", args, data) if not result['success'] : raise MarketoException(data['errors'][0]) return result['success'] From 866761ded15ef19f20c290125361a2148ddb8857 Mon Sep 17 00:00:00 2001 From: Sandip Sinha Date: Fri, 29 May 2015 13:25:37 -0700 Subject: [PATCH 05/28] Added 2 more API handles to the client routine. 1. Retrieve leads using a filter. 2. Merge leads --- pythonmarketo/client.py | 49 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/pythonmarketo/client.py b/pythonmarketo/client.py index 27ffaea..c3af39f 100644 --- a/pythonmarketo/client.py +++ b/pythonmarketo/client.py @@ -38,6 +38,7 @@ 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, @@ -48,6 +49,7 @@ def execute(self, method, *args, **kargs): 'get_email_template_content_by_id':self.get_email_template_content_by_id, 'get_email_templates':self.get_email_templates, 'request_campaign':self.run_request_campaign, + 'merge_leads':self.merge_leads, } result = method_map[method](*args,**kargs) @@ -162,6 +164,30 @@ 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, fieldslist): + self.autheticate() + fieldvalstr = ','.join(filterValues) + fields = fieldliststr = None + if len(fieldslist) > 0: + fieldstr = ','.join(fieldslist) + else: + fieldstr= 'id,lastName,firstName,updatedAt,createdAt' + + args={ + 'access_token' : self.token + } + + inputp={ + 'filterType' : filterType, + 'filterValues' : fieldvalstr, + 'fields' : fieldstr + } + data = HttpLib().get("https://" + self.host + "/rest/v1/leads.json", args,inputp) + if data is None: raise Exception("Empty Response") + if not data['success'] : raise MarketoException(data['errors'][0]) + return data['result'] + + def get_activity_types(self): self.authenticate() args = { @@ -258,7 +284,28 @@ def run_request_campaign(self, campaignID, leadsID, values): if not result['success'] : raise MarketoException(data['errors'][0]) return result['success'] - + def merge_leads(win_ld, loosing_leads_list,mergeInCRM=False): + leadstr = ','.join(loosing_leads_list) + if len(loosing_leads_list) > 1: + data={ + 'leadlds' : leadstr, + 'mergeInCRM' : mergeInCRM + } + else: + data={ + 'leadld' : leadstr + } + + + self.authenticate() + args = { + 'access_token' : self.token + } + result = HttpLib().post("https://" + self.host + "/rest/v1/leads/" + str(win_ld) + "/merge.json" , args, data) + if not result['success'] : raise MarketoException(data['errors'][0]) + return result['success'] + + def post(self, data): self.authenticate() From 15ea561fae9f429ca0d495530cb01e7397d8243b Mon Sep 17 00:00:00 2001 From: Sandip Sinha Date: Fri, 29 May 2015 13:35:08 -0700 Subject: [PATCH 06/28] Added 2 more API handles to the client routine. 1. Retrieve leads using a filter. 2. Merge leads --- pythonmarketo/client.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/pythonmarketo/client.py b/pythonmarketo/client.py index c3af39f..15ea580 100644 --- a/pythonmarketo/client.py +++ b/pythonmarketo/client.py @@ -165,24 +165,24 @@ def get_leads_by_listId(self, listId = None , batchSize = None, fields = []): return result_list def get_multiple_leads_by_filter_type(self, filterType, filterValues, fieldslist): - self.autheticate() + self.authenticate() + args={ + 'access_token' : self.token + } fieldvalstr = ','.join(filterValues) fields = fieldliststr = None if len(fieldslist) > 0: fieldstr = ','.join(fieldslist) else: fieldstr= 'id,lastName,firstName,updatedAt,createdAt' - - args={ - 'access_token' : self.token - } - + inputp={ + 'access_token' : self.token, 'filterType' : filterType, 'filterValues' : fieldvalstr, 'fields' : fieldstr - } - data = HttpLib().get("https://" + self.host + "/rest/v1/leads.json", args,inputp) + } + data = HttpLib().get("https://" + self.host + "/rest/v1/leads.json",inputp) if data is None: raise Exception("Empty Response") if not data['success'] : raise MarketoException(data['errors'][0]) return data['result'] @@ -283,8 +283,9 @@ def run_request_campaign(self, campaignID, leadsID, values): result = HttpLib().post("https://" + self.host + "/rest/v1/campaigns/" + str(campaignID)+ "/trigger.json", args, data) if not result['success'] : raise MarketoException(data['errors'][0]) return result['success'] + - def merge_leads(win_ld, loosing_leads_list,mergeInCRM=False): + def merge_leads(winning_ld, loosing_leads_list,mergeInCRM=False): leadstr = ','.join(loosing_leads_list) if len(loosing_leads_list) > 1: data={ @@ -301,7 +302,7 @@ def merge_leads(win_ld, loosing_leads_list,mergeInCRM=False): args = { 'access_token' : self.token } - result = HttpLib().post("https://" + self.host + "/rest/v1/leads/" + str(win_ld) + "/merge.json" , args, data) + result = HttpLib().post("https://" + self.host + "/rest/v1/leads/" + str(winning_ld) + "/merge.json" , args, data) if not result['success'] : raise MarketoException(data['errors'][0]) return result['success'] From eb0b07c221f5366a86d7a0cca2e2ee8f59d8d9ed Mon Sep 17 00:00:00 2001 From: Sandip Sinha Date: Sat, 30 May 2015 13:50:53 -0700 Subject: [PATCH 07/28] Added a print for debug --- pythonmarketo/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythonmarketo/client.py b/pythonmarketo/client.py index 15ea580..0ca4e36 100644 --- a/pythonmarketo/client.py +++ b/pythonmarketo/client.py @@ -297,7 +297,7 @@ def merge_leads(winning_ld, loosing_leads_list,mergeInCRM=False): 'leadld' : leadstr } - + print 'data', winning_ld, ' And ', data self.authenticate() args = { 'access_token' : self.token From 48e0b387c43d57b19a552816780250f10254ca8f Mon Sep 17 00:00:00 2001 From: Sandip Sinha Date: Sun, 31 May 2015 18:46:19 -0700 Subject: [PATCH 08/28] Added a print for debug --- pythonmarketo/client.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pythonmarketo/client.py b/pythonmarketo/client.py index 0ca4e36..a3c1643 100644 --- a/pythonmarketo/client.py +++ b/pythonmarketo/client.py @@ -296,8 +296,6 @@ def merge_leads(winning_ld, loosing_leads_list,mergeInCRM=False): data={ 'leadld' : leadstr } - - print 'data', winning_ld, ' And ', data self.authenticate() args = { 'access_token' : self.token From 324d21041523104fd81b5c7eea2eda8bf144c858 Mon Sep 17 00:00:00 2001 From: Sandip Sinha Date: Mon, 15 Jun 2015 23:51:00 +0000 Subject: [PATCH 09/28] Changes to the client.py routine --- pythonmarketo/client.py | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/pythonmarketo/client.py b/pythonmarketo/client.py index 0ca4e36..cb1ec23 100644 --- a/pythonmarketo/client.py +++ b/pythonmarketo/client.py @@ -2,6 +2,7 @@ from pythonmarketo.helper.exceptions import MarketoException import json import time +import requests class MarketoClient: host = None @@ -285,26 +286,33 @@ def run_request_campaign(self, campaignID, leadsID, values): return result['success'] - def merge_leads(winning_ld, loosing_leads_list,mergeInCRM=False): - leadstr = ','.join(loosing_leads_list) - if len(loosing_leads_list) > 1: + 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={ - 'leadlds' : leadstr, - 'mergeInCRM' : mergeInCRM + 'leadIds':leadstr } else: data={ - 'leadld' : leadstr + 'leadld' : leadstr, + 'mergeInCRM' : mergeInCRM } - - print 'data', winning_ld, ' And ', data - self.authenticate() - args = { - 'access_token' : self.token - } - result = HttpLib().post("https://" + self.host + "/rest/v1/leads/" + str(winning_ld) + "/merge.json" , args, data) - if not result['success'] : raise MarketoException(data['errors'][0]) - return result['success'] + + 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'] From 99b8fc58baf340768d5d7754b4f3299d40458997 Mon Sep 17 00:00:00 2001 From: Sandip Sinha Date: Tue, 16 Jun 2015 00:14:13 +0000 Subject: [PATCH 10/28] Updated client.py --- pythonmarketo/client.py | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/pythonmarketo/client.py b/pythonmarketo/client.py index 0ca4e36..cc4331a 100644 --- a/pythonmarketo/client.py +++ b/pythonmarketo/client.py @@ -2,6 +2,7 @@ from pythonmarketo.helper.exceptions import MarketoException import json import time +import requests class MarketoClient: host = None @@ -285,26 +286,32 @@ def run_request_campaign(self, campaignID, leadsID, values): return result['success'] - def merge_leads(winning_ld, loosing_leads_list,mergeInCRM=False): - leadstr = ','.join(loosing_leads_list) - if len(loosing_leads_list) > 1: + 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={ - 'leadlds' : leadstr, - 'mergeInCRM' : mergeInCRM + 'leadIds':leadstr } else: data={ - 'leadld' : leadstr + 'leadld' : leadstr, + 'mergeInCRM' : mergeInCRM } - - print 'data', winning_ld, ' And ', data - self.authenticate() - args = { - 'access_token' : self.token - } - result = HttpLib().post("https://" + self.host + "/rest/v1/leads/" + str(winning_ld) + "/merge.json" , args, data) - if not result['success'] : raise MarketoException(data['errors'][0]) - return result['success'] + 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'] From aee7adcd5f0dd572662f0a5d84ded7376ef70cb5 Mon Sep 17 00:00:00 2001 From: Sandip Sinha Date: Tue, 21 Jul 2015 16:39:51 -0700 Subject: [PATCH 11/28] Updated request.campaign --- pythonmarketo/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythonmarketo/client.py b/pythonmarketo/client.py index a3c1643..5ddd4ce 100644 --- a/pythonmarketo/client.py +++ b/pythonmarketo/client.py @@ -266,7 +266,7 @@ def create_lead(self, lookupField, lookupValue, values): 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 leads_list] + leads_list = [{'id':items} for items in leadsID] data={ 'input': {"leads": leads_list, From 10e8c1e6f519e44c1d09e488972d48df32179a3b Mon Sep 17 00:00:00 2001 From: Sandip Sinha Date: Fri, 7 Aug 2015 11:20:28 -0700 Subject: [PATCH 12/28] Updated the error handling routine --- pythonmarketo/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythonmarketo/client.py b/pythonmarketo/client.py index be6571d..9576e5e 100644 --- a/pythonmarketo/client.py +++ b/pythonmarketo/client.py @@ -282,7 +282,7 @@ def run_request_campaign(self, campaignID, leadsID, values): } 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(data['errors'][0]) + if not result['success'] : raise MarketoException(result['errors'][0]) return result['success'] From ef291374a6b76c8e2f5fe2e084ebde499e274d4e Mon Sep 17 00:00:00 2001 From: jepcastelein Date: Sun, 11 Oct 2015 10:43:29 -0700 Subject: [PATCH 13/28] added support for browse folders, get folder by id, get folder by name, create folder, list files and create file; changed urllib.urlencode to urllib.parse.urlencode --- pythonmarketo/client.py | 191 ++++++++++++++++++++++++++++--- pythonmarketo/helper/http_lib.py | 50 ++++++-- 2 files changed, 215 insertions(+), 26 deletions(-) diff --git a/pythonmarketo/client.py b/pythonmarketo/client.py index 9576e5e..b9a4ea1 100644 --- a/pythonmarketo/client.py +++ b/pythonmarketo/client.py @@ -1,6 +1,5 @@ from pythonmarketo.helper.http_lib import HttpLib from pythonmarketo.helper.exceptions import MarketoException -import json import time import requests @@ -51,6 +50,14 @@ def execute(self, method, *args, **kargs): 'get_email_templates':self.get_email_templates, '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, + '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 } result = method_map[method](*args,**kargs) @@ -264,7 +271,27 @@ 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 post(self, data): + self.authenticate() + args = { + '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'] + 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] @@ -293,34 +320,164 @@ def merge_leads(self, winning_ld, loosing_leads_list,mergeInCRM = False): args = { 'access_token' : self.token } - if len(loosing_leads_list) > 1: + if len(loosing_leads_list) > 1: data={ - 'leadIds':leadstr + 'leadIds':leadstr } else: data={ 'leadld' : leadstr, 'mergeInCRM' : mergeInCRM } - data = None - args = None + 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 + 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 + return False else: - return x['success'] - - + 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 post(self, data): + 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 + 'access_token' : self.token, + 'root' : root } - 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'] - + 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]) + return result['result'] + + 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]) + return result['result'] + + 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]) + return result['result'] + + 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 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 + } + #files = {'file': file} + #print(files) + if description is not None: + args['description'] = description + if insertOnly is not None: + args['insertOnly'] = insertOnly + # this one doesn't work yet; issue with file upload & how to pass file on to HttpLib function + 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 diff --git a/pythonmarketo/helper/http_lib.py b/pythonmarketo/helper/http_lib.py index e87560b..808b840 100644 --- a/pythonmarketo/helper/http_lib.py +++ b/pythonmarketo/helper/http_lib.py @@ -7,16 +7,15 @@ class HttpLib: max_retries = 3 sleep_duration = 3 - def get(self, endpoint, args = None): retries = 0 while True: if retries > self.max_retries: - return None + return None try: url = endpoint - if args: - url = endpoint + "?" + urllib.urlencode(args) + if args: + url = endpoint + "?" + urllib.parse.urlencode(args) r = requests.get(url) return r.json() except Exception as e: @@ -24,18 +23,51 @@ def get(self, endpoint, args = None): time.sleep(self.sleep_duration) retries += 1 - - def post(self, endpoint, args, data): + + def post(self, endpoint, args, data=None, files=None): retries = 0 while True: if retries > self.max_retries: - return None + return None try: - url = endpoint + "?" + urllib.urlencode(args) + url = endpoint + "?" + urllib.parse.urlencode(args) headers = {'Content-type': 'application/json'} - r = requests.post(url, data=json.dumps(data), headers=headers) + if 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 + try: + url = endpoint + "?" + urllib.parse.urlencode(args) + headers = {'Content-type': 'application/json'} + r = requests.delete(url, data=json.dumps(data), headers=headers) + return r.json() + except Exception as e: + print("HTTP Delete Exception!!! Retrying....."+ str(e)) + time.sleep(self.sleep_duration) + retries += 1 From c9f85100c5d2168184924be6664b87c2dcaf7692 Mon Sep 17 00:00:00 2001 From: jepcastelein Date: Fri, 30 Oct 2015 22:11:35 -0700 Subject: [PATCH 14/28] added most of the other basic REST API functions except Import Lead; updated http_lib post function to support any combination of args, data and files --- pythonmarketo/client.py | 387 ++++++++++++++++++++++++++++++- pythonmarketo/helper/http_lib.py | 18 +- 2 files changed, 395 insertions(+), 10 deletions(-) diff --git a/pythonmarketo/client.py b/pythonmarketo/client.py index b9a4ea1..9f124f9 100644 --- a/pythonmarketo/client.py +++ b/pythonmarketo/client.py @@ -44,11 +44,12 @@ def execute(self, method, *args, **kargs): '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, - 'request_campaign':self.run_request_campaign, + '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, @@ -57,7 +58,27 @@ def execute(self, method, *args, **kargs): '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 + '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, + '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) @@ -283,6 +304,20 @@ def upsert_lead(self, lookupField, lookupValue, values): } 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 = { @@ -290,7 +325,7 @@ def post(self, data): } 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'] + return data['result'] def run_request_campaign(self, campaignID, leadsID, values): token_list = [{'name':'{{' + k + '}}', 'value':v} for k, v in values.items()] @@ -311,7 +346,6 @@ def run_request_campaign(self, campaignID, leadsID, values): 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('[]') @@ -481,3 +515,348 @@ def create_file(self, name, file, folder, description=None, insertOnly=None): 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): + # this can also use POST, but not implemented + 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()] + 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 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 808b840..9ae97f1 100644 --- a/pythonmarketo/helper/http_lib.py +++ b/pythonmarketo/helper/http_lib.py @@ -1,7 +1,10 @@ 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 @@ -15,7 +18,7 @@ def get(self, endpoint, args = None): try: url = endpoint if args: - url = endpoint + "?" + urllib.parse.urlencode(args) + url = endpoint + "?" + urlencode(args) r = requests.get(url) return r.json() except Exception as e: @@ -24,15 +27,18 @@ def get(self, endpoint, args = None): retries += 1 - def post(self, endpoint, args, data=None, files=None): + 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 + "?" + urllib.parse.urlencode(args) + url = endpoint + "?" + urlencode(args) headers = {'Content-type': 'application/json'} - if data is None and files is None: + 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: @@ -63,7 +69,7 @@ def delete(self, endpoint, args, data): if retries > self.max_retries: return None try: - url = endpoint + "?" + urllib.parse.urlencode(args) + url = endpoint + "?" + urlencode(args) headers = {'Content-type': 'application/json'} r = requests.delete(url, data=json.dumps(data), headers=headers) return r.json() From 3fcb2bf12981ba5af1a4438d556616f82d181073 Mon Sep 17 00:00:00 2001 From: jepcastelein Date: Sat, 31 Oct 2015 14:28:10 -0700 Subject: [PATCH 15/28] added Import Lead, Get Import Lead Status, Get Import Failure File and Get Import Warning File; updated http_lib post function to support downloading a CSV --- pythonmarketo/client.py | 58 ++++++++++++++++++++++++++++++-- pythonmarketo/helper/http_lib.py | 7 ++-- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/pythonmarketo/client.py b/pythonmarketo/client.py index 9f124f9..bd3d895 100644 --- a/pythonmarketo/client.py +++ b/pythonmarketo/client.py @@ -68,6 +68,10 @@ def execute(self, method, *args, **kargs): '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, @@ -503,13 +507,10 @@ def create_file(self, name, file, folder, description=None, insertOnly=None): 'name' : name, 'folder' : folder } - #files = {'file': file} - #print(files) if description is not None: args['description'] = description if insertOnly is not None: args['insertOnly'] = insertOnly - # this one doesn't work yet; issue with file upload & how to pass file on to HttpLib function 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'] @@ -683,6 +684,57 @@ def request_campaign(self, id, leads, tokens=None): 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 = { diff --git a/pythonmarketo/helper/http_lib.py b/pythonmarketo/helper/http_lib.py index 9ae97f1..326f9a0 100644 --- a/pythonmarketo/helper/http_lib.py +++ b/pythonmarketo/helper/http_lib.py @@ -10,7 +10,7 @@ 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: @@ -20,7 +20,10 @@ def get(self, endpoint, args = None): if args: url = endpoint + "?" + urlencode(args) r = requests.get(url) - return r.json() + if mode is 'nojson': + return r + else: + return r.json() except Exception as e: print("HTTP Get Exception!!! Retrying.....") time.sleep(self.sleep_duration) From 936ea1ffbe37650e89582808b6c482ea9e922f62 Mon Sep 17 00:00:00 2001 From: jepcastelein Date: Sun, 1 Nov 2015 10:54:08 -0800 Subject: [PATCH 16/28] re-implemented 'Get Multiple Leads by Filter Type' to use POST and make 'fields' optional --- pythonmarketo/client.py | 36 +++++++++++++----------------------- 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/pythonmarketo/client.py b/pythonmarketo/client.py index bd3d895..cbb8f9b 100644 --- a/pythonmarketo/client.py +++ b/pythonmarketo/client.py @@ -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 @@ -197,29 +197,19 @@ 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, fieldslist): + def get_multiple_leads_by_filter_type(self, filterType, filterValues, fields=None): self.authenticate() - args={ - 'access_token' : self.token - } - fieldvalstr = ','.join(filterValues) - fields = fieldliststr = None - if len(fieldslist) > 0: - fieldstr = ','.join(fieldslist) - else: - fieldstr= 'id,lastName,firstName,updatedAt,createdAt' - - inputp={ - 'access_token' : self.token, - 'filterType' : filterType, - 'filterValues' : fieldvalstr, - 'fields' : fieldstr - } - data = HttpLib().get("https://" + self.host + "/rest/v1/leads.json",inputp) - if data is None: raise Exception("Empty Response") - if not data['success'] : raise MarketoException(data['errors'][0]) - return data['result'] - + data=[('filterValues',filterValues),('filterType',filterType)] + if fields is not None: + data.append(('fields',fields)) + args = { + 'access_token' : self.token, + '_method' : 'GET' + } + 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() From aff8bb22e84f237232beecaee6d978e597514652 Mon Sep 17 00:00:00 2001 From: jepcastelein Date: Mon, 2 Nov 2015 12:57:01 -0800 Subject: [PATCH 17/28] added support for nextPageToken in Get Multiple Leads by Filter Type --- pythonmarketo/client.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pythonmarketo/client.py b/pythonmarketo/client.py index cbb8f9b..a29e9bc 100644 --- a/pythonmarketo/client.py +++ b/pythonmarketo/client.py @@ -197,7 +197,7 @@ 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): + 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: @@ -206,6 +206,8 @@ def get_multiple_leads_by_filter_type(self, filterType, filterValues, fields=Non '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]) From 56176f950e64843c45089d7691ba23ea763c23f1 Mon Sep 17 00:00:00 2001 From: jepcastelein Date: Mon, 2 Nov 2015 12:57:27 -0800 Subject: [PATCH 18/28] added some documentation --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index 171b685..61506e1 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ from pythonmarketo.client import MarketoClient mc = MarketoClient(host = , client_id = , client_secret = ) + +# Example host: "000-AAA-000.mktorest.com" ``` Get Leads --------- @@ -75,6 +77,26 @@ API Ref: http://developers.marketo.com/documentation/rest/createupdate-leads/ mc.execute(method = 'update_lead', lookupField = 'email', lookupValue = 'test@test.com', values = {'firstName':'Test1', 'lastName':'Test2'}) ``` +Get Multiple Leads by Filter Type +--------------------------------- +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; batch size is 300 (fixed); if more than 300 results, pass in nextPageToken +``` + +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 +``` TODO ==== From 4b18a498259c1a3662c060252681959b6d00d1f8 Mon Sep 17 00:00:00 2001 From: jepcastelein Date: Tue, 3 Nov 2015 21:48:28 -0800 Subject: [PATCH 19/28] added some documentation --- README.md | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 61506e1..1061f8c 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,9 @@ API Ref: http://developers.marketo.com/documentation/rest/get-multiple-leads-by- ```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; batch size is 300 (fixed); if more than 300 results, pass in nextPageToken +# fields and nextPageToken are optional +# max 100 filterValues; batch size is 300 (fixed) +# if more than 300 results, pass in nextPageToken ``` Create/Update Leads @@ -91,13 +93,31 @@ 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') +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 ``` +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 +``` + TODO ==== Remaining API From 7a91db99c1b8b47a04d66f54068914cfe24f5e28 Mon Sep 17 00:00:00 2001 From: jepcastelein Date: Wed, 4 Nov 2015 20:38:43 -0800 Subject: [PATCH 20/28] added documentation on folders; modified error handling when no folders found --- README.md | 39 +++++++++++++++++++++++++++++++++++++++ pythonmarketo/client.py | 10 ++++++++-- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1061f8c..672692d 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,45 @@ lead = mc.execute(method = 'remove_from_list', listId = 1, leadIds = [1,2,3]) # can handle 300 Leads at a time ``` +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 +``` + +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 +``` + +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 +``` + TODO ==== Remaining API diff --git a/pythonmarketo/client.py b/pythonmarketo/client.py index a29e9bc..658b062 100644 --- a/pythonmarketo/client.py +++ b/pythonmarketo/client.py @@ -433,7 +433,10 @@ def get_folder_by_id(self, id, type=None): 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'] + try: + return result['result'] + except KeyError: + return False def get_folder_by_name(self, name, type=None, root=None, workSpace=None): self.authenticate() @@ -452,7 +455,10 @@ def get_folder_by_name(self, name, type=None, root=None, workSpace=None): 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'] + try: + return result['result'] + except KeyError: + return False def create_folder(self, name, parent, description=None): self.authenticate() From aada7664e0d07cdf843ad50847ddca5e053977af Mon Sep 17 00:00:00 2001 From: jepcastelein Date: Sun, 8 Nov 2015 15:11:42 -0800 Subject: [PATCH 21/28] use standard requests.get(url,params) format with params rather than urlencoding outside of requests --- pythonmarketo/helper/http_lib.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pythonmarketo/helper/http_lib.py b/pythonmarketo/helper/http_lib.py index 326f9a0..effb331 100644 --- a/pythonmarketo/helper/http_lib.py +++ b/pythonmarketo/helper/http_lib.py @@ -18,8 +18,9 @@ def get(self, endpoint, args = None, mode=None): try: url = endpoint if args: - url = endpoint + "?" + urlencode(args) - r = requests.get(url) + r = requests.get(url, params=args) + else: + r = requests.get(url) if mode is 'nojson': return r else: From 8aa14af42ce00166f0ab9bc5f29e7ea637847b71 Mon Sep 17 00:00:00 2001 From: jepcastelein Date: Sun, 8 Nov 2015 16:13:07 -0800 Subject: [PATCH 22/28] some small updates --- pythonmarketo/client.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pythonmarketo/client.py b/pythonmarketo/client.py index 658b062..53d7789 100644 --- a/pythonmarketo/client.py +++ b/pythonmarketo/client.py @@ -419,7 +419,10 @@ def browse_folders(self, root, offset=None, maxDepth=None, maxReturn=None, workS 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'] + try: + return result['result'] + except KeyError: + return False def get_folder_by_id(self, id, type=None): self.authenticate() @@ -602,7 +605,6 @@ def get_campaign_by_id(self, id): return data['result'] def get_multiple_campaigns(self, id=None, name=None, programName=None, workspaceName=None, batchSize=None, nextPageToken=None): - # this can also use POST, but not implemented self.authenticate() args = { 'access_token' : self.token, @@ -644,6 +646,7 @@ def schedule_campaign(self, id, runAt=None, cloneToProgramName=None, tokens=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 From 640be4314e76ec9dd2337b0d9153b1a0bbf04e28 Mon Sep 17 00:00:00 2001 From: jepcastelein Date: Sun, 8 Nov 2015 16:13:24 -0800 Subject: [PATCH 23/28] additional documentation --- README.md | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 117 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 672692d..43faec0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ python_marketo ============== -Python interface to marketo REST api
+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.
Detailed Doc - http://developers.marketo.com/documentation/rest/ Installation @@ -13,12 +14,16 @@ 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/ @@ -118,6 +123,94 @@ lead = mc.execute(method = 'remove_from_list', listId = 1, leadIds = [1,2,3]) # can handle 300 Leads at a time ``` +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') +``` + +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 +``` + +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 +``` + + + + +Asset Objects +============= + Browse Folders -------------- API Ref: http://developers.marketo.com/documentation/asset-api/browse-folders @@ -157,6 +250,26 @@ lead = mc.execute(method='create_folder', name='pytest2', parent=115, descriptio # description is optional ``` +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 +``` + +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 +``` + + TODO ==== Remaining API From 446b53f545ee712d30afebe1cb7830b6cb2eb036 Mon Sep 17 00:00:00 2001 From: jepcastelein Date: Sun, 8 Nov 2015 16:16:09 -0800 Subject: [PATCH 24/28] fixed formatting issue --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 43faec0..07ba04a 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,7 @@ lead = mc.execute(method='schedule_campaign', id=1878, runAt=time_as_txt, tokens # tokens and cloneToProgramName are optional # token override only works for tokens without spaces # returns True or False +``` Request Campaign ---------------- From 79522c172868ad6f64b076c6023c01b7f84deade Mon Sep 17 00:00:00 2001 From: jepcastelein Date: Mon, 9 Nov 2015 18:48:29 -0800 Subject: [PATCH 25/28] documented remaining API calls --- README.md | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/README.md b/README.md index 07ba04a..60870e0 100644 --- a/README.md +++ b/README.md @@ -206,6 +206,121 @@ lead = mc.execute(method='request_campaign', id=1880, leads=[46,38], tokens={'my # 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) +``` + +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() +``` + +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() +``` + +Describe +-------- +API Ref: +```python +lead = mc.execute(method='describe') +``` + +Get Lead Changes +---------------- +API Ref: http://developers.marketo.com/documentation/rest/get-lead-changes/ +```python +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 +``` + +Get Daily Usage +--------------- +API Ref: http://developers.marketo.com/documentation/rest/get-daily-usage/ +```python +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) +``` From 383ac59c97bdcdf220571d4a3d077b42ed4c900a Mon Sep 17 00:00:00 2001 From: jepcastelein Date: Mon, 9 Nov 2015 21:27:01 -0800 Subject: [PATCH 26/28] cleaned up the docs a little --- README.md | 183 +++++++++++++++++++++++++++++------------------------- 1 file changed, 100 insertions(+), 83 deletions(-) diff --git a/README.md b/README.md index 60870e0..dbde315 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ python_marketo ============== -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. +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.
-Detailed Doc - http://developers.marketo.com/documentation/rest/ + +Full Marketo REST API documentation - http://developers.marketo.com/documentation/rest/ Installation ============ @@ -25,47 +26,31 @@ 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']) -``` - -Get Leads from listId ---------------------- -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']) +# values could be either "v1 v2 v3" or [v1,v2,v3] ``` -Get Activity Types ------------------- -API Ref: http://developers.marketo.com/documentation/rest/get-activity-types/ -```python -mc.execute(method = 'get_activity_types') -``` - -Get PagingToken ----------------- -API Ref: http://developers.marketo.com/documentation/rest/get-paging-token/ +Get Multiple Leads by Filter Type (alternate implementation) +--------------------------------- +API Ref: http://developers.marketo.com/documentation/rest/get-multiple-leads-by-filter-type/ ```python -#sinceDatetime format: -#2014-10-06T13:22:17-08:00 -#2014-10-06T13:22-07:00 -#2014-10-06 +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) -mc.execute(method = 'get_paging_token', sinceDatetime = '2014-10-06') +# 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 Lead Activity ----------------- -API Ref: http://developers.marketo.com/documentation/rest/get-lead-activities/ +Get Leads by List Id +----------------------------- +API Ref: http://developers.marketo.com/documentation/rest/get-multiple-leads-by-list-id/ ```python -#activityTypeIds could be either "v1 v2 v3" or [v1,v2,v3] - -mc.execute(method = 'get_lead_activity', activityTypeIds = ['23','22'], sinceDatetime = '2014-10-06', batchSize = None, listId = None) +mc.execute(method='get_leads_by_listId', listId='676', fields=['email','firstName','lastName','company','postalCode']) ``` Create Lead @@ -82,17 +67,6 @@ API Ref: http://developers.marketo.com/documentation/rest/createupdate-leads/ mc.execute(method = 'update_lead', lookupField = 'email', lookupValue = 'test@test.com', values = {'firstName':'Test1', 'lastName':'Test2'}) ``` -Get Multiple Leads by Filter Type ---------------------------------- -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; batch size is 300 (fixed) -# if more than 300 results, pass in nextPageToken -``` - Create/Update Leads ------------------- API Ref: http://developers.marketo.com/documentation/rest/createupdate-leads/ @@ -105,24 +79,6 @@ lead = mc.execute(method='create_update_leads', leads=leads, lookupField='email' # max batch size is 300 ``` -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 -``` - Associate Lead -------------- API Ref: http://developers.marketo.com/documentation/rest/associate-lead/ @@ -130,6 +86,15 @@ API Ref: http://developers.marketo.com/documentation/rest/associate-lead/ 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/ @@ -153,6 +118,24 @@ lead = mc.execute(method='get_multiple_lists', id=[724,725], name=None, programN # 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/ @@ -221,6 +204,8 @@ 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 @@ -234,6 +219,8 @@ 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 @@ -247,15 +234,45 @@ 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: +API Ref: http://developers.marketo.com/documentation/rest/describe/ ```python lead = mc.execute(method='describe') ``` +Get Activity Types +------------------ +API Ref: http://developers.marketo.com/documentation/rest/get-activity-types/ +```python +mc.execute(method = 'get_activity_types') +``` + +Get PagingToken +---------------- +API Ref: http://developers.marketo.com/documentation/rest/get-paging-token/ +```python +#sinceDatetime format: +#2014-10-06T13:22:17-08:00 +#2014-10-06T13:22-07:00 +#2014-10-06 + +mc.execute(method='get_paging_token', sinceDatetime='2014-10-06') +``` + +Get Lead Activity +---------------- +API Ref: http://developers.marketo.com/documentation/rest/get-lead-activities/ +```python +#activityTypeIds could be either "v1 v2 v3" or [v1,v2,v3] + +mc.execute(method = 'get_lead_activity', activityTypeIds = ['23','22'], sinceDatetime = '2014-10-06', batchSize = None, listId = None) +``` + Get Lead Changes ---------------- API Ref: http://developers.marketo.com/documentation/rest/get-lead-changes/ @@ -327,14 +344,13 @@ lead = mc.execute(method='update_leads_partition', idAndPartitionName=idAndParti Asset Objects ============= -Browse Folders --------------- -API Ref: http://developers.marketo.com/documentation/asset-api/browse-folders +Create Folder +------------- +API Ref: http://developers.marketo.com/documentation/asset-api/create-folder/ ```python -lead = mc.execute(method='browse_folders', root=3, maxDepth=5, maxReturn=200, workSpace='Default') +lead = mc.execute(method='create_folder', name='pytest2', parent=115, description='optional description') -# 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 +# description is optional ``` Get Folder by Id @@ -357,22 +373,14 @@ lead = mc.execute(method='get_folder_by_name', name='pytest', type='Folder', roo # returns False when no folders found ``` -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 -``` - -List Files ----------- -API Ref: http://developers.marketo.com/documentation/asset-api/list-files/ +Browse Folders +-------------- +API Ref: http://developers.marketo.com/documentation/asset-api/browse-folders ```python -lead = mc.execute(method='list_files', folder=709, offset=0, maxReturn=200) +lead = mc.execute(method='browse_folders', root=3, maxDepth=5, maxReturn=200, workSpace='Default') -# offset and maxReturn are optional +# 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 @@ -385,6 +393,15 @@ lead = mc.execute(method='create_file', name='Marketo-Logo3.jpg', file='Marketo- # 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 +``` + TODO ==== From a401fc62d1337912f3bada3fec440f48b6ce06f6 Mon Sep 17 00:00:00 2001 From: jepcastelein Date: Fri, 18 Dec 2015 21:09:33 -0800 Subject: [PATCH 27/28] added create_get_folders to create a folder, but when a folder with the same name exists, it will return that folder's details; when creating a file (in create_file) and the file already exists, it now returns False (rather than erroring out) --- pythonmarketo/client.py | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/pythonmarketo/client.py b/pythonmarketo/client.py index 53d7789..797f68a 100644 --- a/pythonmarketo/client.py +++ b/pythonmarketo/client.py @@ -55,6 +55,7 @@ def execute(self, method, *args, **kargs): '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, @@ -343,7 +344,7 @@ def run_request_campaign(self, campaignID, leadsID, values): if not result['success'] : raise MarketoException(result['errors'][0]) return result['success'] - def merge_leads(self, winning_ld, loosing_leads_list,mergeInCRM = False): + def merge_leads(self, winning_ld, loosing_leads_list, mergeInCRM=False): leadstr = str(loosing_leads_list).strip('[]') leadsing = '&leadIds=' + leadstr self.authenticate() @@ -480,6 +481,31 @@ def create_folder(self, name, parent, description=None): 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() @@ -515,7 +541,11 @@ def create_file(self, name, file, folder, description=None, insertOnly=None): 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]) + if not result['success']: + if result['errors'][0]['code'] == "709" and insertOnly == True: + return False + else: + raise MarketoException(result['errors'][0]) return result From 6ae2297cd99cecb38e4df09c29cd1792355f1b3d Mon Sep 17 00:00:00 2001 From: jepcastelein Date: Tue, 22 Dec 2015 15:24:00 -0800 Subject: [PATCH 28/28] reverted error handling within create_file. --- pythonmarketo/client.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pythonmarketo/client.py b/pythonmarketo/client.py index 797f68a..cc64635 100644 --- a/pythonmarketo/client.py +++ b/pythonmarketo/client.py @@ -541,11 +541,7 @@ def create_file(self, name, file, folder, description=None, insertOnly=None): 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']: - if result['errors'][0]['code'] == "709" and insertOnly == True: - return False - else: - raise MarketoException(result['errors'][0]) + if not result['success'] : raise MarketoException(result['errors'][0]) return result