pyxecm 1.5__py3-none-any.whl → 2.0.0__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 +6 -2
- pyxecm/avts.py +1492 -0
- pyxecm/coreshare.py +1075 -960
- pyxecm/customizer/__init__.py +16 -4
- pyxecm/customizer/__main__.py +58 -0
- pyxecm/customizer/api/__init__.py +5 -0
- pyxecm/customizer/api/__main__.py +6 -0
- pyxecm/customizer/api/app.py +914 -0
- pyxecm/customizer/api/auth.py +154 -0
- pyxecm/customizer/api/metrics.py +92 -0
- pyxecm/customizer/api/models.py +13 -0
- pyxecm/customizer/api/payload_list.py +865 -0
- pyxecm/customizer/api/settings.py +103 -0
- pyxecm/customizer/browser_automation.py +332 -139
- pyxecm/customizer/customizer.py +1075 -1057
- pyxecm/customizer/exceptions.py +35 -0
- pyxecm/customizer/guidewire.py +322 -0
- pyxecm/customizer/k8s.py +787 -338
- pyxecm/customizer/log.py +107 -0
- pyxecm/customizer/m365.py +3424 -2270
- pyxecm/customizer/nhc.py +1169 -0
- pyxecm/customizer/openapi.py +258 -0
- pyxecm/customizer/payload.py +18201 -7030
- pyxecm/customizer/pht.py +1047 -210
- pyxecm/customizer/salesforce.py +836 -727
- pyxecm/customizer/sap.py +58 -41
- pyxecm/customizer/servicenow.py +851 -383
- pyxecm/customizer/settings.py +442 -0
- pyxecm/customizer/successfactors.py +408 -346
- pyxecm/customizer/translate.py +83 -48
- pyxecm/helper/__init__.py +5 -2
- pyxecm/helper/assoc.py +98 -38
- pyxecm/helper/data.py +2482 -742
- pyxecm/helper/logadapter.py +27 -0
- pyxecm/helper/web.py +229 -101
- pyxecm/helper/xml.py +528 -172
- pyxecm/maintenance_page/__init__.py +5 -0
- pyxecm/maintenance_page/__main__.py +6 -0
- pyxecm/maintenance_page/app.py +51 -0
- pyxecm/maintenance_page/settings.py +28 -0
- pyxecm/maintenance_page/static/favicon.avif +0 -0
- pyxecm/maintenance_page/templates/maintenance.html +165 -0
- pyxecm/otac.py +234 -140
- pyxecm/otawp.py +2689 -0
- pyxecm/otcs.py +12344 -7547
- pyxecm/otds.py +3166 -2219
- pyxecm/otiv.py +36 -21
- pyxecm/otmm.py +1363 -296
- pyxecm/otpd.py +231 -127
- pyxecm-2.0.0.dist-info/METADATA +145 -0
- pyxecm-2.0.0.dist-info/RECORD +54 -0
- {pyxecm-1.5.dist-info → pyxecm-2.0.0.dist-info}/WHEEL +1 -1
- pyxecm-1.5.dist-info/METADATA +0 -51
- pyxecm-1.5.dist-info/RECORD +0 -30
- {pyxecm-1.5.dist-info → pyxecm-2.0.0.dist-info/licenses}/LICENSE +0 -0
- {pyxecm-1.5.dist-info → pyxecm-2.0.0.dist-info}/top_level.txt +0 -0
pyxecm/customizer/salesforce.py
CHANGED
|
@@ -1,66 +1,23 @@
|
|
|
1
|
-
"""
|
|
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
|
|
1
|
+
"""Salesforce Module to interact with the Salesforce API.
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
Methods:
|
|
7
|
-
|
|
8
|
-
__init__ : class initializer
|
|
9
|
-
config : Returns config data set
|
|
10
|
-
credentials: Returns the token data
|
|
11
|
-
request_header: Returns the request header for Salesforce API calls
|
|
12
|
-
parse_request_response: Parse the REST API responses and convert
|
|
13
|
-
them to Python dict in a safe way
|
|
14
|
-
exist_result_item: Check if an dict item is in the response
|
|
15
|
-
of the Salesforce API call
|
|
16
|
-
get_result_value: Check if a defined value (based on a key) is in the Salesforce API response
|
|
17
|
-
|
|
18
|
-
authenticate: Authenticates at Salesforce API
|
|
19
|
-
|
|
20
|
-
get_object_id_by_name: Get the ID of a given Salesforce object with a given type and name
|
|
21
|
-
get_object: Get a Salesforce object based on a defined
|
|
22
|
-
field value and return selected result fields.
|
|
23
|
-
add_object: Add object to Salesforce. This is a generic wrapper method
|
|
24
|
-
for the actual add methods.
|
|
25
|
-
|
|
26
|
-
get_group: Get a Salesforce group based on its ID.
|
|
27
|
-
add_group: Add a new Salesforce group.
|
|
28
|
-
update_group: Update a Salesforce group.
|
|
29
|
-
get_group_members: Get Salesforce group members
|
|
30
|
-
add_group_member: Add a user or group to a Salesforce group
|
|
31
|
-
|
|
32
|
-
get_all_user_profiles: Get all user profiles
|
|
33
|
-
get_user_profile_id: Get a user profile ID by profile name
|
|
34
|
-
get_user_id: Get a user ID by user name
|
|
35
|
-
get_user: Get a Salesforce user based on its ID.
|
|
36
|
-
add_user: Add a new Salesforce user.
|
|
37
|
-
update_user: Update a Salesforce user.
|
|
38
|
-
update_user_password: Update the password of a Salesforce user.
|
|
39
|
-
update_user_photo: update the Salesforce user photo.
|
|
40
|
-
|
|
41
|
-
add_account: Add a new Account object to Salesforce.
|
|
42
|
-
add_product: Add a new Product object to Salesforce.
|
|
43
|
-
add_opportunity: Add a new Opportunity object to Salesfoce.
|
|
44
|
-
add_case: Add a new Case object to Salesforce. The case number
|
|
45
|
-
is automatically created and can not be provided.
|
|
46
|
-
add_asset: Add a new Asset object to Salesforce.
|
|
47
|
-
add_contract: Add a new Contract object to Salesforce.
|
|
3
|
+
See: https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/intro_rest.htm
|
|
48
4
|
"""
|
|
49
5
|
|
|
50
6
|
__author__ = "Dr. Marc Diefenbruch"
|
|
51
|
-
__copyright__ = "Copyright 2024, OpenText"
|
|
7
|
+
__copyright__ = "Copyright (C) 2024-2025, OpenText"
|
|
52
8
|
__credits__ = ["Kai-Philip Gatzweiler"]
|
|
53
9
|
__maintainer__ = "Dr. Marc Diefenbruch"
|
|
54
10
|
__email__ = "mdiefenb@opentext.com"
|
|
55
11
|
|
|
56
|
-
import os
|
|
57
12
|
import json
|
|
58
13
|
import logging
|
|
14
|
+
import os
|
|
15
|
+
import time
|
|
16
|
+
from http import HTTPStatus
|
|
59
17
|
|
|
60
|
-
from typing import Optional, Union, Any
|
|
61
18
|
import requests
|
|
62
19
|
|
|
63
|
-
|
|
20
|
+
default_logger = logging.getLogger("pyxecm.customizer.salesforce")
|
|
64
21
|
|
|
65
22
|
REQUEST_LOGIN_HEADERS = {
|
|
66
23
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
@@ -68,10 +25,16 @@ REQUEST_LOGIN_HEADERS = {
|
|
|
68
25
|
}
|
|
69
26
|
|
|
70
27
|
REQUEST_TIMEOUT = 60
|
|
28
|
+
REQUEST_RETRY_DELAY = 20
|
|
29
|
+
REQUEST_MAX_RETRIES = 3
|
|
30
|
+
|
|
71
31
|
SALESFORCE_API_VERSION = "v60.0"
|
|
72
32
|
|
|
73
|
-
|
|
74
|
-
|
|
33
|
+
|
|
34
|
+
class Salesforce:
|
|
35
|
+
"""Class Salesforce is used to retrieve and automate stettings and objects in Salesforce."""
|
|
36
|
+
|
|
37
|
+
logger: logging.Logger = default_logger
|
|
75
38
|
|
|
76
39
|
_config: dict
|
|
77
40
|
_access_token = None
|
|
@@ -86,21 +49,38 @@ class Salesforce(object):
|
|
|
86
49
|
password: str,
|
|
87
50
|
authorization_url: str = "",
|
|
88
51
|
security_token: str = "",
|
|
89
|
-
|
|
90
|
-
|
|
52
|
+
logger: logging.Logger = default_logger,
|
|
53
|
+
) -> None:
|
|
54
|
+
"""Initialize the Salesforce object.
|
|
91
55
|
|
|
92
56
|
Args:
|
|
93
|
-
base_url (str):
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
57
|
+
base_url (str):
|
|
58
|
+
Base URL of the Salesforce tenant.
|
|
59
|
+
authorization_url (str):
|
|
60
|
+
Authorization URL of the Salesforce tenant, typically ending with "/services/oauth2/token".
|
|
61
|
+
client_id (str):
|
|
62
|
+
The Salesforce Client ID.
|
|
63
|
+
client_secret (str):
|
|
64
|
+
The Salesforce Client Secret.
|
|
65
|
+
username (str):
|
|
66
|
+
User name in Saleforce used by the REST API.
|
|
67
|
+
password (str):
|
|
68
|
+
Password of the user used by the REST API.
|
|
69
|
+
authorization_url (str, optional):
|
|
70
|
+
URL for Salesforce login. If not given it will be constructed with default values
|
|
71
|
+
using base_url.
|
|
72
|
+
security_token (str, optional):
|
|
73
|
+
Security token for Salesforce login.
|
|
74
|
+
logger (logging.Logger, optional):
|
|
75
|
+
The logging object to use for all log messages. Defaults to default_logger.
|
|
76
|
+
|
|
102
77
|
"""
|
|
103
78
|
|
|
79
|
+
if logger != default_logger:
|
|
80
|
+
self.logger = logger.getChild("salesforce")
|
|
81
|
+
for logfilter in logger.filters:
|
|
82
|
+
self.logger.addFilter(logfilter)
|
|
83
|
+
|
|
104
84
|
# The instance URL is also returned by the authenticate call
|
|
105
85
|
# but typically it is identical to the base_url.
|
|
106
86
|
self._instance_url = base_url
|
|
@@ -116,38 +96,32 @@ class Salesforce(object):
|
|
|
116
96
|
|
|
117
97
|
# Set the Salesforce URLs and REST API endpoints:
|
|
118
98
|
salesforce_config["baseUrl"] = base_url
|
|
119
|
-
salesforce_config["objectUrl"] = salesforce_config[
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
salesforce_config["queryUrl"] = salesforce_config[
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
salesforce_config["compositeUrl"] = salesforce_config[
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
salesforce_config["connectUrl"] = salesforce_config[
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
salesforce_config["toolingUrl"] = salesforce_config[
|
|
132
|
-
|
|
133
|
-
|
|
99
|
+
salesforce_config["objectUrl"] = salesforce_config["baseUrl"] + "/services/data/{}/sobjects/".format(
|
|
100
|
+
SALESFORCE_API_VERSION,
|
|
101
|
+
)
|
|
102
|
+
salesforce_config["queryUrl"] = salesforce_config["baseUrl"] + "/services/data/{}/query/".format(
|
|
103
|
+
SALESFORCE_API_VERSION,
|
|
104
|
+
)
|
|
105
|
+
salesforce_config["compositeUrl"] = salesforce_config["baseUrl"] + "/services/data/{}/composite/".format(
|
|
106
|
+
SALESFORCE_API_VERSION,
|
|
107
|
+
)
|
|
108
|
+
salesforce_config["connectUrl"] = salesforce_config["baseUrl"] + "/services/data/{}/connect/".format(
|
|
109
|
+
SALESFORCE_API_VERSION,
|
|
110
|
+
)
|
|
111
|
+
salesforce_config["toolingUrl"] = salesforce_config["baseUrl"] + "/services/data/{}/tooling/".format(
|
|
112
|
+
SALESFORCE_API_VERSION,
|
|
113
|
+
)
|
|
134
114
|
if authorization_url:
|
|
135
115
|
salesforce_config["authenticationUrl"] = authorization_url
|
|
136
116
|
else:
|
|
137
|
-
salesforce_config["authenticationUrl"] =
|
|
138
|
-
salesforce_config["baseUrl"] + "/services/oauth2/token"
|
|
139
|
-
)
|
|
117
|
+
salesforce_config["authenticationUrl"] = salesforce_config["baseUrl"] + "/services/oauth2/token"
|
|
140
118
|
# URLs that are based on the objectURL (sobjects/):
|
|
141
119
|
salesforce_config["userUrl"] = salesforce_config["objectUrl"] + "User/"
|
|
142
120
|
salesforce_config["groupUrl"] = salesforce_config["objectUrl"] + "Group/"
|
|
143
|
-
salesforce_config["groupMemberUrl"] =
|
|
144
|
-
salesforce_config["objectUrl"] + "GroupMember/"
|
|
145
|
-
)
|
|
121
|
+
salesforce_config["groupMemberUrl"] = salesforce_config["objectUrl"] + "GroupMember/"
|
|
146
122
|
salesforce_config["accountUrl"] = salesforce_config["objectUrl"] + "Account/"
|
|
147
123
|
salesforce_config["productUrl"] = salesforce_config["objectUrl"] + "Product2/"
|
|
148
|
-
salesforce_config["opportunityUrl"] =
|
|
149
|
-
salesforce_config["objectUrl"] + "Opportunity/"
|
|
150
|
-
)
|
|
124
|
+
salesforce_config["opportunityUrl"] = salesforce_config["objectUrl"] + "Opportunity/"
|
|
151
125
|
salesforce_config["caseUrl"] = salesforce_config["objectUrl"] + "Case/"
|
|
152
126
|
salesforce_config["assetUrl"] = salesforce_config["objectUrl"] + "Asset/"
|
|
153
127
|
salesforce_config["contractUrl"] = salesforce_config["objectUrl"] + "Contract/"
|
|
@@ -166,33 +140,44 @@ class Salesforce(object):
|
|
|
166
140
|
# end method definition
|
|
167
141
|
|
|
168
142
|
def config(self) -> dict:
|
|
169
|
-
"""
|
|
143
|
+
"""Return the configuration dictionary.
|
|
170
144
|
|
|
171
145
|
Returns:
|
|
172
|
-
dict:
|
|
146
|
+
dict:
|
|
147
|
+
The configuration dictionary.
|
|
148
|
+
|
|
173
149
|
"""
|
|
150
|
+
|
|
174
151
|
return self._config
|
|
175
152
|
|
|
176
153
|
# end method definition
|
|
177
154
|
|
|
178
155
|
def credentials(self) -> dict:
|
|
179
|
-
"""Return the login credentials
|
|
156
|
+
"""Return the login credentials.
|
|
180
157
|
|
|
181
158
|
Returns:
|
|
182
|
-
dict:
|
|
159
|
+
dict:
|
|
160
|
+
The dictionary with login credentials for Salesforce.
|
|
161
|
+
|
|
183
162
|
"""
|
|
163
|
+
|
|
184
164
|
return self.config()["authenticationData"]
|
|
185
165
|
|
|
186
166
|
# end method definition
|
|
187
167
|
|
|
188
168
|
def request_header(self, content_type: str = "application/json") -> dict:
|
|
189
|
-
"""
|
|
190
|
-
|
|
169
|
+
"""Return the request header used for Application calls.
|
|
170
|
+
|
|
171
|
+
Consists of Bearer access token and Content Type
|
|
191
172
|
|
|
192
173
|
Args:
|
|
193
|
-
content_type (str, optional):
|
|
194
|
-
|
|
195
|
-
|
|
174
|
+
content_type (str, optional):
|
|
175
|
+
Content type for the request. Default is "pplication/json".
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
dict:
|
|
179
|
+
The equest header values
|
|
180
|
+
|
|
196
181
|
"""
|
|
197
182
|
|
|
198
183
|
request_header = {
|
|
@@ -205,49 +190,254 @@ class Salesforce(object):
|
|
|
205
190
|
|
|
206
191
|
# end method definition
|
|
207
192
|
|
|
193
|
+
def do_request(
|
|
194
|
+
self,
|
|
195
|
+
url: str,
|
|
196
|
+
method: str = "GET",
|
|
197
|
+
headers: dict | None = None,
|
|
198
|
+
data: dict | None = None,
|
|
199
|
+
json_data: dict | None = None,
|
|
200
|
+
files: dict | None = None,
|
|
201
|
+
params: dict | None = None,
|
|
202
|
+
timeout: int | None = REQUEST_TIMEOUT,
|
|
203
|
+
show_error: bool = True,
|
|
204
|
+
show_warning: bool = False,
|
|
205
|
+
warning_message: str = "",
|
|
206
|
+
failure_message: str = "",
|
|
207
|
+
success_message: str = "",
|
|
208
|
+
max_retries: int = REQUEST_MAX_RETRIES,
|
|
209
|
+
retry_forever: bool = False,
|
|
210
|
+
parse_request_response: bool = True,
|
|
211
|
+
stream: bool = False,
|
|
212
|
+
verify: bool = True,
|
|
213
|
+
) -> dict | None:
|
|
214
|
+
"""Call an Salesforce REST API in a safe way.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
url (str):
|
|
218
|
+
The URL to send the request to.
|
|
219
|
+
method (str, optional):
|
|
220
|
+
HTTP method (GET, POST, etc.). Defaults to "GET".
|
|
221
|
+
headers (dict | None, optional):
|
|
222
|
+
Request Headers. Defaults to None.
|
|
223
|
+
data (dict | None, optional):
|
|
224
|
+
Request payload. Defaults to None
|
|
225
|
+
json_data (dict | None, optional):
|
|
226
|
+
Request payload. Defaults to None.
|
|
227
|
+
files (dict | None, optional):
|
|
228
|
+
Dictionary of {"name": file-tuple} for multipart encoding upload.
|
|
229
|
+
The file-tuple can be a 2-tuple ("filename", fileobj) or a 3-tuple ("filename", fileobj, "content_type")
|
|
230
|
+
params (dict | None, optional):
|
|
231
|
+
Add key-value pairs to the query string of the URL.
|
|
232
|
+
When you use the params parameter, requests automatically appends
|
|
233
|
+
the key-value pairs to the URL as part of the query string
|
|
234
|
+
timeout (int | None, optional):
|
|
235
|
+
Timeout for the request in seconds. Defaults to REQUEST_TIMEOUT.
|
|
236
|
+
show_error (bool, optional):
|
|
237
|
+
Whether or not an error should be logged in case of a failed REST call.
|
|
238
|
+
If False, then only a warning is logged. Defaults to True.
|
|
239
|
+
show_warning (bool, optional):
|
|
240
|
+
Whether or not an warning should be logged in case of a failed REST call.
|
|
241
|
+
If False, then only a warning is logged. Defaults to True.
|
|
242
|
+
warning_message (str, optional):
|
|
243
|
+
Specific warning message. Defaults to "".
|
|
244
|
+
If not given the error_message will be used.
|
|
245
|
+
failure_message (str, optional):
|
|
246
|
+
Specific error message. Defaults to "".
|
|
247
|
+
success_message (str, optional):
|
|
248
|
+
Specific success message. Defaults to "".
|
|
249
|
+
max_retries (int, optional):
|
|
250
|
+
How many retries on Connection errors? Default is REQUEST_MAX_RETRIES.
|
|
251
|
+
retry_forever (bool, optional):
|
|
252
|
+
Eventually wait forever - without timeout. Defaults to False.
|
|
253
|
+
parse_request_response (bool, optional):
|
|
254
|
+
Should the response.text be interpreted as json and loaded into a dictionary.
|
|
255
|
+
True is the default.
|
|
256
|
+
stream (bool, optional):
|
|
257
|
+
Control whether the response content should be immediately downloaded or streamed incrementally.
|
|
258
|
+
verify (bool, optional):
|
|
259
|
+
Specify whether or not SSL certificates should be verified when making an HTTPS request.
|
|
260
|
+
Default = True
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
dict | None:
|
|
264
|
+
Response of OTDS REST API or None in case of an error.
|
|
265
|
+
|
|
266
|
+
"""
|
|
267
|
+
|
|
268
|
+
if headers is None:
|
|
269
|
+
self.logger.error(
|
|
270
|
+
"Missing request header. Cannot send request to Core Share!",
|
|
271
|
+
)
|
|
272
|
+
return None
|
|
273
|
+
|
|
274
|
+
# In case of an expired session we reauthenticate and
|
|
275
|
+
# try 1 more time. Session expiration should not happen
|
|
276
|
+
# twice in a row:
|
|
277
|
+
retries = 0
|
|
278
|
+
|
|
279
|
+
while True:
|
|
280
|
+
try:
|
|
281
|
+
response = requests.request(
|
|
282
|
+
method=method,
|
|
283
|
+
url=url,
|
|
284
|
+
data=data,
|
|
285
|
+
json=json_data,
|
|
286
|
+
files=files,
|
|
287
|
+
params=params,
|
|
288
|
+
headers=headers,
|
|
289
|
+
timeout=timeout,
|
|
290
|
+
stream=stream,
|
|
291
|
+
verify=verify,
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
if response.ok:
|
|
295
|
+
if success_message:
|
|
296
|
+
self.logger.info(success_message)
|
|
297
|
+
if parse_request_response:
|
|
298
|
+
return self.parse_request_response(response)
|
|
299
|
+
else:
|
|
300
|
+
return response
|
|
301
|
+
# Check if Session has expired - then re-authenticate and try once more
|
|
302
|
+
elif response.status_code == 401 and retries == 0:
|
|
303
|
+
self.logger.debug("Session has expired - try to re-authenticate...")
|
|
304
|
+
self.authenticate(revalidate=True)
|
|
305
|
+
# Make sure to not change an existing content type
|
|
306
|
+
# the do_request() method is called with:
|
|
307
|
+
headers = self.request_header(
|
|
308
|
+
content_type=headers.get("Content-Type", None),
|
|
309
|
+
)
|
|
310
|
+
retries += 1
|
|
311
|
+
else:
|
|
312
|
+
# Handle plain HTML responses to not pollute the logs
|
|
313
|
+
content_type = response.headers.get("content-type", None)
|
|
314
|
+
if content_type == "text/html":
|
|
315
|
+
response_text = "HTML content (only printed in debug log)"
|
|
316
|
+
else:
|
|
317
|
+
response_text = response.text
|
|
318
|
+
|
|
319
|
+
if show_error:
|
|
320
|
+
self.logger.error(
|
|
321
|
+
"%s; status -> %s/%s; error -> %s",
|
|
322
|
+
failure_message,
|
|
323
|
+
response.status_code,
|
|
324
|
+
HTTPStatus(response.status_code).phrase,
|
|
325
|
+
response_text,
|
|
326
|
+
)
|
|
327
|
+
elif show_warning:
|
|
328
|
+
self.logger.warning(
|
|
329
|
+
"%s; status -> %s/%s; warning -> %s",
|
|
330
|
+
warning_message if warning_message else failure_message,
|
|
331
|
+
response.status_code,
|
|
332
|
+
HTTPStatus(response.status_code).phrase,
|
|
333
|
+
response_text,
|
|
334
|
+
)
|
|
335
|
+
if content_type == "text/html":
|
|
336
|
+
self.logger.debug(
|
|
337
|
+
"%s; status -> %s/%s; warning -> %s",
|
|
338
|
+
failure_message,
|
|
339
|
+
response.status_code,
|
|
340
|
+
HTTPStatus(response.status_code).phrase,
|
|
341
|
+
response.text,
|
|
342
|
+
)
|
|
343
|
+
return None
|
|
344
|
+
except requests.exceptions.Timeout:
|
|
345
|
+
if retries <= max_retries:
|
|
346
|
+
self.logger.warning(
|
|
347
|
+
"Request timed out. Retrying in %s seconds...",
|
|
348
|
+
str(REQUEST_RETRY_DELAY),
|
|
349
|
+
)
|
|
350
|
+
retries += 1
|
|
351
|
+
time.sleep(REQUEST_RETRY_DELAY) # Add a delay before retrying
|
|
352
|
+
else:
|
|
353
|
+
self.logger.error(
|
|
354
|
+
"%s; timeout error.",
|
|
355
|
+
failure_message,
|
|
356
|
+
)
|
|
357
|
+
if retry_forever:
|
|
358
|
+
# If it fails after REQUEST_MAX_RETRIES retries we let it wait forever
|
|
359
|
+
self.logger.warning("Turn timeouts off and wait forever...")
|
|
360
|
+
timeout = None
|
|
361
|
+
else:
|
|
362
|
+
return None
|
|
363
|
+
except requests.exceptions.ConnectionError:
|
|
364
|
+
if retries <= max_retries:
|
|
365
|
+
self.logger.warning(
|
|
366
|
+
"Connection error. Retrying in %s seconds...",
|
|
367
|
+
str(REQUEST_RETRY_DELAY),
|
|
368
|
+
)
|
|
369
|
+
retries += 1
|
|
370
|
+
time.sleep(REQUEST_RETRY_DELAY) # Add a delay before retrying
|
|
371
|
+
else:
|
|
372
|
+
self.logger.error(
|
|
373
|
+
"%s; connection error.",
|
|
374
|
+
failure_message,
|
|
375
|
+
)
|
|
376
|
+
if retry_forever:
|
|
377
|
+
# If it fails after REQUEST_MAX_RETRIES retries we let it wait forever
|
|
378
|
+
self.logger.warning("Turn timeouts off and wait forever...")
|
|
379
|
+
timeout = None
|
|
380
|
+
time.sleep(REQUEST_RETRY_DELAY) # Add a delay before retrying
|
|
381
|
+
else:
|
|
382
|
+
return None
|
|
383
|
+
# end try
|
|
384
|
+
self.logger.debug(
|
|
385
|
+
"Retrying REST API %s call -> %s... (retry = %s)",
|
|
386
|
+
method,
|
|
387
|
+
url,
|
|
388
|
+
str(retries),
|
|
389
|
+
)
|
|
390
|
+
# end while True
|
|
391
|
+
|
|
392
|
+
# end method definition
|
|
393
|
+
|
|
208
394
|
def parse_request_response(
|
|
209
395
|
self,
|
|
210
396
|
response_object: requests.Response,
|
|
211
397
|
additional_error_message: str = "",
|
|
212
398
|
show_error: bool = True,
|
|
213
399
|
) -> dict | None:
|
|
214
|
-
"""
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
400
|
+
"""Convert the request response (JSon) to a Python dict in a safe way.
|
|
401
|
+
|
|
402
|
+
This includes handling exceptions.
|
|
403
|
+
|
|
404
|
+
It first tries to load the response.text via json.loads() that produces
|
|
405
|
+
a dict output. Only if response.text is not set or is empty it just converts
|
|
406
|
+
the response_object to a dict using the vars() built-in method.
|
|
219
407
|
|
|
220
408
|
Args:
|
|
221
|
-
response_object (object):
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
409
|
+
response_object (object):
|
|
410
|
+
This is reponse object delivered by the request call.
|
|
411
|
+
additional_error_message (str, optional):
|
|
412
|
+
Provide a a more specific error message that is logged in case of an error.
|
|
413
|
+
show_error (bool):
|
|
414
|
+
If True, write an error to the log file.
|
|
415
|
+
If False, write a warning to the log file.
|
|
416
|
+
|
|
226
417
|
Returns:
|
|
227
|
-
dict: response information or None in case of an error
|
|
418
|
+
dict | None: Parsed response information or None in case of an error.
|
|
419
|
+
|
|
228
420
|
"""
|
|
229
421
|
|
|
230
422
|
if not response_object:
|
|
231
423
|
return None
|
|
232
424
|
|
|
233
425
|
try:
|
|
234
|
-
if response_object.text
|
|
235
|
-
dict_object = json.loads(response_object.text)
|
|
236
|
-
else:
|
|
237
|
-
dict_object = vars(response_object)
|
|
426
|
+
dict_object = json.loads(response_object.text) if response_object.text else vars(response_object)
|
|
238
427
|
except json.JSONDecodeError as exception:
|
|
239
428
|
if additional_error_message:
|
|
240
429
|
message = "Cannot decode response as JSon. {}; error -> {}".format(
|
|
241
|
-
additional_error_message,
|
|
430
|
+
additional_error_message,
|
|
431
|
+
exception,
|
|
242
432
|
)
|
|
243
433
|
else:
|
|
244
434
|
message = "Cannot decode response as JSon; error -> {}".format(
|
|
245
|
-
exception
|
|
435
|
+
exception,
|
|
246
436
|
)
|
|
247
437
|
if show_error:
|
|
248
|
-
logger.error(message)
|
|
438
|
+
self.logger.error(message)
|
|
249
439
|
else:
|
|
250
|
-
logger.warning(message)
|
|
440
|
+
self.logger.warning(message)
|
|
251
441
|
return None
|
|
252
442
|
else:
|
|
253
443
|
return dict_object
|
|
@@ -255,14 +445,20 @@ class Salesforce(object):
|
|
|
255
445
|
# end method definition
|
|
256
446
|
|
|
257
447
|
def exist_result_item(self, response: dict, key: str, value: str) -> bool:
|
|
258
|
-
"""Check existence of key / value pair in the response properties of
|
|
448
|
+
"""Check existence of key / value pair in the response properties of a Salesforce API call.
|
|
259
449
|
|
|
260
450
|
Args:
|
|
261
|
-
response (dict):
|
|
262
|
-
|
|
263
|
-
|
|
451
|
+
response (dict):
|
|
452
|
+
REST response from an Salesforce API call
|
|
453
|
+
key (str):
|
|
454
|
+
The property name (key) of the item to lookup.
|
|
455
|
+
value (str):
|
|
456
|
+
The value to find in the item with the matching key.
|
|
457
|
+
|
|
264
458
|
Returns:
|
|
265
|
-
bool:
|
|
459
|
+
bool:
|
|
460
|
+
True if the value was found, False otherwise.
|
|
461
|
+
|
|
266
462
|
"""
|
|
267
463
|
|
|
268
464
|
if not response:
|
|
@@ -274,10 +470,10 @@ class Salesforce(object):
|
|
|
274
470
|
return False
|
|
275
471
|
|
|
276
472
|
for record in records:
|
|
277
|
-
if value == record[key]:
|
|
473
|
+
if key in record and value == record[key]:
|
|
278
474
|
return True
|
|
279
475
|
else:
|
|
280
|
-
if not
|
|
476
|
+
if key not in response:
|
|
281
477
|
return False
|
|
282
478
|
if value == response[key]:
|
|
283
479
|
return True
|
|
@@ -292,15 +488,22 @@ class Salesforce(object):
|
|
|
292
488
|
key: str,
|
|
293
489
|
index: int = 0,
|
|
294
490
|
) -> str | None:
|
|
295
|
-
"""Get value of a result property with a given key of an Salesforce API call.
|
|
491
|
+
"""Get the value of a result property with a given key of an Salesforce API call.
|
|
296
492
|
|
|
297
493
|
Args:
|
|
298
|
-
response (dict):
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
494
|
+
response (dict):
|
|
495
|
+
REST response from an Salesforce REST Call.
|
|
496
|
+
key (str):
|
|
497
|
+
The property name (key) of the item to lookup.
|
|
498
|
+
index (int, optional):
|
|
499
|
+
Index to use (1st element has index 0).
|
|
500
|
+
Defaults to 0.
|
|
501
|
+
|
|
302
502
|
Returns:
|
|
303
|
-
str
|
|
503
|
+
str | None:
|
|
504
|
+
The value for the key or None in case of an error or if the
|
|
505
|
+
key is not found.
|
|
506
|
+
|
|
304
507
|
"""
|
|
305
508
|
|
|
306
509
|
if not response:
|
|
@@ -314,7 +517,7 @@ class Salesforce(object):
|
|
|
314
517
|
return None
|
|
315
518
|
value = values[index][key]
|
|
316
519
|
else: # simple response - try to find key in response directly:
|
|
317
|
-
if not
|
|
520
|
+
if key not in response:
|
|
318
521
|
return None
|
|
319
522
|
value = response[key]
|
|
320
523
|
|
|
@@ -326,15 +529,20 @@ class Salesforce(object):
|
|
|
326
529
|
"""Authenticate at Salesforce with client ID and client secret.
|
|
327
530
|
|
|
328
531
|
Args:
|
|
329
|
-
revalidate (bool, optional):
|
|
330
|
-
|
|
532
|
+
revalidate (bool, optional):
|
|
533
|
+
Determins if a re-athentication is enforced
|
|
534
|
+
(e.g. if session has timed out with 401 error).
|
|
535
|
+
|
|
331
536
|
Returns:
|
|
332
|
-
str
|
|
537
|
+
str | None:
|
|
538
|
+
The Access token. Also stores access token in self._access_token.
|
|
539
|
+
None in case of error
|
|
540
|
+
|
|
333
541
|
"""
|
|
334
542
|
|
|
335
543
|
# Already authenticated and session still valid?
|
|
336
544
|
if self._access_token and not revalidate:
|
|
337
|
-
logger.debug(
|
|
545
|
+
self.logger.debug(
|
|
338
546
|
"Session still valid - return existing access token -> %s",
|
|
339
547
|
str(self._access_token),
|
|
340
548
|
)
|
|
@@ -343,7 +551,7 @@ class Salesforce(object):
|
|
|
343
551
|
request_url = self.config()["authenticationUrl"]
|
|
344
552
|
request_header = REQUEST_LOGIN_HEADERS
|
|
345
553
|
|
|
346
|
-
logger.debug("Requesting Salesforce Access Token from -> %s", request_url)
|
|
554
|
+
self.logger.debug("Requesting Salesforce Access Token from -> %s", request_url)
|
|
347
555
|
|
|
348
556
|
authenticate_post_body = self.credentials()
|
|
349
557
|
|
|
@@ -359,7 +567,7 @@ class Salesforce(object):
|
|
|
359
567
|
timeout=REQUEST_TIMEOUT,
|
|
360
568
|
)
|
|
361
569
|
except requests.exceptions.ConnectionError as exception:
|
|
362
|
-
logger.warning(
|
|
570
|
+
self.logger.warning(
|
|
363
571
|
"Unable to connect to -> %s : %s",
|
|
364
572
|
self.config()["authenticationUrl"],
|
|
365
573
|
exception,
|
|
@@ -373,11 +581,11 @@ class Salesforce(object):
|
|
|
373
581
|
else:
|
|
374
582
|
# Store authentication access_token:
|
|
375
583
|
self._access_token = authenticate_dict["access_token"]
|
|
376
|
-
logger.debug("Access Token -> %s", self._access_token)
|
|
584
|
+
self.logger.debug("Access Token -> %s", self._access_token)
|
|
377
585
|
self._instance_url = authenticate_dict["instance_url"]
|
|
378
|
-
logger.debug("Instance URL -> %s", self._instance_url)
|
|
586
|
+
self.logger.debug("Instance URL -> %s", self._instance_url)
|
|
379
587
|
else:
|
|
380
|
-
logger.error(
|
|
588
|
+
self.logger.error(
|
|
381
589
|
"Failed to request an Salesforce Access Token; error -> %s",
|
|
382
590
|
response.text,
|
|
383
591
|
)
|
|
@@ -388,8 +596,11 @@ class Salesforce(object):
|
|
|
388
596
|
# end method definition
|
|
389
597
|
|
|
390
598
|
def get_object_id_by_name(
|
|
391
|
-
self,
|
|
392
|
-
|
|
599
|
+
self,
|
|
600
|
+
object_type: str,
|
|
601
|
+
name: str,
|
|
602
|
+
name_field: str = "Name",
|
|
603
|
+
) -> str | None:
|
|
393
604
|
"""Get the ID of a given Salesforce object with a given type and name.
|
|
394
605
|
|
|
395
606
|
Args:
|
|
@@ -399,6 +610,7 @@ class Salesforce(object):
|
|
|
399
610
|
|
|
400
611
|
Returns:
|
|
401
612
|
Optional[str]: Object ID or None if the request fails.
|
|
613
|
+
|
|
402
614
|
"""
|
|
403
615
|
|
|
404
616
|
if not self._access_token or not self._instance_url:
|
|
@@ -409,32 +621,21 @@ class Salesforce(object):
|
|
|
409
621
|
|
|
410
622
|
query = f"SELECT Id FROM {object_type} WHERE {name_field} = '{name}'"
|
|
411
623
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
request_header = self.request_header()
|
|
428
|
-
retries += 1
|
|
429
|
-
else:
|
|
430
|
-
logger.error(
|
|
431
|
-
"Failed to get Salesforce object ID for object type -> '%s' and object name -> '%s'; status -> %s; error -> %s",
|
|
432
|
-
object_type,
|
|
433
|
-
name,
|
|
434
|
-
response.status_code,
|
|
435
|
-
response.text,
|
|
436
|
-
)
|
|
437
|
-
return None
|
|
624
|
+
response = self.do_request(
|
|
625
|
+
method="GET",
|
|
626
|
+
url=request_url,
|
|
627
|
+
headers=request_header,
|
|
628
|
+
params={"q": query},
|
|
629
|
+
timeout=REQUEST_TIMEOUT,
|
|
630
|
+
failure_message="Failed to get Salesforce object ID for object type -> '{}' and object name -> '{}'".format(
|
|
631
|
+
object_type,
|
|
632
|
+
name,
|
|
633
|
+
),
|
|
634
|
+
)
|
|
635
|
+
if not response:
|
|
636
|
+
return None
|
|
637
|
+
|
|
638
|
+
return self.get_result_value(response, "Id")
|
|
438
639
|
|
|
439
640
|
# end method definition
|
|
440
641
|
|
|
@@ -450,16 +651,18 @@ class Salesforce(object):
|
|
|
450
651
|
|
|
451
652
|
Args:
|
|
452
653
|
object_type (str): Salesforce Business Object type. Such as "Account" or "Case".
|
|
453
|
-
search_field (str): object field to search in
|
|
454
|
-
search_value (str): value to search for
|
|
455
|
-
result_fields (list | None):
|
|
456
|
-
|
|
457
|
-
|
|
654
|
+
search_field (str): The object field to search in.
|
|
655
|
+
search_value (str): The value to search for.
|
|
656
|
+
result_fields (list | None):
|
|
657
|
+
List of fields to return. If None, then all standard fields
|
|
658
|
+
of the object will be returned.
|
|
659
|
+
limit (int, optional):
|
|
660
|
+
The maximum number of fields to return. Salesforce enforces 200 as upper limit.
|
|
458
661
|
|
|
459
662
|
Returns:
|
|
460
663
|
dict | None: Dictionary with the Salesforce object data.
|
|
461
664
|
|
|
462
|
-
|
|
665
|
+
Example:
|
|
463
666
|
{
|
|
464
667
|
'totalSize': 2,
|
|
465
668
|
'done': True,
|
|
@@ -480,20 +683,21 @@ class Salesforce(object):
|
|
|
480
683
|
}
|
|
481
684
|
]
|
|
482
685
|
}
|
|
686
|
+
|
|
483
687
|
"""
|
|
484
688
|
|
|
485
689
|
if not self._access_token or not self._instance_url:
|
|
486
690
|
self.authenticate()
|
|
487
691
|
|
|
488
692
|
if search_field and not search_value:
|
|
489
|
-
logger.error(
|
|
693
|
+
self.logger.error(
|
|
490
694
|
"No search value has been provided for search field -> %s!",
|
|
491
695
|
search_field,
|
|
492
696
|
)
|
|
493
697
|
return None
|
|
494
698
|
if not result_fields:
|
|
495
|
-
logger.debug(
|
|
496
|
-
"No result fields defined. Using 'FIELDS(STANDARD)' to deliver all standard fields of the object."
|
|
699
|
+
self.logger.debug(
|
|
700
|
+
"No result fields defined. Using 'FIELDS(STANDARD)' to deliver all standard fields of the object.",
|
|
497
701
|
)
|
|
498
702
|
result_fields = ["FIELDS(STANDARD)"]
|
|
499
703
|
|
|
@@ -505,45 +709,41 @@ class Salesforce(object):
|
|
|
505
709
|
request_header = self.request_header()
|
|
506
710
|
request_url = self.config()["queryUrl"] + "?q={}".format(query)
|
|
507
711
|
|
|
508
|
-
logger.debug(
|
|
509
|
-
"Sending query -> %s to Salesforce; calling -> %s",
|
|
712
|
+
self.logger.debug(
|
|
713
|
+
"Sending query -> %s to Salesforce; calling -> %s",
|
|
714
|
+
query,
|
|
715
|
+
request_url,
|
|
510
716
|
)
|
|
511
717
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
retries += 1
|
|
524
|
-
else:
|
|
525
|
-
logger.error(
|
|
526
|
-
"Failed to retrieve Salesforce object -> %s with %s = %s; status -> %s; error -> %s",
|
|
527
|
-
object_type,
|
|
528
|
-
search_field,
|
|
529
|
-
search_value,
|
|
530
|
-
response.status_code,
|
|
531
|
-
response.text,
|
|
532
|
-
)
|
|
533
|
-
return None
|
|
718
|
+
return self.do_request(
|
|
719
|
+
method="GET",
|
|
720
|
+
url=request_url,
|
|
721
|
+
headers=request_header,
|
|
722
|
+
timeout=REQUEST_TIMEOUT,
|
|
723
|
+
failure_message="Failed to retrieve Salesforce object type -> '{}' with {} = {}".format(
|
|
724
|
+
object_type,
|
|
725
|
+
search_field,
|
|
726
|
+
search_value,
|
|
727
|
+
),
|
|
728
|
+
)
|
|
534
729
|
|
|
535
730
|
# end method definition
|
|
536
731
|
|
|
537
|
-
def add_object(self, object_type: str, **kwargs:
|
|
538
|
-
"""Add object to Salesforce.
|
|
539
|
-
|
|
732
|
+
def add_object(self, object_type: str, **kwargs: dict[str, str]) -> dict | None:
|
|
733
|
+
"""Add object to Salesforce.
|
|
734
|
+
|
|
735
|
+
This is a generic wrapper method for the actual add methods.
|
|
540
736
|
|
|
541
737
|
Args:
|
|
542
|
-
object_type (str):
|
|
543
|
-
|
|
738
|
+
object_type (str):
|
|
739
|
+
Type of the Salesforce business object, like "Account" or "Case".
|
|
740
|
+
**kwargs (dict):
|
|
741
|
+
This is a keyword / value dictionary with additional parameters that depend
|
|
742
|
+
on the object type.
|
|
544
743
|
|
|
545
744
|
Returns:
|
|
546
745
|
dict | None: Dictionary with the Salesforce object data or None if the request fails.
|
|
746
|
+
|
|
547
747
|
"""
|
|
548
748
|
|
|
549
749
|
match object_type:
|
|
@@ -611,25 +811,30 @@ class Salesforce(object):
|
|
|
611
811
|
**kwargs,
|
|
612
812
|
)
|
|
613
813
|
case _:
|
|
614
|
-
logger.error(
|
|
814
|
+
self.logger.error(
|
|
615
815
|
"Unsupported Salesforce business object -> %s!",
|
|
616
816
|
object_type,
|
|
617
817
|
)
|
|
618
818
|
|
|
819
|
+
return None
|
|
820
|
+
|
|
619
821
|
# end method definition
|
|
620
822
|
|
|
621
|
-
def get_group_id(self,
|
|
823
|
+
def get_group_id(self, group_name: str) -> str | None:
|
|
622
824
|
"""Get a group ID by group name.
|
|
623
825
|
|
|
624
826
|
Args:
|
|
625
|
-
|
|
827
|
+
group_name (str): Name of the Group.
|
|
626
828
|
|
|
627
829
|
Returns:
|
|
628
830
|
Optional[str]: Technical ID of the group
|
|
831
|
+
|
|
629
832
|
"""
|
|
630
833
|
|
|
631
834
|
return self.get_object_id_by_name(
|
|
632
|
-
object_type="Group",
|
|
835
|
+
object_type="Group",
|
|
836
|
+
name=group_name,
|
|
837
|
+
name_field="Name",
|
|
633
838
|
)
|
|
634
839
|
|
|
635
840
|
# end method definition
|
|
@@ -638,10 +843,13 @@ class Salesforce(object):
|
|
|
638
843
|
"""Get a Salesforce group based on its ID.
|
|
639
844
|
|
|
640
845
|
Args:
|
|
641
|
-
group_id (str):
|
|
846
|
+
group_id (str):
|
|
847
|
+
ID of the Salesforce group to retrieve.
|
|
642
848
|
|
|
643
849
|
Returns:
|
|
644
|
-
dict | None:
|
|
850
|
+
dict | None:
|
|
851
|
+
Dictionary with the Salesforce group data or None if the request fails.
|
|
852
|
+
|
|
645
853
|
"""
|
|
646
854
|
|
|
647
855
|
if not self._access_token or not self._instance_url:
|
|
@@ -650,30 +858,21 @@ class Salesforce(object):
|
|
|
650
858
|
request_header = self.request_header()
|
|
651
859
|
request_url = self.config()["groupUrl"] + group_id
|
|
652
860
|
|
|
653
|
-
logger.debug(
|
|
654
|
-
"Get Salesforce group with ID -> %s; calling -> %s",
|
|
861
|
+
self.logger.debug(
|
|
862
|
+
"Get Salesforce group with ID -> %s; calling -> %s",
|
|
863
|
+
group_id,
|
|
864
|
+
request_url,
|
|
655
865
|
)
|
|
656
866
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
self.authenticate(revalidate=True)
|
|
667
|
-
request_header = self.request_header()
|
|
668
|
-
retries += 1
|
|
669
|
-
else:
|
|
670
|
-
logger.error(
|
|
671
|
-
"Failed to get Salesforce group -> %s; status -> %s; error -> %s",
|
|
672
|
-
group_id,
|
|
673
|
-
response.status_code,
|
|
674
|
-
response.text,
|
|
675
|
-
)
|
|
676
|
-
return None
|
|
867
|
+
return self.do_request(
|
|
868
|
+
method="GET",
|
|
869
|
+
url=request_url,
|
|
870
|
+
headers=request_header,
|
|
871
|
+
timeout=REQUEST_TIMEOUT,
|
|
872
|
+
failure_message="Failed to get Salesforce group with ID -> {}".format(
|
|
873
|
+
group_id,
|
|
874
|
+
),
|
|
875
|
+
)
|
|
677
876
|
|
|
678
877
|
# end method definition
|
|
679
878
|
|
|
@@ -686,16 +885,18 @@ class Salesforce(object):
|
|
|
686
885
|
|
|
687
886
|
Args:
|
|
688
887
|
group_name (str): Name of the new Salesforce group
|
|
888
|
+
group_type (str, optional): Type of the group. Default is "Regular".
|
|
689
889
|
|
|
690
890
|
Returns:
|
|
691
891
|
dict | None: Dictionary with the Salesforce Group data or None if the request fails.
|
|
692
892
|
|
|
693
|
-
|
|
893
|
+
Example:
|
|
694
894
|
{
|
|
695
895
|
'id': '00GDn000000KWE0MAO',
|
|
696
896
|
'success': True,
|
|
697
897
|
'errors': []
|
|
698
898
|
}
|
|
899
|
+
|
|
699
900
|
"""
|
|
700
901
|
|
|
701
902
|
if not self._access_token or not self._instance_url:
|
|
@@ -706,33 +907,20 @@ class Salesforce(object):
|
|
|
706
907
|
|
|
707
908
|
payload = {"Name": group_name, "Type": group_type}
|
|
708
909
|
|
|
709
|
-
logger.debug(
|
|
710
|
-
"Adding Salesforce group -> %s; calling -> %s",
|
|
910
|
+
self.logger.debug(
|
|
911
|
+
"Adding Salesforce group -> %s; calling -> %s",
|
|
912
|
+
group_name,
|
|
913
|
+
request_url,
|
|
711
914
|
)
|
|
712
915
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
if response.ok:
|
|
722
|
-
return self.parse_request_response(response)
|
|
723
|
-
elif response.status_code == 401 and retries == 0:
|
|
724
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
725
|
-
self.authenticate(revalidate=True)
|
|
726
|
-
request_header = self.request_header()
|
|
727
|
-
retries += 1
|
|
728
|
-
else:
|
|
729
|
-
logger.error(
|
|
730
|
-
"Failed to add Salesforce group -> %s; status -> %s; error -> %s",
|
|
731
|
-
group_name,
|
|
732
|
-
response.status_code,
|
|
733
|
-
response.text,
|
|
734
|
-
)
|
|
735
|
-
return None
|
|
916
|
+
return self.do_request(
|
|
917
|
+
method="POST",
|
|
918
|
+
url=request_url,
|
|
919
|
+
headers=request_header,
|
|
920
|
+
data=json.dumps(payload),
|
|
921
|
+
timeout=REQUEST_TIMEOUT,
|
|
922
|
+
failure_message="Failed to add Salesforce group -> '{}'".format(group_name),
|
|
923
|
+
)
|
|
736
924
|
|
|
737
925
|
# end method definition
|
|
738
926
|
|
|
@@ -740,7 +928,7 @@ class Salesforce(object):
|
|
|
740
928
|
self,
|
|
741
929
|
group_id: str,
|
|
742
930
|
update_data: dict,
|
|
743
|
-
) -> dict:
|
|
931
|
+
) -> dict | None:
|
|
744
932
|
"""Update a Salesforce group.
|
|
745
933
|
|
|
746
934
|
Args:
|
|
@@ -748,7 +936,8 @@ class Salesforce(object):
|
|
|
748
936
|
update_data (dict): Dictionary containing the fields to update.
|
|
749
937
|
|
|
750
938
|
Returns:
|
|
751
|
-
dict: Response from the Salesforce API.
|
|
939
|
+
dict | None: Response from the Salesforce API. None in case of an error.
|
|
940
|
+
|
|
752
941
|
"""
|
|
753
942
|
|
|
754
943
|
if not self._access_token or not self._instance_url:
|
|
@@ -758,48 +947,37 @@ class Salesforce(object):
|
|
|
758
947
|
|
|
759
948
|
request_url = self.config()["groupUrl"] + group_id
|
|
760
949
|
|
|
761
|
-
logger.debug(
|
|
950
|
+
self.logger.debug(
|
|
762
951
|
"Update Salesforce group with ID -> %s; calling -> %s",
|
|
763
952
|
group_id,
|
|
764
953
|
request_url,
|
|
765
954
|
)
|
|
766
955
|
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
elif response.status_code == 401 and retries == 0:
|
|
778
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
779
|
-
self.authenticate(revalidate=True)
|
|
780
|
-
request_header = self.request_header()
|
|
781
|
-
retries += 1
|
|
782
|
-
else:
|
|
783
|
-
logger.error(
|
|
784
|
-
"Failed to update Salesforce group -> %s; status -> %s; error -> %s",
|
|
785
|
-
group_id,
|
|
786
|
-
response.status_code,
|
|
787
|
-
response.text,
|
|
788
|
-
)
|
|
789
|
-
return None
|
|
956
|
+
return self.do_request(
|
|
957
|
+
method="PATCH",
|
|
958
|
+
url=request_url,
|
|
959
|
+
headers=request_header,
|
|
960
|
+
json_data=update_data,
|
|
961
|
+
timeout=REQUEST_TIMEOUT,
|
|
962
|
+
failure_message="Failed to update Salesforce group with ID -> {}".format(
|
|
963
|
+
group_id,
|
|
964
|
+
),
|
|
965
|
+
)
|
|
790
966
|
|
|
791
967
|
# end method definition
|
|
792
968
|
|
|
793
969
|
def get_group_members(self, group_id: str) -> list | None:
|
|
794
|
-
"""Get Salesforce group members
|
|
970
|
+
"""Get Salesforce group members.
|
|
795
971
|
|
|
796
972
|
Args:
|
|
797
|
-
group_id (str):
|
|
973
|
+
group_id (str):
|
|
974
|
+
The ID of the group to retrieve the members.
|
|
798
975
|
|
|
799
976
|
Returns:
|
|
800
|
-
list | None:
|
|
977
|
+
list | None:
|
|
978
|
+
The group members.
|
|
801
979
|
|
|
802
|
-
|
|
980
|
+
Example:
|
|
803
981
|
{
|
|
804
982
|
'totalSize': 1,
|
|
805
983
|
'done': True,
|
|
@@ -813,6 +991,7 @@ class Salesforce(object):
|
|
|
813
991
|
}
|
|
814
992
|
]
|
|
815
993
|
}
|
|
994
|
+
|
|
816
995
|
"""
|
|
817
996
|
|
|
818
997
|
if not self._access_token or not self._instance_url:
|
|
@@ -825,54 +1004,45 @@ class Salesforce(object):
|
|
|
825
1004
|
query = f"SELECT UserOrGroupId FROM GroupMember WHERE GroupId = '{group_id}'"
|
|
826
1005
|
params = {"q": query}
|
|
827
1006
|
|
|
828
|
-
logger.debug(
|
|
1007
|
+
self.logger.debug(
|
|
829
1008
|
"Get members of Salesforce group with ID -> %s; calling -> %s",
|
|
830
1009
|
group_id,
|
|
831
1010
|
request_url,
|
|
832
1011
|
)
|
|
833
1012
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
elif response.status_code == 401 and retries == 0:
|
|
845
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
846
|
-
self.authenticate(revalidate=True)
|
|
847
|
-
request_header = self.request_header()
|
|
848
|
-
retries += 1
|
|
849
|
-
else:
|
|
850
|
-
logger.error(
|
|
851
|
-
"Failed to retrieve members of Salesforce group with ID -> %s; status -> %s; error -> %s",
|
|
852
|
-
group_id,
|
|
853
|
-
response.status_code,
|
|
854
|
-
response.text,
|
|
855
|
-
)
|
|
856
|
-
return None
|
|
1013
|
+
return self.do_request(
|
|
1014
|
+
method="GET",
|
|
1015
|
+
url=request_url,
|
|
1016
|
+
headers=request_header,
|
|
1017
|
+
params=params,
|
|
1018
|
+
timeout=REQUEST_TIMEOUT,
|
|
1019
|
+
failure_message="Failed to get members of Salesforce group with ID -> {}".format(
|
|
1020
|
+
group_id,
|
|
1021
|
+
),
|
|
1022
|
+
)
|
|
857
1023
|
|
|
858
1024
|
# end method definition
|
|
859
1025
|
|
|
860
1026
|
def add_group_member(self, group_id: str, member_id: str) -> dict | None:
|
|
861
|
-
"""Add a user or group to a Salesforce group
|
|
1027
|
+
"""Add a user or group to a Salesforce group.
|
|
862
1028
|
|
|
863
1029
|
Args:
|
|
864
|
-
group_id (str):
|
|
865
|
-
|
|
1030
|
+
group_id (str):
|
|
1031
|
+
The ID of the Salesforce Group to add member to.
|
|
1032
|
+
member_id (str):
|
|
1033
|
+
The ID of the user or group.
|
|
866
1034
|
|
|
867
1035
|
Returns:
|
|
868
|
-
dict | None:
|
|
1036
|
+
dict | None:
|
|
1037
|
+
Dictionary with the Salesforce membership data or None if the request fails.
|
|
869
1038
|
|
|
870
|
-
|
|
1039
|
+
Example response (id is the membership ID):
|
|
871
1040
|
{
|
|
872
1041
|
'id': '011Dn000000ELhwIAG',
|
|
873
1042
|
'success': True,
|
|
874
1043
|
'errors': []
|
|
875
1044
|
}
|
|
1045
|
+
|
|
876
1046
|
"""
|
|
877
1047
|
|
|
878
1048
|
if not self._access_token or not self._instance_url:
|
|
@@ -884,46 +1054,35 @@ class Salesforce(object):
|
|
|
884
1054
|
|
|
885
1055
|
payload = {"GroupId": group_id, "UserOrGroupId": member_id}
|
|
886
1056
|
|
|
887
|
-
logger.debug(
|
|
1057
|
+
self.logger.debug(
|
|
888
1058
|
"Add member with ID -> %s to Salesforce group with ID -> %s; calling -> %s",
|
|
889
1059
|
member_id,
|
|
890
1060
|
group_id,
|
|
891
1061
|
request_url,
|
|
892
1062
|
)
|
|
893
1063
|
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
906
|
-
self.authenticate(revalidate=True)
|
|
907
|
-
request_header = self.request_header()
|
|
908
|
-
retries += 1
|
|
909
|
-
else:
|
|
910
|
-
logger.error(
|
|
911
|
-
"Failed to retrieve members of Salesforce group with ID -> %s; status -> %s; error -> %s",
|
|
912
|
-
group_id,
|
|
913
|
-
response.status_code,
|
|
914
|
-
response.text,
|
|
915
|
-
)
|
|
916
|
-
return None
|
|
1064
|
+
return self.do_request(
|
|
1065
|
+
method="POST",
|
|
1066
|
+
url=request_url,
|
|
1067
|
+
headers=request_header,
|
|
1068
|
+
json_data=payload,
|
|
1069
|
+
timeout=REQUEST_TIMEOUT,
|
|
1070
|
+
failure_message="Failed to add member with ID -> {} to Salesforce group with ID -> {}".format(
|
|
1071
|
+
member_id,
|
|
1072
|
+
group_id,
|
|
1073
|
+
),
|
|
1074
|
+
)
|
|
917
1075
|
|
|
918
1076
|
# end method definition
|
|
919
1077
|
|
|
920
1078
|
def get_all_user_profiles(self) -> dict | None:
|
|
921
|
-
"""Get all user profiles
|
|
1079
|
+
"""Get all user profiles.
|
|
922
1080
|
|
|
923
1081
|
Returns:
|
|
924
|
-
dict | None:
|
|
1082
|
+
dict | None:
|
|
1083
|
+
Dictionary with Salesforce user profiles.
|
|
925
1084
|
|
|
926
|
-
|
|
1085
|
+
Example response:
|
|
927
1086
|
{
|
|
928
1087
|
'totalSize': 15,
|
|
929
1088
|
'done': True,
|
|
@@ -935,8 +1094,7 @@ class Salesforce(object):
|
|
|
935
1094
|
'url': '/services/data/v52.0/sobjects/Profile/00eDn000001msL8IAI'},
|
|
936
1095
|
'Id': '00eDn000001msL8IAI',
|
|
937
1096
|
'Name': 'Standard User',
|
|
938
|
-
'CreatedById':
|
|
939
|
-
'005Dn000001rRodIAE',
|
|
1097
|
+
'CreatedById': '005Dn000001rRodIAE',
|
|
940
1098
|
'CreatedDate': '2022-11-30T15:30:54.000+0000',
|
|
941
1099
|
'Description': None,
|
|
942
1100
|
'LastModifiedById': '005Dn000001rUacIAE',
|
|
@@ -948,6 +1106,7 @@ class Salesforce(object):
|
|
|
948
1106
|
}, ...
|
|
949
1107
|
]
|
|
950
1108
|
}
|
|
1109
|
+
|
|
951
1110
|
"""
|
|
952
1111
|
|
|
953
1112
|
if not self._access_token or not self._instance_url:
|
|
@@ -958,32 +1117,18 @@ class Salesforce(object):
|
|
|
958
1117
|
|
|
959
1118
|
query = "SELECT Id, Name, CreatedById, CreatedDate, Description, LastModifiedById, LastModifiedDate, PermissionsCustomizeApplication, PermissionsEditTask, PermissionsImportLeads FROM Profile"
|
|
960
1119
|
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
if response.ok:
|
|
970
|
-
return self.parse_request_response(response)
|
|
971
|
-
elif response.status_code == 401 and retries == 0:
|
|
972
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
973
|
-
self.authenticate(revalidate=True)
|
|
974
|
-
request_header = self.request_header()
|
|
975
|
-
retries += 1
|
|
976
|
-
else:
|
|
977
|
-
logger.error(
|
|
978
|
-
"Failed to get Salesforce user profiles; status -> %s; error -> %s",
|
|
979
|
-
response.status_code,
|
|
980
|
-
response.text,
|
|
981
|
-
)
|
|
982
|
-
return None
|
|
1120
|
+
return self.do_request(
|
|
1121
|
+
method="GET",
|
|
1122
|
+
url=request_url,
|
|
1123
|
+
headers=request_header,
|
|
1124
|
+
params={"q": query},
|
|
1125
|
+
timeout=REQUEST_TIMEOUT,
|
|
1126
|
+
failure_message="Failed to get Salesforce user profiles",
|
|
1127
|
+
)
|
|
983
1128
|
|
|
984
1129
|
# end method definition
|
|
985
1130
|
|
|
986
|
-
def get_user_profile_id(self, profile_name: str) ->
|
|
1131
|
+
def get_user_profile_id(self, profile_name: str) -> str | None:
|
|
987
1132
|
"""Get a user profile ID by profile name.
|
|
988
1133
|
|
|
989
1134
|
Args:
|
|
@@ -991,13 +1136,14 @@ class Salesforce(object):
|
|
|
991
1136
|
|
|
992
1137
|
Returns:
|
|
993
1138
|
Optional[str]: Technical ID of the user profile.
|
|
1139
|
+
|
|
994
1140
|
"""
|
|
995
1141
|
|
|
996
1142
|
return self.get_object_id_by_name(object_type="Profile", name=profile_name)
|
|
997
1143
|
|
|
998
1144
|
# end method definition
|
|
999
1145
|
|
|
1000
|
-
def get_user_id(self, username: str) ->
|
|
1146
|
+
def get_user_id(self, username: str) -> str | None:
|
|
1001
1147
|
"""Get a user ID by user name.
|
|
1002
1148
|
|
|
1003
1149
|
Args:
|
|
@@ -1005,10 +1151,13 @@ class Salesforce(object):
|
|
|
1005
1151
|
|
|
1006
1152
|
Returns:
|
|
1007
1153
|
Optional[str]: Technical ID of the user
|
|
1154
|
+
|
|
1008
1155
|
"""
|
|
1009
1156
|
|
|
1010
1157
|
return self.get_object_id_by_name(
|
|
1011
|
-
object_type="User",
|
|
1158
|
+
object_type="User",
|
|
1159
|
+
name=username,
|
|
1160
|
+
name_field="Username",
|
|
1012
1161
|
)
|
|
1013
1162
|
|
|
1014
1163
|
# end method definition
|
|
@@ -1021,6 +1170,7 @@ class Salesforce(object):
|
|
|
1021
1170
|
|
|
1022
1171
|
Returns:
|
|
1023
1172
|
dict | None: Dictionary with the Salesforce user data or None if the request fails.
|
|
1173
|
+
|
|
1024
1174
|
"""
|
|
1025
1175
|
|
|
1026
1176
|
if not self._access_token or not self._instance_url:
|
|
@@ -1029,30 +1179,21 @@ class Salesforce(object):
|
|
|
1029
1179
|
request_header = self.request_header()
|
|
1030
1180
|
request_url = self.config()["userUrl"] + user_id
|
|
1031
1181
|
|
|
1032
|
-
logger.debug(
|
|
1033
|
-
"Get Salesforce user with ID -> %s; calling -> %s",
|
|
1182
|
+
self.logger.debug(
|
|
1183
|
+
"Get Salesforce user with ID -> %s; calling -> %s",
|
|
1184
|
+
user_id,
|
|
1185
|
+
request_url,
|
|
1034
1186
|
)
|
|
1035
1187
|
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
self.authenticate(revalidate=True)
|
|
1046
|
-
request_header = self.request_header()
|
|
1047
|
-
retries += 1
|
|
1048
|
-
else:
|
|
1049
|
-
logger.error(
|
|
1050
|
-
"Failed to get Salesforce user -> %s; status -> %s; error -> %s",
|
|
1051
|
-
user_id,
|
|
1052
|
-
response.status_code,
|
|
1053
|
-
response.text,
|
|
1054
|
-
)
|
|
1055
|
-
return None
|
|
1188
|
+
return self.do_request(
|
|
1189
|
+
method="GET",
|
|
1190
|
+
url=request_url,
|
|
1191
|
+
headers=request_header,
|
|
1192
|
+
timeout=REQUEST_TIMEOUT,
|
|
1193
|
+
failure_message="Failed to get Salesforce user with ID -> {}".format(
|
|
1194
|
+
user_id,
|
|
1195
|
+
),
|
|
1196
|
+
)
|
|
1056
1197
|
|
|
1057
1198
|
# end method definition
|
|
1058
1199
|
|
|
@@ -1065,34 +1206,48 @@ class Salesforce(object):
|
|
|
1065
1206
|
title: str | None = None,
|
|
1066
1207
|
department: str | None = None,
|
|
1067
1208
|
company_name: str = "Innovate",
|
|
1068
|
-
profile_name:
|
|
1069
|
-
profile_id:
|
|
1070
|
-
time_zone_key:
|
|
1071
|
-
email_encoding_key:
|
|
1072
|
-
locale_key:
|
|
1073
|
-
alias:
|
|
1209
|
+
profile_name: str | None = "Standard User",
|
|
1210
|
+
profile_id: str | None = None,
|
|
1211
|
+
time_zone_key: str | None = "America/Los_Angeles",
|
|
1212
|
+
email_encoding_key: str | None = "ISO-8859-1",
|
|
1213
|
+
locale_key: str | None = "en_US",
|
|
1214
|
+
alias: str | None = None,
|
|
1074
1215
|
) -> dict | None:
|
|
1075
1216
|
"""Add a new Salesforce user. The password has to be set separately.
|
|
1076
1217
|
|
|
1077
1218
|
Args:
|
|
1078
|
-
username (str):
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1219
|
+
username (str):
|
|
1220
|
+
Login name of the new user
|
|
1221
|
+
email (str):
|
|
1222
|
+
Email of the new user
|
|
1223
|
+
firstname (str):
|
|
1224
|
+
First name of the new user.
|
|
1225
|
+
lastname (str):
|
|
1226
|
+
Last name of the new user.
|
|
1227
|
+
title (str):
|
|
1228
|
+
Title of the user.
|
|
1229
|
+
department (str):
|
|
1230
|
+
Department of the user.
|
|
1231
|
+
company_name (str):
|
|
1232
|
+
Name of the Company of the user.
|
|
1233
|
+
profile_name (str):
|
|
1234
|
+
Profile name like "Standard User"
|
|
1235
|
+
profile_id (str, optional):
|
|
1236
|
+
Profile ID of the new user. Defaults to None.
|
|
1237
|
+
Use method get_all_user_profiles() to determine
|
|
1238
|
+
the desired Profile for the user. Or pass the profile_name.
|
|
1239
|
+
time_zone_key (str, optional):
|
|
1240
|
+
Timezone provided in format country/city like "America/Los_Angeles",
|
|
1241
|
+
email_encoding_key (str, optional):
|
|
1242
|
+
Default is "ISO-8859-1".
|
|
1243
|
+
locale_key (str, optional):
|
|
1244
|
+
Default is "en_US".
|
|
1245
|
+
alias (str, optional):
|
|
1246
|
+
Alias of the new user. Defaults to None.
|
|
1093
1247
|
|
|
1094
1248
|
Returns:
|
|
1095
1249
|
dict | None: Dictionary with the Salesforce User data or None if the request fails.
|
|
1250
|
+
|
|
1096
1251
|
"""
|
|
1097
1252
|
|
|
1098
1253
|
if not self._access_token or not self._instance_url:
|
|
@@ -1121,33 +1276,20 @@ class Salesforce(object):
|
|
|
1121
1276
|
"LanguageLocaleKey": locale_key, # Set default LanguageLocaleKey
|
|
1122
1277
|
}
|
|
1123
1278
|
|
|
1124
|
-
logger.debug(
|
|
1125
|
-
"Adding Salesforce user -> %s; calling -> %s",
|
|
1279
|
+
self.logger.debug(
|
|
1280
|
+
"Adding Salesforce user -> %s; calling -> %s",
|
|
1281
|
+
username,
|
|
1282
|
+
request_url,
|
|
1126
1283
|
)
|
|
1127
1284
|
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
if response.ok:
|
|
1137
|
-
return self.parse_request_response(response)
|
|
1138
|
-
elif response.status_code == 401 and retries == 0:
|
|
1139
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
1140
|
-
self.authenticate(revalidate=True)
|
|
1141
|
-
request_header = self.request_header()
|
|
1142
|
-
retries += 1
|
|
1143
|
-
else:
|
|
1144
|
-
logger.error(
|
|
1145
|
-
"Failed to add Salesforce user -> %s; status -> %s; error -> %s",
|
|
1146
|
-
username,
|
|
1147
|
-
response.status_code,
|
|
1148
|
-
response.text,
|
|
1149
|
-
)
|
|
1150
|
-
return None
|
|
1285
|
+
return self.do_request(
|
|
1286
|
+
method="POST",
|
|
1287
|
+
url=request_url,
|
|
1288
|
+
headers=request_header,
|
|
1289
|
+
data=json.dumps(payload),
|
|
1290
|
+
timeout=REQUEST_TIMEOUT,
|
|
1291
|
+
failure_message="Failed to add Salesforce user -> {}".format(username),
|
|
1292
|
+
)
|
|
1151
1293
|
|
|
1152
1294
|
# end method definition
|
|
1153
1295
|
|
|
@@ -1164,6 +1306,7 @@ class Salesforce(object):
|
|
|
1164
1306
|
|
|
1165
1307
|
Returns:
|
|
1166
1308
|
dict: Response from the Salesforce API.
|
|
1309
|
+
|
|
1167
1310
|
"""
|
|
1168
1311
|
|
|
1169
1312
|
if not self._access_token or not self._instance_url:
|
|
@@ -1173,33 +1316,22 @@ class Salesforce(object):
|
|
|
1173
1316
|
|
|
1174
1317
|
request_url = self.config()["userUrl"] + user_id
|
|
1175
1318
|
|
|
1176
|
-
logger.debug(
|
|
1177
|
-
"Update Salesforce user with ID -> %s; calling -> %s",
|
|
1319
|
+
self.logger.debug(
|
|
1320
|
+
"Update Salesforce user with ID -> %s; calling -> %s",
|
|
1321
|
+
user_id,
|
|
1322
|
+
request_url,
|
|
1178
1323
|
)
|
|
1179
1324
|
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
elif response.status_code == 401 and retries == 0:
|
|
1191
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
1192
|
-
self.authenticate(revalidate=True)
|
|
1193
|
-
request_header = self.request_header()
|
|
1194
|
-
retries += 1
|
|
1195
|
-
else:
|
|
1196
|
-
logger.error(
|
|
1197
|
-
"Failed to update Salesforce user -> %s; status -> %s; error -> %s",
|
|
1198
|
-
user_id,
|
|
1199
|
-
response.status_code,
|
|
1200
|
-
response.text,
|
|
1201
|
-
)
|
|
1202
|
-
return None
|
|
1325
|
+
return self.do_request(
|
|
1326
|
+
method="PATCH",
|
|
1327
|
+
url=request_url,
|
|
1328
|
+
headers=request_header,
|
|
1329
|
+
json_data=update_data,
|
|
1330
|
+
timeout=REQUEST_TIMEOUT,
|
|
1331
|
+
failure_message="Failed to update Salesforce user with ID -> {}".format(
|
|
1332
|
+
user_id,
|
|
1333
|
+
),
|
|
1334
|
+
)
|
|
1203
1335
|
|
|
1204
1336
|
# end method definition
|
|
1205
1337
|
|
|
@@ -1207,7 +1339,7 @@ class Salesforce(object):
|
|
|
1207
1339
|
self,
|
|
1208
1340
|
user_id: str,
|
|
1209
1341
|
password: str,
|
|
1210
|
-
) -> dict:
|
|
1342
|
+
) -> dict | None:
|
|
1211
1343
|
"""Update the password of a Salesforce user.
|
|
1212
1344
|
|
|
1213
1345
|
Args:
|
|
@@ -1216,6 +1348,7 @@ class Salesforce(object):
|
|
|
1216
1348
|
|
|
1217
1349
|
Returns:
|
|
1218
1350
|
dict: Response from the Salesforce API.
|
|
1351
|
+
|
|
1219
1352
|
"""
|
|
1220
1353
|
|
|
1221
1354
|
if not self._access_token or not self._instance_url:
|
|
@@ -1225,7 +1358,7 @@ class Salesforce(object):
|
|
|
1225
1358
|
|
|
1226
1359
|
request_url = self.config()["userUrl"] + "{}/password".format(user_id)
|
|
1227
1360
|
|
|
1228
|
-
logger.debug(
|
|
1361
|
+
self.logger.debug(
|
|
1229
1362
|
"Update password of Salesforce user with ID -> %s; calling -> %s",
|
|
1230
1363
|
user_id,
|
|
1231
1364
|
request_url,
|
|
@@ -1233,29 +1366,16 @@ class Salesforce(object):
|
|
|
1233
1366
|
|
|
1234
1367
|
update_data = {"NewPassword": password}
|
|
1235
1368
|
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
elif response.status_code == 401 and retries == 0:
|
|
1247
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
1248
|
-
self.authenticate(revalidate=True)
|
|
1249
|
-
request_header = self.request_header()
|
|
1250
|
-
retries += 1
|
|
1251
|
-
else:
|
|
1252
|
-
logger.error(
|
|
1253
|
-
"Failed to update password of Salesforce user -> %s; status -> %s; error -> %s",
|
|
1254
|
-
user_id,
|
|
1255
|
-
response.status_code,
|
|
1256
|
-
response.text,
|
|
1257
|
-
)
|
|
1258
|
-
return None
|
|
1369
|
+
return self.do_request(
|
|
1370
|
+
method="POST",
|
|
1371
|
+
url=request_url,
|
|
1372
|
+
headers=request_header,
|
|
1373
|
+
json_data=update_data,
|
|
1374
|
+
timeout=REQUEST_TIMEOUT,
|
|
1375
|
+
failure_message="Failed to update password of Salesforce user with ID -> {}".format(
|
|
1376
|
+
user_id,
|
|
1377
|
+
),
|
|
1378
|
+
)
|
|
1259
1379
|
|
|
1260
1380
|
# end method definition
|
|
1261
1381
|
|
|
@@ -1269,8 +1389,10 @@ class Salesforce(object):
|
|
|
1269
1389
|
Args:
|
|
1270
1390
|
user_id (str): Salesforce ID of the user
|
|
1271
1391
|
photo_path (str): file system path with the location of the photo
|
|
1392
|
+
|
|
1272
1393
|
Returns:
|
|
1273
1394
|
dict | None: Dictionary with the Salesforce User data or None if the request fails.
|
|
1395
|
+
|
|
1274
1396
|
"""
|
|
1275
1397
|
|
|
1276
1398
|
if not self._access_token or not self._instance_url:
|
|
@@ -1278,20 +1400,23 @@ class Salesforce(object):
|
|
|
1278
1400
|
|
|
1279
1401
|
# Check if the photo file exists
|
|
1280
1402
|
if not os.path.isfile(photo_path):
|
|
1281
|
-
logger.error("Photo file -> %s not found!", photo_path)
|
|
1403
|
+
self.logger.error("Photo file -> %s not found!", photo_path)
|
|
1282
1404
|
return None
|
|
1283
1405
|
|
|
1284
1406
|
try:
|
|
1285
1407
|
# Read the photo file as binary data
|
|
1286
1408
|
with open(photo_path, "rb") as image_file:
|
|
1287
1409
|
photo_data = image_file.read()
|
|
1288
|
-
except OSError
|
|
1410
|
+
except OSError:
|
|
1289
1411
|
# Handle any errors that occurred while reading the photo file
|
|
1290
|
-
logger.error(
|
|
1291
|
-
"Error reading photo file -> %s
|
|
1412
|
+
self.logger.error(
|
|
1413
|
+
"Error reading photo file -> %s",
|
|
1414
|
+
photo_path,
|
|
1292
1415
|
)
|
|
1293
1416
|
return None
|
|
1294
1417
|
|
|
1418
|
+
# Content Type = None is important as upload calls need
|
|
1419
|
+
# a multipart header that is automatically selected if None is used:
|
|
1295
1420
|
request_header = self.request_header(content_type=None)
|
|
1296
1421
|
|
|
1297
1422
|
data = {"json": json.dumps({"cropX": 0, "cropY": 0, "cropSize": 200})}
|
|
@@ -1301,40 +1426,27 @@ class Salesforce(object):
|
|
|
1301
1426
|
photo_path,
|
|
1302
1427
|
photo_data,
|
|
1303
1428
|
"application/octet-stream",
|
|
1304
|
-
)
|
|
1429
|
+
),
|
|
1305
1430
|
}
|
|
1306
1431
|
|
|
1307
|
-
logger.debug(
|
|
1432
|
+
self.logger.debug(
|
|
1308
1433
|
"Update profile photo of Salesforce user with ID -> %s; calling -> %s",
|
|
1309
1434
|
user_id,
|
|
1310
1435
|
request_url,
|
|
1311
1436
|
)
|
|
1312
1437
|
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
)
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
elif response.status_code == 401 and retries == 0:
|
|
1326
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
1327
|
-
self.authenticate(revalidate=True)
|
|
1328
|
-
request_header = self.request_header()
|
|
1329
|
-
retries += 1
|
|
1330
|
-
else:
|
|
1331
|
-
logger.error(
|
|
1332
|
-
"Failed to update profile photo of Salesforce user with ID -> %s; status -> %s; error -> %s",
|
|
1333
|
-
user_id,
|
|
1334
|
-
response.status_code,
|
|
1335
|
-
response.text,
|
|
1336
|
-
)
|
|
1337
|
-
return None
|
|
1438
|
+
return self.do_request(
|
|
1439
|
+
method="POST",
|
|
1440
|
+
url=request_url,
|
|
1441
|
+
headers=request_header,
|
|
1442
|
+
files=files,
|
|
1443
|
+
data=data,
|
|
1444
|
+
timeout=REQUEST_TIMEOUT,
|
|
1445
|
+
failure_message="Failed to update profile photo of Salesforce user with ID -> {}".format(
|
|
1446
|
+
user_id,
|
|
1447
|
+
),
|
|
1448
|
+
verify=False,
|
|
1449
|
+
)
|
|
1338
1450
|
|
|
1339
1451
|
# end method definition
|
|
1340
1452
|
|
|
@@ -1343,11 +1455,11 @@ class Salesforce(object):
|
|
|
1343
1455
|
account_name: str,
|
|
1344
1456
|
account_number: str,
|
|
1345
1457
|
account_type: str = "Customer",
|
|
1346
|
-
description:
|
|
1347
|
-
industry:
|
|
1348
|
-
website:
|
|
1349
|
-
phone:
|
|
1350
|
-
**kwargs:
|
|
1458
|
+
description: str | None = None,
|
|
1459
|
+
industry: str | None = None,
|
|
1460
|
+
website: str | None = None,
|
|
1461
|
+
phone: str | None = None,
|
|
1462
|
+
**kwargs: dict[str, str],
|
|
1351
1463
|
) -> dict | None:
|
|
1352
1464
|
"""Add a new Account object to Salesforce.
|
|
1353
1465
|
|
|
@@ -1359,10 +1471,11 @@ class Salesforce(object):
|
|
|
1359
1471
|
industry (str, optional): Industry of the new Salesforce account. Defaults to None.
|
|
1360
1472
|
website (str, optional): Website of the new Salesforce account. Defaults to None.
|
|
1361
1473
|
phone (str, optional): Phone number of the new Salesforce account. Defaults to None.
|
|
1362
|
-
kwargs (
|
|
1474
|
+
kwargs (dict): Additional values (e.g. custom fields)
|
|
1363
1475
|
|
|
1364
1476
|
Returns:
|
|
1365
1477
|
dict | None: Dictionary with the Salesforce Account data or None if the request fails.
|
|
1478
|
+
|
|
1366
1479
|
"""
|
|
1367
1480
|
|
|
1368
1481
|
if not self._access_token or not self._instance_url:
|
|
@@ -1382,33 +1495,24 @@ class Salesforce(object):
|
|
|
1382
1495
|
}
|
|
1383
1496
|
payload.update(kwargs) # Add additional fields from kwargs
|
|
1384
1497
|
|
|
1385
|
-
logger.debug(
|
|
1386
|
-
"Adding Salesforce account -> %s; calling -> %s",
|
|
1498
|
+
self.logger.debug(
|
|
1499
|
+
"Adding Salesforce account -> '%s' (%s); calling -> %s",
|
|
1500
|
+
account_name,
|
|
1501
|
+
account_number,
|
|
1502
|
+
request_url,
|
|
1387
1503
|
)
|
|
1388
1504
|
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
1401
|
-
self.authenticate(revalidate=True)
|
|
1402
|
-
request_header = self.request_header()
|
|
1403
|
-
retries += 1
|
|
1404
|
-
else:
|
|
1405
|
-
logger.error(
|
|
1406
|
-
"Failed to add Salesforce account -> %s; status -> %s; error -> %s",
|
|
1407
|
-
account_name,
|
|
1408
|
-
response.status_code,
|
|
1409
|
-
response.text,
|
|
1410
|
-
)
|
|
1411
|
-
return None
|
|
1505
|
+
return self.do_request(
|
|
1506
|
+
method="POST",
|
|
1507
|
+
url=request_url,
|
|
1508
|
+
headers=request_header,
|
|
1509
|
+
data=json.dumps(payload),
|
|
1510
|
+
timeout=REQUEST_TIMEOUT,
|
|
1511
|
+
failure_message="Failed to add Salesforce account -> '{}' ({})".format(
|
|
1512
|
+
account_name,
|
|
1513
|
+
account_number,
|
|
1514
|
+
),
|
|
1515
|
+
)
|
|
1412
1516
|
|
|
1413
1517
|
# end method definition
|
|
1414
1518
|
|
|
@@ -1418,18 +1522,25 @@ class Salesforce(object):
|
|
|
1418
1522
|
product_code: str,
|
|
1419
1523
|
description: str,
|
|
1420
1524
|
price: float,
|
|
1421
|
-
**kwargs:
|
|
1525
|
+
**kwargs: dict[str, str],
|
|
1422
1526
|
) -> dict | None:
|
|
1423
1527
|
"""Add a new Product object to Salesforce.
|
|
1424
1528
|
|
|
1425
1529
|
Args:
|
|
1426
|
-
product_name (str):
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1530
|
+
product_name (str):
|
|
1531
|
+
Name of the Salesforce Product.
|
|
1532
|
+
product_code (str):
|
|
1533
|
+
Code of the Salesforce Product.
|
|
1534
|
+
description (str):
|
|
1535
|
+
Description of the Salesforce Product.
|
|
1536
|
+
price (float):
|
|
1537
|
+
Price of the Salesforce Product.
|
|
1538
|
+
kwargs:
|
|
1539
|
+
Additional keyword arguments.
|
|
1430
1540
|
|
|
1431
1541
|
Returns:
|
|
1432
1542
|
dict | None: Dictionary with the Salesforce Product data or None if the request fails.
|
|
1543
|
+
|
|
1433
1544
|
"""
|
|
1434
1545
|
|
|
1435
1546
|
if not self._access_token or not self._instance_url:
|
|
@@ -1446,33 +1557,24 @@ class Salesforce(object):
|
|
|
1446
1557
|
}
|
|
1447
1558
|
payload.update(kwargs) # Add additional fields from kwargs
|
|
1448
1559
|
|
|
1449
|
-
logger.debug(
|
|
1450
|
-
"Add Salesforce product -> %s; calling -> %s",
|
|
1560
|
+
self.logger.debug(
|
|
1561
|
+
"Add Salesforce product -> '%s' (%s); calling -> %s",
|
|
1562
|
+
product_name,
|
|
1563
|
+
product_code,
|
|
1564
|
+
request_url,
|
|
1451
1565
|
)
|
|
1452
1566
|
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
1465
|
-
self.authenticate(revalidate=True)
|
|
1466
|
-
request_header = self.request_header()
|
|
1467
|
-
retries += 1
|
|
1468
|
-
else:
|
|
1469
|
-
logger.error(
|
|
1470
|
-
"Failed to add Salesforce product -> %s; status -> %s; error -> %s",
|
|
1471
|
-
product_name,
|
|
1472
|
-
response.status_code,
|
|
1473
|
-
response.text,
|
|
1474
|
-
)
|
|
1475
|
-
return None
|
|
1567
|
+
return self.do_request(
|
|
1568
|
+
method="POST",
|
|
1569
|
+
url=request_url,
|
|
1570
|
+
headers=request_header,
|
|
1571
|
+
data=json.dumps(payload),
|
|
1572
|
+
timeout=REQUEST_TIMEOUT,
|
|
1573
|
+
failure_message="Failed to add Salesforce product -> '{}' ({})".format(
|
|
1574
|
+
product_name,
|
|
1575
|
+
product_code,
|
|
1576
|
+
),
|
|
1577
|
+
)
|
|
1476
1578
|
|
|
1477
1579
|
# end method definition
|
|
1478
1580
|
|
|
@@ -1481,25 +1583,38 @@ class Salesforce(object):
|
|
|
1481
1583
|
name: str,
|
|
1482
1584
|
stage: str,
|
|
1483
1585
|
close_date: str,
|
|
1484
|
-
amount:
|
|
1586
|
+
amount: float,
|
|
1485
1587
|
account_id: str,
|
|
1486
|
-
description: str = None,
|
|
1487
|
-
**kwargs:
|
|
1588
|
+
description: str | None = None,
|
|
1589
|
+
**kwargs: dict[str, str],
|
|
1488
1590
|
) -> dict | None:
|
|
1489
1591
|
"""Add a new Opportunity object to Salesfoce.
|
|
1490
1592
|
|
|
1491
1593
|
Args:
|
|
1492
1594
|
name (str): Name of the Opportunity.
|
|
1493
|
-
stage (str):
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1595
|
+
stage (str):
|
|
1596
|
+
Stage of the Opportunity. Typical Value:
|
|
1597
|
+
- "Prospecting"
|
|
1598
|
+
- "Qualification"
|
|
1599
|
+
- "Value Proposition"
|
|
1600
|
+
- "Negotiation/Review",
|
|
1601
|
+
- "Closed Won"
|
|
1602
|
+
- "Closed Lost"
|
|
1603
|
+
close_date (str):
|
|
1604
|
+
Close date of the Opportunity. Should be in format YYYY-MM-DD.
|
|
1605
|
+
amount (Union[int, float]):
|
|
1606
|
+
Amount (expected revenue) of the opportunity.
|
|
1607
|
+
Can either be an integer or a float value.
|
|
1608
|
+
account_id (str):
|
|
1609
|
+
The technical ID of the related Salesforce Account.
|
|
1610
|
+
description (str | None, optional):
|
|
1611
|
+
A description of the opportunity.
|
|
1612
|
+
kwargs (dict):
|
|
1613
|
+
Additional keyword arguments.
|
|
1500
1614
|
|
|
1501
1615
|
Returns:
|
|
1502
1616
|
dict | None: Dictionary with the Salesforce Opportunity data or None if the request fails.
|
|
1617
|
+
|
|
1503
1618
|
"""
|
|
1504
1619
|
|
|
1505
1620
|
if not self._access_token or not self._instance_url:
|
|
@@ -1519,33 +1634,20 @@ class Salesforce(object):
|
|
|
1519
1634
|
payload["Description"] = description
|
|
1520
1635
|
payload.update(kwargs) # Add additional fields from kwargs
|
|
1521
1636
|
|
|
1522
|
-
logger.debug(
|
|
1523
|
-
"Add Salesforce opportunity -> %s; calling -> %s",
|
|
1637
|
+
self.logger.debug(
|
|
1638
|
+
"Add Salesforce opportunity -> '%s'; calling -> %s",
|
|
1639
|
+
name,
|
|
1640
|
+
request_url,
|
|
1524
1641
|
)
|
|
1525
1642
|
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
if response.ok:
|
|
1535
|
-
return self.parse_request_response(response)
|
|
1536
|
-
elif response.status_code == 401 and retries == 0:
|
|
1537
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
1538
|
-
self.authenticate(revalidate=True)
|
|
1539
|
-
request_header = self.request_header()
|
|
1540
|
-
retries += 1
|
|
1541
|
-
else:
|
|
1542
|
-
logger.error(
|
|
1543
|
-
"Failed to add Salesforce opportunity -> %s; status -> %s; error -> %s",
|
|
1544
|
-
name,
|
|
1545
|
-
response.status_code,
|
|
1546
|
-
response.text,
|
|
1547
|
-
)
|
|
1548
|
-
return None
|
|
1643
|
+
return self.do_request(
|
|
1644
|
+
method="POST",
|
|
1645
|
+
url=request_url,
|
|
1646
|
+
headers=request_header,
|
|
1647
|
+
data=json.dumps(payload),
|
|
1648
|
+
timeout=REQUEST_TIMEOUT,
|
|
1649
|
+
failure_message="Failed to add Salesforce opportunity -> '{}'".format(name),
|
|
1650
|
+
)
|
|
1549
1651
|
|
|
1550
1652
|
# end method definition
|
|
1551
1653
|
|
|
@@ -1558,27 +1660,40 @@ class Salesforce(object):
|
|
|
1558
1660
|
origin: str,
|
|
1559
1661
|
account_id: str,
|
|
1560
1662
|
owner_id: str,
|
|
1561
|
-
asset_id:
|
|
1562
|
-
product_id:
|
|
1563
|
-
**kwargs:
|
|
1663
|
+
asset_id: str | None = None,
|
|
1664
|
+
product_id: str | None = None,
|
|
1665
|
+
**kwargs: dict[str, str],
|
|
1564
1666
|
) -> dict | None:
|
|
1565
|
-
"""Add a new Case object to Salesforce.
|
|
1566
|
-
|
|
1667
|
+
"""Add a new Case object to Salesforce.
|
|
1668
|
+
|
|
1669
|
+
The case number is automatically created and can not be provided.
|
|
1567
1670
|
|
|
1568
1671
|
Args:
|
|
1569
|
-
subject (str):
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1672
|
+
subject (str):
|
|
1673
|
+
Subject (title) of the case. It's like the name.
|
|
1674
|
+
description (str):
|
|
1675
|
+
Description of the case
|
|
1676
|
+
status (str):
|
|
1677
|
+
Status of the case. Typecal values: "New", "On Hold", "Escalated"
|
|
1678
|
+
priority (str):
|
|
1679
|
+
Priority of the case. Typical values: "High", "Medium", "Low".
|
|
1680
|
+
origin (str):
|
|
1681
|
+
Origin (source) of the case. Typical values: "Email", "Phone", "Web"
|
|
1682
|
+
account_id (str):
|
|
1683
|
+
Technical ID of the related Account
|
|
1684
|
+
owner_id (str):
|
|
1685
|
+
Owner of the case
|
|
1686
|
+
asset_id (str):
|
|
1687
|
+
Technical ID of the related Asset.
|
|
1688
|
+
product_id (str):
|
|
1689
|
+
Technical ID of the related Product.
|
|
1690
|
+
kwargs (dict):
|
|
1691
|
+
Additional values (e.g. custom fields)
|
|
1579
1692
|
|
|
1580
1693
|
Returns:
|
|
1581
|
-
dict | None:
|
|
1694
|
+
dict | None:
|
|
1695
|
+
Dictionary with the Salesforce Case data or None if the request fails.
|
|
1696
|
+
|
|
1582
1697
|
"""
|
|
1583
1698
|
|
|
1584
1699
|
if not self._access_token or not self._instance_url:
|
|
@@ -1603,31 +1718,20 @@ class Salesforce(object):
|
|
|
1603
1718
|
payload["ProductId"] = product_id
|
|
1604
1719
|
payload.update(kwargs) # Add additional fields from kwargs
|
|
1605
1720
|
|
|
1606
|
-
logger.debug(
|
|
1721
|
+
self.logger.debug(
|
|
1722
|
+
"Add Salesforce case -> '%s'; calling -> %s",
|
|
1723
|
+
subject,
|
|
1724
|
+
request_url,
|
|
1725
|
+
)
|
|
1607
1726
|
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
if response.ok:
|
|
1617
|
-
return self.parse_request_response(response)
|
|
1618
|
-
elif response.status_code == 401 and retries == 0:
|
|
1619
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
1620
|
-
self.authenticate(revalidate=True)
|
|
1621
|
-
request_header = self.request_header()
|
|
1622
|
-
retries += 1
|
|
1623
|
-
else:
|
|
1624
|
-
logger.error(
|
|
1625
|
-
"Failed to add Salesforce case -> %s; status -> %s; error -> %s",
|
|
1626
|
-
subject,
|
|
1627
|
-
response.status_code,
|
|
1628
|
-
response.text,
|
|
1629
|
-
)
|
|
1630
|
-
return None
|
|
1727
|
+
return self.do_request(
|
|
1728
|
+
method="POST",
|
|
1729
|
+
url=request_url,
|
|
1730
|
+
headers=request_header,
|
|
1731
|
+
data=json.dumps(payload),
|
|
1732
|
+
timeout=REQUEST_TIMEOUT,
|
|
1733
|
+
failure_message="Failed to add Salesforce case -> '{}'".format(subject),
|
|
1734
|
+
)
|
|
1631
1735
|
|
|
1632
1736
|
# end method definition
|
|
1633
1737
|
|
|
@@ -1640,22 +1744,33 @@ class Salesforce(object):
|
|
|
1640
1744
|
purchase_date: str,
|
|
1641
1745
|
install_date: str,
|
|
1642
1746
|
description: str | None = None,
|
|
1643
|
-
**kwargs:
|
|
1747
|
+
**kwargs: dict[str, str],
|
|
1644
1748
|
) -> dict | None:
|
|
1645
1749
|
"""Add a new Asset object to Salesforce.
|
|
1646
1750
|
|
|
1647
1751
|
Args:
|
|
1648
|
-
asset_name (str):
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1752
|
+
asset_name (str):
|
|
1753
|
+
The name of the Asset.
|
|
1754
|
+
product_id (str):
|
|
1755
|
+
Related Product ID.
|
|
1756
|
+
serial_number (str):
|
|
1757
|
+
Serial Number of the Asset.
|
|
1758
|
+
status (str):
|
|
1759
|
+
The status of the Asset.
|
|
1760
|
+
Typical values are "Purchased", "Shipped", "Installed", "Registered", "Obsolete"
|
|
1761
|
+
purchase_date (str):
|
|
1762
|
+
Purchase date of the Asset.
|
|
1763
|
+
install_date (str):
|
|
1764
|
+
Install date of the Asset.
|
|
1765
|
+
description (str):
|
|
1766
|
+
Description of the Asset.
|
|
1767
|
+
kwargs (dict):
|
|
1768
|
+
Additional values (e.g. custom fields)
|
|
1656
1769
|
|
|
1657
1770
|
Returns:
|
|
1658
|
-
dict | None:
|
|
1771
|
+
dict | None:
|
|
1772
|
+
Dictionary with the Salesforce Asset data or None if the request fails.
|
|
1773
|
+
|
|
1659
1774
|
"""
|
|
1660
1775
|
|
|
1661
1776
|
if not self._access_token or not self._instance_url:
|
|
@@ -1676,33 +1791,20 @@ class Salesforce(object):
|
|
|
1676
1791
|
payload["Description"] = description
|
|
1677
1792
|
payload.update(kwargs) # Add additional fields from kwargs
|
|
1678
1793
|
|
|
1679
|
-
logger.debug(
|
|
1680
|
-
"Add Salesforce asset -> %s; calling -> %s",
|
|
1794
|
+
self.logger.debug(
|
|
1795
|
+
"Add Salesforce asset -> '%s'; calling -> %s",
|
|
1796
|
+
asset_name,
|
|
1797
|
+
request_url,
|
|
1681
1798
|
)
|
|
1682
1799
|
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
if response.ok:
|
|
1692
|
-
return self.parse_request_response(response)
|
|
1693
|
-
elif response.status_code == 401 and retries == 0:
|
|
1694
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
1695
|
-
self.authenticate(revalidate=True)
|
|
1696
|
-
request_header = self.request_header()
|
|
1697
|
-
retries += 1
|
|
1698
|
-
else:
|
|
1699
|
-
logger.error(
|
|
1700
|
-
"Failed to add Salesforce user -> %s; status -> %s; error -> %s",
|
|
1701
|
-
asset_name,
|
|
1702
|
-
response.status_code,
|
|
1703
|
-
response.text,
|
|
1704
|
-
)
|
|
1705
|
-
return None
|
|
1800
|
+
return self.do_request(
|
|
1801
|
+
method="POST",
|
|
1802
|
+
url=request_url,
|
|
1803
|
+
headers=request_header,
|
|
1804
|
+
data=json.dumps(payload),
|
|
1805
|
+
timeout=REQUEST_TIMEOUT,
|
|
1806
|
+
failure_message="Failed to add Salesforce asset -> '{}'".format(asset_name),
|
|
1807
|
+
)
|
|
1706
1808
|
|
|
1707
1809
|
# end method definition
|
|
1708
1810
|
|
|
@@ -1712,23 +1814,43 @@ class Salesforce(object):
|
|
|
1712
1814
|
start_date: str,
|
|
1713
1815
|
contract_term: int,
|
|
1714
1816
|
status: str = "Draft",
|
|
1715
|
-
description:
|
|
1716
|
-
contract_type:
|
|
1717
|
-
**kwargs:
|
|
1817
|
+
description: str | None = None,
|
|
1818
|
+
contract_type: str | None = None,
|
|
1819
|
+
**kwargs: dict[str, str],
|
|
1718
1820
|
) -> dict | None:
|
|
1719
1821
|
"""Add a new Contract object to Salesforce.
|
|
1720
1822
|
|
|
1721
1823
|
Args:
|
|
1722
|
-
account_id (str):
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1824
|
+
account_id (str):
|
|
1825
|
+
The technical ID of the related Salesforce Account object.
|
|
1826
|
+
start_date (str):
|
|
1827
|
+
Start date of the contract. Use YYYY-MM-DD notation.
|
|
1828
|
+
contract_term (int):
|
|
1829
|
+
Term of the contract in number of months, e.g. 48 for 4 years term.
|
|
1830
|
+
The end date of the contract will be calculated from start date + term.
|
|
1831
|
+
contract_type (str):
|
|
1832
|
+
Type of the Contract. Typical values are:
|
|
1833
|
+
- "Subscription"
|
|
1834
|
+
- "Maintenance"
|
|
1835
|
+
- "Support"
|
|
1836
|
+
- "Lease"
|
|
1837
|
+
- "Service"
|
|
1838
|
+
status (str):
|
|
1839
|
+
Status of the Contract. Typical values are:
|
|
1840
|
+
- "Draft"
|
|
1841
|
+
- "Activated"
|
|
1842
|
+
- "In Approval Process"
|
|
1843
|
+
description (str, optional):
|
|
1844
|
+
Description of the contract.
|
|
1845
|
+
contract_type:
|
|
1846
|
+
Type name of the contract.
|
|
1847
|
+
kwargs:
|
|
1848
|
+
Additional keyword arguments.
|
|
1729
1849
|
|
|
1730
1850
|
Returns:
|
|
1731
|
-
dict | None:
|
|
1851
|
+
dict | None:
|
|
1852
|
+
Dictionary with the Salesforce contract data or None if the request fails.
|
|
1853
|
+
|
|
1732
1854
|
"""
|
|
1733
1855
|
|
|
1734
1856
|
if not self._access_token or not self._instance_url:
|
|
@@ -1749,34 +1871,21 @@ class Salesforce(object):
|
|
|
1749
1871
|
payload["ContractType"] = contract_type
|
|
1750
1872
|
payload.update(kwargs) # Add additional fields from kwargs
|
|
1751
1873
|
|
|
1752
|
-
logger.debug(
|
|
1753
|
-
"Adding Salesforce contract for account ID -> %s; calling -> %s",
|
|
1874
|
+
self.logger.debug(
|
|
1875
|
+
"Adding Salesforce contract for account with ID -> %s; calling -> %s",
|
|
1754
1876
|
account_id,
|
|
1755
1877
|
request_url,
|
|
1756
1878
|
)
|
|
1757
1879
|
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
elif response.status_code == 401 and retries == 0:
|
|
1769
|
-
logger.debug("Session has expired - try to re-authenticate...")
|
|
1770
|
-
self.authenticate(revalidate=True)
|
|
1771
|
-
request_header = self.request_header()
|
|
1772
|
-
retries += 1
|
|
1773
|
-
else:
|
|
1774
|
-
logger.error(
|
|
1775
|
-
"Failed to add Salesforce contract for account ID -> %s; status -> %s; error -> %s",
|
|
1776
|
-
account_id,
|
|
1777
|
-
response.status_code,
|
|
1778
|
-
response.text,
|
|
1779
|
-
)
|
|
1780
|
-
return None
|
|
1880
|
+
return self.do_request(
|
|
1881
|
+
method="POST",
|
|
1882
|
+
url=request_url,
|
|
1883
|
+
headers=request_header,
|
|
1884
|
+
data=json.dumps(payload),
|
|
1885
|
+
timeout=REQUEST_TIMEOUT,
|
|
1886
|
+
failure_message="Failed to add Salesforce contract for account with ID -> {}".format(
|
|
1887
|
+
account_id,
|
|
1888
|
+
),
|
|
1889
|
+
)
|
|
1781
1890
|
|
|
1782
1891
|
# end method definition
|