Skip to content

Commit 0f4bf5f

Browse files
author
Matias Melograno
committed
merged with dev
2 parents 558c2bc + 1ae99bf commit 0f4bf5f

File tree

21 files changed

+334
-74
lines changed

21 files changed

+334
-74
lines changed

CHANGES.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
8.1.3 (Oct XX, 2019)
2+
- Added logic to fetch multiple splits at once on get_treatments/get_treatments_with_config.
3+
- Added flag `ipAddressesEnabled` into config to enable/disable sending machineName and machineIp when data is posted in headers.
4+
15
8.1.2 (Jul 19, 2019)
26
- Validated TLS support for redis connections
37
- Fixed traffic type count issue

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ This SDK is designed to work with Split, the platform for controlled rollouts, w
77
[![Twitter Follow](https://img.shields.io/twitter/follow/splitsoftware.svg?style=social&label=Follow&maxAge=1529000)](https://twitter.com/intent/follow?screen_name=splitsoftware)
88

99
## Compatibility
10-
This SDK is compatible with Python 2.7 and higher.
10+
This SDK is compatible with **Python 2.7 and higher**.
1111

1212
## Getting started
1313
Below is a simple example that describes the instantiation and most basic usage of our SDK:
@@ -29,7 +29,7 @@ except TimeoutException:
2929
pass
3030
```
3131

32-
Please refer to [our official docs](https://help.split.io/hc/en-us/articles/360020359652-Python-SDK)) to learn about all the functionality provided by our SDK and the configuration options available for tailoring it to your current application setup.
32+
Please refer to [our official docs](https://help.split.io/hc/en-us/articles/360020359652-Python-SDK) to learn about all the functionality provided by our SDK and the configuration options available for tailoring it to your current application setup.
3333

3434
## Submitting issues
3535
The Split team monitors all issues submitted to this [issue tracker](https://github.com/splitio/python-client/issues). We encourage you to use this issue tracker to submit any bug reports, feedback, and feature enhancements. We'll do our best to respond in a timely manner.

splitio/client/client.py

Lines changed: 57 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -96,14 +96,13 @@ def _evaluate_if_ready(self, matching_key, bucketing_key, feature, attributes=No
9696
}
9797
}
9898

99-
return self._evaluator.evaluate_treatment(
99+
return self._evaluator.evaluate_feature(
100100
feature,
101101
matching_key,
102102
bucketing_key,
103103
attributes
104104
)
105105

106-
107106
def get_treatment_with_config(self, key, feature, attributes=None):
108107
"""
109108
Get the treatment and config for a feature and key, with optional dictionary of attributes.
@@ -131,7 +130,7 @@ def get_treatment_with_config(self, key, feature, attributes=None):
131130
feature = input_validator.validate_feature_name(
132131
feature,
133132
self.ready,
134-
self._factory._get_storage('splits') #pylint: disable=protected-access
133+
self._factory._get_storage('splits') # pylint: disable=protected-access
135134
)
136135

137136
if (matching_key is None and bucketing_key is None) \
@@ -193,6 +192,24 @@ def get_treatment(self, key, feature, attributes=None):
193192
treatment, _ = self.get_treatment_with_config(key, feature, attributes)
194193
return treatment
195194

195+
def _evaluate_features_if_ready(self, matching_key, bucketing_key, features, attributes=None):
196+
if not self.ready:
197+
return {
198+
feature: {
199+
'treatment': CONTROL,
200+
'configurations': None,
201+
'impression': {'label': Label.NOT_READY, 'change_number': None}
202+
}
203+
for feature in features
204+
}
205+
206+
return self._evaluator.evaluate_features(
207+
features,
208+
matching_key,
209+
bucketing_key,
210+
attributes
211+
)
212+
196213
def get_treatments_with_config(self, key, features, attributes=None):
197214
"""
198215
Evaluate multiple features and return a dict with feature -> (treatment, config).
@@ -233,39 +250,47 @@ def get_treatments_with_config(self, key, features, attributes=None):
233250
bulk_impressions = []
234251
treatments = {name: (CONTROL, None) for name in missing}
235252

236-
for feature in features:
253+
try:
254+
evaluations = self._evaluate_features_if_ready(matching_key, bucketing_key,
255+
list(features), attributes)
256+
257+
for feature in features:
258+
try:
259+
result = evaluations[feature]
260+
impression = self._build_impression(matching_key,
261+
feature,
262+
result['treatment'],
263+
result['impression']['label'],
264+
result['impression']['change_number'],
265+
bucketing_key,
266+
start)
267+
268+
bulk_impressions.append(impression)
269+
treatments[feature] = (result['treatment'], result['configurations'])
270+
271+
except Exception: # pylint: disable=broad-except
272+
self._logger.error('get_treatments: An exception occured when evaluating '
273+
'feature ' + feature + ' returning CONTROL.')
274+
treatments[feature] = CONTROL, None
275+
self._logger.debug('Error: ', exc_info=True)
276+
continue
277+
278+
# Register impressions
237279
try:
238-
result = self._evaluate_if_ready(matching_key, bucketing_key, feature, attributes)
239-
impression = self._build_impression(matching_key,
240-
feature,
241-
result['treatment'],
242-
result['impression']['label'],
243-
result['impression']['change_number'],
244-
bucketing_key,
245-
start)
246-
247-
bulk_impressions.append(impression)
248-
treatments[feature] = (result['treatment'], result['configurations'])
249-
280+
if bulk_impressions:
281+
self._record_stats(bulk_impressions, start, self._METRIC_GET_TREATMENTS)
282+
for impression in bulk_impressions:
283+
self._send_impression_to_listener(impression, attributes)
250284
except Exception: # pylint: disable=broad-except
251-
self._logger.error('get_treatments: An exception occured when evaluating '
252-
'feature ' + feature + ' returning CONTROL.')
253-
treatments[feature] = CONTROL, None
285+
self._logger.error('get_treatments: An exception when trying to store '
286+
'impressions.')
254287
self._logger.debug('Error: ', exc_info=True)
255-
continue
256288

257-
# Register impressions
258-
try:
259-
if bulk_impressions:
260-
self._record_stats(bulk_impressions, start, self._METRIC_GET_TREATMENTS)
261-
for impression in bulk_impressions:
262-
self._send_impression_to_listener(impression, attributes)
289+
return treatments
263290
except Exception: # pylint: disable=broad-except
264-
self._logger.error('get_treatments: An exception when trying to store '
265-
'impressions.')
291+
self._logger.error('Error getting treatment for features')
266292
self._logger.debug('Error: ', exc_info=True)
267-
268-
return treatments
293+
return input_validator.generate_control_treatments(list(features))
269294

270295
def get_treatments(self, key, features, attributes=None):
271296
"""
@@ -354,11 +379,11 @@ def track(self, key, traffic_type, event_type, value=None, properties=None):
354379

355380
key = input_validator.validate_track_key(key)
356381
event_type = input_validator.validate_event_type(event_type)
357-
should_validate_existance = self.ready and self._factory._apikey != 'localhost' #pylint: disable=protected-access
382+
should_validate_existance = self.ready and self._factory._apikey != 'localhost' # pylint: disable=protected-access
358383
traffic_type = input_validator.validate_traffic_type(
359384
traffic_type,
360385
should_validate_existance,
361-
self._factory._get_storage('splits'), #pylint: disable=protected-access
386+
self._factory._get_storage('splits'), # pylint: disable=protected-access
362387
)
363388

364389
value = input_validator.validate_value(value)

splitio/client/manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def __init__(self, factory):
1818
"""
1919
self._logger = logging.getLogger(self.__class__.__name__)
2020
self._factory = factory
21-
self._storage = factory._get_storage('splits') #pylint: disable=protected-access
21+
self._storage = factory._get_storage('splits') # pylint: disable=protected-access
2222

2323
def split_names(self):
2424
"""

splitio/client/util.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,7 @@ def get_calls(classes_filter=None):
6868
inspect.getframeinfo(frame[0]).function
6969
for frame in inspect.stack()
7070
if classes_filter is None
71-
or 'self' in frame[0].f_locals
72-
and frame[0].f_locals['self'].__class__.__name__
73-
in classes_filter # pylint: disable=line-too-long
71+
or 'self' in frame[0].f_locals and frame[0].f_locals['self'].__class__.__name__ in classes_filter # pylint: disable=line-too-long
7472
]
7573
except Exception: # pylint: disable=broad-except
7674
return []

splitio/engine/evaluator.py

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Split evaluator module."""
22
import logging
3+
import six
34
from splitio.models.grammar.condition import ConditionType
45
from splitio.models.impressions import Label
56

@@ -25,8 +26,7 @@ def __init__(self, split_storage, segment_storage, splitter):
2526
self._segment_storage = segment_storage
2627
self._splitter = splitter
2728

28-
def evaluate_treatment(self, feature, matching_key,
29-
bucketing_key, attributes=None):
29+
def _evaluate_treatment(self, feature, matching_key, bucketing_key, attributes, split):
3030
"""
3131
Evaluate the user submitted data against a feature and return the resulting treatment.
3232
@@ -42,20 +42,19 @@ def evaluate_treatment(self, feature, matching_key,
4242
:param attributes: An optional dictionary of attributes
4343
:type attributes: dict
4444
45+
:param split: Split object
46+
:type attributes: splitio.models.splits.Split|None
47+
4548
:return: The treatment for the key and split
4649
:rtype: object
4750
"""
4851
label = ''
4952
_treatment = CONTROL
5053
_change_number = -1
5154

52-
# Fetching Split definition
53-
split = self._split_storage.get(feature)
54-
5555
if split is None:
5656
self._logger.warning('Unknown or invalid feature: %s', feature)
5757
label = Label.SPLIT_NOT_FOUND
58-
_treatment = CONTROL
5958
else:
6059
_change_number = split.change_number
6160
if split.killed:
@@ -83,6 +82,70 @@ def evaluate_treatment(self, feature, matching_key,
8382
}
8483
}
8584

85+
def evaluate_feature(self, feature, matching_key, bucketing_key, attributes=None):
86+
"""
87+
Evaluate the user submitted data against a feature and return the resulting treatment.
88+
89+
:param feature: The feature for which to get the treatment
90+
:type feature: str
91+
92+
:param matching_key: The matching_key for which to get the treatment
93+
:type matching_key: str
94+
95+
:param bucketing_key: The bucketing_key for which to get the treatment
96+
:type bucketing_key: str
97+
98+
:param attributes: An optional dictionary of attributes
99+
:type attributes: dict
100+
101+
:return: The treatment for the key and split
102+
:rtype: object
103+
"""
104+
# Fetching Split definition
105+
split = self._split_storage.get(feature)
106+
107+
# Calling evaluation
108+
evaluation = self._evaluate_treatment(feature, matching_key,
109+
bucketing_key, attributes, split)
110+
111+
return evaluation
112+
113+
def evaluate_features(self, features, matching_key, bucketing_key, attributes=None):
114+
"""
115+
Evaluate the user submitted data against multiple features and return the resulting
116+
treatment.
117+
118+
:param features: The features for which to get the treatments
119+
:type feature: list(str)
120+
121+
:param matching_key: The matching_key for which to get the treatment
122+
:type matching_key: str
123+
124+
:param bucketing_key: The bucketing_key for which to get the treatment
125+
:type bucketing_key: str
126+
127+
:param attributes: An optional dictionary of attributes
128+
:type attributes: dict
129+
130+
:return: The treatments for the key and splits
131+
:rtype: object
132+
"""
133+
evaluations = dict()
134+
135+
# Fetching Split definition
136+
splits = self._split_storage.fetch_many(features)
137+
# Calling evaluations
138+
for feature in features:
139+
split = splits[feature]
140+
evaluations[feature] = self._evaluate_treatment(feature, matching_key,
141+
bucketing_key, attributes, split)
142+
return evaluations
143+
return {
144+
feature: self._evaluate_treatment(feature, matching_key,
145+
bucketing_key, attributes, split)
146+
for (feature, split) in six.iteritems(self._split_storage.fetch_many(features))
147+
}
148+
86149
def _get_treatment_for_split(self, split, matching_key, bucketing_key, attributes=None):
87150
"""
88151
Evaluate the feature considering the conditions.

splitio/models/grammar/matchers/misc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def _match(self, key, attributes=None, context=None):
4141

4242
bucketing_key = context.get('bucketing_key')
4343

44-
result = evaluator.evaluate_treatment(self._split_name, key, bucketing_key, attributes)
44+
result = evaluator.evaluate_feature(self._split_name, key, bucketing_key, attributes)
4545
return result['treatment'] in self._treatments
4646

4747
def _add_matcher_specific_properties_to_json(self):

splitio/models/impressions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
)
2020

2121

22-
class Label(object): #pylint: disable=too-few-public-methods
22+
class Label(object): # pylint: disable=too-few-public-methods
2323
"""Impressions labels."""
2424

2525
# Condition: Split Was Killed

splitio/storage/__init__.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from six import add_metaclass
77

8+
89
@add_metaclass(abc.ABCMeta)
910
class SplitStorage(object):
1011
"""Split storage interface implemented as an abstract class."""
@@ -21,6 +22,18 @@ def get(self, split_name):
2122
"""
2223
pass
2324

25+
@abc.abstractmethod
26+
def fetch_many(self, split_names):
27+
"""
28+
Retrieve splits.
29+
30+
:param split_names: Names of the features to fetch.
31+
:type split_names: list(str)
32+
33+
:rtype: dict
34+
"""
35+
pass
36+
2437
@abc.abstractmethod
2538
def put(self, split):
2639
"""

splitio/storage/inmemmory.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,18 @@ def get(self, split_name):
3636
with self._lock:
3737
return self._splits.get(split_name)
3838

39+
def fetch_many(self, split_names):
40+
"""
41+
Retrieve splits.
42+
43+
:param split_names: Names of the features to fetch.
44+
:type split_name: list(str)
45+
46+
:return: A dict with split objects parsed from queue.
47+
:rtype: dict(split_name, splitio.models.splits.Split)
48+
"""
49+
return {split_name: self.get(split_name) for split_name in split_names}
50+
3951
def put(self, split):
4052
"""
4153
Store a split.

0 commit comments

Comments
 (0)