22import logging
33import json
44from collections import namedtuple
5+ import copy
56
67from splitio .engine .evaluator import Evaluator , CONTROL , EvaluationDataFactory , AsyncEvaluationDataFactory
78from splitio .engine .splitters import Splitter
89from splitio .models .impressions import Impression , Label , ImpressionDecorated
910from splitio .models .events import Event , EventWrapper
1011from splitio .models .telemetry import get_latency_bucket_index , MethodExceptionsAndLatencies
1112from splitio .client import input_validator
13+ from splitio .client .util import get_fallback_treatment_and_label
1214from splitio .util .time import get_current_epoch_time_ms , utctime_ms
1315
1416
@@ -39,7 +41,7 @@ class ClientBase(object): # pylint: disable=too-many-instance-attributes
3941 'impressions_disabled' : False
4042 }
4143
42- def __init__ (self , factory , recorder , labels_enabled = True ):
44+ def __init__ (self , factory , recorder , labels_enabled = True , fallback_treatments_configuration = None ):
4345 """
4446 Construct a Client instance.
4547
@@ -64,6 +66,7 @@ def __init__(self, factory, recorder, labels_enabled=True):
6466 self ._evaluator = Evaluator (self ._splitter )
6567 self ._telemetry_evaluation_producer = self ._factory ._telemetry_evaluation_producer
6668 self ._telemetry_init_producer = self ._factory ._telemetry_init_producer
69+ self ._fallback_treatments_configuration = fallback_treatments_configuration
6770
6871 @property
6972 def ready (self ):
@@ -203,11 +206,23 @@ def _validate_track(self, key, traffic_type, event_type, value=None, properties=
203206 def _get_properties (self , evaluation_options ):
204207 return evaluation_options .properties if evaluation_options != None else None
205208
209+ def _get_fallback_treatment_with_config (self , treatment , feature ):
210+ label = ""
211+
212+ label , treatment , config = get_fallback_treatment_and_label (self ._fallback_treatments_configuration ,
213+ feature , treatment , label , _LOGGER )
214+ return treatment , config
215+
216+ def _get_fallback_eval_results (self , eval_result , feature ):
217+ result = copy .deepcopy (eval_result )
218+ result ["impression" ]["label" ], result ["treatment" ], result ["configurations" ] = get_fallback_treatment_and_label (self ._fallback_treatments_configuration ,
219+ feature , result ["treatment" ], result ["impression" ]["label" ], _LOGGER )
220+ return result
206221
207222class Client (ClientBase ): # pylint: disable=too-many-instance-attributes
208223 """Entry point for the split sdk."""
209224
210- def __init__ (self , factory , recorder , labels_enabled = True ):
225+ def __init__ (self , factory , recorder , labels_enabled = True , fallback_treatments_configuration = None ):
211226 """
212227 Construct a Client instance.
213228
@@ -222,7 +237,7 @@ def __init__(self, factory, recorder, labels_enabled=True):
222237
223238 :rtype: Client
224239 """
225- ClientBase .__init__ (self , factory , recorder , labels_enabled )
240+ ClientBase .__init__ (self , factory , recorder , labels_enabled , fallback_treatments_configuration )
226241 self ._context_factory = EvaluationDataFactory (factory ._get_storage ('splits' ), factory ._get_storage ('segments' ), factory ._get_storage ('rule_based_segments' ))
227242
228243 def destroy (self ):
@@ -254,10 +269,11 @@ def get_treatment(self, key, feature_flag_name, attributes=None, evaluation_opti
254269 try :
255270 treatment , _ = self ._get_treatment (MethodExceptionsAndLatencies .TREATMENT , key , feature_flag_name , attributes , evaluation_options )
256271 return treatment
257-
272+
258273 except :
259274 _LOGGER .error ('get_treatment failed' )
260- return CONTROL
275+ treatment , _ = self ._get_fallback_treatment_with_config (CONTROL , feature_flag_name )
276+ return treatment
261277
262278 def get_treatment_with_config (self , key , feature_flag_name , attributes = None , evaluation_options = None ):
263279 """
@@ -282,8 +298,8 @@ def get_treatment_with_config(self, key, feature_flag_name, attributes=None, eva
282298
283299 except Exception :
284300 _LOGGER .error ('get_treatment_with_config failed' )
285- return CONTROL , None
286-
301+ return self . _get_fallback_treatment_with_config ( CONTROL , feature_flag_name )
302+
287303 def _get_treatment (self , method , key , feature , attributes = None , evaluation_options = None ):
288304 """
289305 Validate key, feature flag name and object, and get the treatment and config with an optional dictionary of attributes.
@@ -302,7 +318,7 @@ def _get_treatment(self, method, key, feature, attributes=None, evaluation_optio
302318 :rtype: dict
303319 """
304320 if not self ._client_is_usable (): # not destroyed & not waiting for a fork
305- return CONTROL , None
321+ return self . _get_fallback_treatment_with_config ( CONTROL , feature )
306322
307323 start = get_current_epoch_time_ms ()
308324 if not self .ready :
@@ -312,9 +328,10 @@ def _get_treatment(self, method, key, feature, attributes=None, evaluation_optio
312328 try :
313329 key , bucketing , feature , attributes , evaluation_options = self ._validate_treatment_input (key , feature , attributes , method , evaluation_options )
314330 except _InvalidInputError :
315- return CONTROL , None
331+ return self . _get_fallback_treatment_with_config ( CONTROL , feature )
316332
317- result = self ._NON_READY_EVAL_RESULT
333+ result = self ._get_fallback_eval_results (self ._NON_READY_EVAL_RESULT , feature )
334+
318335 if self .ready :
319336 try :
320337 ctx = self ._context_factory .context_for (key , [feature ])
@@ -324,15 +341,15 @@ def _get_treatment(self, method, key, feature, attributes=None, evaluation_optio
324341 _LOGGER .error ('Error getting treatment for feature flag' )
325342 _LOGGER .debug ('Error: ' , exc_info = True )
326343 self ._telemetry_evaluation_producer .record_exception (method )
327- result = self ._FAILED_EVAL_RESULT
344+ result = self ._get_fallback_eval_results ( self . _FAILED_EVAL_RESULT , feature )
328345
329346 properties = self ._get_properties (evaluation_options )
330- if result ['impression' ]['label' ] != Label .SPLIT_NOT_FOUND :
347+ if result ['impression' ]['label' ]. find ( Label .SPLIT_NOT_FOUND ) == - 1 :
331348 impression_decorated = self ._build_impression (key , bucketing , feature , result , properties )
332349 self ._record_stats ([(impression_decorated , attributes )], start , method )
333350
334351 return result ['treatment' ], result ['configurations' ]
335-
352+
336353 def get_treatments (self , key , feature_flag_names , attributes = None , evaluation_options = None ):
337354 """
338355 Evaluate multiple feature flags and return a dictionary with all the feature flag/treatments.
@@ -356,7 +373,7 @@ def get_treatments(self, key, feature_flag_names, attributes=None, evaluation_op
356373 return {feature_flag : result [0 ] for (feature_flag , result ) in with_config .items ()}
357374
358375 except Exception :
359- return {feature : CONTROL for feature in feature_flag_names }
376+ return {feature : self . _get_fallback_treatment_with_config ( CONTROL , feature )[ 0 ] for feature in feature_flag_names }
360377
361378 def get_treatments_with_config (self , key , feature_flag_names , attributes = None , evaluation_options = None ):
362379 """
@@ -380,7 +397,7 @@ def get_treatments_with_config(self, key, feature_flag_names, attributes=None, e
380397 return self ._get_treatments (key , feature_flag_names , MethodExceptionsAndLatencies .TREATMENTS_WITH_CONFIG , attributes , evaluation_options )
381398
382399 except Exception :
383- return {feature : (CONTROL , None ) for feature in feature_flag_names }
400+ return {feature : (self . _get_fallback_treatment_with_config ( CONTROL , feature ) ) for feature in feature_flag_names }
384401
385402 def get_treatments_by_flag_set (self , key , flag_set , attributes = None , evaluation_options = None ):
386403 """
@@ -604,7 +621,7 @@ def _get_treatments(self, key, features, method, attributes=None, evaluation_opt
604621 """
605622 start = get_current_epoch_time_ms ()
606623 if not self ._client_is_usable ():
607- return input_validator .generate_control_treatments (features )
624+ return input_validator .generate_control_treatments (features , self . _fallback_treatments_configuration )
608625
609626 if not self .ready :
610627 _LOGGER .error ("Client is not ready - no calls possible" )
@@ -613,9 +630,9 @@ def _get_treatments(self, key, features, method, attributes=None, evaluation_opt
613630 try :
614631 key , bucketing , features , attributes , evaluation_options = self ._validate_treatments_input (key , features , attributes , method , evaluation_options )
615632 except _InvalidInputError :
616- return input_validator .generate_control_treatments (features )
633+ return input_validator .generate_control_treatments (features , self . _fallback_treatments_configuration )
617634
618- results = {n : self ._NON_READY_EVAL_RESULT for n in features }
635+ results = {n : self ._get_fallback_eval_results ( self . _NON_READY_EVAL_RESULT , n ) for n in features }
619636 if self .ready :
620637 try :
621638 ctx = self ._context_factory .context_for (key , features )
@@ -625,12 +642,12 @@ def _get_treatments(self, key, features, method, attributes=None, evaluation_opt
625642 _LOGGER .error ('Error getting treatment for feature flag' )
626643 _LOGGER .debug ('Error: ' , exc_info = True )
627644 self ._telemetry_evaluation_producer .record_exception (method )
628- results = {n : self ._FAILED_EVAL_RESULT for n in features }
645+ results = {n : self ._get_fallback_eval_results ( self . _FAILED_EVAL_RESULT , n ) for n in features }
629646
630647 properties = self ._get_properties (evaluation_options )
631648 imp_decorated_attrs = [
632649 (i , attributes ) for i in self ._build_impressions (key , bucketing , results , properties )
633- if i .Impression .label != Label .SPLIT_NOT_FOUND
650+ if i .Impression .label . find ( Label .SPLIT_NOT_FOUND ) == - 1
634651 ]
635652 self ._record_stats (imp_decorated_attrs , start , method )
636653
@@ -706,7 +723,7 @@ def track(self, key, traffic_type, event_type, value=None, properties=None):
706723class ClientAsync (ClientBase ): # pylint: disable=too-many-instance-attributes
707724 """Entry point for the split sdk."""
708725
709- def __init__ (self , factory , recorder , labels_enabled = True ):
726+ def __init__ (self , factory , recorder , labels_enabled = True , fallback_treatments_configuration = None ):
710727 """
711728 Construct a Client instance.
712729
@@ -721,7 +738,7 @@ def __init__(self, factory, recorder, labels_enabled=True):
721738
722739 :rtype: Client
723740 """
724- ClientBase .__init__ (self , factory , recorder , labels_enabled )
741+ ClientBase .__init__ (self , factory , recorder , labels_enabled , fallback_treatments_configuration )
725742 self ._context_factory = AsyncEvaluationDataFactory (factory ._get_storage ('splits' ), factory ._get_storage ('segments' ), factory ._get_storage ('rule_based_segments' ))
726743
727744 async def destroy (self ):
@@ -756,7 +773,8 @@ async def get_treatment(self, key, feature_flag_name, attributes=None, evaluatio
756773
757774 except :
758775 _LOGGER .error ('get_treatment failed' )
759- return CONTROL
776+ treatment , _ = self ._get_fallback_treatment_with_config (CONTROL , feature_flag_name )
777+ return treatment
760778
761779 async def get_treatment_with_config (self , key , feature_flag_name , attributes = None , evaluation_options = None ):
762780 """
@@ -781,7 +799,7 @@ async def get_treatment_with_config(self, key, feature_flag_name, attributes=Non
781799
782800 except Exception :
783801 _LOGGER .error ('get_treatment_with_config failed' )
784- return CONTROL , None
802+ return self . _get_fallback_treatment_with_config ( CONTROL , feature_flag_name )
785803
786804 async def _get_treatment (self , method , key , feature , attributes = None , evaluation_options = None ):
787805 """
@@ -801,7 +819,7 @@ async def _get_treatment(self, method, key, feature, attributes=None, evaluation
801819 :rtype: dict
802820 """
803821 if not self ._client_is_usable (): # not destroyed & not waiting for a fork
804- return CONTROL , None
822+ return self . _get_fallback_treatment_with_config ( CONTROL , feature )
805823
806824 start = get_current_epoch_time_ms ()
807825 if not self .ready :
@@ -811,9 +829,9 @@ async def _get_treatment(self, method, key, feature, attributes=None, evaluation
811829 try :
812830 key , bucketing , feature , attributes , evaluation_options = self ._validate_treatment_input (key , feature , attributes , method , evaluation_options )
813831 except _InvalidInputError :
814- return CONTROL , None
832+ return self . _get_fallback_treatment_with_config ( CONTROL , feature )
815833
816- result = self ._NON_READY_EVAL_RESULT
834+ result = self ._get_fallback_eval_results ( self . _NON_READY_EVAL_RESULT , feature )
817835 if self .ready :
818836 try :
819837 ctx = await self ._context_factory .context_for (key , [feature ])
@@ -823,10 +841,10 @@ async def _get_treatment(self, method, key, feature, attributes=None, evaluation
823841 _LOGGER .error ('Error getting treatment for feature flag' )
824842 _LOGGER .debug ('Error: ' , exc_info = True )
825843 await self ._telemetry_evaluation_producer .record_exception (method )
826- result = self ._FAILED_EVAL_RESULT
844+ result = self ._get_fallback_eval_results ( self . _FAILED_EVAL_RESULT , feature )
827845
828846 properties = self ._get_properties (evaluation_options )
829- if result ['impression' ]['label' ] != Label .SPLIT_NOT_FOUND :
847+ if result ['impression' ]['label' ]. find ( Label .SPLIT_NOT_FOUND ) == - 1 :
830848 impression_decorated = self ._build_impression (key , bucketing , feature , result , properties )
831849 await self ._record_stats ([(impression_decorated , attributes )], start , method )
832850 return result ['treatment' ], result ['configurations' ]
@@ -854,7 +872,7 @@ async def get_treatments(self, key, feature_flag_names, attributes=None, evaluat
854872 return {feature_flag : result [0 ] for (feature_flag , result ) in with_config .items ()}
855873
856874 except Exception :
857- return {feature : CONTROL for feature in feature_flag_names }
875+ return {feature : self . _get_fallback_treatment_with_config ( CONTROL , feature )[ 0 ] for feature in feature_flag_names }
858876
859877 async def get_treatments_with_config (self , key , feature_flag_names , attributes = None , evaluation_options = None ):
860878 """
@@ -878,8 +896,7 @@ async def get_treatments_with_config(self, key, feature_flag_names, attributes=N
878896 return await self ._get_treatments (key , feature_flag_names , MethodExceptionsAndLatencies .TREATMENTS_WITH_CONFIG , attributes , evaluation_options )
879897
880898 except Exception :
881- _LOGGER .error ("AA" , exc_info = True )
882- return {feature : (CONTROL , None ) for feature in feature_flag_names }
899+ return {feature : (self ._get_fallback_treatment_with_config (CONTROL , feature )) for feature in feature_flag_names }
883900
884901 async def get_treatments_by_flag_set (self , key , flag_set , attributes = None , evaluation_options = None ):
885902 """
@@ -1017,7 +1034,7 @@ async def _get_treatments(self, key, features, method, attributes=None, evaluati
10171034 """
10181035 start = get_current_epoch_time_ms ()
10191036 if not self ._client_is_usable ():
1020- return input_validator .generate_control_treatments (features )
1037+ return input_validator .generate_control_treatments (features , self . _fallback_treatments_configuration )
10211038
10221039 if not self .ready :
10231040 _LOGGER .error ("Client is not ready - no calls possible" )
@@ -1026,9 +1043,9 @@ async def _get_treatments(self, key, features, method, attributes=None, evaluati
10261043 try :
10271044 key , bucketing , features , attributes , evaluation_options = self ._validate_treatments_input (key , features , attributes , method , evaluation_options )
10281045 except _InvalidInputError :
1029- return input_validator .generate_control_treatments (features )
1046+ return input_validator .generate_control_treatments (features , self . _fallback_treatments_configuration )
10301047
1031- results = {n : self ._NON_READY_EVAL_RESULT for n in features }
1048+ results = {n : self ._get_fallback_eval_results ( self . _NON_READY_EVAL_RESULT , n ) for n in features }
10321049 if self .ready :
10331050 try :
10341051 ctx = await self ._context_factory .context_for (key , features )
@@ -1038,7 +1055,7 @@ async def _get_treatments(self, key, features, method, attributes=None, evaluati
10381055 _LOGGER .error ('Error getting treatment for feature flag' )
10391056 _LOGGER .debug ('Error: ' , exc_info = True )
10401057 await self ._telemetry_evaluation_producer .record_exception (method )
1041- results = {n : self ._FAILED_EVAL_RESULT for n in features }
1058+ results = {n : self ._get_fallback_eval_results ( self . _FAILED_EVAL_RESULT , n ) for n in features }
10421059
10431060 properties = self ._get_properties (evaluation_options )
10441061 imp_decorated_attrs = [
0 commit comments