ibm-appconfiguration-python-sdk 0.3.8__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.8/ibm_appconfiguration_python_sdk.egg-info → ibm_appconfiguration_python_sdk-0.3.9}/PKG-INFO +1 -1
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/configuration_handler.py +20 -10
- {ibm_appconfiguration_python_sdk-0.3.8 → 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.8 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/utils/url_builder.py +2 -2
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/version.py +1 -1
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9/ibm_appconfiguration_python_sdk.egg-info}/PKG-INFO +1 -1
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration_python_sdk.egg-info/SOURCES.txt +1 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/setup.py +1 -1
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/test_configuration_handler.py +87 -68
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/utils/test_api_manager.py +14 -3
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/utils/test_file_manager.py +2 -2
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/utils/test_url_builder.py +4 -4
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/test_appconfiguration.py +1 -1
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/LICENSE +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/README.md +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/examples/__init__.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/examples/sample_app.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/examples/server_sample.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/__init__.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/appconfiguration.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/__init__.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/__init__.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/common/__init__.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/common/config_constants.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/common/config_messages.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/utils/__init__.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/utils/api_manager.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/utils/compute_percentage.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/utils/connectivity.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/utils/logger.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/utils/metering.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/utils/socket.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/internal/utils/validators.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/models/__init__.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/models/configuration_type.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/models/feature.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/models/property.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/models/rule.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/models/segment.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration/configurations/models/segment_rules.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration_python_sdk.egg-info/dependency_links.txt +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration_python_sdk.egg-info/requires.txt +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/ibm_appconfiguration_python_sdk.egg-info/top_level.txt +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/integration_tests/__init__.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/integration_tests/test_integration.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/setup.cfg +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/__init__.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/__init__.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/models/__init__.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/models/test_feature.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/models/test_property.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/models/test_rule.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/models/test_segment.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/models/test_segment_rules.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/utils/__init__.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/utils/test_metering.py +0 -0
- {ibm_appconfiguration_python_sdk-0.3.8 → ibm_appconfiguration_python_sdk-0.3.9}/unit_tests/configurations/utils/test_socket.py +0 -0
|
@@ -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
|
|
@@ -147,7 +150,9 @@ class ConfigurationHandler:
|
|
|
147
150
|
self.__persistent_data = FileManager.read_files(
|
|
148
151
|
file_path=os.path.join(self.__persistent_cache_dir, 'appconfiguration.json'))
|
|
149
152
|
if self.__persistent_data is not None:
|
|
150
|
-
self.__load_configurations(
|
|
153
|
+
self.__load_configurations(
|
|
154
|
+
extract_configurations(self.__persistent_data, self.__environment_id, self.__collection_id)
|
|
155
|
+
)
|
|
151
156
|
if not os.access(self.__persistent_cache_dir, os.W_OK):
|
|
152
157
|
Logger.error(config_messages.ERROR_NO_WRITE_PERMISSION)
|
|
153
158
|
return
|
|
@@ -156,11 +161,13 @@ class ConfigurationHandler:
|
|
|
156
161
|
if self.__persistent_data is None or len(self.__persistent_data) == 0:
|
|
157
162
|
bootstrap_file_data = FileManager.read_files(file_path=self.__bootstrap_file)
|
|
158
163
|
if bootstrap_file_data is not None:
|
|
159
|
-
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)
|
|
160
168
|
else:
|
|
161
169
|
Logger.error("Error reading bootstrap file data")
|
|
162
170
|
return
|
|
163
|
-
self.__write_to_persistent_storage(bootstrap_file_data, self.__persistent_cache_dir)
|
|
164
171
|
if self.__configuration_update_listener and callable(self.__configuration_update_listener):
|
|
165
172
|
self.__configuration_update_listener()
|
|
166
173
|
else:
|
|
@@ -169,7 +176,9 @@ class ConfigurationHandler:
|
|
|
169
176
|
else:
|
|
170
177
|
bootstrap_file_data = FileManager.read_files(file_path=self.__bootstrap_file)
|
|
171
178
|
if bootstrap_file_data is not None:
|
|
172
|
-
self.__load_configurations(
|
|
179
|
+
self.__load_configurations(
|
|
180
|
+
extract_configurations(bootstrap_file_data, self.__environment_id, self.__collection_id)
|
|
181
|
+
)
|
|
173
182
|
else:
|
|
174
183
|
Logger.error("Error reading bootstrap file data")
|
|
175
184
|
return
|
|
@@ -271,7 +280,8 @@ class ConfigurationHandler:
|
|
|
271
280
|
def __start_web_socket(self):
|
|
272
281
|
bearer_token = URLBuilder.get_iam_authenticator().token_manager.get_token()
|
|
273
282
|
headers = {
|
|
274
|
-
'Authorization': 'Bearer ' + bearer_token
|
|
283
|
+
'Authorization': 'Bearer ' + bearer_token,
|
|
284
|
+
'User-Agent': '{0}/{1}'.format(config_constants.SDK_NAME, __version__)
|
|
275
285
|
}
|
|
276
286
|
if self.__socket:
|
|
277
287
|
self.__socket.cancel()
|
|
@@ -478,8 +488,8 @@ class ConfigurationHandler:
|
|
|
478
488
|
Logger.debug(err)
|
|
479
489
|
return rule_map
|
|
480
490
|
|
|
481
|
-
def __write_to_persistent_storage(self,
|
|
482
|
-
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'))
|
|
483
493
|
|
|
484
494
|
def __fetch_from_api(self):
|
|
485
495
|
if self.__is_initialized:
|
|
@@ -506,8 +516,8 @@ class ConfigurationHandler:
|
|
|
506
516
|
Logger.info(config_messages.CONFIGURATIONS_FETCH_SUCCESS)
|
|
507
517
|
response_data = response.get_result()
|
|
508
518
|
try:
|
|
509
|
-
|
|
510
|
-
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
|
|
511
521
|
if self.__configuration_update_listener and callable(self.__configuration_update_listener):
|
|
512
522
|
self.__configuration_update_listener()
|
|
513
523
|
# we have already loaded the configurations to feature & property dicts.
|
|
@@ -515,7 +525,7 @@ class ConfigurationHandler:
|
|
|
515
525
|
# But the thread shouldn't be a daemon thread, because the writing should complete even if the main thread has terminated.
|
|
516
526
|
if self.__persistent_cache_dir:
|
|
517
527
|
file_write_thread = Thread(target=self.__write_to_persistent_storage,
|
|
518
|
-
|
|
528
|
+
args=(format_config(configurations, self.__environment_id, self.__collection_id), self.__persistent_cache_dir))
|
|
519
529
|
file_write_thread.start()
|
|
520
530
|
except Exception as exception:
|
|
521
531
|
Logger.error(f'error while while fetching {exception}')
|
|
@@ -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,
|
|
@@ -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
|
|
@@ -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
|
|
File without changes
|
{ibm_appconfiguration_python_sdk-0.3.8 → 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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|