ibm-cloud-sdk-core 3.16.0__py3-none-any.whl → 3.21.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.
Files changed (58) 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 +12 -7
  6. ibm_cloud_sdk_core/authenticators/bearer_token_authenticator.py +7 -2
  7. ibm_cloud_sdk_core/authenticators/container_authenticator.py +25 -16
  8. ibm_cloud_sdk_core/authenticators/cp4d_authenticator.py +38 -23
  9. ibm_cloud_sdk_core/authenticators/iam_authenticator.py +22 -13
  10. ibm_cloud_sdk_core/authenticators/iam_request_based_authenticator.py +10 -8
  11. ibm_cloud_sdk_core/authenticators/mcsp_authenticator.py +134 -0
  12. ibm_cloud_sdk_core/authenticators/vpc_instance_authenticator.py +12 -11
  13. ibm_cloud_sdk_core/base_service.py +137 -103
  14. ibm_cloud_sdk_core/detailed_response.py +21 -15
  15. ibm_cloud_sdk_core/get_authenticator.py +35 -17
  16. ibm_cloud_sdk_core/http_adapter.py +28 -0
  17. ibm_cloud_sdk_core/logger.py +85 -0
  18. ibm_cloud_sdk_core/private_helpers.py +34 -0
  19. ibm_cloud_sdk_core/token_managers/container_token_manager.py +63 -33
  20. ibm_cloud_sdk_core/token_managers/cp4d_token_manager.py +35 -22
  21. ibm_cloud_sdk_core/token_managers/iam_request_based_token_manager.py +50 -21
  22. ibm_cloud_sdk_core/token_managers/iam_token_manager.py +24 -13
  23. ibm_cloud_sdk_core/token_managers/jwt_token_manager.py +3 -16
  24. ibm_cloud_sdk_core/token_managers/mcsp_token_manager.py +108 -0
  25. ibm_cloud_sdk_core/token_managers/token_manager.py +20 -23
  26. ibm_cloud_sdk_core/token_managers/vpc_instance_token_manager.py +37 -18
  27. ibm_cloud_sdk_core/utils.py +127 -48
  28. ibm_cloud_sdk_core/version.py +1 -1
  29. ibm_cloud_sdk_core-3.21.0.dist-info/METADATA +159 -0
  30. ibm_cloud_sdk_core-3.21.0.dist-info/RECORD +35 -0
  31. {ibm_cloud_sdk_core-3.16.0.dist-info → ibm_cloud_sdk_core-3.21.0.dist-info}/WHEEL +1 -1
  32. ibm_cloud_sdk_core-3.21.0.dist-info/top_level.txt +1 -0
  33. ibm_cloud_sdk_core-3.16.0.dist-info/METADATA +0 -112
  34. ibm_cloud_sdk_core-3.16.0.dist-info/RECORD +0 -52
  35. ibm_cloud_sdk_core-3.16.0.dist-info/top_level.txt +0 -3
  36. ibm_cloud_sdk_core-3.16.0.dist-info/zip-safe +0 -1
  37. test/__init__.py +0 -0
  38. test/test_api_exception.py +0 -73
  39. test/test_authenticator.py +0 -21
  40. test/test_base_service.py +0 -933
  41. test/test_basic_authenticator.py +0 -36
  42. test/test_bearer_authenticator.py +0 -28
  43. test/test_container_authenticator.py +0 -105
  44. test/test_container_token_manager.py +0 -283
  45. test/test_cp4d_authenticator.py +0 -171
  46. test/test_cp4d_token_manager.py +0 -56
  47. test/test_detailed_response.py +0 -57
  48. test/test_iam_authenticator.py +0 -157
  49. test/test_iam_token_manager.py +0 -362
  50. test/test_jwt_token_manager.py +0 -109
  51. test/test_no_auth_authenticator.py +0 -15
  52. test/test_token_manager.py +0 -84
  53. test/test_utils.py +0 -634
  54. test/test_vpc_instance_authenticator.py +0 -66
  55. test/test_vpc_instance_token_manager.py +0 -266
  56. test_integration/__init__.py +0 -0
  57. test_integration/test_cp4d_authenticator_integration.py +0 -45
  58. {ibm_cloud_sdk_core-3.16.0.dist-info → ibm_cloud_sdk_core-3.21.0.dist-info}/LICENSE +0 -0
test/test_base_service.py DELETED
@@ -1,933 +0,0 @@
1
- # coding=utf-8
2
- # pylint: disable=missing-docstring,protected-access,too-few-public-methods
3
- import gzip
4
- import json
5
- import os
6
- import ssl
7
- import tempfile
8
- import time
9
- from shutil import copyfile
10
- from typing import Optional
11
- from urllib3.exceptions import ConnectTimeoutError, MaxRetryError
12
-
13
- import jwt
14
- import pytest
15
- import responses
16
- import requests
17
-
18
- from ibm_cloud_sdk_core import ApiException
19
- from ibm_cloud_sdk_core import BaseService, DetailedResponse
20
- from ibm_cloud_sdk_core import CP4DTokenManager
21
- from ibm_cloud_sdk_core import get_authenticator_from_environment
22
- from ibm_cloud_sdk_core.authenticators import (IAMAuthenticator, NoAuthAuthenticator, Authenticator,
23
- BasicAuthenticator, CloudPakForDataAuthenticator)
24
-
25
-
26
- class IncludeExternalConfigService(BaseService):
27
- default_service_url = 'https://servicesthatincludeexternalconfig.com/api'
28
-
29
- def __init__(
30
- self,
31
- api_version: str,
32
- authenticator: Optional[Authenticator] = None,
33
- trace_id: Optional[str] = None
34
- ) -> None:
35
- BaseService.__init__(
36
- self,
37
- service_url=self.default_service_url,
38
- authenticator=authenticator,
39
- disable_ssl_verification=False
40
- )
41
- self.api_version = api_version
42
- self.trace_id = trace_id
43
- self.configure_service('include-external-config')
44
-
45
-
46
- class AnyServiceV1(BaseService):
47
- default_url = 'https://gateway.watsonplatform.net/test/api'
48
-
49
- def __init__(
50
- self,
51
- version: str,
52
- service_url: str = default_url,
53
- authenticator: Optional[Authenticator] = None,
54
- disable_ssl_verification: bool = False
55
- ) -> None:
56
- BaseService.__init__(
57
- self,
58
- service_url=service_url,
59
- authenticator=authenticator,
60
- disable_ssl_verification=disable_ssl_verification)
61
- self.version = version
62
-
63
- def op_with_path_params(self, path0: str, path1: str) -> DetailedResponse:
64
- if path0 is None:
65
- raise ValueError('path0 must be provided')
66
- if path1 is None:
67
- raise ValueError('path1 must be provided')
68
- params = {'version': self.version}
69
- url = '/v1/foo/{0}/bar/{1}/baz'.format(
70
- *self._encode_path_vars(path0, path1))
71
- request = self.prepare_request(method='GET', url=url, params=params)
72
- response = self.send(request)
73
- return response
74
-
75
- def with_http_config(self, http_config: dict) -> DetailedResponse:
76
- self.set_http_config(http_config)
77
- request = self.prepare_request(method='GET', url='')
78
- response = self.send(request)
79
- return response
80
-
81
- def any_service_call(self) -> DetailedResponse:
82
- request = self.prepare_request(method='GET', url='')
83
- response = self.send(request)
84
- return response
85
-
86
- def head_request(self) -> DetailedResponse:
87
- request = self.prepare_request(method='HEAD', url='')
88
- response = self.send(request)
89
- return response
90
-
91
- def get_document_as_stream(self) -> DetailedResponse:
92
- params = {'version': self.version}
93
- url = '/v1/streamjson'
94
- request = self.prepare_request(method='GET', url=url, params=params)
95
- response = self.send(request, stream=True)
96
- return response
97
-
98
-
99
- def get_access_token() -> str:
100
- access_token_layout = {
101
- "username": "dummy",
102
- "role": "Admin",
103
- "permissions": ["administrator", "manage_catalog"],
104
- "sub": "admin",
105
- "iss": "sss",
106
- "aud": "sss",
107
- "uid": "sss",
108
- "iat": 3600,
109
- "exp": int(time.time())
110
- }
111
-
112
- access_token = jwt.encode(
113
- access_token_layout,
114
- 'secret',
115
- algorithm='HS256',
116
- headers={
117
- 'kid': '230498151c214b788dd97f22b85410a5'
118
- })
119
- return access_token
120
-
121
-
122
- def test_invalid_authenticator():
123
- with pytest.raises(ValueError) as err:
124
- AnyServiceV1('2021-08-18')
125
-
126
- assert str(err.value) == 'authenticator must be provided'
127
-
128
-
129
- @responses.activate
130
- def test_url_encoding():
131
- service = AnyServiceV1('2017-07-07', authenticator=NoAuthAuthenticator())
132
-
133
- # All characters in path0 _must_ be encoded in path segments
134
- path0 = ' \"<>^`{}|/\\?#%[]'
135
- path0_encoded = '%20%22%3C%3E%5E%60%7B%7D%7C%2F%5C%3F%23%25%5B%5D'
136
- # All non-ASCII chars _must_ be encoded in path segments
137
- path1 = '比萨浇头'.encode('utf8') # "pizza toppings"
138
- path1_encoded = '%E6%AF%94%E8%90%A8%E6%B5%87%E5%A4%B4'
139
-
140
- path_encoded = '/v1/foo/' + path0_encoded + '/bar/' + path1_encoded + '/baz'
141
- test_url = service.default_url + path_encoded
142
-
143
- responses.add(
144
- responses.GET,
145
- test_url,
146
- status=200,
147
- body=json.dumps({
148
- "foobar": "baz"
149
- }),
150
- content_type='application/json')
151
-
152
- # Set Host as a default header on the service.
153
- service.set_default_headers({'Host': 'alternatehost.ibm.com:443'})
154
-
155
- response = service.op_with_path_params(path0, path1)
156
-
157
- assert response is not None
158
- assert len(responses.calls) == 1
159
- assert path_encoded in responses.calls[0].request.url
160
- assert 'version=2017-07-07' in responses.calls[0].request.url
161
-
162
- # Verify that the Host header was set in the request.
163
- assert responses.calls[0].request.headers.get(
164
- 'Host') == 'alternatehost.ibm.com:443'
165
-
166
-
167
- @responses.activate
168
- def test_stream_json_response():
169
- service = AnyServiceV1('2017-07-07', authenticator=NoAuthAuthenticator())
170
-
171
- path = '/v1/streamjson'
172
- test_url = service.default_url + path
173
-
174
- expected_response = json.dumps(
175
- {"id": 1, "rev": "v1", "content": "this is a document"})
176
-
177
- # print("Expected response: ", expected_response)
178
-
179
- # Simulate a JSON response
180
- responses.add(
181
- responses.GET,
182
- test_url,
183
- status=200,
184
- body=expected_response,
185
- content_type='application/json')
186
-
187
- # Invoke the operation and receive an "iterable" as the response
188
- response = service.get_document_as_stream()
189
-
190
- assert response is not None
191
- assert len(responses.calls) == 1
192
-
193
- # retrieve the requests.Response object from the DetailedResponse
194
- resp = response.get_result()
195
- assert isinstance(resp, requests.Response)
196
- assert hasattr(resp, "iter_content")
197
-
198
- # Retrieve the response body, one chunk at a time.
199
- actual_response = ''
200
- for chunk in resp.iter_content(chunk_size=3):
201
- actual_response += chunk.decode("utf-8")
202
-
203
- # print("Actual response: ", actual_response)
204
- assert actual_response == expected_response
205
-
206
-
207
- @responses.activate
208
- def test_http_config():
209
- service = AnyServiceV1('2017-07-07', authenticator=NoAuthAuthenticator())
210
- responses.add(
211
- responses.GET,
212
- service.default_url,
213
- status=200,
214
- body=json.dumps({
215
- "foobar": "baz"
216
- }),
217
- content_type='application/json')
218
-
219
- response = service.with_http_config({'timeout': 100})
220
- assert response is not None
221
- assert len(responses.calls) == 1
222
-
223
-
224
- def test_fail_http_config():
225
- service = AnyServiceV1('2017-07-07', authenticator=NoAuthAuthenticator())
226
- with pytest.raises(TypeError):
227
- service.with_http_config(None)
228
-
229
-
230
- @responses.activate
231
- def test_cwd():
232
- file_path = os.path.join(
233
- os.path.dirname(__file__), '../resources/ibm-credentials.env')
234
- # Try changing working directories to test getting creds from cwd
235
- cwd = os.getcwd()
236
- os.chdir(os.path.dirname(file_path))
237
- iam_authenticator = get_authenticator_from_environment('ibm_watson')
238
- service = AnyServiceV1('2017-07-07', authenticator=iam_authenticator)
239
- service.configure_service('ibm_watson')
240
- os.chdir(cwd)
241
- assert service.service_url == 'https://cwdserviceurl'
242
- assert service.authenticator is not None
243
-
244
- # Copy credentials file to cwd to test loading from current working directory
245
- temp_env_path = os.getcwd() + '/ibm-credentials.env'
246
- copyfile(file_path, temp_env_path)
247
- iam_authenticator = get_authenticator_from_environment('ibm_watson')
248
- service = AnyServiceV1('2017-07-07', authenticator=iam_authenticator)
249
- service.configure_service('ibm_watson')
250
- os.remove(temp_env_path)
251
- assert service.service_url == 'https://cwdserviceurl'
252
- assert service.authenticator is not None
253
-
254
-
255
- @responses.activate
256
- def test_iam():
257
- file_path = os.path.join(
258
- os.path.dirname(__file__), '../resources/ibm-credentials-iam.env')
259
- os.environ['IBM_CREDENTIALS_FILE'] = file_path
260
- iam_authenticator = get_authenticator_from_environment('ibm-watson')
261
- service = AnyServiceV1('2017-07-07', authenticator=iam_authenticator)
262
- assert service.service_url == 'https://gateway.watsonplatform.net/test/api'
263
- del os.environ['IBM_CREDENTIALS_FILE']
264
- assert service.authenticator is not None
265
-
266
- response = {
267
- "access_token": get_access_token(),
268
- "token_type": "Bearer",
269
- "expires_in": 3600,
270
- "expiration": int(time.time()),
271
- "refresh_token": "jy4gl91BQ"
272
- }
273
- responses.add(
274
- responses.POST,
275
- url='https://iam.cloud.ibm.com/identity/token',
276
- body=json.dumps(response),
277
- status=200)
278
- responses.add(
279
- responses.GET,
280
- url='https://gateway.watsonplatform.net/test/api',
281
- body=json.dumps({
282
- "foobar": "baz"
283
- }),
284
- content_type='application/json')
285
- service.any_service_call()
286
- assert "grant-type%3Aapikey" in responses.calls[0].request.body
287
-
288
-
289
- def test_no_auth():
290
- class MadeUp:
291
- def __init__(self):
292
- self.lazy = 'made up'
293
-
294
- with pytest.raises(ValueError) as err:
295
- service = AnyServiceV1('2017-07-07', authenticator=MadeUp())
296
- service.prepare_request(
297
- responses.GET,
298
- url='https://gateway.watsonplatform.net/test/api',
299
- )
300
- assert str(err.value) == 'authenticator should be of type Authenticator'
301
-
302
- service = AnyServiceV1('2017-07-07', authenticator=NoAuthAuthenticator())
303
- service.prepare_request(
304
- responses.GET,
305
- url='https://gateway.watsonplatform.net/test/api',
306
- )
307
- assert service.authenticator is not None
308
- assert isinstance(service.authenticator, Authenticator)
309
-
310
-
311
- def test_for_cp4d():
312
- cp4d_authenticator = CloudPakForDataAuthenticator('my_username', 'my_password',
313
- 'my_url')
314
- service = AnyServiceV1('2017-07-07', authenticator=cp4d_authenticator)
315
- assert service.authenticator.token_manager is not None
316
- assert service.authenticator.token_manager.username == 'my_username'
317
- assert service.authenticator.token_manager.password == 'my_password'
318
- assert service.authenticator.token_manager.url == 'my_url/v1/authorize'
319
- assert isinstance(service.authenticator.token_manager, CP4DTokenManager)
320
-
321
-
322
- def test_disable_ssl_verification():
323
- service1 = AnyServiceV1(
324
- '2017-07-07', authenticator=NoAuthAuthenticator(), disable_ssl_verification=True)
325
- assert service1.disable_ssl_verification is True
326
-
327
- service1.set_disable_ssl_verification(False)
328
- assert service1.disable_ssl_verification is False
329
-
330
- cp4d_authenticator = CloudPakForDataAuthenticator('my_username', 'my_password',
331
- 'my_url')
332
- service2 = AnyServiceV1('2017-07-07', authenticator=cp4d_authenticator)
333
- assert service2.disable_ssl_verification is False
334
- cp4d_authenticator.set_disable_ssl_verification(True)
335
- assert service2.authenticator.token_manager.disable_ssl_verification is True
336
-
337
-
338
- @responses.activate
339
- def test_http_head():
340
- service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator())
341
- expected_headers = {'Test-Header1': 'value1', 'Test-Header2': 'value2'}
342
- responses.add(
343
- responses.HEAD,
344
- service.default_url,
345
- status=200,
346
- headers=expected_headers,
347
- content_type=None)
348
-
349
- response = service.head_request()
350
- assert response is not None
351
- assert len(responses.calls) == 1
352
- assert response.headers is not None
353
- assert response.headers == expected_headers
354
-
355
-
356
- @responses.activate
357
- def test_response_with_no_body():
358
- service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator())
359
- responses.add(responses.GET, service.default_url, status=200, body=None)
360
-
361
- response = service.any_service_call()
362
- assert response is not None
363
- assert len(responses.calls) == 1
364
- assert response.get_result() is None
365
-
366
-
367
- def test_has_bad_first_or_last_char():
368
- with pytest.raises(ValueError) as err:
369
- basic_authenticator = BasicAuthenticator(
370
- '{my_username}', 'my_password')
371
- AnyServiceV1('2018-11-20', authenticator=basic_authenticator).prepare_request(
372
- responses.GET,
373
- 'https://gateway.watsonplatform.net/test/api'
374
- )
375
- assert str(
376
- err.value
377
- ) == 'The username and password shouldn\'t start or end with curly brackets or quotes. '\
378
- 'Please remove any surrounding {, }, or \" characters.'
379
-
380
-
381
- @responses.activate
382
- def test_request_server_error():
383
- responses.add(
384
- responses.GET,
385
- 'https://gateway.watsonplatform.net/test/api',
386
- status=500,
387
- body=json.dumps({
388
- 'error': 'internal server error'
389
- }),
390
- content_type='application/json')
391
- service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator())
392
- try:
393
- prepped = service.prepare_request('GET', url='')
394
- service.send(prepped)
395
- except ApiException as err:
396
- assert err.message == 'internal server error'
397
-
398
-
399
- @responses.activate
400
- def test_request_success_json():
401
- responses.add(
402
- responses.GET,
403
- 'https://gateway.watsonplatform.net/test/api',
404
- status=200,
405
- body=json.dumps({
406
- 'foo': 'bar'
407
- }),
408
- content_type='application/json')
409
- service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator())
410
- prepped = service.prepare_request('GET', url='')
411
- detailed_response = service.send(prepped)
412
- assert detailed_response.get_result() == {'foo': 'bar'}
413
-
414
- service = AnyServiceV1(
415
- '2018-11-20', authenticator=BasicAuthenticator('my_username', 'my_password'))
416
- service.set_default_headers({'test': 'header'})
417
- service.set_disable_ssl_verification(True)
418
- prepped = service.prepare_request('GET', url='')
419
- detailed_response = service.send(prepped)
420
- assert detailed_response.get_result() == {'foo': 'bar'}
421
-
422
-
423
- @responses.activate
424
- def test_request_success_response():
425
- responses.add(
426
- responses.GET,
427
- 'https://gateway.watsonplatform.net/test/api',
428
- status=200,
429
- body=json.dumps({
430
- 'foo': 'bar'
431
- }),
432
- content_type='application/json')
433
- service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator())
434
- prepped = service.prepare_request('GET', url='')
435
- detailed_response = service.send(prepped)
436
- assert detailed_response.get_result() == {"foo": "bar"}
437
-
438
-
439
- @responses.activate
440
- def test_request_fail_401():
441
- responses.add(
442
- responses.GET,
443
- 'https://gateway.watsonplatform.net/test/api',
444
- status=401,
445
- body=json.dumps({
446
- 'foo': 'bar'
447
- }),
448
- content_type='application/json')
449
- service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator())
450
- try:
451
- prepped = service.prepare_request('GET', url='')
452
- service.send(prepped)
453
- except ApiException as err:
454
- assert err.message == 'Unauthorized: Access is denied due to invalid credentials'
455
-
456
-
457
- def test_misc_methods():
458
-
459
- class MockModel:
460
-
461
- def __init__(self, xyz=None):
462
- self.xyz = xyz
463
-
464
- def _to_dict(self):
465
- _dict = {}
466
- if hasattr(self, 'xyz') and self.xyz is not None:
467
- _dict['xyz'] = self.xyz
468
- return _dict
469
-
470
- @classmethod
471
- def _from_dict(cls, _dict):
472
- args = {}
473
- if 'xyz' in _dict:
474
- args['xyz'] = _dict.get('xyz')
475
- return cls(**args)
476
-
477
- mock = MockModel('foo')
478
- service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator())
479
- model1 = service._convert_model(mock)
480
- assert model1 == {'xyz': 'foo'}
481
-
482
- model2 = service._convert_model("{\"xyz\": \"foo\"}")
483
- assert model2 is not None
484
- assert model2['xyz'] == 'foo'
485
-
486
- temp = ['default', '123']
487
- res_str = service._convert_list(temp)
488
- assert res_str == 'default,123'
489
-
490
- temp2 = 'default123'
491
- res_str2 = service._convert_list(temp2)
492
- assert res_str2 == temp2
493
-
494
-
495
- def test_default_headers():
496
- service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator())
497
- service.set_default_headers({'xxx': 'yyy'})
498
- assert service.default_headers == {'xxx': 'yyy'}
499
- with pytest.raises(TypeError):
500
- service.set_default_headers('xxx')
501
-
502
-
503
- def test_set_service_url():
504
- service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator())
505
- with pytest.raises(ValueError) as err:
506
- service.set_service_url('{url}')
507
- assert str(err.value) == 'The service url shouldn\'t start or end with curly brackets or quotes. '\
508
- 'Be sure to remove any {} and \" characters surrounding your service url'
509
-
510
- service.set_service_url('my_url')
511
-
512
-
513
- def test_http_client():
514
- auth = BasicAuthenticator('my_username', 'my_password')
515
- service = AnyServiceV1('2018-11-20', authenticator=auth)
516
- assert isinstance(service.get_http_client(), requests.sessions.Session)
517
- assert service.get_http_client().headers.get(
518
- 'Accept-Encoding') == 'gzip, deflate'
519
-
520
- new_http_client = requests.Session()
521
- new_http_client.headers.update({'Accept-Encoding': 'gzip'})
522
- service.set_http_client(http_client=new_http_client)
523
- assert service.get_http_client().headers.get('Accept-Encoding') == 'gzip'
524
-
525
- with pytest.raises(TypeError):
526
- service.set_http_client("bad_argument_type")
527
-
528
-
529
- def test_get_authenticator():
530
- auth = BasicAuthenticator('my_username', 'my_password')
531
- service = AnyServiceV1('2018-11-20', authenticator=auth)
532
- assert service.get_authenticator() is not None
533
-
534
-
535
- def test_gzip_compression():
536
- # Should return uncompressed data when gzip is off
537
- service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator())
538
- assert not service.get_enable_gzip_compression()
539
- prepped = service.prepare_request(
540
- 'GET', url='', data=json.dumps({"foo": "bar"}))
541
- assert prepped['data'] == b'{"foo": "bar"}'
542
- assert prepped['headers'].get('content-encoding') != 'gzip'
543
-
544
- # Should return compressed data when gzip is on
545
- service.set_enable_gzip_compression(True)
546
- assert service.get_enable_gzip_compression()
547
- prepped = service.prepare_request(
548
- 'GET', url='', data=json.dumps({"foo": "bar"}))
549
- assert prepped['data'] == gzip.compress(b'{"foo": "bar"}')
550
- assert prepped['headers'].get('content-encoding') == 'gzip'
551
-
552
- # Should return compressed data when gzip is on for non-json data
553
- assert service.get_enable_gzip_compression()
554
- prepped = service.prepare_request('GET', url='', data=b'rawdata')
555
- assert prepped['data'] == gzip.compress(b'rawdata')
556
- assert prepped['headers'].get('content-encoding') == 'gzip'
557
-
558
- # Should return compressed data when gzip is on for gzip file data
559
- assert service.get_enable_gzip_compression()
560
- with tempfile.TemporaryFile(mode='w+b') as t_f:
561
- with gzip.GzipFile(mode='wb', fileobj=t_f) as gz_f:
562
- gz_f.write(json.dumps({"foo": "bar"}).encode())
563
- with gzip.GzipFile(mode='rb', fileobj=t_f) as gz_f:
564
- gzip_data = gz_f.read()
565
- prepped = service.prepare_request('GET', url='', data=gzip_data)
566
- assert prepped['data'] == gzip.compress(t_f.read())
567
- assert prepped['headers'].get('content-encoding') == 'gzip'
568
-
569
- # Should return compressed json data when gzip is on for gzip file json data
570
- assert service.get_enable_gzip_compression()
571
- with tempfile.TemporaryFile(mode='w+b') as t_f:
572
- with gzip.GzipFile(mode='wb', fileobj=t_f) as gz_f:
573
- gz_f.write("rawdata".encode())
574
- with gzip.GzipFile(mode='rb', fileobj=t_f) as gz_f:
575
- gzip_data = gz_f.read()
576
- prepped = service.prepare_request('GET', url='', data=gzip_data)
577
- assert prepped['data'] == gzip.compress(t_f.read())
578
- assert prepped['headers'].get('content-encoding') == 'gzip'
579
-
580
- # Should return uncompressed data when content-encoding is set
581
- assert service.get_enable_gzip_compression()
582
- prepped = service.prepare_request('GET', url='', headers={"content-encoding": "gzip"},
583
- data=json.dumps({"foo": "bar"}))
584
- assert prepped['data'] == b'{"foo": "bar"}'
585
- assert prepped['headers'].get('content-encoding') == 'gzip'
586
-
587
-
588
- def test_gzip_compression_external():
589
- # Should set gzip compression from external config
590
- file_path = os.path.join(
591
- os.path.dirname(__file__), '../resources/ibm-credentials-gzip.env')
592
- os.environ['IBM_CREDENTIALS_FILE'] = file_path
593
- service = IncludeExternalConfigService(
594
- 'v1', authenticator=NoAuthAuthenticator())
595
- assert service.service_url == 'https://mockurl'
596
- assert service.get_enable_gzip_compression() is True
597
- prepped = service.prepare_request(
598
- 'GET', url='', data=json.dumps({"foo": "bar"}))
599
- assert prepped['data'] == gzip.compress(b'{"foo": "bar"}')
600
- assert prepped['headers'].get('content-encoding') == 'gzip'
601
-
602
-
603
- def test_retry_config_default():
604
- service = BaseService(service_url='https://mockurl/',
605
- authenticator=NoAuthAuthenticator())
606
- service.enable_retries()
607
- assert service.retry_config.total == 4
608
- assert service.retry_config.backoff_factor == 1.0
609
- assert service.http_client.get_adapter('https://').max_retries.total == 4
610
-
611
- # Ensure retries fail after 4 retries
612
- error = ConnectTimeoutError()
613
- retry = service.http_client.get_adapter('https://').max_retries
614
- retry = retry.increment(error=error)
615
- retry = retry.increment(error=error)
616
- retry = retry.increment(error=error)
617
- retry = retry.increment(error=error)
618
- with pytest.raises(MaxRetryError) as retry_err:
619
- retry.increment(error=error)
620
- assert retry_err.value.reason == error
621
-
622
-
623
- def test_retry_config_disable():
624
- # Test disabling retries
625
- service = BaseService(service_url='https://mockurl/',
626
- authenticator=NoAuthAuthenticator())
627
- service.enable_retries()
628
- service.disable_retries()
629
- assert service.retry_config is None
630
- assert service.http_client.get_adapter('https://').max_retries.total == 0
631
-
632
- # Ensure retries are not started after one connection attempt
633
- error = ConnectTimeoutError()
634
- retry = service.http_client.get_adapter('https://').max_retries
635
- with pytest.raises(MaxRetryError) as retry_err:
636
- retry.increment(error=error)
637
- assert retry_err.value.reason == error
638
-
639
-
640
- def test_retry_config_non_default():
641
- service = BaseService(service_url='https://mockurl/',
642
- authenticator=NoAuthAuthenticator())
643
- service.enable_retries(2, 0.3)
644
- assert service.retry_config.total == 2
645
- assert service.retry_config.backoff_factor == 0.3
646
-
647
- # Ensure retries fail after 2 retries
648
- error = ConnectTimeoutError()
649
- retry = service.http_client.get_adapter('https://').max_retries
650
- retry = retry.increment(error=error)
651
- retry = retry.increment(error=error)
652
- with pytest.raises(MaxRetryError) as retry_err:
653
- retry.increment(error=error)
654
- assert retry_err.value.reason == error
655
-
656
-
657
- def test_retry_config_external():
658
- file_path = os.path.join(
659
- os.path.dirname(__file__), '../resources/ibm-credentials-retry.env')
660
- os.environ['IBM_CREDENTIALS_FILE'] = file_path
661
- service = IncludeExternalConfigService(
662
- 'v1', authenticator=NoAuthAuthenticator())
663
- assert service.retry_config.total == 3
664
- assert service.retry_config.backoff_factor == 0.2
665
-
666
- # Ensure retries fail after 3 retries
667
- error = ConnectTimeoutError()
668
- retry = service.http_client.get_adapter('https://').max_retries
669
- retry = retry.increment(error=error)
670
- retry = retry.increment(error=error)
671
- retry = retry.increment(error=error)
672
- with pytest.raises(MaxRetryError) as retry_err:
673
- retry.increment(error=error)
674
- assert retry_err.value.reason == error
675
-
676
-
677
- @responses.activate
678
- def test_user_agent_header():
679
- service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator())
680
- user_agent_header = service.user_agent_header
681
- assert user_agent_header is not None
682
- assert user_agent_header['User-Agent'] is not None
683
-
684
- responses.add(
685
- responses.GET,
686
- 'https://gateway.watsonplatform.net/test/api',
687
- status=200,
688
- body='some text')
689
- prepped = service.prepare_request('GET', url='', headers={
690
- 'user-agent': 'my_user_agent'
691
- })
692
- response = service.send(prepped)
693
- assert response.get_result().request.headers.get(
694
- 'user-agent') == 'my_user_agent'
695
-
696
- prepped = service.prepare_request('GET', url='', headers=None)
697
- response = service.send(prepped)
698
- assert response.get_result().request.headers.get(
699
- 'user-agent') == user_agent_header['User-Agent']
700
-
701
-
702
- @responses.activate
703
- def test_reserved_keys(caplog):
704
- service = AnyServiceV1('2021-07-02', authenticator=NoAuthAuthenticator())
705
- responses.add(
706
- responses.GET,
707
- 'https://gateway.watsonplatform.net/test/api',
708
- status=200,
709
- body='some text')
710
- prepped = service.prepare_request('GET', url='', headers={'key': 'OK'})
711
- response = service.send(
712
- prepped,
713
- headers={'key': 'bad'},
714
- method='POST',
715
- url='localhost',
716
- cookies=None,
717
- hooks={'response': []})
718
- assert response.get_result().request.headers.get('key') == 'OK'
719
- assert response.get_result().request.url == 'https://gateway.watsonplatform.net/test/api'
720
- assert response.get_result().request.method == 'GET'
721
- assert response.get_result().request.hooks == {'response': []}
722
- assert caplog.record_tuples[0][2] == '"method" has been removed from the request'
723
- assert caplog.record_tuples[1][2] == '"url" has been removed from the request'
724
- assert caplog.record_tuples[2][2] == '"cookies" has been removed from the request'
725
-
726
-
727
- @responses.activate
728
- def test_ssl_error():
729
- responses.add(
730
- responses.GET,
731
- 'https://gateway.watsonplatform.net/test/api',
732
- body=requests.exceptions.SSLError())
733
- service = AnyServiceV1('2021-08-18', authenticator=NoAuthAuthenticator())
734
- with pytest.raises(requests.exceptions.SSLError):
735
- prepped = service.prepare_request('GET', url='')
736
- service.send(prepped)
737
-
738
-
739
- def test_files_dict():
740
- service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator())
741
-
742
- form_data = {}
743
- with open(
744
- os.path.join(
745
- os.path.dirname(__file__), '../resources/ibm-credentials-iam.env'), 'r', encoding='utf-8') as file:
746
- form_data['file1'] = (None, file, 'application/octet-stream')
747
- form_data['string1'] = (None, 'hello', 'text/plain')
748
- request = service.prepare_request(
749
- 'GET', url='', headers={'X-opt-out': True}, files=form_data)
750
- files = request['files']
751
- assert isinstance(files, list)
752
- assert len(files) == 2
753
- files_dict = dict(files)
754
- file1 = files_dict['file1']
755
- assert file1[0] == 'ibm-credentials-iam.env'
756
- string1 = files_dict['string1']
757
- assert string1[0] is None
758
-
759
-
760
- def test_files_list():
761
- service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator())
762
-
763
- form_data = []
764
- with open(
765
- os.path.join(
766
- os.path.dirname(__file__), '../resources/ibm-credentials-iam.env'), 'r', encoding='utf-8') as file:
767
- form_data.append(('file1', (None, file, 'application/octet-stream')))
768
- form_data.append(('string1', (None, 'hello', 'text/plain')))
769
- request = service.prepare_request(
770
- 'GET', url='', headers={'X-opt-out': True}, files=form_data)
771
- files = request['files']
772
- assert isinstance(files, list)
773
- assert len(files) == 2
774
- files_dict = dict(files)
775
- file1 = files_dict['file1']
776
- assert file1[0] == 'ibm-credentials-iam.env'
777
- string1 = files_dict['string1']
778
- assert string1[0] is None
779
-
780
-
781
- def test_files_duplicate_parts():
782
- service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator())
783
-
784
- form_data = []
785
- with open(
786
- os.path.join(
787
- os.path.dirname(__file__), '../resources/ibm-credentials-iam.env'), 'r', encoding='utf-8') as file:
788
- form_data.append(
789
- ('creds_file', (None, file, 'application/octet-stream')))
790
- with open(
791
- os.path.join(
792
- os.path.dirname(__file__), '../resources/ibm-credentials-basic.env'), 'r', encoding='utf-8') as file:
793
- form_data.append(
794
- ('creds_file', (None, file, 'application/octet-stream')))
795
- with open(
796
- os.path.join(
797
- os.path.dirname(__file__), '../resources/ibm-credentials-bearer.env'), 'r', encoding='utf-8') as file:
798
- form_data.append(
799
- ('creds_file', (None, file, 'application/octet-stream')))
800
- request = service.prepare_request(
801
- 'GET', url='', headers={'X-opt-out': True}, files=form_data)
802
- files = request['files']
803
- assert isinstance(files, list)
804
- assert len(files) == 3
805
- for part_name, file_tuple in files:
806
- assert part_name == 'creds_file'
807
- assert file_tuple[0] is not None
808
-
809
-
810
- def test_json():
811
- service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator())
812
- req = service.prepare_request('POST', url='', headers={
813
- 'X-opt-out': True}, data={'hello': 'world', 'fóó': 'bår'})
814
- assert req.get(
815
- 'data') == b'{"hello": "world", "f\\u00f3\\u00f3": "b\\u00e5r"}'
816
-
817
-
818
- def test_service_url_handling():
819
- service = AnyServiceV1(
820
- '2018-11-20', service_url='https://host///////', authenticator=NoAuthAuthenticator())
821
- assert service.service_url == 'https://host'
822
-
823
- service.set_service_url('https://host/')
824
- assert service.service_url == 'https://host'
825
-
826
- req = service.prepare_request('POST',
827
- url='/path/',
828
- headers={'X-opt-out': True},
829
- data={'hello': 'world'})
830
- assert req.get('url') == 'https://host/path/'
831
-
832
- service = AnyServiceV1(
833
- '2018-11-20', service_url='https://host/', authenticator=NoAuthAuthenticator())
834
- assert service.service_url == 'https://host'
835
-
836
- service.set_service_url('https://host/')
837
- assert service.service_url == 'https://host'
838
-
839
- req = service.prepare_request('POST',
840
- url='/',
841
- headers={'X-opt-out': True},
842
- data={'hello': 'world'})
843
- assert req.get('url') == 'https://host/'
844
-
845
- req = service.prepare_request('POST',
846
- url='////',
847
- headers={'X-opt-out': True},
848
- data={'hello': 'world'})
849
- assert req.get('url') == 'https://host/'
850
-
851
- service.set_service_url(None)
852
- assert service.service_url is None
853
-
854
- service = AnyServiceV1('2018-11-20', service_url='/',
855
- authenticator=NoAuthAuthenticator())
856
- assert service.service_url == ''
857
-
858
- service.set_service_url('/')
859
- assert service.service_url == ''
860
-
861
- with pytest.raises(ValueError) as err:
862
- service.prepare_request('POST',
863
- url='/',
864
- headers={'X-opt-out': True},
865
- data={'hello': 'world'})
866
- assert str(err.value) == 'The service_url is required'
867
-
868
-
869
- def test_service_url_slash():
870
- service = AnyServiceV1('2018-11-20', service_url='/',
871
- authenticator=NoAuthAuthenticator())
872
- assert service.service_url == ''
873
- with pytest.raises(ValueError) as err:
874
- service.prepare_request('POST',
875
- url='/',
876
- headers={'X-opt-out': True},
877
- data={'hello': 'world'})
878
- assert str(err.value) == 'The service_url is required'
879
-
880
-
881
- def test_service_url_not_set():
882
- service = BaseService(service_url='', authenticator=NoAuthAuthenticator())
883
- with pytest.raises(ValueError) as err:
884
- service.prepare_request('POST', url='')
885
- assert str(err.value) == 'The service_url is required'
886
-
887
-
888
- def test_setting_proxy():
889
- service = BaseService(service_url='test',
890
- authenticator=IAMAuthenticator('wonder woman'))
891
- assert service.authenticator is not None
892
- assert service.authenticator.token_manager.http_config == {}
893
-
894
- http_config = {
895
- "proxies": {
896
- "http": "user:password@host:port"
897
- }
898
- }
899
- service.set_http_config(http_config)
900
- assert service.authenticator.token_manager.http_config == http_config
901
-
902
- service2 = BaseService(service_url='test', authenticator=BasicAuthenticator(
903
- 'marvellous', 'mrs maisel'))
904
- service2.set_http_config(http_config)
905
- assert service2.authenticator is not None
906
-
907
-
908
- def test_configure_service():
909
- file_path = os.path.join(
910
- os.path.dirname(__file__), '../resources/ibm-credentials-external.env')
911
- os.environ['IBM_CREDENTIALS_FILE'] = file_path
912
- service = IncludeExternalConfigService(
913
- 'v1', authenticator=NoAuthAuthenticator())
914
- assert service.service_url == 'https://externallyconfigured.com/api'
915
- assert service.disable_ssl_verification is True
916
- # The authenticator should not be changed as a result of configure_service()
917
- assert isinstance(service.get_authenticator(), NoAuthAuthenticator)
918
-
919
-
920
- def test_configure_service_error():
921
- service = BaseService(
922
- service_url='v1', authenticator=NoAuthAuthenticator())
923
- with pytest.raises(ValueError) as err:
924
- service.configure_service(None)
925
- assert str(err.value) == 'Service_name must be of type string.'
926
-
927
-
928
- def test_min_ssl_version():
929
- service = AnyServiceV1('2022-03-08', authenticator=NoAuthAuthenticator())
930
- adapter = service.http_client.get_adapter('https://')
931
- ssl_context = adapter.poolmanager.connection_pool_kw.get('ssl_context', None)
932
- assert ssl_context is not None
933
- assert ssl_context.minimum_version == ssl.TLSVersion.TLSv1_2