pyxecm 1.4__py3-none-any.whl → 1.6__py3-none-any.whl
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.
Potentially problematic release.
This version of pyxecm might be problematic. Click here for more details.
- pyxecm/__init__.py +5 -0
- pyxecm/avts.py +1065 -0
- pyxecm/coreshare.py +2532 -0
- pyxecm/customizer/__init__.py +4 -0
- pyxecm/customizer/browser_automation.py +164 -54
- pyxecm/customizer/customizer.py +588 -231
- pyxecm/customizer/k8s.py +143 -29
- pyxecm/customizer/m365.py +1434 -1323
- pyxecm/customizer/payload.py +15073 -5933
- pyxecm/customizer/pht.py +926 -0
- pyxecm/customizer/salesforce.py +866 -351
- pyxecm/customizer/sap.py +4 -4
- pyxecm/customizer/servicenow.py +1467 -0
- pyxecm/customizer/successfactors.py +1056 -0
- pyxecm/helper/__init__.py +2 -0
- pyxecm/helper/assoc.py +44 -1
- pyxecm/helper/data.py +1731 -0
- pyxecm/helper/web.py +170 -46
- pyxecm/helper/xml.py +170 -34
- pyxecm/otac.py +309 -23
- pyxecm/otawp.py +1810 -0
- pyxecm/otcs.py +5308 -2985
- pyxecm/otds.py +1909 -1954
- pyxecm/otmm.py +928 -0
- pyxecm/otpd.py +13 -10
- {pyxecm-1.4.dist-info → pyxecm-1.6.dist-info}/METADATA +5 -1
- pyxecm-1.6.dist-info/RECORD +32 -0
- {pyxecm-1.4.dist-info → pyxecm-1.6.dist-info}/WHEEL +1 -1
- pyxecm-1.4.dist-info/RECORD +0 -24
- {pyxecm-1.4.dist-info → pyxecm-1.6.dist-info}/LICENSE +0 -0
- {pyxecm-1.4.dist-info → pyxecm-1.6.dist-info}/top_level.txt +0 -0
pyxecm/customizer/salesforce.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Salesforce Module to interact with the Salesforce API
|
|
3
|
+
See: https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/intro_rest.htm
|
|
3
4
|
|
|
4
5
|
Class: Salesforce
|
|
5
6
|
Methods:
|
|
@@ -7,22 +8,38 @@ Methods:
|
|
|
7
8
|
__init__ : class initializer
|
|
8
9
|
config : Returns config data set
|
|
9
10
|
credentials: Returns the token data
|
|
11
|
+
|
|
10
12
|
request_header: Returns the request header for Salesforce API calls
|
|
13
|
+
do_request: Call an Salesforce REST API in a safe way
|
|
11
14
|
parse_request_response: Parse the REST API responses and convert
|
|
12
15
|
them to Python dict in a safe way
|
|
13
16
|
exist_result_item: Check if an dict item is in the response
|
|
14
17
|
of the Salesforce API call
|
|
15
18
|
get_result_value: Check if a defined value (based on a key) is in the Salesforce API response
|
|
16
19
|
|
|
17
|
-
authenticate
|
|
18
|
-
|
|
19
|
-
get_user: Get a Salesforce user based on its ID.
|
|
20
|
-
add_user: Add a new Salesforce user.
|
|
20
|
+
authenticate: Authenticates at Salesforce API
|
|
21
21
|
|
|
22
|
+
get_object_id_by_name: Get the ID of a given Salesforce object with a given type and name
|
|
22
23
|
get_object: Get a Salesforce object based on a defined
|
|
23
24
|
field value and return selected result fields.
|
|
24
25
|
add_object: Add object to Salesforce. This is a generic wrapper method
|
|
25
26
|
for the actual add methods.
|
|
27
|
+
|
|
28
|
+
get_group: Get a Salesforce group based on its ID.
|
|
29
|
+
add_group: Add a new Salesforce group.
|
|
30
|
+
update_group: Update a Salesforce group.
|
|
31
|
+
get_group_members: Get Salesforce group members
|
|
32
|
+
add_group_member: Add a user or group to a Salesforce group
|
|
33
|
+
|
|
34
|
+
get_all_user_profiles: Get all user profiles
|
|
35
|
+
get_user_profile_id: Get a user profile ID by profile name
|
|
36
|
+
get_user_id: Get a user ID by user name
|
|
37
|
+
get_user: Get a Salesforce user based on its ID.
|
|
38
|
+
add_user: Add a new Salesforce user.
|
|
39
|
+
update_user: Update a Salesforce user.
|
|
40
|
+
update_user_password: Update the password of a Salesforce user.
|
|
41
|
+
update_user_photo: update the Salesforce user photo.
|
|
42
|
+
|
|
26
43
|
add_account: Add a new Account object to Salesforce.
|
|
27
44
|
add_product: Add a new Product object to Salesforce.
|
|
28
45
|
add_opportunity: Add a new Opportunity object to Salesfoce.
|
|
@@ -38,20 +55,28 @@ __credits__ = ["Kai-Philip Gatzweiler"]
|
|
|
38
55
|
__maintainer__ = "Dr. Marc Diefenbruch"
|
|
39
56
|
__email__ = "mdiefenb@opentext.com"
|
|
40
57
|
|
|
58
|
+
import os
|
|
41
59
|
import json
|
|
42
60
|
import logging
|
|
61
|
+
import time
|
|
43
62
|
|
|
44
63
|
from typing import Optional, Union, Any
|
|
64
|
+
|
|
65
|
+
from http import HTTPStatus
|
|
45
66
|
import requests
|
|
46
67
|
|
|
47
68
|
logger = logging.getLogger("pyxecm.customizer.salesforce")
|
|
48
69
|
|
|
49
|
-
|
|
70
|
+
REQUEST_LOGIN_HEADERS = {
|
|
50
71
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
51
72
|
"Accept": "application/json",
|
|
52
73
|
}
|
|
53
74
|
|
|
54
75
|
REQUEST_TIMEOUT = 60
|
|
76
|
+
REQUEST_RETRY_DELAY = 20
|
|
77
|
+
REQUEST_MAX_RETRIES = 3
|
|
78
|
+
|
|
79
|
+
SALESFORCE_API_VERSION = "v60.0"
|
|
55
80
|
|
|
56
81
|
class Salesforce(object):
|
|
57
82
|
"""Used to retrieve and automate stettings in Salesforce."""
|
|
@@ -84,21 +109,56 @@ class Salesforce(object):
|
|
|
84
109
|
security_token (str, optional): security token for Salesforce login
|
|
85
110
|
"""
|
|
86
111
|
|
|
112
|
+
# The instance URL is also returned by the authenticate call
|
|
113
|
+
# but typically it is identical to the base_url.
|
|
114
|
+
self._instance_url = base_url
|
|
115
|
+
|
|
87
116
|
salesforce_config = {}
|
|
88
117
|
|
|
89
|
-
#
|
|
90
|
-
salesforce_config["baseUrl"] = base_url
|
|
118
|
+
# Store the credentials and parameters in a config dictionary:
|
|
91
119
|
salesforce_config["clientId"] = client_id
|
|
92
120
|
salesforce_config["clientSecret"] = client_secret
|
|
93
121
|
salesforce_config["username"] = username
|
|
94
122
|
salesforce_config["password"] = password
|
|
95
123
|
salesforce_config["securityToken"] = security_token
|
|
124
|
+
|
|
125
|
+
# Set the Salesforce URLs and REST API endpoints:
|
|
126
|
+
salesforce_config["baseUrl"] = base_url
|
|
127
|
+
salesforce_config["objectUrl"] = salesforce_config[
|
|
128
|
+
"baseUrl"
|
|
129
|
+
] + "/services/data/{}/sobjects/".format(SALESFORCE_API_VERSION)
|
|
130
|
+
salesforce_config["queryUrl"] = salesforce_config[
|
|
131
|
+
"baseUrl"
|
|
132
|
+
] + "/services/data/{}/query/".format(SALESFORCE_API_VERSION)
|
|
133
|
+
salesforce_config["compositeUrl"] = salesforce_config[
|
|
134
|
+
"baseUrl"
|
|
135
|
+
] + "/services/data/{}/composite/".format(SALESFORCE_API_VERSION)
|
|
136
|
+
salesforce_config["connectUrl"] = salesforce_config[
|
|
137
|
+
"baseUrl"
|
|
138
|
+
] + "/services/data/{}/connect/".format(SALESFORCE_API_VERSION)
|
|
139
|
+
salesforce_config["toolingUrl"] = salesforce_config[
|
|
140
|
+
"baseUrl"
|
|
141
|
+
] + "/services/data/{}/tooling/".format(SALESFORCE_API_VERSION)
|
|
96
142
|
if authorization_url:
|
|
97
143
|
salesforce_config["authenticationUrl"] = authorization_url
|
|
98
144
|
else:
|
|
99
145
|
salesforce_config["authenticationUrl"] = (
|
|
100
146
|
salesforce_config["baseUrl"] + "/services/oauth2/token"
|
|
101
147
|
)
|
|
148
|
+
# URLs that are based on the objectURL (sobjects/):
|
|
149
|
+
salesforce_config["userUrl"] = salesforce_config["objectUrl"] + "User/"
|
|
150
|
+
salesforce_config["groupUrl"] = salesforce_config["objectUrl"] + "Group/"
|
|
151
|
+
salesforce_config["groupMemberUrl"] = (
|
|
152
|
+
salesforce_config["objectUrl"] + "GroupMember/"
|
|
153
|
+
)
|
|
154
|
+
salesforce_config["accountUrl"] = salesforce_config["objectUrl"] + "Account/"
|
|
155
|
+
salesforce_config["productUrl"] = salesforce_config["objectUrl"] + "Product2/"
|
|
156
|
+
salesforce_config["opportunityUrl"] = (
|
|
157
|
+
salesforce_config["objectUrl"] + "Opportunity/"
|
|
158
|
+
)
|
|
159
|
+
salesforce_config["caseUrl"] = salesforce_config["objectUrl"] + "Case/"
|
|
160
|
+
salesforce_config["assetUrl"] = salesforce_config["objectUrl"] + "Asset/"
|
|
161
|
+
salesforce_config["contractUrl"] = salesforce_config["objectUrl"] + "Contract/"
|
|
102
162
|
|
|
103
163
|
# Set the data for the token request
|
|
104
164
|
salesforce_config["authenticationData"] = {
|
|
@@ -111,6 +171,8 @@ class Salesforce(object):
|
|
|
111
171
|
|
|
112
172
|
self._config = salesforce_config
|
|
113
173
|
|
|
174
|
+
# end method definition
|
|
175
|
+
|
|
114
176
|
def config(self) -> dict:
|
|
115
177
|
"""Returns the configuration dictionary
|
|
116
178
|
|
|
@@ -143,12 +205,187 @@ class Salesforce(object):
|
|
|
143
205
|
|
|
144
206
|
request_header = {
|
|
145
207
|
"Authorization": "Bearer {}".format(self._access_token),
|
|
146
|
-
"Content-Type": content_type,
|
|
147
208
|
}
|
|
209
|
+
if content_type:
|
|
210
|
+
request_header["Content-Type"] = content_type
|
|
211
|
+
|
|
148
212
|
return request_header
|
|
149
213
|
|
|
150
214
|
# end method definition
|
|
151
215
|
|
|
216
|
+
def do_request(
|
|
217
|
+
self,
|
|
218
|
+
url: str,
|
|
219
|
+
method: str = "GET",
|
|
220
|
+
headers: dict | None = None,
|
|
221
|
+
data: dict | None = None,
|
|
222
|
+
json_data: dict | None = None,
|
|
223
|
+
files: dict | None = None,
|
|
224
|
+
params: dict | None = None,
|
|
225
|
+
timeout: int | None = REQUEST_TIMEOUT,
|
|
226
|
+
show_error: bool = True,
|
|
227
|
+
show_warning: bool = False,
|
|
228
|
+
warning_message: str = "",
|
|
229
|
+
failure_message: str = "",
|
|
230
|
+
success_message: str = "",
|
|
231
|
+
max_retries: int = REQUEST_MAX_RETRIES,
|
|
232
|
+
retry_forever: bool = False,
|
|
233
|
+
parse_request_response: bool = True,
|
|
234
|
+
stream: bool = False,
|
|
235
|
+
verify: bool = True,
|
|
236
|
+
) -> dict | None:
|
|
237
|
+
"""Call an Salesforce REST API in a safe way
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
url (str): URL to send the request to.
|
|
241
|
+
method (str, optional): HTTP method (GET, POST, etc.). Defaults to "GET".
|
|
242
|
+
headers (dict | None, optional): Request Headers. Defaults to None.
|
|
243
|
+
data (dict | None, optional): Request payload. Defaults to None
|
|
244
|
+
files (dict | None, optional): Dictionary of {"name": file-tuple} for multipart encoding upload.
|
|
245
|
+
file-tuple can be a 2-tuple ("filename", fileobj) or a 3-tuple ("filename", fileobj, "content_type")
|
|
246
|
+
params (dict | None, optional): Add key-value pairs to the query string of the URL.
|
|
247
|
+
When you use the params parameter, requests automatically appends
|
|
248
|
+
the key-value pairs to the URL as part of the query string
|
|
249
|
+
timeout (int | None, optional): Timeout for the request in seconds. Defaults to REQUEST_TIMEOUT.
|
|
250
|
+
show_error (bool, optional): Whether or not an error should be logged in case of a failed REST call.
|
|
251
|
+
If False, then only a warning is logged. Defaults to True.
|
|
252
|
+
warning_message (str, optional): Specific warning message. Defaults to "". If not given the error_message will be used.
|
|
253
|
+
failure_message (str, optional): Specific error message. Defaults to "".
|
|
254
|
+
success_message (str, optional): Specific success message. Defaults to "".
|
|
255
|
+
max_retries (int, optional): How many retries on Connection errors? Default is REQUEST_MAX_RETRIES.
|
|
256
|
+
retry_forever (bool, optional): Eventually wait forever - without timeout. Defaults to False.
|
|
257
|
+
parse_request_response (bool, optional): should the response.text be interpreted as json and loaded into a dictionary. True is the default.
|
|
258
|
+
stream (bool, optional): parameter is used to control whether the response content should be immediately downloaded or streamed incrementally
|
|
259
|
+
verify (bool, optional): specify whether or not SSL certificates should be verified when making an HTTPS request. Default = True
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
dict | None: Response of OTDS REST API or None in case of an error.
|
|
263
|
+
"""
|
|
264
|
+
|
|
265
|
+
if headers is None:
|
|
266
|
+
logger.error("Missing request header. Cannot send request to Core Share!")
|
|
267
|
+
return None
|
|
268
|
+
|
|
269
|
+
# In case of an expired session we reauthenticate and
|
|
270
|
+
# try 1 more time. Session expiration should not happen
|
|
271
|
+
# twice in a row:
|
|
272
|
+
retries = 0
|
|
273
|
+
|
|
274
|
+
while True:
|
|
275
|
+
try:
|
|
276
|
+
response = requests.request(
|
|
277
|
+
method=method,
|
|
278
|
+
url=url,
|
|
279
|
+
data=data,
|
|
280
|
+
json=json_data,
|
|
281
|
+
files=files,
|
|
282
|
+
params=params,
|
|
283
|
+
headers=headers,
|
|
284
|
+
timeout=timeout,
|
|
285
|
+
stream=stream,
|
|
286
|
+
verify=verify,
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
if response.ok:
|
|
290
|
+
if success_message:
|
|
291
|
+
logger.info(success_message)
|
|
292
|
+
if parse_request_response:
|
|
293
|
+
return self.parse_request_response(response)
|
|
294
|
+
else:
|
|
295
|
+
return response
|
|
296
|
+
# Check if Session has expired - then re-authenticate and try once more
|
|
297
|
+
elif response.status_code == 401 and retries == 0:
|
|
298
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
299
|
+
self.authenticate(revalidate=True)
|
|
300
|
+
# Make sure to not change an existing content type
|
|
301
|
+
# the do_request() method is called with:
|
|
302
|
+
headers = self.request_header(
|
|
303
|
+
content_type=headers.get("Content-Type", None)
|
|
304
|
+
)
|
|
305
|
+
retries += 1
|
|
306
|
+
else:
|
|
307
|
+
# Handle plain HTML responses to not pollute the logs
|
|
308
|
+
content_type = response.headers.get("content-type", None)
|
|
309
|
+
if content_type == "text/html":
|
|
310
|
+
response_text = "HTML content (only printed in debug log)"
|
|
311
|
+
else:
|
|
312
|
+
response_text = response.text
|
|
313
|
+
|
|
314
|
+
if show_error:
|
|
315
|
+
logger.error(
|
|
316
|
+
"%s; status -> %s/%s; error -> %s",
|
|
317
|
+
failure_message,
|
|
318
|
+
response.status_code,
|
|
319
|
+
HTTPStatus(response.status_code).phrase,
|
|
320
|
+
response_text,
|
|
321
|
+
)
|
|
322
|
+
elif show_warning:
|
|
323
|
+
logger.warning(
|
|
324
|
+
"%s; status -> %s/%s; warning -> %s",
|
|
325
|
+
warning_message if warning_message else failure_message,
|
|
326
|
+
response.status_code,
|
|
327
|
+
HTTPStatus(response.status_code).phrase,
|
|
328
|
+
response_text,
|
|
329
|
+
)
|
|
330
|
+
if content_type == "text/html":
|
|
331
|
+
logger.debug(
|
|
332
|
+
"%s; status -> %s/%s; warning -> %s",
|
|
333
|
+
failure_message,
|
|
334
|
+
response.status_code,
|
|
335
|
+
HTTPStatus(response.status_code).phrase,
|
|
336
|
+
response.text,
|
|
337
|
+
)
|
|
338
|
+
return None
|
|
339
|
+
except requests.exceptions.Timeout:
|
|
340
|
+
if retries <= max_retries:
|
|
341
|
+
logger.warning(
|
|
342
|
+
"Request timed out. Retrying in %s seconds...",
|
|
343
|
+
str(REQUEST_RETRY_DELAY),
|
|
344
|
+
)
|
|
345
|
+
retries += 1
|
|
346
|
+
time.sleep(REQUEST_RETRY_DELAY) # Add a delay before retrying
|
|
347
|
+
else:
|
|
348
|
+
logger.error(
|
|
349
|
+
"%s; timeout error",
|
|
350
|
+
failure_message,
|
|
351
|
+
)
|
|
352
|
+
if retry_forever:
|
|
353
|
+
# If it fails after REQUEST_MAX_RETRIES retries we let it wait forever
|
|
354
|
+
logger.warning("Turn timeouts off and wait forever...")
|
|
355
|
+
timeout = None
|
|
356
|
+
else:
|
|
357
|
+
return None
|
|
358
|
+
except requests.exceptions.ConnectionError:
|
|
359
|
+
if retries <= max_retries:
|
|
360
|
+
logger.warning(
|
|
361
|
+
"Connection error. Retrying in %s seconds...",
|
|
362
|
+
str(REQUEST_RETRY_DELAY),
|
|
363
|
+
)
|
|
364
|
+
retries += 1
|
|
365
|
+
time.sleep(REQUEST_RETRY_DELAY) # Add a delay before retrying
|
|
366
|
+
else:
|
|
367
|
+
logger.error(
|
|
368
|
+
"%s; connection error",
|
|
369
|
+
failure_message,
|
|
370
|
+
)
|
|
371
|
+
if retry_forever:
|
|
372
|
+
# If it fails after REQUEST_MAX_RETRIES retries we let it wait forever
|
|
373
|
+
logger.warning("Turn timeouts off and wait forever...")
|
|
374
|
+
timeout = None
|
|
375
|
+
time.sleep(REQUEST_RETRY_DELAY) # Add a delay before retrying
|
|
376
|
+
else:
|
|
377
|
+
return None
|
|
378
|
+
# end try
|
|
379
|
+
logger.debug(
|
|
380
|
+
"Retrying REST API %s call -> %s... (retry = %s)",
|
|
381
|
+
method,
|
|
382
|
+
url,
|
|
383
|
+
str(retries),
|
|
384
|
+
)
|
|
385
|
+
# end while True
|
|
386
|
+
|
|
387
|
+
# end method definition
|
|
388
|
+
|
|
152
389
|
def parse_request_response(
|
|
153
390
|
self,
|
|
154
391
|
response_object: requests.Response,
|
|
@@ -278,16 +515,16 @@ class Salesforce(object):
|
|
|
278
515
|
|
|
279
516
|
# Already authenticated and session still valid?
|
|
280
517
|
if self._access_token and not revalidate:
|
|
281
|
-
logger.
|
|
518
|
+
logger.debug(
|
|
282
519
|
"Session still valid - return existing access token -> %s",
|
|
283
520
|
str(self._access_token),
|
|
284
521
|
)
|
|
285
522
|
return self._access_token
|
|
286
523
|
|
|
287
524
|
request_url = self.config()["authenticationUrl"]
|
|
288
|
-
request_header =
|
|
525
|
+
request_header = REQUEST_LOGIN_HEADERS
|
|
289
526
|
|
|
290
|
-
logger.
|
|
527
|
+
logger.debug("Requesting Salesforce Access Token from -> %s", request_url)
|
|
291
528
|
|
|
292
529
|
authenticate_post_body = self.credentials()
|
|
293
530
|
|
|
@@ -346,70 +583,27 @@ class Salesforce(object):
|
|
|
346
583
|
"""
|
|
347
584
|
|
|
348
585
|
if not self._access_token or not self._instance_url:
|
|
349
|
-
|
|
350
|
-
return None
|
|
586
|
+
self.authenticate()
|
|
351
587
|
|
|
352
588
|
request_header = self.request_header()
|
|
353
|
-
request_url =
|
|
589
|
+
request_url = self.config()["queryUrl"]
|
|
354
590
|
|
|
355
591
|
query = f"SELECT Id FROM {object_type} WHERE {name_field} = '{name}'"
|
|
356
592
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
response = self.parse_request_response(response)
|
|
367
|
-
object_id = self.get_result_value(response, "Id")
|
|
368
|
-
return object_id
|
|
369
|
-
elif response.status_code == 401 and retries == 0:
|
|
370
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
371
|
-
self.authenticate(revalidate=True)
|
|
372
|
-
request_header = self.request_header()
|
|
373
|
-
retries += 1
|
|
374
|
-
else:
|
|
375
|
-
logger.error(
|
|
376
|
-
"Failed to get Salesforce object ID for object type -> %s and object name -> %s; status -> %s; error -> %s",
|
|
377
|
-
object_type,
|
|
378
|
-
name,
|
|
379
|
-
response.status_code,
|
|
380
|
-
response.text,
|
|
381
|
-
)
|
|
382
|
-
return None
|
|
383
|
-
|
|
384
|
-
# end method definition
|
|
385
|
-
|
|
386
|
-
def get_profile_id(self, profile_name: str) -> Optional[str]:
|
|
387
|
-
"""Get a user profile ID by profile name.
|
|
388
|
-
|
|
389
|
-
Args:
|
|
390
|
-
profile_name (str): Name of the User Profile.
|
|
391
|
-
|
|
392
|
-
Returns:
|
|
393
|
-
Optional[str]: Technical ID of the user profile.
|
|
394
|
-
"""
|
|
395
|
-
|
|
396
|
-
return self.get_object_id_by_name(object_type="Profile", name=profile_name)
|
|
397
|
-
|
|
398
|
-
# end method definition
|
|
399
|
-
|
|
400
|
-
def get_user_id(self, username: str) -> Optional[str]:
|
|
401
|
-
"""Get a user ID by user name.
|
|
402
|
-
|
|
403
|
-
Args:
|
|
404
|
-
username (str): Name of the User.
|
|
405
|
-
|
|
406
|
-
Returns:
|
|
407
|
-
Optional[str]: Technical ID of the user
|
|
408
|
-
"""
|
|
409
|
-
|
|
410
|
-
return self.get_object_id_by_name(
|
|
411
|
-
object_type="User", name=username, name_field="Username"
|
|
593
|
+
response = self.do_request(
|
|
594
|
+
method="GET",
|
|
595
|
+
url=request_url,
|
|
596
|
+
headers=request_header,
|
|
597
|
+
params={"q": query},
|
|
598
|
+
timeout=REQUEST_TIMEOUT,
|
|
599
|
+
failure_message="Failed to get Salesforce object ID for object type -> '{}' and object name -> '{}'".format(
|
|
600
|
+
object_type, name
|
|
601
|
+
),
|
|
412
602
|
)
|
|
603
|
+
if not response:
|
|
604
|
+
return None
|
|
605
|
+
|
|
606
|
+
return self.get_result_value(response, "Id")
|
|
413
607
|
|
|
414
608
|
# end method definition
|
|
415
609
|
|
|
@@ -433,11 +627,33 @@ class Salesforce(object):
|
|
|
433
627
|
|
|
434
628
|
Returns:
|
|
435
629
|
dict | None: Dictionary with the Salesforce object data.
|
|
630
|
+
|
|
631
|
+
Example response:
|
|
632
|
+
{
|
|
633
|
+
'totalSize': 2,
|
|
634
|
+
'done': True,
|
|
635
|
+
'records': [
|
|
636
|
+
{
|
|
637
|
+
'attributes': {
|
|
638
|
+
'type': 'Opportunity',
|
|
639
|
+
'url': '/services/data/v60.0/sobjects/Opportunity/006Dn00000EclybIAB'
|
|
640
|
+
},
|
|
641
|
+
'Id': '006Dn00000EclybIAB'
|
|
642
|
+
},
|
|
643
|
+
{
|
|
644
|
+
'attributes': {
|
|
645
|
+
'type': 'Opportunity',
|
|
646
|
+
'url': '/services/data/v60.0/sobjects/Opportunity/006Dn00000EclyfIAB'
|
|
647
|
+
},
|
|
648
|
+
'Id': '006Dn00000EclyfIAB'
|
|
649
|
+
}
|
|
650
|
+
]
|
|
651
|
+
}
|
|
436
652
|
"""
|
|
437
653
|
|
|
438
654
|
if not self._access_token or not self._instance_url:
|
|
439
|
-
|
|
440
|
-
|
|
655
|
+
self.authenticate()
|
|
656
|
+
|
|
441
657
|
if search_field and not search_value:
|
|
442
658
|
logger.error(
|
|
443
659
|
"No search value has been provided for search field -> %s!",
|
|
@@ -445,7 +661,7 @@ class Salesforce(object):
|
|
|
445
661
|
)
|
|
446
662
|
return None
|
|
447
663
|
if not result_fields:
|
|
448
|
-
logger.
|
|
664
|
+
logger.debug(
|
|
449
665
|
"No result fields defined. Using 'FIELDS(STANDARD)' to deliver all standard fields of the object."
|
|
450
666
|
)
|
|
451
667
|
result_fields = ["FIELDS(STANDARD)"]
|
|
@@ -456,32 +672,21 @@ class Salesforce(object):
|
|
|
456
672
|
query += " LIMIT {}".format(str(limit))
|
|
457
673
|
|
|
458
674
|
request_header = self.request_header()
|
|
459
|
-
request_url =
|
|
675
|
+
request_url = self.config()["queryUrl"] + "?q={}".format(query)
|
|
460
676
|
|
|
461
|
-
logger.
|
|
677
|
+
logger.debug(
|
|
462
678
|
"Sending query -> %s to Salesforce; calling -> %s", query, request_url
|
|
463
679
|
)
|
|
464
680
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
retries += 1
|
|
475
|
-
else:
|
|
476
|
-
logger.error(
|
|
477
|
-
"Failed to retrieve Salesforce object -> %s with %s = %s; status -> %s; error -> %s",
|
|
478
|
-
object_type,
|
|
479
|
-
search_field,
|
|
480
|
-
search_value,
|
|
481
|
-
response.status_code,
|
|
482
|
-
response.text,
|
|
483
|
-
)
|
|
484
|
-
return None
|
|
681
|
+
return self.do_request(
|
|
682
|
+
method="GET",
|
|
683
|
+
url=request_url,
|
|
684
|
+
headers=request_header,
|
|
685
|
+
timeout=REQUEST_TIMEOUT,
|
|
686
|
+
failure_message="Failed to retrieve Salesforce object type -> '{}' with {} = {}".format(
|
|
687
|
+
object_type, search_field, search_value
|
|
688
|
+
),
|
|
689
|
+
)
|
|
485
690
|
|
|
486
691
|
# end method definition
|
|
487
692
|
|
|
@@ -491,9 +696,10 @@ class Salesforce(object):
|
|
|
491
696
|
|
|
492
697
|
Args:
|
|
493
698
|
object_type (str): Type of the Salesforce business object, like "Account" or "Case".
|
|
699
|
+
**kwargs (dict): keyword / value ictionary with additional parameters
|
|
494
700
|
|
|
495
701
|
Returns:
|
|
496
|
-
dict | None: Dictionary with the Salesforce
|
|
702
|
+
dict | None: Dictionary with the Salesforce object data or None if the request fails.
|
|
497
703
|
"""
|
|
498
704
|
|
|
499
705
|
match object_type:
|
|
@@ -568,49 +774,237 @@ class Salesforce(object):
|
|
|
568
774
|
|
|
569
775
|
# end method definition
|
|
570
776
|
|
|
571
|
-
def
|
|
572
|
-
"""Get a
|
|
777
|
+
def get_group_id(self, groupname: str) -> Optional[str]:
|
|
778
|
+
"""Get a group ID by group name.
|
|
573
779
|
|
|
574
780
|
Args:
|
|
575
|
-
|
|
781
|
+
groupname (str): Name of the Group.
|
|
576
782
|
|
|
577
783
|
Returns:
|
|
578
|
-
|
|
784
|
+
Optional[str]: Technical ID of the group
|
|
785
|
+
"""
|
|
786
|
+
|
|
787
|
+
return self.get_object_id_by_name(
|
|
788
|
+
object_type="Group", name=groupname, name_field="Name"
|
|
789
|
+
)
|
|
790
|
+
|
|
791
|
+
# end method definition
|
|
792
|
+
|
|
793
|
+
def get_group(self, group_id: str) -> dict | None:
|
|
794
|
+
"""Get a Salesforce group based on its ID.
|
|
795
|
+
|
|
796
|
+
Args:
|
|
797
|
+
group_id (str): ID of the Salesforce group
|
|
798
|
+
|
|
799
|
+
Returns:
|
|
800
|
+
dict | None: Dictionary with the Salesforce group data or None if the request fails.
|
|
579
801
|
"""
|
|
580
802
|
|
|
581
803
|
if not self._access_token or not self._instance_url:
|
|
582
|
-
|
|
583
|
-
return None
|
|
804
|
+
self.authenticate()
|
|
584
805
|
|
|
585
806
|
request_header = self.request_header()
|
|
586
|
-
request_url = (
|
|
587
|
-
|
|
807
|
+
request_url = self.config()["groupUrl"] + group_id
|
|
808
|
+
|
|
809
|
+
logger.debug(
|
|
810
|
+
"Get Salesforce group with ID -> %s; calling -> %s", group_id, request_url
|
|
588
811
|
)
|
|
589
812
|
|
|
590
|
-
|
|
591
|
-
"
|
|
813
|
+
return self.do_request(
|
|
814
|
+
method="GET",
|
|
815
|
+
url=request_url,
|
|
816
|
+
headers=request_header,
|
|
817
|
+
timeout=REQUEST_TIMEOUT,
|
|
818
|
+
failure_message="Failed to get Salesforce group with ID -> {}".format(
|
|
819
|
+
group_id
|
|
820
|
+
),
|
|
592
821
|
)
|
|
593
822
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
823
|
+
# end method definition
|
|
824
|
+
|
|
825
|
+
def add_group(
|
|
826
|
+
self,
|
|
827
|
+
group_name: str,
|
|
828
|
+
group_type: str = "Regular",
|
|
829
|
+
) -> dict | None:
|
|
830
|
+
"""Add a new Salesforce group.
|
|
831
|
+
|
|
832
|
+
Args:
|
|
833
|
+
group_name (str): Name of the new Salesforce group
|
|
834
|
+
|
|
835
|
+
Returns:
|
|
836
|
+
dict | None: Dictionary with the Salesforce Group data or None if the request fails.
|
|
837
|
+
|
|
838
|
+
Example response:
|
|
839
|
+
{
|
|
840
|
+
'id': '00GDn000000KWE0MAO',
|
|
841
|
+
'success': True,
|
|
842
|
+
'errors': []
|
|
843
|
+
}
|
|
844
|
+
"""
|
|
845
|
+
|
|
846
|
+
if not self._access_token or not self._instance_url:
|
|
847
|
+
self.authenticate()
|
|
848
|
+
|
|
849
|
+
request_header = self.request_header()
|
|
850
|
+
request_url = self.config()["groupUrl"]
|
|
851
|
+
|
|
852
|
+
payload = {"Name": group_name, "Type": group_type}
|
|
853
|
+
|
|
854
|
+
logger.debug(
|
|
855
|
+
"Adding Salesforce group -> %s; calling -> %s", group_name, request_url
|
|
856
|
+
)
|
|
857
|
+
|
|
858
|
+
return self.do_request(
|
|
859
|
+
method="POST",
|
|
860
|
+
url=request_url,
|
|
861
|
+
headers=request_header,
|
|
862
|
+
data=json.dumps(payload),
|
|
863
|
+
timeout=REQUEST_TIMEOUT,
|
|
864
|
+
failure_message="Failed to add Salesforce group -> '{}'".format(group_name),
|
|
865
|
+
)
|
|
866
|
+
|
|
867
|
+
# end method definition
|
|
868
|
+
|
|
869
|
+
def update_group(
|
|
870
|
+
self,
|
|
871
|
+
group_id: str,
|
|
872
|
+
update_data: dict,
|
|
873
|
+
) -> dict | None:
|
|
874
|
+
"""Update a Salesforce group.
|
|
875
|
+
|
|
876
|
+
Args:
|
|
877
|
+
group_id (str): The Salesforce group ID.
|
|
878
|
+
update_data (dict): Dictionary containing the fields to update.
|
|
879
|
+
|
|
880
|
+
Returns:
|
|
881
|
+
dict: Response from the Salesforce API.
|
|
882
|
+
"""
|
|
883
|
+
|
|
884
|
+
if not self._access_token or not self._instance_url:
|
|
885
|
+
self.authenticate()
|
|
886
|
+
|
|
887
|
+
request_header = self.request_header()
|
|
888
|
+
|
|
889
|
+
request_url = self.config()["groupUrl"] + group_id
|
|
890
|
+
|
|
891
|
+
logger.debug(
|
|
892
|
+
"Update Salesforce group with ID -> %s; calling -> %s",
|
|
893
|
+
group_id,
|
|
894
|
+
request_url,
|
|
895
|
+
)
|
|
896
|
+
|
|
897
|
+
return self.do_request(
|
|
898
|
+
method="PATCH",
|
|
899
|
+
url=request_url,
|
|
900
|
+
headers=request_header,
|
|
901
|
+
json_data=update_data,
|
|
902
|
+
timeout=REQUEST_TIMEOUT,
|
|
903
|
+
failure_message="Failed to update Salesforce group with ID -> {}".format(
|
|
904
|
+
group_id
|
|
905
|
+
),
|
|
906
|
+
)
|
|
907
|
+
|
|
908
|
+
# end method definition
|
|
909
|
+
|
|
910
|
+
def get_group_members(self, group_id: str) -> list | None:
|
|
911
|
+
"""Get Salesforce group members
|
|
912
|
+
|
|
913
|
+
Args:
|
|
914
|
+
group_id (str): Id of the group to retrieve the members
|
|
915
|
+
|
|
916
|
+
Returns:
|
|
917
|
+
list | None: result
|
|
918
|
+
|
|
919
|
+
Example response:
|
|
920
|
+
{
|
|
921
|
+
'totalSize': 1,
|
|
922
|
+
'done': True,
|
|
923
|
+
'records': [
|
|
924
|
+
{
|
|
925
|
+
'attributes': {
|
|
926
|
+
'type': 'GroupMember',
|
|
927
|
+
'url': '/services/data/v60.0/sobjects/GroupMember/011Dn000000ELhwIAG'
|
|
928
|
+
},
|
|
929
|
+
'UserOrGroupId': '00GDn000000KWE5MAO'
|
|
930
|
+
}
|
|
931
|
+
]
|
|
932
|
+
}
|
|
933
|
+
"""
|
|
934
|
+
|
|
935
|
+
if not self._access_token or not self._instance_url:
|
|
936
|
+
self.authenticate()
|
|
937
|
+
|
|
938
|
+
request_header = self.request_header()
|
|
939
|
+
|
|
940
|
+
request_url = self.config()["queryUrl"]
|
|
941
|
+
|
|
942
|
+
query = f"SELECT UserOrGroupId FROM GroupMember WHERE GroupId = '{group_id}'"
|
|
943
|
+
params = {"q": query}
|
|
944
|
+
|
|
945
|
+
logger.debug(
|
|
946
|
+
"Get members of Salesforce group with ID -> %s; calling -> %s",
|
|
947
|
+
group_id,
|
|
948
|
+
request_url,
|
|
949
|
+
)
|
|
950
|
+
|
|
951
|
+
return self.do_request(
|
|
952
|
+
method="GET",
|
|
953
|
+
url=request_url,
|
|
954
|
+
headers=request_header,
|
|
955
|
+
params=params,
|
|
956
|
+
timeout=REQUEST_TIMEOUT,
|
|
957
|
+
failure_message="Failed to get members of Salesforce group with ID -> {}".format(
|
|
958
|
+
group_id
|
|
959
|
+
),
|
|
960
|
+
)
|
|
961
|
+
|
|
962
|
+
# end method definition
|
|
963
|
+
|
|
964
|
+
def add_group_member(self, group_id: str, member_id: str) -> dict | None:
|
|
965
|
+
"""Add a user or group to a Salesforce group
|
|
966
|
+
|
|
967
|
+
Args:
|
|
968
|
+
group_id (str): ID of the Salesforce Group to add member to.
|
|
969
|
+
member_id (str): ID of the user or group.
|
|
970
|
+
|
|
971
|
+
Returns:
|
|
972
|
+
dict | None: Dictionary with the Salesforce membership data or None if the request fails.
|
|
973
|
+
|
|
974
|
+
Example response (id is the membership ID):
|
|
975
|
+
{
|
|
976
|
+
'id': '011Dn000000ELhwIAG',
|
|
977
|
+
'success': True,
|
|
978
|
+
'errors': []
|
|
979
|
+
}
|
|
980
|
+
"""
|
|
981
|
+
|
|
982
|
+
if not self._access_token or not self._instance_url:
|
|
983
|
+
self.authenticate()
|
|
984
|
+
|
|
985
|
+
request_url = self.config()["groupMemberUrl"]
|
|
986
|
+
|
|
987
|
+
request_header = self.request_header()
|
|
988
|
+
|
|
989
|
+
payload = {"GroupId": group_id, "UserOrGroupId": member_id}
|
|
990
|
+
|
|
991
|
+
logger.debug(
|
|
992
|
+
"Add member with ID -> %s to Salesforce group with ID -> %s; calling -> %s",
|
|
993
|
+
member_id,
|
|
994
|
+
group_id,
|
|
995
|
+
request_url,
|
|
996
|
+
)
|
|
997
|
+
|
|
998
|
+
return self.do_request(
|
|
999
|
+
method="POST",
|
|
1000
|
+
url=request_url,
|
|
1001
|
+
headers=request_header,
|
|
1002
|
+
json_data=payload,
|
|
1003
|
+
timeout=REQUEST_TIMEOUT,
|
|
1004
|
+
failure_message="Failed to add member with ID -> {} to Salesforce group with ID -> {}".format(
|
|
1005
|
+
member_id, group_id
|
|
1006
|
+
),
|
|
1007
|
+
)
|
|
614
1008
|
|
|
615
1009
|
# end method definition
|
|
616
1010
|
|
|
@@ -632,8 +1026,7 @@ class Salesforce(object):
|
|
|
632
1026
|
'url': '/services/data/v52.0/sobjects/Profile/00eDn000001msL8IAI'},
|
|
633
1027
|
'Id': '00eDn000001msL8IAI',
|
|
634
1028
|
'Name': 'Standard User',
|
|
635
|
-
'CreatedById':
|
|
636
|
-
'005Dn000001rRodIAE',
|
|
1029
|
+
'CreatedById': '005Dn000001rRodIAE',
|
|
637
1030
|
'CreatedDate': '2022-11-30T15:30:54.000+0000',
|
|
638
1031
|
'Description': None,
|
|
639
1032
|
'LastModifiedById': '005Dn000001rUacIAE',
|
|
@@ -648,36 +1041,83 @@ class Salesforce(object):
|
|
|
648
1041
|
"""
|
|
649
1042
|
|
|
650
1043
|
if not self._access_token or not self._instance_url:
|
|
651
|
-
|
|
652
|
-
return None
|
|
1044
|
+
self.authenticate()
|
|
653
1045
|
|
|
654
1046
|
request_header = self.request_header()
|
|
655
|
-
request_url =
|
|
1047
|
+
request_url = self.config()["queryUrl"]
|
|
656
1048
|
|
|
657
1049
|
query = "SELECT Id, Name, CreatedById, CreatedDate, Description, LastModifiedById, LastModifiedDate, PermissionsCustomizeApplication, PermissionsEditTask, PermissionsImportLeads FROM Profile"
|
|
658
1050
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
1051
|
+
return self.do_request(
|
|
1052
|
+
method="GET",
|
|
1053
|
+
url=request_url,
|
|
1054
|
+
headers=request_header,
|
|
1055
|
+
params={"q": query},
|
|
1056
|
+
timeout=REQUEST_TIMEOUT,
|
|
1057
|
+
failure_message="Failed to get Salesforce user profiles",
|
|
1058
|
+
)
|
|
1059
|
+
|
|
1060
|
+
# end method definition
|
|
1061
|
+
|
|
1062
|
+
def get_user_profile_id(self, profile_name: str) -> Optional[str]:
|
|
1063
|
+
"""Get a user profile ID by profile name.
|
|
1064
|
+
|
|
1065
|
+
Args:
|
|
1066
|
+
profile_name (str): Name of the User Profile.
|
|
1067
|
+
|
|
1068
|
+
Returns:
|
|
1069
|
+
Optional[str]: Technical ID of the user profile.
|
|
1070
|
+
"""
|
|
1071
|
+
|
|
1072
|
+
return self.get_object_id_by_name(object_type="Profile", name=profile_name)
|
|
1073
|
+
|
|
1074
|
+
# end method definition
|
|
1075
|
+
|
|
1076
|
+
def get_user_id(self, username: str) -> Optional[str]:
|
|
1077
|
+
"""Get a user ID by user name.
|
|
1078
|
+
|
|
1079
|
+
Args:
|
|
1080
|
+
username (str): Name of the User.
|
|
1081
|
+
|
|
1082
|
+
Returns:
|
|
1083
|
+
Optional[str]: Technical ID of the user
|
|
1084
|
+
"""
|
|
1085
|
+
|
|
1086
|
+
return self.get_object_id_by_name(
|
|
1087
|
+
object_type="User", name=username, name_field="Username"
|
|
1088
|
+
)
|
|
1089
|
+
|
|
1090
|
+
# end method definition
|
|
1091
|
+
|
|
1092
|
+
def get_user(self, user_id: str) -> dict | None:
|
|
1093
|
+
"""Get a Salesforce user based on its ID.
|
|
1094
|
+
|
|
1095
|
+
Args:
|
|
1096
|
+
user_id (str): ID of the Salesforce user
|
|
1097
|
+
|
|
1098
|
+
Returns:
|
|
1099
|
+
dict | None: Dictionary with the Salesforce user data or None if the request fails.
|
|
1100
|
+
"""
|
|
1101
|
+
|
|
1102
|
+
if not self._access_token or not self._instance_url:
|
|
1103
|
+
self.authenticate()
|
|
1104
|
+
|
|
1105
|
+
request_header = self.request_header()
|
|
1106
|
+
request_url = self.config()["userUrl"] + user_id
|
|
1107
|
+
|
|
1108
|
+
logger.debug(
|
|
1109
|
+
"Get Salesforce user with ID -> %s; calling -> %s", user_id, request_url
|
|
1110
|
+
)
|
|
1111
|
+
|
|
1112
|
+
return self.do_request(
|
|
1113
|
+
method="GET",
|
|
1114
|
+
url=request_url,
|
|
1115
|
+
headers=request_header,
|
|
1116
|
+
timeout=REQUEST_TIMEOUT,
|
|
1117
|
+
failure_message="Failed to get Salesforce user with ID -> {}".format(
|
|
1118
|
+
user_id
|
|
1119
|
+
),
|
|
1120
|
+
)
|
|
681
1121
|
|
|
682
1122
|
# end method definition
|
|
683
1123
|
|
|
@@ -685,23 +1125,35 @@ class Salesforce(object):
|
|
|
685
1125
|
self,
|
|
686
1126
|
username: str,
|
|
687
1127
|
email: str,
|
|
688
|
-
password: str,
|
|
689
1128
|
firstname: str,
|
|
690
1129
|
lastname: str,
|
|
1130
|
+
title: str | None = None,
|
|
1131
|
+
department: str | None = None,
|
|
1132
|
+
company_name: str = "Innovate",
|
|
1133
|
+
profile_name: Optional[str] = "Standard User",
|
|
691
1134
|
profile_id: Optional[str] = None,
|
|
1135
|
+
time_zone_key: Optional[str] = "America/Los_Angeles",
|
|
1136
|
+
email_encoding_key: Optional[str] = "ISO-8859-1",
|
|
1137
|
+
locale_key: Optional[str] = "en_US",
|
|
692
1138
|
alias: Optional[str] = None,
|
|
693
1139
|
) -> dict | None:
|
|
694
|
-
"""Add a new Salesforce user.
|
|
1140
|
+
"""Add a new Salesforce user. The password has to be set separately.
|
|
695
1141
|
|
|
696
1142
|
Args:
|
|
697
1143
|
username (str): Login name of the new user
|
|
698
1144
|
email (str): Email of the new user
|
|
699
|
-
password (str): Password of the new user
|
|
700
1145
|
firstname (str): First name of the new user.
|
|
701
1146
|
lastname (str): Last name of the new user.
|
|
1147
|
+
title (str): Title of the user.
|
|
1148
|
+
department (str): Department of the user.
|
|
1149
|
+
company_name (str): Name of the Company of the user.
|
|
1150
|
+
profile_name (str): Profile name like "Standard User"
|
|
702
1151
|
profile_id (str, optional): Profile ID of the new user. Defaults to None.
|
|
703
1152
|
Use method get_all_user_profiles() to determine
|
|
704
|
-
the desired Profile for the user.
|
|
1153
|
+
the desired Profile for the user. Or pass the profile_name.
|
|
1154
|
+
time_zone_key (str, optional) in format country/city like "America/Los_Angeles",
|
|
1155
|
+
email_encoding_key (str, optional). Default is "ISO-8859-1".
|
|
1156
|
+
locale_key (str, optional). Default is "en_US".
|
|
705
1157
|
alias (str, optional): Alias of the new user. Defaults to None.
|
|
706
1158
|
|
|
707
1159
|
Returns:
|
|
@@ -709,49 +1161,193 @@ class Salesforce(object):
|
|
|
709
1161
|
"""
|
|
710
1162
|
|
|
711
1163
|
if not self._access_token or not self._instance_url:
|
|
712
|
-
|
|
713
|
-
return None
|
|
1164
|
+
self.authenticate()
|
|
714
1165
|
|
|
715
1166
|
request_header = self.request_header()
|
|
716
|
-
request_url =
|
|
1167
|
+
request_url = self.config()["userUrl"]
|
|
1168
|
+
|
|
1169
|
+
# if just a profile name is given then we determine the profile ID by the name:
|
|
1170
|
+
if profile_name and not profile_id:
|
|
1171
|
+
profile_id = self.get_user_profile_id(profile_name)
|
|
717
1172
|
|
|
718
1173
|
payload = {
|
|
719
1174
|
"Username": username,
|
|
720
1175
|
"Email": email,
|
|
721
|
-
"Password": password,
|
|
722
1176
|
"FirstName": firstname,
|
|
723
1177
|
"LastName": lastname,
|
|
724
1178
|
"ProfileId": profile_id,
|
|
725
|
-
"
|
|
1179
|
+
"Department": department,
|
|
1180
|
+
"CompanyName": company_name,
|
|
1181
|
+
"Title": title,
|
|
1182
|
+
"Alias": alias if alias else username,
|
|
1183
|
+
"TimeZoneSidKey": time_zone_key, # Set default TimeZoneSidKey
|
|
1184
|
+
"LocaleSidKey": locale_key, # Set default LocaleSidKey
|
|
1185
|
+
"EmailEncodingKey": email_encoding_key, # Set default EmailEncodingKey
|
|
1186
|
+
"LanguageLocaleKey": locale_key, # Set default LanguageLocaleKey
|
|
726
1187
|
}
|
|
727
1188
|
|
|
728
|
-
logger.
|
|
1189
|
+
logger.debug(
|
|
729
1190
|
"Adding Salesforce user -> %s; calling -> %s", username, request_url
|
|
730
1191
|
)
|
|
731
1192
|
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
1193
|
+
return self.do_request(
|
|
1194
|
+
method="POST",
|
|
1195
|
+
url=request_url,
|
|
1196
|
+
headers=request_header,
|
|
1197
|
+
data=json.dumps(payload),
|
|
1198
|
+
timeout=REQUEST_TIMEOUT,
|
|
1199
|
+
failure_message="Failed to add Salesforce user -> {}".format(username),
|
|
1200
|
+
)
|
|
1201
|
+
|
|
1202
|
+
# end method definition
|
|
1203
|
+
|
|
1204
|
+
def update_user(
|
|
1205
|
+
self,
|
|
1206
|
+
user_id: str,
|
|
1207
|
+
update_data: dict,
|
|
1208
|
+
) -> dict:
|
|
1209
|
+
"""Update a Salesforce user.
|
|
1210
|
+
|
|
1211
|
+
Args:
|
|
1212
|
+
user_id (str): The Salesforce user ID.
|
|
1213
|
+
update_data (dict): Dictionary containing the fields to update.
|
|
1214
|
+
|
|
1215
|
+
Returns:
|
|
1216
|
+
dict: Response from the Salesforce API.
|
|
1217
|
+
"""
|
|
1218
|
+
|
|
1219
|
+
if not self._access_token or not self._instance_url:
|
|
1220
|
+
self.authenticate()
|
|
1221
|
+
|
|
1222
|
+
request_header = self.request_header()
|
|
1223
|
+
|
|
1224
|
+
request_url = self.config()["userUrl"] + user_id
|
|
1225
|
+
|
|
1226
|
+
logger.debug(
|
|
1227
|
+
"Update Salesforce user with ID -> %s; calling -> %s", user_id, request_url
|
|
1228
|
+
)
|
|
1229
|
+
|
|
1230
|
+
return self.do_request(
|
|
1231
|
+
method="PATCH",
|
|
1232
|
+
url=request_url,
|
|
1233
|
+
headers=request_header,
|
|
1234
|
+
json_data=update_data,
|
|
1235
|
+
timeout=REQUEST_TIMEOUT,
|
|
1236
|
+
failure_message="Failed to update Salesforce user with ID -> {}".format(
|
|
1237
|
+
user_id
|
|
1238
|
+
),
|
|
1239
|
+
)
|
|
1240
|
+
|
|
1241
|
+
# end method definition
|
|
1242
|
+
|
|
1243
|
+
def update_user_password(
|
|
1244
|
+
self,
|
|
1245
|
+
user_id: str,
|
|
1246
|
+
password: str,
|
|
1247
|
+
) -> dict:
|
|
1248
|
+
"""Update the password of a Salesforce user.
|
|
1249
|
+
|
|
1250
|
+
Args:
|
|
1251
|
+
user_id (str): The Salesforce user ID.
|
|
1252
|
+
password (str): New user password.
|
|
1253
|
+
|
|
1254
|
+
Returns:
|
|
1255
|
+
dict: Response from the Salesforce API.
|
|
1256
|
+
"""
|
|
1257
|
+
|
|
1258
|
+
if not self._access_token or not self._instance_url:
|
|
1259
|
+
self.authenticate()
|
|
1260
|
+
|
|
1261
|
+
request_header = self.request_header()
|
|
1262
|
+
|
|
1263
|
+
request_url = self.config()["userUrl"] + "{}/password".format(user_id)
|
|
1264
|
+
|
|
1265
|
+
logger.debug(
|
|
1266
|
+
"Update password of Salesforce user with ID -> %s; calling -> %s",
|
|
1267
|
+
user_id,
|
|
1268
|
+
request_url,
|
|
1269
|
+
)
|
|
1270
|
+
|
|
1271
|
+
update_data = {"NewPassword": password}
|
|
1272
|
+
|
|
1273
|
+
return self.do_request(
|
|
1274
|
+
method="POST",
|
|
1275
|
+
url=request_url,
|
|
1276
|
+
headers=request_header,
|
|
1277
|
+
json_data=update_data,
|
|
1278
|
+
timeout=REQUEST_TIMEOUT,
|
|
1279
|
+
failure_message="Failed to update password of Salesforce user with ID -> {}".format(
|
|
1280
|
+
user_id
|
|
1281
|
+
),
|
|
1282
|
+
)
|
|
1283
|
+
|
|
1284
|
+
# end method definition
|
|
1285
|
+
|
|
1286
|
+
def update_user_photo(
|
|
1287
|
+
self,
|
|
1288
|
+
user_id: str,
|
|
1289
|
+
photo_path: str,
|
|
1290
|
+
) -> dict | None:
|
|
1291
|
+
"""Update the Salesforce user photo.
|
|
1292
|
+
|
|
1293
|
+
Args:
|
|
1294
|
+
user_id (str): Salesforce ID of the user
|
|
1295
|
+
photo_path (str): file system path with the location of the photo
|
|
1296
|
+
Returns:
|
|
1297
|
+
dict | None: Dictionary with the Salesforce User data or None if the request fails.
|
|
1298
|
+
"""
|
|
1299
|
+
|
|
1300
|
+
if not self._access_token or not self._instance_url:
|
|
1301
|
+
self.authenticate()
|
|
1302
|
+
|
|
1303
|
+
# Check if the photo file exists
|
|
1304
|
+
if not os.path.isfile(photo_path):
|
|
1305
|
+
logger.error("Photo file -> %s not found!", photo_path)
|
|
1306
|
+
return None
|
|
1307
|
+
|
|
1308
|
+
try:
|
|
1309
|
+
# Read the photo file as binary data
|
|
1310
|
+
with open(photo_path, "rb") as image_file:
|
|
1311
|
+
photo_data = image_file.read()
|
|
1312
|
+
except OSError as exception:
|
|
1313
|
+
# Handle any errors that occurred while reading the photo file
|
|
1314
|
+
logger.error(
|
|
1315
|
+
"Error reading photo file -> %s; error -> %s", photo_path, exception
|
|
739
1316
|
)
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
1317
|
+
return None
|
|
1318
|
+
|
|
1319
|
+
# Content Type = None is important as upload calls need
|
|
1320
|
+
# a multipart header that is automatically selected if None is used:
|
|
1321
|
+
request_header = self.request_header(content_type=None)
|
|
1322
|
+
|
|
1323
|
+
data = {"json": json.dumps({"cropX": 0, "cropY": 0, "cropSize": 200})}
|
|
1324
|
+
request_url = self.config()["connectUrl"] + f"user-profiles/{user_id}/photo"
|
|
1325
|
+
files = {
|
|
1326
|
+
"fileUpload": (
|
|
1327
|
+
photo_path,
|
|
1328
|
+
photo_data,
|
|
1329
|
+
"application/octet-stream",
|
|
1330
|
+
)
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
logger.debug(
|
|
1334
|
+
"Update profile photo of Salesforce user with ID -> %s; calling -> %s",
|
|
1335
|
+
user_id,
|
|
1336
|
+
request_url,
|
|
1337
|
+
)
|
|
1338
|
+
|
|
1339
|
+
return self.do_request(
|
|
1340
|
+
method="POST",
|
|
1341
|
+
url=request_url,
|
|
1342
|
+
headers=request_header,
|
|
1343
|
+
files=files,
|
|
1344
|
+
data=data,
|
|
1345
|
+
timeout=REQUEST_TIMEOUT,
|
|
1346
|
+
failure_message="Failed to update profile photo of Salesforce user with ID -> {}".format(
|
|
1347
|
+
user_id
|
|
1348
|
+
),
|
|
1349
|
+
verify=False,
|
|
1350
|
+
)
|
|
755
1351
|
|
|
756
1352
|
# end method definition
|
|
757
1353
|
|
|
@@ -783,11 +1379,10 @@ class Salesforce(object):
|
|
|
783
1379
|
"""
|
|
784
1380
|
|
|
785
1381
|
if not self._access_token or not self._instance_url:
|
|
786
|
-
|
|
787
|
-
return None
|
|
1382
|
+
self.authenticate()
|
|
788
1383
|
|
|
789
1384
|
request_header = self.request_header()
|
|
790
|
-
request_url =
|
|
1385
|
+
request_url = self.config()["accountUrl"]
|
|
791
1386
|
|
|
792
1387
|
payload = {
|
|
793
1388
|
"Name": account_name,
|
|
@@ -800,33 +1395,23 @@ class Salesforce(object):
|
|
|
800
1395
|
}
|
|
801
1396
|
payload.update(kwargs) # Add additional fields from kwargs
|
|
802
1397
|
|
|
803
|
-
logger.
|
|
804
|
-
"Adding Salesforce account -> %s; calling -> %s",
|
|
1398
|
+
logger.debug(
|
|
1399
|
+
"Adding Salesforce account -> '%s' (%s); calling -> %s",
|
|
1400
|
+
account_name,
|
|
1401
|
+
account_number,
|
|
1402
|
+
request_url,
|
|
805
1403
|
)
|
|
806
1404
|
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
elif response.status_code == 401 and retries == 0:
|
|
818
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
819
|
-
self.authenticate(revalidate=True)
|
|
820
|
-
request_header = self.request_header()
|
|
821
|
-
retries += 1
|
|
822
|
-
else:
|
|
823
|
-
logger.error(
|
|
824
|
-
"Failed to add Salesforce account -> %s; status -> %s; error -> %s",
|
|
825
|
-
account_name,
|
|
826
|
-
response.status_code,
|
|
827
|
-
response.text,
|
|
828
|
-
)
|
|
829
|
-
return None
|
|
1405
|
+
return self.do_request(
|
|
1406
|
+
method="POST",
|
|
1407
|
+
url=request_url,
|
|
1408
|
+
headers=request_header,
|
|
1409
|
+
data=json.dumps(payload),
|
|
1410
|
+
timeout=REQUEST_TIMEOUT,
|
|
1411
|
+
failure_message="Failed to add Salesforce account -> '{}' ({})".format(
|
|
1412
|
+
account_name, account_number
|
|
1413
|
+
),
|
|
1414
|
+
)
|
|
830
1415
|
|
|
831
1416
|
# end method definition
|
|
832
1417
|
|
|
@@ -851,11 +1436,10 @@ class Salesforce(object):
|
|
|
851
1436
|
"""
|
|
852
1437
|
|
|
853
1438
|
if not self._access_token or not self._instance_url:
|
|
854
|
-
|
|
855
|
-
return None
|
|
1439
|
+
self.authenticate()
|
|
856
1440
|
|
|
857
1441
|
request_header = self.request_header()
|
|
858
|
-
request_url =
|
|
1442
|
+
request_url = self.config()["productUrl"]
|
|
859
1443
|
|
|
860
1444
|
payload = {
|
|
861
1445
|
"Name": product_name,
|
|
@@ -865,33 +1449,23 @@ class Salesforce(object):
|
|
|
865
1449
|
}
|
|
866
1450
|
payload.update(kwargs) # Add additional fields from kwargs
|
|
867
1451
|
|
|
868
|
-
logger.
|
|
869
|
-
"Add Salesforce product -> %s; calling -> %s",
|
|
1452
|
+
logger.debug(
|
|
1453
|
+
"Add Salesforce product -> '%s' (%s); calling -> %s",
|
|
1454
|
+
product_name,
|
|
1455
|
+
product_code,
|
|
1456
|
+
request_url,
|
|
870
1457
|
)
|
|
871
1458
|
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
elif response.status_code == 401 and retries == 0:
|
|
883
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
884
|
-
self.authenticate(revalidate=True)
|
|
885
|
-
request_header = self.request_header()
|
|
886
|
-
retries += 1
|
|
887
|
-
else:
|
|
888
|
-
logger.error(
|
|
889
|
-
"Failed to add Salesforce product -> %s; status -> %s; error -> %s",
|
|
890
|
-
product_name,
|
|
891
|
-
response.status_code,
|
|
892
|
-
response.text,
|
|
893
|
-
)
|
|
894
|
-
return None
|
|
1459
|
+
return self.do_request(
|
|
1460
|
+
method="POST",
|
|
1461
|
+
url=request_url,
|
|
1462
|
+
headers=request_header,
|
|
1463
|
+
data=json.dumps(payload),
|
|
1464
|
+
timeout=REQUEST_TIMEOUT,
|
|
1465
|
+
failure_message="Failed to add Salesforce product -> '{}' ({})".format(
|
|
1466
|
+
product_name, product_code
|
|
1467
|
+
),
|
|
1468
|
+
)
|
|
895
1469
|
|
|
896
1470
|
# end method definition
|
|
897
1471
|
|
|
@@ -922,11 +1496,10 @@ class Salesforce(object):
|
|
|
922
1496
|
"""
|
|
923
1497
|
|
|
924
1498
|
if not self._access_token or not self._instance_url:
|
|
925
|
-
|
|
926
|
-
return None
|
|
1499
|
+
self.authenticate()
|
|
927
1500
|
|
|
928
1501
|
request_header = self.request_header()
|
|
929
|
-
request_url =
|
|
1502
|
+
request_url = self.config()["opportunityUrl"]
|
|
930
1503
|
|
|
931
1504
|
payload = {
|
|
932
1505
|
"Name": name,
|
|
@@ -939,33 +1512,20 @@ class Salesforce(object):
|
|
|
939
1512
|
payload["Description"] = description
|
|
940
1513
|
payload.update(kwargs) # Add additional fields from kwargs
|
|
941
1514
|
|
|
942
|
-
logger.
|
|
943
|
-
"Add Salesforce opportunity -> %s; calling -> %s", name, request_url
|
|
1515
|
+
logger.debug(
|
|
1516
|
+
"Add Salesforce opportunity -> '%s'; calling -> %s", name, request_url
|
|
944
1517
|
)
|
|
945
1518
|
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
elif response.status_code == 401 and retries == 0:
|
|
957
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
958
|
-
self.authenticate(revalidate=True)
|
|
959
|
-
request_header = self.request_header()
|
|
960
|
-
retries += 1
|
|
961
|
-
else:
|
|
962
|
-
logger.error(
|
|
963
|
-
"Failed to add Salesforce opportunity -> %s; status -> %s; error -> %s",
|
|
964
|
-
name,
|
|
965
|
-
response.status_code,
|
|
966
|
-
response.text,
|
|
967
|
-
)
|
|
968
|
-
return None
|
|
1519
|
+
return self.do_request(
|
|
1520
|
+
method="POST",
|
|
1521
|
+
url=request_url,
|
|
1522
|
+
headers=request_header,
|
|
1523
|
+
data=json.dumps(payload),
|
|
1524
|
+
timeout=REQUEST_TIMEOUT,
|
|
1525
|
+
failure_message="Failed to add Salesforce opportunity -> '{}'".format(name),
|
|
1526
|
+
)
|
|
1527
|
+
|
|
1528
|
+
# end method definition
|
|
969
1529
|
|
|
970
1530
|
def add_case(
|
|
971
1531
|
self,
|
|
@@ -990,6 +1550,7 @@ class Salesforce(object):
|
|
|
990
1550
|
priority (str): Priority of the case. Typical values: "High", "Medium", "Low".
|
|
991
1551
|
origin (str): origin (source) of the case. Typical values: "Email", "Phone", "Web"
|
|
992
1552
|
account_id (str): technical ID of the related Account
|
|
1553
|
+
owner_id (str): owner of the case
|
|
993
1554
|
asset_id (str): technical ID of the related Asset
|
|
994
1555
|
product_id (str): technical ID of the related Product
|
|
995
1556
|
kwargs (Any): additional values (e.g. custom fields)
|
|
@@ -999,11 +1560,10 @@ class Salesforce(object):
|
|
|
999
1560
|
"""
|
|
1000
1561
|
|
|
1001
1562
|
if not self._access_token or not self._instance_url:
|
|
1002
|
-
|
|
1003
|
-
return None
|
|
1563
|
+
self.authenticate()
|
|
1004
1564
|
|
|
1005
1565
|
request_header = self.request_header()
|
|
1006
|
-
request_url =
|
|
1566
|
+
request_url = self.config()["caseUrl"]
|
|
1007
1567
|
|
|
1008
1568
|
payload = {
|
|
1009
1569
|
"Subject": subject,
|
|
@@ -1021,31 +1581,16 @@ class Salesforce(object):
|
|
|
1021
1581
|
payload["ProductId"] = product_id
|
|
1022
1582
|
payload.update(kwargs) # Add additional fields from kwargs
|
|
1023
1583
|
|
|
1024
|
-
logger.
|
|
1584
|
+
logger.debug("Add Salesforce case -> '%s'; calling -> %s", subject, request_url)
|
|
1025
1585
|
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
if response.ok:
|
|
1035
|
-
return self.parse_request_response(response)
|
|
1036
|
-
elif response.status_code == 401 and retries == 0:
|
|
1037
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
1038
|
-
self.authenticate(revalidate=True)
|
|
1039
|
-
request_header = self.request_header()
|
|
1040
|
-
retries += 1
|
|
1041
|
-
else:
|
|
1042
|
-
logger.error(
|
|
1043
|
-
"Failed to add Salesforce case -> %s; status -> %s; error -> %s",
|
|
1044
|
-
subject,
|
|
1045
|
-
response.status_code,
|
|
1046
|
-
response.text,
|
|
1047
|
-
)
|
|
1048
|
-
return None
|
|
1586
|
+
return self.do_request(
|
|
1587
|
+
method="POST",
|
|
1588
|
+
url=request_url,
|
|
1589
|
+
headers=request_header,
|
|
1590
|
+
data=json.dumps(payload),
|
|
1591
|
+
timeout=REQUEST_TIMEOUT,
|
|
1592
|
+
failure_message="Failed to add Salesforce case -> '{}'".format(subject),
|
|
1593
|
+
)
|
|
1049
1594
|
|
|
1050
1595
|
# end method definition
|
|
1051
1596
|
|
|
@@ -1077,11 +1622,10 @@ class Salesforce(object):
|
|
|
1077
1622
|
"""
|
|
1078
1623
|
|
|
1079
1624
|
if not self._access_token or not self._instance_url:
|
|
1080
|
-
|
|
1081
|
-
return None
|
|
1625
|
+
self.authenticate()
|
|
1082
1626
|
|
|
1083
1627
|
request_header = self.request_header()
|
|
1084
|
-
request_url =
|
|
1628
|
+
request_url = self.config()["assetUrl"]
|
|
1085
1629
|
|
|
1086
1630
|
payload = {
|
|
1087
1631
|
"Name": asset_name,
|
|
@@ -1095,33 +1639,18 @@ class Salesforce(object):
|
|
|
1095
1639
|
payload["Description"] = description
|
|
1096
1640
|
payload.update(kwargs) # Add additional fields from kwargs
|
|
1097
1641
|
|
|
1098
|
-
logger.
|
|
1099
|
-
"Add Salesforce asset -> %s; calling -> %s", asset_name, request_url
|
|
1642
|
+
logger.debug(
|
|
1643
|
+
"Add Salesforce asset -> '%s'; calling -> %s", asset_name, request_url
|
|
1100
1644
|
)
|
|
1101
1645
|
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
if response.ok:
|
|
1111
|
-
return self.parse_request_response(response)
|
|
1112
|
-
elif response.status_code == 401 and retries == 0:
|
|
1113
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
1114
|
-
self.authenticate(revalidate=True)
|
|
1115
|
-
request_header = self.request_header()
|
|
1116
|
-
retries += 1
|
|
1117
|
-
else:
|
|
1118
|
-
logger.error(
|
|
1119
|
-
"Failed to add Salesforce user -> %s; status -> %s; error -> %s",
|
|
1120
|
-
asset_name,
|
|
1121
|
-
response.status_code,
|
|
1122
|
-
response.text,
|
|
1123
|
-
)
|
|
1124
|
-
return None
|
|
1646
|
+
return self.do_request(
|
|
1647
|
+
method="POST",
|
|
1648
|
+
url=request_url,
|
|
1649
|
+
headers=request_header,
|
|
1650
|
+
data=json.dumps(payload),
|
|
1651
|
+
timeout=REQUEST_TIMEOUT,
|
|
1652
|
+
failure_message="Failed to add Salesforce asset -> '{}'".format(asset_name),
|
|
1653
|
+
)
|
|
1125
1654
|
|
|
1126
1655
|
# end method definition
|
|
1127
1656
|
|
|
@@ -1151,11 +1680,10 @@ class Salesforce(object):
|
|
|
1151
1680
|
"""
|
|
1152
1681
|
|
|
1153
1682
|
if not self._access_token or not self._instance_url:
|
|
1154
|
-
|
|
1155
|
-
return None
|
|
1683
|
+
self.authenticate()
|
|
1156
1684
|
|
|
1157
1685
|
request_header = self.request_header()
|
|
1158
|
-
request_url =
|
|
1686
|
+
request_url = self.config()["contractUrl"]
|
|
1159
1687
|
|
|
1160
1688
|
payload = {
|
|
1161
1689
|
"AccountId": account_id,
|
|
@@ -1169,34 +1697,21 @@ class Salesforce(object):
|
|
|
1169
1697
|
payload["ContractType"] = contract_type
|
|
1170
1698
|
payload.update(kwargs) # Add additional fields from kwargs
|
|
1171
1699
|
|
|
1172
|
-
logger.
|
|
1173
|
-
"Adding Salesforce contract for account ID -> %s; calling -> %s",
|
|
1700
|
+
logger.debug(
|
|
1701
|
+
"Adding Salesforce contract for account with ID -> %s; calling -> %s",
|
|
1174
1702
|
account_id,
|
|
1175
1703
|
request_url,
|
|
1176
1704
|
)
|
|
1177
1705
|
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
elif response.status_code == 401 and retries == 0:
|
|
1189
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
1190
|
-
self.authenticate(revalidate=True)
|
|
1191
|
-
request_header = self.request_header()
|
|
1192
|
-
retries += 1
|
|
1193
|
-
else:
|
|
1194
|
-
logger.error(
|
|
1195
|
-
"Failed to add Salesforce contract for account ID -> %s; status -> %s; error -> %s",
|
|
1196
|
-
account_id,
|
|
1197
|
-
response.status_code,
|
|
1198
|
-
response.text,
|
|
1199
|
-
)
|
|
1200
|
-
return None
|
|
1706
|
+
return self.do_request(
|
|
1707
|
+
method="POST",
|
|
1708
|
+
url=request_url,
|
|
1709
|
+
headers=request_header,
|
|
1710
|
+
data=json.dumps(payload),
|
|
1711
|
+
timeout=REQUEST_TIMEOUT,
|
|
1712
|
+
failure_message="Failed to add Salesforce contract for account with ID -> {}".format(
|
|
1713
|
+
account_id
|
|
1714
|
+
),
|
|
1715
|
+
)
|
|
1201
1716
|
|
|
1202
1717
|
# end method definition
|