ibm-appconfiguration-python-sdk 0.3.7__tar.gz → 0.3.9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ibm_appconfiguration_python_sdk-0.3.7/ibm_appconfiguration_python_sdk.egg-info → ibm_appconfiguration_python_sdk-0.3.9}/PKG-INFO +15 -11
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/README.md +1 -9
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/appconfiguration.py +9 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/configuration_handler.py +34 -10
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/utils/file_manager.py +7 -8
- ibm_appconfiguration_python_sdk-0.3.9/ibm_appconfiguration/configurations/internal/utils/parser.py +149 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/utils/url_builder.py +2 -2
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/models/rule.py +31 -7
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/version.py +1 -1
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9/ibm_appconfiguration_python_sdk.egg-info}/PKG-INFO +15 -11
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration_python_sdk.egg-info/SOURCES.txt +1 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/setup.py +1 -1
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/models/test_rule.py +55 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/test_configuration_handler.py +87 -68
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/utils/test_api_manager.py +14 -3
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/utils/test_file_manager.py +2 -2
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/utils/test_url_builder.py +4 -4
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/test_appconfiguration.py +1 -1
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/LICENSE +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/examples/__init__.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/examples/sample_app.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/examples/server_sample.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/__init__.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/__init__.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/__init__.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/common/__init__.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/common/config_constants.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/common/config_messages.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/utils/__init__.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/utils/api_manager.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/utils/compute_percentage.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/utils/connectivity.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/utils/logger.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/utils/metering.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/utils/socket.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/utils/validators.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/models/__init__.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/models/configuration_type.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/models/feature.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/models/property.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/models/segment.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/models/segment_rules.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration_python_sdk.egg-info/dependency_links.txt +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration_python_sdk.egg-info/requires.txt +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration_python_sdk.egg-info/top_level.txt +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/integration_tests/__init__.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/integration_tests/test_integration.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/setup.cfg +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/__init__.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/__init__.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/models/__init__.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/models/test_feature.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/models/test_property.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/models/test_segment.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/models/test_segment_rules.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/utils/__init__.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/utils/test_metering.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/utils/test_socket.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: ibm-appconfiguration-python-sdk
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.9
|
|
4
4
|
Summary: IBM Cloud App Configuration Python SDK
|
|
5
5
|
Home-page: https://github.com/IBM/appconfiguration-python-sdk
|
|
6
6
|
Author: IBM
|
|
@@ -21,6 +21,18 @@ Requires-Dist: ibm-cloud-sdk-core<4.0.0,>=3.20.3
|
|
|
21
21
|
Requires-Dist: pyyaml>=5.4.1
|
|
22
22
|
Requires-Dist: schema>=0.7.5
|
|
23
23
|
Requires-Dist: mmh3==5.0.1
|
|
24
|
+
Dynamic: author
|
|
25
|
+
Dynamic: author-email
|
|
26
|
+
Dynamic: classifier
|
|
27
|
+
Dynamic: description
|
|
28
|
+
Dynamic: description-content-type
|
|
29
|
+
Dynamic: home-page
|
|
30
|
+
Dynamic: keywords
|
|
31
|
+
Dynamic: license
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
Dynamic: requires-dist
|
|
34
|
+
Dynamic: requires-python
|
|
35
|
+
Dynamic: summary
|
|
24
36
|
|
|
25
37
|
# IBM Cloud App Configuration Python server SDK
|
|
26
38
|
|
|
@@ -74,15 +86,7 @@ The **`init()`** and **`set_context()`** are the initialisation methods and shou
|
|
|
74
86
|
appconfig_client. The appconfig_client, once initialised, can be obtained across modules
|
|
75
87
|
using **`AppConfiguration.get_instance()`**. [See this example below](#fetching-the-appconfig_client-across-other-modules).
|
|
76
88
|
|
|
77
|
-
- region : Region name where the App Configuration service instance is created.
|
|
78
|
-
- `AppConfiguration.REGION_US_SOUTH` for Dallas
|
|
79
|
-
- `AppConfiguration.REGION_EU_GB` for London
|
|
80
|
-
- `AppConfiguration.REGION_AU_SYD` for Sydney
|
|
81
|
-
- `AppConfiguration.REGION_US_EAST` for Washington DC
|
|
82
|
-
- `AppConfiguration.REGION_EU_DE` for Frankfurt
|
|
83
|
-
- `AppConfiguration.REGION_CA_TOR` for Toronto
|
|
84
|
-
- `AppConfiguration.REGION_JP_TOK` for Tokyo
|
|
85
|
-
- `AppConfiguration.REGION_JP_OSA` for Osaka
|
|
89
|
+
- region : Region name where the App Configuration service instance is created. See list of supported locations [here](https://cloud.ibm.com/catalog/services/app-configuration). Eg:- `us-south`, `au-syd` etc.
|
|
86
90
|
- guid : GUID of the App Configuration service. Obtain it from the service credentials section of the dashboard
|
|
87
91
|
- apikey : ApiKey of the App Configuration service. Obtain it from the service credentials section of the dashboard
|
|
88
92
|
- collection_id : Id of the collection created in App Configuration service instance under the **Collections** section.
|
|
@@ -50,15 +50,7 @@ The **`init()`** and **`set_context()`** are the initialisation methods and shou
|
|
|
50
50
|
appconfig_client. The appconfig_client, once initialised, can be obtained across modules
|
|
51
51
|
using **`AppConfiguration.get_instance()`**. [See this example below](#fetching-the-appconfig_client-across-other-modules).
|
|
52
52
|
|
|
53
|
-
- region : Region name where the App Configuration service instance is created.
|
|
54
|
-
- `AppConfiguration.REGION_US_SOUTH` for Dallas
|
|
55
|
-
- `AppConfiguration.REGION_EU_GB` for London
|
|
56
|
-
- `AppConfiguration.REGION_AU_SYD` for Sydney
|
|
57
|
-
- `AppConfiguration.REGION_US_EAST` for Washington DC
|
|
58
|
-
- `AppConfiguration.REGION_EU_DE` for Frankfurt
|
|
59
|
-
- `AppConfiguration.REGION_CA_TOR` for Toronto
|
|
60
|
-
- `AppConfiguration.REGION_JP_TOK` for Tokyo
|
|
61
|
-
- `AppConfiguration.REGION_JP_OSA` for Osaka
|
|
53
|
+
- region : Region name where the App Configuration service instance is created. See list of supported locations [here](https://cloud.ibm.com/catalog/services/app-configuration). Eg:- `us-south`, `au-syd` etc.
|
|
62
54
|
- guid : GUID of the App Configuration service. Obtain it from the service credentials section of the dashboard
|
|
63
55
|
- apikey : ApiKey of the App Configuration service. Obtain it from the service credentials section of the dashboard
|
|
64
56
|
- collection_id : Id of the collection created in App Configuration service instance under the **Collections** section.
|
|
@@ -298,3 +298,12 @@ class AppConfiguration:
|
|
|
298
298
|
return self.__configuration_handler_instance.get_property(property_id)
|
|
299
299
|
Logger.error(config_messages.COLLECTION_INIT_ERROR)
|
|
300
300
|
return None
|
|
301
|
+
|
|
302
|
+
def is_connected(self) -> bool:
|
|
303
|
+
""" Get the status of server-client connection
|
|
304
|
+
|
|
305
|
+
Returns: boolean indicating connection status
|
|
306
|
+
"""
|
|
307
|
+
if self.__configuration_handler_instance is None:
|
|
308
|
+
return False
|
|
309
|
+
return self.__configuration_handler_instance.is_connected()
|
|
@@ -15,11 +15,14 @@
|
|
|
15
15
|
"""
|
|
16
16
|
Internal class to handle the configuration.
|
|
17
17
|
"""
|
|
18
|
+
import json
|
|
18
19
|
import os
|
|
19
20
|
from typing import Dict, List, Any
|
|
20
21
|
from threading import Timer, Thread
|
|
21
22
|
from ibm_appconfiguration.configurations.internal.common import config_messages, config_constants
|
|
23
|
+
from ibm_appconfiguration.version import __version__
|
|
22
24
|
from .internal.utils.logger import Logger
|
|
25
|
+
from .internal.utils.parser import extract_configurations, format_config
|
|
23
26
|
from .internal.utils.validators import Validators
|
|
24
27
|
from .models import Feature
|
|
25
28
|
from .models import SegmentRules
|
|
@@ -46,6 +49,9 @@ class ConfigurationHandler:
|
|
|
46
49
|
"""Internal class to handle the configuration"""
|
|
47
50
|
__instance = None
|
|
48
51
|
|
|
52
|
+
# variable to keep track of server-client connection status
|
|
53
|
+
__is_alive = False
|
|
54
|
+
|
|
49
55
|
@staticmethod
|
|
50
56
|
def get_instance():
|
|
51
57
|
""" Static access method. """
|
|
@@ -144,7 +150,9 @@ class ConfigurationHandler:
|
|
|
144
150
|
self.__persistent_data = FileManager.read_files(
|
|
145
151
|
file_path=os.path.join(self.__persistent_cache_dir, 'appconfiguration.json'))
|
|
146
152
|
if self.__persistent_data is not None:
|
|
147
|
-
self.__load_configurations(
|
|
153
|
+
self.__load_configurations(
|
|
154
|
+
extract_configurations(self.__persistent_data, self.__environment_id, self.__collection_id)
|
|
155
|
+
)
|
|
148
156
|
if not os.access(self.__persistent_cache_dir, os.W_OK):
|
|
149
157
|
Logger.error(config_messages.ERROR_NO_WRITE_PERMISSION)
|
|
150
158
|
return
|
|
@@ -153,11 +161,13 @@ class ConfigurationHandler:
|
|
|
153
161
|
if self.__persistent_data is None or len(self.__persistent_data) == 0:
|
|
154
162
|
bootstrap_file_data = FileManager.read_files(file_path=self.__bootstrap_file)
|
|
155
163
|
if bootstrap_file_data is not None:
|
|
156
|
-
self.
|
|
164
|
+
configurations = extract_configurations(bootstrap_file_data, self.__environment_id, self.__collection_id)
|
|
165
|
+
self.__load_configurations(configurations)
|
|
166
|
+
self.__write_to_persistent_storage(format_config(configurations, self.__environment_id, self.__collection_id),
|
|
167
|
+
self.__persistent_cache_dir)
|
|
157
168
|
else:
|
|
158
169
|
Logger.error("Error reading bootstrap file data")
|
|
159
170
|
return
|
|
160
|
-
self.__write_to_persistent_storage(bootstrap_file_data, self.__persistent_cache_dir)
|
|
161
171
|
if self.__configuration_update_listener and callable(self.__configuration_update_listener):
|
|
162
172
|
self.__configuration_update_listener()
|
|
163
173
|
else:
|
|
@@ -166,7 +176,9 @@ class ConfigurationHandler:
|
|
|
166
176
|
else:
|
|
167
177
|
bootstrap_file_data = FileManager.read_files(file_path=self.__bootstrap_file)
|
|
168
178
|
if bootstrap_file_data is not None:
|
|
169
|
-
self.__load_configurations(
|
|
179
|
+
self.__load_configurations(
|
|
180
|
+
extract_configurations(bootstrap_file_data, self.__environment_id, self.__collection_id)
|
|
181
|
+
)
|
|
170
182
|
else:
|
|
171
183
|
Logger.error("Error reading bootstrap file data")
|
|
172
184
|
return
|
|
@@ -268,7 +280,8 @@ class ConfigurationHandler:
|
|
|
268
280
|
def __start_web_socket(self):
|
|
269
281
|
bearer_token = URLBuilder.get_iam_authenticator().token_manager.get_token()
|
|
270
282
|
headers = {
|
|
271
|
-
'Authorization': 'Bearer ' + bearer_token
|
|
283
|
+
'Authorization': 'Bearer ' + bearer_token,
|
|
284
|
+
'User-Agent': '{0}/{1}'.format(config_constants.SDK_NAME, __version__)
|
|
272
285
|
}
|
|
273
286
|
if self.__socket:
|
|
274
287
|
self.__socket.cancel()
|
|
@@ -475,8 +488,8 @@ class ConfigurationHandler:
|
|
|
475
488
|
Logger.debug(err)
|
|
476
489
|
return rule_map
|
|
477
490
|
|
|
478
|
-
def __write_to_persistent_storage(self,
|
|
479
|
-
FileManager.store_files(json, os.path.join(file_path, 'appconfiguration.json'))
|
|
491
|
+
def __write_to_persistent_storage(self, data: str, file_path: str):
|
|
492
|
+
FileManager.store_files(json.dumps(json.loads(data), indent=2), os.path.join(file_path, 'appconfiguration.json'))
|
|
480
493
|
|
|
481
494
|
def __fetch_from_api(self):
|
|
482
495
|
if self.__is_initialized:
|
|
@@ -503,8 +516,8 @@ class ConfigurationHandler:
|
|
|
503
516
|
Logger.info(config_messages.CONFIGURATIONS_FETCH_SUCCESS)
|
|
504
517
|
response_data = response.get_result()
|
|
505
518
|
try:
|
|
506
|
-
|
|
507
|
-
self.__load_configurations(
|
|
519
|
+
configurations = extract_configurations(json.dumps(response_data), self.__environment_id, self.__collection_id)
|
|
520
|
+
self.__load_configurations(configurations) # load response to cache maps
|
|
508
521
|
if self.__configuration_update_listener and callable(self.__configuration_update_listener):
|
|
509
522
|
self.__configuration_update_listener()
|
|
510
523
|
# we have already loaded the configurations to feature & property dicts.
|
|
@@ -512,7 +525,7 @@ class ConfigurationHandler:
|
|
|
512
525
|
# But the thread shouldn't be a daemon thread, because the writing should complete even if the main thread has terminated.
|
|
513
526
|
if self.__persistent_cache_dir:
|
|
514
527
|
file_write_thread = Thread(target=self.__write_to_persistent_storage,
|
|
515
|
-
|
|
528
|
+
args=(format_config(configurations, self.__environment_id, self.__collection_id), self.__persistent_cache_dir))
|
|
516
529
|
file_write_thread.start()
|
|
517
530
|
except Exception as exception:
|
|
518
531
|
Logger.error(f'error while while fetching {exception}')
|
|
@@ -538,24 +551,35 @@ class ConfigurationHandler:
|
|
|
538
551
|
def __on_web_socket_callback(self, message=None, error_state=None,
|
|
539
552
|
closed_state=None, open_state=None):
|
|
540
553
|
if message:
|
|
554
|
+
self.__is_alive = True
|
|
541
555
|
self.__fetch_from_api()
|
|
542
556
|
Logger.debug(f'Received message from socket. {message}')
|
|
543
557
|
elif error_state:
|
|
558
|
+
self.__is_alive = False
|
|
544
559
|
Logger.error(f'Received error from socket. {error_state}')
|
|
545
560
|
Logger.info('Reconnecting to server....')
|
|
546
561
|
self.__on_socket_retry = True
|
|
547
562
|
sleep(delay)
|
|
548
563
|
self.__start_web_socket()
|
|
549
564
|
elif closed_state:
|
|
565
|
+
self.__is_alive = False
|
|
550
566
|
Logger.error('Received close connection from socket.')
|
|
551
567
|
Logger.info('Reconnecting to server....')
|
|
552
568
|
self.__on_socket_retry = True
|
|
553
569
|
sleep(delay)
|
|
554
570
|
self.__start_web_socket()
|
|
555
571
|
elif open_state:
|
|
572
|
+
self.__is_alive = True
|
|
556
573
|
if self.__on_socket_retry:
|
|
557
574
|
self.__on_socket_retry = False
|
|
558
575
|
self.__fetch_from_api()
|
|
559
576
|
Logger.debug('Received opened connection from socket.')
|
|
560
577
|
else:
|
|
561
578
|
Logger.error('Unknown Error inside the socket connection.')
|
|
579
|
+
|
|
580
|
+
def is_connected(self) -> bool:
|
|
581
|
+
""" Get the status of server-client connection
|
|
582
|
+
|
|
583
|
+
Returns: boolean indicating connection status
|
|
584
|
+
"""
|
|
585
|
+
return self.__is_alive
|
|
@@ -18,8 +18,7 @@ file based cache of the SDK.
|
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
20
|
import fcntl
|
|
21
|
-
import
|
|
22
|
-
from typing import Optional, Any
|
|
21
|
+
from typing import Optional
|
|
23
22
|
|
|
24
23
|
from .logger import Logger
|
|
25
24
|
|
|
@@ -28,17 +27,17 @@ class FileManager:
|
|
|
28
27
|
"""FileManager to handle the cache"""
|
|
29
28
|
|
|
30
29
|
@classmethod
|
|
31
|
-
def store_files(cls,
|
|
30
|
+
def store_files(cls, data: str, file_path: str) -> bool:
|
|
32
31
|
"""Store the file
|
|
33
32
|
|
|
34
33
|
Args:
|
|
35
|
-
|
|
34
|
+
data: Data to be stored.
|
|
36
35
|
file_path: File path for the cache.
|
|
37
36
|
"""
|
|
38
37
|
try:
|
|
39
38
|
with open(file_path, 'w') as cache:
|
|
40
39
|
fcntl.flock(cache, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
41
|
-
|
|
40
|
+
cache.write(data)
|
|
42
41
|
fcntl.flock(cache, fcntl.LOCK_UN)
|
|
43
42
|
return True
|
|
44
43
|
except Exception as err:
|
|
@@ -46,7 +45,7 @@ class FileManager:
|
|
|
46
45
|
return False
|
|
47
46
|
|
|
48
47
|
@classmethod
|
|
49
|
-
def read_files(cls, file_path: str) -> Optional[
|
|
48
|
+
def read_files(cls, file_path: str) -> Optional[str]:
|
|
50
49
|
"""
|
|
51
50
|
Read the data from the given path.
|
|
52
51
|
|
|
@@ -59,9 +58,9 @@ class FileManager:
|
|
|
59
58
|
try:
|
|
60
59
|
with open(file_path, 'r') as file:
|
|
61
60
|
fcntl.flock(file, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
62
|
-
data =
|
|
61
|
+
data = file.read()
|
|
63
62
|
fcntl.flock(file, fcntl.LOCK_UN)
|
|
64
|
-
return data
|
|
63
|
+
return data if len(data) > 0 else None
|
|
65
64
|
except Exception as err:
|
|
66
65
|
Logger.error(err)
|
|
67
66
|
return None
|
ibm_appconfiguration_python_sdk-0.3.9/ibm_appconfiguration/configurations/internal/utils/parser.py
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Any, Dict, List, Set
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def extract_environment_data(data: Dict[str, Any], environment_id: str) -> Dict[str, Any]:
|
|
6
|
+
"""
|
|
7
|
+
Prepares config data for extraction with validation.
|
|
8
|
+
|
|
9
|
+
:param data: The full JSON configuration as a dictionary.
|
|
10
|
+
:param environment_id: The environment ID to extract.
|
|
11
|
+
:return: A dictionary containing 'features', 'properties', and 'segments'.
|
|
12
|
+
:raises: Exception if the format is invalid or environment not found.
|
|
13
|
+
"""
|
|
14
|
+
if not isinstance(data.get("segments"), list) or not isinstance(data.get("environments"), list):
|
|
15
|
+
raise Exception("Improper Data format present in configuration")
|
|
16
|
+
|
|
17
|
+
for environment in data["environments"]:
|
|
18
|
+
if environment.get("environment_id") == environment_id:
|
|
19
|
+
return {
|
|
20
|
+
"features": environment.get("features", []),
|
|
21
|
+
"properties": environment.get("properties", []),
|
|
22
|
+
"segments": data["segments"]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
raise Exception("Matching environment not found in configuration")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def validate_resource(resource: Dict[str, Any], collection: str) -> bool:
|
|
29
|
+
"""
|
|
30
|
+
Validates if the feature/property belongs to the given collection.
|
|
31
|
+
|
|
32
|
+
:param resource: The feature or property dictionary.
|
|
33
|
+
:param collection: The collection ID to match.
|
|
34
|
+
:return: True if valid, False otherwise.
|
|
35
|
+
:raises: Exception if collection format is invalid.
|
|
36
|
+
"""
|
|
37
|
+
if "collections" not in resource:
|
|
38
|
+
return True
|
|
39
|
+
|
|
40
|
+
collections = resource["collections"]
|
|
41
|
+
if not isinstance(collections, list):
|
|
42
|
+
raise Exception("Improper collection format in resource data")
|
|
43
|
+
|
|
44
|
+
for col in collections:
|
|
45
|
+
if col.get("collection_id") == collection:
|
|
46
|
+
return True
|
|
47
|
+
|
|
48
|
+
return False
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def append_segment_ids(resource: Dict[str, Any], segment_ids: Set[str]):
|
|
52
|
+
"""
|
|
53
|
+
Appends segment IDs from the resource's segment rules into the given set.
|
|
54
|
+
|
|
55
|
+
:param resource: The feature or property dictionary.
|
|
56
|
+
:param segment_ids: A set to accumulate segment IDs.
|
|
57
|
+
"""
|
|
58
|
+
for segment_rule in resource.get("segment_rules", []):
|
|
59
|
+
for rule in segment_rule.get("rules", []):
|
|
60
|
+
for segment_id in rule.get("segments", []):
|
|
61
|
+
segment_ids.add(segment_id)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def extract_resources(resource_data: Dict[str, Any], collection: str) -> Dict[str, List[Any]]:
|
|
65
|
+
"""
|
|
66
|
+
Extracts features, properties, and segments after validation.
|
|
67
|
+
|
|
68
|
+
:param resource_data: The environment-specific data.
|
|
69
|
+
:param collection: The collection ID to validate against.
|
|
70
|
+
:return: A dictionary with keys 'features', 'properties', and 'segments'.
|
|
71
|
+
:raises: Exception if any required segment is missing.
|
|
72
|
+
"""
|
|
73
|
+
features = []
|
|
74
|
+
properties = []
|
|
75
|
+
segments = []
|
|
76
|
+
required_segment_ids = set()
|
|
77
|
+
|
|
78
|
+
for feature in resource_data.get("features", []):
|
|
79
|
+
if validate_resource(feature, collection):
|
|
80
|
+
append_segment_ids(feature, required_segment_ids)
|
|
81
|
+
features.append(feature)
|
|
82
|
+
|
|
83
|
+
for property_ in resource_data.get("properties", []):
|
|
84
|
+
if validate_resource(property_, collection):
|
|
85
|
+
append_segment_ids(property_, required_segment_ids)
|
|
86
|
+
properties.append(property_)
|
|
87
|
+
|
|
88
|
+
available_segments = resource_data.get("segments", [])
|
|
89
|
+
for segment in available_segments:
|
|
90
|
+
if segment.get("segment_id") in required_segment_ids:
|
|
91
|
+
segments.append(segment)
|
|
92
|
+
required_segment_ids.remove(segment.get("segment_id"))
|
|
93
|
+
|
|
94
|
+
if len(required_segment_ids) > 0:
|
|
95
|
+
raise Exception(f"Required segment doesn't exist in provided segments")
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
"features": features,
|
|
99
|
+
"properties": properties,
|
|
100
|
+
"segments": segments
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def extract_configurations(data: str, environment: str, collection: str) -> Dict[str, List[Any]]:
|
|
105
|
+
"""
|
|
106
|
+
Unified parser for app-config data for new SDK/export/promote format.
|
|
107
|
+
|
|
108
|
+
:param data: Raw JSON string of the config.
|
|
109
|
+
:param environment: The environment ID.
|
|
110
|
+
:param collection: The collection ID.
|
|
111
|
+
:return: A dictionary with 'features', 'properties', and 'segments'.
|
|
112
|
+
:raises: Exception on any validation or format error.
|
|
113
|
+
"""
|
|
114
|
+
try:
|
|
115
|
+
configurations = json.loads(data)
|
|
116
|
+
|
|
117
|
+
if "collections" not in configurations or not isinstance(configurations["collections"], list):
|
|
118
|
+
raise Exception("Improper/Missing collections in configuration")
|
|
119
|
+
|
|
120
|
+
if not any(col.get("collection_id") == collection for col in configurations["collections"]):
|
|
121
|
+
raise Exception("Required collection not found in collections")
|
|
122
|
+
|
|
123
|
+
config_data = extract_environment_data(configurations, environment)
|
|
124
|
+
return extract_resources(config_data, collection)
|
|
125
|
+
|
|
126
|
+
except Exception as e:
|
|
127
|
+
raise Exception(f"Extraction of configurations failed with error:\n {str(e)}")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def format_config(res: Dict[str, List[Any]], environment_id: str, collection_id: str) -> str:
|
|
131
|
+
"""
|
|
132
|
+
Formats the extracted resources into unified config format.
|
|
133
|
+
|
|
134
|
+
:param res: The extracted config (from `extract_configurations`).
|
|
135
|
+
:param environment_id: The environment ID to include.
|
|
136
|
+
:param collection_id: The collection ID to include.
|
|
137
|
+
:return: A formatted configuration dictionary.
|
|
138
|
+
"""
|
|
139
|
+
return json.dumps({
|
|
140
|
+
"environments": [
|
|
141
|
+
{
|
|
142
|
+
"environment_id": environment_id,
|
|
143
|
+
"features": res.get("features", []),
|
|
144
|
+
"properties": res.get("properties", [])
|
|
145
|
+
}
|
|
146
|
+
],
|
|
147
|
+
"collections": [{"collection_id": collection_id}],
|
|
148
|
+
"segments": res.get("segments", [])
|
|
149
|
+
})
|
|
@@ -84,12 +84,12 @@ class URLBuilder:
|
|
|
84
84
|
cls.__iam_url = "https://iam.cloud.ibm.com"
|
|
85
85
|
cls.__web_socket_base = cls.__wss + region + cls.__base_url
|
|
86
86
|
|
|
87
|
-
cls.__config_path = '{0}{1}{2}/
|
|
87
|
+
cls.__config_path = '{0}{1}{2}/{3}?action=sdkConfig&collection_id={4}&environment_id={5}'.format(
|
|
88
88
|
cls.__service,
|
|
89
89
|
cls.__feature_path,
|
|
90
90
|
guid,
|
|
91
|
-
collection_id,
|
|
92
91
|
cls.__config,
|
|
92
|
+
collection_id,
|
|
93
93
|
environment_id)
|
|
94
94
|
cls.__metering_path = '{0}{1}{2}/usage'.format(cls.__service,
|
|
95
95
|
cls.__events_path,
|
|
@@ -45,18 +45,30 @@ class Rule:
|
|
|
45
45
|
def __ends_with(self, key, value) -> bool:
|
|
46
46
|
return key.endswith(value)
|
|
47
47
|
|
|
48
|
+
def __not_ends_with(self, key, value) -> bool:
|
|
49
|
+
return not key.endswith(value)
|
|
50
|
+
|
|
48
51
|
def __starts_with(self, key, value) -> bool:
|
|
49
52
|
return key.startswith(value)
|
|
50
53
|
|
|
54
|
+
def __not_starts_with(self, key, value) -> bool:
|
|
55
|
+
return not key.startswith(value)
|
|
56
|
+
|
|
51
57
|
def __contains(self, key, value) -> bool:
|
|
52
58
|
return value in key
|
|
53
59
|
|
|
60
|
+
def __not_contains(self, key, value) -> bool:
|
|
61
|
+
return not value in key
|
|
62
|
+
|
|
54
63
|
def __is(self, key, value) -> bool:
|
|
55
64
|
if type(key) is type(value):
|
|
56
65
|
return key == value
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
return str(key) == str(value)
|
|
67
|
+
|
|
68
|
+
def __is_not(self, key, value) -> bool:
|
|
69
|
+
if type(key) is type(value):
|
|
70
|
+
return key != value
|
|
71
|
+
return str(key) != str(value)
|
|
60
72
|
|
|
61
73
|
def __greater_than(self, key, value) -> bool:
|
|
62
74
|
|
|
@@ -102,9 +114,13 @@ class Rule:
|
|
|
102
114
|
|
|
103
115
|
case_checker = {
|
|
104
116
|
"endsWith": self.__ends_with,
|
|
117
|
+
"notEndsWith": self.__not_ends_with,
|
|
105
118
|
"startsWith": self.__starts_with,
|
|
119
|
+
"notStartsWith": self.__not_starts_with,
|
|
106
120
|
"contains": self.__contains,
|
|
121
|
+
"notContains": self.__not_contains,
|
|
107
122
|
"is": self.__is,
|
|
123
|
+
"isNot": self.__is_not,
|
|
108
124
|
"greaterThan": self.__greater_than,
|
|
109
125
|
"lesserThan": self.__lesser_than,
|
|
110
126
|
"greaterThanEquals": self.__greater_than_equals,
|
|
@@ -130,9 +146,17 @@ class Rule:
|
|
|
130
146
|
key = entity_attributes.get(self.__attribute_name)
|
|
131
147
|
else:
|
|
132
148
|
return result
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
149
|
+
|
|
150
|
+
if self.__operator in ["isNot", "notContains", "notStartsWith", "notEndsWith"]:
|
|
151
|
+
result = True
|
|
152
|
+
for i in range(0, len(self.__values)):
|
|
153
|
+
value = self.__values[i]
|
|
154
|
+
if not self.__operator_check(key, value):
|
|
155
|
+
result = False
|
|
156
|
+
else:
|
|
157
|
+
for i in range(0, len(self.__values)):
|
|
158
|
+
value = self.__values[i]
|
|
159
|
+
if self.__operator_check(key, value):
|
|
160
|
+
result = True
|
|
137
161
|
|
|
138
162
|
return result
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: ibm-appconfiguration-python-sdk
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.9
|
|
4
4
|
Summary: IBM Cloud App Configuration Python SDK
|
|
5
5
|
Home-page: https://github.com/IBM/appconfiguration-python-sdk
|
|
6
6
|
Author: IBM
|
|
@@ -21,6 +21,18 @@ Requires-Dist: ibm-cloud-sdk-core<4.0.0,>=3.20.3
|
|
|
21
21
|
Requires-Dist: pyyaml>=5.4.1
|
|
22
22
|
Requires-Dist: schema>=0.7.5
|
|
23
23
|
Requires-Dist: mmh3==5.0.1
|
|
24
|
+
Dynamic: author
|
|
25
|
+
Dynamic: author-email
|
|
26
|
+
Dynamic: classifier
|
|
27
|
+
Dynamic: description
|
|
28
|
+
Dynamic: description-content-type
|
|
29
|
+
Dynamic: home-page
|
|
30
|
+
Dynamic: keywords
|
|
31
|
+
Dynamic: license
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
Dynamic: requires-dist
|
|
34
|
+
Dynamic: requires-python
|
|
35
|
+
Dynamic: summary
|
|
24
36
|
|
|
25
37
|
# IBM Cloud App Configuration Python server SDK
|
|
26
38
|
|
|
@@ -74,15 +86,7 @@ The **`init()`** and **`set_context()`** are the initialisation methods and shou
|
|
|
74
86
|
appconfig_client. The appconfig_client, once initialised, can be obtained across modules
|
|
75
87
|
using **`AppConfiguration.get_instance()`**. [See this example below](#fetching-the-appconfig_client-across-other-modules).
|
|
76
88
|
|
|
77
|
-
- region : Region name where the App Configuration service instance is created.
|
|
78
|
-
- `AppConfiguration.REGION_US_SOUTH` for Dallas
|
|
79
|
-
- `AppConfiguration.REGION_EU_GB` for London
|
|
80
|
-
- `AppConfiguration.REGION_AU_SYD` for Sydney
|
|
81
|
-
- `AppConfiguration.REGION_US_EAST` for Washington DC
|
|
82
|
-
- `AppConfiguration.REGION_EU_DE` for Frankfurt
|
|
83
|
-
- `AppConfiguration.REGION_CA_TOR` for Toronto
|
|
84
|
-
- `AppConfiguration.REGION_JP_TOK` for Tokyo
|
|
85
|
-
- `AppConfiguration.REGION_JP_OSA` for Osaka
|
|
89
|
+
- region : Region name where the App Configuration service instance is created. See list of supported locations [here](https://cloud.ibm.com/catalog/services/app-configuration). Eg:- `us-south`, `au-syd` etc.
|
|
86
90
|
- guid : GUID of the App Configuration service. Obtain it from the service credentials section of the dashboard
|
|
87
91
|
- apikey : ApiKey of the App Configuration service. Obtain it from the service credentials section of the dashboard
|
|
88
92
|
- collection_id : Id of the collection created in App Configuration service instance under the **Collections** section.
|
|
@@ -20,6 +20,7 @@ ibm_appconfiguration/configurations/internal/utils/connectivity.py
|
|
|
20
20
|
ibm_appconfiguration/configurations/internal/utils/file_manager.py
|
|
21
21
|
ibm_appconfiguration/configurations/internal/utils/logger.py
|
|
22
22
|
ibm_appconfiguration/configurations/internal/utils/metering.py
|
|
23
|
+
ibm_appconfiguration/configurations/internal/utils/parser.py
|
|
23
24
|
ibm_appconfiguration/configurations/internal/utils/socket.py
|
|
24
25
|
ibm_appconfiguration/configurations/internal/utils/url_builder.py
|
|
25
26
|
ibm_appconfiguration/configurations/internal/utils/validators.py
|
|
@@ -56,6 +56,17 @@ class MyTestCase(unittest.TestCase):
|
|
|
56
56
|
}
|
|
57
57
|
self.assertFalse(self.sut.evaluate_rule(client_attributes))
|
|
58
58
|
|
|
59
|
+
def test_evaluation_not_ends_with_string(self):
|
|
60
|
+
self.set_up_email('google.com', 'email', 'notEndsWith')
|
|
61
|
+
client_attributes = {
|
|
62
|
+
'email': 'tester@in.ibm.com'
|
|
63
|
+
}
|
|
64
|
+
self.assertTrue(self.sut.evaluate_rule(client_attributes))
|
|
65
|
+
client_attributes = {
|
|
66
|
+
'email': 'tester@google.com'
|
|
67
|
+
}
|
|
68
|
+
self.assertFalse(self.sut.evaluate_rule(client_attributes))
|
|
69
|
+
|
|
59
70
|
def test_evaluation_is(self):
|
|
60
71
|
self.set_up_email("123", "creditValues", "is")
|
|
61
72
|
client_attributes = {
|
|
@@ -99,6 +110,31 @@ class MyTestCase(unittest.TestCase):
|
|
|
99
110
|
self.set_up_email(False, "creditValues", "is")
|
|
100
111
|
self.assertTrue(self.sut.evaluate_rule(client_attributes))
|
|
101
112
|
|
|
113
|
+
def test_evaluation_is_not(self):
|
|
114
|
+
self.set_up_email("123", "creditValues", "isNot")
|
|
115
|
+
client_attributes = {
|
|
116
|
+
'creditValues': '1234'
|
|
117
|
+
}
|
|
118
|
+
self.assertTrue(self.sut.evaluate_rule(client_attributes))
|
|
119
|
+
|
|
120
|
+
self.set_up_email("1234", "creditValues", "isNot")
|
|
121
|
+
client_attributes = {
|
|
122
|
+
'creditValues': 123
|
|
123
|
+
}
|
|
124
|
+
self.assertTrue(self.sut.evaluate_rule(client_attributes))
|
|
125
|
+
|
|
126
|
+
client_attributes = {
|
|
127
|
+
'creditValues': 123
|
|
128
|
+
}
|
|
129
|
+
self.set_up_email(5123, "creditValues", "isNot")
|
|
130
|
+
self.assertTrue(self.sut.evaluate_rule(client_attributes))
|
|
131
|
+
|
|
132
|
+
client_attributes = {
|
|
133
|
+
'creditValues': False
|
|
134
|
+
}
|
|
135
|
+
self.set_up_email(True, "creditValues", "isNot")
|
|
136
|
+
self.assertTrue(self.sut.evaluate_rule(client_attributes))
|
|
137
|
+
|
|
102
138
|
def test_evaluation_startswith(self):
|
|
103
139
|
self.set_up_email('user1', 'email', 'startsWith')
|
|
104
140
|
client_attributes = {
|
|
@@ -106,6 +142,13 @@ class MyTestCase(unittest.TestCase):
|
|
|
106
142
|
}
|
|
107
143
|
self.assertTrue(self.sut.evaluate_rule(client_attributes))
|
|
108
144
|
|
|
145
|
+
def test_evaluation_not_startswith(self):
|
|
146
|
+
self.set_up_email('user2', 'email', 'notStartsWith')
|
|
147
|
+
client_attributes = {
|
|
148
|
+
'email': 'user1@gm.com'
|
|
149
|
+
}
|
|
150
|
+
self.assertTrue(self.sut.evaluate_rule(client_attributes))
|
|
151
|
+
|
|
109
152
|
def test_evaluation_contains(self):
|
|
110
153
|
self.set_up_email('+91', 'mobile', 'contains')
|
|
111
154
|
client_attributes = {
|
|
@@ -118,6 +161,18 @@ class MyTestCase(unittest.TestCase):
|
|
|
118
161
|
}
|
|
119
162
|
self.assertFalse(self.sut.evaluate_rule(client_attributes))
|
|
120
163
|
|
|
164
|
+
def test_evaluation_not_contains(self):
|
|
165
|
+
self.set_up_email('+91', 'mobile', 'notContains')
|
|
166
|
+
client_attributes = {
|
|
167
|
+
'mobile': '+81xxxxxxxxxx'
|
|
168
|
+
}
|
|
169
|
+
self.assertTrue(self.sut.evaluate_rule(client_attributes))
|
|
170
|
+
|
|
171
|
+
client_attributes = {
|
|
172
|
+
'mobile': '+91xxxxxxxxxx'
|
|
173
|
+
}
|
|
174
|
+
self.assertFalse(self.sut.evaluate_rule(client_attributes))
|
|
175
|
+
|
|
121
176
|
def evaluation_common(self, type, val1, val2):
|
|
122
177
|
self.set_up_email(val1, 'balance', type)
|
|
123
178
|
client_attributes = {
|
|
@@ -38,7 +38,7 @@ class MyTestCase(unittest.TestCase):
|
|
|
38
38
|
'bootstrap_file': FILE,
|
|
39
39
|
'live_config_update_enabled': False
|
|
40
40
|
}
|
|
41
|
-
self.sut.set_context("
|
|
41
|
+
self.sut.set_context("collection", "dev", options)
|
|
42
42
|
self.sut.load_data()
|
|
43
43
|
time.sleep(2.5)
|
|
44
44
|
|
|
@@ -48,34 +48,44 @@ class MyTestCase(unittest.TestCase):
|
|
|
48
48
|
def test_load_from_web(self):
|
|
49
49
|
Metering.get_instance().set_repeat_calls(False)
|
|
50
50
|
mock_response = '''
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
51
|
+
{
|
|
52
|
+
"environments": [
|
|
53
|
+
{
|
|
54
|
+
"name": "Dev",
|
|
55
|
+
"environment_id": "dev",
|
|
56
|
+
"features": [
|
|
57
|
+
{
|
|
58
|
+
"name": "featurestring",
|
|
59
|
+
"feature_id": "featurestring",
|
|
60
|
+
"type": "STRING",
|
|
61
|
+
"enabled_value": "Hello",
|
|
62
|
+
"disabled_value": "Hi",
|
|
63
|
+
"segment_rules": [],
|
|
64
|
+
"enabled": true
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
"properties": [
|
|
68
|
+
{
|
|
69
|
+
"name": "numericproperty",
|
|
70
|
+
"property_id": "numericproperty",
|
|
71
|
+
"tags": "",
|
|
72
|
+
"type": "NUMERIC",
|
|
73
|
+
"value": 30,
|
|
74
|
+
"segment_rules": []
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
],
|
|
79
|
+
"collections": [
|
|
80
|
+
{
|
|
81
|
+
"name": "Collection",
|
|
82
|
+
"collection_id": "collection"
|
|
83
|
+
}
|
|
84
|
+
],
|
|
85
|
+
"segments": []
|
|
86
|
+
}
|
|
77
87
|
'''
|
|
78
|
-
url = 'https://region.apprapp.cloud.ibm.com/apprapp/feature/v1/instances/guid/
|
|
88
|
+
url = 'https://region.apprapp.cloud.ibm.com/apprapp/feature/v1/instances/guid/config?action=sdkConfig&collection_id=collection&environment_id=dev'
|
|
79
89
|
self.responses.add(responses.GET,
|
|
80
90
|
url,
|
|
81
91
|
body=mock_response,
|
|
@@ -87,7 +97,7 @@ class MyTestCase(unittest.TestCase):
|
|
|
87
97
|
'bootstrap_file': None,
|
|
88
98
|
'live_config_update_enabled': True
|
|
89
99
|
}
|
|
90
|
-
self.sut.set_context("
|
|
100
|
+
self.sut.set_context("collection", "dev", options)
|
|
91
101
|
self.sut.load_data()
|
|
92
102
|
features = self.sut.get_features()
|
|
93
103
|
self.assertEqual(len(features), 1)
|
|
@@ -178,54 +188,63 @@ class MyTestCase(unittest.TestCase):
|
|
|
178
188
|
Metering.get_instance().set_repeat_calls(False)
|
|
179
189
|
mock_response = '''
|
|
180
190
|
{
|
|
181
|
-
"
|
|
191
|
+
"environments": [
|
|
182
192
|
{
|
|
183
|
-
"name": "
|
|
184
|
-
"
|
|
185
|
-
"
|
|
186
|
-
"format": "YAML",
|
|
187
|
-
"enabled_value": "value: enabled",
|
|
188
|
-
"disabled_value": "value: disabled",
|
|
189
|
-
"segment_rules": [
|
|
193
|
+
"name": "Dev",
|
|
194
|
+
"environment_id": "dev",
|
|
195
|
+
"features": [
|
|
190
196
|
{
|
|
191
|
-
"
|
|
197
|
+
"name": "yamlFeature",
|
|
198
|
+
"feature_id": "yamlFeature",
|
|
199
|
+
"type": "STRING",
|
|
200
|
+
"format": "YAML",
|
|
201
|
+
"enabled_value": "value: enabled",
|
|
202
|
+
"disabled_value": "value: disabled",
|
|
203
|
+
"segment_rules": [
|
|
192
204
|
{
|
|
193
|
-
"
|
|
194
|
-
|
|
195
|
-
|
|
205
|
+
"rules": [
|
|
206
|
+
{
|
|
207
|
+
"segments": [
|
|
208
|
+
"reqbody"
|
|
209
|
+
]
|
|
210
|
+
}
|
|
211
|
+
],
|
|
212
|
+
"value": "value: targeted",
|
|
213
|
+
"order": 1
|
|
196
214
|
}
|
|
197
215
|
],
|
|
198
|
-
"
|
|
199
|
-
"order": 1
|
|
216
|
+
"enabled": true
|
|
200
217
|
}
|
|
201
218
|
],
|
|
202
|
-
"
|
|
203
|
-
}
|
|
204
|
-
],
|
|
205
|
-
"properties": [
|
|
206
|
-
{
|
|
207
|
-
"name": "yamlProperty",
|
|
208
|
-
"property_id": "yamlProperty",
|
|
209
|
-
"tags": "",
|
|
210
|
-
"type": "STRING",
|
|
211
|
-
"format": "YAML",
|
|
212
|
-
"value": "value: enabled",
|
|
213
|
-
"segment_rules": [
|
|
219
|
+
"properties": [
|
|
214
220
|
{
|
|
215
|
-
"
|
|
221
|
+
"name": "yamlProperty",
|
|
222
|
+
"property_id": "yamlProperty",
|
|
223
|
+
"tags": "",
|
|
224
|
+
"type": "STRING",
|
|
225
|
+
"format": "YAML",
|
|
226
|
+
"value": "value: enabled",
|
|
227
|
+
"segment_rules": [
|
|
216
228
|
{
|
|
217
|
-
"
|
|
218
|
-
|
|
219
|
-
|
|
229
|
+
"rules": [
|
|
230
|
+
{
|
|
231
|
+
"segments": [
|
|
232
|
+
"reqbody"
|
|
233
|
+
]
|
|
234
|
+
}
|
|
235
|
+
],
|
|
236
|
+
"value": "value: targeted",
|
|
237
|
+
"order": 1
|
|
220
238
|
}
|
|
221
|
-
]
|
|
222
|
-
"value": "value: targeted",
|
|
223
|
-
"order": 1
|
|
239
|
+
]
|
|
224
240
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
241
|
+
]
|
|
242
|
+
}
|
|
243
|
+
],
|
|
244
|
+
"collections": [
|
|
245
|
+
{
|
|
246
|
+
"name": "Collection",
|
|
247
|
+
"collection_id": "collection"
|
|
229
248
|
}
|
|
230
249
|
],
|
|
231
250
|
"segments": [
|
|
@@ -245,7 +264,7 @@ class MyTestCase(unittest.TestCase):
|
|
|
245
264
|
]
|
|
246
265
|
}
|
|
247
266
|
'''
|
|
248
|
-
url = 'https://region.apprapp.cloud.ibm.com/apprapp/feature/v1/instances/guid/
|
|
267
|
+
url = 'https://region.apprapp.cloud.ibm.com/apprapp/feature/v1/instances/guid/config?action=sdkConfig&collection_id=collection&environment_id=dev'
|
|
249
268
|
self.responses.add(responses.GET,
|
|
250
269
|
url,
|
|
251
270
|
body=mock_response,
|
|
@@ -257,7 +276,7 @@ class MyTestCase(unittest.TestCase):
|
|
|
257
276
|
'bootstrap_file': None,
|
|
258
277
|
'live_config_update_enabled': True
|
|
259
278
|
}
|
|
260
|
-
self.sut.set_context("
|
|
279
|
+
self.sut.set_context("collection", "dev", options)
|
|
261
280
|
self.sut.load_data()
|
|
262
281
|
features = self.sut.get_features()
|
|
263
282
|
properties = self.sut.get_properties()
|
|
@@ -41,8 +41,19 @@ class MyTestCase(unittest.TestCase):
|
|
|
41
41
|
self.api_manager = APIManager.get_instance()
|
|
42
42
|
|
|
43
43
|
def test_get_call(self):
|
|
44
|
-
mock_response = '
|
|
45
|
-
|
|
44
|
+
mock_response = '''
|
|
45
|
+
{
|
|
46
|
+
"environments": [
|
|
47
|
+
{
|
|
48
|
+
"features": [],
|
|
49
|
+
"properties": []
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
"collections": [],
|
|
53
|
+
"segments": []
|
|
54
|
+
}
|
|
55
|
+
'''
|
|
56
|
+
url = 'https://region.apprapp.cloud.ibm.com/apprapp/feature/v1/instances/guid/config?action=sdkConfig&collection_id=collection_id&environment_id=environment_id'
|
|
46
57
|
self.responses.add(responses.GET,
|
|
47
58
|
url,
|
|
48
59
|
body=mock_response,
|
|
@@ -50,7 +61,7 @@ class MyTestCase(unittest.TestCase):
|
|
|
50
61
|
status=200)
|
|
51
62
|
|
|
52
63
|
resp = self.api_manager.prepare_api_request(method="GET", url=URLBuilder.get_config_path())
|
|
53
|
-
self.assertEqual(resp.get_status_code()
|
|
64
|
+
self.assertEqual(200, resp.get_status_code())
|
|
54
65
|
|
|
55
66
|
try:
|
|
56
67
|
response_data = dict(resp.get_result())
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
|
-
|
|
14
|
+
import json
|
|
15
15
|
import unittest
|
|
16
16
|
import os
|
|
17
17
|
from ibm_appconfiguration.configurations.internal.utils.file_manager import FileManager
|
|
@@ -69,7 +69,7 @@ class MyTestCase(unittest.TestCase):
|
|
|
69
69
|
}
|
|
70
70
|
]
|
|
71
71
|
}
|
|
72
|
-
self.assertTrue(FileManager.store_files(data, self.file_path))
|
|
72
|
+
self.assertTrue(FileManager.store_files(json.dumps(data), self.file_path))
|
|
73
73
|
|
|
74
74
|
expected_data = FileManager.read_files(self.file_path)
|
|
75
75
|
|
|
@@ -31,7 +31,7 @@ class MyTestCase(unittest.TestCase):
|
|
|
31
31
|
self.assertEqual(URLBuilder.get_base_url(), 'https://region.apprapp.cloud.ibm.com')
|
|
32
32
|
self.assertEqual(URLBuilder.get_iam_url(), 'https://iam.cloud.ibm.com')
|
|
33
33
|
self.assertEqual(URLBuilder.get_config_path(),
|
|
34
|
-
'/apprapp/feature/v1/instances/guid/
|
|
34
|
+
'/apprapp/feature/v1/instances/guid/config?action=sdkConfig&collection_id=collection_id&environment_id=environment_id')
|
|
35
35
|
self.assertEqual(URLBuilder.get_metering_path(), '/apprapp/events/v1/instances/guid/usage')
|
|
36
36
|
self.assertEqual(URLBuilder.get_web_socket_url(),
|
|
37
37
|
'wss://region.apprapp.cloud.ibm.com/apprapp/wsfeature?instance_id=guid&collection_id=collection_id&environment_id=environment_id')
|
|
@@ -48,7 +48,7 @@ class MyTestCase(unittest.TestCase):
|
|
|
48
48
|
self.assertEqual(URLBuilder.get_base_url(), 'https://private.region.apprapp.cloud.ibm.com')
|
|
49
49
|
self.assertEqual(URLBuilder.get_iam_url(), 'https://private.iam.cloud.ibm.com')
|
|
50
50
|
self.assertEqual(URLBuilder.get_config_path(),
|
|
51
|
-
'/apprapp/feature/v1/instances/guid/
|
|
51
|
+
'/apprapp/feature/v1/instances/guid/config?action=sdkConfig&collection_id=collection_id&environment_id=environment_id')
|
|
52
52
|
self.assertEqual(URLBuilder.get_metering_path(), '/apprapp/events/v1/instances/guid/usage')
|
|
53
53
|
self.assertEqual(URLBuilder.get_web_socket_url(),
|
|
54
54
|
'wss://private.region.apprapp.cloud.ibm.com/apprapp/wsfeature?instance_id=guid&collection_id=collection_id&environment_id=environment_id')
|
|
@@ -65,7 +65,7 @@ class MyTestCase(unittest.TestCase):
|
|
|
65
65
|
self.assertEqual(URLBuilder.get_base_url(), 'https://region.apprapp.test.cloud.ibm.com')
|
|
66
66
|
self.assertEqual(URLBuilder.get_iam_url(), 'https://iam.test.cloud.ibm.com')
|
|
67
67
|
self.assertEqual(URLBuilder.get_config_path(),
|
|
68
|
-
'/apprapp/feature/v1/instances/guid/
|
|
68
|
+
'/apprapp/feature/v1/instances/guid/config?action=sdkConfig&collection_id=collection_id&environment_id=environment_id')
|
|
69
69
|
self.assertEqual(URLBuilder.get_metering_path(), '/apprapp/events/v1/instances/guid/usage')
|
|
70
70
|
self.assertEqual(URLBuilder.get_web_socket_url(),
|
|
71
71
|
'wss://region.apprapp.test.cloud.ibm.com/apprapp/wsfeature?instance_id=guid&collection_id=collection_id&environment_id=environment_id')
|
|
@@ -82,7 +82,7 @@ class MyTestCase(unittest.TestCase):
|
|
|
82
82
|
self.assertEqual(URLBuilder.get_base_url(), 'https://private.region.apprapp.test.cloud.ibm.com')
|
|
83
83
|
self.assertEqual(URLBuilder.get_iam_url(), 'https://private.iam.test.cloud.ibm.com')
|
|
84
84
|
self.assertEqual(URLBuilder.get_config_path(),
|
|
85
|
-
'/apprapp/feature/v1/instances/guid/
|
|
85
|
+
'/apprapp/feature/v1/instances/guid/config?action=sdkConfig&collection_id=collection_id&environment_id=environment_id')
|
|
86
86
|
self.assertEqual(URLBuilder.get_metering_path(), '/apprapp/events/v1/instances/guid/usage')
|
|
87
87
|
self.assertEqual(URLBuilder.get_web_socket_url(),
|
|
88
88
|
'wss://private.region.apprapp.test.cloud.ibm.com/apprapp/wsfeature?instance_id=guid&collection_id=collection_id&environment_id=environment_id')
|
|
@@ -72,7 +72,7 @@ class MyTestCase(unittest.TestCase):
|
|
|
72
72
|
sut1.enable_debug(True)
|
|
73
73
|
|
|
74
74
|
FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'user.json')
|
|
75
|
-
sut1.set_context("
|
|
75
|
+
sut1.set_context("collection", "dev", configuration_file=FILE, live_config_update_enabled=False)
|
|
76
76
|
time.sleep(2.5)
|
|
77
77
|
|
|
78
78
|
self.assertEqual(len(sut1.get_features()), 3)
|
|
File without changes
|
{ibm_appconfiguration_python_sdk-0.3.7 → ibm_appconfiguration_python_sdk-0.3.9}/examples/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|