44
55import logging
66import time
7- import re
8- from numbers import Number
9- from six import string_types
107from splitio .treatments import CONTROL
118from splitio .splitters import Splitter
129from splitio .impressions import Impression , Label
1310from splitio .metrics import SDK_GET_TREATMENT
1411from splitio .splits import ConditionType
1512from splitio .events import Event
16-
17- class Key (object ):
18- """Key class includes a matching key and bucketing key."""
19-
20- def __init__ (self , matching_key , bucketing_key ):
21- """Construct a key object."""
22- self ._matching_key = matching_key
23- self ._bucketing_key = bucketing_key
24-
25- @property
26- def matching_key (self ):
27- """Return matching key."""
28- return self ._matching_key
29-
30- @property
31- def bucketing_key (self ):
32- """Return bucketing key."""
33- return self ._bucketing_key
13+ from splitio .input_validator import InputValidator
14+ from splitio .key import Key
3415
3516
3617class Client (object ):
@@ -54,31 +35,6 @@ def __init__(self, broker, labels_enabled=True):
5435 self ._labels_enabled = labels_enabled
5536 self ._destroyed = False
5637
57- def _get_keys (self , key ):
58- """
59- Parse received key.
60-
61- :param key: user submitted key
62- :type key: mixed
63-
64- :rtype: tuple(string,string)
65- """
66- if isinstance (key , Key ):
67- matching_key = key .matching_key
68- bucketing_key = key .bucketing_key
69- else :
70- if isinstance (key , string_types ):
71- matching_key = key
72- elif isinstance (key , Number ):
73- self ._logger .warning ("Key received as Number. Converting to string" )
74- matching_key = str (key )
75- else :
76- # If the key is not a string, int or Key,
77- # set keys to None in order to return CONTROL
78- return None , None
79- bucketing_key = None
80- return matching_key , bucketing_key
81-
8238 def destroy (self ):
8339 """
8440 Disable the split-client and free all allocated resources.
@@ -88,49 +44,6 @@ def destroy(self):
8844 self ._destroyed = True
8945 self ._broker .destroy ()
9046
91- def _validate_input (self , key , feature , start , attributes = None ):
92- """
93- Validate the user-supplied arguments. Return True if valid, False otherwhise.
94-
95- :param key: user key
96- :type key: mixed
97-
98- :param feature: feature name
99- :type feature: str
100-
101- :param attributes: custom user data
102- :type attributes: dict
103-
104- :rtype: tuple
105- """
106- if feature is None :
107- self ._logger .error ("Neither Key or FeatureName can be None" )
108- return None , None
109-
110- if not isinstance (feature , string_types ):
111- self ._logger .error ("feature name must be a string" )
112- return None , None
113-
114- if key is None :
115- self ._logger .error ("Neither Key or FeatureName can be None" )
116- impression = self ._build_impression ("" , feature , CONTROL , Label .EXCEPTION ,
117- 0 , None , start )
118- self ._record_stats (impression , start , SDK_GET_TREATMENT )
119- return None , None
120-
121-
122- matching_key , bucketing_key = self ._get_keys (key )
123- if matching_key is None and bucketing_key is None :
124- self ._logger .error (
125- "getTreatment: Key should be an object with bucketingKey and matchingKey"
126- "or a string."
127- )
128- return None , None
129-
130- return matching_key , bucketing_key
131-
132-
133-
13447 def get_treatment (self , key , feature , attributes = None ):
13548 """
13649 Get the treatment for a feature and key, with an optional dictionary of attributes.
@@ -153,8 +66,14 @@ def get_treatment(self, key, feature, attributes=None):
15366
15467 start = int (round (time .time () * 1000 ))
15568
156- matching_key , bucketing_key = self ._validate_input (key , feature , start , attributes )
157- if matching_key is None and bucketing_key is None :
69+ input_validator = InputValidator ()
70+ matching_key , bucketing_key = input_validator .validate_key (key )
71+ feature = input_validator .validate_feature_name (feature )
72+
73+ if ((matching_key is None and bucketing_key is None ) or (feature is None )):
74+ impression = self ._build_impression (matching_key , feature , CONTROL , Label .EXCEPTION ,
75+ 0 , None , start )
76+ self ._record_stats (impression , start , SDK_GET_TREATMENT )
15877 return CONTROL
15978
16079 try :
@@ -191,7 +110,7 @@ def get_treatment(self, key, feature, attributes=None):
191110 _change_number , bucketing_key , start )
192111 self ._record_stats (impression , start , SDK_GET_TREATMENT )
193112 return _treatment
194- except Exception : # pylint: disable=broad-except
113+ except Exception : # pylint: disable=broad-except
195114 self ._logger .exception ('Exception caught getting treatment for feature' )
196115
197116 try :
@@ -203,7 +122,7 @@ def get_treatment(self, key, feature, attributes=None):
203122 self ._broker .get_change_number (), bucketing_key , start
204123 )
205124 self ._record_stats (impression , start , SDK_GET_TREATMENT )
206- except Exception : # pylint: disable=broad-except
125+ except Exception : # pylint: disable=broad-except
207126 self ._logger .exception (
208127 'Exception reporting impression into get_treatment exception block'
209128 )
@@ -245,7 +164,7 @@ def _record_stats(self, impression, start, operation):
245164 end = int (round (time .time () * 1000 ))
246165 self ._broker .log_impression (impression )
247166 self ._broker .log_operation_time (operation , end - start )
248- except Exception : # pylint: disable=broad-except
167+ except Exception : # pylint: disable=broad-except
249168 self ._logger .exception ('Exception caught recording impressions and metrics' )
250169
251170 def _get_treatment_for_split (self , split , matching_key , bucketing_key , attributes = None ):
@@ -319,39 +238,13 @@ def track(self, key, traffic_type, event_type, value=None):
319238
320239 :rtype: bool
321240 """
322- if key is None :
323- self ._logger .error ("Key cannot be None" )
324- return False
325-
326- if isinstance (key , Number ):
327- self ._logger .warning ("Key must be string. Number supplied. Converting" )
328- key = str (key )
329-
330- if not isinstance (key , string_types ):
331- self ._logger .error ("Incorrect type of key supplied. Must be string" )
332- return False
333-
334- if event_type is None :
335- self ._logger .warning ("event_type cannot be None" )
336- return False
241+ input_validator = InputValidator ()
242+ key = input_validator .validate_track_key (key )
243+ event_type = input_validator .validate_event_type (event_type )
244+ traffic_type = input_validator .validate_traffic_type (traffic_type )
245+ value = input_validator .validate_value (value )
337246
338- if not isinstance (event_type , string_types ):
339- self ._logger .error ("event_name must be string" )
340- return False
341-
342- if not re .match (r'[a-zA-Z0-9][-_\.a-zA-Z0-9]{0,62}' , event_type ):
343- self ._logger .error (
344- 'event_type must match the regular expression "'
345- r'[a-zA-Z0-9][-_\.a-zA-Z0-9]{0,62}"'
346- )
347- return False
348-
349- if traffic_type is None or not isinstance (traffic_type , string_types ) or traffic_type == '' :
350- self ._logger .error ("traffic_type must be a non-empty string" )
351- return False
352-
353- if value is not None and not isinstance (value , Number ):
354- self ._logger .error ("value must be None or must be a number" )
247+ if key is None or event_type is None or traffic_type is None or value is None :
355248 return False
356249
357250 event = Event (
@@ -371,7 +264,7 @@ class MatcherClient(Client):
371264 TODO: Refactor This!
372265 """
373266
374- def __init__ (self , broker , splitter , logger ): # pylint: disable=super-init-not-called
267+ def __init__ (self , broker , splitter , logger ): # pylint: disable=super-init-not-called
375268 """
376269 Construct a MatcherClient instance.
377270
@@ -406,7 +299,13 @@ def get_treatment(self, key, feature, attributes=None):
406299 if key is None or feature is None :
407300 return CONTROL
408301
409- matching_key , bucketing_key = self ._get_keys (key )
302+ input_validator = InputValidator ()
303+ matching_key , bucketing_key = input_validator .validate_key (key )
304+ feature = input_validator .validate_feature_name (feature )
305+
306+ if ((matching_key is None and bucketing_key is None ) or feature is None ):
307+ return CONTROL
308+
410309 try :
411310 # Fetching Split definition
412311 split = self ._broker .fetch_feature (feature )
@@ -432,7 +331,7 @@ def get_treatment(self, key, feature, attributes=None):
432331 return split .default_treatment
433332
434333 return treatment
435- except Exception : # pylint: disable=broad-except
334+ except Exception : # pylint: disable=broad-except
436335 self ._logger .exception (
437336 'Exception caught retrieving dependent feature. Returning CONTROL'
438337 )
0 commit comments