Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions splitio/models/grammar/condition.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,12 @@ def from_raw(raw_condition):
:return: A condition object.
:rtype: Condition
"""
parsed_partitions = [
partitions.from_raw(raw_partition)
for raw_partition in raw_condition['partitions']
]
parsed_partitions = []
if raw_condition.get("partitions") is not None:
parsed_partitions = [
partitions.from_raw(raw_partition)
for raw_partition in raw_condition['partitions']
]

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

Expand Down
5 changes: 4 additions & 1 deletion splitio/models/grammar/matchers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from splitio.models.grammar.matchers.misc import BooleanMatcher, DependencyMatcher
from splitio.models.grammar.matchers.semver import EqualToSemverMatcher, GreaterThanOrEqualToSemverMatcher, LessThanOrEqualToSemverMatcher, \
BetweenSemverMatcher, InListSemverMatcher
from splitio.models.grammar.matchers.rule_based_segment import RuleBasedSegmentMatcher


MATCHER_TYPE_ALL_KEYS = 'ALL_KEYS'
Expand All @@ -34,6 +35,7 @@
MATCHER_LESS_THAN_OR_EQUAL_TO_SEMVER = 'LESS_THAN_OR_EQUAL_TO_SEMVER'
MATCHER_BETWEEN_SEMVER = 'BETWEEN_SEMVER'
MATCHER_INLIST_SEMVER = 'IN_LIST_SEMVER'
MATCHER_IN_RULE_BASED_SEGMENT = 'IN_RULE_BASED_SEGMENT'


_MATCHER_BUILDERS = {
Expand All @@ -58,7 +60,8 @@
MATCHER_GREATER_THAN_OR_EQUAL_TO_SEMVER: GreaterThanOrEqualToSemverMatcher,
MATCHER_LESS_THAN_OR_EQUAL_TO_SEMVER: LessThanOrEqualToSemverMatcher,
MATCHER_BETWEEN_SEMVER: BetweenSemverMatcher,
MATCHER_INLIST_SEMVER: InListSemverMatcher
MATCHER_INLIST_SEMVER: InListSemverMatcher,
MATCHER_IN_RULE_BASED_SEGMENT: RuleBasedSegmentMatcher
}

def from_raw(raw_matcher):
Expand Down
48 changes: 48 additions & 0 deletions splitio/models/grammar/matchers/rule_based_segment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""Rule based segment matcher classes."""
from splitio.models.grammar.matchers.base import Matcher

class RuleBasedSegmentMatcher(Matcher):

def _build(self, raw_matcher):
"""
Build an RuleBasedSegmentMatcher.

:param raw_matcher: raw matcher as fetched from splitChanges response.
:type raw_matcher: dict
"""
self._rbs_segment_name = raw_matcher['userDefinedSegmentMatcherData']['segmentName']

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._rbs_segment_name == None:
return False

# Check if rbs segment has exclusions
if context['ec'].segment_rbs_memberships.get(self._rbs_segment_name):
return False

for parsed_condition in context['ec'].segment_rbs_conditions.get(self._rbs_segment_name):
if parsed_condition.matches(key, attributes, context):
return True

return False

def _add_matcher_specific_properties_to_json(self):
"""Return UserDefinedSegment specific properties."""
return {
'userDefinedSegmentMatcherData': {
'segmentName': self._rbs_segment_name
}
}
113 changes: 113 additions & 0 deletions splitio/models/rule_based_segments.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
"""RuleBasedSegment module."""

import logging

from splitio.models import MatcherNotFoundException
from splitio.models.splits import _DEFAULT_CONDITIONS_TEMPLATE
from splitio.models.grammar import condition

_LOGGER = logging.getLogger(__name__)

class RuleBasedSegment(object):
"""RuleBasedSegment object class."""

def __init__(self, name, traffic_yype_Name, change_number, status, conditions, excluded):
"""
Class constructor.

:param name: Segment name.
:type name: str
:param traffic_yype_Name: traffic type name.
:type traffic_yype_Name: str
:param change_number: change number.
:type change_number: str
:param status: status.
:type status: str
:param conditions: List of conditions belonging to the segment.
:type conditions: List
:param excluded: excluded objects.
:type excluded: Excluded
"""
self._name = name
self._traffic_yype_Name = traffic_yype_Name
self._change_number = change_number
self._status = status
self._conditions = conditions
self._excluded = excluded

@property
def name(self):
"""Return segment name."""
return self._name

@property
def traffic_yype_Name(self):
"""Return traffic type name."""
return self._traffic_yype_Name

@property
def change_number(self):
"""Return change number."""
return self._change_number

@property
def status(self):
"""Return status."""
return self._status

@property
def conditions(self):
"""Return conditions."""
return self._conditions

@property
def excluded(self):
"""Return excluded."""
return self._excluded

def from_raw(raw_rule_based_segment):
"""
Parse a Rule based segment from a JSON portion of splitChanges.

:param raw_rule_based_segment: JSON object extracted from a splitChange's response
:type raw_rule_based_segment: dict

:return: A parsed RuleBasedSegment object capable of performing evaluations.
:rtype: RuleBasedSegment
"""
try:
conditions = [condition.from_raw(c) for c in raw_rule_based_segment['conditions']]
except MatcherNotFoundException as e:
_LOGGER.error(str(e))
_LOGGER.debug("Using default conditions template for feature flag: %s", raw_rule_based_segment['name'])
conditions = [condition.from_raw(_DEFAULT_CONDITIONS_TEMPLATE)]
return RuleBasedSegment(
raw_rule_based_segment['name'],
raw_rule_based_segment['trafficTypeName'],
raw_rule_based_segment['changeNumber'],
raw_rule_based_segment['status'],
conditions,
Excluded(raw_rule_based_segment['excluded']['keys'], raw_rule_based_segment['excluded']['segments'])
)

class Excluded(object):

def __init__(self, keys, segments):
"""
Class constructor.

:param keys: List of excluded keys in a rule based segment.
:type keys: List
:param segments: List of excluded segments in a rule based segment.
:type segments: List
"""
self._keys = keys
self._segments = segments

def get_excluded_keys(self):
"""Return excluded keys."""
return self._keys

def get_excluded_segments(self):
"""Return excluded segments"""
return self._segments
73 changes: 72 additions & 1 deletion splitio/storage/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,4 +354,75 @@ def intersect(self, flag_sets):
if not isinstance(flag_sets, set) or len(flag_sets) == 0:
return False

return any(self.flag_sets.intersection(flag_sets))
return any(self.flag_sets.intersection(flag_sets))

class RuleBasedSegmentsStorage(object, metaclass=abc.ABCMeta):
"""SplitRule based segment storage interface implemented as an abstract class."""

@abc.abstractmethod
def get(self, segment_name):
"""
Retrieve a rule based segment.

:param segment_name: Name of the segment to fetch.
:type segment_name: str

:rtype: str
"""
pass

@abc.abstractmethod
def update(self, to_add, to_delete, new_change_number):
"""
Update rule based segment..

:param to_add: List of rule based segment. to add
:type to_add: list[splitio.models.rule_based_segments.RuleBasedSegment]
:param to_delete: List of rule based segment. to delete
:type to_delete: list[splitio.models.rule_based_segments.RuleBasedSegment]
:param new_change_number: New change number.
:type new_change_number: int
"""
pass

@abc.abstractmethod
def get_change_number(self):
"""
Retrieve latest rule based segment change number.

:rtype: int
"""
pass

@abc.abstractmethod
def contains(self, segment_names):
"""
Return whether the segments exists in rule based segment in cache.

:param segment_names: segment name to validate.
:type segment_names: str

:return: True if segment names exists. False otherwise.
:rtype: bool
"""
pass

@abc.abstractmethod
def get_segment_names(self):
"""
Retrieve a list of all excluded segments names.

:return: List of segment names.
:rtype: list(str)
"""
pass

@abc.abstractmethod
def get_large_segment_names(self):
"""
Retrieve a list of all excluded large segments names.

:return: List of segment names.
:rtype: list(str)
"""
pass
Loading