salespyforce 1.4.0.dev1__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 +44 -1
- salespyforce/core.py +94 -13
- salespyforce/knowledge.py +21 -5
- salespyforce/utils/core_utils.py +44 -2
- salespyforce/utils/log_utils.py +7 -7
- salespyforce/utils/tests/test_core_utils.py +40 -1
- salespyforce/utils/tests/test_log_utils.py +2 -2
- {salespyforce-1.4.0.dev1.dist-info → salespyforce-1.4.0.dev2.dist-info}/METADATA +1 -9
- {salespyforce-1.4.0.dev1.dist-info → salespyforce-1.4.0.dev2.dist-info}/RECORD +11 -11
- {salespyforce-1.4.0.dev1.dist-info → salespyforce-1.4.0.dev2.dist-info}/LICENSE +0 -0
- {salespyforce-1.4.0.dev1.dist-info → salespyforce-1.4.0.dev2.dist-info}/WHEEL +0 -0
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:
|
|
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:
|
|
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
|
-
..
|
|
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
|
-
..
|
|
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`,
|
|
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`,
|
|
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`,
|
|
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`,
|
|
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`,
|
|
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
|
-
..
|
|
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
|
-
..
|
|
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:
|
|
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
|
-
..
|
|
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
|
-
..
|
|
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
|
-
..
|
|
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
|
-
..
|
|
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)
|
salespyforce/utils/core_utils.py
CHANGED
|
@@ -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:
|
|
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`,
|
|
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>`_,
|
salespyforce/utils/log_utils.py
CHANGED
|
@@ -121,7 +121,7 @@ def _set_logging_level(_logger, _log_level):
|
|
|
121
121
|
"""This function sets the logging level for a :py:class:`logging.Logger` instance.
|
|
122
122
|
|
|
123
123
|
:param _logger: The :py:class:`logging.Logger` instance
|
|
124
|
-
:type _logger: Logger
|
|
124
|
+
:type _logger: class[logging.Logger]
|
|
125
125
|
:param _log_level: The log level as a string (``debug``, ``info``, ``warning``, ``error`` or ``critical``)
|
|
126
126
|
:type _log_level: str
|
|
127
127
|
:returns: The :py:class:`logging.Logger` instance with a logging level set where applicable
|
|
@@ -162,7 +162,7 @@ def _add_file_handler(_logger, _log_level, _log_file, _overwrite, _formatter):
|
|
|
162
162
|
"""This function adds a :py:class:`logging.FileHandler` to the :py:class:`logging.Logger` instance.
|
|
163
163
|
|
|
164
164
|
:param _logger: The :py:class:`logging.Logger` instance
|
|
165
|
-
:type _logger: Logger
|
|
165
|
+
:type _logger: class[logging.Logger]
|
|
166
166
|
:param _log_level: The log level to set for the handler
|
|
167
167
|
:type _log_level: str
|
|
168
168
|
:param _log_file: The log file (as a file name or a file path) to which messages should be written
|
|
@@ -174,7 +174,7 @@ def _add_file_handler(_logger, _log_level, _log_file, _overwrite, _formatter):
|
|
|
174
174
|
:param _overwrite: Determines if messages should be appended to the file (default) or overwrite it
|
|
175
175
|
:type _overwrite: bool
|
|
176
176
|
:param _formatter: The :py:class:`logging.Formatter` to apply to messages passed through the handler
|
|
177
|
-
:type _formatter: Formatter
|
|
177
|
+
:type _formatter: class[logging.Formatter]
|
|
178
178
|
:returns: The :py:class:`logging.Logger` instance with the added :py:class:`logging.FileHandler`
|
|
179
179
|
"""
|
|
180
180
|
# Define the log file to use
|
|
@@ -203,11 +203,11 @@ def _add_stream_handler(_logger, _log_level, _formatter):
|
|
|
203
203
|
"""This function adds a :py:class:`logging.StreamHandler` to the :py:class:`logging.Logger` instance.
|
|
204
204
|
|
|
205
205
|
:param _logger: The :py:class:`logging.Logger` instance
|
|
206
|
-
:type _logger: Logger
|
|
206
|
+
:type _logger: class[logging.Logger]
|
|
207
207
|
:param _log_level: The log level to set for the handler
|
|
208
208
|
:type _log_level: str
|
|
209
209
|
:param _formatter: The :py:class:`logging.Formatter` to apply to messages passed through the handler
|
|
210
|
-
:type _formatter: Formatter
|
|
210
|
+
:type _formatter: class[logging.Formatter]
|
|
211
211
|
:returns: The :py:class:`logging.Logger` instance with the added :py:class:`logging.StreamHandler`
|
|
212
212
|
"""
|
|
213
213
|
_log_level = HANDLER_DEFAULTS.get('console_log_level') if not _log_level else _log_level
|
|
@@ -229,11 +229,11 @@ def _add_split_stream_handlers(_logger, _log_level, _formatter):
|
|
|
229
229
|
more information on how this filtering is implemented and for credit to the original author.
|
|
230
230
|
|
|
231
231
|
:param _logger: The :py:class:`logging.Logger` instance
|
|
232
|
-
:type _logger: Logger
|
|
232
|
+
:type _logger: class[logging.Logger]
|
|
233
233
|
:param _log_level: The log level provided for the stream handler (i.e. console output)
|
|
234
234
|
:type _log_level: str
|
|
235
235
|
:param _formatter: The :py:class:`logging.Formatter` to apply to messages passed through the handlers
|
|
236
|
-
:type _formatter: Formatter
|
|
236
|
+
:type _formatter: class[logging.Formatter]
|
|
237
237
|
:returns: The logger instance with the two handlers added
|
|
238
238
|
"""
|
|
239
239
|
# Configure and add the STDOUT handler
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
:Synopsis: This module is used by pytest to test core utility functions
|
|
6
6
|
:Created By: Jeff Shurtliff
|
|
7
7
|
:Last Modified: Jeff Shurtliff
|
|
8
|
-
:Modified Date:
|
|
8
|
+
:Modified Date: 30 Jan 2026
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
import os
|
|
@@ -115,6 +115,45 @@ def test_get_random_string_returns_expected_length(monkeypatch):
|
|
|
115
115
|
assert all(char in alphabet for char in result.replace("pre_", ""))
|
|
116
116
|
|
|
117
117
|
|
|
118
|
+
def test_converts_15_char_id_to_18_char():
|
|
119
|
+
"""This function tests the conversion of a 15-character Salesforce ID into the 18-character equivalent.
|
|
120
|
+
|
|
121
|
+
.. version-added:: 1.4.0
|
|
122
|
+
"""
|
|
123
|
+
id_15 = "ka4PO0000002hby"
|
|
124
|
+
id_18 = core_utils.get_18_char_id(id_15)
|
|
125
|
+
|
|
126
|
+
assert len(id_18) == 18
|
|
127
|
+
assert id_18.startswith(id_15)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def test_returns_18_char_id_unchanged():
|
|
131
|
+
"""This function tests that an 18-character Salesforce ID is returned unchanged during conversion attempt.
|
|
132
|
+
|
|
133
|
+
.. version-added:: 1.4.0
|
|
134
|
+
"""
|
|
135
|
+
id_18 = "ka4PO0000002hbyYAA"
|
|
136
|
+
assert core_utils.get_18_char_id(id_18) == id_18
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_invalid_id_length_raises_error():
|
|
140
|
+
"""This function tests to ensure passing an invalid Salesforce ID length raises an exception.
|
|
141
|
+
|
|
142
|
+
.. version-added:: 1.4.0
|
|
143
|
+
"""
|
|
144
|
+
with pytest.raises(ValueError):
|
|
145
|
+
core_utils.get_18_char_id("short")
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def test_non_string_id_input_raises_error():
|
|
149
|
+
"""This function tests to ensure passing a non-string to the get_18_char_id function raises an exception.
|
|
150
|
+
|
|
151
|
+
.. version-added:: 1.4.0
|
|
152
|
+
"""
|
|
153
|
+
with pytest.raises(ValueError):
|
|
154
|
+
core_utils.get_18_char_id(12345)
|
|
155
|
+
|
|
156
|
+
|
|
118
157
|
def test_get_image_ref_id_parses_query_param():
|
|
119
158
|
"""This function tests get_image_ref_id parsing.
|
|
120
159
|
|
|
@@ -22,7 +22,7 @@ def _cleanup_logger(logger: logging.Logger) -> None:
|
|
|
22
22
|
.. version-added:: 1.4.0
|
|
23
23
|
|
|
24
24
|
:param logger: The logger instance to clean up
|
|
25
|
-
:type logger: logging.Logger
|
|
25
|
+
:type logger: class[logging.Logger]
|
|
26
26
|
:returns: None
|
|
27
27
|
"""
|
|
28
28
|
for handler in list(logger.handlers):
|
|
@@ -51,7 +51,7 @@ def test_initialize_logging_applies_default_level_to_console_handler(caplog: pyt
|
|
|
51
51
|
.. version-added:: 1.4.0
|
|
52
52
|
|
|
53
53
|
:param caplog: Pytest fixture capturing log records for assertions
|
|
54
|
-
:type caplog: pytest.LogCaptureFixture
|
|
54
|
+
:type caplog: class[pytest.LogCaptureFixture]
|
|
55
55
|
:returns: None
|
|
56
56
|
"""
|
|
57
57
|
logger_name = "salespyforce.test.console.default"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: salespyforce
|
|
3
|
-
Version: 1.4.0.
|
|
3
|
+
Version: 1.4.0.dev2
|
|
4
4
|
Summary: A Python toolset for performing Salesforce API calls
|
|
5
5
|
License: MIT License
|
|
6
6
|
|
|
@@ -151,14 +151,6 @@ The package can be installed via pip using the syntax below.
|
|
|
151
151
|
pip install salespyforce --upgrade
|
|
152
152
|
```
|
|
153
153
|
|
|
154
|
-
You may also clone the repository and install from source using below.
|
|
155
|
-
|
|
156
|
-
```sh
|
|
157
|
-
git clone git://github.com/jeffshurtliff/salespyforce.git
|
|
158
|
-
cd salespyforce/
|
|
159
|
-
python setup.py install
|
|
160
|
-
```
|
|
161
|
-
|
|
162
154
|
## Change Log
|
|
163
155
|
The change log can be found in the [documentation](https://salespyforce.readthedocs.io/en/latest/changelog.html).
|
|
164
156
|
|
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
salespyforce/__init__.py,sha256=W2RY2_kojLcXtTHJrto5FtU6q1umVPaU1RZe_QkFgNY,2031
|
|
2
|
-
salespyforce/api.py,sha256=
|
|
2
|
+
salespyforce/api.py,sha256=fSMZYH1bpdZGS2sq924f1we0Bkubx8rrTx810Xyl3Ug,7732
|
|
3
3
|
salespyforce/chatter.py,sha256=GI9iXmq2mrbrH7daW8Kc3gt2O04dRthSVocJq4BVwCU,7494
|
|
4
|
-
salespyforce/core.py,sha256=
|
|
4
|
+
salespyforce/core.py,sha256=x-D8HsjsRSS7ti1XnwSxKFfqysntI8TttgKBqjsKwh4,52661
|
|
5
5
|
salespyforce/errors/__init__.py,sha256=Tnl4lB2ycpK2UmwmoBoSYuWDtovDNA4CxC5Cku2hDYs,320
|
|
6
6
|
salespyforce/errors/exceptions.py,sha256=6ATDNCPNXnjOzXcaaLZVUy39lsFt2Se5cCvqkd_8faM,17057
|
|
7
7
|
salespyforce/errors/handlers.py,sha256=8Hp-WrpYooMlC7cuuowTkOFyBH8OQFfHKmB8XxDPlyk,479
|
|
8
|
-
salespyforce/knowledge.py,sha256=
|
|
8
|
+
salespyforce/knowledge.py,sha256=5a1kllrggh83vOmFrR-NLaz2YDpoKQweQ1B4e2AwghA,25407
|
|
9
9
|
salespyforce/utils/__init__.py,sha256=ATE6BsWd2qDxsy1fQpvaoSJimZgfCmNPYpcFEucES6Q,292
|
|
10
|
-
salespyforce/utils/core_utils.py,sha256=
|
|
10
|
+
salespyforce/utils/core_utils.py,sha256=_uVVxHcVwCb-h6m-B7IKY77ItaSsHtGwydZN923yIic,7038
|
|
11
11
|
salespyforce/utils/helper.py,sha256=zwbysqwddWwhp3mfzKJf0aeTTkVqoR35L9dLu6ZVJtU,5661
|
|
12
|
-
salespyforce/utils/log_utils.py,sha256
|
|
12
|
+
salespyforce/utils/log_utils.py,sha256=-7qgSNCUQRstRgz2wXBX6LRWO-GMjIXS5WjxkHGvBHk,11936
|
|
13
13
|
salespyforce/utils/tests/__init__.py,sha256=Iv7M9gPyKv5f5tR-pxc089cd2nrWn5M0ZZpWqiB2htc,242
|
|
14
14
|
salespyforce/utils/tests/conftest.py,sha256=zYB5pYNtRHc6cIxY_cBMQHNM52Is6NjLfcCywCD3-Bw,6661
|
|
15
15
|
salespyforce/utils/tests/resources.py,sha256=-PEqzLMsEQSVj3dz_8VTHXINbSeMvmjX3gZRMTJgoac,4774
|
|
16
|
-
salespyforce/utils/tests/test_core_utils.py,sha256=
|
|
16
|
+
salespyforce/utils/tests/test_core_utils.py,sha256=8b3qjzr028wVeQAxBvIeajcqnWCsqUhNgqcquznGQKg,7272
|
|
17
17
|
salespyforce/utils/tests/test_instantiate_object.py,sha256=O_DTljj_QCBZlDiA1KBUmSL2CZfsVU_7K2h6ypEN0D8,2002
|
|
18
|
-
salespyforce/utils/tests/test_log_utils.py,sha256=
|
|
18
|
+
salespyforce/utils/tests/test_log_utils.py,sha256=FxPC21vXRQbFvqmTi_FL1DsGwuH8JHPkl9XytVoe7uY,2328
|
|
19
19
|
salespyforce/utils/tests/test_sobjects.py,sha256=AeKli_LjSkeaspGxHZoZsM1IafKS2HkJ1wzajSXt6Dc,2120
|
|
20
20
|
salespyforce/utils/tests/test_soql.py,sha256=SSOAN-4QZNpwUON-cLndb5W6AAs28Ja1u-Zt1WBx0VA,776
|
|
21
21
|
salespyforce/utils/tests/test_sosl.py,sha256=3Ny1uF0onjcvI_InfMYRdh31rR5MJADTB3DWSm--EjE,868
|
|
22
22
|
salespyforce/utils/tests/test_version_utils.py,sha256=njRKMiwtCCyUHEAFvfxvavuh03fJ_EupM1TUYmdsZAo,2465
|
|
23
23
|
salespyforce/utils/version.py,sha256=uEWnq3DHmN9wZVohiRCRE1KzNF1XVhP4oVY4vBBgoVU,1781
|
|
24
|
-
salespyforce-1.4.0.
|
|
25
|
-
salespyforce-1.4.0.
|
|
26
|
-
salespyforce-1.4.0.
|
|
27
|
-
salespyforce-1.4.0.
|
|
24
|
+
salespyforce-1.4.0.dev2.dist-info/LICENSE,sha256=b_Ch0pvcZN6Mr4dQ9Bo3It-J3MihQYcj_-EIdaZHuWw,1071
|
|
25
|
+
salespyforce-1.4.0.dev2.dist-info/METADATA,sha256=7Loir-UVgxd2NpPpLKFQo-0nnt540mMC2ZSh-0uIyxA,10400
|
|
26
|
+
salespyforce-1.4.0.dev2.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
27
|
+
salespyforce-1.4.0.dev2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|