ibm-cloud-sdk-core 3.16.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 +2 -1
  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 +33 -21
  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 +107 -91
  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 +30 -22
  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 +121 -46
  27. ibm_cloud_sdk_core/version.py +1 -1
  28. {ibm_cloud_sdk_core-3.16.0.dist-info → ibm_cloud_sdk_core-3.20.6.dist-info}/METADATA +40 -28
  29. ibm_cloud_sdk_core-3.20.6.dist-info/RECORD +34 -0
  30. {ibm_cloud_sdk_core-3.16.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.16.0.dist-info/RECORD +0 -52
  33. ibm_cloud_sdk_core-3.16.0.dist-info/top_level.txt +0 -3
  34. ibm_cloud_sdk_core-3.16.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 -933
  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.16.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,9 +15,9 @@
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
@@ -25,15 +25,23 @@ from urllib3.util.retry import Retry
25
25
 
26
26
  import requests
27
27
  from requests.structures import CaseInsensitiveDict
28
+ from requests.exceptions import JSONDecodeError
28
29
 
29
30
  from ibm_cloud_sdk_core.authenticators import Authenticator
30
31
  from .api_exception import ApiException
31
32
  from .detailed_response import DetailedResponse
33
+ from .http_adapter import SSLHTTPAdapter
32
34
  from .token_managers.token_manager import TokenManager
33
- from .utils import (has_bad_first_or_last_char, remove_null_values,
34
- cleanup_values, read_external_sources, strip_extra_slashes,
35
- SSLHTTPAdapter)
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,9 +104,9 @@ 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 = SSLHTTPAdapter()
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):
@@ -103,50 +115,40 @@ class BaseService:
103
115
  self.http_client.mount('http://', self.http_adapter)
104
116
  self.http_client.mount('https://', self.http_adapter)
105
117
 
106
- def enable_retries(self, max_retries: int = 4, retry_interval: float = 1.0) -> None:
118
+ def enable_retries(self, max_retries: int = 4, retry_interval: float = 30.0) -> None:
107
119
  """Enable automatic retries on the underlying http client used by the BaseService instance.
108
120
 
109
121
  Args:
110
122
  max_retries: the maximum number of retries to attempt for a failed retryable request
111
- 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.
112
124
  In general, if a response includes the Retry-After header, that will be used for
113
125
  the wait time associated with the retry attempt. If the Retry-After header is not
114
- present, then the wait time is based on the retry_interval and retry attempt number:
115
- 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".
116
128
  """
117
129
  self.retry_config = Retry(
118
130
  total=max_retries,
119
- backoff_factor=retry_interval,
131
+ backoff_factor=1.0,
132
+ backoff_max=retry_interval,
120
133
  # List of HTTP status codes to retry on in addition to Timeout/Connection Errors
121
134
  status_forcelist=[429, 500, 502, 503, 504],
122
135
  # List of HTTP methods to retry on
123
136
  # Omitting this will default to all methods except POST
124
- allowed_methods=['HEAD', 'GET', 'PUT',
125
- '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
126
141
  )
127
- self.http_adapter = SSLHTTPAdapter(max_retries=self.retry_config)
128
142
  self.http_client.mount('http://', self.http_adapter)
129
143
  self.http_client.mount('https://', self.http_adapter)
130
144
 
131
145
  def disable_retries(self):
132
146
  """Remove retry config from http_adapter"""
133
147
  self.retry_config = None
134
- self.http_adapter = SSLHTTPAdapter()
148
+ self.http_adapter = SSLHTTPAdapter(_disable_ssl_verification=self.disable_ssl_verification)
135
149
  self.http_client.mount('http://', self.http_adapter)
136
150
  self.http_client.mount('https://', self.http_adapter)
137
151
 
138
- @staticmethod
139
- def _get_system_info() -> str:
140
- return '{0} {1} {2}'.format(
141
- platform.system(), # OS
142
- platform.release(), # OS version
143
- platform.python_version() # Python version
144
- )
145
-
146
- def _build_user_agent(self) -> str:
147
- return '{0}-{1} {2}'.format(self.SDK_NAME, __version__,
148
- self._get_system_info())
149
-
150
152
  def configure_service(self, service_name: str) -> None:
151
153
  """Look for external configuration of a service. Set service properties.
152
154
 
@@ -168,19 +170,16 @@ class BaseService:
168
170
  if config.get('URL'):
169
171
  self.set_service_url(config.get('URL'))
170
172
  if config.get('DISABLE_SSL'):
171
- self.set_disable_ssl_verification(
172
- config.get('DISABLE_SSL').lower() == 'true')
173
+ self.set_disable_ssl_verification(config.get('DISABLE_SSL').lower() == 'true')
173
174
  if config.get('ENABLE_GZIP'):
174
- self.set_enable_gzip_compression(
175
- config.get('ENABLE_GZIP').lower() == 'true')
175
+ self.set_enable_gzip_compression(config.get('ENABLE_GZIP').lower() == 'true')
176
176
  if config.get('ENABLE_RETRIES'):
177
177
  if config.get('ENABLE_RETRIES').lower() == 'true':
178
178
  kwargs = {}
179
179
  if config.get('MAX_RETRIES'):
180
180
  kwargs["max_retries"] = int(config.get('MAX_RETRIES'))
181
181
  if config.get('RETRY_INTERVAL'):
182
- kwargs["retry_interval"] = float(
183
- config.get('RETRY_INTERVAL'))
182
+ kwargs["retry_interval"] = float(config.get('RETRY_INTERVAL'))
184
183
  self.enable_retries(**kwargs)
185
184
 
186
185
  def _set_user_agent_header(self, user_agent_string: str) -> None:
@@ -199,10 +198,11 @@ class BaseService:
199
198
  """
200
199
  if isinstance(http_config, dict):
201
200
  self.http_config = http_config
202
- if (self.authenticator
203
- and hasattr(self.authenticator, 'token_manager')
204
- and isinstance(self.authenticator.token_manager,
205
- TokenManager)):
201
+ if (
202
+ self.authenticator
203
+ and hasattr(self.authenticator, 'token_manager')
204
+ and isinstance(self.authenticator.token_manager, TokenManager)
205
+ ):
206
206
  self.authenticator.token_manager.http_config = http_config
207
207
  else:
208
208
  raise TypeError("http_config parameter must be a dictionary")
@@ -214,8 +214,18 @@ class BaseService:
214
214
  Keyword Arguments:
215
215
  status: set to true to disable ssl verification (default: {False})
216
216
  """
217
+ if self.disable_ssl_verification == status:
218
+ # Do nothing if the state doesn't change.
219
+ return
220
+
217
221
  self.disable_ssl_verification = status
218
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
+
219
229
  def set_service_url(self, service_url: str) -> None:
220
230
  """Set the url the service will make HTTP requests too.
221
231
 
@@ -251,8 +261,7 @@ class BaseService:
251
261
  if isinstance(http_client, requests.sessions.Session):
252
262
  self.http_client = http_client
253
263
  else:
254
- raise TypeError(
255
- "http_client parameter must be a requests.sessions.Session")
264
+ raise TypeError("http_client parameter must be a requests.sessions.Session")
256
265
 
257
266
  def get_authenticator(self) -> Authenticator:
258
267
  """Get the authenticator currently used by the service.
@@ -305,35 +314,40 @@ class BaseService:
305
314
  if key not in silent_keys:
306
315
  logger.warning('"%s" has been removed from the request', key)
307
316
  try:
308
- response = self.http_client.request(**request,
309
- cookies=self.jar,
310
- **kwargs)
317
+ response = self.http_client.request(**request, cookies=self.jar, **kwargs)
311
318
 
319
+ # Process a "success" response.
312
320
  if 200 <= response.status_code <= 299:
313
321
  if response.status_code == 204 or request['method'] == 'HEAD':
314
- # 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.
315
323
  result = None
316
324
  elif stream_response:
317
325
  result = response
318
326
  elif not response.text:
319
327
  result = None
320
- else:
328
+ elif is_json_mimetype(response.headers.get('Content-Type')):
329
+ # If this is a JSON response, then try to unmarshal it.
321
330
  try:
322
- result = response.json()
323
- except:
324
- result = response
325
- return DetailedResponse(response=result,
326
- headers=response.headers,
327
- 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)
328
343
 
344
+ # Received error status code from server, raise an APIException.
329
345
  raise ApiException(response.status_code, http_response=response)
330
346
  except requests.exceptions.SSLError:
331
347
  logger.exception(self.ERROR_MSG_DISABLE_SSL)
332
348
  raise
333
349
 
334
- def set_enable_gzip_compression(self,
335
- should_enable_compression: bool = False
336
- ) -> None:
350
+ def set_enable_gzip_compression(self, should_enable_compression: bool = False) -> None:
337
351
  """Set value to enable gzip compression on request bodies"""
338
352
  self.enable_gzip_compression = should_enable_compression
339
353
 
@@ -341,18 +355,17 @@ class BaseService:
341
355
  """Get value for enabling gzip compression on request bodies"""
342
356
  return self.enable_gzip_compression
343
357
 
344
- def prepare_request(self,
345
- method: str,
346
- url: str,
347
- *,
348
- headers: Optional[dict] = None,
349
- params: Optional[dict] = None,
350
- data: Optional[Union[str, dict]] = None,
351
- files: Optional[Union[Dict[str, Tuple[str]],
352
- List[Tuple[str,
353
- Tuple[str,
354
- ...]]]]] = None,
355
- **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:
356
369
  """Build a dict that represents an HTTP service request.
357
370
 
358
371
  Clean up headers, add default http configuration, convert data
@@ -363,8 +376,10 @@ class BaseService:
363
376
  url: The origin + pathname according to WHATWG spec.
364
377
 
365
378
  Keyword Arguments:
366
- headers: Headers of the request.
367
- 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).
368
383
  data: The request body. Converted to json if a dict.
369
384
  files: 'files' can be a dictionary (i.e { '<part-name>': (<tuple>)}),
370
385
  or a list of tuples [ (<part-name>, (<tuple>))... ]
@@ -409,14 +424,16 @@ class BaseService:
409
424
  self.authenticator.authenticate(request)
410
425
 
411
426
  # Compress the request body if applicable
412
- if (self.get_enable_gzip_compression()
413
- and 'content-encoding' not in headers
414
- 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:
415
428
  headers['content-encoding'] = 'gzip'
416
- uncompressed_data = request['data']
417
- request_body = gzip.compress(uncompressed_data)
418
- request['data'] = request_body
419
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)
420
437
 
421
438
  # Next, we need to process the 'files' argument to try to fill in
422
439
  # any missing filenames where possible.
@@ -431,8 +448,7 @@ class BaseService:
431
448
  files = files.items()
432
449
  # Next, fill in any missing filenames from file tuples.
433
450
  for part_name, file_tuple in files:
434
- if file_tuple and len(
435
- file_tuple) == 3 and file_tuple[0] is None:
451
+ if file_tuple and len(file_tuple) == 3 and file_tuple[0] is None:
436
452
  file = file_tuple[1]
437
453
  if file and hasattr(file, 'name'):
438
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())