salespyforce 1.4.0.dev2__tar.gz → 1.4.0rc0__tar.gz
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-1.4.0.dev2 → salespyforce-1.4.0rc0}/PKG-INFO +2 -2
- {salespyforce-1.4.0.dev2 → salespyforce-1.4.0rc0}/README.md +1 -1
- {salespyforce-1.4.0.dev2 → salespyforce-1.4.0rc0}/pyproject.toml +1 -1
- {salespyforce-1.4.0.dev2 → salespyforce-1.4.0rc0}/src/salespyforce/api.py +59 -18
- {salespyforce-1.4.0.dev2 → salespyforce-1.4.0rc0}/src/salespyforce/core.py +286 -5
- salespyforce-1.4.0rc0/src/salespyforce/decorators.py +53 -0
- salespyforce-1.4.0rc0/src/salespyforce/errors/handlers.py +53 -0
- {salespyforce-1.4.0.dev2 → salespyforce-1.4.0rc0}/src/salespyforce/knowledge.py +10 -3
- {salespyforce-1.4.0.dev2 → salespyforce-1.4.0rc0}/src/salespyforce/utils/core_utils.py +43 -1
- salespyforce-1.4.0.dev2/src/salespyforce/errors/handlers.py +0 -15
- {salespyforce-1.4.0.dev2 → salespyforce-1.4.0rc0}/LICENSE +0 -0
- {salespyforce-1.4.0.dev2 → salespyforce-1.4.0rc0}/src/salespyforce/__init__.py +0 -0
- {salespyforce-1.4.0.dev2 → salespyforce-1.4.0rc0}/src/salespyforce/chatter.py +0 -0
- {salespyforce-1.4.0.dev2 → salespyforce-1.4.0rc0}/src/salespyforce/errors/__init__.py +0 -0
- {salespyforce-1.4.0.dev2 → salespyforce-1.4.0rc0}/src/salespyforce/errors/exceptions.py +0 -0
- {salespyforce-1.4.0.dev2 → salespyforce-1.4.0rc0}/src/salespyforce/utils/__init__.py +0 -0
- {salespyforce-1.4.0.dev2 → salespyforce-1.4.0rc0}/src/salespyforce/utils/helper.py +0 -0
- {salespyforce-1.4.0.dev2 → salespyforce-1.4.0rc0}/src/salespyforce/utils/log_utils.py +0 -0
- {salespyforce-1.4.0.dev2 → salespyforce-1.4.0rc0}/src/salespyforce/utils/tests/__init__.py +0 -0
- {salespyforce-1.4.0.dev2 → salespyforce-1.4.0rc0}/src/salespyforce/utils/tests/conftest.py +0 -0
- {salespyforce-1.4.0.dev2 → salespyforce-1.4.0rc0}/src/salespyforce/utils/tests/resources.py +0 -0
- {salespyforce-1.4.0.dev2 → salespyforce-1.4.0rc0}/src/salespyforce/utils/tests/test_core_utils.py +0 -0
- {salespyforce-1.4.0.dev2 → salespyforce-1.4.0rc0}/src/salespyforce/utils/tests/test_instantiate_object.py +0 -0
- {salespyforce-1.4.0.dev2 → salespyforce-1.4.0rc0}/src/salespyforce/utils/tests/test_log_utils.py +0 -0
- {salespyforce-1.4.0.dev2 → salespyforce-1.4.0rc0}/src/salespyforce/utils/tests/test_sobjects.py +0 -0
- {salespyforce-1.4.0.dev2 → salespyforce-1.4.0rc0}/src/salespyforce/utils/tests/test_soql.py +0 -0
- {salespyforce-1.4.0.dev2 → salespyforce-1.4.0rc0}/src/salespyforce/utils/tests/test_sosl.py +0 -0
- {salespyforce-1.4.0.dev2 → salespyforce-1.4.0rc0}/src/salespyforce/utils/tests/test_version_utils.py +0 -0
- {salespyforce-1.4.0.dev2 → salespyforce-1.4.0rc0}/src/salespyforce/utils/version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: salespyforce
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.0rc0
|
|
4
4
|
Summary: A Python toolset for performing Salesforce API calls
|
|
5
5
|
License: MIT License
|
|
6
6
|
|
|
@@ -63,7 +63,7 @@ A Python toolset for performing Salesforce API calls
|
|
|
63
63
|
<td>Latest Beta/RC Release</td>
|
|
64
64
|
<td>
|
|
65
65
|
<a href='https://pypi.org/project/salespyforce/#history'>
|
|
66
|
-
<img alt="PyPI" src="https://img.shields.io/badge/pypi-1.4.0.
|
|
66
|
+
<img alt="PyPI" src="https://img.shields.io/badge/pypi-1.4.0.dev2-blue">
|
|
67
67
|
</a>
|
|
68
68
|
</td>
|
|
69
69
|
</tr>
|
|
@@ -14,7 +14,7 @@ A Python toolset for performing Salesforce API calls
|
|
|
14
14
|
<td>Latest Beta/RC Release</td>
|
|
15
15
|
<td>
|
|
16
16
|
<a href='https://pypi.org/project/salespyforce/#history'>
|
|
17
|
-
<img alt="PyPI" src="https://img.shields.io/badge/pypi-1.4.0.
|
|
17
|
+
<img alt="PyPI" src="https://img.shields.io/badge/pypi-1.4.0.dev2-blue">
|
|
18
18
|
</a>
|
|
19
19
|
</td>
|
|
20
20
|
</tr>
|
|
@@ -4,12 +4,13 @@
|
|
|
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: 30 Jan 2026
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
import requests
|
|
11
11
|
|
|
12
|
-
from .
|
|
12
|
+
from . import errors
|
|
13
|
+
from .utils import core_utils, log_utils
|
|
13
14
|
|
|
14
15
|
# Initialize logging
|
|
15
16
|
logger = log_utils.initialize_logging(__name__)
|
|
@@ -19,6 +20,10 @@ def get(sfdc_object, endpoint, params=None, headers=None, timeout=30, show_full_
|
|
|
19
20
|
"""This method performs a GET request against the Salesforce instance.
|
|
20
21
|
(`Reference <https://jereze.com/code/authentification-salesforce-rest-api-python/>`_)
|
|
21
22
|
|
|
23
|
+
.. version-changed:: 1.4.0
|
|
24
|
+
The full URL for the API call is now constructed prior to making the call.
|
|
25
|
+
The provided URL is also now evaluated to ensure it is a valid Salesforce URL.
|
|
26
|
+
|
|
22
27
|
:param sfdc_object: The instantiated SalesPyForce object
|
|
23
28
|
:param endpoint: The API endpoint to query
|
|
24
29
|
:type endpoint: str
|
|
@@ -40,12 +45,13 @@ def get(sfdc_object, endpoint, params=None, headers=None, timeout=30, show_full_
|
|
|
40
45
|
default_headers = _get_headers(sfdc_object.access_token)
|
|
41
46
|
headers = default_headers if not headers else headers
|
|
42
47
|
|
|
43
|
-
#
|
|
44
|
-
|
|
48
|
+
# Construct the request URL
|
|
49
|
+
url = _construct_full_query_url(endpoint, sfdc_object.instance_url)
|
|
45
50
|
|
|
46
51
|
# Perform the API call
|
|
47
|
-
response = requests.get(
|
|
52
|
+
response = requests.get(url, headers=headers, params=params, timeout=timeout)
|
|
48
53
|
if response.status_code >= 300:
|
|
54
|
+
# TODO: Functionalize this segment and figure out how to improve on the approach somehow
|
|
49
55
|
if show_full_error:
|
|
50
56
|
raise RuntimeError(f'The GET request failed with a {response.status_code} status code.\n'
|
|
51
57
|
f'{response.text}')
|
|
@@ -61,6 +67,10 @@ def api_call_with_payload(sfdc_object, method, endpoint, payload, params=None, h
|
|
|
61
67
|
"""This method performs a POST call against the Salesforce instance.
|
|
62
68
|
(`Reference <https://jereze.com/code/authentification-salesforce-rest-api-python/>`_)
|
|
63
69
|
|
|
70
|
+
.. version-changed:: 1.4.0
|
|
71
|
+
The full URL for the API call is now constructed prior to making the call.
|
|
72
|
+
The provided URL is also now evaluated to ensure it is a valid Salesforce URL.
|
|
73
|
+
|
|
64
74
|
:param sfdc_object: The instantiated SalesPyForce object
|
|
65
75
|
:param method: The API method (``post``, ``put``, or ``patch``)
|
|
66
76
|
:type method: str
|
|
@@ -86,25 +96,23 @@ def api_call_with_payload(sfdc_object, method, endpoint, payload, params=None, h
|
|
|
86
96
|
default_headers = _get_headers(sfdc_object.access_token)
|
|
87
97
|
headers = default_headers if not headers else headers
|
|
88
98
|
|
|
89
|
-
#
|
|
90
|
-
|
|
99
|
+
# Construct the request URL
|
|
100
|
+
url = _construct_full_query_url(endpoint, sfdc_object.instance_url)
|
|
91
101
|
|
|
92
102
|
# Perform the API call
|
|
93
103
|
if method.lower() == 'post':
|
|
94
|
-
response = requests.post(
|
|
95
|
-
timeout=timeout)
|
|
104
|
+
response = requests.post(url, json=payload, headers=headers, params=params, timeout=timeout)
|
|
96
105
|
elif method.lower() == 'patch':
|
|
97
|
-
response = requests.patch(
|
|
98
|
-
timeout=timeout)
|
|
106
|
+
response = requests.patch(url, json=payload, headers=headers, params=params, timeout=timeout)
|
|
99
107
|
elif method.lower() == 'put':
|
|
100
|
-
response = requests.put(
|
|
101
|
-
timeout=timeout)
|
|
108
|
+
response = requests.put(url, json=payload, headers=headers, params=params, timeout=timeout)
|
|
102
109
|
else:
|
|
103
|
-
raise ValueError('The API call method (POST or PATCH
|
|
110
|
+
raise ValueError('The API call method (POST or PATCH or PUT) must be defined')
|
|
104
111
|
|
|
105
112
|
# Examine the result
|
|
106
113
|
if response.status_code >= 300:
|
|
107
114
|
if show_full_error:
|
|
115
|
+
# TODO: Functionalize this segment and figure out how to improve on the approach somehow
|
|
108
116
|
raise RuntimeError(f'The POST request failed with a {response.status_code} status code.\n'
|
|
109
117
|
f'{response.text}')
|
|
110
118
|
else:
|
|
@@ -143,14 +151,14 @@ def delete(sfdc_object, endpoint, params=None, headers=None, timeout=30, show_fu
|
|
|
143
151
|
default_headers = _get_headers(sfdc_object.access_token)
|
|
144
152
|
headers = default_headers if not headers else headers
|
|
145
153
|
|
|
146
|
-
#
|
|
147
|
-
|
|
154
|
+
# Construct the request URL
|
|
155
|
+
url = _construct_full_query_url(endpoint, sfdc_object.instance_url)
|
|
148
156
|
|
|
149
157
|
# Perform the API call
|
|
150
|
-
response = requests.delete(
|
|
151
|
-
timeout=timeout)
|
|
158
|
+
response = requests.delete(url, headers=headers, params=params, timeout=timeout)
|
|
152
159
|
if response.status_code >= 300:
|
|
153
160
|
if show_full_error:
|
|
161
|
+
# TODO: Functionalize this segment and figure out how to improve on the approach somehow
|
|
154
162
|
raise RuntimeError(f'The DELETE request failed with a {response.status_code} status code.\n'
|
|
155
163
|
f'{response.text}')
|
|
156
164
|
else:
|
|
@@ -170,3 +178,36 @@ def _get_headers(_access_token, _header_type='default'):
|
|
|
170
178
|
if _header_type == 'articles':
|
|
171
179
|
headers['accept-language'] = 'en-US'
|
|
172
180
|
return headers
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _construct_full_query_url(_endpoint: str, _instance_url: str) -> str:
|
|
184
|
+
"""This function constructs the URL to use in an API call to the Salesforce REST API.
|
|
185
|
+
|
|
186
|
+
.. version-added:: 1.4.0
|
|
187
|
+
|
|
188
|
+
:param _endpoint: The endpoint provided when calling an API call method or function
|
|
189
|
+
:type _endpoint: str
|
|
190
|
+
:param _instance_url: The Salesforce instance URL defined when the core object was instantiated
|
|
191
|
+
:type _instance_url: str
|
|
192
|
+
:returns: The fully qualified URL
|
|
193
|
+
:raises: :py:exc:`TypeError`,
|
|
194
|
+
:py:exc:`errors.exceptions.InvalidURLError`
|
|
195
|
+
"""
|
|
196
|
+
# Raise an exception if the endpoint is not a string
|
|
197
|
+
if not isinstance(_endpoint, str):
|
|
198
|
+
exc_msg = 'The provided URL must be a string and a valid Salesforce URL'
|
|
199
|
+
logger.critical(exc_msg)
|
|
200
|
+
raise TypeError(exc_msg)
|
|
201
|
+
|
|
202
|
+
# Construct the URL as needed by prepending the instance URL
|
|
203
|
+
if _endpoint.startswith('https://'):
|
|
204
|
+
# Only permit valid Salesforce URLs
|
|
205
|
+
if not core_utils.is_valid_salesforce_url(_endpoint):
|
|
206
|
+
raise errors.exceptions.InvalidURLError(url=_endpoint)
|
|
207
|
+
_url = _endpoint
|
|
208
|
+
else:
|
|
209
|
+
_endpoint = f'/{_endpoint}' if not _endpoint.startswith('/') else _endpoint
|
|
210
|
+
_url = f'{_instance_url}{_endpoint}'
|
|
211
|
+
|
|
212
|
+
# Return the constructed URL
|
|
213
|
+
return _url
|
|
@@ -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: 03 Feb 2026
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
12
|
import re
|
|
@@ -21,6 +21,7 @@ from .utils.helper import get_helper_settings
|
|
|
21
21
|
|
|
22
22
|
# Define constants
|
|
23
23
|
FALLBACK_SFDC_API_VERSION = '65.0' # Used if querying the org for the version fails
|
|
24
|
+
VALID_ACCESS_CONTROL_FIELDS = {'HasReadAccess', 'HasEditAccess', 'HasDeleteAccess'}
|
|
24
25
|
|
|
25
26
|
# Initialize logging
|
|
26
27
|
logger = log_utils.initialize_logging(__name__)
|
|
@@ -95,8 +96,9 @@ class Salesforce(object):
|
|
|
95
96
|
# Get the connection information used to connect to the instance
|
|
96
97
|
self.connection_info = connection_info if connection_info is not None else self._get_empty_connection_info()
|
|
97
98
|
|
|
98
|
-
# Define the base URL
|
|
99
|
-
self.base_url = self.connection_info.get('base_url')
|
|
99
|
+
# Define the base URL and Org ID
|
|
100
|
+
self.base_url = self.connection_info.get('base_url', '')
|
|
101
|
+
self.org_id = self.connection_info.get('org_id', '')
|
|
100
102
|
|
|
101
103
|
# Define the connection response data variables
|
|
102
104
|
auth_response = self.connect()
|
|
@@ -107,6 +109,9 @@ class Salesforce(object):
|
|
|
107
109
|
# Define the version with explicitly provided version or by querying the Salesforce org
|
|
108
110
|
self.version = f'v{version}' if version else f'v{self.get_latest_api_version()}'
|
|
109
111
|
|
|
112
|
+
# Retrieve info about current user
|
|
113
|
+
self.current_user_info = self.retrieve_current_user_info(on_init=True, raise_exc_on_error=False)
|
|
114
|
+
|
|
110
115
|
# Import inner object classes so their methods can be called from the primary object
|
|
111
116
|
self.chatter = self._import_chatter_class()
|
|
112
117
|
self.knowledge = self._import_knowledge_class()
|
|
@@ -143,6 +148,34 @@ class Salesforce(object):
|
|
|
143
148
|
"""This method returns the appropriate HTTP headers to use for different types of API calls."""
|
|
144
149
|
return api._get_headers(_access_token=self.access_token, _header_type=_header_type)
|
|
145
150
|
|
|
151
|
+
def _get_cached_user_info(self, _field: str, _retrieve_if_missing: bool = False):
|
|
152
|
+
"""This method attempts to retrieve a value for a given field in the cached ``userinfo`` data and
|
|
153
|
+
optionally queries the API as needed to retrieve the data when not found.
|
|
154
|
+
|
|
155
|
+
.. version-added:: 1.4.0
|
|
156
|
+
|
|
157
|
+
:param _field: The name of the field for which the value is needed
|
|
158
|
+
:type _field: str
|
|
159
|
+
:param _retrieve_if_missing: Will query the Salesforce REST API for the data when missing if True
|
|
160
|
+
(``False`` by default)
|
|
161
|
+
:type _retrieve_if_missing: bool
|
|
162
|
+
:returns: The field value when found (or retrieved), or a None value if the field value could not be obtained
|
|
163
|
+
:raises: :py:exc:`errors.exceptions.APIRequestError`
|
|
164
|
+
"""
|
|
165
|
+
_field_value = None
|
|
166
|
+
_not_present_msg = f"The '{_field}' field is not present in the current user info data"
|
|
167
|
+
if self.current_user_info and _field in self.current_user_info:
|
|
168
|
+
_field_value = self.current_user_info[_field]
|
|
169
|
+
else:
|
|
170
|
+
logger.warning(_not_present_msg)
|
|
171
|
+
if _retrieve_if_missing:
|
|
172
|
+
self.current_user_info = self.retrieve_current_user_info(raise_exc_on_error=True)
|
|
173
|
+
if self.current_user_info and _field in self.current_user_info:
|
|
174
|
+
_field_value = self.current_user_info[_field]
|
|
175
|
+
else:
|
|
176
|
+
logger.error(f'{_not_present_msg} even after refreshing cached current user info')
|
|
177
|
+
return _field_value
|
|
178
|
+
|
|
146
179
|
def connect(self):
|
|
147
180
|
"""This method connects to the Salesforce instance to obtain the access token.
|
|
148
181
|
(`Reference <https://jereze.com/code/authentification-salesforce-rest-api-python/>`_)
|
|
@@ -162,6 +195,51 @@ class Salesforce(object):
|
|
|
162
195
|
raise RuntimeError(f'Failed to connect to the Salesforce instance.\n{response.text}')
|
|
163
196
|
return response.json()
|
|
164
197
|
|
|
198
|
+
def retrieve_current_user_info(self, all_data=False, raise_exc_on_error=False, on_init=False) -> dict:
|
|
199
|
+
"""This method retrieves the ``userinfo`` data for the current/running user.
|
|
200
|
+
|
|
201
|
+
.. version-added:: 1.4.0
|
|
202
|
+
|
|
203
|
+
:param all_data: Returns all ``userinfo`` data from the API when True instead of only the relevant fields/values
|
|
204
|
+
(``False`` by default)
|
|
205
|
+
:type all_data: bool
|
|
206
|
+
:param raise_exc_on_error: Raises an exception if the API retrieval attempt fails when True (``False`` by default)
|
|
207
|
+
:type raise_exc_on_error: bool
|
|
208
|
+
:param on_init: Indicates if the method is being called during the core object instantiation (``False`` by default)
|
|
209
|
+
:type on_init: bool
|
|
210
|
+
:returns: The user info data within a dictionary
|
|
211
|
+
:raises: :py:exc:`RuntimeError`,
|
|
212
|
+
:py:exc:`errors.exceptions.APIRequestError`
|
|
213
|
+
"""
|
|
214
|
+
user_info = {'user_id': '', 'nickname': '', 'name': '', 'email': '', 'user_type': '',
|
|
215
|
+
'language': '', 'locale': '', 'utcOffset': '', 'is_salesforce_integration_user': None}
|
|
216
|
+
bool_fields = ['is_salesforce_integration_user']
|
|
217
|
+
endpoint = '/services/oauth2/userinfo'
|
|
218
|
+
base_error_msg = 'Failed to retrieve current user info'
|
|
219
|
+
msg_init_segment = 'on core object instantiation'
|
|
220
|
+
if on_init:
|
|
221
|
+
base_error_msg = f'{base_error_msg} {msg_init_segment}'
|
|
222
|
+
try:
|
|
223
|
+
response = self.get(endpoint)
|
|
224
|
+
if isinstance(response, dict) and all_data:
|
|
225
|
+
user_info = response
|
|
226
|
+
elif isinstance(response, dict):
|
|
227
|
+
for field in user_info.keys():
|
|
228
|
+
if field in response:
|
|
229
|
+
default_val = None if field in bool_fields else ''
|
|
230
|
+
user_info[field] = response.get(field, default_val)
|
|
231
|
+
else:
|
|
232
|
+
logger.error(f'{base_error_msg} with a usable format')
|
|
233
|
+
except Exception as exc:
|
|
234
|
+
exc_type = errors.handlers.get_exception_type(exc)
|
|
235
|
+
exc_msg = f'{base_error_msg} due to {exc_type} exception: {exc}'
|
|
236
|
+
logger.error(exc_msg)
|
|
237
|
+
if raise_exc_on_error:
|
|
238
|
+
raise errors.exceptions.APIRequestError(f'{exc_type}: {exc}')
|
|
239
|
+
|
|
240
|
+
# Return the populated user info
|
|
241
|
+
return user_info
|
|
242
|
+
|
|
165
243
|
def get(self, endpoint, params=None, headers=None, timeout=30, show_full_error=True, return_json=True):
|
|
166
244
|
"""This method performs a GET request against the Salesforce instance.
|
|
167
245
|
(`Reference <https://jereze.com/code/authentification-salesforce-rest-api-python/>`_)
|
|
@@ -439,6 +517,204 @@ class Salesforce(object):
|
|
|
439
517
|
query = core_utils.url_encode(query)
|
|
440
518
|
return self.get(f'/services/data/{self.version}/search/?q={query}')
|
|
441
519
|
|
|
520
|
+
def check_user_record_access(self, record_id: str, user_id=None) -> dict:
|
|
521
|
+
"""This method checks the Read, Edit, and Delete access for a given record and user.
|
|
522
|
+
|
|
523
|
+
.. version-added:: 1.4.0
|
|
524
|
+
|
|
525
|
+
:param record_id: The ``Id`` value of the record against which to check the user access
|
|
526
|
+
:type record_id: str
|
|
527
|
+
:param user_id: The ``Id`` of the user to evaluate (or the current user's ID if not explicitly defined)
|
|
528
|
+
:type user_id: str, None
|
|
529
|
+
:returns: Dictionary with Boolean values for ``HasReadAccess``, ``HasEditAccess``, and ``HasDeleteAccess``
|
|
530
|
+
:raises: :py:exc:`RuntimeError`,
|
|
531
|
+
:py:exc:`errors.exceptions.APIRequestError`
|
|
532
|
+
"""
|
|
533
|
+
record_access = {'HasReadAccess': None, 'HasEditAccess': None, 'HasDeleteAccess': None}
|
|
534
|
+
|
|
535
|
+
# Use the current/running user's ID if an ID wasn't explicitly provided
|
|
536
|
+
if not user_id:
|
|
537
|
+
user_id = self._get_cached_user_info(_field='user_id', _retrieve_if_missing=True)
|
|
538
|
+
if user_id:
|
|
539
|
+
logger.debug(f'Using the User Id {user_id} for the running user as an Id was not specified')
|
|
540
|
+
|
|
541
|
+
# Raise an exception if the User ID is still undefined
|
|
542
|
+
if not user_id:
|
|
543
|
+
error_msg = f'The user access for record Id {record_id} cannot be checked as the User Id is undefined'
|
|
544
|
+
logger.error(error_msg)
|
|
545
|
+
raise errors.exceptions.MissingRequiredDataError(error_msg)
|
|
546
|
+
|
|
547
|
+
# Perform SOQL query for the access data
|
|
548
|
+
query = f"""
|
|
549
|
+
SELECT RecordId, HasReadAccess, HasEditAccess, HasDeleteAccess
|
|
550
|
+
FROM UserRecordAccess
|
|
551
|
+
WHERE UserId = '{user_id}' AND RecordId = '{record_id}'
|
|
552
|
+
"""
|
|
553
|
+
response = self.soql_query(query=query)
|
|
554
|
+
|
|
555
|
+
# Parse the response to extract the relevant field values
|
|
556
|
+
if 'records' in response and response['records']:
|
|
557
|
+
response = response['records'][0]
|
|
558
|
+
for field in record_access.keys():
|
|
559
|
+
record_access[field] = response.get(field, None)
|
|
560
|
+
|
|
561
|
+
# Return the record access data
|
|
562
|
+
return record_access
|
|
563
|
+
|
|
564
|
+
@staticmethod
|
|
565
|
+
def _eval_user_record_access(_field: str, _record_id: str,
|
|
566
|
+
_record_access_data: dict, _raise_exc_on_failure: bool = True) -> bool:
|
|
567
|
+
"""This private method checks for an access level given the field and the record access data.
|
|
568
|
+
|
|
569
|
+
.. version-added:: 1.4.0
|
|
570
|
+
|
|
571
|
+
:param _field: The access level field to evaluate (``HasReadAccess``, ``HasEditAccess``, ``HasDeleteAccess``)
|
|
572
|
+
:type _field: str
|
|
573
|
+
:param _record_id: The ID value for the record whose access is being checked
|
|
574
|
+
:type _record_id: str
|
|
575
|
+
:param _record_access_data: The user record access data that has already been retrieved
|
|
576
|
+
:type _record_access_data: dict
|
|
577
|
+
:param _raise_exc_on_failure: Raises an exception rather than returning a ``None`` value (``True`` by default)
|
|
578
|
+
:type _raise_exc_on_failure: bool
|
|
579
|
+
:returns: Boolean value indicating the access level for the given field
|
|
580
|
+
:raises: :py:exc:`errors.exceptions.InvalidFieldError`
|
|
581
|
+
"""
|
|
582
|
+
# Raise an exception if a valid access control field is not provided
|
|
583
|
+
if _field not in VALID_ACCESS_CONTROL_FIELDS:
|
|
584
|
+
_error_msg = f"The field '{_field}' is not a valid record access level field"
|
|
585
|
+
raise errors.exceptions.InvalidFieldError(_error_msg)
|
|
586
|
+
|
|
587
|
+
# Identify the access level value if possible (API retrievals should be handled in a parent method)
|
|
588
|
+
_has_access = _record_access_data.get(_field) if _field in _record_access_data else None
|
|
589
|
+
if _has_access is None:
|
|
590
|
+
_error_msg = f"The value for the '{_field}' is undefined for the given Record Id '{_record_id}'"
|
|
591
|
+
logger.error(_error_msg)
|
|
592
|
+
if _raise_exc_on_failure:
|
|
593
|
+
raise errors.exceptions.MissingRequiredDataError(_error_msg)
|
|
594
|
+
|
|
595
|
+
# Return the identified access level value
|
|
596
|
+
return _has_access
|
|
597
|
+
|
|
598
|
+
def can_access_record(self, access_type, record_id, user_id=None, record_access_data=None, raise_exc_on_failure=True):
|
|
599
|
+
"""This method evaluates if a user can access a specific record given the access type.
|
|
600
|
+
|
|
601
|
+
.. version-added:: 1.4.0
|
|
602
|
+
|
|
603
|
+
:param access_type: The type of access to evaluate (``read``, ``edit``, or ``delete``)
|
|
604
|
+
:type access_type: str
|
|
605
|
+
:param record_id: The ID of the record
|
|
606
|
+
:type record_id: str
|
|
607
|
+
:param user_id: The ID of the user to evaluate (defaults to the current/running user if not defined)
|
|
608
|
+
:type user_id: str, None
|
|
609
|
+
:param record_access_data: The user record access data that has already been retrieved (optional)
|
|
610
|
+
:type record_access_data: dict, None
|
|
611
|
+
:param raise_exc_on_failure: Raises an exception rather than returning a ``None`` value (``True`` by default)
|
|
612
|
+
:type raise_exc_on_failure: bool
|
|
613
|
+
:returns: Boolean value indicating the access level for the given field
|
|
614
|
+
:raises: :py:exc:`errors.exceptions.InvalidFieldError`
|
|
615
|
+
"""
|
|
616
|
+
# Define the initial value for the result
|
|
617
|
+
can_access = None
|
|
618
|
+
|
|
619
|
+
# Identify the correct field to query based on access type
|
|
620
|
+
access_type_field_mapping = {
|
|
621
|
+
'read': 'HasReadAccess',
|
|
622
|
+
'edit': 'HasEditAccess',
|
|
623
|
+
'delete': 'HasDeleteAccess',
|
|
624
|
+
}
|
|
625
|
+
if access_type.lower() not in access_type_field_mapping:
|
|
626
|
+
error_msg = f"The access_type '{access_type}' is invalid (must use 'read', 'edit', or 'delete')"
|
|
627
|
+
logger.error(error_msg)
|
|
628
|
+
if raise_exc_on_failure:
|
|
629
|
+
raise errors.exceptions.InvalidParameterError(error_msg)
|
|
630
|
+
else:
|
|
631
|
+
# Retrieve the field name to check
|
|
632
|
+
read_access_field = access_type_field_mapping.get(access_type.lower())
|
|
633
|
+
|
|
634
|
+
# Check to see if record access data was provided and validate that it is a dictionary
|
|
635
|
+
if record_access_data and not isinstance(record_access_data, dict):
|
|
636
|
+
error_msg = f"The record_access_data provided is Type {type(read_access_field)} but must be a dict"
|
|
637
|
+
logger.error(error_msg)
|
|
638
|
+
if raise_exc_on_failure:
|
|
639
|
+
raise errors.exceptions.DataMismatchError(error_msg)
|
|
640
|
+
|
|
641
|
+
# Perform the API all to check the record access for the user if data not provided
|
|
642
|
+
if not record_access_data:
|
|
643
|
+
record_access_data = self.check_user_record_access(record_id=record_id, user_id=user_id)
|
|
644
|
+
|
|
645
|
+
# Return the access level value
|
|
646
|
+
can_access = self._eval_user_record_access(_field=read_access_field, _record_id=record_id,
|
|
647
|
+
_record_access_data=record_access_data,
|
|
648
|
+
_raise_exc_on_failure=raise_exc_on_failure)
|
|
649
|
+
|
|
650
|
+
# Emit a warning if the value is None rather than a boolean
|
|
651
|
+
if can_access is None:
|
|
652
|
+
warn_msg = 'The record access check could not be completed and the function will return a None value'
|
|
653
|
+
errors.handlers.display_warning(warn_msg)
|
|
654
|
+
|
|
655
|
+
# Return the result
|
|
656
|
+
return can_access
|
|
657
|
+
|
|
658
|
+
def can_read_record(self, record_id, user_id=None, record_access_data=None, raise_exc_on_failure=True):
|
|
659
|
+
"""This method evaluates if a user has access to read a specific record.
|
|
660
|
+
|
|
661
|
+
.. version-added:: 1.4.0
|
|
662
|
+
|
|
663
|
+
:param record_id: The ID of the record
|
|
664
|
+
:type record_id: str
|
|
665
|
+
:param user_id: The ID of the user to evaluate (defaults to the current/running user if not defined)
|
|
666
|
+
:type user_id: str, None
|
|
667
|
+
:param record_access_data: The user record access data that has already been retrieved (optional)
|
|
668
|
+
:type record_access_data: dict, None
|
|
669
|
+
:param raise_exc_on_failure: Raises an exception rather than returning a ``None`` value (``True`` by default)
|
|
670
|
+
:type raise_exc_on_failure: bool
|
|
671
|
+
:returns: Boolean value indicating the access level for the given field
|
|
672
|
+
:raises: :py:exc:`errors.exceptions.InvalidFieldError`
|
|
673
|
+
"""
|
|
674
|
+
return self.can_access_record(access_type='read', record_id=record_id, user_id=user_id,
|
|
675
|
+
record_access_data=record_access_data,
|
|
676
|
+
raise_exc_on_failure=raise_exc_on_failure)
|
|
677
|
+
|
|
678
|
+
def can_edit_record(self, record_id, user_id=None, record_access_data=None, raise_exc_on_failure=True):
|
|
679
|
+
"""This method evaluates if a user has access to edit a specific record.
|
|
680
|
+
|
|
681
|
+
.. version-added:: 1.4.0
|
|
682
|
+
|
|
683
|
+
:param record_id: The ID of the record
|
|
684
|
+
:type record_id: str
|
|
685
|
+
:param user_id: The ID of the user to evaluate (defaults to the current/running user if not defined)
|
|
686
|
+
:type user_id: str, None
|
|
687
|
+
:param record_access_data: The user record access data that has already been retrieved (optional)
|
|
688
|
+
:type record_access_data: dict, None
|
|
689
|
+
:param raise_exc_on_failure: Raises an exception rather than returning a ``None`` value (``True`` by default)
|
|
690
|
+
:type raise_exc_on_failure: bool
|
|
691
|
+
:returns: Boolean value indicating the access level for the given field
|
|
692
|
+
:raises: :py:exc:`errors.exceptions.InvalidFieldError`
|
|
693
|
+
"""
|
|
694
|
+
return self.can_access_record(access_type='edit', record_id=record_id, user_id=user_id,
|
|
695
|
+
record_access_data=record_access_data,
|
|
696
|
+
raise_exc_on_failure=raise_exc_on_failure)
|
|
697
|
+
|
|
698
|
+
def can_delete_record(self, record_id, user_id=None, record_access_data=None, raise_exc_on_failure=True):
|
|
699
|
+
"""This method evaluates if a user has access to delete a specific record.
|
|
700
|
+
|
|
701
|
+
.. version-added:: 1.4.0
|
|
702
|
+
|
|
703
|
+
:param record_id: The ID of the record
|
|
704
|
+
:type record_id: str
|
|
705
|
+
:param user_id: The ID of the user to evaluate (defaults to the current/running user if not defined)
|
|
706
|
+
:type user_id: str, None
|
|
707
|
+
:param record_access_data: The user record access data that has already been retrieved (optional)
|
|
708
|
+
:type record_access_data: dict, None
|
|
709
|
+
:param raise_exc_on_failure: Raises an exception rather than returning a ``None`` value (``True`` by default)
|
|
710
|
+
:type raise_exc_on_failure: bool
|
|
711
|
+
:returns: Boolean value indicating the access level for the given field
|
|
712
|
+
:raises: :py:exc:`errors.exceptions.InvalidFieldError`
|
|
713
|
+
"""
|
|
714
|
+
return self.can_access_record(access_type='edit', record_id=record_id, user_id=user_id,
|
|
715
|
+
record_access_data=record_access_data,
|
|
716
|
+
raise_exc_on_failure=raise_exc_on_failure)
|
|
717
|
+
|
|
442
718
|
def create_sobject_record(self, sobject, payload):
|
|
443
719
|
"""This method creates a new record for a specific sObject.
|
|
444
720
|
(`Reference <https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_sobject_create.htm>`_)
|
|
@@ -887,17 +1163,22 @@ class Salesforce(object):
|
|
|
887
1163
|
"""
|
|
888
1164
|
return knowledge_module.archive_article(self.sfdc_object, article_id=article_id)
|
|
889
1165
|
|
|
890
|
-
def delete_article_draft(self, version_id):
|
|
1166
|
+
def delete_article_draft(self, version_id: str, use_knowledge_management_endpoint: bool = True):
|
|
891
1167
|
"""This function deletes an unpublished knowledge article draft.
|
|
892
1168
|
|
|
893
1169
|
.. version-added:: 1.4.0
|
|
894
1170
|
|
|
895
1171
|
:param version_id: The 15-character or 18-character ``Id`` (Knowledge Article Version ID) value
|
|
896
1172
|
:type version_id: str
|
|
1173
|
+
:param use_knowledge_management_endpoint: Leverage the ``/knowledgeManagement/articleVersions/masterVersions/``
|
|
1174
|
+
endpoint rather than the ``/sobjects/Knowledge__kav/`` endpoint
|
|
1175
|
+
(``True`` by default)
|
|
1176
|
+
:type use_knowledge_management_endpoint: bool
|
|
897
1177
|
:returns: The API response from the DELETE request
|
|
898
1178
|
:raises: :py:exc:`RuntimeError`
|
|
899
1179
|
"""
|
|
900
|
-
return knowledge_module.delete_article_draft(self.sfdc_object, version_id=version_id
|
|
1180
|
+
return knowledge_module.delete_article_draft(self.sfdc_object, version_id=version_id,
|
|
1181
|
+
use_knowledge_management_endpoint=use_knowledge_management_endpoint)
|
|
901
1182
|
|
|
902
1183
|
|
|
903
1184
|
def define_connection_info():
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
:Module: salespyforce.decorators
|
|
4
|
+
:Synopsis: Decorators that can be used to include additional functionality with functions and methods
|
|
5
|
+
:Created By: Jeff Shurtliff
|
|
6
|
+
:Last Modified: Jeff Shurtliff
|
|
7
|
+
:Modified Date: 02 Feb 2026
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import functools
|
|
13
|
+
import warnings
|
|
14
|
+
from typing import Any, Callable, Optional, Type, TypeVar
|
|
15
|
+
|
|
16
|
+
# Define the function Type bound to Callable
|
|
17
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def deprecated(
|
|
21
|
+
*,
|
|
22
|
+
since: str,
|
|
23
|
+
replacement: Optional[str] = None,
|
|
24
|
+
removal: Optional[str] = None,
|
|
25
|
+
category: Type[Warning] = DeprecationWarning,
|
|
26
|
+
stacklevel: int = 2,
|
|
27
|
+
) -> Callable[[F], F]:
|
|
28
|
+
"""This decorator marks a callable as deprecated and emits a warning at runtime.
|
|
29
|
+
|
|
30
|
+
.. version-added:: 1.4.0
|
|
31
|
+
|
|
32
|
+
:param since: Version when deprecation started
|
|
33
|
+
:param replacement: Suggested replacement usage (string)
|
|
34
|
+
:param removal: Version when it will be removed (optional)
|
|
35
|
+
:param category: Warning category (default: DeprecationWarning)
|
|
36
|
+
:param stacklevel: Warning stacklevel (default: 2)
|
|
37
|
+
"""
|
|
38
|
+
def decorator(func: F) -> F:
|
|
39
|
+
message_parts = [f"{func.__name__} is deprecated since {since}."]
|
|
40
|
+
if replacement:
|
|
41
|
+
message_parts.append(f"Use {replacement} instead.")
|
|
42
|
+
if removal:
|
|
43
|
+
message_parts.append(f"It will be removed in {removal}.")
|
|
44
|
+
message = " ".join(message_parts)
|
|
45
|
+
|
|
46
|
+
@functools.wraps(func)
|
|
47
|
+
def wrapper(*args: Any, **kwargs: Any):
|
|
48
|
+
warnings.warn(message, category=category, stacklevel=stacklevel)
|
|
49
|
+
return func(*args, **kwargs)
|
|
50
|
+
|
|
51
|
+
return wrapper # type: ignore[return-value]
|
|
52
|
+
|
|
53
|
+
return decorator
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
:Module: salespyforce.errors.handlers
|
|
4
|
+
:Synopsis: Functions that handle various error situations within the namespace
|
|
5
|
+
:Created By: Jeff Shurtliff
|
|
6
|
+
:Last Modified: Jeff Shurtliff
|
|
7
|
+
:Modified Date: 02 Feb 2026
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import sys
|
|
13
|
+
from typing import Final
|
|
14
|
+
import warnings
|
|
15
|
+
|
|
16
|
+
# Define constants
|
|
17
|
+
_DEFAULT_CATEGORY: Final[type[Warning]] = UserWarning
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def eprint(*args, **kwargs) -> None:
|
|
21
|
+
"""This function behaves the same as the ``print()`` function but is leveraged to print errors to ``sys.stderr``."""
|
|
22
|
+
print(*args, file=sys.stderr, **kwargs)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_exception_type(exc) -> str:
|
|
26
|
+
"""This function returns the exception type (e.g. ``RuntimeError``, ``TypeError``, etc.) for a given exception.
|
|
27
|
+
|
|
28
|
+
.. version-added:: 1.4.0
|
|
29
|
+
|
|
30
|
+
:returns: The exception type as a string
|
|
31
|
+
"""
|
|
32
|
+
return type(exc).__name__
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def display_warning(
|
|
36
|
+
message: str,
|
|
37
|
+
*,
|
|
38
|
+
category: type[Warning] = _DEFAULT_CATEGORY,
|
|
39
|
+
stacklevel: int = 2,
|
|
40
|
+
) -> None:
|
|
41
|
+
"""This function emits a warning that points to the caller by default.
|
|
42
|
+
|
|
43
|
+
.. version-added:: 1.4.0
|
|
44
|
+
|
|
45
|
+
:param message: Warning message to emit
|
|
46
|
+
:type message: str
|
|
47
|
+
:param category: Warning category class (default: ``UserWarning``)
|
|
48
|
+
:type category: type[Warning]
|
|
49
|
+
:param stacklevel: How far up the call stack to attribute the warning (``2`` by default - caller of this helper)
|
|
50
|
+
:type stacklevel: int
|
|
51
|
+
:returns: None
|
|
52
|
+
"""
|
|
53
|
+
warnings.warn(message, category=category, stacklevel=stacklevel)
|
|
@@ -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: 03 Feb 2026
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
from . import errors
|
|
@@ -531,7 +531,7 @@ def archive_article(sfdc_object, article_id):
|
|
|
531
531
|
return sfdc_object.patch(endpoint, payload)
|
|
532
532
|
|
|
533
533
|
|
|
534
|
-
def delete_article_draft(sfdc_object, version_id):
|
|
534
|
+
def delete_article_draft(sfdc_object, version_id: str, use_knowledge_management_endpoint: bool = True):
|
|
535
535
|
"""This function deletes an unpublished knowledge article draft.
|
|
536
536
|
|
|
537
537
|
.. version-added:: 1.4.0
|
|
@@ -540,8 +540,15 @@ def delete_article_draft(sfdc_object, version_id):
|
|
|
540
540
|
:type sfdc_object: class[salespyforce.Salesforce]
|
|
541
541
|
:param version_id: The 15-character or 18-character ``Id`` (Knowledge Article Version ID) value
|
|
542
542
|
:type version_id: str
|
|
543
|
+
:param use_knowledge_management_endpoint: Leverage the ``/knowledgeManagement/articleVersions/masterVersions/``
|
|
544
|
+
endpoint rather than the ``/sobjects/Knowledge__kav/`` endpoint
|
|
545
|
+
(``True`` by default)
|
|
546
|
+
:type use_knowledge_management_endpoint: bool
|
|
543
547
|
:returns: The API response from the DELETE request
|
|
544
548
|
:raises: :py:exc:`RuntimeError`
|
|
545
549
|
"""
|
|
546
|
-
|
|
550
|
+
if use_knowledge_management_endpoint:
|
|
551
|
+
endpoint = f'/services/data/{sfdc_object.version}/knowledgeManagement/articleVersions/masterVersions/{version_id}'
|
|
552
|
+
else:
|
|
553
|
+
endpoint = f'/services/data/{sfdc_object.version}/sobjects/Knowledge__kav/{version_id}'
|
|
547
554
|
return sfdc_object.delete(endpoint)
|
|
@@ -6,9 +6,10 @@
|
|
|
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: 02 Feb 2026
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
|
+
import re
|
|
12
13
|
import random
|
|
13
14
|
import string
|
|
14
15
|
import os.path
|
|
@@ -19,12 +20,14 @@ import requests
|
|
|
19
20
|
|
|
20
21
|
from . import log_utils
|
|
21
22
|
from .. import errors
|
|
23
|
+
from ..decorators import deprecated
|
|
22
24
|
|
|
23
25
|
# Initialize the logger for this module
|
|
24
26
|
logger = log_utils.initialize_logging(__name__)
|
|
25
27
|
|
|
26
28
|
# Define constants
|
|
27
29
|
SALESFORCE_ID_SUFFIX_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ012345'
|
|
30
|
+
VALID_SALESFORCE_URL_PATTERN = r'^https://[a-zA-Z0-9._-]+\.salesforce\.com(/|$)'
|
|
28
31
|
|
|
29
32
|
|
|
30
33
|
def url_encode(raw_string):
|
|
@@ -47,9 +50,13 @@ def url_decode(encoded_string):
|
|
|
47
50
|
return urllib.parse.unquote_plus(encoded_string)
|
|
48
51
|
|
|
49
52
|
|
|
53
|
+
@deprecated(since='1.4.0', replacement='salespyforce.errors.handlers.display_warning', removal='2.0.0')
|
|
50
54
|
def display_warning(warn_msg):
|
|
51
55
|
"""This function displays a :py:exc:`UserWarning` message via the :py:mod:`warnings` module.
|
|
52
56
|
|
|
57
|
+
.. deprecated:: 1.4.0
|
|
58
|
+
Use :py:func:`salespyforce.errors.handlers.display_warning` instead.
|
|
59
|
+
|
|
53
60
|
:param warn_msg: The message to be displayed
|
|
54
61
|
:type warn_msg: str
|
|
55
62
|
:returns: None
|
|
@@ -139,6 +146,41 @@ def get_18_char_id(record_id: str) -> str:
|
|
|
139
146
|
return record_id + suffix
|
|
140
147
|
|
|
141
148
|
|
|
149
|
+
def matches_regex_pattern(pattern: str, text: str, full_match: bool = False, must_start_with: bool = False) -> bool:
|
|
150
|
+
"""This function compares a text string against a regex pattern and determines whether they match.
|
|
151
|
+
|
|
152
|
+
.. version-added:: 1.4.0
|
|
153
|
+
|
|
154
|
+
:param pattern: The regex pattern that should match
|
|
155
|
+
:type pattern: str
|
|
156
|
+
:param text: The text string to evaluate
|
|
157
|
+
:type text: str
|
|
158
|
+
:param full_match: Determines if the entire string should be validated
|
|
159
|
+
:type full_match: bool
|
|
160
|
+
:param must_start_with: Determines if the pattern must be at the beginning of the string
|
|
161
|
+
:returns: True if the regex pattern matches anywhere in the text string
|
|
162
|
+
:raises: :py:exc:`TypeError`
|
|
163
|
+
"""
|
|
164
|
+
if full_match:
|
|
165
|
+
return bool(re.fullmatch(pattern, text))
|
|
166
|
+
elif must_start_with:
|
|
167
|
+
return bool(re.match(pattern, text))
|
|
168
|
+
else:
|
|
169
|
+
return bool(re.search(pattern, text))
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def is_valid_salesforce_url(url: str) -> bool:
|
|
173
|
+
"""This function evaluates a URL to determine if it is a valid Salesforce URL.
|
|
174
|
+
|
|
175
|
+
.. version-added:: 1.4.0
|
|
176
|
+
|
|
177
|
+
:param url: The URL to evaluate
|
|
178
|
+
:type url: str
|
|
179
|
+
:returns: Boolean value depending on whether the URL meets the criteria
|
|
180
|
+
"""
|
|
181
|
+
return True if isinstance(url, str) and matches_regex_pattern(VALID_SALESFORCE_URL_PATTERN, url) else False
|
|
182
|
+
|
|
183
|
+
|
|
142
184
|
def get_image_ref_id(image_url):
|
|
143
185
|
"""This function parses an image URL to identify the reference ID (refid) value.
|
|
144
186
|
(`Reference 1 <https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_sobject_rich_text_image_retrieve.htm>`_,
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
"""
|
|
3
|
-
:Module: salespyforce.errors.handlers
|
|
4
|
-
:Synopsis: Functions that handle various error situations within the namespace
|
|
5
|
-
:Created By: Jeff Shurtliff
|
|
6
|
-
:Last Modified: Jeff Shurtliff
|
|
7
|
-
:Modified Date: 22 Feb 2023
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
import sys
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def eprint(*args, **kwargs):
|
|
14
|
-
"""This function behaves the same as the ``print()`` function but is leveraged to print errors to ``sys.stderr``."""
|
|
15
|
-
print(*args, file=sys.stderr, **kwargs)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{salespyforce-1.4.0.dev2 → salespyforce-1.4.0rc0}/src/salespyforce/utils/tests/test_core_utils.py
RENAMED
|
File without changes
|
|
File without changes
|
{salespyforce-1.4.0.dev2 → salespyforce-1.4.0rc0}/src/salespyforce/utils/tests/test_log_utils.py
RENAMED
|
File without changes
|
{salespyforce-1.4.0.dev2 → salespyforce-1.4.0rc0}/src/salespyforce/utils/tests/test_sobjects.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{salespyforce-1.4.0.dev2 → salespyforce-1.4.0rc0}/src/salespyforce/utils/tests/test_version_utils.py
RENAMED
|
File without changes
|
|
File without changes
|