Skip to content

Commit 8281dec

Browse files
committed
Added matcher
1 parent c093206 commit 8281dec

File tree

3 files changed

+370
-1
lines changed

3 files changed

+370
-1
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
"""Prerequisites matcher classes."""
2+
3+
class PrerequisitesMatcher(object):
4+
5+
def __init__(self, prerequisites):
6+
"""
7+
Build a PrerequisitesMatcher.
8+
9+
:param prerequisites: prerequisites
10+
:type raw_matcher: List of Prerequisites
11+
"""
12+
self._prerequisites = prerequisites
13+
14+
def match(self, key, attributes=None, context=None):
15+
"""
16+
Evaluate user input against a matcher and return whether the match is successful.
17+
18+
:param key: User key.
19+
:type key: str.
20+
:param attributes: Custom user attributes.
21+
:type attributes: dict.
22+
:param context: Evaluation context
23+
:type context: dict
24+
25+
:returns: Wheter the match is successful.
26+
:rtype: bool
27+
"""
28+
if self._prerequisites == None:
29+
return True
30+
31+
if not isinstance(key, str):
32+
return False
33+
34+
evaluator = context.get('evaluator')
35+
bucketing_key = context.get('bucketing_key')
36+
for prerequisite in self._prerequisites:
37+
result = evaluator.eval_with_context(key, bucketing_key, prerequisite.feature_flag_name, attributes, context['ec'])
38+
if result['treatment'] not in prerequisite.treatments:
39+
return False
40+
41+
return True
Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
{"ff": {
2+
"d": [
3+
{
4+
"trafficTypeName": "user",
5+
"name": "test_prereq",
6+
"prerequisites": [
7+
{ "n": "feature_segment", "ts": ["off", "def_test"] },
8+
{ "n": "rbs_flag", "ts": ["on"] }
9+
],
10+
"trafficAllocation": 100,
11+
"trafficAllocationSeed": 1582960494,
12+
"seed": 1842944006,
13+
"status": "ACTIVE",
14+
"killed": false,
15+
"defaultTreatment": "def_treatment",
16+
"changeNumber": 1582741588594,
17+
"algo": 2,
18+
"configurations": {},
19+
"conditions": [
20+
{
21+
"conditionType": "ROLLOUT",
22+
"matcherGroup": {
23+
"combiner": "AND",
24+
"matchers": [
25+
{
26+
"keySelector": {
27+
"trafficType": "user",
28+
"attribute": null
29+
},
30+
"matcherType": "ALL_KEYS",
31+
"negate": false,
32+
"userDefinedSegmentMatcherData": null,
33+
"whitelistMatcherData": null,
34+
"unaryNumericMatcherData": null,
35+
"betweenMatcherData": null,
36+
"booleanMatcherData": null,
37+
"dependencyMatcherData": null,
38+
"stringMatcherData": null
39+
}
40+
]
41+
},
42+
"partitions": [
43+
{
44+
"treatment": "on",
45+
"size": 100
46+
},
47+
{
48+
"treatment": "off",
49+
"size": 0
50+
}
51+
],
52+
"label": "default rule"
53+
}
54+
]
55+
},
56+
{
57+
"name":"feature_segment",
58+
"trafficTypeId":"u",
59+
"trafficTypeName":"User",
60+
"trafficAllocation": 100,
61+
"trafficAllocationSeed": 1582960494,
62+
"seed":-1177551240,
63+
"status":"ACTIVE",
64+
"killed":false,
65+
"defaultTreatment":"def_test",
66+
"changeNumber": 1582741588594,
67+
"algo": 2,
68+
"configurations": {},
69+
"conditions":[
70+
{
71+
"matcherGroup":{
72+
"combiner":"AND",
73+
"matchers":[
74+
{
75+
"matcherType":"IN_SEGMENT",
76+
"negate":false,
77+
"userDefinedSegmentMatcherData":{
78+
"segmentName":"segment-test"
79+
},
80+
"whitelistMatcherData":null
81+
}
82+
]
83+
},
84+
"partitions":[
85+
{
86+
"treatment":"on",
87+
"size":100
88+
},
89+
{
90+
"treatment":"off",
91+
"size":0
92+
}
93+
],
94+
"label": "default label"
95+
}
96+
]
97+
},
98+
{
99+
"changeNumber": 10,
100+
"trafficTypeName": "user",
101+
"name": "rbs_flag",
102+
"trafficAllocation": 100,
103+
"trafficAllocationSeed": 1828377380,
104+
"seed": -286617921,
105+
"status": "ACTIVE",
106+
"killed": false,
107+
"defaultTreatment": "off",
108+
"algo": 2,
109+
"conditions": [
110+
{
111+
"conditionType": "ROLLOUT",
112+
"matcherGroup": {
113+
"combiner": "AND",
114+
"matchers": [
115+
{
116+
"keySelector": {
117+
"trafficType": "user"
118+
},
119+
"matcherType": "IN_RULE_BASED_SEGMENT",
120+
"negate": false,
121+
"userDefinedSegmentMatcherData": {
122+
"segmentName": "sample_rule_based_segment"
123+
}
124+
}
125+
]
126+
},
127+
"partitions": [
128+
{
129+
"treatment": "on",
130+
"size": 100
131+
},
132+
{
133+
"treatment": "off",
134+
"size": 0
135+
}
136+
],
137+
"label": "in rule based segment sample_rule_based_segment"
138+
},
139+
{
140+
"conditionType": "ROLLOUT",
141+
"matcherGroup": {
142+
"combiner": "AND",
143+
"matchers": [
144+
{
145+
"keySelector": {
146+
"trafficType": "user"
147+
},
148+
"matcherType": "ALL_KEYS",
149+
"negate": false
150+
}
151+
]
152+
},
153+
"partitions": [
154+
{
155+
"treatment": "on",
156+
"size": 0
157+
},
158+
{
159+
"treatment": "off",
160+
"size": 100
161+
}
162+
],
163+
"label": "default rule"
164+
}
165+
],
166+
"configurations": {},
167+
"sets": [],
168+
"impressionsDisabled": false
169+
},
170+
{
171+
"trafficTypeName": "user",
172+
"name": "prereq_chain",
173+
"prerequisites": [
174+
{ "n": "test_prereq", "ts": ["on"] }
175+
],
176+
"trafficAllocation": 100,
177+
"trafficAllocationSeed": -2092979940,
178+
"seed": 105482719,
179+
"status": "ACTIVE",
180+
"killed": false,
181+
"defaultTreatment": "on_default",
182+
"changeNumber": 1585948850109,
183+
"algo": 2,
184+
"configurations": {},
185+
"conditions": [
186+
{
187+
"conditionType": "WHITELIST",
188+
"matcherGroup": {
189+
"combiner": "AND",
190+
"matchers": [
191+
{
192+
"keySelector": null,
193+
"matcherType": "WHITELIST",
194+
"negate": false,
195+
"userDefinedSegmentMatcherData": null,
196+
"whitelistMatcherData": {
197+
"whitelist": [
198+
"bilal@split.io"
199+
]
200+
},
201+
"unaryNumericMatcherData": null,
202+
"betweenMatcherData": null,
203+
"booleanMatcherData": null,
204+
"dependencyMatcherData": null,
205+
"stringMatcherData": null
206+
}
207+
]
208+
},
209+
"partitions": [
210+
{
211+
"treatment": "on_whitelist",
212+
"size": 100
213+
}
214+
],
215+
"label": "whitelisted"
216+
},
217+
{
218+
"conditionType": "ROLLOUT",
219+
"matcherGroup": {
220+
"combiner": "AND",
221+
"matchers": [
222+
{
223+
"keySelector": {
224+
"trafficType": "user",
225+
"attribute": null
226+
},
227+
"matcherType": "ALL_KEYS",
228+
"negate": false,
229+
"userDefinedSegmentMatcherData": null,
230+
"whitelistMatcherData": null,
231+
"unaryNumericMatcherData": null,
232+
"betweenMatcherData": null,
233+
"booleanMatcherData": null,
234+
"dependencyMatcherData": null,
235+
"stringMatcherData": null
236+
}
237+
]
238+
},
239+
"partitions": [
240+
{
241+
"treatment": "on",
242+
"size": 100
243+
},
244+
{
245+
"treatment": "off",
246+
"size": 0
247+
},
248+
{
249+
"treatment": "V1",
250+
"size": 0
251+
}
252+
],
253+
"label": "default rule"
254+
}
255+
]
256+
}
257+
],
258+
"s": -1,
259+
"t": 1585948850109
260+
}, "rbs":{"d": [
261+
{
262+
"changeNumber": 5,
263+
"name": "sample_rule_based_segment",
264+
"status": "ACTIVE",
265+
"trafficTypeName": "user",
266+
"excluded":{
267+
"keys":["mauro@split.io","gaston@split.io"],
268+
"segments":[]
269+
},
270+
"conditions": [
271+
{
272+
"matcherGroup": {
273+
"combiner": "AND",
274+
"matchers": [
275+
{
276+
"keySelector": {
277+
"trafficType": "user",
278+
"attribute": "email"
279+
},
280+
"matcherType": "ENDS_WITH",
281+
"negate": false,
282+
"whitelistMatcherData": {
283+
"whitelist": [
284+
"@split.io"
285+
]
286+
}
287+
}
288+
]
289+
}
290+
}
291+
]
292+
}], "s": -1, "t": 1585948850109}
293+
}

tests/models/grammar/test_matchers.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from datetime import datetime
1212

1313
from splitio.models.grammar import matchers
14+
from splitio.models.grammar.matchers.prerequisites import PrerequisitesMatcher
1415
from splitio.models import splits
1516
from splitio.models import rule_based_segments
1617
from splitio.models.grammar import condition
@@ -1136,4 +1137,38 @@ def test_matcher_behaviour(self, mocker):
11361137
)}
11371138
assert matcher._match(None, context=ec) is False
11381139
assert matcher._match('bilal@split.io', context=ec) is False
1139-
assert matcher._match('bilal@split.io', {'email': 'bilal@split.io'}, context=ec) is True
1140+
assert matcher._match('bilal@split.io', {'email': 'bilal@split.io'}, context=ec) is True
1141+
1142+
class PrerequisitesMatcherTests(MatcherTestsBase):
1143+
"""tests for prerequisites matcher."""
1144+
1145+
def test_init(self, mocker):
1146+
"""Test init."""
1147+
split_load = os.path.join(os.path.dirname(__file__), 'files', 'splits_prereq.json')
1148+
with open(split_load, 'r') as flo:
1149+
data = json.loads(flo.read())
1150+
1151+
prereq = splits.from_raw_prerequisites(data['ff']['d'][0]['prerequisites'])
1152+
parsed = PrerequisitesMatcher(prereq)
1153+
assert parsed._prerequisites == prereq
1154+
1155+
def test_matcher_behaviour(self, mocker):
1156+
"""Test if the matcher works properly."""
1157+
split_load = os.path.join(os.path.dirname(__file__), 'files', 'splits_prereq.json')
1158+
with open(split_load, 'r') as flo:
1159+
data = json.loads(flo.read())
1160+
prereq = splits.from_raw_prerequisites(data['ff']['d'][3]['prerequisites'])
1161+
parsed = PrerequisitesMatcher(prereq)
1162+
evaluator = mocker.Mock(spec=Evaluator)
1163+
1164+
1165+
evaluator.eval_with_context.return_value = {'treatment': 'on'}
1166+
assert parsed.match('SPLIT_2', {}, {'evaluator': evaluator, 'ec': [{'flags': ['prereq_chain'], 'segment_memberships': {}}]}) is True
1167+
1168+
evaluator.eval_with_context.return_value = {'treatment': 'off'}
1169+
assert parsed.match('SPLIT_2', {}, {'evaluator': evaluator, 'ec': [{'flags': ['prereq_chain'], 'segment_memberships': {}}]}) is False
1170+
1171+
assert parsed.match([], {}, {'evaluator': evaluator, 'ec': [{'flags': ['prereq_chain'], 'segment_memberships': {}}]}) is False
1172+
assert parsed.match({}, {}, {'evaluator': evaluator, 'ec': [{'flags': ['prereq_chain'], 'segment_memberships': {}}]}) is False
1173+
assert parsed.match(123, {}, {'evaluator': evaluator, 'ec': [{'flags': ['prereq_chain'], 'segment_memberships': {}}]}) is False
1174+
assert parsed.match(object(), {}, {'evaluator': evaluator, 'ec': [{'flags': ['prereq_chain'], 'segment_memberships': {}}]}) is False

0 commit comments

Comments
 (0)