1- """A module for Split.io SDK API clients"""
1+ """A module for Split.io SDK API clients. """
22from __future__ import absolute_import , division , print_function , \
33 unicode_literals
44
1010from splitio .metrics import SDK_GET_TREATMENT
1111from splitio .splits import ConditionType
1212from splitio .events import Event
13-
14-
15- class Key (object ):
16- def __init__ (self , matching_key , bucketing_key ):
17- """Bucketing Key implementation"""
18- self .matching_key = matching_key
19- self .bucketing_key = bucketing_key
13+ from . import input_validator
14+ from splitio .key import Key
2015
2116
2217class Client (object ):
18+ """Client class that uses a broker for storage."""
19+
2320 def __init__ (self , broker , labels_enabled = True , impression_listener = None ):
24- """Basic interface of a Client. Specific implementations need to override the
25- get_split_fetcher method (and optionally the get_splitter method).
21+ """
22+ Construct a Client instance.
23+
24+ :param broker: Broker that accepts/retrieves splits, segments, events, metrics & impressions
25+ :type broker: BaseBroker
26+
27+ :param labels_enabled: Whether to store labels on impressions
28+ :type labels_enabled: bool
29+
30+ :param impression_listener: impression listener implementation
31+ :type impression_listener: ImpressionListener
32+
33+ :rtype: Client
2634 """
2735 self ._logger = logging .getLogger (self .__class__ .__name__ )
2836 self ._splitter = Splitter ()
@@ -31,21 +39,10 @@ def __init__(self, broker, labels_enabled=True, impression_listener=None):
3139 self ._destroyed = False
3240 self ._impression_listener = impression_listener
3341
34- @staticmethod
35- def _get_keys (key ):
36- """
37- """
38- if isinstance (key , Key ):
39- matching_key = key .matching_key
40- bucketing_key = key .bucketing_key
41- else :
42- matching_key = str (key )
43- bucketing_key = None
44- return matching_key , bucketing_key
45-
4642 def destroy (self ):
4743 """
4844 Disable the split-client and free all allocated resources.
45+
4946 Only applicable when using in-memory operation mode.
5047 """
5148 self ._destroyed = True
@@ -64,9 +61,11 @@ def _handle_custom_impression(self, impression, attributes):
6461
6562 def get_treatment (self , key , feature , attributes = None ):
6663 """
67- Get the treatment for a feature and key, with an optional dictionary of attributes. This
68- method never raises an exception. If there's a problem, the appropriate log message will
69- be generated and the method will return the CONTROL treatment.
64+ Get the treatment for a feature and key, with an optional dictionary of attributes.
65+
66+ This method never raises an exception. If there's a problem, the appropriate log message
67+ will be generated and the method will return the CONTROL treatment.
68+
7069 :param key: The key for which to get the treatment
7170 :type key: str
7271 :param feature: The name of the feature for which to get the treatment
@@ -77,14 +76,19 @@ def get_treatment(self, key, feature, attributes=None):
7776 :rtype: str
7877 """
7978 if self ._destroyed :
80- return CONTROL
81-
82- if key is None or feature is None :
79+ self ._logger .warning ("Client has already been destroyed, returning CONTROL" )
8380 return CONTROL
8481
8582 start = int (round (time .time () * 1000 ))
8683
87- matching_key , bucketing_key = self ._get_keys (key )
84+ matching_key , bucketing_key = input_validator .validate_key (key )
85+ feature = input_validator .validate_feature_name (feature )
86+
87+ if (matching_key is None and bucketing_key is None ) or feature is None :
88+ impression = self ._build_impression (matching_key , feature , CONTROL , Label .EXCEPTION ,
89+ 0 , bucketing_key , start )
90+ self ._record_stats (impression , start , SDK_GET_TREATMENT )
91+ return CONTROL
8892
8993 try :
9094 label = ''
@@ -123,49 +127,79 @@ def get_treatment(self, key, feature, attributes=None):
123127 self ._handle_custom_impression (impression , attributes )
124128
125129 return _treatment
126- except :
130+ except Exception : # pylint: disable=broad-except
127131 self ._logger .exception ('Exception caught getting treatment for feature' )
128132
129133 try :
130- impression = self ._build_impression (matching_key , feature , CONTROL , Label .EXCEPTION ,
131- self ._broker .get_change_number (), bucketing_key , start )
134+ impression = self ._build_impression (
135+ matching_key ,
136+ feature ,
137+ CONTROL ,
138+ Label .EXCEPTION ,
139+ self ._broker .get_change_number (), bucketing_key , start
140+ )
132141 self ._record_stats (impression , start , SDK_GET_TREATMENT )
142+ except Exception : # pylint: disable=broad-except
143+ self ._logger .exception (
144+ 'Exception reporting impression into get_treatment exception block'
145+ )
133146
134147 self ._handle_custom_impression (impression , attributes )
135- except :
136- self ._logger .exception ('Exception reporting impression into get_treatment exception block' )
137148
138149 return CONTROL
139150
140- def _build_impression (self , matching_key , feature_name , treatment , label , change_number , bucketing_key , time ):
151+ def _build_impression (
152+ self , matching_key , feature_name , treatment , label ,
153+ change_number , bucketing_key , imp_time
154+ ):
155+ """
156+ Build an impression.
141157
158+ TODO: REFACTOR THIS!
159+ """
142160 if not self ._labels_enabled :
143161 label = None
144162
145163 return Impression (
146164 matching_key = matching_key , feature_name = feature_name ,
147165 treatment = treatment , label = label , change_number = change_number ,
148- bucketing_key = bucketing_key , time = time
166+ bucketing_key = bucketing_key , time = imp_time
149167 )
150168
151169 def _record_stats (self , impression , start , operation ):
170+ """
171+ Record impression and metrics.
172+
173+ :param impression: Generated impression
174+ :type impression: Impression
175+
176+ :param start: timestamp when get_treatment was called
177+ :type start: int
178+
179+ :param operation: operation performed.
180+ :type operation: string
181+ """
152182 try :
153183 end = int (round (time .time () * 1000 ))
154184 self ._broker .log_impression (impression )
155185 self ._broker .log_operation_time (operation , end - start )
156- except :
186+ except Exception : # pylint: disable=broad-except
157187 self ._logger .exception ('Exception caught recording impressions and metrics' )
158188
159189 def _get_treatment_for_split (self , split , matching_key , bucketing_key , attributes = None ):
160190 """
161- Internal method to get the treatment for a given Split and optional attributes. This
162- method might raise exceptions and should never be used directly.
191+ Evaluate the user submitted data againt a feature and return the resulting treatment.
192+
193+ This method might raise exceptions and should never be used directly.
163194 :param split: The split for which to get the treatment
164195 :type split: Split
196+
165197 :param key: The key for which to get the treatment
166198 :type key: str
199+
167200 :param attributes: An optional dictionary of attributes
168201 :type attributes: dict
202+
169203 :return: The treatment for the key and split
170204 :rtype: str
171205 """
@@ -207,32 +241,85 @@ def _get_treatment_for_split(self, split, matching_key, bucketing_key, attribute
207241
208242 def track (self , key , traffic_type , event_type , value = None ):
209243 """
210- Track an event
244+ Track an event.
245+
246+ :param key: user key associated to the event
247+ :type key: string
248+
249+ :param traffic_type: traffic type name
250+ :type traffic_type: string
251+
252+ :param event_type: event type name
253+ :type event_type: string
254+
255+ :param value: (Optional) value associated to the event
256+ :type value: Number
257+
258+ :rtype: bool
211259 """
212- e = Event (
260+ key = input_validator .validate_track_key (key )
261+ event_type = input_validator .validate_event_type (event_type )
262+ traffic_type = input_validator .validate_traffic_type (traffic_type )
263+ value = input_validator .validate_value (value )
264+
265+ if key is None or event_type is None or traffic_type is None or value is None :
266+ return False
267+
268+ event = Event (
213269 key = key ,
214270 trafficTypeName = traffic_type ,
215271 eventTypeId = event_type ,
216272 value = value ,
217273 timestamp = int (time .time ()* 1000 )
218274 )
219- return self ._broker .get_events_log ().log_event (e )
275+ return self ._broker .get_events_log ().log_event (event )
220276
221277
222278class MatcherClient (Client ):
223279 """
280+ Client to be used by matchers such as "Dependency Matcher".
281+
282+ TODO: Refactor This!
224283 """
225- def __init__ (self , broker , splitter , logger ):
284+
285+ def __init__ (self , broker , splitter , logger ): # pylint: disable=super-init-not-called
286+ """
287+ Construct a MatcherClient instance.
288+
289+ :param broker: Broker where splits & segments will be fetched.
290+ :type broker: BaseBroker
291+
292+ :param splitter: splitter
293+ :type splitter: Splitter
294+
295+ :param logger: logger object
296+ :type logger: logging.Logger
297+ """
226298 self ._broker = broker
227299 self ._splitter = splitter
228300 self ._logger = logger
229301
230302 def get_treatment (self , key , feature , attributes = None ):
231303 """
304+ Evaluate a feature and return the appropriate traetment.
305+
306+ Will not generate impressions nor metrics
307+
308+ :param key: user key
309+ :type key: mixed
310+
311+ :param feature: feature name
312+ :type feature: str
313+
314+ :param attributes: (Optional) attributes associated with the user key
315+ :type attributes: dict
232316 """
233- if key is None or feature is None : return CONTROL
317+ matching_key , bucketing_key = input_validator .validate_key (key )
318+ feature = input_validator .validate_feature_name (feature )
319+
320+ if (matching_key is None and bucketing_key is None ) or feature is None :
321+ return CONTROL
234322
235- matching_key , bucketing_key = self ._get_keys (key )
236323 try :
237324 # Fetching Split definition
238325 split = self ._broker .fetch_feature (feature )
@@ -244,7 +331,8 @@ def get_treatment(self, key, feature, attributes=None):
244331 )
245332 return CONTROL
246333
247- if split .killed : return split .default_treatment
334+ if split .killed :
335+ return split .default_treatment
248336
249337 treatment , _ = self ._get_treatment_for_split (
250338 split ,
@@ -253,8 +341,12 @@ def get_treatment(self, key, feature, attributes=None):
253341 attributes
254342 )
255343
256- if treatment is None : return split .default_treatment
344+ if treatment is None :
345+ return split .default_treatment
346+
257347 return treatment
258- except :
259- self ._logger .exception ('Exception caught retrieving dependent feature. Returning CONTROL' )
348+ except Exception : # pylint: disable=broad-except
349+ self ._logger .exception (
350+ 'Exception caught retrieving dependent feature. Returning CONTROL'
351+ )
260352 return CONTROL
0 commit comments