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.
Files changed (56) hide show
  1. ibm_cloud_sdk_core/__init__.py +1 -0
  2. ibm_cloud_sdk_core/api_exception.py +18 -4
  3. ibm_cloud_sdk_core/authenticators/__init__.py +1 -0
  4. ibm_cloud_sdk_core/authenticators/authenticator.py +3 -3
  5. ibm_cloud_sdk_core/authenticators/basic_authenticator.py +5 -6
  6. ibm_cloud_sdk_core/authenticators/bearer_token_authenticator.py +1 -1
  7. ibm_cloud_sdk_core/authenticators/container_authenticator.py +25 -16
  8. ibm_cloud_sdk_core/authenticators/cp4d_authenticator.py +34 -20
  9. ibm_cloud_sdk_core/authenticators/iam_authenticator.py +22 -13
  10. ibm_cloud_sdk_core/authenticators/iam_request_based_authenticator.py +5 -7
  11. ibm_cloud_sdk_core/authenticators/mcsp_authenticator.py +130 -0
  12. ibm_cloud_sdk_core/authenticators/vpc_instance_authenticator.py +7 -10
  13. ibm_cloud_sdk_core/base_service.py +113 -92
  14. ibm_cloud_sdk_core/detailed_response.py +21 -15
  15. ibm_cloud_sdk_core/get_authenticator.py +28 -16
  16. ibm_cloud_sdk_core/http_adapter.py +28 -0
  17. ibm_cloud_sdk_core/private_helpers.py +34 -0
  18. ibm_cloud_sdk_core/token_managers/container_token_manager.py +61 -30
  19. ibm_cloud_sdk_core/token_managers/cp4d_token_manager.py +34 -21
  20. ibm_cloud_sdk_core/token_managers/iam_request_based_token_manager.py +43 -20
  21. ibm_cloud_sdk_core/token_managers/iam_token_manager.py +24 -13
  22. ibm_cloud_sdk_core/token_managers/jwt_token_manager.py +3 -16
  23. ibm_cloud_sdk_core/token_managers/mcsp_token_manager.py +102 -0
  24. ibm_cloud_sdk_core/token_managers/token_manager.py +13 -23
  25. ibm_cloud_sdk_core/token_managers/vpc_instance_token_manager.py +33 -13
  26. ibm_cloud_sdk_core/utils.py +126 -32
  27. ibm_cloud_sdk_core/version.py +1 -1
  28. {ibm_cloud_sdk_core-3.15.0.dist-info → ibm_cloud_sdk_core-3.20.6.dist-info}/METADATA +39 -30
  29. ibm_cloud_sdk_core-3.20.6.dist-info/RECORD +34 -0
  30. {ibm_cloud_sdk_core-3.15.0.dist-info → ibm_cloud_sdk_core-3.20.6.dist-info}/WHEEL +1 -1
  31. ibm_cloud_sdk_core-3.20.6.dist-info/top_level.txt +1 -0
  32. ibm_cloud_sdk_core-3.15.0.dist-info/RECORD +0 -52
  33. ibm_cloud_sdk_core-3.15.0.dist-info/top_level.txt +0 -3
  34. ibm_cloud_sdk_core-3.15.0.dist-info/zip-safe +0 -1
  35. test/__init__.py +0 -0
  36. test/test_api_exception.py +0 -73
  37. test/test_authenticator.py +0 -21
  38. test/test_base_service.py +0 -925
  39. test/test_basic_authenticator.py +0 -36
  40. test/test_bearer_authenticator.py +0 -28
  41. test/test_container_authenticator.py +0 -105
  42. test/test_container_token_manager.py +0 -283
  43. test/test_cp4d_authenticator.py +0 -171
  44. test/test_cp4d_token_manager.py +0 -56
  45. test/test_detailed_response.py +0 -57
  46. test/test_iam_authenticator.py +0 -157
  47. test/test_iam_token_manager.py +0 -362
  48. test/test_jwt_token_manager.py +0 -109
  49. test/test_no_auth_authenticator.py +0 -15
  50. test/test_token_manager.py +0 -84
  51. test/test_utils.py +0 -634
  52. test/test_vpc_instance_authenticator.py +0 -66
  53. test/test_vpc_instance_token_manager.py +0 -266
  54. test_integration/__init__.py +0 -0
  55. test_integration/test_cp4d_authenticator_integration.py +0 -45
  56. {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 (has_bad_first_or_last_char, remove_null_values,
35
- cleanup_values, read_external_sources, strip_extra_slashes)
36
- from .version import __version__
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
- SDK_NAME = 'ibm-python-sdk-core'
76
- ERROR_MSG_DISABLE_SSL = 'The connection failed because the SSL certificate is not valid. To use a self-signed '\
77
- 'certificate, disable verification of the server\'s SSL certificate by invoking the '\
78
- 'set_disable_ssl_verification(True) on your service instance and/ or use the '\
79
- 'disable_ssl_verification option of the authenticator.'
80
-
81
- def __init__(self,
82
- *,
83
- service_url: str = None,
84
- authenticator: Authenticator = None,
85
- disable_ssl_verification: bool = False,
86
- enable_gzip_compression: bool = False) -> None:
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(self._build_user_agent())
107
+ self._set_user_agent_header(_build_user_agent())
96
108
  self.retry_config = None
97
- self.http_adapter = HTTPAdapter()
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
- def enable_retries(self, max_retries: int = 4, retry_interval: float = 1.0) -> None:
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 default wait time (in seconds) to use for the first retry attempt.
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 the retry_interval and retry attempt number:
112
- wait_time = retry_interval * (2 ^ (n-1)), where n is the retry attempt number
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=retry_interval,
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
- 'DELETE', 'OPTIONS', 'TRACE', 'POST']
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 = HTTPAdapter()
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 (self.authenticator
200
- and hasattr(self.authenticator, 'token_manager')
201
- and isinstance(self.authenticator.token_manager,
202
- TokenManager)):
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
- logger.warning('"%s" has been removed from the request', key)
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 request or a 204 response
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
- else:
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
- result = response
320
- return DetailedResponse(response=result,
321
- headers=response.headers,
322
- status_code=response.status_code)
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(self,
340
- method: str,
341
- url: str,
342
- *,
343
- headers: Optional[dict] = None,
344
- params: Optional[dict] = None,
345
- data: Optional[Union[str, dict]] = None,
346
- files: Optional[Union[Dict[str, Tuple[str]],
347
- List[Tuple[str,
348
- Tuple[str,
349
- ...]]]]] = None,
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: Headers of the request.
362
- params: Querystring data to be appended to the url.
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 (self.get_enable_gzip_compression()
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 there response, defaults to None.
29
+ status_code: The status code of the response, defaults to None.
30
30
 
31
31
  Attributes:
32
- response (requests.Response): The response to the service request.
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
- def __init__(self,
38
- *,
39
- response: Optional[requests.Response] = None,
40
- headers: Optional[Dict[str, str]] = None,
41
- status_code: Optional[int] = None) -> None:
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 HTTP response of the service request.
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 of the service request.
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 (Authenticator, BasicAuthenticator, BearerTokenAuthenticator, ContainerAuthenticator,
18
- CloudPakForDataAuthenticator, IAMAuthenticator, NoAuthAuthenticator,
19
- VPCInstanceAuthenticator)
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
- 'AUTH_DISABLE_SSL', 'false').lower() == 'true',
78
- scope=config.get('SCOPE'))
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
- 'AUTH_DISABLE_SSL', 'false').lower() == 'true',
94
- scope=config.get('SCOPE'))
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())