Skip to content

Commit fac0e28

Browse files
authored
Merge pull request #523 from splitio/Feature/SemVer
Feature/sem ver
2 parents 14064fc + fd75c06 commit fac0e28

32 files changed

+989
-111
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
- name: Setup Python
3232
uses: actions/setup-python@v3
3333
with:
34-
python-version: '3.6'
34+
python-version: '3.7'
3535

3636
- name: Install dependencies
3737
run: |

CHANGES.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
9.7.0 (May 15, 2024)
2+
- Added support for targeting rules based on semantic versions (https://semver.org/).
3+
- Added the logic to handle correctly when the SDK receives an unsupported Matcher type.
4+
15
9.6.2 (Apr 5, 2024)
26
- Fixed an issue when pushing unique keys tracker data to redis if no keys exist, i.e. get_treatment flavors are not called.
37

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ This SDK is designed to work with Split, the platform for controlled rollouts, w
77
[![Twitter Follow](https://img.shields.io/twitter/follow/splitsoftware.svg?style=social&label=Follow&maxAge=1529000)](https://twitter.com/intent/follow?screen_name=splitsoftware)
88

99
## Compatibility
10-
This SDK is compatible with **Python 3 and higher**.
10+
This SDK is compatible with **Python 3.7 and higher**.
1111

1212
## Getting started
1313
Below is a simple example that describes the instantiation and most basic usage of our SDK:

setup.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,20 @@
66

77
TESTS_REQUIRES = [
88
'flake8',
9-
'pytest==7.0.1',
10-
'pytest-mock==3.12.0',
11-
'coverage==6.2',
9+
'pytest==7.1.0',
10+
'pytest-mock==3.11.1',
11+
'coverage==7.2.7',
1212
'pytest-cov',
13-
'importlib-metadata==4.2',
14-
'tomli==1.2.3',
15-
'iniconfig==1.1.1',
16-
'attrs==22.1.0'
13+
'importlib-metadata==6.7',
14+
'tomli',
15+
'iniconfig',
16+
'attrs'
1717
]
1818

1919
INSTALL_REQUIRES = [
20-
'requests>=2.9.1',
21-
'pyyaml>=5.4',
20+
'requests',
21+
'pyyaml',
2222
'docopt>=0.6.2',
23-
'enum34;python_version<"3.4"',
2423
'bloom-filter2>=2.0.0'
2524
]
2625

splitio/api/auth.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from splitio.api import APIException
77
from splitio.api.commons import headers_from_metadata, record_telemetry
8+
from splitio.spec import SPEC_VERSION
89
from splitio.util.time import get_current_epoch_time_ms
910
from splitio.api.client import HttpClientException
1011
from splitio.models.token import from_raw
@@ -43,7 +44,7 @@ def authenticate(self):
4344
try:
4445
response = self._client.get(
4546
'auth',
46-
'/v2/auth',
47+
'/v2/auth?s=' + SPEC_VERSION,
4748
self._sdk_key,
4849
extra_headers=self._metadata,
4950
)

splitio/api/commons.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
"""Commons module."""
22
from splitio.util.time import get_current_epoch_time_ms
3+
from splitio.spec import SPEC_VERSION
34

45
_CACHE_CONTROL = 'Cache-Control'
56
_CACHE_CONTROL_NO_CACHE = 'no-cache'
67

7-
88
def headers_from_metadata(sdk_metadata, client_key=None):
99
"""
1010
Generate a dict with headers required by data-recording API endpoints.
@@ -57,7 +57,7 @@ def record_telemetry(status_code, elapsed, metric_name, telemetry_runtime_produc
5757
class FetchOptions(object):
5858
"""Fetch Options object."""
5959

60-
def __init__(self, cache_control_headers=False, change_number=None, sets=None):
60+
def __init__(self, cache_control_headers=False, change_number=None, sets=None, spec=SPEC_VERSION):
6161
"""
6262
Class constructor.
6363
@@ -73,6 +73,7 @@ def __init__(self, cache_control_headers=False, change_number=None, sets=None):
7373
self._cache_control_headers = cache_control_headers
7474
self._change_number = change_number
7575
self._sets = sets
76+
self._spec = spec
7677

7778
@property
7879
def cache_control_headers(self):
@@ -89,6 +90,11 @@ def sets(self):
8990
"""Return sets."""
9091
return self._sets
9192

93+
@property
94+
def spec(self):
95+
"""Return sets."""
96+
return self._spec
97+
9298
def __eq__(self, other):
9399
"""Match between other options."""
94100
if self._cache_control_headers != other._cache_control_headers:
@@ -97,6 +103,8 @@ def __eq__(self, other):
97103
return False
98104
if self._sets != other._sets:
99105
return False
106+
if self._spec != other._spec:
107+
return False
100108
return True
101109

102110

@@ -116,14 +124,15 @@ def build_fetch(change_number, fetch_options, metadata):
116124
:return: Objects for fetch
117125
:rtype: dict, dict
118126
"""
119-
query = {'since': change_number}
127+
query = {'s': fetch_options.spec} if fetch_options.spec is not None else {}
128+
query['since'] = change_number
120129
extra_headers = metadata
121130
if fetch_options is None:
122131
return query, extra_headers
123132
if fetch_options.cache_control_headers:
124133
extra_headers[_CACHE_CONTROL] = _CACHE_CONTROL_NO_CACHE
125-
if fetch_options.change_number is not None:
126-
query['till'] = fetch_options.change_number
127134
if fetch_options.sets is not None:
128135
query['sets'] = fetch_options.sets
136+
if fetch_options.change_number is not None:
137+
query['till'] = fetch_options.change_number
129138
return query, extra_headers

splitio/models/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
class MatcherNotFoundException(Exception):
2+
"""Exception to raise when a matcher is not found."""
3+
4+
def __init__(self, custom_message):
5+
"""Constructor."""
6+
Exception.__init__(self, custom_message)

splitio/models/grammar/condition.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from enum import Enum
44

5+
from splitio.models import MatcherNotFoundException
56
from splitio.models.grammar import matchers
67
from splitio.models.grammar import partitions
78

@@ -124,6 +125,7 @@ def from_raw(raw_condition):
124125
]
125126

126127
matcher_objects = [matchers.from_raw(x) for x in raw_condition['matcherGroup']['matchers']]
128+
127129
combiner = _MATCHER_COMBINERS[raw_condition['matcherGroup']['combiner']]
128130
label = raw_condition.get('label')
129131

splitio/models/grammar/matchers/__init__.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Matchers entrypoint module."""
2+
from splitio.models import MatcherNotFoundException
23
from splitio.models.grammar.matchers.keys import AllKeysMatcher, UserDefinedSegmentMatcher
34
from splitio.models.grammar.matchers.numeric import BetweenMatcher, EqualToMatcher, \
45
GreaterThanOrEqualMatcher, LessThanOrEqualMatcher
@@ -7,6 +8,8 @@
78
from splitio.models.grammar.matchers.string import ContainsStringMatcher, \
89
EndsWithMatcher, RegexMatcher, StartsWithMatcher, WhitelistMatcher
910
from splitio.models.grammar.matchers.misc import BooleanMatcher, DependencyMatcher
11+
from splitio.models.grammar.matchers.semver import EqualToSemverMatcher, GreaterThanOrEqualToSemverMatcher, LessThanOrEqualToSemverMatcher, \
12+
BetweenSemverMatcher, InListSemverMatcher
1013

1114

1215
MATCHER_TYPE_ALL_KEYS = 'ALL_KEYS'
@@ -26,6 +29,11 @@
2629
MATCHER_TYPE_IN_SPLIT_TREATMENT = 'IN_SPLIT_TREATMENT'
2730
MATCHER_TYPE_EQUAL_TO_BOOLEAN = 'EQUAL_TO_BOOLEAN'
2831
MATCHER_TYPE_MATCHES_STRING = 'MATCHES_STRING'
32+
MATCHER_TYPE_EQUAL_TO_SEMVER = 'EQUAL_TO_SEMVER'
33+
MATCHER_GREATER_THAN_OR_EQUAL_TO_SEMVER = 'GREATER_THAN_OR_EQUAL_TO_SEMVER'
34+
MATCHER_LESS_THAN_OR_EQUAL_TO_SEMVER = 'LESS_THAN_OR_EQUAL_TO_SEMVER'
35+
MATCHER_BETWEEN_SEMVER = 'BETWEEN_SEMVER'
36+
MATCHER_INLIST_SEMVER = 'IN_LIST_SEMVER'
2937

3038

3139
_MATCHER_BUILDERS = {
@@ -45,10 +53,14 @@
4553
MATCHER_TYPE_CONTAINS_STRING: ContainsStringMatcher,
4654
MATCHER_TYPE_IN_SPLIT_TREATMENT: DependencyMatcher,
4755
MATCHER_TYPE_EQUAL_TO_BOOLEAN: BooleanMatcher,
48-
MATCHER_TYPE_MATCHES_STRING: RegexMatcher
56+
MATCHER_TYPE_MATCHES_STRING: RegexMatcher,
57+
MATCHER_TYPE_EQUAL_TO_SEMVER: EqualToSemverMatcher,
58+
MATCHER_GREATER_THAN_OR_EQUAL_TO_SEMVER: GreaterThanOrEqualToSemverMatcher,
59+
MATCHER_LESS_THAN_OR_EQUAL_TO_SEMVER: LessThanOrEqualToSemverMatcher,
60+
MATCHER_BETWEEN_SEMVER: BetweenSemverMatcher,
61+
MATCHER_INLIST_SEMVER: InListSemverMatcher
4962
}
5063

51-
5264
def from_raw(raw_matcher):
5365
"""
5466
Parse a condition from a JSON portion of splitChanges.
@@ -63,5 +75,5 @@ def from_raw(raw_matcher):
6375
try:
6476
builder = _MATCHER_BUILDERS[matcher_type]
6577
except KeyError:
66-
raise ValueError('Invalid matcher type %s' % matcher_type)
78+
raise MatcherNotFoundException('Invalid matcher type %s' % matcher_type)
6779
return builder(raw_matcher)

0 commit comments

Comments
 (0)