An absurdly simple package for making Salesforce Rest API requests
Project description
SalesforcePy
An absurdly simple package for making Salesforce Rest API calls.
by Aaron Caffrey, Colin Cheevers, Jose Garcia, Tania Prince
Salesforce.com
Table of Contents
- Introduction
- Contributors
- Requirements
- Install
- Create a client and log in
- Query
- Query More
- Insert SObject
- Update SObject
- Delete SObject
- Query SObject Row
- Describe SObject
- Describe Global
- Insert File
- Search
- Execute Anonymous
- Approval Process
- Chatter
- Wave
- Bulk API 2.0
- Logout
- Contributing
- FAQ
Introduction
The reason this package exists is to produce:
- A Salesforce client that is reusable, minimalistic, and pythonic
- Interfaces that are closely knit to the Salesforce Rest API service specification
- Gradual support for the Salesforce API extended family (ie. Chatter, Analytics, Wave, Tooling, Bulk, Metadata, etc.)
Contributors
Thanks goes to the people who have contributed code to this module, see the GitHub Contributors page.
Requirements
- Python 2 or 3
Install
From git
pip install git+ssh://git@github.com/forcedotcom/SalesforcePy.git
From local source
- Download and extract, or clone this repo
cd
into the/SalesforcePy
directory- Run the following:
pip install .
Create a client and log in
Getting started is a three-step process:
- Import
SalesforcePy
- Create a client
- Perform a login request
import SalesforcePy as sfdc
# Create an instance of a Salesforce client, replacing the credentials below with valid ones.
client = sfdc.client(
username="jsoap@universalcontainers.com",
password="p@ssword1",
client_id="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
client_secret="123456789123456789",
login_url="test.salesforce.com",
version = "38.0", #optional parameter, defaults to the latest version for your instance
timeout = "30", # optional, defines a connect/read timeout value, if not specified requests can hang for minutes or more.
)
# Log in
login_results = client.login()
In the example above, login_results[0]
will be a dict with the response from the Salesforce OAuth resource. The only supported flow at present is username-password. For more on the response, see: https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/intro_understanding_username_password_oauth_flow.htm.
You can also use the context manager which handles login()
and logout()
automatically.
client_args = {'username' :'jsoap@universalcontainers.com',
'password' :'p@ssword1',
'client_id': 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
'client_secret' : '123456789123456789'
}
with sfdc.client(**client_args) as client:
search_result = client.search('FIND {"test"} RETURNING Case(Id)')
Query
Once the login call has been performed successfully, the client will maintain the session leaving you free to make API calls. This example demonstrates how to perform a query.
query_results = client.query('SELECT Id, Name FROM Account LIMIT 1')
In the example above query_results[0]
will be a dict with the response as documented here: https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_query.htm.
Query More
While the client query()
method can be useful for making requests when you're expecting a small amount of data, a single query result won't give you all records if the total size of records exceeds the standard batch size (2000).
In such scenarios, you may wish to use query_more()
. As the example shows, you simply provide the query string in the same way you did previously with query()
:
query_results = client.query_more('SELECT Id, Name FROM Account')
In the example above query_results[0]
will be a list of dicts, each of which is a query result (batch). The behaviour of query_more
is to consume "nextRecordsUrl"
of each query result recursively until it runs out. For more on this topic, check out https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_query.htm under the heading "Retrieving the Remaining SOQL Query Results".
Insert SObjects
From the client, the sobjects
class can be used to perform DML statements and file i/o with Salesforce objects. This example shows how to insert.
create_result = client.sobjects(object_type='Account').insert({"Name" : "SalesforcePy"})
In the example above create_result[0]
will be a dict with the response as documented here: https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_sobject_create.htm.
Update SObjects
Update works similarly to insert, with the main difference being that id
is a required kwarg in sobjects
so it is clear which record is to be updated.
update_result = client.sobjects(id='0010Y0000055YG7QAM',object_type='Account').update({"Name" : "SalesforcePy 2"})
In the example above update_result[0]
will be None
. This is because the HTTP method used under the hood is PATCH
, for which the expected success code is 204
. The success code can be found in update_result[1].status
. For more, read: https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_update_fields.htm.
Delete SObjects
delete_result = client.sobjects(id='0010Y0000055YG7QAM',object_type='Account').delete()
In the example above delete_result[0]
will be None
. This is because the HTTP method used under the hood is DELETE
, for which the expected success code is 204
. The success code can be found in delete_result[1].status
. For more, read: https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_delete_record.htm.
Query SObject Row
If you know the ID of a record, you can easily retrieve the entire row using this method. This may be preferable to the query
call documented further up if you wish to get all fields without specifying them.
query_result = client.sobjects( object_type="Account", id="0010Y0000056ljcQAA" ).query()
Describe SObject
The describe method retrieves the individual metadata at all levels for the specified SObject
describe_result = client.sobjects(object_type='Account').describe()
In the example above describe_result[0]
will be a dict with the response as documented here: https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_sobject_describe.htm. The If-Modified-Since header cannot be used with this method.
Describe Global
The describe global method lists the available objects and their metadata for the organization’s data. In addition, it provides the organization encoding, as well as the maximum batch size permitted in queries.
describe_global_result = client.sobjects().describe_global()
In the example above describe_global_result[0]
will be a dict with the response as documented here: https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_describeGlobal.htm. The If-Modified-Since header cannot be used with this method.
Insert File
There are some special objects in Salesforce such as Attachment
, Document
, and ContentVersion
which allow storage of files as blobs. The following is an example of how to insert a file.
# Create a file tuple ordered like so: ( filename, body, content_type )
file = ( "SalesforcePy.txt", "Hello world", "text/plain" )
insert_result = client.sobjects(object_type = "Attachment", binary_field="Body").insert({
"Name":"SalesforcePy",
"ParentId":"0010Y0000056ljcQAA",
"Description":"An excellent package"
}, binary=file ) # Pass your file through using the binary kwarg
Search
SOSL search statements can be made like so:
search_result = client.search('FIND {SalesforcePy} RETURNING Account(Id, Name) LIMIT 5')
Execute Anonymous
Anonymous Apex can be executed in a Salesforce organisation like so:
ea_result = client.execute_anonymous('system.debug(\'Hello world.\');')
Approval Process
Approvals can be retrieved, submitted and approved/rejected
ap_result = client.approvals(requestBody)
See documentation for sample request body
Chatter
Create a feed item (chatter post). It returns a 201 status code for a successful request. See Chatter REST api documentation for information on the expected body to create feed items.
# create chatter post
client.chatter.feed_item(body)
# create a comment on a chatter post
client.chatter.feed_comment('feed-elementid', body)
Wave
Retrieve a data set
Retrieve a wave data set using the datataset()
function.
client.wave.dataset("opportunities")
Perform a query
Perform a SAQL query using the wave query()
function.
query = {
"query": """q = load \"0Fb0N000000XuvBSAS/0Fc0N000001M5BMSA0\";\nq = filter q by 'Account.Industry' in
[\"Apparel\", \"Banking\", \"Biotechnology\"];\nq = group q by 'Account.Industry';\nq = foreach q generate
'Account.Industry' as 'Account.Industry', count() as 'count';\nq = order q by 'Account.Industry' asc;\nq = limit q
2000;"""
}
client.wave.query(query)
Bulk API 2.0
As a general rule, supported Bulk API 2.0 calls can be made from client.jobs.ingest
. The samples below cover specific
calls.
Create a job
In this example, we create a job to insert accounts.
job_resource = {"object": "Account", "operation": "insert", "lineEnding": "CRLF"}
client.jobs.ingest.create(job_resource=job_resource)
For more information on the response for this request, see https://developer.salesforce.com/docs/atlas.en-us.api_bulk_v2.meta/api_bulk_v2/create_job.htm.
Upload job data
In this example, we create a job, then upload a csv file using the job ID.
job_resource = {"object": "Account", "operation": "insert", "lineEnding": "CRLF"}
create_result = client.jobs.ingest.create(job_resource=job_resource)
with open("/path/to/accounts.csv") as f:
csv_file = f.read()
job_id = create_result[0].get("id")
batches_result = client.jobs.ingest.batches(job_id=job_id, csv_file=csv_file)
For more information on the response for this request, see https://developer.salesforce.com/docs/atlas.en-us.api_bulk_v2.meta/api_bulk_v2/upload_job_data.htm.
Update a job state
In this example, we create a job, upload a csv file using its job ID, then update it with a state of "UploadComplete"
.
job_resource = {"object": "Account", "operation": "insert", "lineEnding": "CRLF"}
create_result = client.jobs.ingest.create(job_resource=job_resource)
job_id = create_result[0].get("Id")
with open("/path/to/accounts.csv") as f:
csv_file = f.read()
batches_result = client.jobs.ingest.batches(job_id=job_id, csv_file=csv_file)
client.jobs.ingest.update(job_id=job_id, state="UploadComplete")
For more information on the response for this request, see https://developer.salesforce.com/docs/atlas.en-us.api_bulk_v2.meta/api_bulk_v2/close_job.htm
Delete a job
In this example, we delete a job based on its ID. Assumed in this example that this value is stored in job_id
.
delete_result = client.jobs.ingest.delete(job_id=job_id)
For more information on the response for this request, see https://developer.salesforce.com/docs/atlas.en-us.api_bulk_v2.meta/api_bulk_v2/delete_job.htm
Get all jobs
In this example, we get a list of all jobs.
get_result = client.jobs.ingest.get()
For more information on the response for this request, see https://developer.salesforce.com/docs/atlas.en-us.api_bulk_v2.meta/api_bulk_v2/get_all_jobs.htm
Get job info
In this example, we get information for a specific job based on its ID. Assumed in this example that this value is
stored in job_id
.
get_result = client.jobs.ingest.get(job_id=job_id)
For more information on the response for this request, see https://developer.salesforce.com/docs/atlas.en-us.api_bulk_v2.meta/api_bulk_v2/get_job_info.htm
Get job successes
In this example, we get a successes CSV for a given job based on its ID. Assumed in this example that this value is
stored in job_id
.
get_result = client.jobs.ingest.get(job_id=job_id, successes=True)
For more information on the response for this request, see https://developer.salesforce.com/docs/atlas.en-us.api_bulk_v2.meta/api_bulk_v2/get_job_successful_results.htm
Get job failures
In this example, we get a failures CSV for a given job based on its ID. Assumed in this example that this value is
stored in job_id
.
get_result = client.jobs.ingest.get(job_id=job_id, failures=True)
For more information on the response for this request, see https://developer.salesforce.com/docs/atlas.en-us.api_bulk_v2.meta/api_bulk_v2/get_job_failed_results.htm
Get job unprocessed rows
In this example, we get an unprocessed rows CSV for a given job based on its ID. Assumed in this example that this value
is stored in job_id
.
get_result = client.jobs.ingest.get(job_id=job_id, unprocessed=True)
For more information on the response for this request, see https://developer.salesforce.com/docs/atlas.en-us.api_bulk_v2.meta/api_bulk_v2/get_job_unprocessed_results.htm
Logout
Expires the session by revoking the access token. It returns a 200 status code for a successful token revocation.
client.logout()
Contributing
What's the git workflow?
- Fork this repo
git clone -b <yourbranch> <yourfork>
- From within the project root directory run
pip install .
- Develop
- Cover your code in
/tests
- Create a pull request to the
forcedotcom:developer
branch
How to format code
We recommend following the Style Guide for Python the best you can: https://www.python.org/dev/peps/pep-0008/.
autopep8
is a great tool for automatic formatting, we encourage its use: https://pypi.python.org/pypi/autopep8.
FAQ
I need to inspect my organisation schema. What's an easy way to do this?
- Log in to Workbench: https://workbench.developerforce.com/login.php
- Go to Info > Standard and Custom Objects
- In the Object dropdown, choose the object you wish to inspect (eg. Case) then click Select
- Expand Fields. You should find what you're looking for here.
I need to test a query. What's an easy way to do this?
- Log in to Workbench: https://workbench.developerforce.com/login.php
- Go to Queries > SOQL Query
- Enter your query or optionally use the form to help you build the query, then click Query
Is it possible to debug requests being made by SalesforcePy
?
Yes. Here's an example of how to do it, and what to expect.
import logging
import SalesforcePy as sfdc
username = "jsoap@universalcontainers.com"
password = "p@ssword1"
client_id = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
client_secret = "123456789123456789"
client = sfdc.client(
username=username,
password=password,
client_id=client_id,
client_secret=client_secret
)
client.debug(level=logging.INFO) # Tell the client to debug at an info level
client.login() # Outputs "POST https://login.salesforce.com/services/oauth2/token" to logs
I need a proxy to talk to Salesforce orgs. Can I specify this in the code?
Yes. Here's an example of how to do it.
import SalesforcePy as sfdc
username = "jsoap@universalcontainers.com"
password = "p@ssword1"
client_id = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
client_secret = "123456789123456789"
client = sfdc.client(
username=username,
password=password,
client_id=client_id,
client_secret=client_secret,
proxies={"https": "localhost:8888/example/"} # `proxies` kwarg takes a dict as required by the `requests` module.
)
Advanced Usage
Using keyword arguments
Some of the parameters that are optionally defined at the client level can be defined at the function level as well. Function level arguments supersede the client arguments.
For example, you may want to define an overall timeout value of "30"
for all requests but specify a higher
value for query calls.
client = sfdc.client(
username=username,
password=password,
client_id=client_id,
client_secret=client_secret,
timeout="30"
)
query_kwarg= {"timeout" : "60"}
client.query("Select Id FROM Account",**query_kwarg)
The following parameters support function level overriding:
proxies
timeout
version
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.