From 48c30847ca55a49b24dbda918008a09255c76395 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Tue, 9 Sep 2025 20:26:44 -0700 Subject: [PATCH] Added fallback calculator --- splitio/client/client.py | 63 +++++++------- splitio/client/factory.py | 32 +++---- splitio/client/input_validator.py | 11 +-- splitio/client/util.py | 21 +---- splitio/engine/evaluator.py | 11 +-- splitio/models/fallback_config.py | 44 ++++++++++ splitio/models/fallback_treatment.py | 12 ++- tests/client/test_client.py | 122 +++++++++++++-------------- tests/client/test_config.py | 4 +- tests/client/test_factory.py | 6 +- tests/client/test_input_validator.py | 37 ++++---- tests/client/test_utils.py | 1 - tests/engine/test_evaluator.py | 19 ++--- tests/integration/test_client_e2e.py | 52 +++++++----- tests/models/test_fallback.py | 32 ++++++- 15 files changed, 256 insertions(+), 211 deletions(-) diff --git a/splitio/client/client.py b/splitio/client/client.py index 9a33e67c..9e1ddffc 100644 --- a/splitio/client/client.py +++ b/splitio/client/client.py @@ -10,7 +10,6 @@ from splitio.models.events import Event, EventWrapper from splitio.models.telemetry import get_latency_bucket_index, MethodExceptionsAndLatencies from splitio.client import input_validator -from splitio.client.util import get_fallback_treatment_and_label from splitio.util.time import get_current_epoch_time_ms, utctime_ms @@ -41,7 +40,7 @@ class ClientBase(object): # pylint: disable=too-many-instance-attributes 'impressions_disabled': False } - def __init__(self, factory, recorder, labels_enabled=True, fallback_treatments_configuration=None): + def __init__(self, factory, recorder, labels_enabled=True, fallback_treatment_calculator=None): """ Construct a Client instance. @@ -63,10 +62,10 @@ def __init__(self, factory, recorder, labels_enabled=True, fallback_treatments_c self._feature_flag_storage = factory._get_storage('splits') # pylint: disable=protected-access self._segment_storage = factory._get_storage('segments') # pylint: disable=protected-access self._events_storage = factory._get_storage('events') # pylint: disable=protected-access - self._evaluator = Evaluator(self._splitter, fallback_treatments_configuration) + self._evaluator = Evaluator(self._splitter, fallback_treatment_calculator) self._telemetry_evaluation_producer = self._factory._telemetry_evaluation_producer self._telemetry_init_producer = self._factory._telemetry_init_producer - self._fallback_treatments_configuration = fallback_treatments_configuration + self._fallback_treatment_calculator = fallback_treatment_calculator @property def ready(self): @@ -206,17 +205,17 @@ def _validate_track(self, key, traffic_type, event_type, value=None, properties= def _get_properties(self, evaluation_options): return evaluation_options.properties if evaluation_options != None else None - def _get_fallback_treatment_with_config(self, treatment, feature): - label = "" - - label, treatment, config = get_fallback_treatment_and_label(self._fallback_treatments_configuration, - feature, treatment, label, _LOGGER) - return treatment, config + def _get_fallback_treatment_with_config(self, feature): + fallback_treatment = self._fallback_treatment_calculator.resolve(feature, "") + return fallback_treatment.treatment, fallback_treatment.config def _get_fallback_eval_results(self, eval_result, feature): result = copy.deepcopy(eval_result) - result["impression"]["label"], result["treatment"], result["configurations"] = get_fallback_treatment_and_label(self._fallback_treatments_configuration, - feature, result["treatment"], result["impression"]["label"], _LOGGER) + fallback_treatment = self._fallback_treatment_calculator.resolve(feature, result["impression"]["label"]) + result["impression"]["label"] = fallback_treatment.label + result["treatment"] = fallback_treatment.treatment + result["configurations"] = fallback_treatment.config + return result def _check_impression_label(self, result): @@ -225,7 +224,7 @@ def _check_impression_label(self, result): class Client(ClientBase): # pylint: disable=too-many-instance-attributes """Entry point for the split sdk.""" - def __init__(self, factory, recorder, labels_enabled=True, fallback_treatments_configuration=None): + def __init__(self, factory, recorder, labels_enabled=True, fallback_treatment_calculator=None): """ Construct a Client instance. @@ -240,7 +239,7 @@ def __init__(self, factory, recorder, labels_enabled=True, fallback_treatments_c :rtype: Client """ - ClientBase.__init__(self, factory, recorder, labels_enabled, fallback_treatments_configuration) + ClientBase.__init__(self, factory, recorder, labels_enabled, fallback_treatment_calculator) self._context_factory = EvaluationDataFactory(factory._get_storage('splits'), factory._get_storage('segments'), factory._get_storage('rule_based_segments')) def destroy(self): @@ -275,7 +274,7 @@ def get_treatment(self, key, feature_flag_name, attributes=None, evaluation_opti except: _LOGGER.error('get_treatment failed') - treatment, _ = self._get_fallback_treatment_with_config(CONTROL, feature_flag_name) + treatment, _ = self._get_fallback_treatment_with_config(feature_flag_name) return treatment def get_treatment_with_config(self, key, feature_flag_name, attributes=None, evaluation_options=None): @@ -301,7 +300,7 @@ def get_treatment_with_config(self, key, feature_flag_name, attributes=None, eva except Exception: _LOGGER.error('get_treatment_with_config failed') - return self._get_fallback_treatment_with_config(CONTROL, feature_flag_name) + return self._get_fallback_treatment_with_config(feature_flag_name) def _get_treatment(self, method, key, feature, attributes=None, evaluation_options=None): """ @@ -321,7 +320,7 @@ def _get_treatment(self, method, key, feature, attributes=None, evaluation_optio :rtype: dict """ if not self._client_is_usable(): # not destroyed & not waiting for a fork - return self._get_fallback_treatment_with_config(CONTROL, feature) + return self._get_fallback_treatment_with_config(feature) start = get_current_epoch_time_ms() if not self.ready: @@ -331,7 +330,7 @@ def _get_treatment(self, method, key, feature, attributes=None, evaluation_optio try: key, bucketing, feature, attributes, evaluation_options = self._validate_treatment_input(key, feature, attributes, method, evaluation_options) except _InvalidInputError: - return self._get_fallback_treatment_with_config(CONTROL, feature) + return self._get_fallback_treatment_with_config(feature) result = self._get_fallback_eval_results(self._NON_READY_EVAL_RESULT, feature) @@ -376,7 +375,7 @@ def get_treatments(self, key, feature_flag_names, attributes=None, evaluation_op return {feature_flag: result[0] for (feature_flag, result) in with_config.items()} except Exception: - return {feature: self._get_fallback_treatment_with_config(CONTROL, feature)[0] for feature in feature_flag_names} + return {feature: self._get_fallback_treatment_with_config(feature)[0] for feature in feature_flag_names} def get_treatments_with_config(self, key, feature_flag_names, attributes=None, evaluation_options=None): """ @@ -400,7 +399,7 @@ def get_treatments_with_config(self, key, feature_flag_names, attributes=None, e return self._get_treatments(key, feature_flag_names, MethodExceptionsAndLatencies.TREATMENTS_WITH_CONFIG, attributes, evaluation_options) except Exception: - return {feature: (self._get_fallback_treatment_with_config(CONTROL, feature)) for feature in feature_flag_names} + return {feature: (self._get_fallback_treatment_with_config(feature)) for feature in feature_flag_names} def get_treatments_by_flag_set(self, key, flag_set, attributes=None, evaluation_options=None): """ @@ -624,7 +623,7 @@ def _get_treatments(self, key, features, method, attributes=None, evaluation_opt """ start = get_current_epoch_time_ms() if not self._client_is_usable(): - return input_validator.generate_control_treatments(features, self._fallback_treatments_configuration) + return input_validator.generate_control_treatments(features, self._fallback_treatment_calculator) if not self.ready: _LOGGER.error("Client is not ready - no calls possible") @@ -633,7 +632,7 @@ def _get_treatments(self, key, features, method, attributes=None, evaluation_opt try: key, bucketing, features, attributes, evaluation_options = self._validate_treatments_input(key, features, attributes, method, evaluation_options) except _InvalidInputError: - return input_validator.generate_control_treatments(features, self._fallback_treatments_configuration) + return input_validator.generate_control_treatments(features, self._fallback_treatment_calculator) results = {n: self._get_fallback_eval_results(self._NON_READY_EVAL_RESULT, n) for n in features} if self.ready: @@ -726,7 +725,7 @@ def track(self, key, traffic_type, event_type, value=None, properties=None): class ClientAsync(ClientBase): # pylint: disable=too-many-instance-attributes """Entry point for the split sdk.""" - def __init__(self, factory, recorder, labels_enabled=True, fallback_treatments_configuration=None): + def __init__(self, factory, recorder, labels_enabled=True, fallback_treatment_calculator=None): """ Construct a Client instance. @@ -741,7 +740,7 @@ def __init__(self, factory, recorder, labels_enabled=True, fallback_treatments_c :rtype: Client """ - ClientBase.__init__(self, factory, recorder, labels_enabled, fallback_treatments_configuration) + ClientBase.__init__(self, factory, recorder, labels_enabled, fallback_treatment_calculator) self._context_factory = AsyncEvaluationDataFactory(factory._get_storage('splits'), factory._get_storage('segments'), factory._get_storage('rule_based_segments')) async def destroy(self): @@ -776,7 +775,7 @@ async def get_treatment(self, key, feature_flag_name, attributes=None, evaluatio except: _LOGGER.error('get_treatment failed') - treatment, _ = self._get_fallback_treatment_with_config(CONTROL, feature_flag_name) + treatment, _ = self._get_fallback_treatment_with_config(feature_flag_name) return treatment async def get_treatment_with_config(self, key, feature_flag_name, attributes=None, evaluation_options=None): @@ -802,7 +801,7 @@ async def get_treatment_with_config(self, key, feature_flag_name, attributes=Non except Exception: _LOGGER.error('get_treatment_with_config failed') - return self._get_fallback_treatment_with_config(CONTROL, feature_flag_name) + return self._get_fallback_treatment_with_config(feature_flag_name) async def _get_treatment(self, method, key, feature, attributes=None, evaluation_options=None): """ @@ -822,7 +821,7 @@ async def _get_treatment(self, method, key, feature, attributes=None, evaluation :rtype: dict """ if not self._client_is_usable(): # not destroyed & not waiting for a fork - return self._get_fallback_treatment_with_config(CONTROL, feature) + return self._get_fallback_treatment_with_config(feature) start = get_current_epoch_time_ms() if not self.ready: @@ -832,7 +831,7 @@ async def _get_treatment(self, method, key, feature, attributes=None, evaluation try: key, bucketing, feature, attributes, evaluation_options = self._validate_treatment_input(key, feature, attributes, method, evaluation_options) except _InvalidInputError: - return self._get_fallback_treatment_with_config(CONTROL, feature) + return self._get_fallback_treatment_with_config(feature) result = self._get_fallback_eval_results(self._NON_READY_EVAL_RESULT, feature) if self.ready: @@ -875,7 +874,7 @@ async def get_treatments(self, key, feature_flag_names, attributes=None, evaluat return {feature_flag: result[0] for (feature_flag, result) in with_config.items()} except Exception: - return {feature: self._get_fallback_treatment_with_config(CONTROL, feature)[0] for feature in feature_flag_names} + return {feature: self._get_fallback_treatment_with_config(feature)[0] for feature in feature_flag_names} async def get_treatments_with_config(self, key, feature_flag_names, attributes=None, evaluation_options=None): """ @@ -899,7 +898,7 @@ async def get_treatments_with_config(self, key, feature_flag_names, attributes=N return await self._get_treatments(key, feature_flag_names, MethodExceptionsAndLatencies.TREATMENTS_WITH_CONFIG, attributes, evaluation_options) except Exception: - return {feature: (self._get_fallback_treatment_with_config(CONTROL, feature)) for feature in feature_flag_names} + return {feature: (self._get_fallback_treatment_with_config(feature)) for feature in feature_flag_names} async def get_treatments_by_flag_set(self, key, flag_set, attributes=None, evaluation_options=None): """ @@ -1037,7 +1036,7 @@ async def _get_treatments(self, key, features, method, attributes=None, evaluati """ start = get_current_epoch_time_ms() if not self._client_is_usable(): - return input_validator.generate_control_treatments(features, self._fallback_treatments_configuration) + return input_validator.generate_control_treatments(features, self._fallback_treatment_calculator) if not self.ready: _LOGGER.error("Client is not ready - no calls possible") @@ -1046,7 +1045,7 @@ async def _get_treatments(self, key, features, method, attributes=None, evaluati try: key, bucketing, features, attributes, evaluation_options = self._validate_treatments_input(key, features, attributes, method, evaluation_options) except _InvalidInputError: - return input_validator.generate_control_treatments(features, self._fallback_treatments_configuration) + return input_validator.generate_control_treatments(features, self._fallback_treatment_calculator) results = {n: self._get_fallback_eval_results(self._NON_READY_EVAL_RESULT, n) for n in features} if self.ready: diff --git a/splitio/client/factory.py b/splitio/client/factory.py index e06d6cf9..6c7ce990 100644 --- a/splitio/client/factory.py +++ b/splitio/client/factory.py @@ -18,7 +18,7 @@ TelemetryStorageProducerAsync, TelemetryStorageConsumerAsync from splitio.engine.impressions.manager import Counter as ImpressionsCounter from splitio.engine.impressions.unique_keys_tracker import UniqueKeysTracker, UniqueKeysTrackerAsync - +from splitio.models.fallback_config import FallbackTreatmentCalculator # Storage from splitio.storage.inmemmory import InMemorySplitStorage, InMemorySegmentStorage, \ InMemoryImpressionStorage, InMemoryEventStorage, InMemoryTelemetryStorage, LocalhostTelemetryStorage, \ @@ -171,7 +171,7 @@ def __init__( # pylint: disable=too-many-arguments telemetry_init_producer=None, telemetry_submitter=None, preforked_initialization=False, - fallback_treatments_configuration=None + fallback_treatment_calculator=None ): """ Class constructor. @@ -202,7 +202,7 @@ def __init__( # pylint: disable=too-many-arguments self._ready_time = get_current_epoch_time_ms() _LOGGER.debug("Running in threading mode") self._sdk_internal_ready_flag = sdk_ready_flag - self._fallback_treatments_configuration = fallback_treatments_configuration + self._fallback_treatment_calculator = fallback_treatment_calculator self._start_status_updater() def _start_status_updater(self): @@ -244,7 +244,7 @@ def client(self): This client is only a set of references to structures hold by the factory. Creating one a fast operation and safe to be used anywhere. """ - return Client(self, self._recorder, self._labels_enabled, self._fallback_treatments_configuration) + return Client(self, self._recorder, self._labels_enabled, self._fallback_treatment_calculator) def manager(self): """ @@ -341,7 +341,7 @@ def __init__( # pylint: disable=too-many-arguments telemetry_submitter=None, manager_start_task=None, api_client=None, - fallback_treatments_configuration=None + fallback_treatment_calculator=None ): """ Class constructor. @@ -375,7 +375,7 @@ def __init__( # pylint: disable=too-many-arguments self._sdk_ready_flag = asyncio.Event() self._ready_task = asyncio.get_running_loop().create_task(self._update_status_when_ready_async()) self._api_client = api_client - self._fallback_treatments_configuration = fallback_treatments_configuration + self._fallback_treatment_calculator = fallback_treatment_calculator async def _update_status_when_ready_async(self): """Wait until the sdk is ready and update the status for async mode.""" @@ -464,7 +464,7 @@ def client(self): This client is only a set of references to structures hold by the factory. Creating one a fast operation and safe to be used anywhere. """ - return ClientAsync(self, self._recorder, self._labels_enabled, self._fallback_treatments_configuration) + return ClientAsync(self, self._recorder, self._labels_enabled, self._fallback_treatment_calculator) def _wrap_impression_listener(listener, metadata): """ @@ -628,7 +628,7 @@ def _build_in_memory_factory(api_key, cfg, sdk_url=None, events_url=None, # pyl return SplitFactory(api_key, storages, cfg['labelsEnabled'], recorder, manager, None, telemetry_producer, telemetry_init_producer, telemetry_submitter, preforked_initialization=preforked_initialization, - fallback_treatments_configuration=cfg['fallbackTreatments']) + fallback_treatment_calculator=FallbackTreatmentCalculator(cfg['fallbackTreatments'])) initialization_thread = threading.Thread(target=manager.start, name="SDKInitializer", daemon=True) initialization_thread.start() @@ -636,7 +636,7 @@ def _build_in_memory_factory(api_key, cfg, sdk_url=None, events_url=None, # pyl return SplitFactory(api_key, storages, cfg['labelsEnabled'], recorder, manager, sdk_ready_flag, telemetry_producer, telemetry_init_producer, - telemetry_submitter, fallback_treatments_configuration=cfg['fallbackTreatments']) + telemetry_submitter, fallback_treatment_calculator = FallbackTreatmentCalculator(cfg['fallbackTreatments'])) async def _build_in_memory_factory_async(api_key, cfg, sdk_url=None, events_url=None, # pylint:disable=too-many-arguments,too-many-localsa auth_api_base_url=None, streaming_api_base_url=None, telemetry_api_base_url=None, @@ -755,7 +755,7 @@ async def _build_in_memory_factory_async(api_key, cfg, sdk_url=None, events_url= recorder, manager, telemetry_producer, telemetry_init_producer, telemetry_submitter, manager_start_task=manager_start_task, - api_client=http_client, fallback_treatments_configuration=cfg['fallbackTreatments']) + api_client=http_client, fallback_treatment_calculator=FallbackTreatmentCalculator(cfg['fallbackTreatments'])) def _build_redis_factory(api_key, cfg): """Build and return a split factory with redis-based storage.""" @@ -834,7 +834,7 @@ def _build_redis_factory(api_key, cfg): sdk_ready_flag=None, telemetry_producer=telemetry_producer, telemetry_init_producer=telemetry_init_producer, - fallback_treatments_configuration=cfg['fallbackTreatments'] + fallback_treatment_calculator=FallbackTreatmentCalculator(cfg['fallbackTreatments']) ) redundant_factory_count, active_factory_count = _get_active_and_redundant_count() storages['telemetry'].record_active_and_redundant_factories(active_factory_count, redundant_factory_count) @@ -917,7 +917,7 @@ async def _build_redis_factory_async(api_key, cfg): telemetry_producer=telemetry_producer, telemetry_init_producer=telemetry_init_producer, telemetry_submitter=telemetry_submitter, - fallback_treatments_configuration=cfg['fallbackTreatments'] + fallback_treatment_calculator=FallbackTreatmentCalculator(cfg['fallbackTreatments']) ) redundant_factory_count, active_factory_count = _get_active_and_redundant_count() await storages['telemetry'].record_active_and_redundant_factories(active_factory_count, redundant_factory_count) @@ -1000,7 +1000,7 @@ def _build_pluggable_factory(api_key, cfg): sdk_ready_flag=None, telemetry_producer=telemetry_producer, telemetry_init_producer=telemetry_init_producer, - fallback_treatments_configuration=cfg['fallbackTreatments'] + fallback_treatment_calculator=FallbackTreatmentCalculator(cfg['fallbackTreatments']) ) redundant_factory_count, active_factory_count = _get_active_and_redundant_count() storages['telemetry'].record_active_and_redundant_factories(active_factory_count, redundant_factory_count) @@ -1081,7 +1081,7 @@ async def _build_pluggable_factory_async(api_key, cfg): telemetry_producer=telemetry_producer, telemetry_init_producer=telemetry_init_producer, telemetry_submitter=telemetry_submitter, - fallback_treatments_configuration=cfg['fallbackTreatments'] + fallback_treatment_calculator=FallbackTreatmentCalculator(cfg['fallbackTreatments']) ) redundant_factory_count, active_factory_count = _get_active_and_redundant_count() await storages['telemetry'].record_active_and_redundant_factories(active_factory_count, redundant_factory_count) @@ -1159,7 +1159,7 @@ def _build_localhost_factory(cfg): telemetry_producer=telemetry_producer, telemetry_init_producer=telemetry_producer.get_telemetry_init_producer(), telemetry_submitter=LocalhostTelemetrySubmitter(), - fallback_treatments_configuration=cfg['fallbackTreatments'] + fallback_treatment_calculator=FallbackTreatmentCalculator(cfg['fallbackTreatments']) ) async def _build_localhost_factory_async(cfg): @@ -1231,7 +1231,7 @@ async def _build_localhost_factory_async(cfg): telemetry_init_producer=telemetry_producer.get_telemetry_init_producer(), telemetry_submitter=LocalhostTelemetrySubmitterAsync(), manager_start_task=manager_start_task, - fallback_treatments_configuration=cfg['fallbackTreatments'] + fallback_treatment_calculator=FallbackTreatmentCalculator(cfg['fallbackTreatments']) ) def get_factory(api_key, **kwargs): diff --git a/splitio/client/input_validator.py b/splitio/client/input_validator.py index aaaf8026..dfded942 100644 --- a/splitio/client/input_validator.py +++ b/splitio/client/input_validator.py @@ -7,7 +7,6 @@ from splitio.client.key import Key from splitio.client import client -from splitio.client.util import get_fallback_treatment_and_label from splitio.engine.evaluator import CONTROL from splitio.models.fallback_treatment import FallbackTreatment @@ -503,7 +502,7 @@ def validate_feature_flags_get_treatments( # pylint: disable=invalid-name valid_feature_flags.append(ff) return valid_feature_flags -def generate_control_treatments(feature_flags, fallback_treatments_configuration): +def generate_control_treatments(feature_flags, fallback_treatment_calculator): """ Generate valid feature flags to control. @@ -518,11 +517,9 @@ def generate_control_treatments(feature_flags, fallback_treatments_configuration to_return = {} for feature_flag in feature_flags: if isinstance(feature_flag, str) and len(feature_flag.strip())> 0: - treatment = CONTROL - config = None - label = "" - label, treatment, config = get_fallback_treatment_and_label(fallback_treatments_configuration, - feature_flag, treatment, label, _LOGGER) + fallback_treatment = fallback_treatment_calculator.resolve(feature_flag, "") + treatment = fallback_treatment.treatment + config = fallback_treatment.config to_return[feature_flag] = (treatment, config) return to_return diff --git a/splitio/client/util.py b/splitio/client/util.py index 1f01de3f..b5b693cb 100644 --- a/splitio/client/util.py +++ b/splitio/client/util.py @@ -50,23 +50,4 @@ def get_metadata(config): """ version = 'python-%s' % __version__ ip_address, hostname = _get_hostname_and_ip(config) - return SdkMetadata(version, hostname, ip_address) - -def get_fallback_treatment_and_label(fallback_treatments_configuration, feature_name, treatment, label, _logger): - if fallback_treatments_configuration == None: - return label, treatment, None - - if fallback_treatments_configuration.by_flag_fallback_treatment != None and \ - fallback_treatments_configuration.by_flag_fallback_treatment.get(feature_name) != None: - _logger.debug('Using Fallback Treatment for feature: %s', feature_name) - return fallback_treatments_configuration.by_flag_fallback_treatment.get(feature_name).label_prefix + label, \ - fallback_treatments_configuration.by_flag_fallback_treatment.get(feature_name).treatment, \ - fallback_treatments_configuration.by_flag_fallback_treatment.get(feature_name).config - - if fallback_treatments_configuration.global_fallback_treatment != None: - _logger.debug('Using Global Fallback Treatment.') - return fallback_treatments_configuration.global_fallback_treatment.label_prefix + label, \ - fallback_treatments_configuration.global_fallback_treatment.treatment, \ - fallback_treatments_configuration.global_fallback_treatment.config - - return label, treatment, None + return SdkMetadata(version, hostname, ip_address) \ No newline at end of file diff --git a/splitio/engine/evaluator.py b/splitio/engine/evaluator.py index 2a564d3a..017b5e74 100644 --- a/splitio/engine/evaluator.py +++ b/splitio/engine/evaluator.py @@ -2,7 +2,6 @@ import logging from collections import namedtuple -from splitio.client.util import get_fallback_treatment_and_label from splitio.models.impressions import Label from splitio.models.grammar.condition import ConditionType from splitio.models.grammar.matchers.misc import DependencyMatcher @@ -21,7 +20,7 @@ class Evaluator(object): # pylint: disable=too-few-public-methods """Split Evaluator class.""" - def __init__(self, splitter, fallback_treatments_configuration=None): + def __init__(self, splitter, fallback_treatment_calculator=None): """ Construct a Evaluator instance. @@ -29,7 +28,7 @@ def __init__(self, splitter, fallback_treatments_configuration=None): :type splitter: splitio.engine.splitters.Splitters """ self._splitter = splitter - self._fallback_treatments_configuration = fallback_treatments_configuration + self._fallback_treatment_calculator = fallback_treatment_calculator def eval_many_with_context(self, key, bucketing, features, attrs, ctx): """ @@ -53,8 +52,10 @@ def eval_with_context(self, key, bucketing, feature_name, attrs, ctx): if not feature: _LOGGER.warning('Unknown or invalid feature: %s', feature) label = Label.SPLIT_NOT_FOUND - label, _treatment, config = get_fallback_treatment_and_label(self._fallback_treatments_configuration, - feature_name, _treatment, label, _LOGGER) + fallback_treatment = self._fallback_treatment_calculator.resolve(feature_name, label) + label = fallback_treatment.label + _treatment = fallback_treatment.treatment + config = fallback_treatment.config else: _change_number = feature.change_number if feature.killed: diff --git a/splitio/models/fallback_config.py b/splitio/models/fallback_config.py index 14b00dda..aba7ad7b 100644 --- a/splitio/models/fallback_config.py +++ b/splitio/models/fallback_config.py @@ -1,4 +1,6 @@ """Segment module.""" +from splitio.models.fallback_treatment import FallbackTreatment +from splitio.client.client import CONTROL class FallbackTreatmentsConfiguration(object): """FallbackTreatmentsConfiguration object class.""" @@ -35,3 +37,45 @@ def by_flag_fallback_treatment(self): def by_flag_fallback_treatment(self, new_value): """Set global fallback treatment.""" self.by_flag_fallback_treatment = new_value + +class FallbackTreatmentCalculator(object): + """FallbackTreatmentCalculator object class.""" + + def __init__(self, fallback_treatment_configuration): + """ + Class constructor. + + :param fallback_treatment_configuration: fallback treatment configuration + :type fallback_treatment_configuration: FallbackTreatmentsConfiguration + """ + self._label_prefix = "fallback - " + self._fallback_treatments_configuration = fallback_treatment_configuration + + @property + def fallback_treatments_configuration(self): + """Return fallback treatment configuration.""" + return self._fallback_treatments_configuration + + def resolve(self, flag_name, label): + if self._fallback_treatments_configuration != None: + if self._fallback_treatments_configuration.by_flag_fallback_treatment != None \ + and self._fallback_treatments_configuration.by_flag_fallback_treatment.get(flag_name) != None: + return self._copy_with_label(self._fallback_treatments_configuration.by_flag_fallback_treatment.get(flag_name), \ + self._resolve_label(label)) + + if self._fallback_treatments_configuration.global_fallback_treatment != None: + return self._copy_with_label(self._fallback_treatments_configuration.global_fallback_treatment, \ + self._resolve_label(label)) + + return FallbackTreatment(CONTROL, None, label) + + def _resolve_label(self, label): + if label == None: + return None + + return self._label_prefix + label + + def _copy_with_label(self, fallback_treatment, label): + return FallbackTreatment(fallback_treatment.treatment, fallback_treatment.config, label) + + \ No newline at end of file diff --git a/splitio/models/fallback_treatment.py b/splitio/models/fallback_treatment.py index c8e60001..19b58665 100644 --- a/splitio/models/fallback_treatment.py +++ b/splitio/models/fallback_treatment.py @@ -4,7 +4,7 @@ class FallbackTreatment(object): """Segment object class.""" - def __init__(self, treatment, config=None): + def __init__(self, treatment, config=None, label=None): """ Class constructor. @@ -15,10 +15,8 @@ def __init__(self, treatment, config=None): :type config: json """ self._treatment = treatment - self._config = None - if config != None: - self._config = json.dumps(config) - self._label_prefix = "fallback - " + self._config = config + self._label = label @property def treatment(self): @@ -31,6 +29,6 @@ def config(self): return self._config @property - def label_prefix(self): + def label(self): """Return label prefix.""" - return self._label_prefix \ No newline at end of file + return self._label \ No newline at end of file diff --git a/tests/client/test_client.py b/tests/client/test_client.py index 75f46464..15c2b96b 100644 --- a/tests/client/test_client.py +++ b/tests/client/test_client.py @@ -9,7 +9,7 @@ from splitio.client.client import Client, _LOGGER as _logger, CONTROL, ClientAsync, EvaluationOptions from splitio.client.factory import SplitFactory, Status as FactoryStatus, SplitFactoryAsync -from splitio.models.fallback_config import FallbackTreatmentsConfiguration +from splitio.models.fallback_config import FallbackTreatmentsConfiguration, FallbackTreatmentCalculator from splitio.models.fallback_treatment import FallbackTreatment from splitio.models.impressions import Impression, Label from splitio.models.events import Event, EventWrapper @@ -76,7 +76,7 @@ def synchronize_config(*_): factory.block_until_ready(5) split_storage.update([from_raw(splits_json['splitChange1_1']['ff']['d'][0])], [], -1) - client = Client(factory, recorder, True) + client = Client(factory, recorder, True, FallbackTreatmentCalculator(None)) client._evaluator = mocker.Mock(spec=Evaluator) client._evaluator.eval_with_context.return_value = { 'treatment': 'on', @@ -148,7 +148,7 @@ def synchronize_config(*_): mocker.patch('splitio.client.client.get_latency_bucket_index', new=lambda x: 5) split_storage.update([from_raw(splits_json['splitChange1_1']['ff']['d'][0])], [], -1) - client = Client(factory, recorder, True) + client = Client(factory, recorder, True, FallbackTreatmentCalculator(None)) client._evaluator = mocker.Mock(spec=Evaluator) client._evaluator.eval_with_context.return_value = { 'treatment': 'on', @@ -225,7 +225,7 @@ def synchronize_config(*_): mocker.patch('splitio.client.client.utctime_ms', new=lambda: 1000) mocker.patch('splitio.client.client.get_latency_bucket_index', new=lambda x: 5) - client = Client(factory, recorder, True) + client = Client(factory, recorder, True, FallbackTreatmentCalculator(None)) client._evaluator = mocker.Mock(spec=Evaluator) evaluation = { 'treatment': 'on', @@ -305,7 +305,7 @@ def synchronize_config(*_): mocker.patch('splitio.client.client.utctime_ms', new=lambda: 1000) mocker.patch('splitio.client.client.get_latency_bucket_index', new=lambda x: 5) - client = Client(factory, recorder, True) + client = Client(factory, recorder, True, FallbackTreatmentCalculator(None)) client._evaluator = mocker.Mock(spec=Evaluator) evaluation = { 'treatment': 'on', @@ -384,7 +384,7 @@ def synchronize_config(*_): mocker.patch('splitio.client.client.utctime_ms', new=lambda: 1000) mocker.patch('splitio.client.client.get_latency_bucket_index', new=lambda x: 5) - client = Client(factory, recorder, True) + client = Client(factory, recorder, True, FallbackTreatmentCalculator(None)) client._evaluator = mocker.Mock(spec=Evaluator) evaluation = { 'treatment': 'on', @@ -462,7 +462,7 @@ def synchronize_config(*_): mocker.patch('splitio.client.client.utctime_ms', new=lambda: 1000) mocker.patch('splitio.client.client.get_latency_bucket_index', new=lambda x: 5) - client = Client(factory, recorder, True) + client = Client(factory, recorder, True, FallbackTreatmentCalculator(None)) client._evaluator = mocker.Mock(spec=Evaluator) evaluation = { 'treatment': 'on', @@ -545,7 +545,7 @@ def synchronize_config(*_): mocker.patch('splitio.client.client.utctime_ms', new=lambda: 1000) mocker.patch('splitio.client.client.get_latency_bucket_index', new=lambda x: 5) - client = Client(factory, recorder, True) + client = Client(factory, recorder, True, FallbackTreatmentCalculator(None)) client._evaluator = mocker.Mock(spec=Evaluator) evaluation = { 'treatment': 'on', @@ -625,7 +625,7 @@ def synchronize_config(*_): mocker.patch('splitio.client.client.utctime_ms', new=lambda: 1000) mocker.patch('splitio.client.client.get_latency_bucket_index', new=lambda x: 5) - client = Client(factory, recorder, True) + client = Client(factory, recorder, True, FallbackTreatmentCalculator(None)) client._evaluator = mocker.Mock(spec=Evaluator) evaluation = { 'treatment': 'on', @@ -712,7 +712,7 @@ def synchronize_config(*_): from_raw(splits_json['splitChange1_1']['ff']['d'][1]), from_raw(splits_json['splitChange1_1']['ff']['d'][2]) ], [], -1) - client = Client(factory, recorder, True) + client = Client(factory, recorder, True, FallbackTreatmentCalculator(None)) assert client.get_treatment('some_key', 'SPLIT_1') == 'off' assert client.get_treatment('some_key', 'SPLIT_2') == 'on' assert client.get_treatment('some_key', 'SPLIT_3') == 'on' @@ -776,7 +776,7 @@ def synchronize_config(*_): from_raw(splits_json['splitChange1_1']['ff']['d'][1]), from_raw(splits_json['splitChange1_1']['ff']['d'][2]) ], [], -1) - client = Client(factory, recorder, True) + client = Client(factory, recorder, True, FallbackTreatmentCalculator(None)) assert client.get_treatment('some_key', 'SPLIT_1') == 'off' assert client.get_treatment('some_key', 'SPLIT_2') == 'on' assert client.get_treatment('some_key', 'SPLIT_3') == 'on' @@ -840,7 +840,7 @@ def synchronize_config(*_): from_raw(splits_json['splitChange1_1']['ff']['d'][1]), from_raw(splits_json['splitChange1_1']['ff']['d'][2]) ], [], -1) - client = Client(factory, recorder, True) + client = Client(factory, recorder, True, FallbackTreatmentCalculator(None)) assert client.get_treatment('some_key', 'SPLIT_1') == 'off' assert client.get_treatment('some_key', 'SPLIT_2') == 'on' assert client.get_treatment('some_key', 'SPLIT_3') == 'on' @@ -881,7 +881,7 @@ def synchronize_config(*_): pass factory._telemetry_submitter = TelemetrySubmitterMock() - client = Client(factory, recorder, True) + client = Client(factory, recorder, True, FallbackTreatmentCalculator(None)) client.destroy() assert client.destroyed is not None assert(mocker.called) @@ -923,7 +923,7 @@ def synchronize_config(*_): factory._apikey = 'test' mocker.patch('splitio.client.client.utctime_ms', new=lambda: 1000) - client = Client(factory, recorder, True) + client = Client(factory, recorder, True, FallbackTreatmentCalculator(None)) assert client.track('key', 'user', 'purchase', 12) is True assert mocker.call([ EventWrapper( @@ -972,7 +972,7 @@ def synchronize_config(*_): mocker.call('Client is not ready - no calls possible') ] - client = Client(factory, mocker.Mock()) + client = Client(factory, mocker.Mock(), mocker.Mock(), FallbackTreatmentCalculator(None)) _logger = mocker.Mock() mocker.patch('splitio.client.client._LOGGER', new=_logger) @@ -1044,7 +1044,7 @@ def synchronize_config(*_): pass factory._telemetry_submitter = TelemetrySubmitterMock() - client = Client(factory, mocker.Mock()) + client = Client(factory, mocker.Mock(), mocker.Mock(), FallbackTreatmentCalculator(None)) client.ready = False assert client.get_treatment('some_key', 'SPLIT_2') == CONTROL assert(telemetry_storage._tel_config._not_ready == 1) @@ -1096,7 +1096,7 @@ def stop(*_): ready_property = mocker.PropertyMock() ready_property.return_value = True type(factory).ready = ready_property - client = Client(factory, recorder, True) + client = Client(factory, recorder, True, FallbackTreatmentCalculator(None)) def _raise(*_): raise RuntimeError('something') client._evaluator.eval_many_with_context = _raise @@ -1192,7 +1192,7 @@ def stop(*_): pass factory._sync_manager.stop = stop - client = Client(factory, recorder, True) + client = Client(factory, recorder, True, FallbackTreatmentCalculator(None)) assert client.get_treatment('key', 'SPLIT_2') == 'on' assert(telemetry_storage._method_latencies._treatment[0] == 1) @@ -1258,7 +1258,7 @@ def synchronize_config(*_): pass factory._telemetry_submitter = TelemetrySubmitterMock() - client = Client(factory, recorder, True) + client = Client(factory, recorder, True, FallbackTreatmentCalculator(None)) try: client.track('key', 'tt', 'ev') except: @@ -1311,7 +1311,7 @@ def synchronize_config(*_): factory.block_until_ready(5) split_storage.update([from_raw(splits_json['splitChange1_1']['ff']['d'][0])], [], -1) - client = Client(factory, recorder, True) + client = Client(factory, recorder, True, FallbackTreatmentCalculator(None)) client._evaluator = mocker.Mock(spec=Evaluator) evaluation = { 'treatment': 'on', @@ -1419,7 +1419,7 @@ class TelemetrySubmitterMock(): def synchronize_config(*_): pass factory._telemetry_submitter = TelemetrySubmitterMock() - client = Client(factory, recorder, True, FallbackTreatmentsConfiguration(FallbackTreatment("on-global", {"prop":"val"}))) + client = Client(factory, recorder, True, FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(FallbackTreatment("on-global", '{"prop": "val"}')))) def get_feature_flag_names_by_flag_sets(*_): return ["some", "some2"] @@ -1446,7 +1446,7 @@ def get_feature_flag_names_by_flag_sets(*_): assert(client.get_treatments_with_config_by_flag_sets("key_m", ["set"]) == {"some": ("on-global", '{"prop": "val"}'), "some2": ("on-global", '{"prop": "val"}')}) self.imps = None - client._fallback_treatments_configuration = FallbackTreatmentsConfiguration(FallbackTreatment("on-global", {"prop":"val"}), {'some': FallbackTreatment("on-local")}) + client._fallback_treatment_calculator = FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(FallbackTreatment("on-global", '{"prop": "val"}'), {'some': FallbackTreatment("on-local")})) treatment = client.get_treatment("key2", "some") assert(treatment == "on-local") assert(self.imps[0].treatment == "on-local") @@ -1475,7 +1475,7 @@ def get_feature_flag_names_by_flag_sets(*_): assert(client.get_treatments_with_config_by_flag_sets("key_m", ["set"]) == {"some": ("on-local", None), "some2": ("on-global", '{"prop": "val"}')}) self.imps = None - client._fallback_treatments_configuration = FallbackTreatmentsConfiguration(None, {'some': FallbackTreatment("on-local", {"prop":"val"})}) + client._fallback_treatment_calculator = FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(None, {'some': FallbackTreatment("on-local", '{"prop": "val"}')})) treatment = client.get_treatment("key3", "some") assert(treatment == "on-local") assert(self.imps[0].treatment == "on-local") @@ -1504,7 +1504,7 @@ def get_feature_flag_names_by_flag_sets(*_): assert(client.get_treatments_with_config_by_flag_sets("key_m", ["set"]) == {"some": ("on-local", '{"prop": "val"}'), "some2": ("control", None)}) self.imps = None - client._fallback_treatments_configuration = FallbackTreatmentsConfiguration(None, {'some2': FallbackTreatment("on-local")}) + client._fallback_treatment_calculator = FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(None, {'some2': FallbackTreatment("on-local")})) treatment = client.get_treatment("key4", "some") assert(treatment == "control") assert(self.imps[0].treatment == "control") @@ -1557,25 +1557,25 @@ class TelemetrySubmitterMock(): def synchronize_config(*_): pass factory._telemetry_submitter = TelemetrySubmitterMock() - client = Client(factory, recorder, True, FallbackTreatmentsConfiguration(FallbackTreatment("on-global"))) + client = Client(factory, recorder, True, FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(FallbackTreatment("on-global")))) treatment = client.get_treatment("key", "some") assert(treatment == "on-global") assert(self.imps == None) self.imps = None - client._fallback_treatments_configuration = FallbackTreatmentsConfiguration(FallbackTreatment("on-global"), {'some': FallbackTreatment("on-local")}) + client._fallback_treatment_calculator = FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(FallbackTreatment("on-global"), {'some': FallbackTreatment("on-local")})) treatment = client.get_treatment("key2", "some") assert(treatment == "on-local") assert(self.imps == None) self.imps = None - client._fallback_treatments_configuration = FallbackTreatmentsConfiguration(None, {'some': FallbackTreatment("on-local")}) + client._fallback_treatment_calculator = FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(None, {'some': FallbackTreatment("on-local")})) treatment = client.get_treatment("key3", "some") assert(treatment == "on-local") assert(self.imps == None) self.imps = None - client._fallback_treatments_configuration = FallbackTreatmentsConfiguration(None, {'some2': FallbackTreatment("on-local")}) + client._fallback_treatment_calculator = FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(None, {'some2': FallbackTreatment("on-local")})) treatment = client.get_treatment("key4", "some") assert(treatment == "control") assert(self.imps == None) @@ -1625,7 +1625,7 @@ class TelemetrySubmitterMock(): def synchronize_config(*_): pass factory._telemetry_submitter = TelemetrySubmitterMock() - client = Client(factory, recorder, True, FallbackTreatmentsConfiguration(FallbackTreatment("on-global"))) + client = Client(factory, recorder, True, FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(FallbackTreatment("on-global")))) client.ready = False treatment = client.get_treatment("key", "some") @@ -1633,21 +1633,21 @@ def synchronize_config(*_): assert(self.imps[0].label == "fallback - not ready") self.imps = None - client._fallback_treatments_configuration = FallbackTreatmentsConfiguration(FallbackTreatment("on-global"), {'some': FallbackTreatment("on-local")}) + client._fallback_treatment_calculator = FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(FallbackTreatment("on-global"), {'some': FallbackTreatment("on-local")})) treatment = client.get_treatment("key2", "some") assert(treatment == "on-local") assert(self.imps[0].treatment == "on-local") assert(self.imps[0].label == "fallback - not ready") self.imps = None - client._fallback_treatments_configuration = FallbackTreatmentsConfiguration(None, {'some': FallbackTreatment("on-local")}) + client._fallback_treatment_calculator = FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(None, {'some': FallbackTreatment("on-local")})) treatment = client.get_treatment("key3", "some") assert(treatment == "on-local") assert(self.imps[0].treatment == "on-local") assert(self.imps[0].label == "fallback - not ready") self.imps = None - client._fallback_treatments_configuration = FallbackTreatmentsConfiguration(None, {'some2': FallbackTreatment("on-local")}) + client._fallback_treatment_calculator = FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(None, {'some2': FallbackTreatment("on-local")})) treatment = client.get_treatment("key4", "some") assert(treatment == "control") assert(self.imps[0].treatment == "control") @@ -1700,7 +1700,7 @@ async def synchronize_config(*_): ) await factory.block_until_ready(1) - client = ClientAsync(factory, recorder, True) + client = ClientAsync(factory, recorder, True, FallbackTreatmentCalculator(None)) client._evaluator = mocker.Mock(spec=Evaluator) client._evaluator.eval_with_context.return_value = { 'treatment': 'on', @@ -1772,7 +1772,7 @@ async def synchronize_config(*_): mocker.patch('splitio.client.client.get_latency_bucket_index', new=lambda x: 5) await factory.block_until_ready(1) - client = ClientAsync(factory, recorder, True) + client = ClientAsync(factory, recorder, True, FallbackTreatmentCalculator(None)) client._evaluator = mocker.Mock(spec=Evaluator) client._evaluator.eval_with_context.return_value = { 'treatment': 'on', @@ -1849,7 +1849,7 @@ async def synchronize_config(*_): mocker.patch('splitio.client.client.get_latency_bucket_index', new=lambda x: 5) await factory.block_until_ready(1) - client = ClientAsync(factory, recorder, True) + client = ClientAsync(factory, recorder, True, FallbackTreatmentCalculator(None)) client._evaluator = mocker.Mock(spec=Evaluator) evaluation = { 'treatment': 'on', @@ -1929,7 +1929,7 @@ async def synchronize_config(*_): mocker.patch('splitio.client.client.get_latency_bucket_index', new=lambda x: 5) await factory.block_until_ready(1) - client = ClientAsync(factory, recorder, True) + client = ClientAsync(factory, recorder, True, FallbackTreatmentCalculator(None)) client._evaluator = mocker.Mock(spec=Evaluator) evaluation = { 'treatment': 'on', @@ -2009,7 +2009,7 @@ async def synchronize_config(*_): mocker.patch('splitio.client.client.get_latency_bucket_index', new=lambda x: 5) await factory.block_until_ready(1) - client = ClientAsync(factory, recorder, True) + client = ClientAsync(factory, recorder, True, FallbackTreatmentCalculator(None)) client._evaluator = mocker.Mock(spec=Evaluator) evaluation = { 'treatment': 'on', @@ -2088,7 +2088,7 @@ async def synchronize_config(*_): mocker.patch('splitio.client.client.get_latency_bucket_index', new=lambda x: 5) await factory.block_until_ready(1) - client = ClientAsync(factory, recorder, True) + client = ClientAsync(factory, recorder, True, FallbackTreatmentCalculator(None)) client._evaluator = mocker.Mock(spec=Evaluator) evaluation = { 'treatment': 'on', @@ -2172,7 +2172,7 @@ async def synchronize_config(*_): mocker.patch('splitio.client.client.get_latency_bucket_index', new=lambda x: 5) await factory.block_until_ready(1) - client = ClientAsync(factory, recorder, True) + client = ClientAsync(factory, recorder, True, FallbackTreatmentCalculator(None)) client._evaluator = mocker.Mock(spec=Evaluator) evaluation = { 'treatment': 'on', @@ -2256,7 +2256,7 @@ async def synchronize_config(*_): mocker.patch('splitio.client.client.get_latency_bucket_index', new=lambda x: 5) await factory.block_until_ready(1) - client = ClientAsync(factory, recorder, True) + client = ClientAsync(factory, recorder, True, FallbackTreatmentCalculator(None)) client._evaluator = mocker.Mock(spec=Evaluator) evaluation = { 'treatment': 'on', @@ -2342,7 +2342,7 @@ async def test_impression_toggle_optimized(self, mocker): from_raw(splits_json['splitChange1_1']['ff']['d'][1]), from_raw(splits_json['splitChange1_1']['ff']['d'][2]) ], [], -1) - client = ClientAsync(factory, recorder, True) + client = ClientAsync(factory, recorder, True, FallbackTreatmentCalculator(None)) treatment = await client.get_treatment('some_key', 'SPLIT_1') assert treatment == 'off' treatment = await client.get_treatment('some_key', 'SPLIT_2') @@ -2405,7 +2405,7 @@ async def test_impression_toggle_debug(self, mocker): from_raw(splits_json['splitChange1_1']['ff']['d'][1]), from_raw(splits_json['splitChange1_1']['ff']['d'][2]) ], [], -1) - client = ClientAsync(factory, recorder, True) + client = ClientAsync(factory, recorder, True, FallbackTreatmentCalculator(None)) assert await client.get_treatment('some_key', 'SPLIT_1') == 'off' assert await client.get_treatment('some_key', 'SPLIT_2') == 'on' assert await client.get_treatment('some_key', 'SPLIT_3') == 'on' @@ -2465,7 +2465,7 @@ async def test_impression_toggle_none(self, mocker): from_raw(splits_json['splitChange1_1']['ff']['d'][1]), from_raw(splits_json['splitChange1_1']['ff']['d'][2]) ], [], -1) - client = ClientAsync(factory, recorder, True) + client = ClientAsync(factory, recorder, True, FallbackTreatmentCalculator(None)) assert await client.get_treatment('some_key', 'SPLIT_1') == 'off' assert await client.get_treatment('some_key', 'SPLIT_2') == 'on' assert await client.get_treatment('some_key', 'SPLIT_3') == 'on' @@ -2516,7 +2516,7 @@ async def synchronize_config(*_): mocker.patch('splitio.client.client.utctime_ms', new=lambda: 1000) await factory.block_until_ready(1) - client = ClientAsync(factory, recorder, True) + client = ClientAsync(factory, recorder, True, FallbackTreatmentCalculator(None)) assert await client.track('key', 'user', 'purchase', 12) is True assert self.events[0] == [EventWrapper( event=Event('key', 'user', 'purchase', 12, 1000, None), @@ -2560,7 +2560,7 @@ async def synchronize_config(*_): type(factory).ready = ready_property await factory.block_until_ready(1) - client = ClientAsync(factory, recorder) + client = ClientAsync(factory, recorder, True, FallbackTreatmentCalculator(None)) assert await client.get_treatment('some_key', 'SPLIT_2') == CONTROL assert(telemetry_storage._tel_config._not_ready == 1) await client.track('key', 'tt', 'ev') @@ -2608,7 +2608,7 @@ async def synchronize_config(*_): ready_property.return_value = True type(factory).ready = ready_property - client = ClientAsync(factory, recorder, True) + client = ClientAsync(factory, recorder, True, FallbackTreatmentCalculator(None)) client._evaluator = mocker.Mock() def _raise(*_): raise RuntimeError('something') @@ -2686,7 +2686,7 @@ async def synchronize_config(*_): await factory.block_until_ready(1) except: pass - client = ClientAsync(factory, recorder, True) + client = ClientAsync(factory, recorder, True, FallbackTreatmentCalculator(None)) assert await client.get_treatment('key', 'SPLIT_2') == 'on' assert(telemetry_storage._method_latencies._treatment[0] == 1) @@ -2756,7 +2756,7 @@ async def exc(*_): recorder.record_track_stats = exc await factory.block_until_ready(1) - client = ClientAsync(factory, recorder, True) + client = ClientAsync(factory, recorder, True, FallbackTreatmentCalculator(None)) try: await client.track('key', 'tt', 'ev') except: @@ -2803,7 +2803,7 @@ async def synchronize_config(*_): ) await factory.block_until_ready(1) - client = ClientAsync(factory, recorder, True) + client = ClientAsync(factory, recorder, True, FallbackTreatmentCalculator(None)) client._evaluator = mocker.Mock(spec=Evaluator) evaluation = { 'treatment': 'on', @@ -2914,7 +2914,7 @@ class TelemetrySubmitterMock(): async def synchronize_config(*_): pass factory._telemetry_submitter = TelemetrySubmitterMock() - client = ClientAsync(factory, recorder, True, FallbackTreatmentsConfiguration(FallbackTreatment("on-global", {"prop":"val"}))) + client = ClientAsync(factory, recorder, True, FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(FallbackTreatment("on-global", '{"prop": "val"}')))) async def get_feature_flag_names_by_flag_sets(*_): return ["some", "some2"] @@ -2949,7 +2949,7 @@ async def fetch_many_rbs(*_): assert(await client.get_treatments_with_config_by_flag_sets("key_m", ["set"]) == {"some": ("on-global", '{"prop": "val"}'), "some2": ("on-global", '{"prop": "val"}')}) self.imps = None - client._fallback_treatments_configuration = FallbackTreatmentsConfiguration(FallbackTreatment("on-global", {"prop":"val"}), {'some': FallbackTreatment("on-local")}) + client._fallback_treatment_calculator = FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(FallbackTreatment("on-global", '{"prop": "val"}'), {'some': FallbackTreatment("on-local")})) treatment = await client.get_treatment("key2", "some") assert(treatment == "on-local") assert(self.imps[0].treatment == "on-local") @@ -2978,7 +2978,7 @@ async def fetch_many_rbs(*_): assert(await client.get_treatments_with_config_by_flag_sets("key_m", ["set"]) == {"some": ("on-local", None), "some2": ("on-global", '{"prop": "val"}')}) self.imps = None - client._fallback_treatments_configuration = FallbackTreatmentsConfiguration(None, {'some': FallbackTreatment("on-local", {"prop":"val"})}) + client._fallback_treatment_calculator = FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(None, {'some': FallbackTreatment("on-local", '{"prop": "val"}')})) treatment = await client.get_treatment("key3", "some") assert(treatment == "on-local") assert(self.imps[0].treatment == "on-local") @@ -3007,7 +3007,7 @@ async def fetch_many_rbs(*_): assert(await client.get_treatments_with_config_by_flag_sets("key_m", ["set"]) == {"some": ("on-local", '{"prop": "val"}'), "some2": ("control", None)}) self.imps = None - client._fallback_treatments_configuration = FallbackTreatmentsConfiguration(None, {'some2': FallbackTreatment("on-local")}) + client._fallback_treatment_calculator = FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(None, {'some2': FallbackTreatment("on-local")})) treatment = await client.get_treatment("key4", "some") assert(treatment == "control") assert(self.imps[0].treatment == "control") @@ -3061,25 +3061,25 @@ class TelemetrySubmitterMock(): def synchronize_config(*_): pass factory._telemetry_submitter = TelemetrySubmitterMock() - client = Client(factory, recorder, True, FallbackTreatmentsConfiguration(FallbackTreatment("on-global"))) + client = Client(factory, recorder, True, FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(FallbackTreatment("on-global")))) treatment = client.get_treatment("key", "some") assert(treatment == "on-global") assert(self.imps == None) self.imps = None - client._fallback_treatments_configuration = FallbackTreatmentsConfiguration(FallbackTreatment("on-global"), {'some': FallbackTreatment("on-local")}) + client._fallback_treatment_calculator = FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(FallbackTreatment("on-global"), {'some': FallbackTreatment("on-local")})) treatment = client.get_treatment("key2", "some") assert(treatment == "on-local") assert(self.imps == None) self.imps = None - client._fallback_treatments_configuration = FallbackTreatmentsConfiguration(None, {'some': FallbackTreatment("on-local")}) + client._fallback_treatment_calculator = FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(None, {'some': FallbackTreatment("on-local")})) treatment = client.get_treatment("key3", "some") assert(treatment == "on-local") assert(self.imps == None) self.imps = None - client._fallback_treatments_configuration = FallbackTreatmentsConfiguration(None, {'some2': FallbackTreatment("on-local")}) + client._fallback_treatment_calculator = FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(None, {'some2': FallbackTreatment("on-local")})) treatment = client.get_treatment("key4", "some") assert(treatment == "control") assert(self.imps == None) @@ -3130,7 +3130,7 @@ class TelemetrySubmitterMock(): def synchronize_config(*_): pass factory._telemetry_submitter = TelemetrySubmitterMock() - client = Client(factory, recorder, True, FallbackTreatmentsConfiguration(FallbackTreatment("on-global"))) + client = Client(factory, recorder, True, FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(FallbackTreatment("on-global")))) client.ready = False treatment = client.get_treatment("key", "some") @@ -3138,21 +3138,21 @@ def synchronize_config(*_): assert(self.imps[0].label == "fallback - not ready") self.imps = None - client._fallback_treatments_configuration = FallbackTreatmentsConfiguration(FallbackTreatment("on-global"), {'some': FallbackTreatment("on-local")}) + client._fallback_treatment_calculator = FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(FallbackTreatment("on-global"), {'some': FallbackTreatment("on-local")})) treatment = client.get_treatment("key2", "some") assert(treatment == "on-local") assert(self.imps[0].treatment == "on-local") assert(self.imps[0].label == "fallback - not ready") self.imps = None - client._fallback_treatments_configuration = FallbackTreatmentsConfiguration(None, {'some': FallbackTreatment("on-local")}) + client._fallback_treatment_calculator = FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(None, {'some': FallbackTreatment("on-local")})) treatment = client.get_treatment("key3", "some") assert(treatment == "on-local") assert(self.imps[0].treatment == "on-local") assert(self.imps[0].label == "fallback - not ready") self.imps = None - client._fallback_treatments_configuration = FallbackTreatmentsConfiguration(None, {'some2': FallbackTreatment("on-local")}) + client._fallback_treatment_calculator = FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(None, {'some2': FallbackTreatment("on-local")})) treatment = client.get_treatment("key4", "some") assert(treatment == "control") assert(self.imps[0].treatment == "control") diff --git a/tests/client/test_config.py b/tests/client/test_config.py index 0017938c..e08a1d4b 100644 --- a/tests/client/test_config.py +++ b/tests/client/test_config.py @@ -109,13 +109,13 @@ def test_sanitize(self, mocker): fb = FallbackTreatmentsConfiguration(FallbackTreatment('on')) processed = config.sanitize('some', {'fallbackTreatments': fb}) assert processed['fallbackTreatments'].global_fallback_treatment.treatment == fb.global_fallback_treatment.treatment - assert processed['fallbackTreatments'].global_fallback_treatment.label_prefix == "fallback - " + assert processed['fallbackTreatments'].global_fallback_treatment.label == None fb = FallbackTreatmentsConfiguration(FallbackTreatment('on'), {"flag": FallbackTreatment("off")}) processed = config.sanitize('some', {'fallbackTreatments': fb}) assert processed['fallbackTreatments'].global_fallback_treatment.treatment == fb.global_fallback_treatment.treatment assert processed['fallbackTreatments'].by_flag_fallback_treatment["flag"] == fb.by_flag_fallback_treatment["flag"] - assert processed['fallbackTreatments'].by_flag_fallback_treatment["flag"].label_prefix == "fallback - " + assert processed['fallbackTreatments'].by_flag_fallback_treatment["flag"].label == None _logger.reset_mock() fb = FallbackTreatmentsConfiguration(None, {"flag#%": FallbackTreatment("off"), "flag2": FallbackTreatment("on")}) diff --git a/tests/client/test_factory.py b/tests/client/test_factory.py index 86e13088..9a5ad992 100644 --- a/tests/client/test_factory.py +++ b/tests/client/test_factory.py @@ -13,7 +13,7 @@ from splitio.storage import redis, inmemmory, pluggable from splitio.tasks.util import asynctask from splitio.engine.impressions.impressions import Manager as ImpressionsManager -from splitio.models.fallback_config import FallbackTreatmentsConfiguration +from splitio.models.fallback_config import FallbackTreatmentsConfiguration, FallbackTreatmentCalculator from splitio.models.fallback_treatment import FallbackTreatment from splitio.sync.manager import Manager, ManagerAsync from splitio.sync.synchronizer import Synchronizer, SynchronizerAsync, SplitSynchronizers, SplitTasks @@ -136,7 +136,7 @@ def synchronize_config(*_): assert isinstance(factory._get_storage('events'), redis.RedisEventsStorage) assert factory._get_storage('splits').flag_set_filter.flag_sets == set([]) - assert factory._fallback_treatments_configuration.global_fallback_treatment.treatment == fallback_treatments_configuration.global_fallback_treatment.treatment + assert factory._fallback_treatment_calculator.fallback_treatments_configuration.global_fallback_treatment.treatment == fallback_treatments_configuration.global_fallback_treatment.treatment adapter = factory._get_storage('splits')._redis assert adapter == factory._get_storage('segments')._redis @@ -715,7 +715,7 @@ async def test_flag_sets_counts(self): 'streamEnabled': False, 'fallbackTreatments': fallback_treatments_configuration }) - assert factory._fallback_treatments_configuration.global_fallback_treatment.treatment == fallback_treatments_configuration.global_fallback_treatment.treatment + assert factory._fallback_treatment_calculator.fallback_treatments_configuration.global_fallback_treatment.treatment == fallback_treatments_configuration.global_fallback_treatment.treatment assert factory._telemetry_init_producer._telemetry_storage._tel_config._flag_sets == 3 assert factory._telemetry_init_producer._telemetry_storage._tel_config._flag_sets_invalid == 0 await factory.destroy() diff --git a/tests/client/test_input_validator.py b/tests/client/test_input_validator.py index 85afb248..06ae5b60 100644 --- a/tests/client/test_input_validator.py +++ b/tests/client/test_input_validator.py @@ -9,6 +9,7 @@ from splitio.storage.inmemmory import InMemoryTelemetryStorage, InMemoryTelemetryStorageAsync, \ InMemorySplitStorage, InMemorySplitStorageAsync, InMemoryRuleBasedSegmentStorage, InMemoryRuleBasedSegmentStorageAsync from splitio.models.splits import Split +from splitio.models.fallback_config import FallbackTreatmentCalculator from splitio.client import input_validator from splitio.client.manager import SplitManager, SplitManagerAsync from splitio.recorder.recorder import StandardRecorder, StandardRecorderAsync @@ -56,7 +57,7 @@ def test_get_treatment(self, mocker): mocker.Mock() ) - client = Client(factory, mocker.Mock()) + client = Client(factory, mocker.Mock(), mocker.Mock(), FallbackTreatmentCalculator(None)) _logger = mocker.Mock() mocker.patch('splitio.client.input_validator._LOGGER', new=_logger) @@ -297,7 +298,7 @@ def _configs(treatment): mocker.Mock() ) - client = Client(factory, mocker.Mock()) + client = Client(factory, mocker.Mock(), mocker.Mock(), FallbackTreatmentCalculator(None)) _logger = mocker.Mock() mocker.patch('splitio.client.input_validator._LOGGER', new=_logger) @@ -573,7 +574,7 @@ def test_track(self, mocker): ) factory._sdk_key = 'some-test' - client = Client(factory, recorder) + client = Client(factory, recorder, mocker.Mock(), FallbackTreatmentCalculator(None)) client._event_storage = event_storage _logger = mocker.Mock() mocker.patch('splitio.client.input_validator._LOGGER', new=_logger) @@ -855,7 +856,7 @@ def test_get_treatments(self, mocker): ready_mock.return_value = True type(factory).ready = ready_mock - client = Client(factory, recorder) + client = Client(factory, recorder, mocker.Mock(), FallbackTreatmentCalculator(None)) _logger = mocker.Mock() mocker.patch('splitio.client.input_validator._LOGGER', new=_logger) @@ -1005,7 +1006,7 @@ def _configs(treatment): return '{"some": "property"}' if treatment == 'default_treatment' else None split_mock.get_configurations_for.side_effect = _configs - client = Client(factory, mocker.Mock()) + client = Client(factory, mocker.Mock(), mocker.Mock(), FallbackTreatmentCalculator(None)) _logger = mocker.Mock() mocker.patch('splitio.client.input_validator._LOGGER', new=_logger) @@ -1151,7 +1152,7 @@ def test_get_treatments_by_flag_set(self, mocker): ready_mock.return_value = True type(factory).ready = ready_mock - client = Client(factory, recorder) + client = Client(factory, recorder, mocker.Mock(), FallbackTreatmentCalculator(None)) _logger = mocker.Mock() mocker.patch('splitio.client.input_validator._LOGGER', new=_logger) @@ -1270,7 +1271,7 @@ def test_get_treatments_by_flag_sets(self, mocker): ready_mock.return_value = True type(factory).ready = ready_mock - client = Client(factory, recorder) + client = Client(factory, recorder, mocker.Mock(), FallbackTreatmentCalculator(None)) _logger = mocker.Mock() mocker.patch('splitio.client.input_validator._LOGGER', new=_logger) @@ -1400,7 +1401,7 @@ def _configs(treatment): ready_mock.return_value = True type(factory).ready = ready_mock - client = Client(factory, recorder) + client = Client(factory, recorder, mocker.Mock(), FallbackTreatmentCalculator(None)) _logger = mocker.Mock() mocker.patch('splitio.client.input_validator._LOGGER', new=_logger) @@ -1524,7 +1525,7 @@ def _configs(treatment): ready_mock.return_value = True type(factory).ready = ready_mock - client = Client(factory, recorder) + client = Client(factory, recorder, mocker.Mock(), FallbackTreatmentCalculator(None)) _logger = mocker.Mock() mocker.patch('splitio.client.input_validator._LOGGER', new=_logger) @@ -1710,7 +1711,7 @@ async def get_change_number(*_): ready_mock.return_value = True type(factory).ready = ready_mock - client = ClientAsync(factory, mocker.Mock()) + client = ClientAsync(factory, mocker.Mock(), mocker.Mock(), FallbackTreatmentCalculator(None)) async def record_treatment_stats(*_): pass @@ -1972,7 +1973,7 @@ async def get_change_number(*_): ready_mock.return_value = True type(factory).ready = ready_mock - client = ClientAsync(factory, mocker.Mock()) + client = ClientAsync(factory, mocker.Mock(), mocker.Mock(), FallbackTreatmentCalculator(None)) async def record_treatment_stats(*_): pass client._recorder.record_treatment_stats = record_treatment_stats @@ -2214,7 +2215,7 @@ async def put(*_): ) factory._sdk_key = 'some-test' - client = ClientAsync(factory, recorder) + client = ClientAsync(factory, recorder, mocker.Mock(), FallbackTreatmentCalculator(None)) client._event_storage = event_storage _logger = mocker.Mock() mocker.patch('splitio.client.input_validator._LOGGER', new=_logger) @@ -2506,7 +2507,7 @@ async def fetch_many_rbs(*_): ready_mock.return_value = True type(factory).ready = ready_mock - client = ClientAsync(factory, recorder) + client = ClientAsync(factory, recorder, mocker.Mock(), FallbackTreatmentCalculator(None)) async def record_treatment_stats(*_): pass client._recorder.record_treatment_stats = record_treatment_stats @@ -2672,7 +2673,7 @@ def _configs(treatment): return '{"some": "property"}' if treatment == 'default_treatment' else None split_mock.get_configurations_for.side_effect = _configs - client = ClientAsync(factory, mocker.Mock()) + client = ClientAsync(factory, mocker.Mock(), mocker.Mock(), FallbackTreatmentCalculator(None)) async def record_treatment_stats(*_): pass client._recorder.record_treatment_stats = record_treatment_stats @@ -2837,7 +2838,7 @@ async def fetch_many_rbs(*_): ready_mock.return_value = True type(factory).ready = ready_mock - client = ClientAsync(factory, recorder) + client = ClientAsync(factory, recorder, mocker.Mock(), FallbackTreatmentCalculator(None)) async def record_treatment_stats(*_): pass client._recorder.record_treatment_stats = record_treatment_stats @@ -2983,7 +2984,7 @@ async def get_feature_flags_by_sets(*_): ready_mock.return_value = True type(factory).ready = ready_mock - client = ClientAsync(factory, recorder) + client = ClientAsync(factory, recorder, mocker.Mock(), FallbackTreatmentCalculator(None)) async def record_treatment_stats(*_): pass client._recorder.record_treatment_stats = record_treatment_stats @@ -3138,7 +3139,7 @@ async def get_feature_flags_by_sets(*_): ready_mock.return_value = True type(factory).ready = ready_mock - client = ClientAsync(factory, recorder) + client = ClientAsync(factory, recorder, mocker.Mock(), FallbackTreatmentCalculator(None)) async def record_treatment_stats(*_): pass client._recorder.record_treatment_stats = record_treatment_stats @@ -3287,7 +3288,7 @@ async def get_feature_flags_by_sets(*_): ready_mock.return_value = True type(factory).ready = ready_mock - client = ClientAsync(factory, recorder) + client = ClientAsync(factory, recorder, mocker.Mock(), FallbackTreatmentCalculator(None)) async def record_treatment_stats(*_): pass client._recorder.record_treatment_stats = record_treatment_stats diff --git a/tests/client/test_utils.py b/tests/client/test_utils.py index 64edb076..98d9d8f6 100644 --- a/tests/client/test_utils.py +++ b/tests/client/test_utils.py @@ -14,7 +14,6 @@ class ClientUtilsTests(object): def test_get_metadata(self, mocker): """Test the get_metadata function.""" meta = util.get_metadata({'machineIp': 'some_ip', 'machineName': 'some_machine_name'}) - # assert _get_hostname_and_ip.mock_calls == [] assert meta.instance_ip == 'some_ip' assert meta.instance_name == 'some_machine_name' assert meta.sdk_version == 'python-' + __version__ diff --git a/tests/engine/test_evaluator.py b/tests/engine/test_evaluator.py index ba51f901..07f79a80 100644 --- a/tests/engine/test_evaluator.py +++ b/tests/engine/test_evaluator.py @@ -12,7 +12,7 @@ from splitio.models.grammar import condition from splitio.models import rule_based_segments from splitio.models.fallback_treatment import FallbackTreatment -from splitio.models.fallback_config import FallbackTreatmentsConfiguration +from splitio.models.fallback_config import FallbackTreatmentsConfiguration, FallbackTreatmentCalculator from splitio.engine import evaluator, splitters from splitio.engine.evaluator import EvaluationContext from splitio.storage.inmemmory import InMemorySplitStorage, InMemorySegmentStorage, InMemoryRuleBasedSegmentStorage, \ @@ -383,40 +383,35 @@ def test_evaluate_treatment_with_fallback(self, mocker): ctx = EvaluationContext(flags={'some': mocked_split}, segment_memberships=set(), rbs_segments={}) # should use global fallback - logger_mock.reset_mock() - e = evaluator.Evaluator(splitter_mock, FallbackTreatmentsConfiguration(FallbackTreatment("off-global", {"prop":"val"}))) + e = evaluator.Evaluator(splitter_mock, FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(FallbackTreatment("off-global", '{"prop": "val"}')))) result = e.eval_with_context('some_key', 'some_bucketing_key', 'some2', {}, ctx) assert result['treatment'] == 'off-global' assert result['configurations'] == '{"prop": "val"}' - assert result['impression']['label'] == "fallback - " + Label.SPLIT_NOT_FOUND - assert logger_mock.debug.mock_calls[0] == mocker.call("Using Global Fallback Treatment.") - + assert result['impression']['label'] == "fallback - " + Label.SPLIT_NOT_FOUND # should use by flag fallback - logger_mock.reset_mock() - e = evaluator.Evaluator(splitter_mock, FallbackTreatmentsConfiguration(None, {"some2": FallbackTreatment("off-some2", {"prop2":"val2"})})) + e = evaluator.Evaluator(splitter_mock, FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(None, {"some2": FallbackTreatment("off-some2", '{"prop2": "val2"}')}))) result = e.eval_with_context('some_key', 'some_bucketing_key', 'some2', {}, ctx) assert result['treatment'] == 'off-some2' assert result['configurations'] == '{"prop2": "val2"}' assert result['impression']['label'] == "fallback - " + Label.SPLIT_NOT_FOUND - assert logger_mock.debug.mock_calls[0] == mocker.call("Using Fallback Treatment for feature: %s", "some2") # should not use any fallback - e = evaluator.Evaluator(splitter_mock, FallbackTreatmentsConfiguration(None, {"some2": FallbackTreatment("off-some2", {"prop2":"val2"})})) + e = evaluator.Evaluator(splitter_mock, FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(None, {"some2": FallbackTreatment("off-some2", '{"prop2": "val2"}')}))) result = e.eval_with_context('some_key', 'some_bucketing_key', 'some3', {}, ctx) assert result['treatment'] == 'control' assert result['configurations'] == None assert result['impression']['label'] == Label.SPLIT_NOT_FOUND # should use by flag fallback - e = evaluator.Evaluator(splitter_mock, FallbackTreatmentsConfiguration(FallbackTreatment("off-global", {"prop":"val"}), {"some2": FallbackTreatment("off-some2", {"prop2":"val2"})})) + e = evaluator.Evaluator(splitter_mock, FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(FallbackTreatment("off-global", '{"prop": "val"}'), {"some2": FallbackTreatment("off-some2", '{"prop2": "val2"}')}))) result = e.eval_with_context('some_key', 'some_bucketing_key', 'some2', {}, ctx) assert result['treatment'] == 'off-some2' assert result['configurations'] == '{"prop2": "val2"}' assert result['impression']['label'] == "fallback - " + Label.SPLIT_NOT_FOUND # should global flag fallback - e = evaluator.Evaluator(splitter_mock, FallbackTreatmentsConfiguration(FallbackTreatment("off-global", {"prop":"val"}), {"some2": FallbackTreatment("off-some2", {"prop2":"val2"})})) + e = evaluator.Evaluator(splitter_mock, FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(FallbackTreatment("off-global", '{"prop": "val"}'), {"some2": FallbackTreatment("off-some2", '{"prop2": "val2"}')}))) result = e.eval_with_context('some_key', 'some_bucketing_key', 'some3', {}, ctx) assert result['treatment'] == 'off-global' assert result['configurations'] == '{"prop": "val"}' diff --git a/tests/integration/test_client_e2e.py b/tests/integration/test_client_e2e.py index 257d9099..9e7c614e 100644 --- a/tests/integration/test_client_e2e.py +++ b/tests/integration/test_client_e2e.py @@ -29,7 +29,7 @@ PluggableRuleBasedSegmentsStorage, PluggableRuleBasedSegmentsStorageAsync from splitio.storage.adapters.redis import build, RedisAdapter, RedisAdapterAsync, build_async from splitio.models import splits, segments, rule_based_segments -from splitio.models.fallback_config import FallbackTreatmentsConfiguration +from splitio.models.fallback_config import FallbackTreatmentsConfiguration, FallbackTreatmentCalculator from splitio.models.fallback_treatment import FallbackTreatment from splitio.engine.impressions.impressions import Manager as ImpressionsManager, ImpressionsMode from splitio.engine.impressions import set_classes, set_classes_async @@ -561,7 +561,7 @@ def setup_method(self): None, telemetry_producer=telemetry_producer, telemetry_init_producer=telemetry_producer.get_telemetry_init_producer(), - fallback_treatments_configuration=FallbackTreatmentsConfiguration(None, {'fallback_feature': FallbackTreatment("on-local", {"prop":"val"})}) + fallback_treatment_calculator=FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(None, {'fallback_feature': FallbackTreatment("on-local", '{"prop": "val"}')})) ) # pylint:disable=attribute-defined-outside-init except: pass @@ -720,7 +720,7 @@ def setup_method(self): None, telemetry_producer=telemetry_producer, telemetry_init_producer=telemetry_producer.get_telemetry_init_producer(), - fallback_treatments_configuration=FallbackTreatmentsConfiguration(None, {'fallback_feature': FallbackTreatment("on-local", {"prop":"val"})}) + fallback_treatment_calculator=FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(None, {'fallback_feature': FallbackTreatment("on-local", '{"prop": "val"}')})) ) # pylint:disable=attribute-defined-outside-init def test_get_treatment(self): @@ -846,7 +846,7 @@ def setup_method(self): 'config': {'connectTimeout': 10000, 'streamingEnabled': False, 'impressionsMode': 'debug', - 'fallbackTreatments': FallbackTreatmentsConfiguration(None, {'fallback_feature': FallbackTreatment("on-local", {"prop":"val"})}) + 'fallbackTreatments': FallbackTreatmentsConfiguration(None, {'fallback_feature': FallbackTreatment("on-local", '{"prop": "val"}')}) } } @@ -1017,7 +1017,7 @@ def setup_method(self): recorder, telemetry_producer=telemetry_producer, telemetry_init_producer=telemetry_producer.get_telemetry_init_producer(), - fallback_treatments_configuration=FallbackTreatmentsConfiguration(None, {'fallback_feature': FallbackTreatment("on-local", {"prop":"val"})}) + fallback_treatment_calculator=FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(None, {'fallback_feature': FallbackTreatment("on-local", '{"prop": "val"}')})) ) # pylint:disable=attribute-defined-outside-init def test_get_treatment(self): @@ -1206,7 +1206,7 @@ def setup_method(self): recorder, telemetry_producer=telemetry_producer, telemetry_init_producer=telemetry_producer.get_telemetry_init_producer(), - fallback_treatments_configuration=FallbackTreatmentsConfiguration(None, {'fallback_feature': FallbackTreatment("on-local", {"prop":"val"})}) + fallback_treatment_calculator=FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(None, {'fallback_feature': FallbackTreatment("on-local", '{"prop": "val"}')})) ) # pylint:disable=attribute-defined-outside-init class LocalhostIntegrationTests(object): # pylint: disable=too-few-public-methods @@ -1430,7 +1430,7 @@ def setup_method(self): sdk_ready_flag=None, telemetry_producer=telemetry_producer, telemetry_init_producer=telemetry_producer.get_telemetry_init_producer(), - fallback_treatments_configuration=FallbackTreatmentsConfiguration(None, {'fallback_feature': FallbackTreatment("on-local", {"prop":"val"})}) + fallback_treatment_calculator=FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(None, {'fallback_feature': FallbackTreatment("on-local", '{"prop": "val"}')})) ) # pylint:disable=attribute-defined-outside-init # Adding data to storage @@ -1626,7 +1626,7 @@ def setup_method(self): sdk_ready_flag=None, telemetry_producer=telemetry_producer, telemetry_init_producer=telemetry_producer.get_telemetry_init_producer(), - fallback_treatments_configuration=FallbackTreatmentsConfiguration(None, {'fallback_feature': FallbackTreatment("on-local", {"prop":"val"})}) + fallback_treatment_calculator=FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(None, {'fallback_feature': FallbackTreatment("on-local", '{"prop": "val"}')})) ) # pylint:disable=attribute-defined-outside-init # Adding data to storage @@ -1821,7 +1821,7 @@ def setup_method(self): sdk_ready_flag=None, telemetry_producer=telemetry_producer, telemetry_init_producer=telemetry_producer.get_telemetry_init_producer(), - fallback_treatments_configuration=FallbackTreatmentsConfiguration(None, {'fallback_feature': FallbackTreatment("on-local", {"prop":"val"})}) + fallback_treatment_calculator=FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(None, {'fallback_feature': FallbackTreatment("on-local", '{"prop": "val"}')})) ) # pylint:disable=attribute-defined-outside-init # Adding data to storage @@ -1975,7 +1975,7 @@ def test_optimized(self): None, telemetry_producer=telemetry_producer, telemetry_init_producer=telemetry_producer.get_telemetry_init_producer(), - fallback_treatments_configuration=FallbackTreatmentsConfiguration(FallbackTreatment("on-global"), {'fallback_feature': FallbackTreatment("on-local", {"prop":"val"})}) + fallback_treatment_calculator=FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(FallbackTreatment("on-global"), {'fallback_feature': FallbackTreatment("on-local", '{"prop": "val"}')})) ) # pylint:disable=attribute-defined-outside-init except: pass @@ -2033,7 +2033,7 @@ def test_debug(self): None, telemetry_producer=telemetry_producer, telemetry_init_producer=telemetry_producer.get_telemetry_init_producer(), - fallback_treatments_configuration=FallbackTreatmentsConfiguration(FallbackTreatment("on-global"), {'fallback_feature': FallbackTreatment("on-local", {"prop":"val"})}) + fallback_treatment_calculator=FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(FallbackTreatment("on-global"), {'fallback_feature': FallbackTreatment("on-local", '{"prop": "val"}')})) ) # pylint:disable=attribute-defined-outside-init except: pass @@ -2091,7 +2091,7 @@ def test_none(self): None, telemetry_producer=telemetry_producer, telemetry_init_producer=telemetry_producer.get_telemetry_init_producer(), - fallback_treatments_configuration=FallbackTreatmentsConfiguration(FallbackTreatment("on-global"), {'fallback_feature': FallbackTreatment("on-local", {"prop":"val"})}) + fallback_treatment_calculator=FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(FallbackTreatment("on-global"), {'fallback_feature': FallbackTreatment("on-local", '{"prop": "val"}')})) ) # pylint:disable=attribute-defined-outside-init except: pass @@ -2155,7 +2155,7 @@ def test_optimized(self): recorder, telemetry_producer=telemetry_producer, telemetry_init_producer=telemetry_producer.get_telemetry_init_producer(), - fallback_treatments_configuration=FallbackTreatmentsConfiguration(FallbackTreatment("on-global"), {'fallback_feature': FallbackTreatment("on-local", {"prop":"val"})}) + fallback_treatment_calculator=FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(FallbackTreatment("on-global"), {'fallback_feature': FallbackTreatment("on-local", '{"prop": "val"}')})) ) # pylint:disable=attribute-defined-outside-init try: @@ -2222,7 +2222,7 @@ def test_debug(self): recorder, telemetry_producer=telemetry_producer, telemetry_init_producer=telemetry_producer.get_telemetry_init_producer(), - fallback_treatments_configuration=FallbackTreatmentsConfiguration(FallbackTreatment("on-global"), {'fallback_feature': FallbackTreatment("on-local", {"prop":"val"})}) + fallback_treatment_calculator=FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(FallbackTreatment("on-global"), {'fallback_feature': FallbackTreatment("on-local", '{"prop": "val"}')})) ) # pylint:disable=attribute-defined-outside-init try: @@ -2289,7 +2289,7 @@ def test_none(self): recorder, telemetry_producer=telemetry_producer, telemetry_init_producer=telemetry_producer.get_telemetry_init_producer(), - fallback_treatments_configuration=FallbackTreatmentsConfiguration(FallbackTreatment("on-global"), {'fallback_feature': FallbackTreatment("on-local", {"prop":"val"})}) + fallback_treatment_calculator=FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(FallbackTreatment("on-global"), {'fallback_feature': FallbackTreatment("on-local", '{"prop":"val"}')})) ) # pylint:disable=attribute-defined-outside-init try: @@ -2393,7 +2393,7 @@ async def _setup_method(self): None, telemetry_producer=telemetry_producer, telemetry_init_producer=telemetry_producer.get_telemetry_init_producer(), - fallback_treatments_configuration=FallbackTreatmentsConfiguration(None, {'fallback_feature': FallbackTreatment("on-local", {"prop":"val"})}) + fallback_treatment_calculator=FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(None, {'fallback_feature': FallbackTreatment("on-local", '{"prop": "val"}')})) ) # pylint:disable=attribute-defined-outside-init except: pass @@ -2565,7 +2565,7 @@ async def _setup_method(self): None, telemetry_producer=telemetry_producer, telemetry_init_producer=telemetry_producer.get_telemetry_init_producer(), - fallback_treatments_configuration=FallbackTreatmentsConfiguration(None, {'fallback_feature': FallbackTreatment("on-local", {"prop":"val"})}) + fallback_treatment_calculator=FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(None, {'fallback_feature': FallbackTreatment("on-local", '{"prop": "val"}')})) ) # pylint:disable=attribute-defined-outside-init except: pass @@ -2709,7 +2709,7 @@ async def _setup_method(self): 'config': {'connectTimeout': 10000, 'streamingEnabled': False, 'impressionsMode': 'debug', - 'fallbackTreatments': FallbackTreatmentsConfiguration(None, {'fallback_feature': FallbackTreatment("on-local", {"prop":"val"})}) + 'fallbackTreatments': FallbackTreatmentsConfiguration(None, {'fallback_feature': FallbackTreatment("on-local", '{"prop": "val"}')}) } } @@ -2919,7 +2919,7 @@ async def _setup_method(self): telemetry_producer=telemetry_producer, telemetry_init_producer=telemetry_producer.get_telemetry_init_producer(), telemetry_submitter=telemetry_submitter, - fallback_treatments_configuration=FallbackTreatmentsConfiguration(None, {'fallback_feature': FallbackTreatment("on-local", {"prop":"val"})}) + fallback_treatment_calculator=FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(None, {'fallback_feature': FallbackTreatment("on-local", '{"prop": "val"}')})) ) # pylint:disable=attribute-defined-outside-init ready_property = mocker.PropertyMock() ready_property.return_value = True @@ -3142,7 +3142,7 @@ async def _setup_method(self): telemetry_producer=telemetry_producer, telemetry_init_producer=telemetry_producer.get_telemetry_init_producer(), telemetry_submitter=telemetry_submitter, - fallback_treatments_configuration=FallbackTreatmentsConfiguration(None, {'fallback_feature': FallbackTreatment("on-local", {"prop":"val"})}) + fallback_treatment_calculator=FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(None, {'fallback_feature': FallbackTreatment("on-local", '{"prop": "val"}')})) ) # pylint:disable=attribute-defined-outside-init ready_property = mocker.PropertyMock() ready_property.return_value = True @@ -3377,7 +3377,7 @@ async def _setup_method(self): telemetry_producer=telemetry_producer, telemetry_init_producer=telemetry_producer.get_telemetry_init_producer(), telemetry_submitter=telemetry_submitter, - fallback_treatments_configuration=FallbackTreatmentsConfiguration(None, {'fallback_feature': FallbackTreatment("on-local", {"prop":"val"})}) + fallback_treatment_calculator=FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(None, {'fallback_feature': FallbackTreatment("on-local", '{"prop": "val"}')})) ) # pylint:disable=attribute-defined-outside-init ready_property = mocker.PropertyMock() ready_property.return_value = True @@ -3607,7 +3607,7 @@ async def _setup_method(self): telemetry_producer=telemetry_producer, telemetry_init_producer=telemetry_producer.get_telemetry_init_producer(), telemetry_submitter=telemetry_submitter, - fallback_treatments_configuration=FallbackTreatmentsConfiguration(None, {'fallback_feature': FallbackTreatment("on-local", {"prop":"val"})}) + fallback_treatment_calculator=FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(None, {'fallback_feature': FallbackTreatment("on-local", '{"prop": "val"}')})) ) # pylint:disable=attribute-defined-outside-init ready_property = mocker.PropertyMock() @@ -3842,7 +3842,7 @@ async def _setup_method(self): manager, telemetry_producer=telemetry_producer, telemetry_init_producer=telemetry_producer.get_telemetry_init_producer(), - fallback_treatments_configuration=FallbackTreatmentsConfiguration(None, {'fallback_feature': FallbackTreatment("on-local", {"prop":"val"})}) + fallback_treatment_calculator=FallbackTreatmentCalculator(FallbackTreatmentsConfiguration(None, {'fallback_feature': FallbackTreatment("on-local", '{"prop": "val"}')})) ) # pylint:disable=attribute-defined-outside-init # Adding data to storage @@ -4056,6 +4056,7 @@ async def test_optimized(self): None, telemetry_producer=telemetry_producer, telemetry_init_producer=telemetry_producer.get_telemetry_init_producer(), + fallback_treatment_calculator=FallbackTreatmentCalculator(None) ) # pylint:disable=attribute-defined-outside-init except: pass @@ -4116,6 +4117,7 @@ async def test_debug(self): None, telemetry_producer=telemetry_producer, telemetry_init_producer=telemetry_producer.get_telemetry_init_producer(), + fallback_treatment_calculator=FallbackTreatmentCalculator(None) ) # pylint:disable=attribute-defined-outside-init except: pass @@ -4176,6 +4178,7 @@ async def test_none(self): None, telemetry_producer=telemetry_producer, telemetry_init_producer=telemetry_producer.get_telemetry_init_producer(), + fallback_treatment_calculator=FallbackTreatmentCalculator(None) ) # pylint:disable=attribute-defined-outside-init except: pass @@ -4243,6 +4246,7 @@ async def test_optimized(self): recorder, telemetry_producer=telemetry_producer, telemetry_init_producer=telemetry_producer.get_telemetry_init_producer(), + fallback_treatment_calculator=FallbackTreatmentCalculator(None) ) # pylint:disable=attribute-defined-outside-init ready_property = mocker.PropertyMock() ready_property.return_value = True @@ -4312,6 +4316,7 @@ async def test_debug(self): recorder, telemetry_producer=telemetry_producer, telemetry_init_producer=telemetry_producer.get_telemetry_init_producer(), + fallback_treatment_calculator=FallbackTreatmentCalculator(None) ) # pylint:disable=attribute-defined-outside-init ready_property = mocker.PropertyMock() ready_property.return_value = True @@ -4381,6 +4386,7 @@ async def test_none(self): recorder, telemetry_producer=telemetry_producer, telemetry_init_producer=telemetry_producer.get_telemetry_init_producer(), + fallback_treatment_calculator=FallbackTreatmentCalculator(None) ) # pylint:disable=attribute-defined-outside-init ready_property = mocker.PropertyMock() ready_property.return_value = True diff --git a/tests/models/test_fallback.py b/tests/models/test_fallback.py index a3111277..4dfdf79e 100644 --- a/tests/models/test_fallback.py +++ b/tests/models/test_fallback.py @@ -1,11 +1,11 @@ from splitio.models.fallback_treatment import FallbackTreatment -from splitio.models.fallback_config import FallbackTreatmentsConfiguration +from splitio.models.fallback_config import FallbackTreatmentsConfiguration, FallbackTreatmentCalculator class FallbackTreatmentModelTests(object): """Fallback treatment model tests.""" def test_working(self): - fallback_treatment = FallbackTreatment("on", {"prop": "val"}) + fallback_treatment = FallbackTreatment("on", '{"prop": "val"}') assert fallback_treatment.config == '{"prop": "val"}' assert fallback_treatment.treatment == 'on' @@ -14,7 +14,7 @@ def test_working(self): assert fallback_treatment.treatment == 'off' class FallbackTreatmentsConfigModelTests(object): - """Fallback treatment model tests.""" + """Fallback treatment configuration model tests.""" def test_working(self): global_fb = FallbackTreatment("on") @@ -29,4 +29,28 @@ def test_working(self): fallback_config.by_flag_fallback_treatment["flag2"] = flag_fb assert fallback_config.by_flag_fallback_treatment == {"flag1": flag_fb, "flag2": flag_fb} - \ No newline at end of file + +class FallbackTreatmentCalculatorTests(object): + """Fallback treatment calculator model tests.""" + + def test_working(self): + fallback_config = FallbackTreatmentsConfiguration(FallbackTreatment("on" ,"{}"), None) + fallback_calculator = FallbackTreatmentCalculator(fallback_config) + assert fallback_calculator.fallback_treatments_configuration == fallback_config + assert fallback_calculator._label_prefix == "fallback - " + + fallback_treatment = fallback_calculator.resolve("feature", "not ready") + assert fallback_treatment.treatment == "on" + assert fallback_treatment.label == "fallback - not ready" + assert fallback_treatment.config == "{}" + + fallback_calculator._fallback_treatments_configuration = FallbackTreatmentsConfiguration(FallbackTreatment("on" ,"{}"), {'feature': FallbackTreatment("off" , '{"prop": "val"}')}) + fallback_treatment = fallback_calculator.resolve("feature", "not ready") + assert fallback_treatment.treatment == "off" + assert fallback_treatment.label == "fallback - not ready" + assert fallback_treatment.config == '{"prop": "val"}' + + fallback_treatment = fallback_calculator.resolve("feature2", "not ready") + assert fallback_treatment.treatment == "on" + assert fallback_treatment.label == "fallback - not ready" + assert fallback_treatment.config == "{}"