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
test/test_base_service.py DELETED
@@ -1,925 +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 tempfile
7
- import time
8
- from shutil import copyfile
9
- from typing import Optional
10
- from urllib3.exceptions import ConnectTimeoutError, MaxRetryError
11
-
12
- import jwt
13
- import pytest
14
- import responses
15
- import requests
16
-
17
- from ibm_cloud_sdk_core import ApiException
18
- from ibm_cloud_sdk_core import BaseService, DetailedResponse
19
- from ibm_cloud_sdk_core import CP4DTokenManager
20
- from ibm_cloud_sdk_core import get_authenticator_from_environment
21
- from ibm_cloud_sdk_core.authenticators import (IAMAuthenticator, NoAuthAuthenticator, Authenticator,
22
- BasicAuthenticator, CloudPakForDataAuthenticator)
23
-
24
-
25
- class IncludeExternalConfigService(BaseService):
26
- default_service_url = 'https://servicesthatincludeexternalconfig.com/api'
27
-
28
- def __init__(
29
- self,
30
- api_version: str,
31
- authenticator: Optional[Authenticator] = None,
32
- trace_id: Optional[str] = None
33
- ) -> None:
34
- BaseService.__init__(
35
- self,
36
- service_url=self.default_service_url,
37
- authenticator=authenticator,
38
- disable_ssl_verification=False
39
- )
40
- self.api_version = api_version
41
- self.trace_id = trace_id
42
- self.configure_service('include-external-config')
43
-
44
-
45
- class AnyServiceV1(BaseService):
46
- default_url = 'https://gateway.watsonplatform.net/test/api'
47
-
48
- def __init__(
49
- self,
50
- version: str,
51
- service_url: str = default_url,
52
- authenticator: Optional[Authenticator] = None,
53
- disable_ssl_verification: bool = False
54
- ) -> None:
55
- BaseService.__init__(
56
- self,
57
- service_url=service_url,
58
- authenticator=authenticator,
59
- disable_ssl_verification=disable_ssl_verification)
60
- self.version = version
61
-
62
- def op_with_path_params(self, path0: str, path1: str) -> DetailedResponse:
63
- if path0 is None:
64
- raise ValueError('path0 must be provided')
65
- if path1 is None:
66
- raise ValueError('path1 must be provided')
67
- params = {'version': self.version}
68
- url = '/v1/foo/{0}/bar/{1}/baz'.format(
69
- *self._encode_path_vars(path0, path1))
70
- request = self.prepare_request(method='GET', url=url, params=params)
71
- response = self.send(request)
72
- return response
73
-
74
- def with_http_config(self, http_config: dict) -> DetailedResponse:
75
- self.set_http_config(http_config)
76
- request = self.prepare_request(method='GET', url='')
77
- response = self.send(request)
78
- return response
79
-
80
- def any_service_call(self) -> DetailedResponse:
81
- request = self.prepare_request(method='GET', url='')
82
- response = self.send(request)
83
- return response
84
-
85
- def head_request(self) -> DetailedResponse:
86
- request = self.prepare_request(method='HEAD', url='')
87
- response = self.send(request)
88
- return response
89
-
90
- def get_document_as_stream(self) -> DetailedResponse:
91
- params = {'version': self.version}
92
- url = '/v1/streamjson'
93
- request = self.prepare_request(method='GET', url=url, params=params)
94
- response = self.send(request, stream=True)
95
- return response
96
-
97
-
98
- def get_access_token() -> str:
99
- access_token_layout = {
100
- "username": "dummy",
101
- "role": "Admin",
102
- "permissions": ["administrator", "manage_catalog"],
103
- "sub": "admin",
104
- "iss": "sss",
105
- "aud": "sss",
106
- "uid": "sss",
107
- "iat": 3600,
108
- "exp": int(time.time())
109
- }
110
-
111
- access_token = jwt.encode(
112
- access_token_layout,
113
- 'secret',
114
- algorithm='HS256',
115
- headers={
116
- 'kid': '230498151c214b788dd97f22b85410a5'
117
- })
118
- return access_token
119
-
120
-
121
- def test_invalid_authenticator():
122
- with pytest.raises(ValueError) as err:
123
- AnyServiceV1('2021-08-18')
124
-
125
- assert str(err.value) == 'authenticator must be provided'
126
-
127
-
128
- @responses.activate
129
- def test_url_encoding():
130
- service = AnyServiceV1('2017-07-07', authenticator=NoAuthAuthenticator())
131
-
132
- # All characters in path0 _must_ be encoded in path segments
133
- path0 = ' \"<>^`{}|/\\?#%[]'
134
- path0_encoded = '%20%22%3C%3E%5E%60%7B%7D%7C%2F%5C%3F%23%25%5B%5D'
135
- # All non-ASCII chars _must_ be encoded in path segments
136
- path1 = '比萨浇头'.encode('utf8') # "pizza toppings"
137
- path1_encoded = '%E6%AF%94%E8%90%A8%E6%B5%87%E5%A4%B4'
138
-
139
- path_encoded = '/v1/foo/' + path0_encoded + '/bar/' + path1_encoded + '/baz'
140
- test_url = service.default_url + path_encoded
141
-
142
- responses.add(
143
- responses.GET,
144
- test_url,
145
- status=200,
146
- body=json.dumps({
147
- "foobar": "baz"
148
- }),
149
- content_type='application/json')
150
-
151
- # Set Host as a default header on the service.
152
- service.set_default_headers({'Host': 'alternatehost.ibm.com:443'})
153
-
154
- response = service.op_with_path_params(path0, path1)
155
-
156
- assert response is not None
157
- assert len(responses.calls) == 1
158
- assert path_encoded in responses.calls[0].request.url
159
- assert 'version=2017-07-07' in responses.calls[0].request.url
160
-
161
- # Verify that the Host header was set in the request.
162
- assert responses.calls[0].request.headers.get(
163
- 'Host') == 'alternatehost.ibm.com:443'
164
-
165
-
166
- @responses.activate
167
- def test_stream_json_response():
168
- service = AnyServiceV1('2017-07-07', authenticator=NoAuthAuthenticator())
169
-
170
- path = '/v1/streamjson'
171
- test_url = service.default_url + path
172
-
173
- expected_response = json.dumps(
174
- {"id": 1, "rev": "v1", "content": "this is a document"})
175
-
176
- # print("Expected response: ", expected_response)
177
-
178
- # Simulate a JSON response
179
- responses.add(
180
- responses.GET,
181
- test_url,
182
- status=200,
183
- body=expected_response,
184
- content_type='application/json')
185
-
186
- # Invoke the operation and receive an "iterable" as the response
187
- response = service.get_document_as_stream()
188
-
189
- assert response is not None
190
- assert len(responses.calls) == 1
191
-
192
- # retrieve the requests.Response object from the DetailedResponse
193
- resp = response.get_result()
194
- assert isinstance(resp, requests.Response)
195
- assert hasattr(resp, "iter_content")
196
-
197
- # Retrieve the response body, one chunk at a time.
198
- actual_response = ''
199
- for chunk in resp.iter_content(chunk_size=3):
200
- actual_response += chunk.decode("utf-8")
201
-
202
- # print("Actual response: ", actual_response)
203
- assert actual_response == expected_response
204
-
205
-
206
- @responses.activate
207
- def test_http_config():
208
- service = AnyServiceV1('2017-07-07', authenticator=NoAuthAuthenticator())
209
- responses.add(
210
- responses.GET,
211
- service.default_url,
212
- status=200,
213
- body=json.dumps({
214
- "foobar": "baz"
215
- }),
216
- content_type='application/json')
217
-
218
- response = service.with_http_config({'timeout': 100})
219
- assert response is not None
220
- assert len(responses.calls) == 1
221
-
222
-
223
- def test_fail_http_config():
224
- service = AnyServiceV1('2017-07-07', authenticator=NoAuthAuthenticator())
225
- with pytest.raises(TypeError):
226
- service.with_http_config(None)
227
-
228
-
229
- @responses.activate
230
- def test_cwd():
231
- file_path = os.path.join(
232
- os.path.dirname(__file__), '../resources/ibm-credentials.env')
233
- # Try changing working directories to test getting creds from cwd
234
- cwd = os.getcwd()
235
- os.chdir(os.path.dirname(file_path))
236
- iam_authenticator = get_authenticator_from_environment('ibm_watson')
237
- service = AnyServiceV1('2017-07-07', authenticator=iam_authenticator)
238
- service.configure_service('ibm_watson')
239
- os.chdir(cwd)
240
- assert service.service_url == 'https://cwdserviceurl'
241
- assert service.authenticator is not None
242
-
243
- # Copy credentials file to cwd to test loading from current working directory
244
- temp_env_path = os.getcwd() + '/ibm-credentials.env'
245
- copyfile(file_path, temp_env_path)
246
- iam_authenticator = get_authenticator_from_environment('ibm_watson')
247
- service = AnyServiceV1('2017-07-07', authenticator=iam_authenticator)
248
- service.configure_service('ibm_watson')
249
- os.remove(temp_env_path)
250
- assert service.service_url == 'https://cwdserviceurl'
251
- assert service.authenticator is not None
252
-
253
-
254
- @responses.activate
255
- def test_iam():
256
- file_path = os.path.join(
257
- os.path.dirname(__file__), '../resources/ibm-credentials-iam.env')
258
- os.environ['IBM_CREDENTIALS_FILE'] = file_path
259
- iam_authenticator = get_authenticator_from_environment('ibm-watson')
260
- service = AnyServiceV1('2017-07-07', authenticator=iam_authenticator)
261
- assert service.service_url == 'https://gateway.watsonplatform.net/test/api'
262
- del os.environ['IBM_CREDENTIALS_FILE']
263
- assert service.authenticator is not None
264
-
265
- response = {
266
- "access_token": get_access_token(),
267
- "token_type": "Bearer",
268
- "expires_in": 3600,
269
- "expiration": int(time.time()),
270
- "refresh_token": "jy4gl91BQ"
271
- }
272
- responses.add(
273
- responses.POST,
274
- url='https://iam.cloud.ibm.com/identity/token',
275
- body=json.dumps(response),
276
- status=200)
277
- responses.add(
278
- responses.GET,
279
- url='https://gateway.watsonplatform.net/test/api',
280
- body=json.dumps({
281
- "foobar": "baz"
282
- }),
283
- content_type='application/json')
284
- service.any_service_call()
285
- assert "grant-type%3Aapikey" in responses.calls[0].request.body
286
-
287
-
288
- def test_no_auth():
289
- class MadeUp:
290
- def __init__(self):
291
- self.lazy = 'made up'
292
-
293
- with pytest.raises(ValueError) as err:
294
- service = AnyServiceV1('2017-07-07', authenticator=MadeUp())
295
- service.prepare_request(
296
- responses.GET,
297
- url='https://gateway.watsonplatform.net/test/api',
298
- )
299
- assert str(err.value) == 'authenticator should be of type Authenticator'
300
-
301
- service = AnyServiceV1('2017-07-07', authenticator=NoAuthAuthenticator())
302
- service.prepare_request(
303
- responses.GET,
304
- url='https://gateway.watsonplatform.net/test/api',
305
- )
306
- assert service.authenticator is not None
307
- assert isinstance(service.authenticator, Authenticator)
308
-
309
-
310
- def test_for_cp4d():
311
- cp4d_authenticator = CloudPakForDataAuthenticator('my_username', 'my_password',
312
- 'my_url')
313
- service = AnyServiceV1('2017-07-07', authenticator=cp4d_authenticator)
314
- assert service.authenticator.token_manager is not None
315
- assert service.authenticator.token_manager.username == 'my_username'
316
- assert service.authenticator.token_manager.password == 'my_password'
317
- assert service.authenticator.token_manager.url == 'my_url/v1/authorize'
318
- assert isinstance(service.authenticator.token_manager, CP4DTokenManager)
319
-
320
-
321
- def test_disable_ssl_verification():
322
- service1 = AnyServiceV1(
323
- '2017-07-07', authenticator=NoAuthAuthenticator(), disable_ssl_verification=True)
324
- assert service1.disable_ssl_verification is True
325
-
326
- service1.set_disable_ssl_verification(False)
327
- assert service1.disable_ssl_verification is False
328
-
329
- cp4d_authenticator = CloudPakForDataAuthenticator('my_username', 'my_password',
330
- 'my_url')
331
- service2 = AnyServiceV1('2017-07-07', authenticator=cp4d_authenticator)
332
- assert service2.disable_ssl_verification is False
333
- cp4d_authenticator.set_disable_ssl_verification(True)
334
- assert service2.authenticator.token_manager.disable_ssl_verification is True
335
-
336
-
337
- @responses.activate
338
- def test_http_head():
339
- service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator())
340
- expected_headers = {'Test-Header1': 'value1', 'Test-Header2': 'value2'}
341
- responses.add(
342
- responses.HEAD,
343
- service.default_url,
344
- status=200,
345
- headers=expected_headers,
346
- content_type=None)
347
-
348
- response = service.head_request()
349
- assert response is not None
350
- assert len(responses.calls) == 1
351
- assert response.headers is not None
352
- assert response.headers == expected_headers
353
-
354
-
355
- @responses.activate
356
- def test_response_with_no_body():
357
- service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator())
358
- responses.add(responses.GET, service.default_url, status=200, body=None)
359
-
360
- response = service.any_service_call()
361
- assert response is not None
362
- assert len(responses.calls) == 1
363
- assert response.get_result() is None
364
-
365
-
366
- def test_has_bad_first_or_last_char():
367
- with pytest.raises(ValueError) as err:
368
- basic_authenticator = BasicAuthenticator(
369
- '{my_username}', 'my_password')
370
- AnyServiceV1('2018-11-20', authenticator=basic_authenticator).prepare_request(
371
- responses.GET,
372
- 'https://gateway.watsonplatform.net/test/api'
373
- )
374
- assert str(
375
- err.value
376
- ) == 'The username and password shouldn\'t start or end with curly brackets or quotes. '\
377
- 'Please remove any surrounding {, }, or \" characters.'
378
-
379
-
380
- @responses.activate
381
- def test_request_server_error():
382
- responses.add(
383
- responses.GET,
384
- 'https://gateway.watsonplatform.net/test/api',
385
- status=500,
386
- body=json.dumps({
387
- 'error': 'internal server error'
388
- }),
389
- content_type='application/json')
390
- service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator())
391
- try:
392
- prepped = service.prepare_request('GET', url='')
393
- service.send(prepped)
394
- except ApiException as err:
395
- assert err.message == 'internal server error'
396
-
397
-
398
- @responses.activate
399
- def test_request_success_json():
400
- responses.add(
401
- responses.GET,
402
- 'https://gateway.watsonplatform.net/test/api',
403
- status=200,
404
- body=json.dumps({
405
- 'foo': 'bar'
406
- }),
407
- content_type='application/json')
408
- service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator())
409
- prepped = service.prepare_request('GET', url='')
410
- detailed_response = service.send(prepped)
411
- assert detailed_response.get_result() == {'foo': 'bar'}
412
-
413
- service = AnyServiceV1(
414
- '2018-11-20', authenticator=BasicAuthenticator('my_username', 'my_password'))
415
- service.set_default_headers({'test': 'header'})
416
- service.set_disable_ssl_verification(True)
417
- prepped = service.prepare_request('GET', url='')
418
- detailed_response = service.send(prepped)
419
- assert detailed_response.get_result() == {'foo': 'bar'}
420
-
421
-
422
- @responses.activate
423
- def test_request_success_response():
424
- responses.add(
425
- responses.GET,
426
- 'https://gateway.watsonplatform.net/test/api',
427
- status=200,
428
- body=json.dumps({
429
- 'foo': 'bar'
430
- }),
431
- content_type='application/json')
432
- service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator())
433
- prepped = service.prepare_request('GET', url='')
434
- detailed_response = service.send(prepped)
435
- assert detailed_response.get_result() == {"foo": "bar"}
436
-
437
-
438
- @responses.activate
439
- def test_request_fail_401():
440
- responses.add(
441
- responses.GET,
442
- 'https://gateway.watsonplatform.net/test/api',
443
- status=401,
444
- body=json.dumps({
445
- 'foo': 'bar'
446
- }),
447
- content_type='application/json')
448
- service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator())
449
- try:
450
- prepped = service.prepare_request('GET', url='')
451
- service.send(prepped)
452
- except ApiException as err:
453
- assert err.message == 'Unauthorized: Access is denied due to invalid credentials'
454
-
455
-
456
- def test_misc_methods():
457
-
458
- class MockModel:
459
-
460
- def __init__(self, xyz=None):
461
- self.xyz = xyz
462
-
463
- def _to_dict(self):
464
- _dict = {}
465
- if hasattr(self, 'xyz') and self.xyz is not None:
466
- _dict['xyz'] = self.xyz
467
- return _dict
468
-
469
- @classmethod
470
- def _from_dict(cls, _dict):
471
- args = {}
472
- if 'xyz' in _dict:
473
- args['xyz'] = _dict.get('xyz')
474
- return cls(**args)
475
-
476
- mock = MockModel('foo')
477
- service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator())
478
- model1 = service._convert_model(mock)
479
- assert model1 == {'xyz': 'foo'}
480
-
481
- model2 = service._convert_model("{\"xyz\": \"foo\"}")
482
- assert model2 is not None
483
- assert model2['xyz'] == 'foo'
484
-
485
- temp = ['default', '123']
486
- res_str = service._convert_list(temp)
487
- assert res_str == 'default,123'
488
-
489
- temp2 = 'default123'
490
- res_str2 = service._convert_list(temp2)
491
- assert res_str2 == temp2
492
-
493
-
494
- def test_default_headers():
495
- service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator())
496
- service.set_default_headers({'xxx': 'yyy'})
497
- assert service.default_headers == {'xxx': 'yyy'}
498
- with pytest.raises(TypeError):
499
- service.set_default_headers('xxx')
500
-
501
-
502
- def test_set_service_url():
503
- service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator())
504
- with pytest.raises(ValueError) as err:
505
- service.set_service_url('{url}')
506
- assert str(err.value) == 'The service url shouldn\'t start or end with curly brackets or quotes. '\
507
- 'Be sure to remove any {} and \" characters surrounding your service url'
508
-
509
- service.set_service_url('my_url')
510
-
511
-
512
- def test_http_client():
513
- auth = BasicAuthenticator('my_username', 'my_password')
514
- service = AnyServiceV1('2018-11-20', authenticator=auth)
515
- assert isinstance(service.get_http_client(), requests.sessions.Session)
516
- assert service.get_http_client().headers.get(
517
- 'Accept-Encoding') == 'gzip, deflate'
518
-
519
- new_http_client = requests.Session()
520
- new_http_client.headers.update({'Accept-Encoding': 'gzip'})
521
- service.set_http_client(http_client=new_http_client)
522
- assert service.get_http_client().headers.get('Accept-Encoding') == 'gzip'
523
-
524
- with pytest.raises(TypeError):
525
- service.set_http_client("bad_argument_type")
526
-
527
-
528
- def test_get_authenticator():
529
- auth = BasicAuthenticator('my_username', 'my_password')
530
- service = AnyServiceV1('2018-11-20', authenticator=auth)
531
- assert service.get_authenticator() is not None
532
-
533
-
534
- def test_gzip_compression():
535
- # Should return uncompressed data when gzip is off
536
- service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator())
537
- assert not service.get_enable_gzip_compression()
538
- prepped = service.prepare_request(
539
- 'GET', url='', data=json.dumps({"foo": "bar"}))
540
- assert prepped['data'] == b'{"foo": "bar"}'
541
- assert prepped['headers'].get('content-encoding') != 'gzip'
542
-
543
- # Should return compressed data when gzip is on
544
- service.set_enable_gzip_compression(True)
545
- assert service.get_enable_gzip_compression()
546
- prepped = service.prepare_request(
547
- 'GET', url='', data=json.dumps({"foo": "bar"}))
548
- assert prepped['data'] == gzip.compress(b'{"foo": "bar"}')
549
- assert prepped['headers'].get('content-encoding') == 'gzip'
550
-
551
- # Should return compressed data when gzip is on for non-json data
552
- assert service.get_enable_gzip_compression()
553
- prepped = service.prepare_request('GET', url='', data=b'rawdata')
554
- assert prepped['data'] == gzip.compress(b'rawdata')
555
- assert prepped['headers'].get('content-encoding') == 'gzip'
556
-
557
- # Should return compressed data when gzip is on for gzip file data
558
- assert service.get_enable_gzip_compression()
559
- with tempfile.TemporaryFile(mode='w+b') as t_f:
560
- with gzip.GzipFile(mode='wb', fileobj=t_f) as gz_f:
561
- gz_f.write(json.dumps({"foo": "bar"}).encode())
562
- with gzip.GzipFile(mode='rb', fileobj=t_f) as gz_f:
563
- gzip_data = gz_f.read()
564
- prepped = service.prepare_request('GET', url='', data=gzip_data)
565
- assert prepped['data'] == gzip.compress(t_f.read())
566
- assert prepped['headers'].get('content-encoding') == 'gzip'
567
-
568
- # Should return compressed json data when gzip is on for gzip file json data
569
- assert service.get_enable_gzip_compression()
570
- with tempfile.TemporaryFile(mode='w+b') as t_f:
571
- with gzip.GzipFile(mode='wb', fileobj=t_f) as gz_f:
572
- gz_f.write("rawdata".encode())
573
- with gzip.GzipFile(mode='rb', fileobj=t_f) as gz_f:
574
- gzip_data = gz_f.read()
575
- prepped = service.prepare_request('GET', url='', data=gzip_data)
576
- assert prepped['data'] == gzip.compress(t_f.read())
577
- assert prepped['headers'].get('content-encoding') == 'gzip'
578
-
579
- # Should return uncompressed data when content-encoding is set
580
- assert service.get_enable_gzip_compression()
581
- prepped = service.prepare_request('GET', url='', headers={"content-encoding": "gzip"},
582
- data=json.dumps({"foo": "bar"}))
583
- assert prepped['data'] == b'{"foo": "bar"}'
584
- assert prepped['headers'].get('content-encoding') == 'gzip'
585
-
586
-
587
- def test_gzip_compression_external():
588
- # Should set gzip compression from external config
589
- file_path = os.path.join(
590
- os.path.dirname(__file__), '../resources/ibm-credentials-gzip.env')
591
- os.environ['IBM_CREDENTIALS_FILE'] = file_path
592
- service = IncludeExternalConfigService(
593
- 'v1', authenticator=NoAuthAuthenticator())
594
- assert service.service_url == 'https://mockurl'
595
- assert service.get_enable_gzip_compression() is True
596
- prepped = service.prepare_request(
597
- 'GET', url='', data=json.dumps({"foo": "bar"}))
598
- assert prepped['data'] == gzip.compress(b'{"foo": "bar"}')
599
- assert prepped['headers'].get('content-encoding') == 'gzip'
600
-
601
-
602
- def test_retry_config_default():
603
- service = BaseService(service_url='https://mockurl/',
604
- authenticator=NoAuthAuthenticator())
605
- service.enable_retries()
606
- assert service.retry_config.total == 4
607
- assert service.retry_config.backoff_factor == 1.0
608
- assert service.http_client.get_adapter('https://').max_retries.total == 4
609
-
610
- # Ensure retries fail after 4 retries
611
- error = ConnectTimeoutError()
612
- retry = service.http_client.get_adapter('https://').max_retries
613
- retry = retry.increment(error=error)
614
- retry = retry.increment(error=error)
615
- retry = retry.increment(error=error)
616
- retry = retry.increment(error=error)
617
- with pytest.raises(MaxRetryError) as retry_err:
618
- retry.increment(error=error)
619
- assert retry_err.value.reason == error
620
-
621
-
622
- def test_retry_config_disable():
623
- # Test disabling retries
624
- service = BaseService(service_url='https://mockurl/',
625
- authenticator=NoAuthAuthenticator())
626
- service.enable_retries()
627
- service.disable_retries()
628
- assert service.retry_config is None
629
- assert service.http_client.get_adapter('https://').max_retries.total == 0
630
-
631
- # Ensure retries are not started after one connection attempt
632
- error = ConnectTimeoutError()
633
- retry = service.http_client.get_adapter('https://').max_retries
634
- with pytest.raises(MaxRetryError) as retry_err:
635
- retry.increment(error=error)
636
- assert retry_err.value.reason == error
637
-
638
-
639
- def test_retry_config_non_default():
640
- service = BaseService(service_url='https://mockurl/',
641
- authenticator=NoAuthAuthenticator())
642
- service.enable_retries(2, 0.3)
643
- assert service.retry_config.total == 2
644
- assert service.retry_config.backoff_factor == 0.3
645
-
646
- # Ensure retries fail after 2 retries
647
- error = ConnectTimeoutError()
648
- retry = service.http_client.get_adapter('https://').max_retries
649
- retry = retry.increment(error=error)
650
- retry = retry.increment(error=error)
651
- with pytest.raises(MaxRetryError) as retry_err:
652
- retry.increment(error=error)
653
- assert retry_err.value.reason == error
654
-
655
-
656
- def test_retry_config_external():
657
- file_path = os.path.join(
658
- os.path.dirname(__file__), '../resources/ibm-credentials-retry.env')
659
- os.environ['IBM_CREDENTIALS_FILE'] = file_path
660
- service = IncludeExternalConfigService(
661
- 'v1', authenticator=NoAuthAuthenticator())
662
- assert service.retry_config.total == 3
663
- assert service.retry_config.backoff_factor == 0.2
664
-
665
- # Ensure retries fail after 3 retries
666
- error = ConnectTimeoutError()
667
- retry = service.http_client.get_adapter('https://').max_retries
668
- retry = retry.increment(error=error)
669
- retry = retry.increment(error=error)
670
- retry = retry.increment(error=error)
671
- with pytest.raises(MaxRetryError) as retry_err:
672
- retry.increment(error=error)
673
- assert retry_err.value.reason == error
674
-
675
-
676
- @responses.activate
677
- def test_user_agent_header():
678
- service = AnyServiceV1('2018-11-20', authenticator=NoAuthAuthenticator())
679
- user_agent_header = service.user_agent_header
680
- assert user_agent_header is not None
681
- assert user_agent_header['User-Agent'] is not None
682
-
683
- responses.add(
684
- responses.GET,
685
- 'https://gateway.watsonplatform.net/test/api',
686
- status=200,
687
- body='some text')
688
- prepped = service.prepare_request('GET', url='', headers={
689
- 'user-agent': 'my_user_agent'
690
- })
691
- response = service.send(prepped)
692
- assert response.get_result().request.headers.__getitem__(
693
- 'user-agent') == 'my_user_agent'
694
-
695
- prepped = service.prepare_request('GET', url='', headers=None)
696
- response = service.send(prepped)
697
- assert response.get_result().request.headers.__getitem__(
698
- 'user-agent') == user_agent_header['User-Agent']
699
-
700
-
701
- @responses.activate
702
- def test_reserved_keys(caplog):
703
- service = AnyServiceV1('2021-07-02', authenticator=NoAuthAuthenticator())
704
- responses.add(
705
- responses.GET,
706
- 'https://gateway.watsonplatform.net/test/api',
707
- status=200,
708
- body='some text')
709
- prepped = service.prepare_request('GET', url='', headers={'key': 'OK'})
710
- response = service.send(
711
- prepped,
712
- headers={'key': 'bad'},
713
- method='POST',
714
- url='localhost',
715
- cookies=None,
716
- hooks={'response': []})
717
- assert response.get_result().request.headers.__getitem__('key') == 'OK'
718
- assert response.get_result().request.url == 'https://gateway.watsonplatform.net/test/api'
719
- assert response.get_result().request.method == 'GET'
720
- assert response.get_result().request.hooks == {'response': []}
721
- assert caplog.record_tuples[0][2] == '"method" has been removed from the request'
722
- assert caplog.record_tuples[1][2] == '"url" has been removed from the request'
723
- assert caplog.record_tuples[2][2] == '"headers" has been removed from the request'
724
- assert caplog.record_tuples[3][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.'