salespyforce 1.4.0.dev0__py3-none-any.whl → 1.4.0.dev2__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.
salespyforce/api.py CHANGED
@@ -4,7 +4,7 @@
4
4
  :Synopsis: Defines the basic functions associated with the Salesforce API
5
5
  :Created By: Jeff Shurtliff
6
6
  :Last Modified: Jeff Shurtliff
7
- :Modified Date: 17 Feb 2023
7
+ :Modified Date: 29 Jan 2026
8
8
  """
9
9
 
10
10
  import requests
@@ -117,6 +117,49 @@ def api_call_with_payload(sfdc_object, method, endpoint, payload, params=None, h
117
117
  return response
118
118
 
119
119
 
120
+ def delete(sfdc_object, endpoint, params=None, headers=None, timeout=30, show_full_error=True, return_json=True):
121
+ """This method performs a DELETE request against the Salesforce instance.
122
+
123
+ .. version-added:: 1.4.0
124
+
125
+ :param sfdc_object: The instantiated SalesPyForce object
126
+ :param endpoint: The API endpoint to query
127
+ :type endpoint: str
128
+ :param params: The query parameters (where applicable)
129
+ :type params: dict, None
130
+ :param headers: Specific API headers to use when performing the API call
131
+ :type headers: dict, None
132
+ :param timeout: The timeout period in seconds (defaults to ``30``)
133
+ :type timeout: int, str, None
134
+ :param show_full_error: Determines if the full error message should be displayed (defaults to ``True``)
135
+ :type show_full_error: bool
136
+ :param return_json: Determines if the response should be returned in JSON format (defaults to ``True``)
137
+ :returns: The API response in JSON format or as a ``requests`` object
138
+ """
139
+ # Define the parameters as an empty dictionary if none are provided
140
+ params = {} if params is None else params
141
+
142
+ # Define the headers
143
+ default_headers = _get_headers(sfdc_object.access_token)
144
+ headers = default_headers if not headers else headers
145
+
146
+ # Make sure the endpoint begins with a slash
147
+ endpoint = f'/{endpoint}' if not endpoint.startswith('/') else endpoint
148
+
149
+ # Perform the API call
150
+ response = requests.delete(f'{sfdc_object.instance_url}{endpoint}', headers=headers, params=params,
151
+ timeout=timeout)
152
+ if response.status_code >= 300:
153
+ if show_full_error:
154
+ raise RuntimeError(f'The DELETE request failed with a {response.status_code} status code.\n'
155
+ f'{response.text}')
156
+ else:
157
+ raise RuntimeError(f'The DELETE request failed with a {response.status_code} status code.')
158
+ if return_json:
159
+ response = response.json()
160
+ return response
161
+
162
+
120
163
  def _get_headers(_access_token, _header_type='default'):
121
164
  """This function returns the appropriate HTTP headers to use for different types of API calls."""
122
165
  headers = {
salespyforce/core.py CHANGED
@@ -6,7 +6,7 @@
6
6
  :Example: ``sfdc = Salesforce(helper=helper_file_path)``
7
7
  :Created By: Jeff Shurtliff
8
8
  :Last Modified: Jeff Shurtliff
9
- :Modified Date: 17 Nov 2025
9
+ :Modified Date: 30 Jan 2026
10
10
  """
11
11
 
12
12
  import re
@@ -60,7 +60,8 @@ class Salesforce(object):
60
60
  :param helper: The file path of a helper file
61
61
  :type helper: str, None
62
62
  :returns: The instantiated object
63
- :raises: :py:exc:`TypeError`
63
+ :raises: :py:exc:`TypeError`,
64
+ :py:exc:`RuntimeError`
64
65
  """
65
66
  # Define the default settings
66
67
  self._helper_settings = {}
@@ -147,6 +148,7 @@ class Salesforce(object):
147
148
  (`Reference <https://jereze.com/code/authentification-salesforce-rest-api-python/>`_)
148
149
 
149
150
  :returns: The API call response with the authorization information
151
+ :raises: :py:exc:`RuntimeError`
150
152
  """
151
153
  params = {
152
154
  'grant_type': 'password',
@@ -176,6 +178,7 @@ class Salesforce(object):
176
178
  :type show_full_error: bool
177
179
  :param return_json: Determines if the response should be returned in JSON format (defaults to ``True``)
178
180
  :returns: The API response in JSON format or as a ``requests`` object
181
+ :raises: :py:exc:`RuntimeError`
179
182
  """
180
183
  return api.get(self, endpoint=endpoint, params=params, headers=headers, timeout=timeout,
181
184
  show_full_error=show_full_error, return_json=return_json)
@@ -201,6 +204,7 @@ class Salesforce(object):
201
204
  :type show_full_error: bool
202
205
  :param return_json: Determines if the response should be returned in JSON format (defaults to ``True``)
203
206
  :returns: The API response in JSON format or as a ``requests`` object
207
+ :raises: :py:exc:`RuntimeError`
204
208
  """
205
209
  return api.api_call_with_payload(self, method=method, endpoint=endpoint, payload=payload, params=params,
206
210
  headers=headers, timeout=timeout, show_full_error=show_full_error,
@@ -224,6 +228,7 @@ class Salesforce(object):
224
228
  :type show_full_error: bool
225
229
  :param return_json: Determines if the response should be returned in JSON format (defaults to ``True``)
226
230
  :returns: The API response in JSON format or as a ``requests`` object
231
+ :raises: :py:exc:`RuntimeError`
227
232
  """
228
233
  return api.api_call_with_payload(self, 'post', endpoint=endpoint, payload=payload, params=params,
229
234
  headers=headers, timeout=timeout, show_full_error=show_full_error,
@@ -247,6 +252,7 @@ class Salesforce(object):
247
252
  :type show_full_error: bool
248
253
  :param return_json: Determines if the response should be returned in JSON format (defaults to ``True``)
249
254
  :returns: The API response in JSON format or as a ``requests`` object
255
+ :raises: :py:exc:`RuntimeError`
250
256
  """
251
257
  return api.api_call_with_payload(self, 'patch', endpoint=endpoint, payload=payload, params=params,
252
258
  headers=headers, timeout=timeout, show_full_error=show_full_error,
@@ -270,16 +276,41 @@ class Salesforce(object):
270
276
  :type show_full_error: bool
271
277
  :param return_json: Determines if the response should be returned in JSON format (defaults to ``True``)
272
278
  :returns: The API response in JSON format or as a ``requests`` object
279
+ :raises: :py:exc:`RuntimeError`
273
280
  """
274
281
  return api.api_call_with_payload(self, 'put', endpoint=endpoint, payload=payload, params=params,
275
282
  headers=headers, timeout=timeout, show_full_error=show_full_error,
276
283
  return_json=return_json)
277
284
 
285
+ def delete(self, endpoint, params=None, headers=None, timeout=30, show_full_error=True, return_json=True):
286
+ """This method performs a DELETE request against the Salesforce instance.
287
+ (`Reference <https://jereze.com/code/authentification-salesforce-rest-api-python/>`_)
288
+
289
+ .. version-added:: 1.4.0
290
+
291
+ :param endpoint: The API endpoint to query
292
+ :type endpoint: str
293
+ :param params: The query parameters (where applicable)
294
+ :type params: dict, None
295
+ :param headers: Specific API headers to use when performing the API call
296
+ :type headers: dict, None
297
+ :param timeout: The timeout period in seconds (defaults to ``30``)
298
+ :type timeout: int, str, None
299
+ :param show_full_error: Determines if the full error message should be displayed (defaults to ``True``)
300
+ :type show_full_error: bool
301
+ :param return_json: Determines if the response should be returned in JSON format (defaults to ``True``)
302
+ :returns: The API response in JSON format or as a ``requests`` object
303
+ :raises: :py:exc:`RuntimeError`
304
+ """
305
+ return api.delete(self, endpoint=endpoint, params=params, headers=headers, timeout=timeout,
306
+ show_full_error=show_full_error, return_json=return_json)
307
+
278
308
  def get_api_versions(self) -> list:
279
309
  """This method returns the API versions for the Salesforce releases.
280
310
  (`Reference <https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_versions.htm>`_)
281
311
 
282
312
  :returns: A list containing the API metadata from the ``/services/data`` endpoint.
313
+ :raises: :py:exc:`RuntimeError`
283
314
  """
284
315
  return self.get('/services/data')
285
316
 
@@ -305,13 +336,19 @@ class Salesforce(object):
305
336
  def get_org_limits(self):
306
337
  """This method returns a list of all org limits.
307
338
 
308
- .. versionadded:: 1.1.0
339
+ .. version-added:: 1.1.0
340
+
341
+ :returns: The Salesforce org governor limits data
342
+ :raises: :py:exc:`RuntimeError`
309
343
  """
310
344
  return self.get(f'/services/data/{self.version}/limits')
311
345
 
312
346
  def get_all_sobjects(self):
313
347
  """This method returns a list of all Salesforce objects. (i.e. sObjects)
314
348
  (`Reference <https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_describeGlobal.htm>`_)
349
+
350
+ :returns: The list of all Salesforce objects
351
+ :raises: :py:exc:`RuntimeError`
315
352
  """
316
353
  return self.get(f'/services/data/{self.version}/sobjects')
317
354
 
@@ -325,6 +362,7 @@ class Salesforce(object):
325
362
  :param describe: Determines if the full (i.e. ``describe``) data should be returned (defaults to ``False``)
326
363
  :type describe: bool
327
364
  :returns: The Salesforce object data
365
+ :raises: :py:exc:`RuntimeError`
328
366
  """
329
367
  uri = f'/services/data/{self.version}/sobjects/{object_name}'
330
368
  uri = f'{uri}/describe' if describe else uri
@@ -337,15 +375,32 @@ class Salesforce(object):
337
375
  :param object_name: The name of the Salesforce object
338
376
  :type object_name: str
339
377
  :returns: The Salesforce object data
378
+ :raises: :py:exc:`RuntimeError`
340
379
  """
341
380
  return self.get_sobject(object_name, describe=True)
342
381
 
343
382
  def get_rest_resources(self):
344
383
  """This method returns a list of all available REST resources.
345
384
  (`Reference <https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_discoveryresource.htm>`_)
385
+
386
+ :returns: The list of all available REST resources for the Salesforce org
387
+ :raises: :py:exc:`RuntimeError`
346
388
  """
347
389
  return self.get(f'/services/data/{self.version}')
348
390
 
391
+ @staticmethod
392
+ def get_18_char_id(record_id: str) -> str:
393
+ """This method converts a 15-character Salesforce record ID to its 18-character case-insensitive form.
394
+
395
+ .. version-added:: 1.4.0
396
+
397
+ :param record_id: The Salesforce record ID to convert (or return unchanged if already 18 characters)
398
+ :type record_id: str
399
+ :returns: The 18-character Salesforce record ID
400
+ :raises: :py:exc:`ValueError`
401
+ """
402
+ return core_utils.get_18_char_id(record_id=record_id)
403
+
349
404
  def soql_query(self, query, replace_quotes=True, next_records_url=False):
350
405
  """This method performs a SOQL query and returns the results in JSON format.
351
406
  (`Reference 1 <https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_query.htm>`_,
@@ -358,6 +413,7 @@ class Salesforce(object):
358
413
  :param next_records_url: Indicates that the ``query`` parameter is a ``nextRecordsUrl`` value.
359
414
  :type next_records_url: bool
360
415
  :returns: The result of the SOQL query
416
+ :raises: :py:exc:`RuntimeError`
361
417
  """
362
418
  if next_records_url:
363
419
  query = re.sub(r'^.*/', '', query) if '/' in query else query
@@ -372,11 +428,12 @@ class Salesforce(object):
372
428
  """This method performs a SOSL query to search for a given string.
373
429
  (`Reference <https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_search.htm>`_)
374
430
 
375
- .. versionadded:: 1.1.0
431
+ .. version-added:: 1.1.0
376
432
 
377
433
  :param string_to_search: The string for which to search
378
434
  :type string_to_search: str
379
435
  :returns: The SOSL response data in JSON format
436
+ :raises: :py:exc:`RuntimeError`
380
437
  """
381
438
  query = 'FIND {' + string_to_search + '}'
382
439
  query = core_utils.url_encode(query)
@@ -391,7 +448,8 @@ class Salesforce(object):
391
448
  :param payload: The JSON payload with the record details
392
449
  :type payload: dict
393
450
  :returns: The API response from the POST request
394
- :raises: :py:exc:`RuntimeError`, :py:exc:`TypeError`
451
+ :raises: :py:exc:`RuntimeError`,
452
+ :py:exc:`TypeError`
395
453
  """
396
454
  # Ensure the payload is in the appropriate format
397
455
  if not isinstance(payload, dict):
@@ -412,7 +470,8 @@ class Salesforce(object):
412
470
  :param payload: The JSON payload with the record details to be updated
413
471
  :type payload: dict
414
472
  :returns: The API response from the PATCH request
415
- :raises: :py:exc:`RuntimeError`, :py:exc:`TypeError`
473
+ :raises: :py:exc:`RuntimeError`,
474
+ :py:exc:`TypeError`
416
475
  """
417
476
  # Ensure the payload is in the appropriate format
418
477
  if not isinstance(payload, dict):
@@ -596,7 +655,8 @@ class Salesforce(object):
596
655
  :param return_uri: Determines if the URI of the article should be returned rather than the ID (``False`` by default)
597
656
  :type return_uri: bool
598
657
  :returns: The Article ID or Article URI, or a blank string if no article is found
599
- :raises: :py:exc:`ValueError`
658
+ :raises: :py:exc:`ValueError`,
659
+ :py:exc:`RuntimeError`
600
660
  """
601
661
  return knowledge_module.get_article_id_from_number(self.sfdc_object, article_number=article_number,
602
662
  sobject=sobject, return_uri=return_uri)
@@ -616,6 +676,7 @@ class Salesforce(object):
616
676
  :param page_num: The starting page number (``1`` by default)
617
677
  :type page_num: int
618
678
  :returns: The list of retrieved knowledge articles
679
+ :raises: :py:exc:`RuntimeError`
619
680
  """
620
681
  return knowledge_module.get_articles_list(self.sfdc_object, query=query, sort=sort, order=order,
621
682
  page_size=page_size, page_num=page_num)
@@ -629,6 +690,7 @@ class Salesforce(object):
629
690
  :param sobject: The Salesforce object to query (``Knowledge__kav`` by default)
630
691
  :type sobject: str, None
631
692
  :returns: The details for the knowledge article
693
+ :raises: :py:exc:`RuntimeError`
632
694
  """
633
695
  return knowledge_module.get_article_details(self.sfdc_object, article_id=article_id, sobject=sobject)
634
696
 
@@ -680,7 +742,8 @@ class Salesforce(object):
680
742
  :param sobject: The Salesforce object to query (``Knowledge__kav`` by default)
681
743
  :type sobject: str, None
682
744
  :returns: The article URL as a string
683
- :raises: :py:exc:`ValueError`
745
+ :raises: :py:exc:`ValueError`,
746
+ :py:exc:`RuntimeError`
684
747
  """
685
748
  return knowledge_module.get_article_url(self.sfdc_object, article_id=article_id,
686
749
  article_number=article_number, sobject=sobject)
@@ -696,7 +759,9 @@ class Salesforce(object):
696
759
  :param full_response: Determines if the full API response should be returned instead of the article ID (``False`` by default)
697
760
  :type full_response: bool
698
761
  :returns: The API response or the ID of the article draft
699
- :raises: :py:exc:`ValueError`, :py:exc:`TypeError`
762
+ :raises: :py:exc:`ValueError`,
763
+ :py:exc:`TypeError`,
764
+ :py:exc:`RuntimeError`
700
765
  """
701
766
  return knowledge_module.create_article(self.sfdc_object, article_data=article_data, sobject=sobject,
702
767
  full_response=full_response)
@@ -714,7 +779,9 @@ class Salesforce(object):
714
779
  :param include_status_code: Determines if the API response status code should be returned (``False`` by default)
715
780
  :type include_status_code: bool
716
781
  :returns: A Boolean indicating if the update operation was successful, and optionally the API response status code
717
- :raises: :py:exc:`ValueError`, :py:exc:`TypeError`, :py:exc:`RuntimeError`
782
+ :raises: :py:exc:`ValueError`,
783
+ :py:exc:`TypeError`,
784
+ :py:exc:`RuntimeError`
718
785
  """
719
786
  return knowledge_module.update_article(self.sfdc_object, record_id=record_id, article_data=article_data,
720
787
  sobject=sobject, include_status_code=include_status_code)
@@ -781,7 +848,9 @@ class Salesforce(object):
781
848
  :param major_version: Determines if the published article should be a major version (``True`` by default)
782
849
  :type major_version: bool
783
850
  :returns: The API response from the POST request
784
- :raises: :py:exc:`RuntimeError`, :py:exc:`TypeError`, :py:exc:`ValueError`
851
+ :raises: :py:exc:`RuntimeError`,
852
+ :py:exc:`TypeError`,
853
+ :py:exc:`ValueError`
785
854
  """
786
855
  return knowledge_module.publish_multiple_articles(self.sfdc_object, article_id_list=article_id_list,
787
856
  major_version=major_version)
@@ -790,7 +859,7 @@ class Salesforce(object):
790
859
  """This method assigns a single data category for a knowledge article.
791
860
  (`Reference <https://itsmemohit.medium.com/quick-win-15-salesforce-knowledge-rest-apis-bb0725b2040e>`_)
792
861
 
793
- .. versionadded:: 1.2.0
862
+ .. version-added:: 1.2.0
794
863
 
795
864
  :param article_id: The ID of the article to update
796
865
  :type article_id: str
@@ -809,7 +878,7 @@ class Salesforce(object):
809
878
  """This function archives a published knowledge article.
810
879
  (`Reference <https://developer.salesforce.com/docs/atlas.en-us.knowledge_dev.meta/knowledge_dev/knowledge_REST_archive_master_version.htm>`_)
811
880
 
812
- .. versionadded:: 1.3.0
881
+ .. version-added:: 1.3.0
813
882
 
814
883
  :param article_id: The ID of the article to archive
815
884
  :type article_id: str
@@ -818,6 +887,18 @@ class Salesforce(object):
818
887
  """
819
888
  return knowledge_module.archive_article(self.sfdc_object, article_id=article_id)
820
889
 
890
+ def delete_article_draft(self, version_id):
891
+ """This function deletes an unpublished knowledge article draft.
892
+
893
+ .. version-added:: 1.4.0
894
+
895
+ :param version_id: The 15-character or 18-character ``Id`` (Knowledge Article Version ID) value
896
+ :type version_id: str
897
+ :returns: The API response from the DELETE request
898
+ :raises: :py:exc:`RuntimeError`
899
+ """
900
+ return knowledge_module.delete_article_draft(self.sfdc_object, version_id=version_id)
901
+
821
902
 
822
903
  def define_connection_info():
823
904
  """This function prompts the user for the connection information.
salespyforce/knowledge.py CHANGED
@@ -4,7 +4,7 @@
4
4
  :Synopsis: Defines the Knowledge-related functions associated with the Salesforce API
5
5
  :Created By: Jeff Shurtliff
6
6
  :Last Modified: Jeff Shurtliff
7
- :Modified Date: 14 Nov 2023
7
+ :Modified Date: 30 Jan 2026
8
8
  """
9
9
 
10
10
  from . import errors
@@ -20,7 +20,7 @@ def check_for_existing_article(sfdc_object, title, sobject=None, return_id=False
20
20
  (`Reference 1 <https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_query.htm>`_,
21
21
  `Reference 2 <https://developer.salesforce.com/docs/atlas.en-us.knowledge_dev.meta/knowledge_dev/knowledge_development_soql_sosl_intro.htm>`_)
22
22
 
23
- .. versionchanged:: 1.2.2
23
+ .. version-changed:: 1.2.2
24
24
  You can now specify whether archived articles are included in the query results.
25
25
 
26
26
  :param sfdc_object: The instantiated SalesPyForce object
@@ -230,7 +230,7 @@ def get_article_version(sfdc_object, article_id):
230
230
  def get_article_url(sfdc_object, article_id=None, article_number=None, sobject=None):
231
231
  """This function constructs the URL to view a knowledge article in Lightning or Classic.
232
232
 
233
- .. versionchanged:: 1.2.0
233
+ .. version-changed:: 1.2.0
234
234
  Changed when lightning URLs are defined and fixed an issue with extraneous slashes.
235
235
 
236
236
  :param sfdc_object: The instantiated SalesPyForce object
@@ -483,7 +483,7 @@ def assign_data_category(sfdc_object, article_id, category_group_name, category_
483
483
  """This function assigns a single data category for a knowledge article.
484
484
  (`Reference <https://itsmemohit.medium.com/quick-win-15-salesforce-knowledge-rest-apis-bb0725b2040e>`_)
485
485
 
486
- .. versionadded:: 1.2.0
486
+ .. version-added:: 1.2.0
487
487
 
488
488
  :param sfdc_object: The instantiated SalesPyForce object
489
489
  :type sfdc_object: class[salespyforce.Salesforce]
@@ -512,7 +512,7 @@ def archive_article(sfdc_object, article_id):
512
512
  """This function archives a published knowledge article.
513
513
  (`Reference <https://developer.salesforce.com/docs/atlas.en-us.knowledge_dev.meta/knowledge_dev/knowledge_REST_archive_master_version.htm>`_)
514
514
 
515
- .. versionadded:: 1.3.0
515
+ .. version-added:: 1.3.0
516
516
 
517
517
  :param sfdc_object: The instantiated SalesPyForce object
518
518
  :type sfdc_object: class[salespyforce.Salesforce]
@@ -529,3 +529,19 @@ def archive_article(sfdc_object, article_id):
529
529
  # Perform the API call
530
530
  endpoint = f'/services/data/{sfdc_object.version}/knowledgeManagement/articleVersions/masterVersions/{article_id}'
531
531
  return sfdc_object.patch(endpoint, payload)
532
+
533
+
534
+ def delete_article_draft(sfdc_object, version_id):
535
+ """This function deletes an unpublished knowledge article draft.
536
+
537
+ .. version-added:: 1.4.0
538
+
539
+ :param sfdc_object: The instantiated SalesPyForce object
540
+ :type sfdc_object: class[salespyforce.Salesforce]
541
+ :param version_id: The 15-character or 18-character ``Id`` (Knowledge Article Version ID) value
542
+ :type version_id: str
543
+ :returns: The API response from the DELETE request
544
+ :raises: :py:exc:`RuntimeError`
545
+ """
546
+ endpoint = f'/services/data/{sfdc_object.version}/sobjects/Knowledge__kav/{version_id}'
547
+ return sfdc_object.delete(endpoint)
@@ -6,7 +6,7 @@
6
6
  :Example: ``encoded_string = core_utils.encode_url(decoded_string)``
7
7
  :Created By: Jeff Shurtliff
8
8
  :Last Modified: Jeff Shurtliff
9
- :Modified Date: 29 May 2023
9
+ :Modified Date: 30 Jan 2026
10
10
  """
11
11
 
12
12
  import random
@@ -23,6 +23,9 @@ from .. import errors
23
23
  # Initialize the logger for this module
24
24
  logger = log_utils.initialize_logging(__name__)
25
25
 
26
+ # Define constants
27
+ SALESFORCE_ID_SUFFIX_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ012345'
28
+
26
29
 
27
30
  def url_encode(raw_string):
28
31
  """This function encodes a string for use in URLs.
@@ -60,7 +63,8 @@ def get_file_type(file_path):
60
63
  :param file_path: The full path to the file
61
64
  :type file_path: str
62
65
  :returns: The file type in string format (e.g. ``yaml`` or ``json``)
63
- :raises: :py:exc:`FileNotFoundError`, :py:exc:`khoros.errors.exceptions.UnknownFileTypeError`
66
+ :raises: :py:exc:`FileNotFoundError`,
67
+ :py:exc:`salespyforce.errors.exceptions.UnknownFileTypeError`
64
68
  """
65
69
  file_type = 'unknown'
66
70
  if os.path.isfile(file_path):
@@ -97,6 +101,44 @@ def get_random_string(length=32, prefix_string=""):
97
101
  return f"{prefix_string}{''.join([random.choice(string.ascii_letters + string.digits) for _ in range(length)])}"
98
102
 
99
103
 
104
+ def get_18_char_id(record_id: str) -> str:
105
+ """This function converts a 15-character Salesforce record ID to its 18-character case-insensitive form.
106
+
107
+ .. version-added:: 1.4.0
108
+
109
+ :param record_id: The Salesforce record ID to convert (or return unchanged if already 18 characters)
110
+ :type record_id: str
111
+ :returns: The 18-character Salesforce record ID
112
+ :raises: :py:exc:`ValueError`
113
+ """
114
+ # Ensure the provided record ID is a string
115
+ if not isinstance(record_id, str):
116
+ raise ValueError("Salesforce ID must be a string")
117
+
118
+ # Return the record ID unchanged if it is already 18 characters in length
119
+ if len(record_id) == 18:
120
+ return record_id
121
+
122
+ # Ensure the record ID is a valid 15-character value
123
+ if len(record_id) != 15:
124
+ raise ValueError("Salesforce ID must be 15 or 18 characters long")
125
+
126
+ # Define the checksum suffix (additional 3 characters)
127
+ suffix = ""
128
+ for i in range(0, 15, 5):
129
+ chunk = record_id[i:i + 5]
130
+ bitmask = 0
131
+
132
+ for index, char in enumerate(chunk):
133
+ if "A" <= char <= "Z":
134
+ bitmask |= 1 << index
135
+
136
+ suffix += SALESFORCE_ID_SUFFIX_ALPHABET[bitmask]
137
+
138
+ # Return the 18-character ID value
139
+ return record_id + suffix
140
+
141
+
100
142
  def get_image_ref_id(image_url):
101
143
  """This function parses an image URL to identify the reference ID (refid) value.
102
144
  (`Reference 1 <https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_sobject_rich_text_image_retrieve.htm>`_,
@@ -6,7 +6,7 @@
6
6
  :Example: ``logger = log_utils.initialize_logging(__name__)``
7
7
  :Created By: Jeff Shurtliff
8
8
  :Last Modified: Jeff Shurtliff
9
- :Modified Date: 22 Jan 2023
9
+ :Modified Date: 31 Dec 2025
10
10
  """
11
11
 
12
12
  import os
@@ -59,7 +59,7 @@ class LessThanFilter(logging.Filter):
59
59
  self.max_level = exclusive_maximum
60
60
 
61
61
  def filter(self, record):
62
- """This method returns a Boolean integer value indicating whether or not a message should be logged.
62
+ """This method returns a Boolean integer value indicating whether a message should be logged.
63
63
 
64
64
  .. note:: A non-zero return indicates that the message will be logged.
65
65
  """
@@ -69,6 +69,9 @@ class LessThanFilter(logging.Filter):
69
69
  def _apply_defaults(_logger_name, _formatter, _debug, _log_level, _file_level, _console_level, _syslog_level):
70
70
  """This function applies default values to the configuration settings if not explicitly defined.
71
71
 
72
+ .. version-changed:: 1.4.0
73
+ The default logging level is now defined.
74
+
72
75
  :param _logger_name: The name of the logger instance
73
76
  :type _logger_name: str, None
74
77
  :param _formatter: The log format to utilize for the logger instance
@@ -79,8 +82,9 @@ def _apply_defaults(_logger_name, _formatter, _debug, _log_level, _file_level, _
79
82
  :type _log_level: str, None
80
83
  :returns: The values that will be used for the configuration settings
81
84
  """
85
+ _default_log_level = LOGGING_DEFAULTS.get('log_level')
82
86
  _log_levels = {
83
- 'general': _log_level,
87
+ 'general': _log_level or _default_log_level,
84
88
  'file': _file_level,
85
89
  'console': _console_level,
86
90
  'syslog': _syslog_level,
@@ -90,12 +94,9 @@ def _apply_defaults(_logger_name, _formatter, _debug, _log_level, _file_level, _
90
94
  for _log_type in _log_levels:
91
95
  _log_levels[_log_type] = 'debug'
92
96
  else:
93
- if _log_level:
94
- for _lvl_type, _lvl_value in _log_levels.items():
95
- if _lvl_type != 'general' and _lvl_value is None:
96
- _log_levels[_lvl_type] = _log_level
97
- else:
98
- _log_level = LOGGING_DEFAULTS.get('log_level')
97
+ for _lvl_type, _lvl_value in _log_levels.items():
98
+ if _lvl_value is None:
99
+ _log_levels[_lvl_type] = _log_levels['general']
99
100
  if _formatter and isinstance(_formatter, str):
100
101
  _formatter = logging.Formatter(_formatter)
101
102
  _formatter = LOGGING_DEFAULTS.get('formatter') if not _formatter else _formatter
@@ -120,7 +121,7 @@ def _set_logging_level(_logger, _log_level):
120
121
  """This function sets the logging level for a :py:class:`logging.Logger` instance.
121
122
 
122
123
  :param _logger: The :py:class:`logging.Logger` instance
123
- :type _logger: Logger
124
+ :type _logger: class[logging.Logger]
124
125
  :param _log_level: The log level as a string (``debug``, ``info``, ``warning``, ``error`` or ``critical``)
125
126
  :type _log_level: str
126
127
  :returns: The :py:class:`logging.Logger` instance with a logging level set where applicable
@@ -161,7 +162,7 @@ def _add_file_handler(_logger, _log_level, _log_file, _overwrite, _formatter):
161
162
  """This function adds a :py:class:`logging.FileHandler` to the :py:class:`logging.Logger` instance.
162
163
 
163
164
  :param _logger: The :py:class:`logging.Logger` instance
164
- :type _logger: Logger
165
+ :type _logger: class[logging.Logger]
165
166
  :param _log_level: The log level to set for the handler
166
167
  :type _log_level: str
167
168
  :param _log_file: The log file (as a file name or a file path) to which messages should be written
@@ -173,7 +174,7 @@ def _add_file_handler(_logger, _log_level, _log_file, _overwrite, _formatter):
173
174
  :param _overwrite: Determines if messages should be appended to the file (default) or overwrite it
174
175
  :type _overwrite: bool
175
176
  :param _formatter: The :py:class:`logging.Formatter` to apply to messages passed through the handler
176
- :type _formatter: Formatter
177
+ :type _formatter: class[logging.Formatter]
177
178
  :returns: The :py:class:`logging.Logger` instance with the added :py:class:`logging.FileHandler`
178
179
  """
179
180
  # Define the log file to use
@@ -202,11 +203,11 @@ def _add_stream_handler(_logger, _log_level, _formatter):
202
203
  """This function adds a :py:class:`logging.StreamHandler` to the :py:class:`logging.Logger` instance.
203
204
 
204
205
  :param _logger: The :py:class:`logging.Logger` instance
205
- :type _logger: Logger
206
+ :type _logger: class[logging.Logger]
206
207
  :param _log_level: The log level to set for the handler
207
208
  :type _log_level: str
208
209
  :param _formatter: The :py:class:`logging.Formatter` to apply to messages passed through the handler
209
- :type _formatter: Formatter
210
+ :type _formatter: class[logging.Formatter]
210
211
  :returns: The :py:class:`logging.Logger` instance with the added :py:class:`logging.StreamHandler`
211
212
  """
212
213
  _log_level = HANDLER_DEFAULTS.get('console_log_level') if not _log_level else _log_level
@@ -228,11 +229,11 @@ def _add_split_stream_handlers(_logger, _log_level, _formatter):
228
229
  more information on how this filtering is implemented and for credit to the original author.
229
230
 
230
231
  :param _logger: The :py:class:`logging.Logger` instance
231
- :type _logger: Logger
232
+ :type _logger: class[logging.Logger]
232
233
  :param _log_level: The log level provided for the stream handler (i.e. console output)
233
234
  :type _log_level: str
234
235
  :param _formatter: The :py:class:`logging.Formatter` to apply to messages passed through the handlers
235
- :type _formatter: Formatter
236
+ :type _formatter: class[logging.Formatter]
236
237
  :returns: The logger instance with the two handlers added
237
238
  """
238
239
  # Configure and add the STDOUT handler