Skip to content

Commit 2c9c47e

Browse files
authored
Merge pull request #557 from splitio/rbs-models-mem-storage
Rbs models mem storage
2 parents fb2723a + c07651e commit 2c9c47e

File tree

10 files changed

+733
-11
lines changed

10 files changed

+733
-11
lines changed

splitio/models/grammar/condition.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,12 @@ def from_raw(raw_condition):
119119
:return: A condition object.
120120
:rtype: Condition
121121
"""
122-
parsed_partitions = [
123-
partitions.from_raw(raw_partition)
124-
for raw_partition in raw_condition['partitions']
125-
]
122+
parsed_partitions = []
123+
if raw_condition.get("partitions") is not None:
124+
parsed_partitions = [
125+
partitions.from_raw(raw_partition)
126+
for raw_partition in raw_condition['partitions']
127+
]
126128

127129
matcher_objects = [matchers.from_raw(x) for x in raw_condition['matcherGroup']['matchers']]
128130

splitio/models/grammar/matchers/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from splitio.models.grammar.matchers.misc import BooleanMatcher, DependencyMatcher
1111
from splitio.models.grammar.matchers.semver import EqualToSemverMatcher, GreaterThanOrEqualToSemverMatcher, LessThanOrEqualToSemverMatcher, \
1212
BetweenSemverMatcher, InListSemverMatcher
13+
from splitio.models.grammar.matchers.rule_based_segment import RuleBasedSegmentMatcher
1314

1415

1516
MATCHER_TYPE_ALL_KEYS = 'ALL_KEYS'
@@ -34,6 +35,7 @@
3435
MATCHER_LESS_THAN_OR_EQUAL_TO_SEMVER = 'LESS_THAN_OR_EQUAL_TO_SEMVER'
3536
MATCHER_BETWEEN_SEMVER = 'BETWEEN_SEMVER'
3637
MATCHER_INLIST_SEMVER = 'IN_LIST_SEMVER'
38+
MATCHER_IN_RULE_BASED_SEGMENT = 'IN_RULE_BASED_SEGMENT'
3739

3840

3941
_MATCHER_BUILDERS = {
@@ -58,7 +60,8 @@
5860
MATCHER_GREATER_THAN_OR_EQUAL_TO_SEMVER: GreaterThanOrEqualToSemverMatcher,
5961
MATCHER_LESS_THAN_OR_EQUAL_TO_SEMVER: LessThanOrEqualToSemverMatcher,
6062
MATCHER_BETWEEN_SEMVER: BetweenSemverMatcher,
61-
MATCHER_INLIST_SEMVER: InListSemverMatcher
63+
MATCHER_INLIST_SEMVER: InListSemverMatcher,
64+
MATCHER_IN_RULE_BASED_SEGMENT: RuleBasedSegmentMatcher
6265
}
6366

6467
def from_raw(raw_matcher):
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
"""Rule based segment matcher classes."""
2+
from splitio.models.grammar.matchers.base import Matcher
3+
4+
class RuleBasedSegmentMatcher(Matcher):
5+
6+
def _build(self, raw_matcher):
7+
"""
8+
Build an RuleBasedSegmentMatcher.
9+
10+
:param raw_matcher: raw matcher as fetched from splitChanges response.
11+
:type raw_matcher: dict
12+
"""
13+
self._rbs_segment_name = raw_matcher['userDefinedSegmentMatcherData']['segmentName']
14+
15+
def _match(self, key, attributes=None, context=None):
16+
"""
17+
Evaluate user input against a matcher and return whether the match is successful.
18+
19+
:param key: User key.
20+
:type key: str.
21+
:param attributes: Custom user attributes.
22+
:type attributes: dict.
23+
:param context: Evaluation context
24+
:type context: dict
25+
26+
:returns: Wheter the match is successful.
27+
:rtype: bool
28+
"""
29+
if self._rbs_segment_name == None:
30+
return False
31+
32+
# Check if rbs segment has exclusions
33+
if context['ec'].segment_rbs_memberships.get(self._rbs_segment_name):
34+
return False
35+
36+
for parsed_condition in context['ec'].segment_rbs_conditions.get(self._rbs_segment_name):
37+
if parsed_condition.matches(key, attributes, context):
38+
return True
39+
40+
return False
41+
42+
def _add_matcher_specific_properties_to_json(self):
43+
"""Return UserDefinedSegment specific properties."""
44+
return {
45+
'userDefinedSegmentMatcherData': {
46+
'segmentName': self._rbs_segment_name
47+
}
48+
}

splitio/models/rule_based_segments.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
"""RuleBasedSegment module."""
2+
3+
import logging
4+
5+
from splitio.models import MatcherNotFoundException
6+
from splitio.models.splits import _DEFAULT_CONDITIONS_TEMPLATE
7+
from splitio.models.grammar import condition
8+
9+
_LOGGER = logging.getLogger(__name__)
10+
11+
class RuleBasedSegment(object):
12+
"""RuleBasedSegment object class."""
13+
14+
def __init__(self, name, traffic_yype_Name, change_number, status, conditions, excluded):
15+
"""
16+
Class constructor.
17+
18+
:param name: Segment name.
19+
:type name: str
20+
:param traffic_yype_Name: traffic type name.
21+
:type traffic_yype_Name: str
22+
:param change_number: change number.
23+
:type change_number: str
24+
:param status: status.
25+
:type status: str
26+
:param conditions: List of conditions belonging to the segment.
27+
:type conditions: List
28+
:param excluded: excluded objects.
29+
:type excluded: Excluded
30+
"""
31+
self._name = name
32+
self._traffic_yype_Name = traffic_yype_Name
33+
self._change_number = change_number
34+
self._status = status
35+
self._conditions = conditions
36+
self._excluded = excluded
37+
38+
@property
39+
def name(self):
40+
"""Return segment name."""
41+
return self._name
42+
43+
@property
44+
def traffic_yype_Name(self):
45+
"""Return traffic type name."""
46+
return self._traffic_yype_Name
47+
48+
@property
49+
def change_number(self):
50+
"""Return change number."""
51+
return self._change_number
52+
53+
@property
54+
def status(self):
55+
"""Return status."""
56+
return self._status
57+
58+
@property
59+
def conditions(self):
60+
"""Return conditions."""
61+
return self._conditions
62+
63+
@property
64+
def excluded(self):
65+
"""Return excluded."""
66+
return self._excluded
67+
68+
def from_raw(raw_rule_based_segment):
69+
"""
70+
Parse a Rule based segment from a JSON portion of splitChanges.
71+
72+
:param raw_rule_based_segment: JSON object extracted from a splitChange's response
73+
:type raw_rule_based_segment: dict
74+
75+
:return: A parsed RuleBasedSegment object capable of performing evaluations.
76+
:rtype: RuleBasedSegment
77+
"""
78+
try:
79+
conditions = [condition.from_raw(c) for c in raw_rule_based_segment['conditions']]
80+
except MatcherNotFoundException as e:
81+
_LOGGER.error(str(e))
82+
_LOGGER.debug("Using default conditions template for feature flag: %s", raw_rule_based_segment['name'])
83+
conditions = [condition.from_raw(_DEFAULT_CONDITIONS_TEMPLATE)]
84+
return RuleBasedSegment(
85+
raw_rule_based_segment['name'],
86+
raw_rule_based_segment['trafficTypeName'],
87+
raw_rule_based_segment['changeNumber'],
88+
raw_rule_based_segment['status'],
89+
conditions,
90+
Excluded(raw_rule_based_segment['excluded']['keys'], raw_rule_based_segment['excluded']['segments'])
91+
)
92+
93+
class Excluded(object):
94+
95+
def __init__(self, keys, segments):
96+
"""
97+
Class constructor.
98+
99+
:param keys: List of excluded keys in a rule based segment.
100+
:type keys: List
101+
:param segments: List of excluded segments in a rule based segment.
102+
:type segments: List
103+
"""
104+
self._keys = keys
105+
self._segments = segments
106+
107+
def get_excluded_keys(self):
108+
"""Return excluded keys."""
109+
return self._keys
110+
111+
def get_excluded_segments(self):
112+
"""Return excluded segments"""
113+
return self._segments

splitio/storage/__init__.py

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,4 +354,75 @@ def intersect(self, flag_sets):
354354
if not isinstance(flag_sets, set) or len(flag_sets) == 0:
355355
return False
356356

357-
return any(self.flag_sets.intersection(flag_sets))
357+
return any(self.flag_sets.intersection(flag_sets))
358+
359+
class RuleBasedSegmentsStorage(object, metaclass=abc.ABCMeta):
360+
"""SplitRule based segment storage interface implemented as an abstract class."""
361+
362+
@abc.abstractmethod
363+
def get(self, segment_name):
364+
"""
365+
Retrieve a rule based segment.
366+
367+
:param segment_name: Name of the segment to fetch.
368+
:type segment_name: str
369+
370+
:rtype: str
371+
"""
372+
pass
373+
374+
@abc.abstractmethod
375+
def update(self, to_add, to_delete, new_change_number):
376+
"""
377+
Update rule based segment..
378+
379+
:param to_add: List of rule based segment. to add
380+
:type to_add: list[splitio.models.rule_based_segments.RuleBasedSegment]
381+
:param to_delete: List of rule based segment. to delete
382+
:type to_delete: list[splitio.models.rule_based_segments.RuleBasedSegment]
383+
:param new_change_number: New change number.
384+
:type new_change_number: int
385+
"""
386+
pass
387+
388+
@abc.abstractmethod
389+
def get_change_number(self):
390+
"""
391+
Retrieve latest rule based segment change number.
392+
393+
:rtype: int
394+
"""
395+
pass
396+
397+
@abc.abstractmethod
398+
def contains(self, segment_names):
399+
"""
400+
Return whether the segments exists in rule based segment in cache.
401+
402+
:param segment_names: segment name to validate.
403+
:type segment_names: str
404+
405+
:return: True if segment names exists. False otherwise.
406+
:rtype: bool
407+
"""
408+
pass
409+
410+
@abc.abstractmethod
411+
def get_segment_names(self):
412+
"""
413+
Retrieve a list of all excluded segments names.
414+
415+
:return: List of segment names.
416+
:rtype: list(str)
417+
"""
418+
pass
419+
420+
@abc.abstractmethod
421+
def get_large_segment_names(self):
422+
"""
423+
Retrieve a list of all excluded large segments names.
424+
425+
:return: List of segment names.
426+
:rtype: list(str)
427+
"""
428+
pass

0 commit comments

Comments
 (0)