ibm-cloud-sdk-core 3.15.0__py3-none-any.whl → 3.20.6__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.
- ibm_cloud_sdk_core/__init__.py +1 -0
- ibm_cloud_sdk_core/api_exception.py +18 -4
- ibm_cloud_sdk_core/authenticators/__init__.py +1 -0
- ibm_cloud_sdk_core/authenticators/authenticator.py +3 -3
- ibm_cloud_sdk_core/authenticators/basic_authenticator.py +5 -6
- ibm_cloud_sdk_core/authenticators/bearer_token_authenticator.py +1 -1
- ibm_cloud_sdk_core/authenticators/container_authenticator.py +25 -16
- ibm_cloud_sdk_core/authenticators/cp4d_authenticator.py +34 -20
- ibm_cloud_sdk_core/authenticators/iam_authenticator.py +22 -13
- ibm_cloud_sdk_core/authenticators/iam_request_based_authenticator.py +5 -7
- ibm_cloud_sdk_core/authenticators/mcsp_authenticator.py +130 -0
- ibm_cloud_sdk_core/authenticators/vpc_instance_authenticator.py +7 -10
- ibm_cloud_sdk_core/base_service.py +113 -92
- ibm_cloud_sdk_core/detailed_response.py +21 -15
- ibm_cloud_sdk_core/get_authenticator.py +28 -16
- ibm_cloud_sdk_core/http_adapter.py +28 -0
- ibm_cloud_sdk_core/private_helpers.py +34 -0
- ibm_cloud_sdk_core/token_managers/container_token_manager.py +61 -30
- ibm_cloud_sdk_core/token_managers/cp4d_token_manager.py +34 -21
- ibm_cloud_sdk_core/token_managers/iam_request_based_token_manager.py +43 -20
- ibm_cloud_sdk_core/token_managers/iam_token_manager.py +24 -13
- ibm_cloud_sdk_core/token_managers/jwt_token_manager.py +3 -16
- ibm_cloud_sdk_core/token_managers/mcsp_token_manager.py +102 -0
- ibm_cloud_sdk_core/token_managers/token_manager.py +13 -23
- ibm_cloud_sdk_core/token_managers/vpc_instance_token_manager.py +33 -13
- ibm_cloud_sdk_core/utils.py +126 -32
- ibm_cloud_sdk_core/version.py +1 -1
- {ibm_cloud_sdk_core-3.15.0.dist-info → ibm_cloud_sdk_core-3.20.6.dist-info}/METADATA +39 -30
- ibm_cloud_sdk_core-3.20.6.dist-info/RECORD +34 -0
- {ibm_cloud_sdk_core-3.15.0.dist-info → ibm_cloud_sdk_core-3.20.6.dist-info}/WHEEL +1 -1
- ibm_cloud_sdk_core-3.20.6.dist-info/top_level.txt +1 -0
- ibm_cloud_sdk_core-3.15.0.dist-info/RECORD +0 -52
- ibm_cloud_sdk_core-3.15.0.dist-info/top_level.txt +0 -3
- ibm_cloud_sdk_core-3.15.0.dist-info/zip-safe +0 -1
- test/__init__.py +0 -0
- test/test_api_exception.py +0 -73
- test/test_authenticator.py +0 -21
- test/test_base_service.py +0 -925
- test/test_basic_authenticator.py +0 -36
- test/test_bearer_authenticator.py +0 -28
- test/test_container_authenticator.py +0 -105
- test/test_container_token_manager.py +0 -283
- test/test_cp4d_authenticator.py +0 -171
- test/test_cp4d_token_manager.py +0 -56
- test/test_detailed_response.py +0 -57
- test/test_iam_authenticator.py +0 -157
- test/test_iam_token_manager.py +0 -362
- test/test_jwt_token_manager.py +0 -109
- test/test_no_auth_authenticator.py +0 -15
- test/test_token_manager.py +0 -84
- test/test_utils.py +0 -634
- test/test_vpc_instance_authenticator.py +0 -66
- test/test_vpc_instance_token_manager.py +0 -266
- test_integration/__init__.py +0 -0
- test_integration/test_cp4d_authenticator_integration.py +0 -45
- {ibm_cloud_sdk_core-3.15.0.dist-info → ibm_cloud_sdk_core-3.20.6.dist-info}/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# coding: utf-8
|
|
2
2
|
|
|
3
|
-
# Copyright 2019 IBM All Rights Reserved.
|
|
3
|
+
# Copyright 2019, 2024 IBM All Rights Reserved.
|
|
4
4
|
#
|
|
5
5
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
6
|
# you may not use this file except in compliance with the License.
|
|
@@ -15,25 +15,33 @@
|
|
|
15
15
|
# limitations under the License.
|
|
16
16
|
|
|
17
17
|
import gzip
|
|
18
|
+
import io
|
|
18
19
|
import json as json_import
|
|
19
20
|
import logging
|
|
20
|
-
import platform
|
|
21
21
|
from http.cookiejar import CookieJar
|
|
22
22
|
from os.path import basename
|
|
23
23
|
from typing import Dict, List, Optional, Tuple, Union
|
|
24
24
|
from urllib3.util.retry import Retry
|
|
25
25
|
|
|
26
26
|
import requests
|
|
27
|
-
from requests.adapters import HTTPAdapter
|
|
28
27
|
from requests.structures import CaseInsensitiveDict
|
|
28
|
+
from requests.exceptions import JSONDecodeError
|
|
29
29
|
|
|
30
30
|
from ibm_cloud_sdk_core.authenticators import Authenticator
|
|
31
31
|
from .api_exception import ApiException
|
|
32
32
|
from .detailed_response import DetailedResponse
|
|
33
|
+
from .http_adapter import SSLHTTPAdapter
|
|
33
34
|
from .token_managers.token_manager import TokenManager
|
|
34
|
-
from .utils import (
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
from .utils import (
|
|
36
|
+
has_bad_first_or_last_char,
|
|
37
|
+
is_json_mimetype,
|
|
38
|
+
remove_null_values,
|
|
39
|
+
cleanup_values,
|
|
40
|
+
read_external_sources,
|
|
41
|
+
strip_extra_slashes,
|
|
42
|
+
GzipStream,
|
|
43
|
+
)
|
|
44
|
+
from .private_helpers import _build_user_agent
|
|
37
45
|
|
|
38
46
|
# Uncomment this to enable http debugging
|
|
39
47
|
# import http.client as http_client
|
|
@@ -43,8 +51,8 @@ from .version import __version__
|
|
|
43
51
|
logger = logging.getLogger(__name__)
|
|
44
52
|
|
|
45
53
|
|
|
46
|
-
#pylint: disable=too-many-instance-attributes
|
|
47
|
-
#pylint: disable=too-many-locals
|
|
54
|
+
# pylint: disable=too-many-instance-attributes
|
|
55
|
+
# pylint: disable=too-many-locals
|
|
48
56
|
class BaseService:
|
|
49
57
|
"""Common functionality shared by generated service classes.
|
|
50
58
|
|
|
@@ -72,18 +80,22 @@ class BaseService:
|
|
|
72
80
|
Raises:
|
|
73
81
|
ValueError: If Authenticator is not provided or invalid type.
|
|
74
82
|
"""
|
|
75
|
-
|
|
76
|
-
ERROR_MSG_DISABLE_SSL =
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
|
|
84
|
+
ERROR_MSG_DISABLE_SSL = (
|
|
85
|
+
'The connection failed because the SSL certificate is not valid. To use a self-signed '
|
|
86
|
+
'certificate, disable verification of the server\'s SSL certificate by invoking the '
|
|
87
|
+
'set_disable_ssl_verification(True) on your service instance and/ or use the '
|
|
88
|
+
'disable_ssl_verification option of the authenticator.'
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
def __init__(
|
|
92
|
+
self,
|
|
93
|
+
*,
|
|
94
|
+
service_url: str = None,
|
|
95
|
+
authenticator: Authenticator = None,
|
|
96
|
+
disable_ssl_verification: bool = False,
|
|
97
|
+
enable_gzip_compression: bool = False,
|
|
98
|
+
) -> None:
|
|
87
99
|
self.set_service_url(service_url)
|
|
88
100
|
self.http_client = requests.Session()
|
|
89
101
|
self.http_config = {}
|
|
@@ -92,58 +104,51 @@ class BaseService:
|
|
|
92
104
|
self.disable_ssl_verification = disable_ssl_verification
|
|
93
105
|
self.default_headers = None
|
|
94
106
|
self.enable_gzip_compression = enable_gzip_compression
|
|
95
|
-
self._set_user_agent_header(
|
|
107
|
+
self._set_user_agent_header(_build_user_agent())
|
|
96
108
|
self.retry_config = None
|
|
97
|
-
self.http_adapter =
|
|
109
|
+
self.http_adapter = SSLHTTPAdapter(_disable_ssl_verification=self.disable_ssl_verification)
|
|
98
110
|
if not self.authenticator:
|
|
99
111
|
raise ValueError('authenticator must be provided')
|
|
100
112
|
if not isinstance(self.authenticator, Authenticator):
|
|
101
113
|
raise ValueError('authenticator should be of type Authenticator')
|
|
102
114
|
|
|
103
|
-
|
|
115
|
+
self.http_client.mount('http://', self.http_adapter)
|
|
116
|
+
self.http_client.mount('https://', self.http_adapter)
|
|
117
|
+
|
|
118
|
+
def enable_retries(self, max_retries: int = 4, retry_interval: float = 30.0) -> None:
|
|
104
119
|
"""Enable automatic retries on the underlying http client used by the BaseService instance.
|
|
105
120
|
|
|
106
121
|
Args:
|
|
107
122
|
max_retries: the maximum number of retries to attempt for a failed retryable request
|
|
108
|
-
retry_interval: the
|
|
123
|
+
retry_interval: the maximum wait time (in seconds) to use for retry attempts.
|
|
109
124
|
In general, if a response includes the Retry-After header, that will be used for
|
|
110
125
|
the wait time associated with the retry attempt. If the Retry-After header is not
|
|
111
|
-
present, then the wait time is based on
|
|
112
|
-
|
|
126
|
+
present, then the wait time is based on an exponential backoff policy with a maximum
|
|
127
|
+
backoff time of "retry_interval".
|
|
113
128
|
"""
|
|
114
129
|
self.retry_config = Retry(
|
|
115
130
|
total=max_retries,
|
|
116
|
-
backoff_factor=
|
|
131
|
+
backoff_factor=1.0,
|
|
132
|
+
backoff_max=retry_interval,
|
|
117
133
|
# List of HTTP status codes to retry on in addition to Timeout/Connection Errors
|
|
118
134
|
status_forcelist=[429, 500, 502, 503, 504],
|
|
119
135
|
# List of HTTP methods to retry on
|
|
120
136
|
# Omitting this will default to all methods except POST
|
|
121
|
-
allowed_methods=['HEAD', 'GET', 'PUT',
|
|
122
|
-
|
|
137
|
+
allowed_methods=['HEAD', 'GET', 'PUT', 'DELETE', 'OPTIONS', 'TRACE', 'POST'],
|
|
138
|
+
)
|
|
139
|
+
self.http_adapter = SSLHTTPAdapter(
|
|
140
|
+
max_retries=self.retry_config, _disable_ssl_verification=self.disable_ssl_verification
|
|
123
141
|
)
|
|
124
|
-
self.http_adapter = HTTPAdapter(max_retries=self.retry_config)
|
|
125
142
|
self.http_client.mount('http://', self.http_adapter)
|
|
126
143
|
self.http_client.mount('https://', self.http_adapter)
|
|
127
144
|
|
|
128
145
|
def disable_retries(self):
|
|
129
146
|
"""Remove retry config from http_adapter"""
|
|
130
147
|
self.retry_config = None
|
|
131
|
-
self.http_adapter =
|
|
148
|
+
self.http_adapter = SSLHTTPAdapter(_disable_ssl_verification=self.disable_ssl_verification)
|
|
132
149
|
self.http_client.mount('http://', self.http_adapter)
|
|
133
150
|
self.http_client.mount('https://', self.http_adapter)
|
|
134
151
|
|
|
135
|
-
@staticmethod
|
|
136
|
-
def _get_system_info() -> str:
|
|
137
|
-
return '{0} {1} {2}'.format(
|
|
138
|
-
platform.system(), # OS
|
|
139
|
-
platform.release(), # OS version
|
|
140
|
-
platform.python_version() # Python version
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
def _build_user_agent(self) -> str:
|
|
144
|
-
return '{0}-{1} {2}'.format(self.SDK_NAME, __version__,
|
|
145
|
-
self._get_system_info())
|
|
146
|
-
|
|
147
152
|
def configure_service(self, service_name: str) -> None:
|
|
148
153
|
"""Look for external configuration of a service. Set service properties.
|
|
149
154
|
|
|
@@ -165,19 +170,16 @@ class BaseService:
|
|
|
165
170
|
if config.get('URL'):
|
|
166
171
|
self.set_service_url(config.get('URL'))
|
|
167
172
|
if config.get('DISABLE_SSL'):
|
|
168
|
-
self.set_disable_ssl_verification(
|
|
169
|
-
config.get('DISABLE_SSL').lower() == 'true')
|
|
173
|
+
self.set_disable_ssl_verification(config.get('DISABLE_SSL').lower() == 'true')
|
|
170
174
|
if config.get('ENABLE_GZIP'):
|
|
171
|
-
self.set_enable_gzip_compression(
|
|
172
|
-
config.get('ENABLE_GZIP').lower() == 'true')
|
|
175
|
+
self.set_enable_gzip_compression(config.get('ENABLE_GZIP').lower() == 'true')
|
|
173
176
|
if config.get('ENABLE_RETRIES'):
|
|
174
177
|
if config.get('ENABLE_RETRIES').lower() == 'true':
|
|
175
178
|
kwargs = {}
|
|
176
179
|
if config.get('MAX_RETRIES'):
|
|
177
180
|
kwargs["max_retries"] = int(config.get('MAX_RETRIES'))
|
|
178
181
|
if config.get('RETRY_INTERVAL'):
|
|
179
|
-
kwargs["retry_interval"] = float(
|
|
180
|
-
config.get('RETRY_INTERVAL'))
|
|
182
|
+
kwargs["retry_interval"] = float(config.get('RETRY_INTERVAL'))
|
|
181
183
|
self.enable_retries(**kwargs)
|
|
182
184
|
|
|
183
185
|
def _set_user_agent_header(self, user_agent_string: str) -> None:
|
|
@@ -196,10 +198,11 @@ class BaseService:
|
|
|
196
198
|
"""
|
|
197
199
|
if isinstance(http_config, dict):
|
|
198
200
|
self.http_config = http_config
|
|
199
|
-
if (
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
201
|
+
if (
|
|
202
|
+
self.authenticator
|
|
203
|
+
and hasattr(self.authenticator, 'token_manager')
|
|
204
|
+
and isinstance(self.authenticator.token_manager, TokenManager)
|
|
205
|
+
):
|
|
203
206
|
self.authenticator.token_manager.http_config = http_config
|
|
204
207
|
else:
|
|
205
208
|
raise TypeError("http_config parameter must be a dictionary")
|
|
@@ -211,8 +214,18 @@ class BaseService:
|
|
|
211
214
|
Keyword Arguments:
|
|
212
215
|
status: set to true to disable ssl verification (default: {False})
|
|
213
216
|
"""
|
|
217
|
+
if self.disable_ssl_verification == status:
|
|
218
|
+
# Do nothing if the state doesn't change.
|
|
219
|
+
return
|
|
220
|
+
|
|
214
221
|
self.disable_ssl_verification = status
|
|
215
222
|
|
|
223
|
+
self.http_adapter = SSLHTTPAdapter(
|
|
224
|
+
max_retries=self.retry_config, _disable_ssl_verification=self.disable_ssl_verification
|
|
225
|
+
)
|
|
226
|
+
self.http_client.mount('http://', self.http_adapter)
|
|
227
|
+
self.http_client.mount('https://', self.http_adapter)
|
|
228
|
+
|
|
216
229
|
def set_service_url(self, service_url: str) -> None:
|
|
217
230
|
"""Set the url the service will make HTTP requests too.
|
|
218
231
|
|
|
@@ -248,8 +261,7 @@ class BaseService:
|
|
|
248
261
|
if isinstance(http_client, requests.sessions.Session):
|
|
249
262
|
self.http_client = http_client
|
|
250
263
|
else:
|
|
251
|
-
raise TypeError(
|
|
252
|
-
"http_client parameter must be a requests.sessions.Session")
|
|
264
|
+
raise TypeError("http_client parameter must be a requests.sessions.Session")
|
|
253
265
|
|
|
254
266
|
def get_authenticator(self) -> Authenticator:
|
|
255
267
|
"""Get the authenticator currently used by the service.
|
|
@@ -295,40 +307,47 @@ class BaseService:
|
|
|
295
307
|
|
|
296
308
|
# Remove the keys we set manually, don't let the user to overwrite these.
|
|
297
309
|
reserved_keys = ['method', 'url', 'headers', 'params', 'cookies']
|
|
310
|
+
silent_keys = ['headers']
|
|
298
311
|
for key in reserved_keys:
|
|
299
312
|
if key in kwargs:
|
|
300
313
|
del kwargs[key]
|
|
301
|
-
|
|
314
|
+
if key not in silent_keys:
|
|
315
|
+
logger.warning('"%s" has been removed from the request', key)
|
|
302
316
|
try:
|
|
303
|
-
response = self.http_client.request(**request,
|
|
304
|
-
cookies=self.jar,
|
|
305
|
-
**kwargs)
|
|
317
|
+
response = self.http_client.request(**request, cookies=self.jar, **kwargs)
|
|
306
318
|
|
|
319
|
+
# Process a "success" response.
|
|
307
320
|
if 200 <= response.status_code <= 299:
|
|
308
321
|
if response.status_code == 204 or request['method'] == 'HEAD':
|
|
309
|
-
# There is no body content for a HEAD
|
|
322
|
+
# There is no body content for a HEAD response or a 204 response.
|
|
310
323
|
result = None
|
|
311
324
|
elif stream_response:
|
|
312
325
|
result = response
|
|
313
326
|
elif not response.text:
|
|
314
327
|
result = None
|
|
315
|
-
|
|
328
|
+
elif is_json_mimetype(response.headers.get('Content-Type')):
|
|
329
|
+
# If this is a JSON response, then try to unmarshal it.
|
|
316
330
|
try:
|
|
317
|
-
result = response.json()
|
|
318
|
-
except:
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
331
|
+
result = response.json(strict=False)
|
|
332
|
+
except JSONDecodeError as err:
|
|
333
|
+
raise ApiException(
|
|
334
|
+
code=response.status_code,
|
|
335
|
+
http_response=response,
|
|
336
|
+
message='Error processing the HTTP response',
|
|
337
|
+
) from err
|
|
338
|
+
else:
|
|
339
|
+
# Non-JSON response, just use response body as-is.
|
|
340
|
+
result = response
|
|
341
|
+
|
|
342
|
+
return DetailedResponse(response=result, headers=response.headers, status_code=response.status_code)
|
|
323
343
|
|
|
344
|
+
# Received error status code from server, raise an APIException.
|
|
324
345
|
raise ApiException(response.status_code, http_response=response)
|
|
325
346
|
except requests.exceptions.SSLError:
|
|
326
347
|
logger.exception(self.ERROR_MSG_DISABLE_SSL)
|
|
327
348
|
raise
|
|
328
349
|
|
|
329
|
-
def set_enable_gzip_compression(self,
|
|
330
|
-
should_enable_compression: bool = False
|
|
331
|
-
) -> None:
|
|
350
|
+
def set_enable_gzip_compression(self, should_enable_compression: bool = False) -> None:
|
|
332
351
|
"""Set value to enable gzip compression on request bodies"""
|
|
333
352
|
self.enable_gzip_compression = should_enable_compression
|
|
334
353
|
|
|
@@ -336,18 +355,17 @@ class BaseService:
|
|
|
336
355
|
"""Get value for enabling gzip compression on request bodies"""
|
|
337
356
|
return self.enable_gzip_compression
|
|
338
357
|
|
|
339
|
-
def prepare_request(
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
**kwargs) -> dict:
|
|
358
|
+
def prepare_request(
|
|
359
|
+
self,
|
|
360
|
+
method: str,
|
|
361
|
+
url: str,
|
|
362
|
+
*,
|
|
363
|
+
headers: Optional[dict] = None,
|
|
364
|
+
params: Optional[dict] = None,
|
|
365
|
+
data: Optional[Union[str, dict]] = None,
|
|
366
|
+
files: Optional[Union[Dict[str, Tuple[str]], List[Tuple[str, Tuple[str, ...]]]]] = None,
|
|
367
|
+
**kwargs,
|
|
368
|
+
) -> dict:
|
|
351
369
|
"""Build a dict that represents an HTTP service request.
|
|
352
370
|
|
|
353
371
|
Clean up headers, add default http configuration, convert data
|
|
@@ -358,8 +376,10 @@ class BaseService:
|
|
|
358
376
|
url: The origin + pathname according to WHATWG spec.
|
|
359
377
|
|
|
360
378
|
Keyword Arguments:
|
|
361
|
-
headers:
|
|
362
|
-
|
|
379
|
+
headers: A dictionary containing the headers to be included in the request.
|
|
380
|
+
Entries with a value of None will be ignored (excluded).
|
|
381
|
+
params: A dictionary containing the query parameters to be included in the request.
|
|
382
|
+
Entries with a value of None will be ignored (excluded).
|
|
363
383
|
data: The request body. Converted to json if a dict.
|
|
364
384
|
files: 'files' can be a dictionary (i.e { '<part-name>': (<tuple>)}),
|
|
365
385
|
or a list of tuples [ (<part-name>, (<tuple>))... ]
|
|
@@ -404,14 +424,16 @@ class BaseService:
|
|
|
404
424
|
self.authenticator.authenticate(request)
|
|
405
425
|
|
|
406
426
|
# Compress the request body if applicable
|
|
407
|
-
if
|
|
408
|
-
and 'content-encoding' not in headers
|
|
409
|
-
and request['data'] is not None):
|
|
427
|
+
if self.get_enable_gzip_compression() and 'content-encoding' not in headers and request['data'] is not None:
|
|
410
428
|
headers['content-encoding'] = 'gzip'
|
|
411
|
-
uncompressed_data = request['data']
|
|
412
|
-
request_body = gzip.compress(uncompressed_data)
|
|
413
|
-
request['data'] = request_body
|
|
414
429
|
request['headers'] = headers
|
|
430
|
+
# If the provided data is a file-like object, we create `GzipStream` which will handle
|
|
431
|
+
# the compression on-the-fly when the requests package starts reading its content.
|
|
432
|
+
# This helps avoid OOM errors when the opened file is too big.
|
|
433
|
+
# In any other cases, we use the in memory compression directly from
|
|
434
|
+
# the `gzip` package for backward compatibility.
|
|
435
|
+
raw_data = request['data']
|
|
436
|
+
request['data'] = GzipStream(raw_data) if isinstance(raw_data, io.IOBase) else gzip.compress(raw_data)
|
|
415
437
|
|
|
416
438
|
# Next, we need to process the 'files' argument to try to fill in
|
|
417
439
|
# any missing filenames where possible.
|
|
@@ -426,8 +448,7 @@ class BaseService:
|
|
|
426
448
|
files = files.items()
|
|
427
449
|
# Next, fill in any missing filenames from file tuples.
|
|
428
450
|
for part_name, file_tuple in files:
|
|
429
|
-
if file_tuple and len(
|
|
430
|
-
file_tuple) == 3 and file_tuple[0] is None:
|
|
451
|
+
if file_tuple and len(file_tuple) == 3 and file_tuple[0] is None:
|
|
431
452
|
file = file_tuple[1]
|
|
432
453
|
if file and hasattr(file, 'name'):
|
|
433
454
|
filename = basename(file.name)
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
# limitations under the License.
|
|
16
16
|
|
|
17
17
|
import json
|
|
18
|
-
from typing import Dict, Optional
|
|
18
|
+
from typing import Dict, Optional, Union
|
|
19
19
|
|
|
20
20
|
import requests
|
|
21
21
|
|
|
@@ -26,44 +26,50 @@ class DetailedResponse:
|
|
|
26
26
|
Keyword Args:
|
|
27
27
|
response: The response to the service request, defaults to None.
|
|
28
28
|
headers: The headers of the response, defaults to None.
|
|
29
|
-
status_code: The status code of
|
|
29
|
+
status_code: The status code of the response, defaults to None.
|
|
30
30
|
|
|
31
31
|
Attributes:
|
|
32
|
-
|
|
32
|
+
result (dict, requests.Response, None): The response to the service request.
|
|
33
33
|
headers (dict): The headers of the response.
|
|
34
34
|
status_code (int): The status code of the response.
|
|
35
35
|
|
|
36
36
|
"""
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
*,
|
|
41
|
+
response: Optional[Union[dict, requests.Response]] = None,
|
|
42
|
+
headers: Optional[Dict[str, str]] = None,
|
|
43
|
+
status_code: Optional[int] = None,
|
|
44
|
+
) -> None:
|
|
42
45
|
self.result = response
|
|
43
46
|
self.headers = headers
|
|
44
47
|
self.status_code = status_code
|
|
45
48
|
|
|
46
|
-
def get_result(self) -> requests.Response:
|
|
47
|
-
"""Get the
|
|
49
|
+
def get_result(self) -> Optional[Union[dict, requests.Response]]:
|
|
50
|
+
"""Get the response returned by the service request.
|
|
48
51
|
|
|
49
52
|
Returns:
|
|
50
|
-
The response to the service request
|
|
53
|
+
The response to the service request. This could be one of the following:
|
|
54
|
+
1. a dict that represents an instance of a response model
|
|
55
|
+
2. a requests.Response instance if the operation returns a streamed response
|
|
56
|
+
3. None if the server returned no response body
|
|
51
57
|
"""
|
|
52
58
|
return self.result
|
|
53
59
|
|
|
54
|
-
def get_headers(self) -> dict:
|
|
60
|
+
def get_headers(self) -> Optional[dict]:
|
|
55
61
|
"""The HTTP response headers of the service request.
|
|
56
62
|
|
|
57
63
|
Returns:
|
|
58
|
-
A dictionary of response headers
|
|
64
|
+
A dictionary of response headers or None if no headers are present.
|
|
59
65
|
"""
|
|
60
66
|
return self.headers
|
|
61
67
|
|
|
62
|
-
def get_status_code(self) -> int:
|
|
68
|
+
def get_status_code(self) -> Union[int, None]:
|
|
63
69
|
"""The HTTP status code of the service request.
|
|
64
70
|
|
|
65
71
|
Returns:
|
|
66
|
-
The status code
|
|
72
|
+
The status code associated with the service request.
|
|
67
73
|
"""
|
|
68
74
|
return self.status_code
|
|
69
75
|
|
|
@@ -14,9 +14,17 @@
|
|
|
14
14
|
# See the License for the specific language governing permissions and
|
|
15
15
|
# limitations under the License.
|
|
16
16
|
|
|
17
|
-
from .authenticators import (
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
from .authenticators import (
|
|
18
|
+
Authenticator,
|
|
19
|
+
BasicAuthenticator,
|
|
20
|
+
BearerTokenAuthenticator,
|
|
21
|
+
ContainerAuthenticator,
|
|
22
|
+
CloudPakForDataAuthenticator,
|
|
23
|
+
IAMAuthenticator,
|
|
24
|
+
NoAuthAuthenticator,
|
|
25
|
+
VPCInstanceAuthenticator,
|
|
26
|
+
MCSPAuthenticator,
|
|
27
|
+
)
|
|
20
28
|
from .utils import read_external_sources
|
|
21
29
|
|
|
22
30
|
|
|
@@ -59,12 +67,9 @@ def __construct_authenticator(config: dict) -> Authenticator:
|
|
|
59
67
|
authenticator = None
|
|
60
68
|
|
|
61
69
|
if auth_type == Authenticator.AUTHTYPE_BASIC.lower():
|
|
62
|
-
authenticator = BasicAuthenticator(
|
|
63
|
-
username=config.get('USERNAME'),
|
|
64
|
-
password=config.get('PASSWORD'))
|
|
70
|
+
authenticator = BasicAuthenticator(username=config.get('USERNAME'), password=config.get('PASSWORD'))
|
|
65
71
|
elif auth_type == Authenticator.AUTHTYPE_BEARERTOKEN.lower():
|
|
66
|
-
authenticator = BearerTokenAuthenticator(
|
|
67
|
-
bearer_token=config.get('BEARER_TOKEN'))
|
|
72
|
+
authenticator = BearerTokenAuthenticator(bearer_token=config.get('BEARER_TOKEN'))
|
|
68
73
|
elif auth_type == Authenticator.AUTHTYPE_CONTAINER.lower():
|
|
69
74
|
authenticator = ContainerAuthenticator(
|
|
70
75
|
cr_token_filename=config.get('CR_TOKEN_FILENAME'),
|
|
@@ -73,30 +78,37 @@ def __construct_authenticator(config: dict) -> Authenticator:
|
|
|
73
78
|
url=config.get('AUTH_URL'),
|
|
74
79
|
client_id=config.get('CLIENT_ID'),
|
|
75
80
|
client_secret=config.get('CLIENT_SECRET'),
|
|
76
|
-
disable_ssl_verification=config.get(
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
disable_ssl_verification=config.get('AUTH_DISABLE_SSL', 'false').lower() == 'true',
|
|
82
|
+
scope=config.get('SCOPE'),
|
|
83
|
+
)
|
|
79
84
|
elif auth_type == Authenticator.AUTHTYPE_CP4D.lower():
|
|
80
85
|
authenticator = CloudPakForDataAuthenticator(
|
|
81
86
|
username=config.get('USERNAME'),
|
|
82
87
|
password=config.get('PASSWORD'),
|
|
83
88
|
url=config.get('AUTH_URL'),
|
|
84
89
|
apikey=config.get('APIKEY'),
|
|
85
|
-
disable_ssl_verification=config.get('AUTH_DISABLE_SSL', 'false').lower() == 'true'
|
|
90
|
+
disable_ssl_verification=config.get('AUTH_DISABLE_SSL', 'false').lower() == 'true',
|
|
91
|
+
)
|
|
86
92
|
elif auth_type == Authenticator.AUTHTYPE_IAM.lower() and config.get('APIKEY'):
|
|
87
93
|
authenticator = IAMAuthenticator(
|
|
88
94
|
apikey=config.get('APIKEY'),
|
|
89
95
|
url=config.get('AUTH_URL'),
|
|
90
96
|
client_id=config.get('CLIENT_ID'),
|
|
91
97
|
client_secret=config.get('CLIENT_SECRET'),
|
|
92
|
-
disable_ssl_verification=config.get(
|
|
93
|
-
|
|
94
|
-
|
|
98
|
+
disable_ssl_verification=config.get('AUTH_DISABLE_SSL', 'false').lower() == 'true',
|
|
99
|
+
scope=config.get('SCOPE'),
|
|
100
|
+
)
|
|
95
101
|
elif auth_type == Authenticator.AUTHTYPE_VPC.lower():
|
|
96
102
|
authenticator = VPCInstanceAuthenticator(
|
|
97
103
|
iam_profile_crn=config.get('IAM_PROFILE_CRN'),
|
|
98
104
|
iam_profile_id=config.get('IAM_PROFILE_ID'),
|
|
99
|
-
url=config.get('AUTH_URL')
|
|
105
|
+
url=config.get('AUTH_URL'),
|
|
106
|
+
)
|
|
107
|
+
elif auth_type == Authenticator.AUTHTYPE_MCSP.lower():
|
|
108
|
+
authenticator = MCSPAuthenticator(
|
|
109
|
+
apikey=config.get('APIKEY'),
|
|
110
|
+
url=config.get('AUTH_URL'),
|
|
111
|
+
)
|
|
100
112
|
elif auth_type == Authenticator.AUTHTYPE_NOAUTH.lower():
|
|
101
113
|
authenticator = NoAuthAuthenticator()
|
|
102
114
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import ssl
|
|
2
|
+
|
|
3
|
+
from requests import certs
|
|
4
|
+
from requests.adapters import HTTPAdapter, DEFAULT_POOLBLOCK
|
|
5
|
+
from urllib3.util.ssl_ import create_urllib3_context
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SSLHTTPAdapter(HTTPAdapter):
|
|
9
|
+
"""Wraps the original HTTP adapter and adds additional SSL context."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, *args, **kwargs):
|
|
12
|
+
self._disable_ssl_verification = kwargs.pop('_disable_ssl_verification', None)
|
|
13
|
+
|
|
14
|
+
super().__init__(*args, **kwargs)
|
|
15
|
+
|
|
16
|
+
def init_poolmanager(self, connections, maxsize, block=DEFAULT_POOLBLOCK, **pool_kwargs):
|
|
17
|
+
"""Create and use custom SSL configuration."""
|
|
18
|
+
|
|
19
|
+
ssl_context = create_urllib3_context()
|
|
20
|
+
# NOTE: https://github.com/psf/requests/pull/6731/files#r1622893724
|
|
21
|
+
ssl_context.load_verify_locations(certs.where())
|
|
22
|
+
ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2
|
|
23
|
+
|
|
24
|
+
if self._disable_ssl_verification:
|
|
25
|
+
ssl_context.check_hostname = False
|
|
26
|
+
ssl_context.verify_mode = ssl.CERT_NONE
|
|
27
|
+
|
|
28
|
+
super().init_poolmanager(connections, maxsize, block, ssl_context=ssl_context, **pool_kwargs)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
|
|
3
|
+
# Copyright 2024 IBM All Rights Reserved.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
# from ibm_cloud_sdk_core.authenticators import Authenticator
|
|
17
|
+
|
|
18
|
+
import platform
|
|
19
|
+
from .version import __version__
|
|
20
|
+
|
|
21
|
+
SDK_NAME = 'ibm-python-sdk-core'
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _get_system_info() -> str:
|
|
25
|
+
return 'os.name={0} os.version={1} python.version={2}'.format(
|
|
26
|
+
platform.system(), platform.release(), platform.python_version()
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _build_user_agent(component: str = None) -> str:
|
|
31
|
+
sub_component = ""
|
|
32
|
+
if component is not None:
|
|
33
|
+
sub_component = '/{0}'.format(component)
|
|
34
|
+
return '{0}{1}-{2} {3}'.format(SDK_NAME, sub_component, __version__, _get_system_info())
|