Skip to content

Commit 9a2f178

Browse files
authored
Merge pull request #143 from splitio/development
Development
2 parents 71dfab2 + 65f33af commit 9a2f178

29 files changed

+1644
-296
lines changed

CHANGES.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
8.1.0 (3 Jun, 2019)
2+
- Added properties to track method.
3+
- Input Validation: added validation for traffic types, split names and multiple factory instantiation.
14
8.0.0 (Apr 24, 2019)
25
- Full SDK Refactor/rewrite.
36
- New block until ready behaviour.

splitio/api/events.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from splitio.api.client import HttpClientException
88

99

10-
class EventsAPI(object): #pylint: disable=too-few-public-methods
10+
class EventsAPI(object): # pylint: disable=too-few-public-methods
1111
"""Class that uses an httpClient to communicate with the events API."""
1212

1313
def __init__(self, http_client, apikey, sdk_metadata):
@@ -43,7 +43,8 @@ def _build_bulk(events):
4343
'trafficTypeName': event.traffic_type_name,
4444
'eventTypeId': event.event_type_id,
4545
'value': event.value,
46-
'timestamp': event.timestamp
46+
'timestamp': event.timestamp,
47+
'properties': event.properties,
4748
}
4849
for event in events
4950
]

splitio/api/impressions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from splitio.api.client import HttpClientException
1010

1111

12-
class ImpressionsAPI(object): # pylint: disable=too-few-public-methods
12+
class ImpressionsAPI(object): # pylint: disable=too-few-public-methods
1313
"""Class that uses an httpClient to communicate with the impressions API."""
1414

1515
def __init__(self, client, apikey, sdk_metadata):

splitio/client/client.py

Lines changed: 67 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@
88
from splitio.engine.evaluator import Evaluator, CONTROL
99
from splitio.engine.splitters import Splitter
1010
from splitio.models.impressions import Impression, Label
11-
from splitio.models.events import Event
11+
from splitio.models.events import Event, EventWrapper
1212
from splitio.models.telemetry import get_latency_bucket_index
1313
from splitio.client import input_validator
1414
from 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

Comments
 (0)