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