Skip to content

Commit 8a07375

Browse files
authored
Merge pull request #104 from splitio/development
[SDKS-128]: GetTreatments
2 parents 324fe0b + 7efaa33 commit 8a07375

File tree

8 files changed

+277
-32
lines changed

8 files changed

+277
-32
lines changed

.github/pull_request_template.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Python SDK
22

33
## Tickets covered:
4-
* [SDKS-{TICKET}](https://splitio.atlassian.net/browse/SDKS-{TICKET})
4+
*
55

66
## What did you accomplish?
77
* Bullet 1
@@ -12,4 +12,4 @@
1212

1313
## Extra Notes
1414
* Bullet 1
15-
* Bullet 2
15+
* Bullet 2

CHANGES.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
6.2.0 (Oct 5, 2018)
2+
- Added get_treatments method.
13
6.1.0 (Sep 25, 2018)
24
- Add custom impression listener feature.
35
- Input Sanitization for track, get_treatment and split.

splitio/clients.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,31 @@ def get_treatment(self, key, feature, attributes=None):
152152
)
153153
return CONTROL
154154

155+
def get_treatments(self, key, features, attributes=None):
156+
"""
157+
Get the treatments for a list of features considering a key, with an optional dictionary of
158+
attributes. This method never raises an exception. If there's a problem, the appropriate
159+
log message will be generated and the method will return the CONTROL treatment.
160+
:param key: The key for which to get the treatment
161+
:type key: str
162+
:param features: Array of the names of the features for which to get the treatment
163+
:type feature: list
164+
:param attributes: An optional dictionary of attributes
165+
:type attributes: dict
166+
:return: Dictionary with the result of all the features provided
167+
:rtype: dict
168+
"""
169+
if self._destroyed:
170+
self._logger.warning("Client has already been destroyed, returning None")
171+
return None
172+
173+
features = input_validator.validate_features_get_treatments(features)
174+
175+
if features is None:
176+
return None
177+
178+
return {feature: self.get_treatment(key, feature, attributes) for feature in features}
179+
155180
def _build_impression(
156181
self, matching_key, feature_name, treatment, label,
157182
change_number, bucketing_key, imp_time

splitio/input_validator.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ def _check_is_string(value, name, operation):
4343
:rtype: True|False
4444
"""
4545
if isinstance(value, six.string_types) is False:
46-
_LOGGER.error('{}: {} {} has to be of type string.'.format(
47-
operation, name, value))
46+
_LOGGER.error('{}: {} has to be of type string.'.format(
47+
operation, name))
4848
return False
4949
return True
5050

@@ -109,7 +109,7 @@ def _check_can_convert(value, name, operation, message):
109109
return True
110110
else:
111111
if isinstance(value, bool) or (not isinstance(value, Number)):
112-
_LOGGER.error('{}: {} {} {}'.format(operation, name, value, message))
112+
_LOGGER.error('{}: {} {}'.format(operation, name, message))
113113
return False
114114
_LOGGER.warning('{}: {} {} is not of type string, converting.'
115115
.format(operation, name, value))
@@ -282,3 +282,24 @@ def validate_manager_feature_name(feature_name):
282282
(not _check_is_string(feature_name, 'feature_name', 'split')):
283283
return None
284284
return feature_name
285+
286+
287+
def validate_features_get_treatments(features):
288+
"""
289+
Checks if features is valid for get_treatments
290+
291+
:param features: array of features
292+
:type features: list
293+
:return: filtered_features
294+
:rtype: list|None
295+
"""
296+
if not _check_not_null(features, 'features', 'get_treatments'):
297+
return None
298+
if not isinstance(features, list):
299+
_LOGGER.error('get_treatments: features must be a list.')
300+
return None
301+
filtered_features = set(filter(lambda x: x is not None and
302+
_check_is_string(x, 'feature_name', 'get_treatments'), features))
303+
if len(filtered_features) == 0:
304+
_LOGGER.warning('get_treatments: features is an empty list or has None values.')
305+
return filtered_features
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
"splits": [
3+
{
4+
"orgId": null,
5+
"environment": null,
6+
"trafficTypeId": null,
7+
"trafficTypeName": null,
8+
"name": "get_treatments_test",
9+
"seed": -1329591480,
10+
"status": "ACTIVE",
11+
"killed": false,
12+
"defaultTreatment": "off",
13+
"changeNumber": 1325599980,
14+
"conditions": [
15+
{
16+
"matcherGroup": {
17+
"combiner": "AND",
18+
"matchers": [
19+
{
20+
"keySelector": null,
21+
"matcherType": "WHITELIST",
22+
"negate": false,
23+
"userDefinedSegmentMatcherData": null,
24+
"whitelistMatcherData": {
25+
"whitelist": [
26+
"valid"
27+
]
28+
},
29+
"unaryNumericMatcherData": null,
30+
"betweenMatcherData": null
31+
}
32+
]
33+
},
34+
"partitions": [
35+
{
36+
"treatment": "on",
37+
"size": 100
38+
}
39+
]
40+
}
41+
]
42+
}
43+
],
44+
"since": -1,
45+
"till": 1461957424937
46+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
try:
2+
from unittest import mock
3+
except ImportError:
4+
# Python 2
5+
import mock
6+
7+
from os.path import dirname, join
8+
from json import load
9+
from unittest import TestCase
10+
11+
from splitio.clients import Client
12+
from splitio.redis_support import (RedisSplitCache, get_redis)
13+
from splitio.brokers import RedisBroker
14+
from splitio import get_factory
15+
16+
17+
class GetTreatmentsTest(TestCase):
18+
def setUp(self):
19+
self._some_config = mock.MagicMock()
20+
self._split_changes_file_name = join(dirname(__file__),
21+
'splitGetTreatments.json')
22+
23+
with open(self._split_changes_file_name) as f:
24+
self._json = load(f)
25+
split_definition = self._json['splits'][0]
26+
split_name = split_definition['name']
27+
28+
self._redis = get_redis({'redisPrefix': 'getTreatmentsTest'})
29+
30+
self._redis_split_cache = RedisSplitCache(self._redis)
31+
self._redis_split_cache.add_split(split_name, split_definition)
32+
self._client = Client(RedisBroker(self._redis, self._some_config))
33+
34+
self._config = {
35+
'ready': 180000,
36+
'redisDb': 0,
37+
'redisHost': 'localhost',
38+
'redisPosrt': 6379,
39+
'redisPrefix': 'getTreatmentsTest'
40+
}
41+
self._factory = get_factory('asdqwe123456', config=self._config)
42+
self._split = self._factory.client()
43+
44+
def test_clien_with_distinct_features(self):
45+
results = self._split.get_treatments('some_key', ['some_feature', 'some_feature_2'])
46+
self.assertIn('some_feature', results)
47+
self.assertIn('some_feature_2', results)
48+
self.assertDictEqual(results, {
49+
'some_feature': 'control',
50+
'some_feature_2': 'control'
51+
})
52+
53+
def test_clien_with_repeated_features(self):
54+
results = self._split.get_treatments('some_key', ['some_feature', 'some_feature_2',
55+
'some_feature', 'some_feature'])
56+
self.assertIn('some_feature', results)
57+
self.assertIn('some_feature_2', results)
58+
self.assertDictEqual(results, {
59+
'some_feature': 'control',
60+
'some_feature_2': 'control'
61+
})
62+
63+
def test_clien_with_none_and_repeated_features(self):
64+
results = self._split.get_treatments('some_key', ['some_feature', None, 'some_feature_2',
65+
'some_feature', 'some_feature', None])
66+
self.assertIn('some_feature', results)
67+
self.assertIn('some_feature_2', results)
68+
self.assertDictEqual(results, {
69+
'some_feature': 'control',
70+
'some_feature_2': 'control'
71+
})
72+
73+
def test_client_with_valid_none_and_repeated_features_and_invalid_key(self):
74+
features = ['some_feature', 'get_treatments_test', 'some_feature_2',
75+
'some_feature', 'get_treatments_test', None, 'valid']
76+
results = self._split.get_treatments('some_key', features)
77+
self.assertIn('some_feature', results)
78+
self.assertIn('some_feature_2', results)
79+
self.assertIn('get_treatments_test', results)
80+
self.assertEqual(results['some_feature'], 'control')
81+
self.assertEqual(results['some_feature_2'], 'control')
82+
self.assertEqual(results['get_treatments_test'], 'off')
83+
84+
def test_client_with_valid_none_and_repeated_features_and_valid_key(self):
85+
features = ['some_feature', 'get_treatments_test', 'some_feature_2',
86+
'some_feature', 'get_treatments_test', None, 'valid']
87+
results = self._split.get_treatments('valid', features)
88+
self.assertIn('some_feature', results)
89+
self.assertIn('some_feature_2', results)
90+
self.assertIn('get_treatments_test', results)
91+
self.assertEqual(results['some_feature'], 'control')
92+
self.assertEqual(results['some_feature_2'], 'control')
93+
self.assertEqual(results['get_treatments_test'], 'on')
94+
95+
def test_client_with_valid_none_invalid_and_repeated_features_and_valid_key(self):
96+
features = ['some_feature', 'get_treatments_test', 'some_feature_2',
97+
'some_feature', 'get_treatments_test', None, 'valid',
98+
True, [], True]
99+
results = self._split.get_treatments('valid', features)
100+
self.assertIn('some_feature', results)
101+
self.assertIn('some_feature_2', results)
102+
self.assertIn('get_treatments_test', results)
103+
self.assertEqual(results['some_feature'], 'control')
104+
self.assertEqual(results['some_feature_2'], 'control')
105+
self.assertEqual(results['get_treatments_test'], 'on')

0 commit comments

Comments
 (0)