pyxecm 1.6__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.

Files changed (56) hide show
  1. pyxecm/__init__.py +6 -4
  2. pyxecm/avts.py +673 -246
  3. pyxecm/coreshare.py +686 -467
  4. pyxecm/customizer/__init__.py +16 -4
  5. pyxecm/customizer/__main__.py +58 -0
  6. pyxecm/customizer/api/__init__.py +5 -0
  7. pyxecm/customizer/api/__main__.py +6 -0
  8. pyxecm/customizer/api/app.py +914 -0
  9. pyxecm/customizer/api/auth.py +154 -0
  10. pyxecm/customizer/api/metrics.py +92 -0
  11. pyxecm/customizer/api/models.py +13 -0
  12. pyxecm/customizer/api/payload_list.py +865 -0
  13. pyxecm/customizer/api/settings.py +103 -0
  14. pyxecm/customizer/browser_automation.py +332 -139
  15. pyxecm/customizer/customizer.py +1007 -1130
  16. pyxecm/customizer/exceptions.py +35 -0
  17. pyxecm/customizer/guidewire.py +322 -0
  18. pyxecm/customizer/k8s.py +713 -378
  19. pyxecm/customizer/log.py +107 -0
  20. pyxecm/customizer/m365.py +2867 -909
  21. pyxecm/customizer/nhc.py +1169 -0
  22. pyxecm/customizer/openapi.py +258 -0
  23. pyxecm/customizer/payload.py +16817 -7467
  24. pyxecm/customizer/pht.py +699 -285
  25. pyxecm/customizer/salesforce.py +516 -342
  26. pyxecm/customizer/sap.py +58 -41
  27. pyxecm/customizer/servicenow.py +593 -371
  28. pyxecm/customizer/settings.py +442 -0
  29. pyxecm/customizer/successfactors.py +408 -346
  30. pyxecm/customizer/translate.py +83 -48
  31. pyxecm/helper/__init__.py +5 -2
  32. pyxecm/helper/assoc.py +83 -43
  33. pyxecm/helper/data.py +2406 -870
  34. pyxecm/helper/logadapter.py +27 -0
  35. pyxecm/helper/web.py +229 -101
  36. pyxecm/helper/xml.py +527 -171
  37. pyxecm/maintenance_page/__init__.py +5 -0
  38. pyxecm/maintenance_page/__main__.py +6 -0
  39. pyxecm/maintenance_page/app.py +51 -0
  40. pyxecm/maintenance_page/settings.py +28 -0
  41. pyxecm/maintenance_page/static/favicon.avif +0 -0
  42. pyxecm/maintenance_page/templates/maintenance.html +165 -0
  43. pyxecm/otac.py +234 -140
  44. pyxecm/otawp.py +1436 -557
  45. pyxecm/otcs.py +7716 -3161
  46. pyxecm/otds.py +2150 -919
  47. pyxecm/otiv.py +36 -21
  48. pyxecm/otmm.py +1272 -325
  49. pyxecm/otpd.py +231 -127
  50. pyxecm-2.0.0.dist-info/METADATA +145 -0
  51. pyxecm-2.0.0.dist-info/RECORD +54 -0
  52. {pyxecm-1.6.dist-info → pyxecm-2.0.0.dist-info}/WHEEL +1 -1
  53. pyxecm-1.6.dist-info/METADATA +0 -53
  54. pyxecm-1.6.dist-info/RECORD +0 -32
  55. {pyxecm-1.6.dist-info → pyxecm-2.0.0.dist-info/licenses}/LICENSE +0 -0
  56. {pyxecm-1.6.dist-info → pyxecm-2.0.0.dist-info}/top_level.txt +0 -0
@@ -1,71 +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
- Class: Salesforce
6
- Methods:
7
-
8
- __init__ : class initializer
9
- config : Returns config data set
10
- credentials: Returns the token data
11
-
12
- request_header: Returns the request header for Salesforce API calls
13
- do_request: Call an Salesforce REST API in a safe way
14
- parse_request_response: Parse the REST API responses and convert
15
- them to Python dict in a safe way
16
- exist_result_item: Check if an dict item is in the response
17
- of the Salesforce API call
18
- get_result_value: Check if a defined value (based on a key) is in the Salesforce API response
19
-
20
- authenticate: Authenticates at Salesforce API
21
-
22
- get_object_id_by_name: Get the ID of a given Salesforce object with a given type and name
23
- get_object: Get a Salesforce object based on a defined
24
- field value and return selected result fields.
25
- add_object: Add object to Salesforce. This is a generic wrapper method
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
-
43
- add_account: Add a new Account object to Salesforce.
44
- add_product: Add a new Product object to Salesforce.
45
- add_opportunity: Add a new Opportunity object to Salesfoce.
46
- add_case: Add a new Case object to Salesforce. The case number
47
- is automatically created and can not be provided.
48
- add_asset: Add a new Asset object to Salesforce.
49
- 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
50
4
  """
51
5
 
52
6
  __author__ = "Dr. Marc Diefenbruch"
53
- __copyright__ = "Copyright 2024, OpenText"
7
+ __copyright__ = "Copyright (C) 2024-2025, OpenText"
54
8
  __credits__ = ["Kai-Philip Gatzweiler"]
55
9
  __maintainer__ = "Dr. Marc Diefenbruch"
56
10
  __email__ = "mdiefenb@opentext.com"
57
11
 
58
- import os
59
12
  import json
60
13
  import logging
14
+ import os
61
15
  import time
62
-
63
- from typing import Optional, Union, Any
64
-
65
16
  from http import HTTPStatus
17
+
66
18
  import requests
67
19
 
68
- logger = logging.getLogger("pyxecm.customizer.salesforce")
20
+ default_logger = logging.getLogger("pyxecm.customizer.salesforce")
69
21
 
70
22
  REQUEST_LOGIN_HEADERS = {
71
23
  "Content-Type": "application/x-www-form-urlencoded",
@@ -78,8 +30,11 @@ REQUEST_MAX_RETRIES = 3
78
30
 
79
31
  SALESFORCE_API_VERSION = "v60.0"
80
32
 
81
- class Salesforce(object):
82
- """Used to retrieve and automate stettings in Salesforce."""
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
83
38
 
84
39
  _config: dict
85
40
  _access_token = None
@@ -94,21 +49,38 @@ class Salesforce(object):
94
49
  password: str,
95
50
  authorization_url: str = "",
96
51
  security_token: str = "",
97
- ):
98
- """Initialize the Salesforce object
52
+ logger: logging.Logger = default_logger,
53
+ ) -> None:
54
+ """Initialize the Salesforce object.
99
55
 
100
56
  Args:
101
- base_url (str): base URL of the Salesforce tenant
102
- authorization_url (str): authorization URL of the Salesforce tenant, typically ending with "/services/oauth2/token"
103
- client_id (str): Salesforce Client ID
104
- client_secret (str): Salesforce Client Secret
105
- username (str): user name in Saleforce
106
- password (str): password of the user
107
- authorization_url (str, optional): URL for Salesforce login. If not given it will be constructed with default values
108
- using base_url
109
- security_token (str, optional): security token for Salesforce login
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
+
110
77
  """
111
78
 
79
+ if logger != default_logger:
80
+ self.logger = logger.getChild("salesforce")
81
+ for logfilter in logger.filters:
82
+ self.logger.addFilter(logfilter)
83
+
112
84
  # The instance URL is also returned by the authenticate call
113
85
  # but typically it is identical to the base_url.
114
86
  self._instance_url = base_url
@@ -124,38 +96,32 @@ class Salesforce(object):
124
96
 
125
97
  # Set the Salesforce URLs and REST API endpoints:
126
98
  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)
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
+ )
142
114
  if authorization_url:
143
115
  salesforce_config["authenticationUrl"] = authorization_url
144
116
  else:
145
- salesforce_config["authenticationUrl"] = (
146
- salesforce_config["baseUrl"] + "/services/oauth2/token"
147
- )
117
+ salesforce_config["authenticationUrl"] = salesforce_config["baseUrl"] + "/services/oauth2/token"
148
118
  # URLs that are based on the objectURL (sobjects/):
149
119
  salesforce_config["userUrl"] = salesforce_config["objectUrl"] + "User/"
150
120
  salesforce_config["groupUrl"] = salesforce_config["objectUrl"] + "Group/"
151
- salesforce_config["groupMemberUrl"] = (
152
- salesforce_config["objectUrl"] + "GroupMember/"
153
- )
121
+ salesforce_config["groupMemberUrl"] = salesforce_config["objectUrl"] + "GroupMember/"
154
122
  salesforce_config["accountUrl"] = salesforce_config["objectUrl"] + "Account/"
155
123
  salesforce_config["productUrl"] = salesforce_config["objectUrl"] + "Product2/"
156
- salesforce_config["opportunityUrl"] = (
157
- salesforce_config["objectUrl"] + "Opportunity/"
158
- )
124
+ salesforce_config["opportunityUrl"] = salesforce_config["objectUrl"] + "Opportunity/"
159
125
  salesforce_config["caseUrl"] = salesforce_config["objectUrl"] + "Case/"
160
126
  salesforce_config["assetUrl"] = salesforce_config["objectUrl"] + "Asset/"
161
127
  salesforce_config["contractUrl"] = salesforce_config["objectUrl"] + "Contract/"
@@ -174,33 +140,44 @@ class Salesforce(object):
174
140
  # end method definition
175
141
 
176
142
  def config(self) -> dict:
177
- """Returns the configuration dictionary
143
+ """Return the configuration dictionary.
178
144
 
179
145
  Returns:
180
- dict: Configuration dictionary
146
+ dict:
147
+ The configuration dictionary.
148
+
181
149
  """
150
+
182
151
  return self._config
183
152
 
184
153
  # end method definition
185
154
 
186
155
  def credentials(self) -> dict:
187
- """Return the login credentials
156
+ """Return the login credentials.
188
157
 
189
158
  Returns:
190
- dict: dictionary with login credentials for Salesforce
159
+ dict:
160
+ The dictionary with login credentials for Salesforce.
161
+
191
162
  """
163
+
192
164
  return self.config()["authenticationData"]
193
165
 
194
166
  # end method definition
195
167
 
196
168
  def request_header(self, content_type: str = "application/json") -> dict:
197
- """Returns the request header used for Application calls.
198
- Consists of Bearer access token and Content Type
169
+ """Return the request header used for Application calls.
170
+
171
+ Consists of Bearer access token and Content Type
199
172
 
200
173
  Args:
201
- content_type (str, optional): content type for the request
202
- Return:
203
- dict: request header values
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
+
204
181
  """
205
182
 
206
183
  request_header = {
@@ -234,36 +211,64 @@ class Salesforce(object):
234
211
  stream: bool = False,
235
212
  verify: bool = True,
236
213
  ) -> dict | None:
237
- """Call an Salesforce REST API in a safe way
214
+ """Call an Salesforce REST API in a safe way.
238
215
 
239
216
  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
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
260
261
 
261
262
  Returns:
262
- dict | None: Response of OTDS REST API or None in case of an error.
263
+ dict | None:
264
+ Response of OTDS REST API or None in case of an error.
265
+
263
266
  """
264
267
 
265
268
  if headers is None:
266
- logger.error("Missing request header. Cannot send request to Core Share!")
269
+ self.logger.error(
270
+ "Missing request header. Cannot send request to Core Share!",
271
+ )
267
272
  return None
268
273
 
269
274
  # In case of an expired session we reauthenticate and
@@ -288,19 +293,19 @@ class Salesforce(object):
288
293
 
289
294
  if response.ok:
290
295
  if success_message:
291
- logger.info(success_message)
296
+ self.logger.info(success_message)
292
297
  if parse_request_response:
293
298
  return self.parse_request_response(response)
294
299
  else:
295
300
  return response
296
301
  # Check if Session has expired - then re-authenticate and try once more
297
302
  elif response.status_code == 401 and retries == 0:
298
- logger.debug("Session has expired - try to re-authenticate...")
303
+ self.logger.debug("Session has expired - try to re-authenticate...")
299
304
  self.authenticate(revalidate=True)
300
305
  # Make sure to not change an existing content type
301
306
  # the do_request() method is called with:
302
307
  headers = self.request_header(
303
- content_type=headers.get("Content-Type", None)
308
+ content_type=headers.get("Content-Type", None),
304
309
  )
305
310
  retries += 1
306
311
  else:
@@ -312,7 +317,7 @@ class Salesforce(object):
312
317
  response_text = response.text
313
318
 
314
319
  if show_error:
315
- logger.error(
320
+ self.logger.error(
316
321
  "%s; status -> %s/%s; error -> %s",
317
322
  failure_message,
318
323
  response.status_code,
@@ -320,7 +325,7 @@ class Salesforce(object):
320
325
  response_text,
321
326
  )
322
327
  elif show_warning:
323
- logger.warning(
328
+ self.logger.warning(
324
329
  "%s; status -> %s/%s; warning -> %s",
325
330
  warning_message if warning_message else failure_message,
326
331
  response.status_code,
@@ -328,7 +333,7 @@ class Salesforce(object):
328
333
  response_text,
329
334
  )
330
335
  if content_type == "text/html":
331
- logger.debug(
336
+ self.logger.debug(
332
337
  "%s; status -> %s/%s; warning -> %s",
333
338
  failure_message,
334
339
  response.status_code,
@@ -338,45 +343,45 @@ class Salesforce(object):
338
343
  return None
339
344
  except requests.exceptions.Timeout:
340
345
  if retries <= max_retries:
341
- logger.warning(
346
+ self.logger.warning(
342
347
  "Request timed out. Retrying in %s seconds...",
343
348
  str(REQUEST_RETRY_DELAY),
344
349
  )
345
350
  retries += 1
346
351
  time.sleep(REQUEST_RETRY_DELAY) # Add a delay before retrying
347
352
  else:
348
- logger.error(
349
- "%s; timeout error",
353
+ self.logger.error(
354
+ "%s; timeout error.",
350
355
  failure_message,
351
356
  )
352
357
  if retry_forever:
353
358
  # If it fails after REQUEST_MAX_RETRIES retries we let it wait forever
354
- logger.warning("Turn timeouts off and wait forever...")
359
+ self.logger.warning("Turn timeouts off and wait forever...")
355
360
  timeout = None
356
361
  else:
357
362
  return None
358
363
  except requests.exceptions.ConnectionError:
359
364
  if retries <= max_retries:
360
- logger.warning(
365
+ self.logger.warning(
361
366
  "Connection error. Retrying in %s seconds...",
362
367
  str(REQUEST_RETRY_DELAY),
363
368
  )
364
369
  retries += 1
365
370
  time.sleep(REQUEST_RETRY_DELAY) # Add a delay before retrying
366
371
  else:
367
- logger.error(
368
- "%s; connection error",
372
+ self.logger.error(
373
+ "%s; connection error.",
369
374
  failure_message,
370
375
  )
371
376
  if retry_forever:
372
377
  # If it fails after REQUEST_MAX_RETRIES retries we let it wait forever
373
- logger.warning("Turn timeouts off and wait forever...")
378
+ self.logger.warning("Turn timeouts off and wait forever...")
374
379
  timeout = None
375
380
  time.sleep(REQUEST_RETRY_DELAY) # Add a delay before retrying
376
381
  else:
377
382
  return None
378
383
  # end try
379
- logger.debug(
384
+ self.logger.debug(
380
385
  "Retrying REST API %s call -> %s... (retry = %s)",
381
386
  method,
382
387
  url,
@@ -392,43 +397,47 @@ class Salesforce(object):
392
397
  additional_error_message: str = "",
393
398
  show_error: bool = True,
394
399
  ) -> dict | None:
395
- """Converts the request response (JSon) to a Python dict in a safe way
396
- that also handles exceptions. It first tries to load the response.text
397
- via json.loads() that produces a dict output. Only if response.text is
398
- not set or is empty it just converts the response_object to a dict using
399
- the vars() built-in method.
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.
400
407
 
401
408
  Args:
402
- response_object (object): this is reponse object delivered by the request call
403
- additional_error_message (str, optional): use a more specific error message
404
- in case of an error
405
- show_error (bool): True: write an error to the log file
406
- False: write a warning to the log file
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
+
407
417
  Returns:
408
- dict: response information or None in case of an error
418
+ dict | None: Parsed response information or None in case of an error.
419
+
409
420
  """
410
421
 
411
422
  if not response_object:
412
423
  return None
413
424
 
414
425
  try:
415
- if response_object.text:
416
- dict_object = json.loads(response_object.text)
417
- else:
418
- dict_object = vars(response_object)
426
+ dict_object = json.loads(response_object.text) if response_object.text else vars(response_object)
419
427
  except json.JSONDecodeError as exception:
420
428
  if additional_error_message:
421
429
  message = "Cannot decode response as JSon. {}; error -> {}".format(
422
- additional_error_message, exception
430
+ additional_error_message,
431
+ exception,
423
432
  )
424
433
  else:
425
434
  message = "Cannot decode response as JSon; error -> {}".format(
426
- exception
435
+ exception,
427
436
  )
428
437
  if show_error:
429
- logger.error(message)
438
+ self.logger.error(message)
430
439
  else:
431
- logger.warning(message)
440
+ self.logger.warning(message)
432
441
  return None
433
442
  else:
434
443
  return dict_object
@@ -436,14 +445,20 @@ class Salesforce(object):
436
445
  # end method definition
437
446
 
438
447
  def exist_result_item(self, response: dict, key: str, value: str) -> bool:
439
- """Check existence of key / value pair in the response properties of an Salesforce API call.
448
+ """Check existence of key / value pair in the response properties of a Salesforce API call.
440
449
 
441
450
  Args:
442
- response (dict): REST response from an Salesforce API call
443
- key (str): property name (key)
444
- value (str): value to find in the item with the matching key
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
+
445
458
  Returns:
446
- bool: True if the value was found, False otherwise
459
+ bool:
460
+ True if the value was found, False otherwise.
461
+
447
462
  """
448
463
 
449
464
  if not response:
@@ -455,10 +470,10 @@ class Salesforce(object):
455
470
  return False
456
471
 
457
472
  for record in records:
458
- if value == record[key]:
473
+ if key in record and value == record[key]:
459
474
  return True
460
475
  else:
461
- if not key in response:
476
+ if key not in response:
462
477
  return False
463
478
  if value == response[key]:
464
479
  return True
@@ -473,15 +488,22 @@ class Salesforce(object):
473
488
  key: str,
474
489
  index: int = 0,
475
490
  ) -> str | None:
476
- """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.
477
492
 
478
493
  Args:
479
- response (dict): REST response from an Salesforce REST Call
480
- key (str): property name (key)
481
- index (int, optional): Index to use (1st element has index 0).
482
- Defaults to 0.
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
+
483
502
  Returns:
484
- str: value for the key, None otherwise
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
+
485
507
  """
486
508
 
487
509
  if not response:
@@ -495,7 +517,7 @@ class Salesforce(object):
495
517
  return None
496
518
  value = values[index][key]
497
519
  else: # simple response - try to find key in response directly:
498
- if not key in response:
520
+ if key not in response:
499
521
  return None
500
522
  value = response[key]
501
523
 
@@ -507,15 +529,20 @@ class Salesforce(object):
507
529
  """Authenticate at Salesforce with client ID and client secret.
508
530
 
509
531
  Args:
510
- revalidate (bool, optional): determinse if a re-athentication is enforced
511
- (e.g. if session has timed out with 401 error)
532
+ revalidate (bool, optional):
533
+ Determins if a re-athentication is enforced
534
+ (e.g. if session has timed out with 401 error).
535
+
512
536
  Returns:
513
- str: Access token. Also stores access token in self._access_token. None in case of error
537
+ str | None:
538
+ The Access token. Also stores access token in self._access_token.
539
+ None in case of error
540
+
514
541
  """
515
542
 
516
543
  # Already authenticated and session still valid?
517
544
  if self._access_token and not revalidate:
518
- logger.debug(
545
+ self.logger.debug(
519
546
  "Session still valid - return existing access token -> %s",
520
547
  str(self._access_token),
521
548
  )
@@ -524,7 +551,7 @@ class Salesforce(object):
524
551
  request_url = self.config()["authenticationUrl"]
525
552
  request_header = REQUEST_LOGIN_HEADERS
526
553
 
527
- logger.debug("Requesting Salesforce Access Token from -> %s", request_url)
554
+ self.logger.debug("Requesting Salesforce Access Token from -> %s", request_url)
528
555
 
529
556
  authenticate_post_body = self.credentials()
530
557
 
@@ -540,7 +567,7 @@ class Salesforce(object):
540
567
  timeout=REQUEST_TIMEOUT,
541
568
  )
542
569
  except requests.exceptions.ConnectionError as exception:
543
- logger.warning(
570
+ self.logger.warning(
544
571
  "Unable to connect to -> %s : %s",
545
572
  self.config()["authenticationUrl"],
546
573
  exception,
@@ -554,11 +581,11 @@ class Salesforce(object):
554
581
  else:
555
582
  # Store authentication access_token:
556
583
  self._access_token = authenticate_dict["access_token"]
557
- logger.debug("Access Token -> %s", self._access_token)
584
+ self.logger.debug("Access Token -> %s", self._access_token)
558
585
  self._instance_url = authenticate_dict["instance_url"]
559
- logger.debug("Instance URL -> %s", self._instance_url)
586
+ self.logger.debug("Instance URL -> %s", self._instance_url)
560
587
  else:
561
- logger.error(
588
+ self.logger.error(
562
589
  "Failed to request an Salesforce Access Token; error -> %s",
563
590
  response.text,
564
591
  )
@@ -569,8 +596,11 @@ class Salesforce(object):
569
596
  # end method definition
570
597
 
571
598
  def get_object_id_by_name(
572
- self, object_type: str, name: str, name_field: str = "Name"
573
- ) -> Optional[str]:
599
+ self,
600
+ object_type: str,
601
+ name: str,
602
+ name_field: str = "Name",
603
+ ) -> str | None:
574
604
  """Get the ID of a given Salesforce object with a given type and name.
575
605
 
576
606
  Args:
@@ -580,6 +610,7 @@ class Salesforce(object):
580
610
 
581
611
  Returns:
582
612
  Optional[str]: Object ID or None if the request fails.
613
+
583
614
  """
584
615
 
585
616
  if not self._access_token or not self._instance_url:
@@ -597,7 +628,8 @@ class Salesforce(object):
597
628
  params={"q": query},
598
629
  timeout=REQUEST_TIMEOUT,
599
630
  failure_message="Failed to get Salesforce object ID for object type -> '{}' and object name -> '{}'".format(
600
- object_type, name
631
+ object_type,
632
+ name,
601
633
  ),
602
634
  )
603
635
  if not response:
@@ -619,16 +651,18 @@ class Salesforce(object):
619
651
 
620
652
  Args:
621
653
  object_type (str): Salesforce Business Object type. Such as "Account" or "Case".
622
- search_field (str): object field to search in
623
- search_value (str): value to search for
624
- result_fields (list | None): list of fields to return. If None, then all standard fields
625
- of the object will be returned.
626
- limit (int, optional): maximum number of fields to return. Salesforce enforces 200 as upper limit.
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.
627
661
 
628
662
  Returns:
629
663
  dict | None: Dictionary with the Salesforce object data.
630
664
 
631
- Example response:
665
+ Example:
632
666
  {
633
667
  'totalSize': 2,
634
668
  'done': True,
@@ -649,20 +683,21 @@ class Salesforce(object):
649
683
  }
650
684
  ]
651
685
  }
686
+
652
687
  """
653
688
 
654
689
  if not self._access_token or not self._instance_url:
655
690
  self.authenticate()
656
691
 
657
692
  if search_field and not search_value:
658
- logger.error(
693
+ self.logger.error(
659
694
  "No search value has been provided for search field -> %s!",
660
695
  search_field,
661
696
  )
662
697
  return None
663
698
  if not result_fields:
664
- logger.debug(
665
- "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.",
666
701
  )
667
702
  result_fields = ["FIELDS(STANDARD)"]
668
703
 
@@ -674,8 +709,10 @@ class Salesforce(object):
674
709
  request_header = self.request_header()
675
710
  request_url = self.config()["queryUrl"] + "?q={}".format(query)
676
711
 
677
- logger.debug(
678
- "Sending query -> %s to Salesforce; calling -> %s", query, request_url
712
+ self.logger.debug(
713
+ "Sending query -> %s to Salesforce; calling -> %s",
714
+ query,
715
+ request_url,
679
716
  )
680
717
 
681
718
  return self.do_request(
@@ -684,22 +721,29 @@ class Salesforce(object):
684
721
  headers=request_header,
685
722
  timeout=REQUEST_TIMEOUT,
686
723
  failure_message="Failed to retrieve Salesforce object type -> '{}' with {} = {}".format(
687
- object_type, search_field, search_value
724
+ object_type,
725
+ search_field,
726
+ search_value,
688
727
  ),
689
728
  )
690
729
 
691
730
  # end method definition
692
731
 
693
- def add_object(self, object_type: str, **kwargs: Any) -> dict | None:
694
- """Add object to Salesforce. This is a generic wrapper method
695
- for the actual add methods.
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.
696
736
 
697
737
  Args:
698
- object_type (str): Type of the Salesforce business object, like "Account" or "Case".
699
- **kwargs (dict): keyword / value ictionary with additional parameters
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.
700
743
 
701
744
  Returns:
702
745
  dict | None: Dictionary with the Salesforce object data or None if the request fails.
746
+
703
747
  """
704
748
 
705
749
  match object_type:
@@ -767,25 +811,30 @@ class Salesforce(object):
767
811
  **kwargs,
768
812
  )
769
813
  case _:
770
- logger.error(
814
+ self.logger.error(
771
815
  "Unsupported Salesforce business object -> %s!",
772
816
  object_type,
773
817
  )
774
818
 
819
+ return None
820
+
775
821
  # end method definition
776
822
 
777
- def get_group_id(self, groupname: str) -> Optional[str]:
823
+ def get_group_id(self, group_name: str) -> str | None:
778
824
  """Get a group ID by group name.
779
825
 
780
826
  Args:
781
- groupname (str): Name of the Group.
827
+ group_name (str): Name of the Group.
782
828
 
783
829
  Returns:
784
830
  Optional[str]: Technical ID of the group
831
+
785
832
  """
786
833
 
787
834
  return self.get_object_id_by_name(
788
- object_type="Group", name=groupname, name_field="Name"
835
+ object_type="Group",
836
+ name=group_name,
837
+ name_field="Name",
789
838
  )
790
839
 
791
840
  # end method definition
@@ -794,10 +843,13 @@ class Salesforce(object):
794
843
  """Get a Salesforce group based on its ID.
795
844
 
796
845
  Args:
797
- group_id (str): ID of the Salesforce group
846
+ group_id (str):
847
+ ID of the Salesforce group to retrieve.
798
848
 
799
849
  Returns:
800
- dict | None: Dictionary with the Salesforce group data or None if the request fails.
850
+ dict | None:
851
+ Dictionary with the Salesforce group data or None if the request fails.
852
+
801
853
  """
802
854
 
803
855
  if not self._access_token or not self._instance_url:
@@ -806,8 +858,10 @@ class Salesforce(object):
806
858
  request_header = self.request_header()
807
859
  request_url = self.config()["groupUrl"] + group_id
808
860
 
809
- logger.debug(
810
- "Get Salesforce group with ID -> %s; calling -> %s", group_id, request_url
861
+ self.logger.debug(
862
+ "Get Salesforce group with ID -> %s; calling -> %s",
863
+ group_id,
864
+ request_url,
811
865
  )
812
866
 
813
867
  return self.do_request(
@@ -816,7 +870,7 @@ class Salesforce(object):
816
870
  headers=request_header,
817
871
  timeout=REQUEST_TIMEOUT,
818
872
  failure_message="Failed to get Salesforce group with ID -> {}".format(
819
- group_id
873
+ group_id,
820
874
  ),
821
875
  )
822
876
 
@@ -831,16 +885,18 @@ class Salesforce(object):
831
885
 
832
886
  Args:
833
887
  group_name (str): Name of the new Salesforce group
888
+ group_type (str, optional): Type of the group. Default is "Regular".
834
889
 
835
890
  Returns:
836
891
  dict | None: Dictionary with the Salesforce Group data or None if the request fails.
837
892
 
838
- Example response:
893
+ Example:
839
894
  {
840
895
  'id': '00GDn000000KWE0MAO',
841
896
  'success': True,
842
897
  'errors': []
843
898
  }
899
+
844
900
  """
845
901
 
846
902
  if not self._access_token or not self._instance_url:
@@ -851,8 +907,10 @@ class Salesforce(object):
851
907
 
852
908
  payload = {"Name": group_name, "Type": group_type}
853
909
 
854
- logger.debug(
855
- "Adding Salesforce group -> %s; calling -> %s", group_name, request_url
910
+ self.logger.debug(
911
+ "Adding Salesforce group -> %s; calling -> %s",
912
+ group_name,
913
+ request_url,
856
914
  )
857
915
 
858
916
  return self.do_request(
@@ -878,7 +936,8 @@ class Salesforce(object):
878
936
  update_data (dict): Dictionary containing the fields to update.
879
937
 
880
938
  Returns:
881
- dict: Response from the Salesforce API.
939
+ dict | None: Response from the Salesforce API. None in case of an error.
940
+
882
941
  """
883
942
 
884
943
  if not self._access_token or not self._instance_url:
@@ -888,7 +947,7 @@ class Salesforce(object):
888
947
 
889
948
  request_url = self.config()["groupUrl"] + group_id
890
949
 
891
- logger.debug(
950
+ self.logger.debug(
892
951
  "Update Salesforce group with ID -> %s; calling -> %s",
893
952
  group_id,
894
953
  request_url,
@@ -901,22 +960,24 @@ class Salesforce(object):
901
960
  json_data=update_data,
902
961
  timeout=REQUEST_TIMEOUT,
903
962
  failure_message="Failed to update Salesforce group with ID -> {}".format(
904
- group_id
963
+ group_id,
905
964
  ),
906
965
  )
907
966
 
908
967
  # end method definition
909
968
 
910
969
  def get_group_members(self, group_id: str) -> list | None:
911
- """Get Salesforce group members
970
+ """Get Salesforce group members.
912
971
 
913
972
  Args:
914
- group_id (str): Id of the group to retrieve the members
973
+ group_id (str):
974
+ The ID of the group to retrieve the members.
915
975
 
916
976
  Returns:
917
- list | None: result
977
+ list | None:
978
+ The group members.
918
979
 
919
- Example response:
980
+ Example:
920
981
  {
921
982
  'totalSize': 1,
922
983
  'done': True,
@@ -930,6 +991,7 @@ class Salesforce(object):
930
991
  }
931
992
  ]
932
993
  }
994
+
933
995
  """
934
996
 
935
997
  if not self._access_token or not self._instance_url:
@@ -942,7 +1004,7 @@ class Salesforce(object):
942
1004
  query = f"SELECT UserOrGroupId FROM GroupMember WHERE GroupId = '{group_id}'"
943
1005
  params = {"q": query}
944
1006
 
945
- logger.debug(
1007
+ self.logger.debug(
946
1008
  "Get members of Salesforce group with ID -> %s; calling -> %s",
947
1009
  group_id,
948
1010
  request_url,
@@ -955,28 +1017,32 @@ class Salesforce(object):
955
1017
  params=params,
956
1018
  timeout=REQUEST_TIMEOUT,
957
1019
  failure_message="Failed to get members of Salesforce group with ID -> {}".format(
958
- group_id
1020
+ group_id,
959
1021
  ),
960
1022
  )
961
1023
 
962
1024
  # end method definition
963
1025
 
964
1026
  def add_group_member(self, group_id: str, member_id: str) -> dict | None:
965
- """Add a user or group to a Salesforce group
1027
+ """Add a user or group to a Salesforce group.
966
1028
 
967
1029
  Args:
968
- group_id (str): ID of the Salesforce Group to add member to.
969
- member_id (str): ID of the user or group.
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.
970
1034
 
971
1035
  Returns:
972
- dict | None: Dictionary with the Salesforce membership data or None if the request fails.
1036
+ dict | None:
1037
+ Dictionary with the Salesforce membership data or None if the request fails.
973
1038
 
974
- Example response (id is the membership ID):
1039
+ Example response (id is the membership ID):
975
1040
  {
976
1041
  'id': '011Dn000000ELhwIAG',
977
1042
  'success': True,
978
1043
  'errors': []
979
1044
  }
1045
+
980
1046
  """
981
1047
 
982
1048
  if not self._access_token or not self._instance_url:
@@ -988,7 +1054,7 @@ class Salesforce(object):
988
1054
 
989
1055
  payload = {"GroupId": group_id, "UserOrGroupId": member_id}
990
1056
 
991
- logger.debug(
1057
+ self.logger.debug(
992
1058
  "Add member with ID -> %s to Salesforce group with ID -> %s; calling -> %s",
993
1059
  member_id,
994
1060
  group_id,
@@ -1002,19 +1068,21 @@ class Salesforce(object):
1002
1068
  json_data=payload,
1003
1069
  timeout=REQUEST_TIMEOUT,
1004
1070
  failure_message="Failed to add member with ID -> {} to Salesforce group with ID -> {}".format(
1005
- member_id, group_id
1071
+ member_id,
1072
+ group_id,
1006
1073
  ),
1007
1074
  )
1008
1075
 
1009
1076
  # end method definition
1010
1077
 
1011
1078
  def get_all_user_profiles(self) -> dict | None:
1012
- """Get all user profiles
1079
+ """Get all user profiles.
1013
1080
 
1014
1081
  Returns:
1015
- dict | None: Dictionary with salesforce user profiles.
1082
+ dict | None:
1083
+ Dictionary with Salesforce user profiles.
1016
1084
 
1017
- Example response:
1085
+ Example response:
1018
1086
  {
1019
1087
  'totalSize': 15,
1020
1088
  'done': True,
@@ -1038,6 +1106,7 @@ class Salesforce(object):
1038
1106
  }, ...
1039
1107
  ]
1040
1108
  }
1109
+
1041
1110
  """
1042
1111
 
1043
1112
  if not self._access_token or not self._instance_url:
@@ -1059,7 +1128,7 @@ class Salesforce(object):
1059
1128
 
1060
1129
  # end method definition
1061
1130
 
1062
- def get_user_profile_id(self, profile_name: str) -> Optional[str]:
1131
+ def get_user_profile_id(self, profile_name: str) -> str | None:
1063
1132
  """Get a user profile ID by profile name.
1064
1133
 
1065
1134
  Args:
@@ -1067,13 +1136,14 @@ class Salesforce(object):
1067
1136
 
1068
1137
  Returns:
1069
1138
  Optional[str]: Technical ID of the user profile.
1139
+
1070
1140
  """
1071
1141
 
1072
1142
  return self.get_object_id_by_name(object_type="Profile", name=profile_name)
1073
1143
 
1074
1144
  # end method definition
1075
1145
 
1076
- def get_user_id(self, username: str) -> Optional[str]:
1146
+ def get_user_id(self, username: str) -> str | None:
1077
1147
  """Get a user ID by user name.
1078
1148
 
1079
1149
  Args:
@@ -1081,10 +1151,13 @@ class Salesforce(object):
1081
1151
 
1082
1152
  Returns:
1083
1153
  Optional[str]: Technical ID of the user
1154
+
1084
1155
  """
1085
1156
 
1086
1157
  return self.get_object_id_by_name(
1087
- object_type="User", name=username, name_field="Username"
1158
+ object_type="User",
1159
+ name=username,
1160
+ name_field="Username",
1088
1161
  )
1089
1162
 
1090
1163
  # end method definition
@@ -1097,6 +1170,7 @@ class Salesforce(object):
1097
1170
 
1098
1171
  Returns:
1099
1172
  dict | None: Dictionary with the Salesforce user data or None if the request fails.
1173
+
1100
1174
  """
1101
1175
 
1102
1176
  if not self._access_token or not self._instance_url:
@@ -1105,8 +1179,10 @@ class Salesforce(object):
1105
1179
  request_header = self.request_header()
1106
1180
  request_url = self.config()["userUrl"] + user_id
1107
1181
 
1108
- logger.debug(
1109
- "Get Salesforce user with ID -> %s; calling -> %s", user_id, request_url
1182
+ self.logger.debug(
1183
+ "Get Salesforce user with ID -> %s; calling -> %s",
1184
+ user_id,
1185
+ request_url,
1110
1186
  )
1111
1187
 
1112
1188
  return self.do_request(
@@ -1115,7 +1191,7 @@ class Salesforce(object):
1115
1191
  headers=request_header,
1116
1192
  timeout=REQUEST_TIMEOUT,
1117
1193
  failure_message="Failed to get Salesforce user with ID -> {}".format(
1118
- user_id
1194
+ user_id,
1119
1195
  ),
1120
1196
  )
1121
1197
 
@@ -1130,34 +1206,48 @@ class Salesforce(object):
1130
1206
  title: str | None = None,
1131
1207
  department: str | None = None,
1132
1208
  company_name: str = "Innovate",
1133
- profile_name: Optional[str] = "Standard User",
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",
1138
- alias: Optional[str] = None,
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,
1139
1215
  ) -> dict | None:
1140
1216
  """Add a new Salesforce user. The password has to be set separately.
1141
1217
 
1142
1218
  Args:
1143
- username (str): Login name of the new user
1144
- email (str): Email of the new user
1145
- firstname (str): First name of the new user.
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"
1151
- profile_id (str, optional): Profile ID of the new user. Defaults to None.
1152
- Use method get_all_user_profiles() to determine
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".
1157
- alias (str, optional): Alias of the new user. Defaults to None.
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.
1158
1247
 
1159
1248
  Returns:
1160
1249
  dict | None: Dictionary with the Salesforce User data or None if the request fails.
1250
+
1161
1251
  """
1162
1252
 
1163
1253
  if not self._access_token or not self._instance_url:
@@ -1186,8 +1276,10 @@ class Salesforce(object):
1186
1276
  "LanguageLocaleKey": locale_key, # Set default LanguageLocaleKey
1187
1277
  }
1188
1278
 
1189
- logger.debug(
1190
- "Adding Salesforce user -> %s; calling -> %s", username, request_url
1279
+ self.logger.debug(
1280
+ "Adding Salesforce user -> %s; calling -> %s",
1281
+ username,
1282
+ request_url,
1191
1283
  )
1192
1284
 
1193
1285
  return self.do_request(
@@ -1214,6 +1306,7 @@ class Salesforce(object):
1214
1306
 
1215
1307
  Returns:
1216
1308
  dict: Response from the Salesforce API.
1309
+
1217
1310
  """
1218
1311
 
1219
1312
  if not self._access_token or not self._instance_url:
@@ -1223,8 +1316,10 @@ class Salesforce(object):
1223
1316
 
1224
1317
  request_url = self.config()["userUrl"] + user_id
1225
1318
 
1226
- logger.debug(
1227
- "Update Salesforce user with ID -> %s; calling -> %s", user_id, request_url
1319
+ self.logger.debug(
1320
+ "Update Salesforce user with ID -> %s; calling -> %s",
1321
+ user_id,
1322
+ request_url,
1228
1323
  )
1229
1324
 
1230
1325
  return self.do_request(
@@ -1234,7 +1329,7 @@ class Salesforce(object):
1234
1329
  json_data=update_data,
1235
1330
  timeout=REQUEST_TIMEOUT,
1236
1331
  failure_message="Failed to update Salesforce user with ID -> {}".format(
1237
- user_id
1332
+ user_id,
1238
1333
  ),
1239
1334
  )
1240
1335
 
@@ -1244,7 +1339,7 @@ class Salesforce(object):
1244
1339
  self,
1245
1340
  user_id: str,
1246
1341
  password: str,
1247
- ) -> dict:
1342
+ ) -> dict | None:
1248
1343
  """Update the password of a Salesforce user.
1249
1344
 
1250
1345
  Args:
@@ -1253,6 +1348,7 @@ class Salesforce(object):
1253
1348
 
1254
1349
  Returns:
1255
1350
  dict: Response from the Salesforce API.
1351
+
1256
1352
  """
1257
1353
 
1258
1354
  if not self._access_token or not self._instance_url:
@@ -1262,7 +1358,7 @@ class Salesforce(object):
1262
1358
 
1263
1359
  request_url = self.config()["userUrl"] + "{}/password".format(user_id)
1264
1360
 
1265
- logger.debug(
1361
+ self.logger.debug(
1266
1362
  "Update password of Salesforce user with ID -> %s; calling -> %s",
1267
1363
  user_id,
1268
1364
  request_url,
@@ -1277,7 +1373,7 @@ class Salesforce(object):
1277
1373
  json_data=update_data,
1278
1374
  timeout=REQUEST_TIMEOUT,
1279
1375
  failure_message="Failed to update password of Salesforce user with ID -> {}".format(
1280
- user_id
1376
+ user_id,
1281
1377
  ),
1282
1378
  )
1283
1379
 
@@ -1293,8 +1389,10 @@ class Salesforce(object):
1293
1389
  Args:
1294
1390
  user_id (str): Salesforce ID of the user
1295
1391
  photo_path (str): file system path with the location of the photo
1392
+
1296
1393
  Returns:
1297
1394
  dict | None: Dictionary with the Salesforce User data or None if the request fails.
1395
+
1298
1396
  """
1299
1397
 
1300
1398
  if not self._access_token or not self._instance_url:
@@ -1302,17 +1400,18 @@ class Salesforce(object):
1302
1400
 
1303
1401
  # Check if the photo file exists
1304
1402
  if not os.path.isfile(photo_path):
1305
- logger.error("Photo file -> %s not found!", photo_path)
1403
+ self.logger.error("Photo file -> %s not found!", photo_path)
1306
1404
  return None
1307
1405
 
1308
1406
  try:
1309
1407
  # Read the photo file as binary data
1310
1408
  with open(photo_path, "rb") as image_file:
1311
1409
  photo_data = image_file.read()
1312
- except OSError as exception:
1410
+ except OSError:
1313
1411
  # Handle any errors that occurred while reading the photo file
1314
- logger.error(
1315
- "Error reading photo file -> %s; error -> %s", photo_path, exception
1412
+ self.logger.error(
1413
+ "Error reading photo file -> %s",
1414
+ photo_path,
1316
1415
  )
1317
1416
  return None
1318
1417
 
@@ -1327,10 +1426,10 @@ class Salesforce(object):
1327
1426
  photo_path,
1328
1427
  photo_data,
1329
1428
  "application/octet-stream",
1330
- )
1429
+ ),
1331
1430
  }
1332
1431
 
1333
- logger.debug(
1432
+ self.logger.debug(
1334
1433
  "Update profile photo of Salesforce user with ID -> %s; calling -> %s",
1335
1434
  user_id,
1336
1435
  request_url,
@@ -1344,7 +1443,7 @@ class Salesforce(object):
1344
1443
  data=data,
1345
1444
  timeout=REQUEST_TIMEOUT,
1346
1445
  failure_message="Failed to update profile photo of Salesforce user with ID -> {}".format(
1347
- user_id
1446
+ user_id,
1348
1447
  ),
1349
1448
  verify=False,
1350
1449
  )
@@ -1356,11 +1455,11 @@ class Salesforce(object):
1356
1455
  account_name: str,
1357
1456
  account_number: str,
1358
1457
  account_type: str = "Customer",
1359
- description: Optional[str] = None,
1360
- industry: Optional[str] = None,
1361
- website: Optional[str] = None,
1362
- phone: Optional[str] = None,
1363
- **kwargs: Any,
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],
1364
1463
  ) -> dict | None:
1365
1464
  """Add a new Account object to Salesforce.
1366
1465
 
@@ -1372,10 +1471,11 @@ class Salesforce(object):
1372
1471
  industry (str, optional): Industry of the new Salesforce account. Defaults to None.
1373
1472
  website (str, optional): Website of the new Salesforce account. Defaults to None.
1374
1473
  phone (str, optional): Phone number of the new Salesforce account. Defaults to None.
1375
- kwargs (Any): Additional values (e.g. custom fields)
1474
+ kwargs (dict): Additional values (e.g. custom fields)
1376
1475
 
1377
1476
  Returns:
1378
1477
  dict | None: Dictionary with the Salesforce Account data or None if the request fails.
1478
+
1379
1479
  """
1380
1480
 
1381
1481
  if not self._access_token or not self._instance_url:
@@ -1395,7 +1495,7 @@ class Salesforce(object):
1395
1495
  }
1396
1496
  payload.update(kwargs) # Add additional fields from kwargs
1397
1497
 
1398
- logger.debug(
1498
+ self.logger.debug(
1399
1499
  "Adding Salesforce account -> '%s' (%s); calling -> %s",
1400
1500
  account_name,
1401
1501
  account_number,
@@ -1409,7 +1509,8 @@ class Salesforce(object):
1409
1509
  data=json.dumps(payload),
1410
1510
  timeout=REQUEST_TIMEOUT,
1411
1511
  failure_message="Failed to add Salesforce account -> '{}' ({})".format(
1412
- account_name, account_number
1512
+ account_name,
1513
+ account_number,
1413
1514
  ),
1414
1515
  )
1415
1516
 
@@ -1421,18 +1522,25 @@ class Salesforce(object):
1421
1522
  product_code: str,
1422
1523
  description: str,
1423
1524
  price: float,
1424
- **kwargs: Any,
1525
+ **kwargs: dict[str, str],
1425
1526
  ) -> dict | None:
1426
1527
  """Add a new Product object to Salesforce.
1427
1528
 
1428
1529
  Args:
1429
- product_name (str): Name of the Salesforce Product.
1430
- product_code (str): Code of the Salesforce Product.
1431
- description (str): Description of the Salesforce Product.
1432
- price (float): Price of the Salesforce Product.
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.
1433
1540
 
1434
1541
  Returns:
1435
1542
  dict | None: Dictionary with the Salesforce Product data or None if the request fails.
1543
+
1436
1544
  """
1437
1545
 
1438
1546
  if not self._access_token or not self._instance_url:
@@ -1449,7 +1557,7 @@ class Salesforce(object):
1449
1557
  }
1450
1558
  payload.update(kwargs) # Add additional fields from kwargs
1451
1559
 
1452
- logger.debug(
1560
+ self.logger.debug(
1453
1561
  "Add Salesforce product -> '%s' (%s); calling -> %s",
1454
1562
  product_name,
1455
1563
  product_code,
@@ -1463,7 +1571,8 @@ class Salesforce(object):
1463
1571
  data=json.dumps(payload),
1464
1572
  timeout=REQUEST_TIMEOUT,
1465
1573
  failure_message="Failed to add Salesforce product -> '{}' ({})".format(
1466
- product_name, product_code
1574
+ product_name,
1575
+ product_code,
1467
1576
  ),
1468
1577
  )
1469
1578
 
@@ -1474,25 +1583,38 @@ class Salesforce(object):
1474
1583
  name: str,
1475
1584
  stage: str,
1476
1585
  close_date: str,
1477
- amount: Union[int, float],
1586
+ amount: float,
1478
1587
  account_id: str,
1479
- description: str = None,
1480
- **kwargs: Any,
1588
+ description: str | None = None,
1589
+ **kwargs: dict[str, str],
1481
1590
  ) -> dict | None:
1482
1591
  """Add a new Opportunity object to Salesfoce.
1483
1592
 
1484
1593
  Args:
1485
1594
  name (str): Name of the Opportunity.
1486
- stage (str): Stage of the Opportunity. Typical Value:
1487
- "Prospecting", "Qualification", "Value Proposition", "Negotiation/Review",
1488
- "Closed Won", "Closed Lost"
1489
- close_date (str): Close date of the Opportunity. Should be in format YYYY-MM-DD.
1490
- amount (Union[int, float]): Amount (expected revenue) of the opportunity.
1491
- Can either be an integer or a float value.
1492
- account_id (str): Technical ID of the related Salesforce Account.
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.
1493
1614
 
1494
1615
  Returns:
1495
1616
  dict | None: Dictionary with the Salesforce Opportunity data or None if the request fails.
1617
+
1496
1618
  """
1497
1619
 
1498
1620
  if not self._access_token or not self._instance_url:
@@ -1512,8 +1634,10 @@ class Salesforce(object):
1512
1634
  payload["Description"] = description
1513
1635
  payload.update(kwargs) # Add additional fields from kwargs
1514
1636
 
1515
- logger.debug(
1516
- "Add Salesforce opportunity -> '%s'; calling -> %s", name, request_url
1637
+ self.logger.debug(
1638
+ "Add Salesforce opportunity -> '%s'; calling -> %s",
1639
+ name,
1640
+ request_url,
1517
1641
  )
1518
1642
 
1519
1643
  return self.do_request(
@@ -1536,27 +1660,40 @@ class Salesforce(object):
1536
1660
  origin: str,
1537
1661
  account_id: str,
1538
1662
  owner_id: str,
1539
- asset_id: Optional[str] = None,
1540
- product_id: Optional[str] = None,
1541
- **kwargs: Any,
1663
+ asset_id: str | None = None,
1664
+ product_id: str | None = None,
1665
+ **kwargs: dict[str, str],
1542
1666
  ) -> dict | None:
1543
- """Add a new Case object to Salesforce. The case number is automatically created and can not be
1544
- provided.
1667
+ """Add a new Case object to Salesforce.
1668
+
1669
+ The case number is automatically created and can not be provided.
1545
1670
 
1546
1671
  Args:
1547
- subject (str): Subject (title) of the case. It's like the name.
1548
- description (str): Description of the case
1549
- status (str): Status of the case. Typecal values: "New", "On Hold", "Escalated"
1550
- priority (str): Priority of the case. Typical values: "High", "Medium", "Low".
1551
- origin (str): origin (source) of the case. Typical values: "Email", "Phone", "Web"
1552
- account_id (str): technical ID of the related Account
1553
- owner_id (str): owner of the case
1554
- asset_id (str): technical ID of the related Asset
1555
- product_id (str): technical ID of the related Product
1556
- kwargs (Any): additional values (e.g. custom fields)
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)
1557
1692
 
1558
1693
  Returns:
1559
- dict | None: Dictionary with the Salesforce Case data or None if the request fails.
1694
+ dict | None:
1695
+ Dictionary with the Salesforce Case data or None if the request fails.
1696
+
1560
1697
  """
1561
1698
 
1562
1699
  if not self._access_token or not self._instance_url:
@@ -1581,7 +1718,11 @@ class Salesforce(object):
1581
1718
  payload["ProductId"] = product_id
1582
1719
  payload.update(kwargs) # Add additional fields from kwargs
1583
1720
 
1584
- logger.debug("Add Salesforce case -> '%s'; calling -> %s", subject, request_url)
1721
+ self.logger.debug(
1722
+ "Add Salesforce case -> '%s'; calling -> %s",
1723
+ subject,
1724
+ request_url,
1725
+ )
1585
1726
 
1586
1727
  return self.do_request(
1587
1728
  method="POST",
@@ -1603,22 +1744,33 @@ class Salesforce(object):
1603
1744
  purchase_date: str,
1604
1745
  install_date: str,
1605
1746
  description: str | None = None,
1606
- **kwargs: Any,
1747
+ **kwargs: dict[str, str],
1607
1748
  ) -> dict | None:
1608
1749
  """Add a new Asset object to Salesforce.
1609
1750
 
1610
1751
  Args:
1611
- asset_name (str): Name of the Asset.
1612
- product_id (str): Related Product ID.
1613
- serial_number (str): Serial Number of the Asset.
1614
- status (str): Status of the Asset. Typical values are "Purchased", "Shipped", "Installed", "Registered", "Obsolete"
1615
- purchase_date (str): Purchase date of the Asset.
1616
- install_date (str): Install date of the Asset.
1617
- description (str): Description of the Asset.
1618
- kwargs (Any): Additional values (e.g. custom fields)
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)
1619
1769
 
1620
1770
  Returns:
1621
- dict | None: Dictionary with the Salesforce Asset data or None if the request fails.
1771
+ dict | None:
1772
+ Dictionary with the Salesforce Asset data or None if the request fails.
1773
+
1622
1774
  """
1623
1775
 
1624
1776
  if not self._access_token or not self._instance_url:
@@ -1639,8 +1791,10 @@ class Salesforce(object):
1639
1791
  payload["Description"] = description
1640
1792
  payload.update(kwargs) # Add additional fields from kwargs
1641
1793
 
1642
- logger.debug(
1643
- "Add Salesforce asset -> '%s'; calling -> %s", asset_name, request_url
1794
+ self.logger.debug(
1795
+ "Add Salesforce asset -> '%s'; calling -> %s",
1796
+ asset_name,
1797
+ request_url,
1644
1798
  )
1645
1799
 
1646
1800
  return self.do_request(
@@ -1660,23 +1814,43 @@ class Salesforce(object):
1660
1814
  start_date: str,
1661
1815
  contract_term: int,
1662
1816
  status: str = "Draft",
1663
- description: Optional[str] = None,
1664
- contract_type: Optional[str] = None,
1665
- **kwargs: Any,
1817
+ description: str | None = None,
1818
+ contract_type: str | None = None,
1819
+ **kwargs: dict[str, str],
1666
1820
  ) -> dict | None:
1667
1821
  """Add a new Contract object to Salesforce.
1668
1822
 
1669
1823
  Args:
1670
- account_id (str): Technical ID of the related Salesforce Account object.
1671
- start_date (str): Start date of the Contract. Use YYYY-MM-DD notation.
1672
- contract_term (int): Term of the Contract in number of months, e.g. 48 for 4 years term.
1673
- The end date of the contract will be calculated from start date + term.
1674
- contract_type (str): Type of the Contract. Typical values are "Subscription",
1675
- "Maintenance", "Support", "Lease", or "Service".
1676
- status (str): Status of the Contract. Typical values are "Draft", "Activated", or "In Approval Process"
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.
1677
1849
 
1678
1850
  Returns:
1679
- dict | None: Dictionary with the Salesforce user data or None if the request fails.
1851
+ dict | None:
1852
+ Dictionary with the Salesforce contract data or None if the request fails.
1853
+
1680
1854
  """
1681
1855
 
1682
1856
  if not self._access_token or not self._instance_url:
@@ -1697,7 +1871,7 @@ class Salesforce(object):
1697
1871
  payload["ContractType"] = contract_type
1698
1872
  payload.update(kwargs) # Add additional fields from kwargs
1699
1873
 
1700
- logger.debug(
1874
+ self.logger.debug(
1701
1875
  "Adding Salesforce contract for account with ID -> %s; calling -> %s",
1702
1876
  account_id,
1703
1877
  request_url,
@@ -1710,7 +1884,7 @@ class Salesforce(object):
1710
1884
  data=json.dumps(payload),
1711
1885
  timeout=REQUEST_TIMEOUT,
1712
1886
  failure_message="Failed to add Salesforce contract for account with ID -> {}".format(
1713
- account_id
1887
+ account_id,
1714
1888
  ),
1715
1889
  )
1716
1890