88from splitio .engine .evaluator import Evaluator , CONTROL
99from splitio .engine .splitters import Splitter
1010from splitio .models .impressions import Impression , Label
11- from splitio .models .events import Event
11+ from splitio .models .events import Event , EventWrapper
1212from splitio .models .telemetry import get_latency_bucket_index
1313from splitio .client import input_validator
1414from splitio .client .listener import ImpressionListenerException
1515
1616
17- class Client (object ): #pylint: disable=too-many-instance-attributes
17+ class Client (object ): # pylint: disable=too-many-instance-attributes
1818 """Entry point for the split sdk."""
1919
2020 _METRIC_GET_TREATMENT = 'sdk.getTreatment'
@@ -41,11 +41,11 @@ def __init__(self, factory, labels_enabled=True, impression_listener=None):
4141 self ._impression_listener = impression_listener
4242
4343 self ._splitter = Splitter ()
44- self ._split_storage = factory ._get_storage ('splits' ) #pylint: disable=protected-access
45- self ._segment_storage = factory ._get_storage ('segments' ) #pylint: disable=protected-access
46- self ._impressions_storage = factory ._get_storage ('impressions' ) #pylint: disable=protected-access
47- self ._events_storage = factory ._get_storage ('events' ) #pylint: disable=protected-access
48- self ._telemetry_storage = factory ._get_storage ('telemetry' ) #pylint: disable=protected-access
44+ self ._split_storage = factory ._get_storage ('splits' ) # pylint: disable=protected-access
45+ self ._segment_storage = factory ._get_storage ('segments' ) # pylint: disable=protected-access
46+ self ._impressions_storage = factory ._get_storage ('impressions' ) # pylint: disable=protected-access
47+ self ._events_storage = factory ._get_storage ('events' ) # pylint: disable=protected-access
48+ self ._telemetry_storage = factory ._get_storage ('telemetry' ) # pylint: disable=protected-access
4949 self ._evaluator = Evaluator (self ._split_storage , self ._segment_storage , self ._splitter )
5050
5151 def destroy (self ):
@@ -85,6 +85,25 @@ def _send_impression_to_listener(self, impression, attributes):
8585 )
8686 self ._logger .debug ('Error' , exc_info = True )
8787
88+ def _evaluate_if_ready (self , matching_key , bucketing_key , feature , attributes = None ):
89+ if not self .ready :
90+ return {
91+ 'treatment' : CONTROL ,
92+ 'configurations' : None ,
93+ 'impression' : {
94+ 'label' : Label .NOT_READY ,
95+ 'change_number' : None
96+ }
97+ }
98+
99+ return self ._evaluator .evaluate_treatment (
100+ feature ,
101+ matching_key ,
102+ bucketing_key ,
103+ attributes
104+ )
105+
106+
88107 def get_treatment_with_config (self , key , feature , attributes = None ):
89108 """
90109 Get the treatment and config for a feature and key, with optional dictionary of attributes.
@@ -109,19 +128,18 @@ def get_treatment_with_config(self, key, feature, attributes=None):
109128 start = int (round (time .time () * 1000 ))
110129
111130 matching_key , bucketing_key = input_validator .validate_key (key )
112- feature = input_validator .validate_feature_name (feature )
131+ feature = input_validator .validate_feature_name (
132+ feature ,
133+ self .ready ,
134+ self ._factory ._get_storage ('splits' ) #pylint: disable=protected-access
135+ )
113136
114137 if (matching_key is None and bucketing_key is None ) \
115138 or feature is None \
116139 or not input_validator .validate_attributes (attributes ):
117140 return CONTROL , None
118141
119- result = self ._evaluator .evaluate_treatment (
120- feature ,
121- matching_key ,
122- bucketing_key ,
123- attributes
124- )
142+ result = self ._evaluate_if_ready (matching_key , bucketing_key , feature , attributes )
125143
126144 impression = self ._build_impression (
127145 matching_key ,
@@ -136,7 +154,7 @@ def get_treatment_with_config(self, key, feature, attributes=None):
136154 self ._record_stats (impression , start , self ._METRIC_GET_TREATMENT )
137155 self ._send_impression_to_listener (impression , attributes )
138156 return result ['treatment' ], result ['configurations' ]
139- except Exception : #pylint: disable=broad-except
157+ except Exception : # pylint: disable=broad-except
140158 self ._logger .error ('Error getting treatment for feature' )
141159 self ._logger .debug ('Error: ' , exc_info = True )
142160 try :
@@ -204,34 +222,32 @@ def get_treatments_with_config(self, key, features, attributes=None):
204222 if input_validator .validate_attributes (attributes ) is False :
205223 return input_validator .generate_control_treatments (features )
206224
207- features = input_validator .validate_features_get_treatments (features )
225+ features , missing = input_validator .validate_features_get_treatments (
226+ features ,
227+ self .ready ,
228+ self ._factory ._get_storage ('splits' ) # pylint: disable=protected-access
229+ )
208230 if features is None :
209231 return {}
210232
211233 bulk_impressions = []
212- treatments = {}
234+ treatments = {name : ( CONTROL , None ) for name in missing }
213235
214236 for feature in features :
215237 try :
216- treatment = self ._evaluator .evaluate_treatment (
217- feature ,
218- matching_key ,
219- bucketing_key ,
220- attributes
221- )
222-
238+ result = self ._evaluate_if_ready (matching_key , bucketing_key , feature , attributes )
223239 impression = self ._build_impression (matching_key ,
224240 feature ,
225- treatment ['treatment' ],
226- treatment ['impression' ]['label' ],
227- treatment ['impression' ]['change_number' ],
241+ result ['treatment' ],
242+ result ['impression' ]['label' ],
243+ result ['impression' ]['change_number' ],
228244 bucketing_key ,
229245 start )
230246
231247 bulk_impressions .append (impression )
232- treatments [feature ] = (treatment ['treatment' ], treatment ['configurations' ])
248+ treatments [feature ] = (result ['treatment' ], result ['configurations' ])
233249
234- except Exception : #pylint: disable=broad-except
250+ except Exception : # pylint: disable=broad-except
235251 self ._logger .error ('get_treatments: An exception occured when evaluating '
236252 'feature ' + feature + ' returning CONTROL.' )
237253 treatments [feature ] = CONTROL , None
@@ -244,14 +260,13 @@ def get_treatments_with_config(self, key, features, attributes=None):
244260 self ._record_stats (bulk_impressions , start , self ._METRIC_GET_TREATMENTS )
245261 for impression in bulk_impressions :
246262 self ._send_impression_to_listener (impression , attributes )
247- except Exception : #pylint: disable=broad-except
263+ except Exception : # pylint: disable=broad-except
248264 self ._logger .error ('get_treatments: An exception when trying to store '
249265 'impressions.' )
250266 self ._logger .debug ('Error: ' , exc_info = True )
251267
252268 return treatments
253269
254-
255270 def get_treatments (self , key , features , attributes = None ):
256271 """
257272 Evaluate multiple features and return a dictionary with all the feature/treatments.
@@ -271,7 +286,7 @@ def get_treatments(self, key, features, attributes=None):
271286 with_config = self .get_treatments_with_config (key , features , attributes )
272287 return {feature : result [0 ] for (feature , result ) in six .iteritems (with_config )}
273288
274- def _build_impression ( #pylint: disable=too-many-arguments
289+ def _build_impression ( # pylint: disable=too-many-arguments
275290 self ,
276291 matching_key ,
277292 feature_name ,
@@ -311,11 +326,11 @@ def _record_stats(self, impressions, start, operation):
311326 else :
312327 self ._impressions_storage .put (impressions )
313328 self ._telemetry_storage .inc_latency (operation , get_latency_bucket_index (end - start ))
314- except Exception : #pylint: disable=broad-except
329+ except Exception : # pylint: disable=broad-except
315330 self ._logger .error ('Error recording impressions and metrics' )
316331 self ._logger .debug ('Error: ' , exc_info = True )
317332
318- def track (self , key , traffic_type , event_type , value = None ):
333+ def track (self , key , traffic_type , event_type , value = None , properties = None ):
319334 """
320335 Track an event.
321336
@@ -327,6 +342,8 @@ def track(self, key, traffic_type, event_type, value=None):
327342 :type event_type: str
328343 :param value: (Optional) value associated to the event
329344 :type value: Number
345+ :param properties: (Optional) properties associated to the event
346+ :type properties: dict
330347
331348 :return: Whether the event was created or not.
332349 :rtype: bool
@@ -337,17 +354,29 @@ def track(self, key, traffic_type, event_type, value=None):
337354
338355 key = input_validator .validate_track_key (key )
339356 event_type = input_validator .validate_event_type (event_type )
340- traffic_type = input_validator .validate_traffic_type (traffic_type )
357+ should_validate_existance = self .ready and self ._factory ._apikey != 'localhost' #pylint: disable=protected-access
358+ traffic_type = input_validator .validate_traffic_type (
359+ traffic_type ,
360+ should_validate_existance ,
361+ self ._factory ._get_storage ('splits' ), #pylint: disable=protected-access
362+ )
363+
341364 value = input_validator .validate_value (value )
365+ valid , properties , size = input_validator .valid_properties (properties )
342366
343- if key is None or event_type is None or traffic_type is None or value is False :
367+ if key is None or event_type is None or traffic_type is None or value is False \
368+ or valid is False :
344369 return False
345370
346371 event = Event (
347372 key = key ,
348373 traffic_type_name = traffic_type ,
349374 event_type_id = event_type ,
350375 value = value ,
351- timestamp = int (time .time ()* 1000 )
376+ timestamp = int (time .time ()* 1000 ),
377+ properties = properties ,
352378 )
353- return self ._events_storage .put ([event ])
379+ return self ._events_storage .put ([EventWrapper (
380+ event = event ,
381+ size = size ,
382+ )])
0 commit comments