chub-dev 0.1.0 → 0.1.2-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -0
- package/bin/chub-mcp +2 -0
- package/dist/airtable/docs/database/javascript/DOC.md +1437 -0
- package/dist/airtable/docs/database/python/DOC.md +1735 -0
- package/dist/amplitude/docs/analytics/javascript/DOC.md +1282 -0
- package/dist/amplitude/docs/analytics/python/DOC.md +1199 -0
- package/dist/anthropic/docs/claude-api/javascript/DOC.md +503 -0
- package/dist/anthropic/docs/claude-api/python/DOC.md +389 -0
- package/dist/asana/docs/tasks/DOC.md +1396 -0
- package/dist/assemblyai/docs/transcription/DOC.md +1043 -0
- package/dist/atlassian/docs/confluence/javascript/DOC.md +1347 -0
- package/dist/atlassian/docs/confluence/python/DOC.md +1604 -0
- package/dist/auth0/docs/identity/javascript/DOC.md +968 -0
- package/dist/auth0/docs/identity/python/DOC.md +1199 -0
- package/dist/aws/docs/s3/javascript/DOC.md +1773 -0
- package/dist/aws/docs/s3/python/DOC.md +1807 -0
- package/dist/binance/docs/trading/javascript/DOC.md +1315 -0
- package/dist/binance/docs/trading/python/DOC.md +1454 -0
- package/dist/braintree/docs/gateway/javascript/DOC.md +1278 -0
- package/dist/braintree/docs/gateway/python/DOC.md +1179 -0
- package/dist/chromadb/docs/embeddings-db/javascript/DOC.md +1263 -0
- package/dist/chromadb/docs/embeddings-db/python/DOC.md +1707 -0
- package/dist/clerk/docs/auth/javascript/DOC.md +1220 -0
- package/dist/clerk/docs/auth/python/DOC.md +274 -0
- package/dist/cloudflare/docs/workers/javascript/DOC.md +918 -0
- package/dist/cloudflare/docs/workers/python/DOC.md +994 -0
- package/dist/cockroachdb/docs/distributed-db/DOC.md +1500 -0
- package/dist/cohere/docs/llm/DOC.md +1335 -0
- package/dist/datadog/docs/monitoring/javascript/DOC.md +1740 -0
- package/dist/datadog/docs/monitoring/python/DOC.md +1815 -0
- package/dist/deepgram/docs/speech/javascript/DOC.md +885 -0
- package/dist/deepgram/docs/speech/python/DOC.md +685 -0
- package/dist/deepl/docs/translation/javascript/DOC.md +887 -0
- package/dist/deepl/docs/translation/python/DOC.md +944 -0
- package/dist/deepseek/docs/llm/DOC.md +1220 -0
- package/dist/directus/docs/headless-cms/javascript/DOC.md +1128 -0
- package/dist/directus/docs/headless-cms/python/DOC.md +1276 -0
- package/dist/discord/docs/bot/javascript/DOC.md +1090 -0
- package/dist/discord/docs/bot/python/DOC.md +1130 -0
- package/dist/elasticsearch/docs/search/DOC.md +1634 -0
- package/dist/elevenlabs/docs/text-to-speech/javascript/DOC.md +336 -0
- package/dist/elevenlabs/docs/text-to-speech/python/DOC.md +552 -0
- package/dist/firebase/docs/auth/DOC.md +1015 -0
- package/dist/gemini/docs/genai/javascript/DOC.md +691 -0
- package/dist/gemini/docs/genai/python/DOC.md +555 -0
- package/dist/github/docs/octokit/DOC.md +1560 -0
- package/dist/google/docs/bigquery/javascript/DOC.md +1688 -0
- package/dist/google/docs/bigquery/python/DOC.md +1503 -0
- package/dist/hubspot/docs/crm/javascript/DOC.md +1805 -0
- package/dist/hubspot/docs/crm/python/DOC.md +2033 -0
- package/dist/huggingface/docs/transformers/DOC.md +948 -0
- package/dist/intercom/docs/messaging/javascript/DOC.md +1844 -0
- package/dist/intercom/docs/messaging/python/DOC.md +1797 -0
- package/dist/jira/docs/issues/javascript/DOC.md +1420 -0
- package/dist/jira/docs/issues/python/DOC.md +1492 -0
- package/dist/kafka/docs/streaming/javascript/DOC.md +1671 -0
- package/dist/kafka/docs/streaming/python/DOC.md +1464 -0
- package/dist/landingai-ade/docs/api/DOC.md +620 -0
- package/dist/landingai-ade/docs/sdk/python/DOC.md +489 -0
- package/dist/landingai-ade/docs/sdk/typescript/DOC.md +542 -0
- package/dist/landingai-ade/skills/SKILL.md +489 -0
- package/dist/launchdarkly/docs/feature-flags/javascript/DOC.md +1191 -0
- package/dist/launchdarkly/docs/feature-flags/python/DOC.md +1671 -0
- package/dist/linear/docs/tracker/DOC.md +1554 -0
- package/dist/livekit/docs/realtime/javascript/DOC.md +303 -0
- package/dist/livekit/docs/realtime/python/DOC.md +163 -0
- package/dist/mailchimp/docs/marketing/DOC.md +1420 -0
- package/dist/meilisearch/docs/search/DOC.md +1241 -0
- package/dist/microsoft/docs/onedrive/javascript/DOC.md +1421 -0
- package/dist/microsoft/docs/onedrive/python/DOC.md +1549 -0
- package/dist/mongodb/docs/atlas/DOC.md +2041 -0
- package/dist/notion/docs/workspace-api/javascript/DOC.md +1435 -0
- package/dist/notion/docs/workspace-api/python/DOC.md +1400 -0
- package/dist/okta/docs/identity/javascript/DOC.md +1171 -0
- package/dist/okta/docs/identity/python/DOC.md +1401 -0
- package/dist/openai/docs/chat/javascript/DOC.md +407 -0
- package/dist/openai/docs/chat/python/DOC.md +568 -0
- package/dist/paypal/docs/checkout/DOC.md +278 -0
- package/dist/pinecone/docs/sdk/javascript/DOC.md +984 -0
- package/dist/pinecone/docs/sdk/python/DOC.md +1395 -0
- package/dist/plaid/docs/banking/javascript/DOC.md +1163 -0
- package/dist/plaid/docs/banking/python/DOC.md +1203 -0
- package/dist/playwright-community/skills/login-flows/SKILL.md +108 -0
- package/dist/postmark/docs/transactional-email/DOC.md +1168 -0
- package/dist/prisma/docs/orm/javascript/DOC.md +1419 -0
- package/dist/prisma/docs/orm/python/DOC.md +1317 -0
- package/dist/qdrant/docs/vector-search/javascript/DOC.md +1221 -0
- package/dist/qdrant/docs/vector-search/python/DOC.md +1653 -0
- package/dist/rabbitmq/docs/message-queue/javascript/DOC.md +1193 -0
- package/dist/rabbitmq/docs/message-queue/python/DOC.md +1243 -0
- package/dist/razorpay/docs/payments/javascript/DOC.md +1219 -0
- package/dist/razorpay/docs/payments/python/DOC.md +1330 -0
- package/dist/redis/docs/key-value/javascript/DOC.md +1851 -0
- package/dist/redis/docs/key-value/python/DOC.md +2054 -0
- package/dist/registry.json +2817 -0
- package/dist/replicate/docs/model-hosting/DOC.md +1318 -0
- package/dist/resend/docs/email/DOC.md +1271 -0
- package/dist/salesforce/docs/crm/javascript/DOC.md +1241 -0
- package/dist/salesforce/docs/crm/python/DOC.md +1183 -0
- package/dist/search-index.json +1 -0
- package/dist/sendgrid/docs/email-api/javascript/DOC.md +371 -0
- package/dist/sendgrid/docs/email-api/python/DOC.md +656 -0
- package/dist/sentry/docs/error-tracking/javascript/DOC.md +1073 -0
- package/dist/sentry/docs/error-tracking/python/DOC.md +1309 -0
- package/dist/shopify/docs/storefront/DOC.md +457 -0
- package/dist/slack/docs/workspace/javascript/DOC.md +933 -0
- package/dist/slack/docs/workspace/python/DOC.md +271 -0
- package/dist/square/docs/payments/javascript/DOC.md +1855 -0
- package/dist/square/docs/payments/python/DOC.md +1728 -0
- package/dist/stripe/docs/api/DOC.md +1727 -0
- package/dist/stripe/docs/payments/DOC.md +1726 -0
- package/dist/stytch/docs/auth/javascript/DOC.md +1813 -0
- package/dist/stytch/docs/auth/python/DOC.md +1962 -0
- package/dist/supabase/docs/client/DOC.md +1606 -0
- package/dist/twilio/docs/messaging/python/DOC.md +469 -0
- package/dist/twilio/docs/messaging/typescript/DOC.md +946 -0
- package/dist/vercel/docs/platform/DOC.md +1940 -0
- package/dist/weaviate/docs/vector-db/javascript/DOC.md +1268 -0
- package/dist/weaviate/docs/vector-db/python/DOC.md +1388 -0
- package/dist/zendesk/docs/support/javascript/DOC.md +2150 -0
- package/dist/zendesk/docs/support/python/DOC.md +2297 -0
- package/package.json +22 -6
- package/skills/get-api-docs/SKILL.md +84 -0
- package/src/commands/annotate.js +83 -0
- package/src/commands/build.js +12 -1
- package/src/commands/feedback.js +150 -0
- package/src/commands/get.js +83 -42
- package/src/commands/search.js +7 -0
- package/src/index.js +43 -17
- package/src/lib/analytics.js +90 -0
- package/src/lib/annotations.js +57 -0
- package/src/lib/bm25.js +170 -0
- package/src/lib/cache.js +69 -6
- package/src/lib/config.js +8 -3
- package/src/lib/identity.js +99 -0
- package/src/lib/registry.js +103 -20
- package/src/lib/telemetry.js +86 -0
- package/src/mcp/server.js +177 -0
- package/src/mcp/tools.js +251 -0
|
@@ -0,0 +1,1183 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: crm
|
|
3
|
+
description: "Salesforce Python SDK (simple-salesforce) coding guidelines for Salesforce REST API interactions"
|
|
4
|
+
metadata:
|
|
5
|
+
languages: "python"
|
|
6
|
+
versions: "1.12.9"
|
|
7
|
+
updated-on: "2026-03-02"
|
|
8
|
+
source: maintainer
|
|
9
|
+
tags: "salesforce,crm,soql,enterprise,api"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Salesforce Python SDK (simple-salesforce) Coding Guidelines
|
|
13
|
+
|
|
14
|
+
You are a Salesforce API coding expert. Help me with writing code using the Salesforce API calling the official libraries and SDKs.
|
|
15
|
+
|
|
16
|
+
You can find the official SDK documentation and code samples here:
|
|
17
|
+
https://simple-salesforce.readthedocs.io/
|
|
18
|
+
|
|
19
|
+
## Golden Rule: Use the Correct and Current SDK
|
|
20
|
+
|
|
21
|
+
Always use simple-salesforce to interact with Salesforce APIs. Simple-salesforce is the most widely used Python library for Salesforce REST API interactions.
|
|
22
|
+
|
|
23
|
+
- **Library Name:** simple-salesforce
|
|
24
|
+
- **PyPI Package:** `simple-salesforce`
|
|
25
|
+
- **Alternative Library:** `salesforce-api` (less popular)
|
|
26
|
+
- **Legacy Libraries**: Do not use deprecated or unofficial Salesforce Python packages
|
|
27
|
+
|
|
28
|
+
**Installation:**
|
|
29
|
+
|
|
30
|
+
- **Correct:** `pip install simple-salesforce`
|
|
31
|
+
- **With environment variables support:** `pip install simple-salesforce python-dotenv`
|
|
32
|
+
|
|
33
|
+
**APIs and Usage:**
|
|
34
|
+
|
|
35
|
+
- **Correct:** `from simple_salesforce import Salesforce`
|
|
36
|
+
- **Correct:** `sf = Salesforce(username='...', password='...', security_token='...')`
|
|
37
|
+
- **Correct:** `sf.query("SELECT Id, Name FROM Account")`
|
|
38
|
+
- **Correct:** `sf.Account.create({'Name': 'Test'})`
|
|
39
|
+
- **Incorrect:** Using unofficial Salesforce client libraries
|
|
40
|
+
- **Incorrect:** Direct REST API calls without SDK
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
Install simple-salesforce via pip:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install simple-salesforce
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
For environment variable support:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install simple-salesforce python-dotenv
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Environment Variables
|
|
57
|
+
|
|
58
|
+
Set up your Salesforce credentials using environment variables:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
export SALESFORCE_USERNAME="user@example.org"
|
|
62
|
+
export SALESFORCE_PASSWORD="your_password"
|
|
63
|
+
export SALESFORCE_SECURITY_TOKEN="your_security_token"
|
|
64
|
+
export SALESFORCE_DOMAIN="login" # or "test" for sandbox
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Or create a `.env` file:
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
SALESFORCE_USERNAME=user@example.org
|
|
71
|
+
SALESFORCE_PASSWORD=your_password
|
|
72
|
+
SALESFORCE_SECURITY_TOKEN=your_security_token
|
|
73
|
+
SALESFORCE_DOMAIN=login
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Load environment variables in code:
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
import os
|
|
80
|
+
from dotenv import load_dotenv
|
|
81
|
+
|
|
82
|
+
load_dotenv()
|
|
83
|
+
|
|
84
|
+
username = os.getenv('SALESFORCE_USERNAME')
|
|
85
|
+
password = os.getenv('SALESFORCE_PASSWORD')
|
|
86
|
+
security_token = os.getenv('SALESFORCE_SECURITY_TOKEN')
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Initialization
|
|
90
|
+
|
|
91
|
+
Simple-salesforce requires creating a `Salesforce` instance for all API calls.
|
|
92
|
+
|
|
93
|
+
### Basic Connection (Username/Password/Token)
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from simple_salesforce import Salesforce
|
|
97
|
+
|
|
98
|
+
sf = Salesforce(
|
|
99
|
+
username='user@example.org',
|
|
100
|
+
password='your_password',
|
|
101
|
+
security_token='your_security_token'
|
|
102
|
+
)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Connection with Environment Variables
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
import os
|
|
109
|
+
from dotenv import load_dotenv
|
|
110
|
+
from simple_salesforce import Salesforce
|
|
111
|
+
|
|
112
|
+
load_dotenv()
|
|
113
|
+
|
|
114
|
+
sf = Salesforce(
|
|
115
|
+
username=os.getenv('SALESFORCE_USERNAME'),
|
|
116
|
+
password=os.getenv('SALESFORCE_PASSWORD'),
|
|
117
|
+
security_token=os.getenv('SALESFORCE_SECURITY_TOKEN')
|
|
118
|
+
)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Connection to Sandbox
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from simple_salesforce import Salesforce
|
|
125
|
+
|
|
126
|
+
sf = Salesforce(
|
|
127
|
+
username='user@example.org',
|
|
128
|
+
password='your_password',
|
|
129
|
+
security_token='your_security_token',
|
|
130
|
+
domain='test' # Use 'test' for sandbox
|
|
131
|
+
)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Connection with Custom Domain
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from simple_salesforce import Salesforce
|
|
138
|
+
|
|
139
|
+
sf = Salesforce(
|
|
140
|
+
username='user@example.org',
|
|
141
|
+
password='your_password',
|
|
142
|
+
security_token='your_security_token',
|
|
143
|
+
domain='mydomain' # Your My Domain
|
|
144
|
+
)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Authentication
|
|
148
|
+
|
|
149
|
+
### Username/Password/Token Authentication
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
from simple_salesforce import Salesforce
|
|
153
|
+
|
|
154
|
+
sf = Salesforce(
|
|
155
|
+
username='user@example.org',
|
|
156
|
+
password='your_password',
|
|
157
|
+
security_token='your_security_token'
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
print(f"Session ID: {sf.session_id}")
|
|
161
|
+
print(f"Instance URL: {sf.sf_instance}")
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### SalesforceLogin (No Token Required)
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from simple_salesforce import Salesforce, SalesforceLogin
|
|
168
|
+
|
|
169
|
+
session_id, instance = SalesforceLogin(
|
|
170
|
+
username='user@example.org',
|
|
171
|
+
password='your_password'
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
sf = Salesforce(instance=instance, session_id=session_id)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Session ID Authentication (OAuth)
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
from simple_salesforce import Salesforce
|
|
181
|
+
|
|
182
|
+
sf = Salesforce(
|
|
183
|
+
instance='na1.salesforce.com',
|
|
184
|
+
session_id='your_session_id_from_oauth'
|
|
185
|
+
)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### OAuth 2.0 Access Token
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
from simple_salesforce import Salesforce
|
|
192
|
+
|
|
193
|
+
sf = Salesforce(
|
|
194
|
+
instance_url='https://na1.salesforce.com',
|
|
195
|
+
session_id='your_oauth_access_token'
|
|
196
|
+
)
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Using Requests Session
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
import requests
|
|
203
|
+
from simple_salesforce import Salesforce
|
|
204
|
+
|
|
205
|
+
session = requests.Session()
|
|
206
|
+
session.headers.update({'Custom-Header': 'value'})
|
|
207
|
+
|
|
208
|
+
sf = Salesforce(
|
|
209
|
+
username='user@example.org',
|
|
210
|
+
password='your_password',
|
|
211
|
+
security_token='your_security_token',
|
|
212
|
+
session=session
|
|
213
|
+
)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## SOQL Queries
|
|
217
|
+
|
|
218
|
+
### Basic Query
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
from simple_salesforce import Salesforce
|
|
222
|
+
|
|
223
|
+
sf = Salesforce(
|
|
224
|
+
username='user@example.org',
|
|
225
|
+
password='your_password',
|
|
226
|
+
security_token='your_security_token'
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
result = sf.query("SELECT Id, Name FROM Account LIMIT 10")
|
|
230
|
+
|
|
231
|
+
print(f"Total records: {result['totalSize']}")
|
|
232
|
+
|
|
233
|
+
for record in result['records']:
|
|
234
|
+
print(f"Account: {record['Name']}")
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Query with WHERE Clause
|
|
238
|
+
|
|
239
|
+
```python
|
|
240
|
+
result = sf.query(
|
|
241
|
+
"SELECT Id, Name, Industry FROM Account WHERE Industry = 'Technology'"
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
print(f"Found {result['totalSize']} Technology accounts")
|
|
245
|
+
|
|
246
|
+
for record in result['records']:
|
|
247
|
+
print(f"{record['Name']} - {record['Industry']}")
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Query with Relationships
|
|
251
|
+
|
|
252
|
+
```python
|
|
253
|
+
# Parent to child relationship
|
|
254
|
+
result = sf.query(
|
|
255
|
+
"SELECT Id, Name, (SELECT Id, Name FROM Contacts) FROM Account LIMIT 5"
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
for account in result['records']:
|
|
259
|
+
print(f"Account: {account['Name']}")
|
|
260
|
+
if account.get('Contacts'):
|
|
261
|
+
for contact in account['Contacts']['records']:
|
|
262
|
+
print(f" Contact: {contact['Name']}")
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Query with Child to Parent Relationship
|
|
266
|
+
|
|
267
|
+
```python
|
|
268
|
+
result = sf.query(
|
|
269
|
+
"SELECT Id, Name, Account.Name FROM Contact LIMIT 10"
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
for contact in result['records']:
|
|
273
|
+
account_name = contact.get('Account', {}).get('Name', 'N/A')
|
|
274
|
+
print(f"{contact['Name']} - Account: {account_name}")
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Query All (Including Deleted Records)
|
|
278
|
+
|
|
279
|
+
```python
|
|
280
|
+
result = sf.query_all(
|
|
281
|
+
"SELECT Id, Name, IsDeleted FROM Account WHERE IsDeleted = true"
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
print(f"Deleted accounts: {result['totalSize']}")
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Query with Automatic Pagination
|
|
288
|
+
|
|
289
|
+
```python
|
|
290
|
+
# query_all handles pagination automatically
|
|
291
|
+
result = sf.query_all("SELECT Id, Name FROM Account")
|
|
292
|
+
|
|
293
|
+
print(f"Total records: {result['totalSize']}")
|
|
294
|
+
|
|
295
|
+
for record in result['records']:
|
|
296
|
+
print(f"Account: {record['Name']}")
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Query All Iterator (Memory Efficient)
|
|
300
|
+
|
|
301
|
+
```python
|
|
302
|
+
# Use query_all_iter for large datasets
|
|
303
|
+
for record in sf.query_all_iter("SELECT Id, Name FROM Account"):
|
|
304
|
+
print(f"Account: {record['Name']}")
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Manual Pagination
|
|
308
|
+
|
|
309
|
+
```python
|
|
310
|
+
result = sf.query("SELECT Id, Name FROM Account")
|
|
311
|
+
all_records = result['records']
|
|
312
|
+
|
|
313
|
+
while not result['done']:
|
|
314
|
+
result = sf.query_more(result['nextRecordsUrl'], identifier_is_url=True)
|
|
315
|
+
all_records.extend(result['records'])
|
|
316
|
+
|
|
317
|
+
print(f"Total records retrieved: {len(all_records)}")
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Safe Query with Parameter Formatting
|
|
321
|
+
|
|
322
|
+
```python
|
|
323
|
+
from simple_salesforce.format import format_soql
|
|
324
|
+
|
|
325
|
+
# Safe parameter substitution
|
|
326
|
+
last_name = "O'Brien"
|
|
327
|
+
query = format_soql("SELECT Id, Email FROM Contact WHERE LastName = {}", last_name)
|
|
328
|
+
result = sf.query(query)
|
|
329
|
+
|
|
330
|
+
# LIKE queries
|
|
331
|
+
name_pattern = "John"
|
|
332
|
+
query = format_soql(
|
|
333
|
+
"SELECT Id, Name FROM Contact WHERE Name LIKE '{:like}%'",
|
|
334
|
+
name_pattern
|
|
335
|
+
)
|
|
336
|
+
result = sf.query(query)
|
|
337
|
+
|
|
338
|
+
# Multiple parameters
|
|
339
|
+
query = format_soql(
|
|
340
|
+
"SELECT Id, Name FROM Account WHERE Industry = {} AND AnnualRevenue > {}",
|
|
341
|
+
"Technology",
|
|
342
|
+
1000000
|
|
343
|
+
)
|
|
344
|
+
result = sf.query(query)
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### Query with Date Filters
|
|
348
|
+
|
|
349
|
+
```python
|
|
350
|
+
from datetime import datetime, timedelta
|
|
351
|
+
|
|
352
|
+
# Query records created in the last 7 days
|
|
353
|
+
seven_days_ago = (datetime.now() - timedelta(days=7)).strftime('%Y-%m-%dT%H:%M:%SZ')
|
|
354
|
+
query = f"SELECT Id, Name FROM Account WHERE CreatedDate > {seven_days_ago}"
|
|
355
|
+
result = sf.query(query)
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## SOSL Search
|
|
359
|
+
|
|
360
|
+
### Basic Search
|
|
361
|
+
|
|
362
|
+
```python
|
|
363
|
+
result = sf.search(
|
|
364
|
+
"FIND {United*} IN NAME FIELDS RETURNING Account(Id, Name), Contact(Id, Name)"
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
print(f"Search results: {result['searchRecords']}")
|
|
368
|
+
|
|
369
|
+
for record in result['searchRecords']:
|
|
370
|
+
print(f"{record['attributes']['type']}: {record['Name']}")
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## CRUD Operations
|
|
374
|
+
|
|
375
|
+
### Create Single Record
|
|
376
|
+
|
|
377
|
+
```python
|
|
378
|
+
from simple_salesforce import Salesforce
|
|
379
|
+
|
|
380
|
+
sf = Salesforce(
|
|
381
|
+
username='user@example.org',
|
|
382
|
+
password='your_password',
|
|
383
|
+
security_token='your_security_token'
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
result = sf.Account.create({
|
|
387
|
+
'Name': 'New Account',
|
|
388
|
+
'Industry': 'Technology',
|
|
389
|
+
'BillingCity': 'San Francisco'
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
print(f"Created account ID: {result['id']}")
|
|
393
|
+
print(f"Success: {result['success']}")
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### Create with Error Handling
|
|
397
|
+
|
|
398
|
+
```python
|
|
399
|
+
try:
|
|
400
|
+
result = sf.Account.create({
|
|
401
|
+
'Name': 'Test Account',
|
|
402
|
+
'Industry': 'Technology'
|
|
403
|
+
})
|
|
404
|
+
print(f"Created: {result['id']}")
|
|
405
|
+
except Exception as e:
|
|
406
|
+
print(f"Error creating account: {e}")
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### Retrieve Single Record by ID
|
|
410
|
+
|
|
411
|
+
```python
|
|
412
|
+
account = sf.Account.get('001XXXXXXXXXXXXXXX')
|
|
413
|
+
|
|
414
|
+
print(f"Account Name: {account['Name']}")
|
|
415
|
+
print(f"Industry: {account['Industry']}")
|
|
416
|
+
print(f"Created Date: {account['CreatedDate']}")
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### Retrieve Specific Fields
|
|
420
|
+
|
|
421
|
+
```python
|
|
422
|
+
account = sf.Account.get('001XXXXXXXXXXXXXXX', fields=['Id', 'Name', 'Industry'])
|
|
423
|
+
|
|
424
|
+
print(f"Name: {account['Name']}")
|
|
425
|
+
print(f"Industry: {account['Industry']}")
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### Update Single Record
|
|
429
|
+
|
|
430
|
+
```python
|
|
431
|
+
result = sf.Account.update('001XXXXXXXXXXXXXXX', {
|
|
432
|
+
'Name': 'Updated Account Name',
|
|
433
|
+
'Industry': 'Media'
|
|
434
|
+
})
|
|
435
|
+
|
|
436
|
+
print(f"Updated: {result}")
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
### Update Multiple Fields
|
|
440
|
+
|
|
441
|
+
```python
|
|
442
|
+
result = sf.Account.update('001XXXXXXXXXXXXXXX', {
|
|
443
|
+
'Name': 'New Name',
|
|
444
|
+
'Industry': 'Finance',
|
|
445
|
+
'BillingCity': 'New York',
|
|
446
|
+
'Phone': '555-1234'
|
|
447
|
+
})
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### Upsert (Update or Insert)
|
|
451
|
+
|
|
452
|
+
```python
|
|
453
|
+
# Upsert using external ID
|
|
454
|
+
result = sf.Account.upsert('ExternalId__c/EXT001', {
|
|
455
|
+
'Name': 'Upserted Account',
|
|
456
|
+
'Industry': 'Technology',
|
|
457
|
+
'ExternalId__c': 'EXT001'
|
|
458
|
+
})
|
|
459
|
+
|
|
460
|
+
if result == 204:
|
|
461
|
+
print("Record updated")
|
|
462
|
+
elif result == 201:
|
|
463
|
+
print("Record created")
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### Upsert with Response Details
|
|
467
|
+
|
|
468
|
+
```python
|
|
469
|
+
try:
|
|
470
|
+
result = sf.Contact.upsert('Email/[email protected]', {
|
|
471
|
+
'FirstName': 'John',
|
|
472
|
+
'LastName': 'Doe',
|
|
473
|
+
'Email': '[email protected]'
|
|
474
|
+
}, raw_response=True)
|
|
475
|
+
|
|
476
|
+
if result.status_code == 201:
|
|
477
|
+
print("Contact created")
|
|
478
|
+
elif result.status_code == 204:
|
|
479
|
+
print("Contact updated")
|
|
480
|
+
except Exception as e:
|
|
481
|
+
print(f"Upsert error: {e}")
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### Delete Single Record
|
|
485
|
+
|
|
486
|
+
```python
|
|
487
|
+
result = sf.Account.delete('001XXXXXXXXXXXXXXX')
|
|
488
|
+
|
|
489
|
+
print(f"Deleted: {result}")
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
### Delete with Error Handling
|
|
493
|
+
|
|
494
|
+
```python
|
|
495
|
+
try:
|
|
496
|
+
sf.Account.delete('001XXXXXXXXXXXXXXX')
|
|
497
|
+
print("Account deleted successfully")
|
|
498
|
+
except Exception as e:
|
|
499
|
+
print(f"Error deleting account: {e}")
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
## Bulk Operations
|
|
503
|
+
|
|
504
|
+
### Bulk Insert
|
|
505
|
+
|
|
506
|
+
```python
|
|
507
|
+
from simple_salesforce import Salesforce
|
|
508
|
+
|
|
509
|
+
sf = Salesforce(
|
|
510
|
+
username='user@example.org',
|
|
511
|
+
password='your_password',
|
|
512
|
+
security_token='your_security_token'
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
# Prepare data
|
|
516
|
+
accounts = []
|
|
517
|
+
for i in range(1000):
|
|
518
|
+
accounts.append({
|
|
519
|
+
'Name': f'Bulk Account {i}',
|
|
520
|
+
'Industry': 'Technology'
|
|
521
|
+
})
|
|
522
|
+
|
|
523
|
+
# Bulk insert using Bulk 2.0 API
|
|
524
|
+
result = sf.bulk2.Account.insert(accounts)
|
|
525
|
+
|
|
526
|
+
print(f"Job ID: {result['job_id']}")
|
|
527
|
+
print(f"Records processed: {result['numberRecordsProcessed']}")
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
### Bulk Insert from CSV
|
|
531
|
+
|
|
532
|
+
```python
|
|
533
|
+
# Insert from CSV file
|
|
534
|
+
result = sf.bulk2.Account.insert('./accounts.csv')
|
|
535
|
+
|
|
536
|
+
print(f"Job ID: {result['job_id']}")
|
|
537
|
+
print(f"Records processed: {result['numberRecordsProcessed']}")
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
### Bulk Update
|
|
541
|
+
|
|
542
|
+
```python
|
|
543
|
+
# Prepare update data
|
|
544
|
+
updates = []
|
|
545
|
+
for account_id in account_ids:
|
|
546
|
+
updates.append({
|
|
547
|
+
'Id': account_id,
|
|
548
|
+
'Industry': 'Finance'
|
|
549
|
+
})
|
|
550
|
+
|
|
551
|
+
result = sf.bulk2.Account.update(updates)
|
|
552
|
+
|
|
553
|
+
print(f"Records updated: {result['numberRecordsProcessed']}")
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
### Bulk Update from CSV
|
|
557
|
+
|
|
558
|
+
```python
|
|
559
|
+
result = sf.bulk2.Account.update('./account_updates.csv')
|
|
560
|
+
|
|
561
|
+
print(f"Job ID: {result['job_id']}")
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
### Bulk Delete
|
|
565
|
+
|
|
566
|
+
```python
|
|
567
|
+
# Prepare delete data (IDs only)
|
|
568
|
+
deletes = []
|
|
569
|
+
for account_id in account_ids_to_delete:
|
|
570
|
+
deletes.append({'Id': account_id})
|
|
571
|
+
|
|
572
|
+
result = sf.bulk2.Account.delete(deletes)
|
|
573
|
+
|
|
574
|
+
print(f"Records deleted: {result['numberRecordsProcessed']}")
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
### Bulk Upsert
|
|
578
|
+
|
|
579
|
+
```python
|
|
580
|
+
upserts = []
|
|
581
|
+
for i in range(100):
|
|
582
|
+
upserts.append({
|
|
583
|
+
'ExternalId__c': f'EXT{i}',
|
|
584
|
+
'Name': f'Bulk Upsert Account {i}',
|
|
585
|
+
'Industry': 'Technology'
|
|
586
|
+
})
|
|
587
|
+
|
|
588
|
+
result = sf.bulk2.Account.upsert(upserts, 'ExternalId__c')
|
|
589
|
+
|
|
590
|
+
print(f"Records processed: {result['numberRecordsProcessed']}")
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
### Get Bulk Job Results
|
|
594
|
+
|
|
595
|
+
```python
|
|
596
|
+
job_id = 'bulk_job_id_from_insert_or_update'
|
|
597
|
+
|
|
598
|
+
# Get successful records
|
|
599
|
+
successful = sf.bulk2.Account.get_successful_records(job_id)
|
|
600
|
+
print(f"Successful records: {successful}")
|
|
601
|
+
|
|
602
|
+
# Get failed records
|
|
603
|
+
failed = sf.bulk2.Account.get_failed_records(job_id)
|
|
604
|
+
print(f"Failed records: {failed}")
|
|
605
|
+
|
|
606
|
+
# Save failed records to CSV
|
|
607
|
+
sf.bulk2.Account.get_failed_records(job_id, file='failed_records.csv')
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
### Bulk Query
|
|
611
|
+
|
|
612
|
+
```python
|
|
613
|
+
# Bulk 2.0 query for large datasets
|
|
614
|
+
result = sf.bulk2.Account.query("SELECT Id, Name, Industry FROM Account")
|
|
615
|
+
|
|
616
|
+
print(f"Job ID: {result['job_id']}")
|
|
617
|
+
|
|
618
|
+
# Get query results
|
|
619
|
+
query_results = sf.bulk2.Account.get_query_results(result['job_id'])
|
|
620
|
+
|
|
621
|
+
for record in query_results:
|
|
622
|
+
print(f"Account: {record['Name']}")
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
## SObject Metadata
|
|
626
|
+
|
|
627
|
+
### Describe SObject
|
|
628
|
+
|
|
629
|
+
```python
|
|
630
|
+
metadata = sf.Account.describe()
|
|
631
|
+
|
|
632
|
+
print(f"Label: {metadata['label']}")
|
|
633
|
+
print(f"Updateable: {metadata['updateable']}")
|
|
634
|
+
print(f"Deletable: {metadata['deletable']}")
|
|
635
|
+
print(f"Number of fields: {len(metadata['fields'])}")
|
|
636
|
+
|
|
637
|
+
for field in metadata['fields']:
|
|
638
|
+
print(f" {field['name']} ({field['type']})")
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
### Describe All SObjects
|
|
642
|
+
|
|
643
|
+
```python
|
|
644
|
+
result = sf.describe()
|
|
645
|
+
|
|
646
|
+
for sobject in result['sobjects']:
|
|
647
|
+
print(f"{sobject['name']} - {sobject['label']}")
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
### Get Field Information
|
|
651
|
+
|
|
652
|
+
```python
|
|
653
|
+
metadata = sf.Contact.describe()
|
|
654
|
+
|
|
655
|
+
for field in metadata['fields']:
|
|
656
|
+
if field['name'] == 'Email':
|
|
657
|
+
print(f"Type: {field['type']}")
|
|
658
|
+
print(f"Length: {field['length']}")
|
|
659
|
+
print(f"Required: {field['nillable'] == False}")
|
|
660
|
+
print(f"Updateable: {field['updateable']}")
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
### Get Picklist Values
|
|
664
|
+
|
|
665
|
+
```python
|
|
666
|
+
metadata = sf.Account.describe()
|
|
667
|
+
|
|
668
|
+
for field in metadata['fields']:
|
|
669
|
+
if field['name'] == 'Industry' and field['type'] == 'picklist':
|
|
670
|
+
print("Industry picklist values:")
|
|
671
|
+
for value in field['picklistValues']:
|
|
672
|
+
print(f" - {value['value']}")
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
## Apex REST API
|
|
676
|
+
|
|
677
|
+
### Call Custom Apex REST Endpoint (GET)
|
|
678
|
+
|
|
679
|
+
```python
|
|
680
|
+
# Call GET endpoint
|
|
681
|
+
result = sf.apexecute('MyApexRestService', method='GET')
|
|
682
|
+
|
|
683
|
+
print(f"Response: {result}")
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
### Call Custom Apex REST Endpoint (POST)
|
|
687
|
+
|
|
688
|
+
```python
|
|
689
|
+
payload = {
|
|
690
|
+
'name': 'Test',
|
|
691
|
+
'value': 123,
|
|
692
|
+
'active': True
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
result = sf.apexecute('MyApexRestService', method='POST', data=payload)
|
|
696
|
+
|
|
697
|
+
print(f"Response: {result}")
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
### Call Custom Apex REST Endpoint (PUT)
|
|
701
|
+
|
|
702
|
+
```python
|
|
703
|
+
payload = {
|
|
704
|
+
'name': 'Updated Name',
|
|
705
|
+
'value': 456
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
result = sf.apexecute('MyApexRestService/recordId', method='PUT', data=payload)
|
|
709
|
+
|
|
710
|
+
print(f"Response: {result}")
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
### Call Custom Apex REST Endpoint (DELETE)
|
|
714
|
+
|
|
715
|
+
```python
|
|
716
|
+
result = sf.apexecute('MyApexRestService/recordId', method='DELETE')
|
|
717
|
+
|
|
718
|
+
print(f"Response: {result}")
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
### Call with URL Parameters
|
|
722
|
+
|
|
723
|
+
```python
|
|
724
|
+
result = sf.apexecute('MyApexRestService', method='GET', params={'filter': 'active'})
|
|
725
|
+
|
|
726
|
+
print(f"Response: {result}")
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
## Generic REST API Requests
|
|
730
|
+
|
|
731
|
+
### GET Request
|
|
732
|
+
|
|
733
|
+
```python
|
|
734
|
+
# Make a generic GET request
|
|
735
|
+
result = sf.restful('sobjects/Account/001XXXXXXXXXXXXXXX')
|
|
736
|
+
|
|
737
|
+
print(f"Account: {result}")
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
### POST Request
|
|
741
|
+
|
|
742
|
+
```python
|
|
743
|
+
data = {
|
|
744
|
+
'Name': 'New Account',
|
|
745
|
+
'Industry': 'Technology'
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
result = sf.restful('sobjects/Account', method='POST', data=data)
|
|
749
|
+
|
|
750
|
+
print(f"Created: {result}")
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
### Custom API Endpoint
|
|
754
|
+
|
|
755
|
+
```python
|
|
756
|
+
# Call a custom REST endpoint
|
|
757
|
+
result = sf.restful('services/apexrest/CustomEndpoint', method='GET')
|
|
758
|
+
|
|
759
|
+
print(f"Response: {result}")
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
## Advanced Features
|
|
763
|
+
|
|
764
|
+
### Set API Version
|
|
765
|
+
|
|
766
|
+
```python
|
|
767
|
+
sf = Salesforce(
|
|
768
|
+
username='user@example.org',
|
|
769
|
+
password='your_password',
|
|
770
|
+
security_token='your_security_token',
|
|
771
|
+
version='58.0'
|
|
772
|
+
)
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
### Disable SSL Verification (Not Recommended)
|
|
776
|
+
|
|
777
|
+
```python
|
|
778
|
+
import urllib3
|
|
779
|
+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
780
|
+
|
|
781
|
+
sf = Salesforce(
|
|
782
|
+
username='user@example.org',
|
|
783
|
+
password='your_password',
|
|
784
|
+
security_token='your_security_token',
|
|
785
|
+
security_token_bypass=True,
|
|
786
|
+
session=None
|
|
787
|
+
)
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
### Custom Timeout
|
|
791
|
+
|
|
792
|
+
```python
|
|
793
|
+
import requests
|
|
794
|
+
from simple_salesforce import Salesforce
|
|
795
|
+
|
|
796
|
+
session = requests.Session()
|
|
797
|
+
session.timeout = 60 # 60 seconds
|
|
798
|
+
|
|
799
|
+
sf = Salesforce(
|
|
800
|
+
username='user@example.org',
|
|
801
|
+
password='your_password',
|
|
802
|
+
security_token='your_security_token',
|
|
803
|
+
session=session
|
|
804
|
+
)
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
### Proxies
|
|
808
|
+
|
|
809
|
+
```python
|
|
810
|
+
import requests
|
|
811
|
+
from simple_salesforce import Salesforce
|
|
812
|
+
|
|
813
|
+
session = requests.Session()
|
|
814
|
+
session.proxies = {
|
|
815
|
+
'http': 'http://proxy.example.com:8080',
|
|
816
|
+
'https': 'https://proxy.example.com:8080'
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
sf = Salesforce(
|
|
820
|
+
username='user@example.org',
|
|
821
|
+
password='your_password',
|
|
822
|
+
security_token='your_security_token',
|
|
823
|
+
session=session
|
|
824
|
+
)
|
|
825
|
+
```
|
|
826
|
+
|
|
827
|
+
### Custom Headers
|
|
828
|
+
|
|
829
|
+
```python
|
|
830
|
+
import requests
|
|
831
|
+
from simple_salesforce import Salesforce
|
|
832
|
+
|
|
833
|
+
session = requests.Session()
|
|
834
|
+
session.headers.update({
|
|
835
|
+
'Custom-Header': 'value',
|
|
836
|
+
'Another-Header': 'another-value'
|
|
837
|
+
})
|
|
838
|
+
|
|
839
|
+
sf = Salesforce(
|
|
840
|
+
username='user@example.org',
|
|
841
|
+
password='your_password',
|
|
842
|
+
security_token='your_security_token',
|
|
843
|
+
session=session
|
|
844
|
+
)
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
## Composite API
|
|
848
|
+
|
|
849
|
+
### Composite Request
|
|
850
|
+
|
|
851
|
+
```python
|
|
852
|
+
composite_request = {
|
|
853
|
+
'compositeRequest': [
|
|
854
|
+
{
|
|
855
|
+
'method': 'POST',
|
|
856
|
+
'url': '/services/data/v58.0/sobjects/Account',
|
|
857
|
+
'referenceId': 'NewAccount',
|
|
858
|
+
'body': {
|
|
859
|
+
'Name': 'Composite Account'
|
|
860
|
+
}
|
|
861
|
+
},
|
|
862
|
+
{
|
|
863
|
+
'method': 'POST',
|
|
864
|
+
'url': '/services/data/v58.0/sobjects/Contact',
|
|
865
|
+
'referenceId': 'NewContact',
|
|
866
|
+
'body': {
|
|
867
|
+
'FirstName': 'John',
|
|
868
|
+
'LastName': 'Doe',
|
|
869
|
+
'AccountId': '@{NewAccount.id}'
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
]
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
result = sf.restful(
|
|
876
|
+
'composite',
|
|
877
|
+
method='POST',
|
|
878
|
+
data=composite_request
|
|
879
|
+
)
|
|
880
|
+
|
|
881
|
+
print(f"Composite results: {result}")
|
|
882
|
+
```
|
|
883
|
+
|
|
884
|
+
## Working with Attachments
|
|
885
|
+
|
|
886
|
+
### Upload Attachment
|
|
887
|
+
|
|
888
|
+
```python
|
|
889
|
+
import base64
|
|
890
|
+
|
|
891
|
+
with open('document.pdf', 'rb') as f:
|
|
892
|
+
file_data = base64.b64encode(f.read()).decode()
|
|
893
|
+
|
|
894
|
+
result = sf.Attachment.create({
|
|
895
|
+
'Name': 'document.pdf',
|
|
896
|
+
'ParentId': '001XXXXXXXXXXXXXXX', # Record ID
|
|
897
|
+
'Body': file_data,
|
|
898
|
+
'ContentType': 'application/pdf'
|
|
899
|
+
})
|
|
900
|
+
|
|
901
|
+
print(f"Attachment ID: {result['id']}")
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
### Download Attachment
|
|
905
|
+
|
|
906
|
+
```python
|
|
907
|
+
import base64
|
|
908
|
+
|
|
909
|
+
attachment = sf.Attachment.get('00PXXXXXXXXXXXXXXX')
|
|
910
|
+
file_data = base64.b64decode(attachment['Body'])
|
|
911
|
+
|
|
912
|
+
with open('downloaded_file.pdf', 'wb') as f:
|
|
913
|
+
f.write(file_data)
|
|
914
|
+
```
|
|
915
|
+
|
|
916
|
+
## Working with ContentVersion (Files)
|
|
917
|
+
|
|
918
|
+
### Upload File
|
|
919
|
+
|
|
920
|
+
```python
|
|
921
|
+
import base64
|
|
922
|
+
|
|
923
|
+
with open('document.pdf', 'rb') as f:
|
|
924
|
+
file_data = base64.b64encode(f.read()).decode()
|
|
925
|
+
|
|
926
|
+
result = sf.ContentVersion.create({
|
|
927
|
+
'Title': 'My Document',
|
|
928
|
+
'PathOnClient': 'document.pdf',
|
|
929
|
+
'VersionData': file_data,
|
|
930
|
+
'FirstPublishLocationId': '001XXXXXXXXXXXXXXX' # Record ID
|
|
931
|
+
})
|
|
932
|
+
|
|
933
|
+
print(f"ContentVersion ID: {result['id']}")
|
|
934
|
+
```
|
|
935
|
+
|
|
936
|
+
### Query Files
|
|
937
|
+
|
|
938
|
+
```python
|
|
939
|
+
result = sf.query(
|
|
940
|
+
"SELECT Id, Title, FileType FROM ContentDocument WHERE Title LIKE '%Report%'"
|
|
941
|
+
)
|
|
942
|
+
|
|
943
|
+
for doc in result['records']:
|
|
944
|
+
print(f"Document: {doc['Title']} ({doc['FileType']})")
|
|
945
|
+
```
|
|
946
|
+
|
|
947
|
+
## Error Handling
|
|
948
|
+
|
|
949
|
+
### Basic Error Handling
|
|
950
|
+
|
|
951
|
+
```python
|
|
952
|
+
from simple_salesforce import Salesforce, SalesforceAuthenticationFailed
|
|
953
|
+
|
|
954
|
+
try:
|
|
955
|
+
sf = Salesforce(
|
|
956
|
+
username='user@example.org',
|
|
957
|
+
password='wrong_password',
|
|
958
|
+
security_token='token'
|
|
959
|
+
)
|
|
960
|
+
except SalesforceAuthenticationFailed as e:
|
|
961
|
+
print(f"Authentication failed: {e}")
|
|
962
|
+
```
|
|
963
|
+
|
|
964
|
+
### CRUD Error Handling
|
|
965
|
+
|
|
966
|
+
```python
|
|
967
|
+
from simple_salesforce.exceptions import SalesforceError
|
|
968
|
+
|
|
969
|
+
try:
|
|
970
|
+
result = sf.Account.create({
|
|
971
|
+
'Name': '' # Required field missing
|
|
972
|
+
})
|
|
973
|
+
except SalesforceError as e:
|
|
974
|
+
print(f"Error code: {e.status}")
|
|
975
|
+
print(f"Error message: {e.content}")
|
|
976
|
+
```
|
|
977
|
+
|
|
978
|
+
### Query Error Handling
|
|
979
|
+
|
|
980
|
+
```python
|
|
981
|
+
try:
|
|
982
|
+
result = sf.query("SELECT Id, InvalidField FROM Account")
|
|
983
|
+
except SalesforceError as e:
|
|
984
|
+
print(f"Query error: {e}")
|
|
985
|
+
```
|
|
986
|
+
|
|
987
|
+
### Generic Exception Handling
|
|
988
|
+
|
|
989
|
+
```python
|
|
990
|
+
import requests
|
|
991
|
+
|
|
992
|
+
try:
|
|
993
|
+
result = sf.Account.update('001XXXXXXXXXXXXXXX', {
|
|
994
|
+
'Name': 'Updated Name'
|
|
995
|
+
})
|
|
996
|
+
except requests.exceptions.Timeout:
|
|
997
|
+
print("Request timed out")
|
|
998
|
+
except requests.exceptions.ConnectionError:
|
|
999
|
+
print("Connection error")
|
|
1000
|
+
except Exception as e:
|
|
1001
|
+
print(f"Unexpected error: {e}")
|
|
1002
|
+
```
|
|
1003
|
+
|
|
1004
|
+
## Best Practices
|
|
1005
|
+
|
|
1006
|
+
### Batch Processing
|
|
1007
|
+
|
|
1008
|
+
```python
|
|
1009
|
+
def process_accounts_in_batches(account_ids, batch_size=200):
|
|
1010
|
+
for i in range(0, len(account_ids), batch_size):
|
|
1011
|
+
batch = account_ids[i:i + batch_size]
|
|
1012
|
+
|
|
1013
|
+
# Build query for batch
|
|
1014
|
+
ids_str = "','".join(batch)
|
|
1015
|
+
query = f"SELECT Id, Name FROM Account WHERE Id IN ('{ids_str}')"
|
|
1016
|
+
|
|
1017
|
+
result = sf.query(query)
|
|
1018
|
+
|
|
1019
|
+
for record in result['records']:
|
|
1020
|
+
# Process each record
|
|
1021
|
+
print(f"Processing: {record['Name']}")
|
|
1022
|
+
```
|
|
1023
|
+
|
|
1024
|
+
### Retry Logic
|
|
1025
|
+
|
|
1026
|
+
```python
|
|
1027
|
+
import time
|
|
1028
|
+
from simple_salesforce.exceptions import SalesforceError
|
|
1029
|
+
|
|
1030
|
+
def create_with_retry(sobject, data, max_retries=3):
|
|
1031
|
+
for attempt in range(max_retries):
|
|
1032
|
+
try:
|
|
1033
|
+
result = getattr(sf, sobject).create(data)
|
|
1034
|
+
return result
|
|
1035
|
+
except SalesforceError as e:
|
|
1036
|
+
if attempt < max_retries - 1:
|
|
1037
|
+
wait_time = 2 ** attempt # Exponential backoff
|
|
1038
|
+
print(f"Retry {attempt + 1} after {wait_time}s")
|
|
1039
|
+
time.sleep(wait_time)
|
|
1040
|
+
else:
|
|
1041
|
+
raise
|
|
1042
|
+
```
|
|
1043
|
+
|
|
1044
|
+
### Connection Pooling
|
|
1045
|
+
|
|
1046
|
+
```python
|
|
1047
|
+
import requests
|
|
1048
|
+
from requests.adapters import HTTPAdapter
|
|
1049
|
+
from urllib3.util.retry import Retry
|
|
1050
|
+
from simple_salesforce import Salesforce
|
|
1051
|
+
|
|
1052
|
+
def create_sf_session():
|
|
1053
|
+
session = requests.Session()
|
|
1054
|
+
|
|
1055
|
+
retry_strategy = Retry(
|
|
1056
|
+
total=3,
|
|
1057
|
+
status_forcelist=[429, 500, 502, 503, 504],
|
|
1058
|
+
method_whitelist=["HEAD", "GET", "OPTIONS", "POST", "PATCH", "PUT"]
|
|
1059
|
+
)
|
|
1060
|
+
|
|
1061
|
+
adapter = HTTPAdapter(max_retries=retry_strategy)
|
|
1062
|
+
session.mount("https://", adapter)
|
|
1063
|
+
session.mount("http://", adapter)
|
|
1064
|
+
|
|
1065
|
+
return session
|
|
1066
|
+
|
|
1067
|
+
sf = Salesforce(
|
|
1068
|
+
username='user@example.org',
|
|
1069
|
+
password='your_password',
|
|
1070
|
+
security_token='your_security_token',
|
|
1071
|
+
session=create_sf_session()
|
|
1072
|
+
)
|
|
1073
|
+
```
|
|
1074
|
+
|
|
1075
|
+
### Efficient Field Selection
|
|
1076
|
+
|
|
1077
|
+
```python
|
|
1078
|
+
# Only query fields you need
|
|
1079
|
+
result = sf.query(
|
|
1080
|
+
"SELECT Id, Name, Industry FROM Account LIMIT 100"
|
|
1081
|
+
)
|
|
1082
|
+
|
|
1083
|
+
# Instead of:
|
|
1084
|
+
# result = sf.query("SELECT Id, Name, Industry, ... (all fields) FROM Account LIMIT 100")
|
|
1085
|
+
```
|
|
1086
|
+
|
|
1087
|
+
### Use Bulk API for Large Data Sets
|
|
1088
|
+
|
|
1089
|
+
```python
|
|
1090
|
+
# For < 2000 records, use regular API
|
|
1091
|
+
if len(records) < 2000:
|
|
1092
|
+
for record in records:
|
|
1093
|
+
sf.Account.create(record)
|
|
1094
|
+
else:
|
|
1095
|
+
# For >= 2000 records, use Bulk API
|
|
1096
|
+
sf.bulk2.Account.insert(records)
|
|
1097
|
+
```
|
|
1098
|
+
|
|
1099
|
+
## Complete Example Application
|
|
1100
|
+
|
|
1101
|
+
```python
|
|
1102
|
+
import os
|
|
1103
|
+
from dotenv import load_dotenv
|
|
1104
|
+
from simple_salesforce import Salesforce, SalesforceAuthenticationFailed
|
|
1105
|
+
from simple_salesforce.exceptions import SalesforceError
|
|
1106
|
+
|
|
1107
|
+
# Load environment variables
|
|
1108
|
+
load_dotenv()
|
|
1109
|
+
|
|
1110
|
+
def main():
|
|
1111
|
+
try:
|
|
1112
|
+
# Initialize connection
|
|
1113
|
+
sf = Salesforce(
|
|
1114
|
+
username=os.getenv('SALESFORCE_USERNAME'),
|
|
1115
|
+
password=os.getenv('SALESFORCE_PASSWORD'),
|
|
1116
|
+
security_token=os.getenv('SALESFORCE_SECURITY_TOKEN')
|
|
1117
|
+
)
|
|
1118
|
+
|
|
1119
|
+
print("Connected to Salesforce successfully!")
|
|
1120
|
+
|
|
1121
|
+
# Create account
|
|
1122
|
+
account_result = sf.Account.create({
|
|
1123
|
+
'Name': 'Example Corp',
|
|
1124
|
+
'Industry': 'Technology',
|
|
1125
|
+
'BillingCity': 'San Francisco'
|
|
1126
|
+
})
|
|
1127
|
+
account_id = account_result['id']
|
|
1128
|
+
print(f"Created account: {account_id}")
|
|
1129
|
+
|
|
1130
|
+
# Create contact
|
|
1131
|
+
contact_result = sf.Contact.create({
|
|
1132
|
+
'FirstName': 'John',
|
|
1133
|
+
'LastName': 'Doe',
|
|
1134
|
+
'Email': '[email protected]',
|
|
1135
|
+
'AccountId': account_id
|
|
1136
|
+
})
|
|
1137
|
+
contact_id = contact_result['id']
|
|
1138
|
+
print(f"Created contact: {contact_id}")
|
|
1139
|
+
|
|
1140
|
+
# Query accounts with contacts
|
|
1141
|
+
result = sf.query(f"""
|
|
1142
|
+
SELECT Id, Name, Industry,
|
|
1143
|
+
(SELECT Id, Name, Email FROM Contacts)
|
|
1144
|
+
FROM Account
|
|
1145
|
+
WHERE Id = '{account_id}'
|
|
1146
|
+
""")
|
|
1147
|
+
|
|
1148
|
+
for account in result['records']:
|
|
1149
|
+
print(f"\nAccount: {account['Name']}")
|
|
1150
|
+
if account.get('Contacts'):
|
|
1151
|
+
for contact in account['Contacts']['records']:
|
|
1152
|
+
print(f" Contact: {contact['Name']} - {contact['Email']}")
|
|
1153
|
+
|
|
1154
|
+
# Update account
|
|
1155
|
+
sf.Account.update(account_id, {
|
|
1156
|
+
'Industry': 'Finance'
|
|
1157
|
+
})
|
|
1158
|
+
print(f"\nUpdated account industry")
|
|
1159
|
+
|
|
1160
|
+
# Clean up - delete records
|
|
1161
|
+
sf.Contact.delete(contact_id)
|
|
1162
|
+
sf.Account.delete(account_id)
|
|
1163
|
+
print("\nCleaned up test data")
|
|
1164
|
+
|
|
1165
|
+
except SalesforceAuthenticationFailed as e:
|
|
1166
|
+
print(f"Authentication failed: {e}")
|
|
1167
|
+
except SalesforceError as e:
|
|
1168
|
+
print(f"Salesforce API error: {e}")
|
|
1169
|
+
except Exception as e:
|
|
1170
|
+
print(f"Unexpected error: {e}")
|
|
1171
|
+
|
|
1172
|
+
if __name__ == '__main__':
|
|
1173
|
+
main()
|
|
1174
|
+
```
|
|
1175
|
+
|
|
1176
|
+
## Useful Links
|
|
1177
|
+
|
|
1178
|
+
- Documentation: https://simple-salesforce.readthedocs.io/
|
|
1179
|
+
- GitHub: https://github.com/simple-salesforce/simple-salesforce
|
|
1180
|
+
- PyPI: https://pypi.org/project/simple-salesforce/
|
|
1181
|
+
- Salesforce Developer Docs: https://developer.salesforce.com/
|
|
1182
|
+
- Salesforce REST API Guide: https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/
|
|
1183
|
+
- SOQL Reference: https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/
|