ibm-appconfiguration-python-sdk 0.4.1__tar.gz → 0.4.2__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.4.1/ibm_appconfiguration_python_sdk.egg-info → ibm_appconfiguration_python_sdk-0.4.2}/PKG-INFO +1 -1
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/ibm_appconfiguration/configurations/configuration_handler.py +83 -5
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/ibm_appconfiguration/configurations/internal/common/config_constants.py +5 -1
- ibm_appconfiguration_python_sdk-0.4.2/ibm_appconfiguration/configurations/internal/utils/rollout_utils.py +101 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/ibm_appconfiguration/configurations/models/feature.py +16 -1
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/ibm_appconfiguration/configurations/models/segment_rules.py +22 -1
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/ibm_appconfiguration/version.py +1 -1
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2/ibm_appconfiguration_python_sdk.egg-info}/PKG-INFO +1 -1
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/ibm_appconfiguration_python_sdk.egg-info/SOURCES.txt +1 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/setup.py +1 -1
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/LICENSE +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/README.md +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/examples/__init__.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/examples/sample_app.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/examples/server_sample.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/ibm_appconfiguration/__init__.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/ibm_appconfiguration/appconfiguration.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/ibm_appconfiguration/configurations/__init__.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/ibm_appconfiguration/configurations/internal/__init__.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/ibm_appconfiguration/configurations/internal/common/__init__.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/ibm_appconfiguration/configurations/internal/common/config_messages.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/ibm_appconfiguration/configurations/internal/utils/__init__.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/ibm_appconfiguration/configurations/internal/utils/api_manager.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/ibm_appconfiguration/configurations/internal/utils/compute_percentage.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/ibm_appconfiguration/configurations/internal/utils/connectivity.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/ibm_appconfiguration/configurations/internal/utils/file_manager.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/ibm_appconfiguration/configurations/internal/utils/logger.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/ibm_appconfiguration/configurations/internal/utils/metering.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/ibm_appconfiguration/configurations/internal/utils/parser.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/ibm_appconfiguration/configurations/internal/utils/socket.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/ibm_appconfiguration/configurations/internal/utils/url_builder.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/ibm_appconfiguration/configurations/internal/utils/validators.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/ibm_appconfiguration/configurations/models/__init__.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/ibm_appconfiguration/configurations/models/configuration_type.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/ibm_appconfiguration/configurations/models/property.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/ibm_appconfiguration/configurations/models/rule.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/ibm_appconfiguration/configurations/models/segment.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/ibm_appconfiguration_python_sdk.egg-info/dependency_links.txt +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/ibm_appconfiguration_python_sdk.egg-info/requires.txt +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/ibm_appconfiguration_python_sdk.egg-info/top_level.txt +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/integration_tests/__init__.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/integration_tests/test_integration.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/setup.cfg +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/unit_tests/__init__.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/unit_tests/configurations/__init__.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/unit_tests/configurations/models/__init__.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/unit_tests/configurations/models/test_feature.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/unit_tests/configurations/models/test_property.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/unit_tests/configurations/models/test_rule.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/unit_tests/configurations/models/test_segment.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/unit_tests/configurations/models/test_segment_rules.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/unit_tests/configurations/test_configuration_handler.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/unit_tests/configurations/utils/__init__.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/unit_tests/configurations/utils/test_api_manager.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/unit_tests/configurations/utils/test_file_manager.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/unit_tests/configurations/utils/test_metering.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/unit_tests/configurations/utils/test_socket.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/unit_tests/configurations/utils/test_url_builder.py +0 -0
- {ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/unit_tests/test_appconfiguration.py +0 -0
|
@@ -33,6 +33,7 @@ from .internal.utils.metering import Metering
|
|
|
33
33
|
from .internal.utils.socket import Socket
|
|
34
34
|
from .internal.utils.url_builder import URLBuilder
|
|
35
35
|
from .internal.utils.api_manager import APIManager
|
|
36
|
+
from .internal.utils.rollout_utils import parse_rollout_configuration_phases, get_current_rollout_percentage
|
|
36
37
|
|
|
37
38
|
|
|
38
39
|
class ConfigurationHandler:
|
|
@@ -61,6 +62,7 @@ class ConfigurationHandler:
|
|
|
61
62
|
self.__feature_map = dict()
|
|
62
63
|
self.__property_map = dict()
|
|
63
64
|
self.__segment_map = dict()
|
|
65
|
+
self.__rollout_config_map = dict()
|
|
64
66
|
self.__live_config_update_enabled = True
|
|
65
67
|
ConfigurationHandler.__instance = self
|
|
66
68
|
self.__retry_interval = 120
|
|
@@ -97,6 +99,7 @@ class ConfigurationHandler:
|
|
|
97
99
|
self.__feature_map = dict()
|
|
98
100
|
self.__property_map = dict()
|
|
99
101
|
self.__segment_map = dict()
|
|
102
|
+
self.__rollout_config_map = dict()
|
|
100
103
|
|
|
101
104
|
def set_context(self, collection_id: str, environment_id: str, options: dict):
|
|
102
105
|
"""Set the context for the configuration
|
|
@@ -249,11 +252,33 @@ class ConfigurationHandler:
|
|
|
249
252
|
if len(data) != 0:
|
|
250
253
|
if 'features' in data:
|
|
251
254
|
self.__feature_map = dict()
|
|
255
|
+
self.__rollout_config_map = dict()
|
|
252
256
|
try:
|
|
253
257
|
all_feature_list: List = data.get('features')
|
|
254
258
|
for i, feature in enumerate(all_feature_list):
|
|
255
259
|
feature_obj = Feature(feature)
|
|
256
260
|
self.__feature_map[feature_obj.get_feature_id()] = feature_obj
|
|
261
|
+
|
|
262
|
+
# Parse feature-level progressive rollout
|
|
263
|
+
if feature_obj.get_rollout_configuration() is not None:
|
|
264
|
+
rollout_config = feature_obj.get_rollout_configuration()
|
|
265
|
+
if rollout_config:
|
|
266
|
+
rollout_map = parse_rollout_configuration_phases(rollout_config)
|
|
267
|
+
if rollout_map:
|
|
268
|
+
self.__rollout_config_map[feature_obj.get_feature_id()] = rollout_map
|
|
269
|
+
|
|
270
|
+
# Parse segment-level progressive rollout
|
|
271
|
+
segment_rules = feature_obj.get_segment_rules()
|
|
272
|
+
if segment_rules and isinstance(segment_rules, list):
|
|
273
|
+
for segment_rule in segment_rules:
|
|
274
|
+
segment_rule_obj = SegmentRules(segment_rule)
|
|
275
|
+
if segment_rule_obj.get_rollout_configuration() is not None:
|
|
276
|
+
rollout_config = segment_rule_obj.get_rollout_configuration()
|
|
277
|
+
if rollout_config and segment_rule_obj.get_rule_id():
|
|
278
|
+
rollout_map = parse_rollout_configuration_phases(rollout_config)
|
|
279
|
+
if rollout_map:
|
|
280
|
+
key = feature_obj.get_feature_id() + config_constants.DELIMITER + segment_rule_obj.get_rule_id()
|
|
281
|
+
self.__rollout_config_map[key] = rollout_map
|
|
257
282
|
except Exception as err:
|
|
258
283
|
Logger.debug(err)
|
|
259
284
|
|
|
@@ -359,8 +384,21 @@ class ConfigurationHandler:
|
|
|
359
384
|
if feature.get_feature_data_format() == "YAML" and type(result_dict['value']) == str:
|
|
360
385
|
return Validators.validate_yaml_string(result_dict['value']), result_dict['is_enabled']
|
|
361
386
|
return result_dict['value'], result_dict['is_enabled']
|
|
362
|
-
|
|
363
|
-
|
|
387
|
+
|
|
388
|
+
# Check feature-level rollout
|
|
389
|
+
rollout_percentage = None
|
|
390
|
+
if feature.get_rollout_configuration() is not None:
|
|
391
|
+
rollout_map = self.__rollout_config_map.get(feature.get_feature_id())
|
|
392
|
+
if rollout_map:
|
|
393
|
+
entity_id += feature.get_rollout_configuration().get('start_at')
|
|
394
|
+
rollout_percentage = get_current_rollout_percentage(rollout_map)
|
|
395
|
+
else:
|
|
396
|
+
rollout_percentage = 0
|
|
397
|
+
else:
|
|
398
|
+
rollout_percentage = feature.get_rollout_percentage() if feature.get_rollout_percentage() is not None else 100
|
|
399
|
+
|
|
400
|
+
if rollout_percentage == 100 or (get_normalized_value(
|
|
401
|
+
entity_id + ":" + feature.get_feature_id()) < rollout_percentage):
|
|
364
402
|
return feature.get_enabled_value(), True
|
|
365
403
|
return feature.get_disabled_value(), False
|
|
366
404
|
return feature.get_disabled_value(), False
|
|
@@ -391,7 +429,35 @@ class ConfigurationHandler:
|
|
|
391
429
|
result_dict['evaluated_segment_id'] = segment_key
|
|
392
430
|
if feature is not None:
|
|
393
431
|
# evaluate_rules was called for feature flag
|
|
394
|
-
segment_rollout_percentage =
|
|
432
|
+
segment_rollout_percentage = None
|
|
433
|
+
|
|
434
|
+
# Check if segment rule has progressive rollout
|
|
435
|
+
if segment_rule.get_rollout_configuration() is not None or segment_rule.get_rollout_type() == config_constants.PROGRESSIVE:
|
|
436
|
+
# Determine which rollout map to use
|
|
437
|
+
if segment_rule.get_rollout_percentage() == config_constants.DEFAULT_ROLLOUT_PERCENTAGE:
|
|
438
|
+
# Use feature-level rollout
|
|
439
|
+
rollout_map = self.__rollout_config_map.get(feature.get_feature_id())
|
|
440
|
+
else:
|
|
441
|
+
# Use segment-level rollout
|
|
442
|
+
rule_id = segment_rule.get_rule_id()
|
|
443
|
+
if rule_id:
|
|
444
|
+
key = feature.get_feature_id() + config_constants.DELIMITER + rule_id
|
|
445
|
+
rollout_map = self.__rollout_config_map.get(key)
|
|
446
|
+
else:
|
|
447
|
+
rollout_map = None
|
|
448
|
+
|
|
449
|
+
if rollout_map:
|
|
450
|
+
entity_id += segment_rule.get_rollout_configuration().get('start_at')
|
|
451
|
+
segment_rollout_percentage = get_current_rollout_percentage(rollout_map)
|
|
452
|
+
else:
|
|
453
|
+
segment_rollout_percentage = 0
|
|
454
|
+
else:
|
|
455
|
+
# Use manual rollout percentage
|
|
456
|
+
if segment_rule.get_rollout_percentage() == config_constants.DEFAULT_ROLLOUT_PERCENTAGE:
|
|
457
|
+
segment_rollout_percentage = feature.get_rollout_percentage() if feature.get_rollout_percentage() is not None else 100
|
|
458
|
+
else:
|
|
459
|
+
segment_rollout_percentage = segment_rule.get_rollout_percentage() if segment_rule.get_rollout_percentage() is not None else 100
|
|
460
|
+
|
|
395
461
|
if segment_rollout_percentage == 100 or (get_normalized_value(
|
|
396
462
|
entity_id + ":" + feature.get_feature_id())) < segment_rollout_percentage:
|
|
397
463
|
if segment_rule.get_value() == config_constants.DEFAULT_FEATURE_VALUE:
|
|
@@ -413,8 +479,20 @@ class ConfigurationHandler:
|
|
|
413
479
|
Logger.debug(err)
|
|
414
480
|
|
|
415
481
|
if feature is not None:
|
|
416
|
-
|
|
417
|
-
|
|
482
|
+
# Check feature-level rollout
|
|
483
|
+
rollout_percentage = None
|
|
484
|
+
if feature.get_rollout_configuration() is not None:
|
|
485
|
+
rollout_map = self.__rollout_config_map.get(feature.get_feature_id())
|
|
486
|
+
if rollout_map:
|
|
487
|
+
entity_id += feature.get_rollout_configuration().get('start_at')
|
|
488
|
+
rollout_percentage = get_current_rollout_percentage(rollout_map)
|
|
489
|
+
else:
|
|
490
|
+
rollout_percentage = 0
|
|
491
|
+
else:
|
|
492
|
+
rollout_percentage = feature.get_rollout_percentage() if feature.get_rollout_percentage() is not None else 100
|
|
493
|
+
|
|
494
|
+
if rollout_percentage == 100 or get_normalized_value(
|
|
495
|
+
entity_id + ":" + feature.get_feature_id()) < rollout_percentage:
|
|
418
496
|
result_dict['value'] = feature.get_enabled_value()
|
|
419
497
|
result_dict['is_enabled'] = True
|
|
420
498
|
else:
|
|
@@ -18,7 +18,7 @@ This file defines the constants used by the SDK.
|
|
|
18
18
|
|
|
19
19
|
DEFAULT_SEGMENT_ID = '$$null$$'
|
|
20
20
|
DEFAULT_ENTITY_ID = '$$null$$'
|
|
21
|
-
DEFAULT_USAGE_LIMIT =
|
|
21
|
+
DEFAULT_USAGE_LIMIT = 30
|
|
22
22
|
SDK_NAME = "appconfiguration-python-sdk"
|
|
23
23
|
MAX_NUMBER_OF_RETRIES = 3
|
|
24
24
|
DEFAULT_ROLLOUT_PERCENTAGE = '$default'
|
|
@@ -26,3 +26,7 @@ DEFAULT_FEATURE_VALUE = '$default'
|
|
|
26
26
|
DEFAULT_PROPERTY_VALUE = '$default'
|
|
27
27
|
WEBSOCKET_RECONNECT_DELAY = 15 # Constant delay between reconnection attempts for server errors
|
|
28
28
|
CUSTOM_SOCKET_CLOSE_REASON_CODE = 4001
|
|
29
|
+
|
|
30
|
+
MANUAL = 'MANUAL'
|
|
31
|
+
PROGRESSIVE = 'PROGRESSIVE'
|
|
32
|
+
DELIMITER = '\u001F'
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Copyright 2021 IBM All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
Utility module for handling progressive rollout configurations.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from datetime import datetime
|
|
20
|
+
from sortedcontainers import SortedDict
|
|
21
|
+
from .logger import Logger
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def parse_rollout_configuration_phases(configuration: dict) -> SortedDict:
|
|
25
|
+
"""
|
|
26
|
+
Parse progressive rollout phases into a SortedDict for efficient timestamp-to-percentage lookups.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
configuration: Rollout configuration dict with 'start_at' and 'phases' keys
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
SortedDict with timestamps (ms) as keys and percentages as values, or None if parsing fails
|
|
33
|
+
"""
|
|
34
|
+
if not configuration or 'start_at' not in configuration or 'phases' not in configuration:
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
if not isinstance(configuration['phases'], list) or len(configuration['phases']) == 0:
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
# Parse start timestamp
|
|
42
|
+
start_time = datetime.fromisoformat(configuration['start_at'].replace('Z', '+00:00'))
|
|
43
|
+
transition_time = int(start_time.timestamp() * 1000) # Convert to milliseconds
|
|
44
|
+
|
|
45
|
+
# Create SortedDict for efficient lookups
|
|
46
|
+
result = SortedDict()
|
|
47
|
+
result[0] = 0
|
|
48
|
+
|
|
49
|
+
# Duration multipliers in milliseconds
|
|
50
|
+
multipliers = {
|
|
51
|
+
'days': 86400000, # days
|
|
52
|
+
'hours': 3600000, # hours
|
|
53
|
+
'minutes': 60000, # minutes
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
for phase in configuration['phases']:
|
|
57
|
+
if not isinstance(phase, dict) or 'percentage' not in phase:
|
|
58
|
+
continue
|
|
59
|
+
|
|
60
|
+
# Add phase entry
|
|
61
|
+
result[transition_time] = phase['percentage']
|
|
62
|
+
|
|
63
|
+
# Calculate next transition time if duration is specified
|
|
64
|
+
duration = phase.get('duration')
|
|
65
|
+
if duration:
|
|
66
|
+
transition_time += multipliers.get(phase.get('duration_type')) * duration
|
|
67
|
+
|
|
68
|
+
return result
|
|
69
|
+
|
|
70
|
+
except (ValueError, AttributeError) as e:
|
|
71
|
+
Logger.error(f"Invalid start_at timestamp: {configuration.get('start_at')}, error: {str(e)}")
|
|
72
|
+
return None
|
|
73
|
+
except Exception as e:
|
|
74
|
+
Logger.error(f"Error parsing rollout configuration: {str(e)}")
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def get_current_rollout_percentage(rollout_map: SortedDict) -> int:
|
|
79
|
+
"""
|
|
80
|
+
Returns the current rollout percentage based on current time.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
rollout_map: SortedDict containing timestamp-to-percentage mappings
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
The current rollout percentage, or 0 if rollout_map is None or empty
|
|
87
|
+
"""
|
|
88
|
+
if not rollout_map or len(rollout_map) == 0:
|
|
89
|
+
return 0
|
|
90
|
+
|
|
91
|
+
current_time = int(datetime.now().timestamp() * 1000) # Current time in milliseconds
|
|
92
|
+
|
|
93
|
+
# Find the entry with the largest timestamp that is <= current_time
|
|
94
|
+
# SortedDict.bisect_right returns the index where current_time would be inserted
|
|
95
|
+
# We want the item just before that position
|
|
96
|
+
idx = rollout_map.bisect_right(current_time)
|
|
97
|
+
if idx > 0:
|
|
98
|
+
key = rollout_map.keys()[idx - 1]
|
|
99
|
+
return rollout_map[key]
|
|
100
|
+
|
|
101
|
+
return 0
|
|
@@ -20,6 +20,7 @@ from typing import Any
|
|
|
20
20
|
from ..internal.utils.logger import Logger
|
|
21
21
|
from ..internal.utils.validators import Validators
|
|
22
22
|
from .configuration_type import ConfigurationType
|
|
23
|
+
from ..internal.common import config_constants
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
class Feature:
|
|
@@ -39,7 +40,13 @@ class Feature:
|
|
|
39
40
|
self.__format = feature_list.get('format', None)
|
|
40
41
|
self.__disabled_value = feature_list.get('disabled_value', object)
|
|
41
42
|
self.__enabled_value = feature_list.get('enabled_value', object)
|
|
42
|
-
self.
|
|
43
|
+
self.__rollout_type = feature_list.get('rollout_type', config_constants.MANUAL)
|
|
44
|
+
if feature_list.get('rollout_configuration', None) is not None:
|
|
45
|
+
self.__rollout_configuration = feature_list.get('rollout_configuration')
|
|
46
|
+
self.__rollout_percentage = None
|
|
47
|
+
else:
|
|
48
|
+
self.__rollout_percentage = feature_list.get('rollout_percentage', 100)
|
|
49
|
+
self.__rollout_configuration = None
|
|
43
50
|
|
|
44
51
|
def get_feature_name(self) -> str:
|
|
45
52
|
"""Get the Feature name"""
|
|
@@ -74,6 +81,14 @@ class Feature:
|
|
|
74
81
|
def get_rollout_percentage(self) -> int:
|
|
75
82
|
"""Get the Feature flag's rollout percentage"""
|
|
76
83
|
return self.__rollout_percentage
|
|
84
|
+
|
|
85
|
+
def get_rollout_type(self) -> str:
|
|
86
|
+
"""Get the Feature flag's rollout type"""
|
|
87
|
+
return self.__rollout_type
|
|
88
|
+
|
|
89
|
+
def get_rollout_configuration(self) -> Any:
|
|
90
|
+
"""Get the Feature flag's rollout configuration"""
|
|
91
|
+
return self.__rollout_configuration
|
|
77
92
|
|
|
78
93
|
def is_enabled(self) -> bool:
|
|
79
94
|
"""
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
This module defines the model of a segment rule defined in App Configuration service.
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
+
from ..internal.common import config_constants
|
|
19
|
+
|
|
18
20
|
|
|
19
21
|
class SegmentRules:
|
|
20
22
|
"""
|
|
@@ -26,7 +28,14 @@ class SegmentRules:
|
|
|
26
28
|
self.__order = segment_rules.get("order", 1)
|
|
27
29
|
self.__value = segment_rules.get("value", object)
|
|
28
30
|
self.__rules = segment_rules.get("rules", list())
|
|
29
|
-
self.
|
|
31
|
+
self.__rule_id = segment_rules.get("rule_id", None)
|
|
32
|
+
self.__rollout_type = segment_rules.get("rollout_type", config_constants.MANUAL)
|
|
33
|
+
if segment_rules.get("rollout_configuration", None) is not None:
|
|
34
|
+
self.__rollout_configuration = segment_rules.get("rollout_configuration")
|
|
35
|
+
self.__rollout_percentage = None
|
|
36
|
+
else:
|
|
37
|
+
self.__rollout_percentage = segment_rules.get("rollout_percentage", 100)
|
|
38
|
+
self.__rollout_configuration = None
|
|
30
39
|
|
|
31
40
|
def get_order(self) -> int:
|
|
32
41
|
"""Get the SegmentRule order"""
|
|
@@ -43,3 +52,15 @@ class SegmentRules:
|
|
|
43
52
|
def get_rollout_percentage(self) -> int:
|
|
44
53
|
"""Get the rollout percentage for SegmentRule"""
|
|
45
54
|
return self.__rollout_percentage
|
|
55
|
+
|
|
56
|
+
def get_rule_id(self) -> str:
|
|
57
|
+
"""Get the rule ID for SegmentRule"""
|
|
58
|
+
return self.__rule_id
|
|
59
|
+
|
|
60
|
+
def get_rollout_type(self) -> str:
|
|
61
|
+
"""Get the rollout type for SegmentRule"""
|
|
62
|
+
return self.__rollout_type
|
|
63
|
+
|
|
64
|
+
def get_rollout_configuration(self) -> dict:
|
|
65
|
+
"""Get the rollout configuration for SegmentRule"""
|
|
66
|
+
return self.__rollout_configuration
|
|
@@ -21,6 +21,7 @@ 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
23
|
ibm_appconfiguration/configurations/internal/utils/parser.py
|
|
24
|
+
ibm_appconfiguration/configurations/internal/utils/rollout_utils.py
|
|
24
25
|
ibm_appconfiguration/configurations/internal/utils/socket.py
|
|
25
26
|
ibm_appconfiguration/configurations/internal/utils/url_builder.py
|
|
26
27
|
ibm_appconfiguration/configurations/internal/utils/validators.py
|
|
File without changes
|
|
File without changes
|
{ibm_appconfiguration_python_sdk-0.4.1 → ibm_appconfiguration_python_sdk-0.4.2}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|