pydplus 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pydplus/__init__.py +17 -0
- pydplus/api.py +556 -0
- pydplus/auth.py +753 -0
- pydplus/constants.py +1460 -0
- pydplus/core.py +1749 -0
- pydplus/credentials.py +372 -0
- pydplus/decorators.py +61 -0
- pydplus/errors/__init__.py +13 -0
- pydplus/errors/exceptions.py +463 -0
- pydplus/errors/handlers.py +50 -0
- pydplus/users.py +445 -0
- pydplus/utils/__init__.py +10 -0
- pydplus/utils/core_utils.py +295 -0
- pydplus/utils/helper.py +199 -0
- pydplus/utils/log_utils.py +421 -0
- pydplus/utils/version.py +86 -0
- pydplus-1.0.0.dist-info/LICENSE +21 -0
- pydplus-1.0.0.dist-info/METADATA +296 -0
- pydplus-1.0.0.dist-info/RECORD +20 -0
- pydplus-1.0.0.dist-info/WHEEL +4 -0
pydplus/__init__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
:Module: pydplus
|
|
4
|
+
:Synopsis: This is the ``__init__`` module for the pydplus package
|
|
5
|
+
:Created By: Jeff Shurtliff
|
|
6
|
+
:Last Modified: Jeff Shurtliff
|
|
7
|
+
:Modified Date: 30 Mar 2026
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from . import core
|
|
11
|
+
from .core import PyDPlus
|
|
12
|
+
from .utils import version
|
|
13
|
+
|
|
14
|
+
__all__ = ['core', 'PyDPlus']
|
|
15
|
+
|
|
16
|
+
# Define the package version by pulling from the pydplus.utils.version module
|
|
17
|
+
__version__ = version.get_full_version()
|
pydplus/api.py
ADDED
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
:Module: pydplus.api
|
|
4
|
+
:Synopsis: Defines the basic functions associated with the RSA ID Plus API
|
|
5
|
+
:Created By: Jeff Shurtliff
|
|
6
|
+
:Last Modified: Jeff Shurtliff
|
|
7
|
+
:Modified Date: 30 Mar 2026
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
from typing import Optional, Union
|
|
14
|
+
|
|
15
|
+
import requests
|
|
16
|
+
|
|
17
|
+
from . import constants as const
|
|
18
|
+
from . import errors
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get(
|
|
24
|
+
pydp_object,
|
|
25
|
+
endpoint: str,
|
|
26
|
+
params: Optional[dict] = None,
|
|
27
|
+
headers: Optional[dict] = None,
|
|
28
|
+
api_type: str = const.DEFAULT_API_TYPE,
|
|
29
|
+
timeout: int = const.DEFAULT_API_TIMEOUT_SECONDS,
|
|
30
|
+
show_full_error: bool = True,
|
|
31
|
+
return_json: bool = True,
|
|
32
|
+
allow_failed_response: Optional[bool] = None,
|
|
33
|
+
):
|
|
34
|
+
"""Perform a GET request against the ID Plus tenant.
|
|
35
|
+
|
|
36
|
+
:param pydp_object: The instantiated pydplus object
|
|
37
|
+
:type pydp_object: class[pydplus.PyDPlus]
|
|
38
|
+
:param endpoint: The API endpoint to query
|
|
39
|
+
:type endpoint: str
|
|
40
|
+
:param params: The query parameters (where applicable)
|
|
41
|
+
:type params: dict, None
|
|
42
|
+
:param headers: Specific API headers to use when performing the API call (beyond the base headers)
|
|
43
|
+
:type headers: dict, None
|
|
44
|
+
:param api_type: Indicates if the ``admin`` (default) or ``auth`` API will be leveraged.
|
|
45
|
+
:type api_type: str
|
|
46
|
+
:param timeout: The timeout period in seconds (defaults to ``30``)
|
|
47
|
+
:type timeout: int
|
|
48
|
+
:param show_full_error: Determines if the full error message should be displayed (defaults to ``True``)
|
|
49
|
+
:type show_full_error: bool
|
|
50
|
+
:param return_json: Determines if the response should be returned in JSON format (defaults to ``True``)
|
|
51
|
+
:type return_json: bool
|
|
52
|
+
:param allow_failed_response: Indicates that failed responses should return and should not raise an exception
|
|
53
|
+
(If not explicitly defined then ``True`` if Strict Mode is disabled)
|
|
54
|
+
:type allow_failed_response: bool, None
|
|
55
|
+
:returns: The API response in JSON format or as a ``requests`` object
|
|
56
|
+
:raises: :py:exc:`errors.exceptions.APIRequestError`,
|
|
57
|
+
:py:exc:`errors.exceptions.APIResponseConversionError`,
|
|
58
|
+
:py:exc:`errors.exceptions.InvalidFieldError`
|
|
59
|
+
"""
|
|
60
|
+
# Define the parameters as an empty dictionary if none are provided
|
|
61
|
+
params = {} if params is None else params
|
|
62
|
+
|
|
63
|
+
# Define the headers
|
|
64
|
+
additional_headers = {} if headers is None else dict(headers)
|
|
65
|
+
request_headers = _get_headers(
|
|
66
|
+
pydp_object,
|
|
67
|
+
_additional_headers=additional_headers,
|
|
68
|
+
_api_type=api_type,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Perform the API call
|
|
72
|
+
full_api_url = _get_full_api_url(pydp_object, endpoint, api_type)
|
|
73
|
+
response = requests.get(full_api_url, headers=request_headers, params=params, timeout=timeout, verify=pydp_object.verify_ssl)
|
|
74
|
+
|
|
75
|
+
# Retry once after a forced OAuth token refresh when the token is rejected.
|
|
76
|
+
if _should_retry_oauth_401(pydp_object, api_type, response):
|
|
77
|
+
logger.debug('The OAuth token was rejected and will be refreshed before trying the API call again')
|
|
78
|
+
request_headers = _get_headers(
|
|
79
|
+
pydp_object,
|
|
80
|
+
_additional_headers=additional_headers,
|
|
81
|
+
_api_type=api_type,
|
|
82
|
+
_force_oauth_refresh=True,
|
|
83
|
+
)
|
|
84
|
+
response = requests.get(
|
|
85
|
+
full_api_url, headers=request_headers, params=params, timeout=timeout, verify=pydp_object.verify_ssl
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# Examine the result
|
|
89
|
+
allow_failed_response = _should_allow_failed_responses(pydp_object, allow_failed_response)
|
|
90
|
+
if response.status_code >= 300 and not allow_failed_response:
|
|
91
|
+
_raise_status_code_exception(response, const.API_REQUEST_TYPES.GET, show_full_error)
|
|
92
|
+
if return_json:
|
|
93
|
+
response = _convert_response_to_json(response, allow_failed_response)
|
|
94
|
+
return response
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def api_call_with_payload(
|
|
98
|
+
pydp_object,
|
|
99
|
+
method: str,
|
|
100
|
+
endpoint: str,
|
|
101
|
+
payload: Union[Optional[dict], Optional[str]] = None,
|
|
102
|
+
params: Optional[dict] = None,
|
|
103
|
+
headers: Optional[dict] = None,
|
|
104
|
+
api_type: str = const.DEFAULT_API_TYPE,
|
|
105
|
+
timeout: int = const.DEFAULT_API_TIMEOUT_SECONDS,
|
|
106
|
+
show_full_error: bool = True,
|
|
107
|
+
return_json: bool = True,
|
|
108
|
+
allow_failed_response: Optional[bool] = None,
|
|
109
|
+
):
|
|
110
|
+
"""Perform an API call with payload against the ID Plus tenant.
|
|
111
|
+
|
|
112
|
+
:param pydp_object: The instantiated pydplus object
|
|
113
|
+
:type pydp_object: class[pydplus.PyDPlus]
|
|
114
|
+
:param method: The API method (``post``, ``put``, or ``patch``)
|
|
115
|
+
:type method: str
|
|
116
|
+
:param endpoint: The API endpoint to query
|
|
117
|
+
:type endpoint: str
|
|
118
|
+
:param payload: The payload to leverage in the API call
|
|
119
|
+
:type payload: dict, str, None
|
|
120
|
+
:param params: The query parameters (where applicable)
|
|
121
|
+
:type params: dict, None
|
|
122
|
+
:param headers: Specific API headers to use when performing the API call (beyond the base headers)
|
|
123
|
+
:type headers: dict, None
|
|
124
|
+
:param api_type: Indicates if the ``admin`` (default) or ``auth`` API will be leveraged.
|
|
125
|
+
:type api_type: str
|
|
126
|
+
:param timeout: The timeout period in seconds (defaults to ``30``)
|
|
127
|
+
:type timeout: int
|
|
128
|
+
:param show_full_error: Determines if the full error message should be displayed (defaults to ``True``)
|
|
129
|
+
:type show_full_error: bool
|
|
130
|
+
:param return_json: Determines if the response should be returned in JSON format (defaults to ``True``)
|
|
131
|
+
:type return_json: bool
|
|
132
|
+
:param allow_failed_response: Indicates that failed responses should return and should not raise an exception
|
|
133
|
+
(If not explicitly defined then ``True`` if Strict Mode is disabled)
|
|
134
|
+
:type allow_failed_response: bool, None
|
|
135
|
+
:returns: The API response in JSON format or as a ``requests`` object
|
|
136
|
+
:raises: :py:exc:`TypeError`,
|
|
137
|
+
:py:exc:`errors.exceptions.APIMethodError`,
|
|
138
|
+
:py:exc:`errors.exceptions.APIRequestError`,
|
|
139
|
+
:py:exc:`errors.exceptions.APIResponseConversionError`,
|
|
140
|
+
:py:exc:`errors.exceptions.InvalidFieldError`
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
def _raise_exception_for_payload():
|
|
144
|
+
"""Raise a :py:exc:`TypeError` exception when the payload is an invalid data type."""
|
|
145
|
+
_error_msg = f'The API payload must be a dictionary or string (provided: {type(payload)})'
|
|
146
|
+
logger.error(_error_msg)
|
|
147
|
+
raise TypeError(_error_msg)
|
|
148
|
+
|
|
149
|
+
# Define the parameters as an empty dictionary if none are provided
|
|
150
|
+
params = {} if params is None else params
|
|
151
|
+
|
|
152
|
+
# Define the headers
|
|
153
|
+
additional_headers = {} if headers is None else dict(headers)
|
|
154
|
+
request_headers = _get_headers(
|
|
155
|
+
pydp_object,
|
|
156
|
+
_additional_headers=additional_headers,
|
|
157
|
+
_api_type=api_type,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
# Perform the API call
|
|
161
|
+
full_api_url = _get_full_api_url(pydp_object, endpoint, api_type)
|
|
162
|
+
response = _perform_api_call_with_payload(
|
|
163
|
+
pydp_object=pydp_object,
|
|
164
|
+
method=method,
|
|
165
|
+
payload=payload,
|
|
166
|
+
params=params,
|
|
167
|
+
headers=request_headers,
|
|
168
|
+
timeout=timeout,
|
|
169
|
+
full_api_url=full_api_url,
|
|
170
|
+
raise_payload_exception=_raise_exception_for_payload,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# Retry once after a forced OAuth token refresh when the token is rejected.
|
|
174
|
+
if response is not None and _should_retry_oauth_401(pydp_object, api_type, response):
|
|
175
|
+
request_headers = _get_headers(
|
|
176
|
+
pydp_object,
|
|
177
|
+
_additional_headers=additional_headers,
|
|
178
|
+
_api_type=api_type,
|
|
179
|
+
_force_oauth_refresh=True,
|
|
180
|
+
)
|
|
181
|
+
response = _perform_api_call_with_payload(
|
|
182
|
+
pydp_object=pydp_object,
|
|
183
|
+
method=method,
|
|
184
|
+
payload=payload,
|
|
185
|
+
params=params,
|
|
186
|
+
headers=request_headers,
|
|
187
|
+
timeout=timeout,
|
|
188
|
+
full_api_url=full_api_url,
|
|
189
|
+
raise_payload_exception=_raise_exception_for_payload,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
# Examine the result
|
|
193
|
+
allow_failed_response = _should_allow_failed_responses(pydp_object, allow_failed_response)
|
|
194
|
+
if response and response.status_code >= 300 and not allow_failed_response:
|
|
195
|
+
_raise_status_code_exception(response, method, show_full_error)
|
|
196
|
+
if response and return_json:
|
|
197
|
+
response = _convert_response_to_json(response, allow_failed_response)
|
|
198
|
+
return response
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def post(
|
|
202
|
+
pydp_object,
|
|
203
|
+
endpoint: str,
|
|
204
|
+
payload: Union[Optional[dict], Optional[str]] = None,
|
|
205
|
+
params: Optional[dict] = None,
|
|
206
|
+
headers: Optional[dict] = None,
|
|
207
|
+
api_type: str = const.DEFAULT_API_TYPE,
|
|
208
|
+
timeout: int = const.DEFAULT_API_TIMEOUT_SECONDS,
|
|
209
|
+
show_full_error: bool = True,
|
|
210
|
+
return_json: bool = True,
|
|
211
|
+
allow_failed_response: Optional[bool] = None,
|
|
212
|
+
):
|
|
213
|
+
"""Perform a POST call with payload against the ID Plus tenant.
|
|
214
|
+
|
|
215
|
+
:param pydp_object: The instantiated pydplus object
|
|
216
|
+
:type pydp_object: class[pydplus.PyDPlus]
|
|
217
|
+
:param endpoint: The API endpoint to query
|
|
218
|
+
:type endpoint: str
|
|
219
|
+
:param payload: The payload to leverage in the API call
|
|
220
|
+
:type payload: dict, str, None
|
|
221
|
+
:param params: The query parameters (where applicable)
|
|
222
|
+
:type params: dict, None
|
|
223
|
+
:param headers: Specific API headers to use when performing the API call (beyond the base headers)
|
|
224
|
+
:type headers: dict, None
|
|
225
|
+
:param api_type: Indicates if the ``admin`` (default) or ``auth`` API will be leveraged.
|
|
226
|
+
:type api_type: str
|
|
227
|
+
:param timeout: The timeout period in seconds (defaults to ``30``)
|
|
228
|
+
:type timeout: int
|
|
229
|
+
:param show_full_error: Determines if the full error message should be displayed (defaults to ``True``)
|
|
230
|
+
:type show_full_error: bool
|
|
231
|
+
:param return_json: Determines if the response should be returned in JSON format (defaults to ``True``)
|
|
232
|
+
:type return_json: bool
|
|
233
|
+
:param allow_failed_response: Indicates that failed responses should return and should not raise an exception
|
|
234
|
+
(If not explicitly defined then ``True`` if Strict Mode is disabled)
|
|
235
|
+
:type allow_failed_response: bool, None
|
|
236
|
+
:returns: The API response in JSON format or as a ``requests`` object
|
|
237
|
+
:raises: :py:exc:`errors.exceptions.APIMethodError`,
|
|
238
|
+
:py:exc:`errors.exceptions.APIRequestError`,
|
|
239
|
+
:py:exc:`errors.exceptions.APIResponseConversionError`,
|
|
240
|
+
:py:exc:`errors.exceptions.InvalidFieldError`
|
|
241
|
+
"""
|
|
242
|
+
return api_call_with_payload(
|
|
243
|
+
pydp_object=pydp_object,
|
|
244
|
+
method=const.API_REQUEST_TYPES.POST,
|
|
245
|
+
endpoint=endpoint,
|
|
246
|
+
payload=payload,
|
|
247
|
+
params=params,
|
|
248
|
+
headers=headers,
|
|
249
|
+
api_type=api_type,
|
|
250
|
+
timeout=timeout,
|
|
251
|
+
show_full_error=show_full_error,
|
|
252
|
+
return_json=return_json,
|
|
253
|
+
allow_failed_response=allow_failed_response,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def patch(
|
|
258
|
+
pydp_object,
|
|
259
|
+
endpoint: str,
|
|
260
|
+
payload: Union[Optional[dict], Optional[str]] = None,
|
|
261
|
+
params: Optional[dict] = None,
|
|
262
|
+
headers: Optional[dict] = None,
|
|
263
|
+
api_type: str = const.DEFAULT_API_TYPE,
|
|
264
|
+
timeout: int = const.DEFAULT_API_TIMEOUT_SECONDS,
|
|
265
|
+
show_full_error: bool = True,
|
|
266
|
+
return_json: bool = True,
|
|
267
|
+
allow_failed_response: Optional[bool] = None,
|
|
268
|
+
):
|
|
269
|
+
"""Perform a PATCH call with payload against the ID Plus tenant.
|
|
270
|
+
|
|
271
|
+
:param pydp_object: The instantiated pydplus object
|
|
272
|
+
:type pydp_object: class[pydplus.PyDPlus]
|
|
273
|
+
:param endpoint: The API endpoint to query
|
|
274
|
+
:type endpoint: str
|
|
275
|
+
:param payload: The payload to leverage in the API call
|
|
276
|
+
:type payload: dict, str, None
|
|
277
|
+
:param params: The query parameters (where applicable)
|
|
278
|
+
:type params: dict, None
|
|
279
|
+
:param headers: Specific API headers to use when performing the API call (beyond the base headers)
|
|
280
|
+
:type headers: dict, None
|
|
281
|
+
:param api_type: Indicates if the ``admin`` (default) or ``auth`` API will be leveraged.
|
|
282
|
+
:type api_type: str
|
|
283
|
+
:param timeout: The timeout period in seconds (defaults to ``30``)
|
|
284
|
+
:type timeout: int
|
|
285
|
+
:param show_full_error: Determines if the full error message should be displayed (defaults to ``True``)
|
|
286
|
+
:type show_full_error: bool
|
|
287
|
+
:param return_json: Determines if the response should be returned in JSON format (defaults to ``True``)
|
|
288
|
+
:type return_json: bool
|
|
289
|
+
:param allow_failed_response: Indicates that failed responses should return and should not raise an exception
|
|
290
|
+
(If not explicitly defined then ``True`` if Strict Mode is disabled)
|
|
291
|
+
:type allow_failed_response: bool, None
|
|
292
|
+
:returns: The API response in JSON format or as a ``requests`` object
|
|
293
|
+
:raises: :py:exc:`errors.exceptions.APIMethodError`,
|
|
294
|
+
:py:exc:`errors.exceptions.APIRequestError`,
|
|
295
|
+
:py:exc:`errors.exceptions.APIResponseConversionError`,
|
|
296
|
+
:py:exc:`errors.exceptions.InvalidFieldError`
|
|
297
|
+
"""
|
|
298
|
+
return api_call_with_payload(
|
|
299
|
+
pydp_object=pydp_object,
|
|
300
|
+
method=const.API_REQUEST_TYPES.PATCH,
|
|
301
|
+
endpoint=endpoint,
|
|
302
|
+
payload=payload,
|
|
303
|
+
params=params,
|
|
304
|
+
headers=headers,
|
|
305
|
+
api_type=api_type,
|
|
306
|
+
timeout=timeout,
|
|
307
|
+
show_full_error=show_full_error,
|
|
308
|
+
return_json=return_json,
|
|
309
|
+
allow_failed_response=allow_failed_response,
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def put(
|
|
314
|
+
pydp_object,
|
|
315
|
+
endpoint: str,
|
|
316
|
+
payload: Union[Optional[dict], Optional[str]] = None,
|
|
317
|
+
params: Optional[dict] = None,
|
|
318
|
+
headers: Optional[dict] = None,
|
|
319
|
+
api_type: str = const.DEFAULT_API_TYPE,
|
|
320
|
+
timeout: int = const.DEFAULT_API_TIMEOUT_SECONDS,
|
|
321
|
+
show_full_error: bool = True,
|
|
322
|
+
return_json: bool = True,
|
|
323
|
+
allow_failed_response: Optional[bool] = None,
|
|
324
|
+
):
|
|
325
|
+
"""Perform a PUT call with payload against the ID Plus tenant.
|
|
326
|
+
|
|
327
|
+
:param pydp_object: The instantiated pydplus object
|
|
328
|
+
:type pydp_object: class[pydplus.PyDPlus]
|
|
329
|
+
:param endpoint: The API endpoint to query
|
|
330
|
+
:type endpoint: str
|
|
331
|
+
:param payload: The payload to leverage in the API call
|
|
332
|
+
:type payload: dict, str, None
|
|
333
|
+
:param params: The query parameters (where applicable)
|
|
334
|
+
:type params: dict, None
|
|
335
|
+
:param headers: Specific API headers to use when performing the API call (beyond the base headers)
|
|
336
|
+
:type headers: dict, None
|
|
337
|
+
:param api_type: Indicates if the ``admin`` (default) or ``auth`` API will be leveraged.
|
|
338
|
+
:type api_type: str
|
|
339
|
+
:param timeout: The timeout period in seconds (defaults to ``30``)
|
|
340
|
+
:type timeout: int
|
|
341
|
+
:param show_full_error: Determines if the full error message should be displayed (defaults to ``True``)
|
|
342
|
+
:type show_full_error: bool
|
|
343
|
+
:param return_json: Determines if the response should be returned in JSON format (defaults to ``True``)
|
|
344
|
+
:type return_json: bool
|
|
345
|
+
:param allow_failed_response: Indicates that failed responses should return and should not raise an exception
|
|
346
|
+
(If not explicitly defined then ``True`` if Strict Mode is disabled)
|
|
347
|
+
:type allow_failed_response: bool, None
|
|
348
|
+
:returns: The API response in JSON format or as a ``requests`` object
|
|
349
|
+
:raises: :py:exc:`errors.exceptions.APIMethodError`,
|
|
350
|
+
:py:exc:`errors.exceptions.APIRequestError`,
|
|
351
|
+
:py:exc:`errors.exceptions.APIResponseConversionError`,
|
|
352
|
+
:py:exc:`errors.exceptions.InvalidFieldError`
|
|
353
|
+
"""
|
|
354
|
+
return api_call_with_payload(
|
|
355
|
+
pydp_object=pydp_object,
|
|
356
|
+
method=const.API_REQUEST_TYPES.PUT,
|
|
357
|
+
endpoint=endpoint,
|
|
358
|
+
payload=payload,
|
|
359
|
+
params=params,
|
|
360
|
+
headers=headers,
|
|
361
|
+
api_type=api_type,
|
|
362
|
+
timeout=timeout,
|
|
363
|
+
show_full_error=show_full_error,
|
|
364
|
+
return_json=return_json,
|
|
365
|
+
allow_failed_response=allow_failed_response,
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def _should_allow_failed_responses(_pydp_object, _allow_failed_response: Optional[bool]) -> bool:
|
|
370
|
+
"""Determine if failed responses are allowed based on the defined value or strict mode setting."""
|
|
371
|
+
# Only define the value if not already defined
|
|
372
|
+
if not isinstance(_allow_failed_response, bool) or _allow_failed_response is None:
|
|
373
|
+
try:
|
|
374
|
+
# Define the value based on the strict mode define in the instantiated object
|
|
375
|
+
_allow_failed_response = False if _pydp_object.strict_mode is True else True
|
|
376
|
+
except Exception as _exc:
|
|
377
|
+
# Use the default strict mode value to define the value if an exception is raised
|
|
378
|
+
_allow_failed_response = False if const.DEFAULT_STRICT_MODE is True else True
|
|
379
|
+
_exc_type = errors.handlers.get_exception_type(_exc)
|
|
380
|
+
_error_msg = f'Using default strict mode due to the following {_exc_type} exception: {_exc}'
|
|
381
|
+
logger.error(_error_msg)
|
|
382
|
+
return _allow_failed_response
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def _is_admin_oauth_request(_pydp_object, _api_type: str) -> bool:
|
|
386
|
+
"""Return whether the request targets Admin API over an OAuth connection."""
|
|
387
|
+
if not isinstance(_api_type, str):
|
|
388
|
+
return False
|
|
389
|
+
return (
|
|
390
|
+
_api_type.lower() == const.ADMIN_API_TYPE
|
|
391
|
+
and getattr(_pydp_object, const.CLIENT_SETTINGS.CONNECTION_TYPE, None) == const.CONNECTION_INFO.OAUTH
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def _should_retry_oauth_401(_pydp_object, _api_type: str, _response) -> bool:
|
|
396
|
+
"""Return whether a failed response is eligible for OAuth token refresh retry."""
|
|
397
|
+
return (
|
|
398
|
+
_is_admin_oauth_request(_pydp_object, _api_type)
|
|
399
|
+
and _response is not None
|
|
400
|
+
and getattr(_response, const.RESPONSE_KEYS.STATUS_CODE, None) == 401
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def _get_headers(
|
|
405
|
+
_pydp_object,
|
|
406
|
+
_additional_headers: Optional[dict] = None,
|
|
407
|
+
_api_type: str = const.DEFAULT_API_TYPE,
|
|
408
|
+
_header_type: str = const.DEFAULT_HEADER_TYPE,
|
|
409
|
+
_force_oauth_refresh: bool = const.AUTH_VALUES.OAUTH_DEFAULT_FORCE_REFRESH,
|
|
410
|
+
) -> dict:
|
|
411
|
+
"""Return the appropriate HTTP headers to use for different types of API calls."""
|
|
412
|
+
_additional_headers = {} if _additional_headers is None else _additional_headers
|
|
413
|
+
_headers = dict(_pydp_object.base_headers) if isinstance(_pydp_object.base_headers, dict) else {}
|
|
414
|
+
|
|
415
|
+
if _is_admin_oauth_request(_pydp_object, _api_type):
|
|
416
|
+
if _force_oauth_refresh:
|
|
417
|
+
_headers = _pydp_object.refresh_oauth_token()
|
|
418
|
+
else:
|
|
419
|
+
_headers = _pydp_object._ensure_oauth_headers()
|
|
420
|
+
|
|
421
|
+
# TODO: Define additional headers as needed based on header type
|
|
422
|
+
_headers.update(_additional_headers)
|
|
423
|
+
return _headers
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def _perform_api_call_with_payload(
|
|
427
|
+
pydp_object,
|
|
428
|
+
method: str,
|
|
429
|
+
payload: Union[Optional[dict], Optional[str]] = None,
|
|
430
|
+
params: Optional[dict] = None,
|
|
431
|
+
headers: Optional[dict] = None,
|
|
432
|
+
timeout: int = const.DEFAULT_API_TIMEOUT_SECONDS,
|
|
433
|
+
full_api_url: Optional[str] = None,
|
|
434
|
+
raise_payload_exception=None,
|
|
435
|
+
):
|
|
436
|
+
"""Perform API requests that include payload data and return the response object."""
|
|
437
|
+
if not full_api_url:
|
|
438
|
+
error_msg = 'A full API URL must be defined before calling _perform_api_call_with_payload()'
|
|
439
|
+
logger.error(error_msg)
|
|
440
|
+
raise errors.exceptions.APIMethodError(error_msg)
|
|
441
|
+
|
|
442
|
+
if isinstance(method, str) and method.upper() == const.API_REQUEST_TYPES.POST:
|
|
443
|
+
if isinstance(payload, dict):
|
|
444
|
+
return requests.post(
|
|
445
|
+
full_api_url, json=payload, headers=headers, params=params, timeout=timeout, verify=pydp_object.verify_ssl
|
|
446
|
+
)
|
|
447
|
+
if isinstance(payload, str):
|
|
448
|
+
return requests.post(
|
|
449
|
+
full_api_url, data=payload, headers=headers, params=params, timeout=timeout, verify=pydp_object.verify_ssl
|
|
450
|
+
)
|
|
451
|
+
if callable(raise_payload_exception):
|
|
452
|
+
raise_payload_exception()
|
|
453
|
+
elif isinstance(method, str) and method.upper() == const.API_REQUEST_TYPES.PATCH:
|
|
454
|
+
if isinstance(payload, dict):
|
|
455
|
+
return requests.patch(
|
|
456
|
+
full_api_url, json=payload, headers=headers, params=params, timeout=timeout, verify=pydp_object.verify_ssl
|
|
457
|
+
)
|
|
458
|
+
if isinstance(payload, str):
|
|
459
|
+
return requests.patch(
|
|
460
|
+
full_api_url, data=payload, headers=headers, params=params, timeout=timeout, verify=pydp_object.verify_ssl
|
|
461
|
+
)
|
|
462
|
+
if callable(raise_payload_exception):
|
|
463
|
+
raise_payload_exception()
|
|
464
|
+
elif isinstance(method, str) and method.upper() == const.API_REQUEST_TYPES.PUT:
|
|
465
|
+
if isinstance(payload, dict):
|
|
466
|
+
return requests.put(
|
|
467
|
+
full_api_url, json=payload, headers=headers, params=params, timeout=timeout, verify=pydp_object.verify_ssl
|
|
468
|
+
)
|
|
469
|
+
if isinstance(payload, str):
|
|
470
|
+
return requests.put(
|
|
471
|
+
full_api_url, data=payload, headers=headers, params=params, timeout=timeout, verify=pydp_object.verify_ssl
|
|
472
|
+
)
|
|
473
|
+
if callable(raise_payload_exception):
|
|
474
|
+
raise_payload_exception()
|
|
475
|
+
elif isinstance(method, str) and method.upper() == const.API_REQUEST_TYPES.GET:
|
|
476
|
+
error_msg = 'The GET API call method is not valid when a payload has been provided.'
|
|
477
|
+
logger.error(error_msg)
|
|
478
|
+
raise errors.exceptions.APIMethodError(error_msg)
|
|
479
|
+
else:
|
|
480
|
+
error_msg = 'A valid API call method (POST or PATCH or PUT) must be defined.'
|
|
481
|
+
logger.error(error_msg)
|
|
482
|
+
raise errors.exceptions.APIMethodError(error_msg)
|
|
483
|
+
return None
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def _get_full_api_url(_pydp_object, _endpoint: str, _api_type: str = const.DEFAULT_API_TYPE) -> str:
|
|
487
|
+
"""Construct the full API URL to use in an API call based on the API type.
|
|
488
|
+
|
|
489
|
+
:param _pydp_object: The instantiated pydplus object
|
|
490
|
+
:type _pydp_object: class[pydplus.PyDPlus]
|
|
491
|
+
:param _endpoint: The API endpoint to be called
|
|
492
|
+
:type _endpoint: str
|
|
493
|
+
:param _api_type: Indicates which API to leverage: ``admin`` (default) or ``auth``
|
|
494
|
+
:type _api_type: str
|
|
495
|
+
:returns: The full API URL path including the base URL as a string
|
|
496
|
+
:raises: :py:exc:`pydplus.errors.exceptions.InvalidFieldError`
|
|
497
|
+
"""
|
|
498
|
+
# Define the base URL to leverage based on the API type or raise an exception if API type is invalid
|
|
499
|
+
if _api_type.lower() == const.ADMIN_API_TYPE:
|
|
500
|
+
_base_url = _pydp_object.admin_base_rest_url
|
|
501
|
+
elif _api_type.lower() == const.AUTH_API_TYPE:
|
|
502
|
+
_base_url = _pydp_object.auth_base_rest_url
|
|
503
|
+
else:
|
|
504
|
+
if not isinstance(_api_type, str):
|
|
505
|
+
_error_msg = f'The API Type value must be a string. (provided: {type(_api_type)})'
|
|
506
|
+
else:
|
|
507
|
+
_error_msg = f"The value '{_api_type}' is not a valid API type. "
|
|
508
|
+
_error_msg += f"(expected: '{const.ADMIN_API_TYPE}' or '{const.AUTH_API_TYPE}')"
|
|
509
|
+
logger.error(_error_msg)
|
|
510
|
+
raise errors.exceptions.InvalidFieldError(_error_msg)
|
|
511
|
+
|
|
512
|
+
# Make sure the endpoint begins with a slash
|
|
513
|
+
_endpoint = f'/{_endpoint}' if not _endpoint.startswith('/') else _endpoint
|
|
514
|
+
|
|
515
|
+
# Return the crafted full API URL
|
|
516
|
+
return f'{_base_url}{_endpoint}'
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
def _raise_status_code_exception(_response, _method: str, _show_full_error: bool = True) -> None:
|
|
520
|
+
"""Raise an exception when a non-OK status code is returned for an API call.
|
|
521
|
+
|
|
522
|
+
:param _response: The API response
|
|
523
|
+
:param _method: The API request type (``GET``, ``POST``, ``PATCH``, ``PUT``, or ``DELETE``)
|
|
524
|
+
:type _method: str
|
|
525
|
+
:param _show_full_error: Determine if the full error message should be reported (``True`` by default)
|
|
526
|
+
:type _show_full_error: bool
|
|
527
|
+
:returns: None
|
|
528
|
+
:raises: :py:exc:`pydplus.errors.exceptions.APIRequestError`
|
|
529
|
+
"""
|
|
530
|
+
_exc_msg = f'The {_method.upper()} request failed with a {_response.status_code} status code.'
|
|
531
|
+
if _show_full_error:
|
|
532
|
+
_exc_msg += f'\n{_response.text}'
|
|
533
|
+
logger.error(_exc_msg)
|
|
534
|
+
raise errors.exceptions.APIRequestError(_exc_msg)
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
def _convert_response_to_json(_response, _allow_failed_response: bool = False):
|
|
538
|
+
"""Attempt to convert an API response to JSON format and raises an exception if unsuccessful.
|
|
539
|
+
|
|
540
|
+
:param _response: The API response
|
|
541
|
+
:param _allow_failed_response: Determines if failed responses are accepted (``False`` by default) or if an
|
|
542
|
+
exception should be raised if the conversion fails
|
|
543
|
+
:type _allow_failed_response: bool
|
|
544
|
+
:returns: The API response converted to a JSON dictionary (or returned unchanged if the conversion failed and
|
|
545
|
+
no exception was raised)
|
|
546
|
+
:raises: :py:exc:`pydplus.errors.exceptions.APIResponseConversionError`
|
|
547
|
+
"""
|
|
548
|
+
try:
|
|
549
|
+
_response = _response.json()
|
|
550
|
+
except Exception as _exc:
|
|
551
|
+
_exc_type = errors.handlers.get_exception_type(_exc)
|
|
552
|
+
_error_msg = f'Failed to convert the API response to JSON format due to the following {_exc_type} exception: {_exc}'
|
|
553
|
+
logger.error(_error_msg)
|
|
554
|
+
if not _allow_failed_response:
|
|
555
|
+
raise errors.exceptions.APIResponseConversionError(_error_msg)
|
|
556
|
+
return _response
|