Skip to content

Commit 6e8188d

Browse files
authored
Merge pull request #563 from splitio/update-evaluator-rbs-storage
updated storage helper and evaluator
2 parents db38e3e + 2e7f5d3 commit 6e8188d

File tree

8 files changed

+272
-35
lines changed

8 files changed

+272
-35
lines changed

splitio/engine/evaluator.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -133,16 +133,19 @@ def context_for(self, key, feature_names):
133133
key_membership = False
134134
segment_memberhsip = False
135135
for rbs_segment in pending_rbs_memberships:
136-
key_membership = key in self._rbs_segment_storage.get(rbs_segment).excluded.get_excluded_keys()
136+
rbs_segment_obj = self._rbs_segment_storage.get(rbs_segment)
137+
pending_memberships.update(rbs_segment_obj.get_condition_segment_names())
138+
139+
key_membership = key in rbs_segment_obj.excluded.get_excluded_keys()
137140
segment_memberhsip = False
138-
for segment_name in self._rbs_segment_storage.get(rbs_segment).excluded.get_excluded_segments():
141+
for segment_name in rbs_segment_obj.excluded.get_excluded_segments():
139142
if self._segment_storage.segment_contains(segment_name, key):
140143
segment_memberhsip = True
141144
break
142145

143146
rbs_segment_memberships.update({rbs_segment: segment_memberhsip or key_membership})
144147
if not (segment_memberhsip or key_membership):
145-
rbs_segment_conditions.update({rbs_segment: [condition for condition in self._rbs_segment_storage.get(rbs_segment).conditions]})
148+
rbs_segment_conditions.update({rbs_segment: [condition for condition in rbs_segment_obj.conditions]})
146149

147150
return EvaluationContext(
148151
splits,
@@ -184,18 +187,14 @@ async def context_for(self, key, feature_names):
184187
pending_memberships.update(cs)
185188
pending_rbs_memberships.update(crbs)
186189

187-
segment_names = list(pending_memberships)
188-
segment_memberships = await asyncio.gather(*[
189-
self._segment_storage.segment_contains(segment, key)
190-
for segment in segment_names
191-
])
192-
193190
rbs_segment_memberships = {}
194191
rbs_segment_conditions = {}
195192
key_membership = False
196193
segment_memberhsip = False
197194
for rbs_segment in pending_rbs_memberships:
198195
rbs_segment_obj = await self._rbs_segment_storage.get(rbs_segment)
196+
pending_memberships.update(rbs_segment_obj.get_condition_segment_names())
197+
199198
key_membership = key in rbs_segment_obj.excluded.get_excluded_keys()
200199
segment_memberhsip = False
201200
for segment_name in rbs_segment_obj.excluded.get_excluded_segments():
@@ -207,6 +206,11 @@ async def context_for(self, key, feature_names):
207206
if not (segment_memberhsip or key_membership):
208207
rbs_segment_conditions.update({rbs_segment: [condition for condition in rbs_segment_obj.conditions]})
209208

209+
segment_names = list(pending_memberships)
210+
segment_memberships = await asyncio.gather(*[
211+
self._segment_storage.segment_contains(segment, key)
212+
for segment in segment_names
213+
])
210214
return EvaluationContext(
211215
splits,
212216
dict(zip(segment_names, segment_memberships)),

splitio/models/rule_based_segments.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,14 @@ def to_json(self):
7676
'excluded': self.excluded.to_json()
7777
}
7878

79+
def get_condition_segment_names(self):
80+
segments = set()
81+
for condition in self._conditions:
82+
for matcher in condition.matchers:
83+
if matcher._matcher_type == 'IN_SEGMENT':
84+
segments.add(matcher.to_json()['userDefinedSegmentMatcherData']['segmentName'])
85+
return segments
86+
7987
def from_raw(raw_rule_based_segment):
8088
"""
8189
Parse a Rule based segment from a JSON portion of splitChanges.

splitio/storage/inmemmory.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ def get_segment_names(self):
200200
"""
201201
with self._lock:
202202
return list(self._rule_based_segments.keys())
203-
203+
204204
def get_large_segment_names(self):
205205
"""
206206
Retrieve a list of all excluded large segments names.

splitio/util/storage_helper.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ def update_rule_based_segment_storage(rule_based_segment_storage, rule_based_seg
5353
if rule_based_segment.status == "ACTIVE":
5454
to_add.append(rule_based_segment)
5555
segment_list.update(set(rule_based_segment.excluded.get_excluded_segments()))
56+
segment_list.update(rule_based_segment.get_condition_segment_names())
5657
else:
5758
if rule_based_segment_storage.get(rule_based_segment.name) is not None:
5859
to_delete.append(rule_based_segment.name)
@@ -109,6 +110,7 @@ async def update_rule_based_segment_storage_async(rule_based_segment_storage, ru
109110
if rule_based_segment.status == "ACTIVE":
110111
to_add.append(rule_based_segment)
111112
segment_list.update(set(rule_based_segment.excluded.get_excluded_segments()))
113+
segment_list.update(rule_based_segment.get_condition_segment_names())
112114
else:
113115
if await rule_based_segment_storage.get(rule_based_segment.name) is not None:
114116
to_delete.append(rule_based_segment.name)

tests/engine/test_evaluator.py

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Evaluator tests module."""
22
import logging
33
import pytest
4+
import copy
45

56
from splitio.models.splits import Split, Status
67
from splitio.models.grammar.condition import Condition, ConditionType
@@ -243,7 +244,7 @@ def test_evaluate_treatment_with_rule_based_segment(self, mocker):
243244
ctx = EvaluationContext(flags={'some': mocked_split}, segment_memberships=set(), segment_rbs_memberships={'sample_rule_based_segment': True}, segment_rbs_conditions={'sample_rule_based_segment': []})
244245
result = e.eval_with_context('[email protected]', '[email protected]', 'some', {'email': '[email protected]'}, ctx)
245246
assert result['treatment'] == 'off'
246-
247+
247248
class EvaluationDataFactoryTests(object):
248249
"""Test evaluation factory class."""
249250

@@ -254,37 +255,75 @@ def test_get_context(self):
254255
segment_storage = InMemorySegmentStorage()
255256
rbs_segment_storage = InMemoryRuleBasedSegmentStorage()
256257
flag_storage.update([mocked_split], [], -1)
257-
rbs = rule_based_segments.from_raw(rbs_raw)
258+
rbs = copy.deepcopy(rbs_raw)
259+
rbs['conditions'].append(
260+
{"matcherGroup": {
261+
"combiner": "AND",
262+
"matchers": [
263+
{
264+
"matcherType": "IN_SEGMENT",
265+
"negate": False,
266+
"userDefinedSegmentMatcherData": {
267+
"segmentName": "employees"
268+
},
269+
"whitelistMatcherData": None
270+
}
271+
]
272+
},
273+
})
274+
rbs = rule_based_segments.from_raw(rbs)
258275
rbs_segment_storage.update([rbs], [], -1)
259276

260277
eval_factory = EvaluationDataFactory(flag_storage, segment_storage, rbs_segment_storage)
261278
ec = eval_factory.context_for('[email protected]', ['some'])
262279
assert ec.segment_rbs_conditions == {'sample_rule_based_segment': rbs.conditions}
263280
assert ec.segment_rbs_memberships == {'sample_rule_based_segment': False}
281+
assert ec.segment_memberships == {"employees": False}
264282

283+
segment_storage.update("employees", {"[email protected]"}, {}, 1234)
265284
ec = eval_factory.context_for('[email protected]', ['some'])
266285
assert ec.segment_rbs_conditions == {}
267286
assert ec.segment_rbs_memberships == {'sample_rule_based_segment': True}
268-
287+
assert ec.segment_memberships == {"employees": True}
288+
269289
class EvaluationDataFactoryAsyncTests(object):
270290
"""Test evaluation factory class."""
271291

272292
@pytest.mark.asyncio
273293
async def test_get_context(self):
274294
"""Test context."""
275-
mocked_split = Split('some', 12345, False, 'off', 'user', Status.ACTIVE, 12, split_conditions, 1.2, 100, 1234, {}, None, False)
295+
mocked_split = Split('some', 123, False, 'off', 'user', Status.ACTIVE, 12, split_conditions, 1.2, 100, 1234, {}, None, False)
276296
flag_storage = InMemorySplitStorageAsync([])
277297
segment_storage = InMemorySegmentStorageAsync()
278298
rbs_segment_storage = InMemoryRuleBasedSegmentStorageAsync()
279299
await flag_storage.update([mocked_split], [], -1)
280-
rbs = rule_based_segments.from_raw(rbs_raw)
300+
rbs = copy.deepcopy(rbs_raw)
301+
rbs['conditions'].append(
302+
{"matcherGroup": {
303+
"combiner": "AND",
304+
"matchers": [
305+
{
306+
"matcherType": "IN_SEGMENT",
307+
"negate": False,
308+
"userDefinedSegmentMatcherData": {
309+
"segmentName": "employees"
310+
},
311+
"whitelistMatcherData": None
312+
}
313+
]
314+
},
315+
})
316+
rbs = rule_based_segments.from_raw(rbs)
281317
await rbs_segment_storage.update([rbs], [], -1)
282318

283319
eval_factory = AsyncEvaluationDataFactory(flag_storage, segment_storage, rbs_segment_storage)
284320
ec = await eval_factory.context_for('[email protected]', ['some'])
285321
assert ec.segment_rbs_conditions == {'sample_rule_based_segment': rbs.conditions}
286322
assert ec.segment_rbs_memberships == {'sample_rule_based_segment': False}
323+
assert ec.segment_memberships == {"employees": False}
287324

325+
await segment_storage.update("employees", {"[email protected]"}, {}, 1234)
288326
ec = await eval_factory.context_for('[email protected]', ['some'])
289327
assert ec.segment_rbs_conditions == {}
290328
assert ec.segment_rbs_memberships == {'sample_rule_based_segment': True}
329+
assert ec.segment_memberships == {"employees": True}

tests/models/test_rule_based_segments.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Split model tests module."""
22
import copy
3-
3+
import pytest
44
from splitio.models import rule_based_segments
55
from splitio.models import splits
66
from splitio.models.grammar.condition import Condition
@@ -79,4 +79,26 @@ def test_incorrect_matcher(self):
7979
rbs['conditions'].append(rbs['conditions'][0])
8080
rbs['conditions'][0]['matcherGroup']['matchers'][0]['matcherType'] = 'INVALID_MATCHER'
8181
parsed = rule_based_segments.from_raw(rbs)
82-
assert parsed.conditions[0].to_json() == splits._DEFAULT_CONDITIONS_TEMPLATE
82+
assert parsed.conditions[0].to_json() == splits._DEFAULT_CONDITIONS_TEMPLATE
83+
84+
def test_get_condition_segment_names(self):
85+
rbs = copy.deepcopy(self.raw)
86+
rbs['conditions'].append(
87+
{"matcherGroup": {
88+
"combiner": "AND",
89+
"matchers": [
90+
{
91+
"matcherType": "IN_SEGMENT",
92+
"negate": False,
93+
"userDefinedSegmentMatcherData": {
94+
"segmentName": "employees"
95+
},
96+
"whitelistMatcherData": None
97+
}
98+
]
99+
},
100+
})
101+
rbs = rule_based_segments.from_raw(rbs)
102+
103+
assert rbs.get_condition_segment_names() == {"employees"}
104+

tests/storage/test_pluggable.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1386,11 +1386,11 @@ def test_get(self):
13861386
for sprefix in [None, 'myprefix']:
13871387
pluggable_rbs_storage = PluggableRuleBasedSegmentsStorage(self.mock_adapter, prefix=sprefix)
13881388

1389-
rbs1 = rule_based_segments.from_raw(rbsegments_json[0]['segment1'])
1390-
rbs_name = rbsegments_json[0]['segment1']['name']
1389+
rbs1 = rule_based_segments.from_raw(rbsegments_json[0])
1390+
rbs_name = rbsegments_json[0]['name']
13911391

13921392
self.mock_adapter.set(pluggable_rbs_storage._prefix.format(segment_name=rbs_name), rbs1.to_json())
1393-
assert(pluggable_rbs_storage.get(rbs_name).to_json() == rule_based_segments.from_raw(rbsegments_json[0]['segment1']).to_json())
1393+
assert(pluggable_rbs_storage.get(rbs_name).to_json() == rule_based_segments.from_raw(rbsegments_json[0]).to_json())
13941394
assert(pluggable_rbs_storage.get('not_existing') == None)
13951395

13961396
def test_get_change_number(self):
@@ -1408,8 +1408,8 @@ def test_get_segment_names(self):
14081408
self.mock_adapter._keys = {}
14091409
for sprefix in [None, 'myprefix']:
14101410
pluggable_rbs_storage = PluggableRuleBasedSegmentsStorage(self.mock_adapter, prefix=sprefix)
1411-
rbs1 = rule_based_segments.from_raw(rbsegments_json[0]['segment1'])
1412-
rbs2_temp = copy.deepcopy(rbsegments_json[0]['segment1'])
1411+
rbs1 = rule_based_segments.from_raw(rbsegments_json[0])
1412+
rbs2_temp = copy.deepcopy(rbsegments_json[0])
14131413
rbs2_temp['name'] = 'another_segment'
14141414
rbs2 = rule_based_segments.from_raw(rbs2_temp)
14151415
self.mock_adapter.set(pluggable_rbs_storage._prefix.format(segment_name=rbs1.name), rbs1.to_json())
@@ -1420,8 +1420,8 @@ def test_contains(self):
14201420
self.mock_adapter._keys = {}
14211421
for sprefix in [None, 'myprefix']:
14221422
pluggable_rbs_storage = PluggableRuleBasedSegmentsStorage(self.mock_adapter, prefix=sprefix)
1423-
rbs1 = rule_based_segments.from_raw(rbsegments_json[0]['segment1'])
1424-
rbs2_temp = copy.deepcopy(rbsegments_json[0]['segment1'])
1423+
rbs1 = rule_based_segments.from_raw(rbsegments_json[0])
1424+
rbs2_temp = copy.deepcopy(rbsegments_json[0])
14251425
rbs2_temp['name'] = 'another_segment'
14261426
rbs2 = rule_based_segments.from_raw(rbs2_temp)
14271427
self.mock_adapter.set(pluggable_rbs_storage._prefix.format(segment_name=rbs1.name), rbs1.to_json())
@@ -1445,12 +1445,12 @@ async def test_get(self):
14451445
for sprefix in [None, 'myprefix']:
14461446
pluggable_rbs_storage = PluggableRuleBasedSegmentsStorageAsync(self.mock_adapter, prefix=sprefix)
14471447

1448-
rbs1 = rule_based_segments.from_raw(rbsegments_json[0]['segment1'])
1449-
rbs_name = rbsegments_json[0]['segment1']['name']
1448+
rbs1 = rule_based_segments.from_raw(rbsegments_json[0])
1449+
rbs_name = rbsegments_json[0]['name']
14501450

14511451
await self.mock_adapter.set(pluggable_rbs_storage._prefix.format(segment_name=rbs_name), rbs1.to_json())
14521452
rbs = await pluggable_rbs_storage.get(rbs_name)
1453-
assert(rbs.to_json() == rule_based_segments.from_raw(rbsegments_json[0]['segment1']).to_json())
1453+
assert(rbs.to_json() == rule_based_segments.from_raw(rbsegments_json[0]).to_json())
14541454
assert(await pluggable_rbs_storage.get('not_existing') == None)
14551455

14561456
@pytest.mark.asyncio
@@ -1470,8 +1470,8 @@ async def test_get_segment_names(self):
14701470
self.mock_adapter._keys = {}
14711471
for sprefix in [None, 'myprefix']:
14721472
pluggable_rbs_storage = PluggableRuleBasedSegmentsStorageAsync(self.mock_adapter, prefix=sprefix)
1473-
rbs1 = rule_based_segments.from_raw(rbsegments_json[0]['segment1'])
1474-
rbs2_temp = copy.deepcopy(rbsegments_json[0]['segment1'])
1473+
rbs1 = rule_based_segments.from_raw(rbsegments_json[0])
1474+
rbs2_temp = copy.deepcopy(rbsegments_json[0])
14751475
rbs2_temp['name'] = 'another_segment'
14761476
rbs2 = rule_based_segments.from_raw(rbs2_temp)
14771477
await self.mock_adapter.set(pluggable_rbs_storage._prefix.format(segment_name=rbs1.name), rbs1.to_json())
@@ -1483,8 +1483,8 @@ async def test_contains(self):
14831483
self.mock_adapter._keys = {}
14841484
for sprefix in [None, 'myprefix']:
14851485
pluggable_rbs_storage = PluggableRuleBasedSegmentsStorageAsync(self.mock_adapter, prefix=sprefix)
1486-
rbs1 = rule_based_segments.from_raw(rbsegments_json[0]['segment1'])
1487-
rbs2_temp = copy.deepcopy(rbsegments_json[0]['segment1'])
1486+
rbs1 = rule_based_segments.from_raw(rbsegments_json[0])
1487+
rbs2_temp = copy.deepcopy(rbsegments_json[0])
14881488
rbs2_temp['name'] = 'another_segment'
14891489
rbs2 = rule_based_segments.from_raw(rbs2_temp)
14901490
await self.mock_adapter.set(pluggable_rbs_storage._prefix.format(segment_name=rbs1.name), rbs1.to_json())

0 commit comments

Comments
 (0)