Skip to content
38 changes: 38 additions & 0 deletions splitio/models/grammar/matchers/prerequisites.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""Prerequisites matcher classes."""

class PrerequisitesMatcher(object):

def __init__(self, prerequisites):
"""
Build a PrerequisitesMatcher.

:param prerequisites: prerequisites
:type raw_matcher: List of Prerequisites
"""
self._prerequisites = prerequisites

def match(self, key, attributes=None, context=None):
"""
Evaluate user input against a matcher and return whether the match is successful.

:param key: User key.
:type key: str.
:param attributes: Custom user attributes.
:type attributes: dict.
:param context: Evaluation context
:type context: dict

:returns: Wheter the match is successful.
:rtype: bool
"""
if self._prerequisites == None:
return True

evaluator = context.get('evaluator')
bucketing_key = context.get('bucketing_key')
for prerequisite in self._prerequisites:
result = evaluator.eval_with_context(key, bucketing_key, prerequisite.feature_flag_name, attributes, context['ec'])
if result['treatment'] not in prerequisite.treatments:
return False

return True
293 changes: 293 additions & 0 deletions tests/models/grammar/files/splits_prereq.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
{"ff": {
"d": [
{
"trafficTypeName": "user",
"name": "test_prereq",
"prerequisites": [
{ "n": "feature_segment", "ts": ["off", "def_test"] },
{ "n": "rbs_flag", "ts": ["on"] }
],
"trafficAllocation": 100,
"trafficAllocationSeed": 1582960494,
"seed": 1842944006,
"status": "ACTIVE",
"killed": false,
"defaultTreatment": "def_treatment",
"changeNumber": 1582741588594,
"algo": 2,
"configurations": {},
"conditions": [
{
"conditionType": "ROLLOUT",
"matcherGroup": {
"combiner": "AND",
"matchers": [
{
"keySelector": {
"trafficType": "user",
"attribute": null
},
"matcherType": "ALL_KEYS",
"negate": false,
"userDefinedSegmentMatcherData": null,
"whitelistMatcherData": null,
"unaryNumericMatcherData": null,
"betweenMatcherData": null,
"booleanMatcherData": null,
"dependencyMatcherData": null,
"stringMatcherData": null
}
]
},
"partitions": [
{
"treatment": "on",
"size": 100
},
{
"treatment": "off",
"size": 0
}
],
"label": "default rule"
}
]
},
{
"name":"feature_segment",
"trafficTypeId":"u",
"trafficTypeName":"User",
"trafficAllocation": 100,
"trafficAllocationSeed": 1582960494,
"seed":-1177551240,
"status":"ACTIVE",
"killed":false,
"defaultTreatment":"def_test",
"changeNumber": 1582741588594,
"algo": 2,
"configurations": {},
"conditions":[
{
"matcherGroup":{
"combiner":"AND",
"matchers":[
{
"matcherType":"IN_SEGMENT",
"negate":false,
"userDefinedSegmentMatcherData":{
"segmentName":"segment-test"
},
"whitelistMatcherData":null
}
]
},
"partitions":[
{
"treatment":"on",
"size":100
},
{
"treatment":"off",
"size":0
}
],
"label": "default label"
}
]
},
{
"changeNumber": 10,
"trafficTypeName": "user",
"name": "rbs_flag",
"trafficAllocation": 100,
"trafficAllocationSeed": 1828377380,
"seed": -286617921,
"status": "ACTIVE",
"killed": false,
"defaultTreatment": "off",
"algo": 2,
"conditions": [
{
"conditionType": "ROLLOUT",
"matcherGroup": {
"combiner": "AND",
"matchers": [
{
"keySelector": {
"trafficType": "user"
},
"matcherType": "IN_RULE_BASED_SEGMENT",
"negate": false,
"userDefinedSegmentMatcherData": {
"segmentName": "sample_rule_based_segment"
}
}
]
},
"partitions": [
{
"treatment": "on",
"size": 100
},
{
"treatment": "off",
"size": 0
}
],
"label": "in rule based segment sample_rule_based_segment"
},
{
"conditionType": "ROLLOUT",
"matcherGroup": {
"combiner": "AND",
"matchers": [
{
"keySelector": {
"trafficType": "user"
},
"matcherType": "ALL_KEYS",
"negate": false
}
]
},
"partitions": [
{
"treatment": "on",
"size": 0
},
{
"treatment": "off",
"size": 100
}
],
"label": "default rule"
}
],
"configurations": {},
"sets": [],
"impressionsDisabled": false
},
{
"trafficTypeName": "user",
"name": "prereq_chain",
"prerequisites": [
{ "n": "test_prereq", "ts": ["on"] }
],
"trafficAllocation": 100,
"trafficAllocationSeed": -2092979940,
"seed": 105482719,
"status": "ACTIVE",
"killed": false,
"defaultTreatment": "on_default",
"changeNumber": 1585948850109,
"algo": 2,
"configurations": {},
"conditions": [
{
"conditionType": "WHITELIST",
"matcherGroup": {
"combiner": "AND",
"matchers": [
{
"keySelector": null,
"matcherType": "WHITELIST",
"negate": false,
"userDefinedSegmentMatcherData": null,
"whitelistMatcherData": {
"whitelist": [
"bilal@split.io"
]
},
"unaryNumericMatcherData": null,
"betweenMatcherData": null,
"booleanMatcherData": null,
"dependencyMatcherData": null,
"stringMatcherData": null
}
]
},
"partitions": [
{
"treatment": "on_whitelist",
"size": 100
}
],
"label": "whitelisted"
},
{
"conditionType": "ROLLOUT",
"matcherGroup": {
"combiner": "AND",
"matchers": [
{
"keySelector": {
"trafficType": "user",
"attribute": null
},
"matcherType": "ALL_KEYS",
"negate": false,
"userDefinedSegmentMatcherData": null,
"whitelistMatcherData": null,
"unaryNumericMatcherData": null,
"betweenMatcherData": null,
"booleanMatcherData": null,
"dependencyMatcherData": null,
"stringMatcherData": null
}
]
},
"partitions": [
{
"treatment": "on",
"size": 100
},
{
"treatment": "off",
"size": 0
},
{
"treatment": "V1",
"size": 0
}
],
"label": "default rule"
}
]
}
],
"s": -1,
"t": 1585948850109
}, "rbs":{"d": [
{
"changeNumber": 5,
"name": "sample_rule_based_segment",
"status": "ACTIVE",
"trafficTypeName": "user",
"excluded":{
"keys":["mauro@split.io","gaston@split.io"],
"segments":[]
},
"conditions": [
{
"matcherGroup": {
"combiner": "AND",
"matchers": [
{
"keySelector": {
"trafficType": "user",
"attribute": "email"
},
"matcherType": "ENDS_WITH",
"negate": false,
"whitelistMatcherData": {
"whitelist": [
"@split.io"
]
}
}
]
}
}
]
}], "s": -1, "t": 1585948850109}
}
32 changes: 31 additions & 1 deletion tests/models/grammar/test_matchers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from datetime import datetime

from splitio.models.grammar import matchers
from splitio.models.grammar.matchers.prerequisites import PrerequisitesMatcher
from splitio.models import splits
from splitio.models import rule_based_segments
from splitio.models.grammar import condition
Expand Down Expand Up @@ -1136,4 +1137,33 @@ def test_matcher_behaviour(self, mocker):
)}
assert matcher._match(None, context=ec) is False
assert matcher._match('bilal@split.io', context=ec) is False
assert matcher._match('bilal@split.io', {'email': 'bilal@split.io'}, context=ec) is True
assert matcher._match('bilal@split.io', {'email': 'bilal@split.io'}, context=ec) is True

class PrerequisitesMatcherTests(MatcherTestsBase):
"""tests for prerequisites matcher."""

def test_init(self, mocker):
"""Test init."""
split_load = os.path.join(os.path.dirname(__file__), 'files', 'splits_prereq.json')
with open(split_load, 'r') as flo:
data = json.loads(flo.read())

prereq = splits.from_raw_prerequisites(data['ff']['d'][0]['prerequisites'])
parsed = PrerequisitesMatcher(prereq)
assert parsed._prerequisites == prereq

def test_matcher_behaviour(self, mocker):
"""Test if the matcher works properly."""
split_load = os.path.join(os.path.dirname(__file__), 'files', 'splits_prereq.json')
with open(split_load, 'r') as flo:
data = json.loads(flo.read())
prereq = splits.from_raw_prerequisites(data['ff']['d'][3]['prerequisites'])
parsed = PrerequisitesMatcher(prereq)
evaluator = mocker.Mock(spec=Evaluator)


evaluator.eval_with_context.return_value = {'treatment': 'on'}
assert parsed.match('SPLIT_2', {}, {'evaluator': evaluator, 'ec': [{'flags': ['prereq_chain'], 'segment_memberships': {}}]}) is True

evaluator.eval_with_context.return_value = {'treatment': 'off'}
assert parsed.match('SPLIT_2', {}, {'evaluator': evaluator, 'ec': [{'flags': ['prereq_chain'], 'segment_memberships': {}}]}) is False