CRUD Metadata API Calls

You can use simple_salesforce to make CRUD (Create, Read, Update and Delete) API calls to the metadata API.

First, get the metadata API object:

mdapi = sf.mdapi

To create a new metadata component in Salesforce, define the metadata component using the metadata types reference given in Salesforce’s metadata API documentation

custom_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:

mdapi.CustomObject.create(custom_object)

Similarly, any metadata type can be created in Salesforce using the syntax 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 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:

describe_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 a DescribeValueTypeResult object.

Just 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 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.

mdapi.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 of FileProperties objects.

File Based Metadata API Calls

You 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:

sfdx force:source:convert -r src/folder_name -d dx

Then navigate into the converted folder and zip it up:

zip -r -X package.zip *

Then you can use this to deploy that zipfile:

result = sf.mdapi.deploy("path/to/zip", sandbox=False)
asyncId = result.get('asyncId')
state = result.get('state')

Both deploy and check_deploy_status 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 at https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/meta_deploy.htm

You can check on the progress of the deploy which returns a dictionary with status, state_detail, deployment_detail, unit_test_detail:

sf.mdapi.check_deploy_status(asyncId)

Example of a use-case:

from simple_salesforce import Salesforce

deployment_finished = False
successful = False

sf = Salesforce(session_id="id", instance="instance")
sf.mdapi.deploy("path/to/zip", sandbox=False)

while not deployment_finished:
    result = sf.mdapi.check_deploy_status(asyncId)
    if result.get('status') in ["Succeeded", "Completed", "Error", "Failed", None]:
        deployment_finished = True
    if result.get('status') in ["Succeeded", "Completed"]:
        successful = True

if successful:
    print("✅")
else:
    print("🥔")