Package: async-simple-salesforce
Async Simple SalesforceThis is anasync forkof the librarysimple-salesforce. It is consideredalphasoftware.This fork is available on PyPI and can be installed like this:$pipinstall"async-simple-salesforce==1.12.4a1"VersionsVersioning for this library tracks the upstream version; i.e., when a new version of upstream has been published, it will be integrated into this codebase and released under thesameversion identifier, with one notable difference: we addalphaorbetaidentifiers to make it more obvious that this fork has not been around as long as upstream and that it has also not seen the same wide-scale usage that upstream has.For example, when changes from upstream version1.12.4have been integrated into this library, a new version of this library will be published as1.12.4a1. Further changes under the same version will increment the alpha version identifier.How to UseThis library attempts to offer thesame APIassimple-salesforceinside anaiosubpackage.For instance, here's how to create and use an async Salesforce client:importasyncioimportdatetimefromsimple_salesforce.aioimportbuild_async_salesforce_client,AsyncSalesforceasyncdefcreate_client(username,consumer_key,private_key)->AsyncSalesforce:# build_async_salesforce_client accepts all args of simple-salesforce Loginreturnawaitbuild_async_salesforce_client(username=username,consumer_key=consumer_key,privatekey_file=private_key,request_timeout_seconds=60*2,)asyncdefupdate_contact(sf_client):end=datetime.datetime.now(pytz.UTC)# we need to use UTC as salesforce API requires thisawaitsf_client.Contact.updated(end-datetime.timedelta(days=10),end)asyncdefrun_query(sf_client,opportunity_name:str):query=(f"SELECT Id,StageName from Opportunity where "f"Name='{opportunity_name}'")returnawaitsf_client.query_all(search)You can typically callanymethod available on the synchronousSalesforceobject as an async method with anawaitadded.See upstream docs continued below.Simple Salesforce.. image::https://api.travis-ci.org/simple-salesforce/simple-salesforce.svg?branch=master:target:https://travis-ci.org/simple-salesforce/simple-salesforce.. image::https://readthedocs.org/projects/simple-salesforce/badge/?version=latest:target:http://simple-salesforce.readthedocs.io/en/latest/?badge=latest:alt: Documentation StatusSimple Salesforce is a basic Salesforce.com REST API client built for Python 3.6, 3.7 3.8, 3.9, 3.10, and 3.11. The goal is to provide a very low-level interface to the REST Resource and APEX API, returning a dictionary of the API JSON response.=============You can find out more regarding the format of the results in theOfficial Salesforce.com REST API Documentation_.. _Official Salesforce.com REST API Documentation:http://www.salesforce.com/us/developer/docs/api_rest/index.htmExamplesThere are two ways to gain access to SalesforceThe first is to simply pass the domain of your Salesforce instance and an access token straight toSalesforce()For example:.. code-block:: pythonfrom simple_salesforce import Salesforce
sf = Salesforce(instance='na1.salesforce.com', session_id='')If you have the full URL of your instance (perhaps including the schema, as is included in the OAuth2 request process), you can pass that in instead usinginstance_url:.. code-block:: pythonfrom simple_salesforce import Salesforce
sf = Salesforce(instance_url='https://na1.salesforce.com', session_id='')There are also four means of authentication, one that uses username, password and security token; one that uses IP filtering, username, password  and organizationId, one that uses a private key to sign a JWT, and one for connected apps that uses username, password, consumer key, and consumer secret;To login using the security token method, simply include the Salesforce method and pass in your Salesforce username, password and token (this is usually provided when you change your password):.. code-block:: pythonfrom simple_salesforce import Salesforce
sf = Salesforce(username='myemail@example.com', password='password', security_token='token')To login using IP-whitelist Organization ID method, simply use your Salesforce username, password and organizationId:.. code-block:: pythonfrom simple_salesforce import Salesforce
sf = Salesforce(password='password', username='myemail@example.com', organizationId='OrgId')To login using the JWT method, use your Salesforce username, consumer key from your app, and private key (How To <https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_auth_jwt_flow.htm#sfdx_dev_auth_jwt_flow>_):.. code-block:: pythonfrom simple_salesforce import Salesforce
sf = Salesforce(username='myemail@example.com', consumer_key='XYZ', privatekey_file='filename.key')To login using a connected app, simply include the Salesforce method and pass in your Salesforce username, password, consumer_key and consumer_secret (the consumer key and consumer secret are provided when you setup your connected app):.. code-block:: pythonfrom simple_salesforce import Salesforce
sf = Salesforce(username='myemail@example.com', password='password', consumer_key='consumer_key', consumer_secret='consumer_secret')If you'd like to enter a sandbox, simply adddomain='test'to yourSalesforce()call.For example:.. code-block:: pythonfrom simple_salesforce import Salesforce
sf = Salesforce(username='myemail@example.com.sandbox', password='password', security_token='token', domain='test')Note that specifying if you want to use a domain is only necessary if you are using the built-in username/password/security token authentication and is used exclusively during the authentication step.If you'd like to keep track where your API calls are coming from, simply addclient_id='My App'to yourSalesforce()call... code-block:: pythonfrom simple_salesforce import Salesforce
sf = Salesforce(username='myemail@example.com.sandbox', password='password', security_token='token', client_id='My App', domain='test')If you view the API calls in your Salesforce instance by Client Id it will be prefixed withsimple-salesforce/, for examplesimple-salesforce/My App.When instantiating aSalesforceobject, it's also possible to include an
instance ofrequests.Session. This is to allow for specialized
session handling not otherwise exposed by simple_salesforce.For example:.. code-block:: pythonfrom simple_salesforce import Salesforce
import requestssession = requests.Session()manipulate the session instance (optional)sf = Salesforce(
username='user@example.com', password='password', organizationId='OrgId',
session=session)Record ManagementTo create a new 'Contact' in Salesforce:.. code-block:: pythonsf.Contact.create({'LastName':'Smith','Email':'example@example.com'})This will return a dictionary such as{u'errors': [], u'id': u'003e0000003GuNXAA0', u'success': True}To get a dictionary with all the information regarding that record, use:.. code-block:: pythoncontact = sf.Contact.get('003e0000003GuNXAA0')To get a dictionary with all the information regarding that record, using acustomfield that was defined as External ID:.. code-block:: pythoncontact = sf.Contact.get_by_custom_id('My_Custom_ID__c', '22')To change that contact's last name from 'Smith' to 'Jones' and add a first name of 'John' use:.. code-block:: pythonsf.Contact.update('003e0000003GuNXAA0',{'LastName': 'Jones', 'FirstName': 'John'})To delete the contact:.. code-block:: pythonsf.Contact.delete('003e0000003GuNXAA0')To retrieve a list of Contact records deleted over the past 10 days (datetimes are required to be in UTC):.. code-block:: pythonimport pytz
import datetime
end = datetime.datetime.now(pytz.UTC)  # we need to use UTC as salesforce API requires this!
sf.Contact.deleted(end - datetime.timedelta(days=10), end)To retrieve a list of Contact records updated over the past 10 days (datetimes are required to be in UTC):.. code-block:: pythonimport pytz
import datetime
end = datetime.datetime.now(pytz.UTC) # we need to use UTC as salesforce API requires this
sf.Contact.updated(end - datetime.timedelta(days=10), end)Note that Update, Delete and Upsert actions return the associatedSalesforce HTTP Status Code_Use the same format to create any record, including 'Account', 'Opportunity', and 'Lead'.
Make sure to have all the required fields for any entry. TheSalesforce API_ has all objects found under 'Reference -> Standard Objects' and the required fields can be found there... _Salesforce HTTP Status Code:http://www.salesforce.com/us/developer/docs/api_rest/Content/errorcodes.htm.. _Salesforce API:https://www.salesforce.com/developer/docs/api/QueriesIt's also possible to write select queries in Salesforce Object Query Language (SOQL) and search queries in Salesforce Object Search Language (SOSL).All SOQL queries are supported and parent/child relationships can be queried using the standard format (Parent__r.FieldName). SOQL queries are done via:.. code-block:: pythonsf.query("SELECT Id, Email, ParentAccount.Name FROM Contact WHERE LastName = 'Jones'")If, due to an especially large result, Salesforce adds anextRecordsUrlto your query result, such as"nextRecordsUrl" : "/services/data/v26.0/query/01gD0000002HU6KIAW-2000", you can pull the additional results with either the ID or the full URL (if using the full URL, you must pass 'True' as your second argument).. code-block:: pythonsf.query_more("01gD0000002HU6KIAW-2000")
sf.query_more("/services/data/v26.0/query/01gD0000002HU6KIAW-2000", True)As a convenience, to retrieve all of the results in a single local method call use.. code-block:: pythonsf.query_all("SELECT Id, Email FROM Contact WHERE LastName = 'Jones'")Whilequery_allmaterializes the whole result into a Python list,query_all_iterreturns an iterator, which allows you to lazily process each element separately.. code-block:: pythondata = sf.query_all_iter("SELECT Id, Email FROM Contact WHERE LastName = 'Jones'")
for row in data:
  process(row)Values used in SOQL queries can be quoted and escaped usingformat_soql:.. code-block:: pythonsf.query(format_soql("SELECT Id, Email FROM Contact WHERE LastName = {}", "Jones"))
sf.query(format_soql("SELECT Id, Email FROM Contact WHERE LastName = {last_name}", last_name="Jones"))
sf.query(format_soql("SELECT Id, Email FROM Contact WHERE LastName IN {names}", names=["Smith", "Jones"]))To skip quoting and escaping for one value while still using the format string, use:literal:.. code-block:: pythonsf.query(format_soql("SELECT Id, Email FROM Contact WHERE Income > {:literal}", "USD100"))To escape a substring used in a LIKE expression while being able to use % around it, use:like:.. code-block:: pythonsf.query(format_soql("SELECT Id, Email FROM Contact WHERE Name LIKE '{:like}%'", "Jones"))SOSL queries are done via:.. code-block:: pythonsf.search("FIND {Jones}")There is also 'Quick Search', which inserts your query inside the {} in the SOSL syntax. Be careful, there is no escaping!.. code-block:: pythonsf.quick_search("Jones")Search and Quick Search returnNoneif there are no records, otherwise they return a dictionary of search results.More details about syntax is available on theSalesforce Query Language Documentation Developer Website_.. _Salesforce Query Language Documentation Developer Website:http://www.salesforce.com/us/developer/docs/soql_sosl/index.htmCRUD Metadata API CallsYou can use simple_salesforce to make CRUD (Create, Read, Update and Delete) API calls to the metadata API.First, get the metadata API object:.. code-block:: pythonmdapi = sf.mdapiTo create a new metadata component in Salesforce, define the metadata component using the metadata types reference
given in Salesforce'smetadata API documentation_.. _metadata API documentation:https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/meta_types_list.htm.. code-block:: pythoncustom_object = mdapi.CustomObject(
    fullName = "CustomObject__c",
    label = "Custom Object",
    pluralLabel = "Custom Objects",
    nameField = mdapi.CustomField(
        label = "Name",
        type = mdapi.FieldType("Text")
    ),
    deploymentStatus = mdapi.DeploymentStatus("Deployed"),
    sharingModel = mdapi.SharingModel("Read")
)This custom object metadata can then be created in Salesforce using the createMetadata API call:.. code-block:: pythonmdapi.CustomObject.create(custom_object)Similarly, any metadata type can be created in Salesforce using the syntax :code:mdapi.MetadataType.create(). It is
also possible to create more than one metadata component in Salesforce with a single createMetadata API call. This can
be done by passing a list of metadata definitions to :code:mdapi.MetadataType.create(). Up to 10 metadata components
of the same metadata type can be created in a single API call (This limit is 200 in the case of CustomMetadata and
CustomApplication).readMetadata, updateMetadata, upsertMetadata, deleteMetadata, renameMetadata and describeValueType API calls can be
performed with similar syntax to createMetadata:.. code-block:: pythondescribe_response = mdapi.CustomObject.describe()
custom_object = mdapi.CustomObject.read("CustomObject__c")
custom_object.sharingModel = mdapi.SharingModel("ReadWrite")
mdapi.CustomObject.update(custom_object)
mdapi.CustomObject.rename("CustomObject__c", "CustomObject2__c")
mdapi.CustomObject.delete("CustomObject2__c")The describe method returns aDescribeValueTypeResult_ object... _DescribeValueTypeResult:https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/meta_describeValueTypeResult.htmJust like with the createMetadata API call, multiple metadata components can be dealt with in a single API call for all
CRUD operations by passing a list to their respective methods. In the case of readMetadata, if multiple components are
read in a single API call, a list will be returned.simple_salesforce validates the response received from Salesforce. Create, update, upsert, delete and rename
methods return :code:None, but raise an Exception with error message (from Salesforce) if Salesforce does not return
success. So, error handling can be done by catching the python exception.simple_salesforce also supports describeMetadata and listMetadata API calls as follows. describeMetadata uses the API
version set for the Salesforce object and will return a DescribeMetadataResult object... code-block:: pythonmdapi.describe()
query = mdapi.ListMetadataQuery(type='CustomObject')
query_response = mdapi.list_metadata(query)Up to 3 ListMetadataQuery objects can be submitted in one list_metadata API call by passing a list. The list_metadata
method returns a list ofFileProperties_ objects... _FileProperties:https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/meta_retrieveresult.htm#retrieveresult_filepropertiesFile Based Metadata API CallsYou can use simple_salesforce to make file-based calls to the Metadata API, to deploy a zip file to an org.First, convert and zip the file with:.. code-block::sfdx force:source:convert -r src/folder_name -d dxThen navigate into the converted folder and zip it up:.. code-block::zip -r -X package.zip *Then you can use this to deploy that zipfile:.. code-block:: pythonresult = sf.deploy("path/to/zip", sandbox=False, **kwargs)
asyncId = result.get('asyncId')
state = result.get('state')Both deploy and checkDeployStatus take keyword arguments. The single package argument is not currently available to be set for deployments. More details on the deploy options can be found athttps://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/meta_deploy.htmYou can check on the progress of the deploy which returns a dictionary with status, state_detail, deployment_detail, unit_test_detail:.. code-block:: pythonsf.checkDeployStatus(asyncId)Example of a use-case:.. code-block:: pythonfrom simple_salesforce import Salesforcedeployment_finished = False
successful = Falsesf = Salesforce(session_id="id", instance="instance")
sf.deploy("path/to/zip", sandbox=False ,**kwargs)while not deployment_finished:
result = sf.checkDeployStatus(asyncId)
if result.get('status') in ["Succeeded", "Completed", "Error", "Failed", None]:
deployment_finished = True
if result.get('status') in ["Succeeded", "Completed"]:
successful = Trueif successful:
print("✅")
else:
print("🥔")Other OptionsTo insert or update (upsert) a record using an external ID, use:.. code-block:: pythonsf.Contact.upsert('customExtIdField__c/11999',{'LastName': 'Smith','Email': 'smith@example.com'})To format an external ID that could contain non-URL-safe characters, use:.. code-block:: pythonexternal_id = format_external_id('customExtIdField__c', 'this/that & the other')To retrieve basic metadata use:.. code-block:: pythonsf.Contact.metadata()To retrieve a description of the object, use:.. code-block:: pythonsf.Contact.describe()To retrieve a description of the record layout of an object by its record layout unique id, use:.. code-block:: pythonsf.Contact.describe_layout('39wmxcw9r23r492')To retrieve a list of top level description of instance metadata, user:.. code-block:: pythonsf.describe()

for x in sf.describe()["sobjects"]:
  print x["label"]Using BulkYou can use this library to access Bulk API functions. The data element can be a list of records of any size and by default batch sizes are 10,000 records and run in parallel concurrency mode. To set the batch size for insert, upsert, delete, hard_delete, and update use the batch_size argument. To set the concurrency mode for the salesforce job the use_serial argument can be set to use_serial=True.Create new records:.. code-block:: pythondata = [
      {'LastName':'Smith','Email':'example@example.com'},
      {'LastName':'Jones','Email':'test@test.com'}
    ]

sf.bulk.Contact.insert(data,batch_size=10000,use_serial=True)Update existing records:.. code-block:: pythondata = [
      {'Id': '0000000000AAAAA', 'Email': 'examplenew@example.com'},
      {'Id': '0000000000BBBBB', 'Email': 'testnew@test.com'}
    ]

sf.bulk.Contact.update(data,batch_size=10000,use_serial=True)Update existing records and update lookup fields from an external id field:.. code-block:: pythondata = [
      {'Id': '0000000000AAAAA', 'Custom_Object__r': {'Email__c':'examplenew@example.com'}},
      {'Id': '0000000000BBBBB', 'Custom_Object__r': {'Email__c': 'testnew@test.com'}}
    ]

sf.bulk.Contact.update(data,batch_size=10000,use_serial=True)Upsert records:.. code-block:: pythondata = [
      {'Id': '0000000000AAAAA', 'Email': 'examplenew2@example.com'},
      {'Email': 'foo@foo.com'}
    ]

sf.bulk.Contact.upsert(data, 'Id', batch_size=10000, use_serial=True)Query records:.. code-block:: pythonquery = 'SELECT Id, Name FROM Account LIMIT 10'

sf.bulk.Account.query(query)To retrieve large amounts of data, use.. code-block:: pythonquery = 'SELECT Id, Name FROM Account'

# generator on the results page
fetch_results = sf.bulk.Account.query(query, lazy_operation=True)

# the generator provides the list of results for every call to next()
all_results = []
for list_results in fetch_results:
  all_results.extend(list_results)Query all records:QueryAll will return records that have been deleted because of a merge or delete. QueryAll will also return information about archived Task and Event records... code-block:: pythonquery = 'SELECT Id, Name FROM Account LIMIT 10'

sf.bulk.Account.query_all(query)To retrieve large amounts of data, use.. code-block:: pythonquery = 'SELECT Id, Name FROM Account'

# generator on the results page
fetch_results = sf.bulk.Account.query_all(query, lazy_operation=True)

# the generator provides the list of results for every call to next()
all_results = []
for list_results in fetch_results:
  all_results.extend(list_results)Delete records (soft deletion):.. code-block:: pythondata = [{'Id': '0000000000AAAAA'}]

sf.bulk.Contact.delete(data,batch_size=10000,use_serial=True)Hard deletion:.. code-block:: pythondata = [{'Id': '0000000000BBBBB'}]

sf.bulk.Contact.hard_delete(data,batch_size=10000,use_serial=True)Using ApexYou can also use this library to call custom Apex methods:.. code-block:: pythonpayload = {
  "activity": [
    {"user": "12345", "action": "update page", "time": "2014-04-21T13:00:15Z"}
  ]
}
result = sf.apexecute('User/Activity', method='POST', data=payload)This would call the endpointhttps://<instance>.salesforce.com/services/apexrest/User/Activitywithdata=as
the body content encoded withjson.dumpsYou can read more about Apex on theForce.com Apex Code Developer's Guide_.. _Force.com Apex Code Developer's Guide:https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_dev_guide.htmAdditional FeaturesThere are a few helper classes that are used internally and available to you.Included in them areSalesforceLogin, which takes in a username, password, security token, optional version and optional domain and returns a tuple of(session_id, sf_instance)wheresession_idis the session ID to use for authentication to Salesforce andsf_instanceis the domain of the instance of Salesforce to use for the session.For example, to use SalesforceLogin for a sandbox account you'd use:.. code-block:: pythonfrom simple_salesforce import SalesforceLogin
session_id, instance = SalesforceLogin(
    username='myemail@example.com.sandbox',
    password='password',
    security_token='token',
    domain='test')Simply leave off the final domain if you do not wish to use a sandbox.Also exposed is theSFTypeclass, which is used internally by the__getattr__()method in theSalesforce()class and represents a specific SObject type.SFTyperequiresobject_name(i.e.Contact),session_id(an authentication ID),sf_instance(hostname of your Salesforce instance), and an optionalsf_versionTo add a Contact using the default version of the API you'd use:.. code-block:: pythonfrom simple_salesforce import SFType
contact = SFType('Contact','sesssionid','na1.salesforce.com')
contact.create({'LastName':'Smith','Email':'example@example.com'})To use a proxy server between your client and the SalesForce endpoint, use the proxies argument when creating SalesForce object.
The proxy argument is the same as what requests uses, a map of scheme to proxy URL:.. code-block:: pythonproxies = {
  "http": "http://10.10.1.10:3128",
  "https": "http://10.10.1.10:1080",
}
SalesForce(instance='na1.salesforce.com', session_id='', proxies=proxies)All results are returned as JSON converted OrderedDict to preserve order of keys from REST responses.Helpful Datetime ResourcesA list of helpful resources when working with datetime/dates from SalesforceConvert SFDC Datetime to Datetime or Date object
.. code-block:: pythonimport datetime
# Formatting to SFDC datetime
formatted_datetime =  datetime.datetime.strptime(x, "%Y-%m-%dT%H:%M:%S.%f%z")

#Formatting to SFDC date
formatted_date = datetime.strptime(x, "%Y-%m-%d")Helpful Pandas ResourcesA list of helpful resources when working with Pandas and simple-salesforceGenerate list for SFDC Query "IN" operations from a Pandas Dataframe.. code-block:: pythonimport pandas as pddf = pd.DataFrame([{'Id':1},{'Id':2},{'Id':3}])
def dataframe_to_sfdc_list(df,column):
df_list = df[column].unique()
df_list = [str(x) for x in df_list]
df_list = ','.join("'"+item+"'" for item in df_list)
return df_listsf.query(format_soql("SELECT Id, Email FROM Contact WHERE Id IN ({})", dataframe_to_sfdc_list(df,column)))Generate Pandas Dataframe from SFDC API Query (ex.query,query_all).. code-block:: pythonimport pandas as pdsf.query("SELECT Id, Email FROM Contact")df = pd.DataFrame(data['records']).drop(['attributes'],axis=1)Generate Pandas Dataframe from SFDC API Query (ex.query,query_all) and append related fields from query to data frame.. code-block:: pythonimport pandas as pddef sf_api_query(data):
df = pd.DataFrame(data['records']).drop('attributes', axis=1)
listColumns = list(df.columns)
for col in listColumns:
if any (isinstance (df[col].values[i], dict) for i in range(0, len(df[col].values))):
df = pd.concat([df.drop(columns=[col]),df[col].apply(pd.Series).drop('attributes',axis=1).add_prefix(col+'.')],axis=1)
new_columns = np.setdiff1d(df.columns, listColumns)
for i in new_columns:
listColumns.append(i)
return dfdf = sf_api_query(sf.query("SELECT Id, Email,ParentAccount.Name FROM Contact"))Generate Pandas Dataframe from SFDC Bulk API Query (ex.bulk.Account.query).. code-block:: pythonimport pandas as pdsf.bulk.Account.query("SELECT Id, Email FROM Contact")
df = pd.DataFrame.from_dict(data,orient='columns').drop('attributes',axis=1)YouTube TutorialHere is a helpfulYouTube tutorial_  which shows how you can manage records in bulk using a jupyter notebook, simple-salesforce and pandas.This can be a effective way to manage records, and perform simple operations like reassigning accounts, deleting test records, inserting new records, etc..... _YouTube tutorial:https://youtu.be/nPQFUgsk6Oo?t=282Authors & LicenseThis package is released under an open source Apache 2.0 license. Simple-Salesforce was originally written byNick Catalano_ but most newer features and bugfixes come fromcommunity contributors. Pull requests submitted to theGitHub Repoare highly encouraged!Authentication mechanisms were adapted from Dave Wingate'sRestForce_ and licensed under a MIT licenseThe latest build status can be found atTravis CI_.. _Nick Catalano:https://github.com/nickcatal.. _community contributors:https://github.com/simple-salesforce/simple-salesforce/graphs/contributors.. _RestForce:http://pypi.python.org/pypi/RestForce/.. _GitHub Repo:https://github.com/simple-salesforce/simple-salesforce.. _Travis CI:https://travis-ci.com/simple-salesforce/simple-salesforce