Skip to content

Commit 79bddcd

Browse files
authored
Merge pull request #513 from splitio/semver-inlist-matcher
added in list semver matcher
2 parents a8a7c24 + e80b3eb commit 79bddcd

File tree

3 files changed

+109
-7
lines changed

3 files changed

+109
-7
lines changed

splitio/models/grammar/matchers/__init__.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
from splitio.models.grammar.matchers.string import ContainsStringMatcher, \
99
EndsWithMatcher, RegexMatcher, StartsWithMatcher, WhitelistMatcher
1010
from splitio.models.grammar.matchers.misc import BooleanMatcher, DependencyMatcher
11-
from splitio.models.grammar.matchers.semver import EqualToSemverMatcher, GreaterThanOrEqualToSemverMatcher, LessThanOrEqualToSemverMatcher, BetweenSemverMatcher
11+
from splitio.models.grammar.matchers.semver import EqualToSemverMatcher, GreaterThanOrEqualToSemverMatcher, LessThanOrEqualToSemverMatcher, \
12+
BetweenSemverMatcher, InListSemverMatcher
1213

1314

1415
MATCHER_TYPE_ALL_KEYS = 'ALL_KEYS'
@@ -32,6 +33,8 @@
3233
MATCHER_GREATER_THAN_OR_EQUAL_TO_SEMVER = 'GREATER_THAN_OR_EQUAL_TO_SEMVER'
3334
MATCHER_LESS_THAN_OR_EQUAL_TO_SEMVER = 'LESS_THAN_OR_EQUAL_TO_SEMVER'
3435
MATCHER_BETWEEN_SEMVER = 'BETWEEN_SEMVER'
36+
MATCHER_INLIST_SEMVER = 'IN_LIST_SEMVER'
37+
3538

3639
_MATCHER_BUILDERS = {
3740
MATCHER_TYPE_ALL_KEYS: AllKeysMatcher,
@@ -54,7 +57,8 @@
5457
MATCHER_TYPE_EQUAL_TO_SEMVER: EqualToSemverMatcher,
5558
MATCHER_GREATER_THAN_OR_EQUAL_TO_SEMVER: GreaterThanOrEqualToSemverMatcher,
5659
MATCHER_LESS_THAN_OR_EQUAL_TO_SEMVER: LessThanOrEqualToSemverMatcher,
57-
MATCHER_BETWEEN_SEMVER: BetweenSemverMatcher
60+
MATCHER_BETWEEN_SEMVER: BetweenSemverMatcher,
61+
MATCHER_INLIST_SEMVER: InListSemverMatcher
5862
}
5963

6064
def from_raw(raw_matcher):

splitio/models/grammar/matchers/semver.py

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,7 @@ def _parse(self, version):
4343
"""
4444
Parse the string in self.version to update the other internal variables
4545
"""
46-
without_metadata = self.remove_metadata_if_exists(version)
47-
46+
without_metadata = self._remove_metadata_if_exists(version)
4847
index = without_metadata.find(self._PRE_RELEASE_DELIMITER)
4948
if index == -1:
5049
self._is_stable = True
@@ -56,9 +55,9 @@ def _parse(self, version):
5655
without_metadata = without_metadata[:index]
5756
self._pre_release = pre_release_data.split(self._VALUE_DELIMITER)
5857

59-
self.set_major_minor_and_patch(without_metadata)
58+
self._set_major_minor_and_patch(without_metadata)
6059

61-
def remove_metadata_if_exists(self, version):
60+
def _remove_metadata_if_exists(self, version):
6261
"""
6362
Check if there is any metadata characters in self.version.
6463
@@ -75,7 +74,7 @@ def remove_metadata_if_exists(self, version):
7574

7675
return version[:index]
7776

78-
def set_major_minor_and_patch(self, version):
77+
def _set_major_minor_and_patch(self, version):
7978
"""
8079
Set the major, minor and patch internal variables based on string passed.
8180
@@ -362,3 +361,55 @@ def __str__(self):
362361
def _add_matcher_specific_properties_to_json(self):
363362
"""Add matcher specific properties to base dict before returning it."""
364363
return {'matcherType': 'BETWEEN_SEMVER', 'betweenStringMatcherData': self._data}
364+
365+
class InListSemverMatcher(Matcher):
366+
"""A matcher for Semver in list."""
367+
368+
def _build(self, raw_matcher):
369+
"""
370+
Build a InListSemverMatcher.
371+
372+
:param raw_matcher: raw matcher as fetched from splitChanges response.
373+
:type raw_matcher: dict
374+
"""
375+
self._data = raw_matcher.get('whitelistMatcherData')
376+
if self._data is not None:
377+
self._data = self._data.get('whitelist')
378+
379+
self._semver_list = [Semver.build(item) if item is not None else None for item in self._data] if self._data is not None else []
380+
381+
def _match(self, key, attributes=None, context=None):
382+
"""
383+
Evaluate user input against a matcher and return whether the match is successful.
384+
385+
:param key: User key.
386+
:type key: str.
387+
:param attributes: Custom user attributes.
388+
:type attributes: dict.
389+
:param context: Evaluation context
390+
:type context: dict
391+
392+
:returns: Wheter the match is successful.
393+
:rtype: bool
394+
"""
395+
if self._data is None:
396+
_LOGGER.error("whitelistMatcherData is required for IN_LIST_SEMVER matcher type")
397+
return False
398+
399+
matching_data = Sanitizer.ensure_string(self._get_matcher_input(key, attributes))
400+
if matching_data is None:
401+
return False
402+
403+
matching_semver = Semver.build(matching_data)
404+
if matching_semver is None:
405+
return False
406+
407+
return any([item.version == matching_semver.version if item is not None else False for item in self._semver_list])
408+
409+
def __str__(self):
410+
"""Return string Representation."""
411+
return 'in list semver {data}'.format(data=self._data)
412+
413+
def _add_matcher_specific_properties_to_json(self):
414+
"""Add matcher specific properties to base dict before returning it."""
415+
return {'matcherType': 'IN_LIST_SEMVER', 'whitelistMatcherData': {'whitelist': self._data}}

tests/models/grammar/test_matchers.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1058,3 +1058,50 @@ def test_to_str(self):
10581058
"""Test that the object serializes to str properly."""
10591059
as_str = matchers.BetweenSemverMatcher(self.raw)
10601060
assert str(as_str) == "between semver 2.1.8 and 2.1.11"
1061+
1062+
class InListSemverMatcherTests(MatcherTestsBase):
1063+
"""Semver inlist matcher test cases."""
1064+
1065+
raw = {
1066+
'negate': False,
1067+
'matcherType': 'IN_LIST_SEMVER',
1068+
'whitelistMatcherData': {"whitelist": ["2.1.8", "2.1.11"]}
1069+
}
1070+
1071+
def test_from_raw(self, mocker):
1072+
"""Test parsing from raw json/dict."""
1073+
parsed = matchers.from_raw(self.raw)
1074+
assert isinstance(parsed, matchers.InListSemverMatcher)
1075+
assert parsed._data == ["2.1.8", "2.1.11"]
1076+
assert [isinstance(item, Semver) for item in parsed._semver_list]
1077+
assert parsed._semver_list[0]._major == 2
1078+
assert parsed._semver_list[0]._minor == 1
1079+
assert parsed._semver_list[0]._patch == 8
1080+
assert parsed._semver_list[0]._pre_release == []
1081+
1082+
assert parsed._semver_list[1]._major == 2
1083+
assert parsed._semver_list[1]._minor == 1
1084+
assert parsed._semver_list[1]._patch == 11
1085+
assert parsed._semver_list[1]._pre_release == []
1086+
1087+
def test_matcher_behaviour(self, mocker):
1088+
"""Test if the matcher works properly."""
1089+
parsed = matchers.from_raw(self.raw)
1090+
assert not parsed._match("2.1.8+rc")
1091+
assert parsed._match("2.1.8")
1092+
assert not parsed._match("2.1.11-rc12")
1093+
assert parsed._match("2.1.11")
1094+
assert not parsed._match("2.1.7")
1095+
assert not parsed._match(None)
1096+
assert not parsed._match("semver")
1097+
1098+
def test_to_json(self):
1099+
"""Test that the object serializes to JSON properly."""
1100+
as_json = matchers.InListSemverMatcher(self.raw).to_json()
1101+
assert as_json['matcherType'] == 'IN_LIST_SEMVER'
1102+
assert as_json['whitelistMatcherData'] == {"whitelist": ["2.1.8", "2.1.11"]}
1103+
1104+
def test_to_str(self):
1105+
"""Test that the object serializes to str properly."""
1106+
as_str = matchers.InListSemverMatcher(self.raw)
1107+
assert str(as_str) == "in list semver ['2.1.8', '2.1.11']"

0 commit comments

Comments
 (0)