Updates and deletes
Partial updates that send only what changed, diff-driven writes and deleting records by ID.
Updating a record
An update is a PATCH on the record's path, and it is always partial - the request carries only the fields to change and everything else stays as it was. There is no read-modify-write cycle and no risk of overwriting fields you never meant to touch:
# -*- coding: utf-8 -*-
# Zato
from zato.server.service import Service
class CloseOpportunity(Service):
name = 'crm.close-opportunity'
input = 'opportunity_id'
def handle(self):
opportunity_id = self.request.input.opportunity_id
conn = self.salesforce['My Salesforce Connection']
# Only the fields that change go into the request ..
changes = {
'StageName': 'Closed Won',
}
# .. and everything else stays untouched.
_ = conn.patch(f'/sobjects/Opportunity/{opportunity_id}', changes)
A successful update answers with HTTP 204 and no content - conn.patch returns an empty dict. Anything else is an error, delivered in the error array format.
Writing only what drifted
In a sync that runs on a schedule, most records have not changed since the previous run. Updating them all anyway burns API requests, fires the org's automation - triggers, flows, field history - for records where nothing happened, and makes every run look like a mass modification in the audit trail.
The pattern that avoids this is compare-then-write: build the desired values, compare them field by field with what Salesforce currently holds, and send a PATCH only for the fields that differ:
# The values this record should have, keyed by Salesforce field name
desired = {
'StageName': stage_name,
'Amount': amount,
'Partner_Code__c': partner_code,
}
# Compare with the record as Salesforce returned it from a query ..
changes = {}
for field_name, new_value in desired.items():
if record[field_name] != new_value:
changes[field_name] = new_value
# .. and write only if anything actually drifted.
if changes:
opportunity_id = record['Id']
changed_fields = sorted(changes)
_ = conn.patch(f'/sobjects/Opportunity/{opportunity_id}', changes)
self.logger.info('Updated %s, fields: %s', opportunity_id, changed_fields)
Over a whole run, logging which fields changed per record gives you an exact, reviewable account of what the sync did - and a run where nothing drifted makes zero write calls. The CRM sync scenario uses this pattern throughout.
Deleting a record
A delete is a DELETE on the record's path:
# -*- coding: utf-8 -*-
# Zato
from zato.server.service import Service
class DeleteCampaign(Service):
name = 'crm.delete-campaign'
input = 'campaign_id'
def handle(self):
campaign_id = self.request.input.campaign_id
conn = self.salesforce['My Salesforce Connection']
# Salesforce moves the record to the recycle bin and answers with 204 No Content.
_ = conn.delete(f'/sobjects/Campaign/{campaign_id}')
Deleted records go to the org's recycle bin, where they stay for 15 days before permanent removal, so an accidental delete is recoverable by an administrator.
Deleting a record that does not exist - or was already deleted - fails with a NOT_FOUND error. In integrations where deletes may be replayed, treat that error as success: the record being gone is exactly the state the caller asked for.
Prefer marking over deleting
In long-lived integrations, an actual delete is rarely what the business wants - a cancelled deal or a lapsed partner usually should remain visible in reports and history. The convention that keeps everyone happy is a status field: instead of conn.delete, PATCH a field such as Status__c to a value like Inactive or Do not sync, and filter on it in your SOQL queries. Reserve real deletes for data that was wrong in the first place, such as duplicates from an import.