Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
106 commits
Select commit Hold shift + click to select a range
8ab6b6e
Update split model
chillaq Aug 22, 2023
43df7f4
polish
chillaq Aug 22, 2023
80d77df
updated api.split and api.commons
chillaq Aug 22, 2023
cc10af7
polish
chillaq Aug 22, 2023
8c03313
Merge pull request #445 from splitio/flagsets-model-split
chillaq Aug 22, 2023
231281f
added sorting sets in uri
chillaq Aug 22, 2023
ff3eeaa
updated model telemetry
chillaq Aug 23, 2023
1aaa950
Merge pull request #447 from splitio/flagsets-model-telemetry
chillaq Aug 23, 2023
4a675f1
updated storage inmemory telemetry
chillaq Aug 23, 2023
a65a8e4
Merge pull request #448 from splitio/flagsets-storage-memory-telemetry
chillaq Aug 23, 2023
a4d8ee8
polish
chillaq Aug 23, 2023
587cbe1
updated engine.telemetry class
chillaq Aug 24, 2023
9917aeb
updated storage inmemory split storage
chillaq Aug 24, 2023
a7366a7
polish
chillaq Aug 24, 2023
89788f5
Update tests/engine/test_telemetry.py
chillaq Aug 28, 2023
c33717a
Merge pull request #450 from splitio/flagsets-engine-telemetry
chillaq Aug 28, 2023
6b28c0d
Merge pull request #451 from splitio/flagsets-storage-memory-split
chillaq Aug 28, 2023
89671a6
Added "update" to split storage to replace put, remove and set_change…
chillaq Aug 28, 2023
be21ee2
added flag set validations to config
chillaq Aug 28, 2023
3649ada
polish
chillaq Aug 28, 2023
efa99a7
Merge pull request #453 from splitio/flagsets-client-config
chillaq Aug 29, 2023
0bf33de
Update splitio/storage/__init__.py
chillaq Aug 29, 2023
06de1c9
Update splitio/storage/inmemmory.py
chillaq Aug 29, 2023
ebb9e68
Update splitio/storage/pluggable.py
chillaq Aug 29, 2023
954465b
Update splitio/storage/redis.py
chillaq Aug 29, 2023
d35ddda
Merge pull request #452 from splitio/flagsets-storage-memory-split
chillaq Aug 29, 2023
016102b
Merge pull request #446 from splitio/flagsets-api-split
chillaq Aug 29, 2023
85840f8
Added sync.split logic, updated storage.inmemory.split and sync.synch…
chillaq Aug 29, 2023
8bccfc9
polish
chillaq Aug 29, 2023
874f205
Merge pull request #455 from splitio/flagsets-sync-split
chillaq Aug 30, 2023
d53bda2
Added flagset support in client, updated client.config and models.tel…
chillaq Aug 30, 2023
6743839
polish
chillaq Aug 30, 2023
1ee5866
Merge pull request #456 from splitio/flagsets-client-client
chillaq Aug 30, 2023
bf63614
added client.manager test, minor fix in models.splits and updated con…
chillaq Aug 31, 2023
f2b9858
Added flagset support to factory
chillaq Aug 31, 2023
f62b88a
added test
chillaq Sep 1, 2023
3e27c40
Merge pull request #457 from splitio/flagsets-client-manager
chillaq Sep 1, 2023
f5c3a96
updated config param name
chillaq Sep 1, 2023
c3341fd
Merge branch 'Feature/FlagSets' into flagsets-client-factory
chillaq Sep 1, 2023
19f5f61
added flagsets to redis split storage
chillaq Sep 1, 2023
0c93d55
polish
chillaq Sep 1, 2023
198f5b2
Updated fetching splits by flag set to batch fetching for redis and m…
chillaq Sep 1, 2023
44447f6
polish
chillaq Sep 1, 2023
a299882
polish
chillaq Sep 5, 2023
e6a78c8
polish
chillaq Sep 5, 2023
c86273a
Updated push.splitworker and sync.split
chillaq Sep 5, 2023
afa9112
Merge pull request #463 from splitio/flagset-push-splitworker
chillaq Sep 6, 2023
823e5b3
polishing
chillaq Sep 6, 2023
9a63a90
polish
chillaq Sep 6, 2023
fd94eea
added helper for checking flag sets in config flag sets
chillaq Sep 6, 2023
4b157e0
added helper function to combine valid sets into one set
chillaq Sep 6, 2023
a5a872e
polish
chillaq Sep 6, 2023
f01618e
updted storage.pluggable class
chillaq Sep 6, 2023
76cde5d
updated e2e inmemory tests
chillaq Sep 6, 2023
c339280
Added none check for fetched splits by flag set in client.
chillaq Sep 7, 2023
e57205f
Merge pull request #464 from splitio/flagsets-storage-pluggable
chillaq Sep 7, 2023
db469a5
Merge pull request #458 from splitio/flagsets-client-factory
chillaq Sep 7, 2023
837e571
added e2e tests, remved set validation from pluggable since there is …
chillaq Sep 7, 2023
38329f2
Merge branch 'Feature/FlagSets' into flagsets-tests-e2e
chillaq Sep 7, 2023
a69f8f6
Merge pull request #465 from splitio/flagsets-tests-e2e
chillaq Sep 8, 2023
9e94dcd
added flagset and flagsetfilter classes and updated other classes
chillaq Sep 8, 2023
23380af
polishing
chillaq Sep 8, 2023
7cbfa0e
Fixed all tests
chillaq Sep 11, 2023
0dc547c
added updating fetchOptions with filter flagsets
chillaq Sep 11, 2023
f483caf
moved sorting from flagset validation to main validation
chillaq Sep 12, 2023
17b6d2f
fixed tests and pluggable fetching flagset
chillaq Sep 12, 2023
6b9b8ac
Fixed exception when no token is fecthed in SSE
chillaq Sep 12, 2023
5fca0c1
moved flagset classes to model, polish in input_validator and storage…
chillaq Sep 13, 2023
1157c93
polish
chillaq Sep 13, 2023
43ec2a4
polish
chillaq Sep 13, 2023
474c72f
corrected config param typo
chillaq Sep 14, 2023
d16c9ff
updated tests
chillaq Sep 14, 2023
3d9f005
fixed config
chillaq Sep 14, 2023
3fac14b
Merge branch 'Feature/FlagSets' into flagsets-storage-redis
chillaq Sep 14, 2023
8a48ab4
Added flagset methods to Telemetry and added localhost json sync spli…
chillaq Sep 14, 2023
c15e337
polish
chillaq Sep 14, 2023
aa2a2fe
added character length for regex check
chillaq Sep 15, 2023
b0e92bf
Merge pull request #461 from splitio/flagsets-storage-redis
chillaq Sep 15, 2023
7c6b1eb
moved flagset and flagset filter classes to storage
chillaq Sep 15, 2023
52f4530
Merge branch 'Feature/FlagSets' into flagsets-test-integration
chillaq Sep 15, 2023
9bd41e2
Merge pull request #466 from splitio/flagsets-test-integration
chillaq Sep 15, 2023
d783365
polishing
chillaq Sep 15, 2023
9f22a20
Added flagsets total and invalid count for telemetry config
chillaq Sep 19, 2023
065badf
added property to flagset filter to provide sorted flagsets
chillaq Sep 19, 2023
9dc6f0e
Merge pull request #469 from splitio/flagsets-telemetry-init
chillaq Sep 22, 2023
f55128c
Update version.py
chillaq Sep 22, 2023
1add907
fixed exception when new flagset detected
chillaq Oct 12, 2023
576a0f4
cleanup
chillaq Oct 12, 2023
f1ae8b9
added warning when not ready
chillaq Oct 16, 2023
6636c84
added default_treatment to split view
chillaq Oct 16, 2023
c6c0f11
polishing
chillaq Oct 17, 2023
f52756f
Merge pull request #475 from splitio/flagsets-client-fix-warning
chillaq Oct 17, 2023
2e00bff
Merge pull request #473 from splitio/flagset-storage-fix
chillaq Oct 17, 2023
a9270a1
Merge branch 'development' into Feature/FlagSets
chillaq Oct 25, 2023
878b0a6
Updated redis storage to reflect flagset filter
chillaq Oct 30, 2023
6994602
removed raising exception at posting config data
chillaq Oct 31, 2023
ddf9881
Merge pull request #483 from splitio/flagset-storage-redis
chillaq Oct 31, 2023
7e316c9
Revert "Updated redis storage to reflect flagset filter"
chillaq Nov 1, 2023
84b5f57
Merge pull request #484 from splitio/revert-483-flagset-storage-redis
chillaq Nov 1, 2023
e6119de
1- Added flagset filter check with consumer mode
chillaq Nov 1, 2023
55f39a0
cleanup
chillaq Nov 1, 2023
5213955
Merge pull request #485 from splitio/flagset-config-fix
chillaq Nov 1, 2023
e70a440
fixed version
chillaq Nov 1, 2023
2a1df38
fixed version in changes
chillaq Nov 1, 2023
ed3dae4
polishing
chillaq Nov 1, 2023
b4dc2a1
Merge pull request #487 from splitio/flagset-polish
mmelograno Nov 2, 2023
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: 10 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
9.6.0 (Nov 3, 2023)
- Added support for Flag Sets on the SDK, which enables grouping feature flags and interacting with the group rather than individually (more details in our documentation):
- Added new variations of the get treatment methods to support evaluating flags in given flag set/s.
- get_treatments_by_flag_set and get_treatments_by_flag_sets
- get_treatments_with_config_by_flag_set and get_treatments_with_config_by_flag_sets
- Added a new optional Split Filter configuration option. This allows the SDK and Split services to only synchronize the flags in the specified flag sets, avoiding unused or unwanted flags from being synced on the SDK instance, bringing all the benefits from a reduced payload.
- Note: Only applicable when the SDK is in charge of the rollout data synchronization. When not applicable, the SDK will log a warning on init.
- Updated the following SDK manager methods to expose flag sets on flag views.
- Removed raising an exception when Telemetry post config data fails, SDK will only log the error.

9.5.1 (Sep 5, 2023)
- Exclude tests from when building the package
- Fixed exception when fetching telemetry stats if no SSE Feature flags update events are stored
Expand Down
15 changes: 14 additions & 1 deletion splitio/api/commons.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def record_telemetry(status_code, elapsed, metric_name, telemetry_runtime_produc
class FetchOptions(object):
"""Fetch Options object."""

def __init__(self, cache_control_headers=False, change_number=None):
def __init__(self, cache_control_headers=False, change_number=None, sets=None):
"""
Class constructor.

Expand All @@ -66,9 +66,13 @@ def __init__(self, cache_control_headers=False, change_number=None):

:param change_number: ChangeNumber to use for bypassing CDN in request.
:type change_number: int

:param sets: list of flag sets
:type sets: list
"""
self._cache_control_headers = cache_control_headers
self._change_number = change_number
self._sets = sets

@property
def cache_control_headers(self):
Expand All @@ -80,12 +84,19 @@ def change_number(self):
"""Return change number."""
return self._change_number

@property
def sets(self):
"""Return sets."""
return self._sets

def __eq__(self, other):
"""Match between other options."""
if self._cache_control_headers != other._cache_control_headers:
return False
if self._change_number != other._change_number:
return False
if self._sets != other._sets:
return False
return True


Expand Down Expand Up @@ -113,4 +124,6 @@ def build_fetch(change_number, fetch_options, metadata):
extra_headers[_CACHE_CONTROL] = _CACHE_CONTROL_NO_CACHE
if fetch_options.change_number is not None:
query['till'] = fetch_options.change_number
if fetch_options.sets is not None:
query['sets'] = fetch_options.sets
return query, extra_headers
2 changes: 2 additions & 0 deletions splitio/api/splits.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ def fetch_splits(self, change_number, fetch_options):
if 200 <= response.status_code < 300:
return json.loads(response.body)
else:
if response.status_code == 414:
_LOGGER.error('Error fetching feature flags; the amount of flag sets provided are too big, causing uri length error.')
raise APIException(response.body, response.status_code)
except HttpClientException as exc:
_LOGGER.error('Error fetching feature flags because an exception was raised by the HTTPClient')
Expand Down
1 change: 0 additions & 1 deletion splitio/api/telemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ def record_init(self, configs):
'Error posting init config because an exception was raised by the HTTPClient'
)
_LOGGER.debug('Error: ', exc_info=True)
raise APIException('Init config data not flushed properly.') from exc

def record_stats(self, stats):
"""
Expand Down
138 changes: 133 additions & 5 deletions splitio/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from splitio.models.impressions import Impression, Label
from splitio.models.events import Event, EventWrapper
from splitio.models.telemetry import get_latency_bucket_index, MethodExceptionsAndLatencies
from splitio.client import input_validator
from splitio.client import input_validator, config
from splitio.util.time import get_current_epoch_time_ms, utctime_ms

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -59,8 +59,9 @@ def destroyed(self):
"""Return whether the factory holding this client has been destroyed."""
return self._factory.destroyed

def _evaluate_if_ready(self, matching_key, bucketing_key, feature, attributes=None):
def _evaluate_if_ready(self, matching_key, bucketing_key, feature, method, attributes=None):
if not self.ready:
_LOGGER.warning("%s: The SDK is not ready, results may be incorrect for feature flag %s. Make sure to wait for SDK readiness before using this method", method, feature)
self._telemetry_init_producer.record_not_ready_usage()
return {
'treatment': CONTROL,
Expand Down Expand Up @@ -102,7 +103,7 @@ def _make_evaluation(self, key, feature_flag, attributes, method_name, metric_na
or not input_validator.validate_attributes(attributes, method_name):
return CONTROL, None

result = self._evaluate_if_ready(matching_key, bucketing_key, feature_flag, attributes)
result = self._evaluate_if_ready(matching_key, bucketing_key, feature_flag, method_name, attributes)

impression = self._build_impression(
matching_key,
Expand Down Expand Up @@ -167,7 +168,7 @@ def _make_evaluations(self, key, feature_flags, attributes, method_name, metric_

try:
evaluations = self._evaluate_features_if_ready(matching_key, bucketing_key,
list(feature_flags), attributes)
list(feature_flags), method_name, attributes)

for feature_flag in feature_flags:
try:
Expand Down Expand Up @@ -212,8 +213,9 @@ def _make_evaluations(self, key, feature_flags, attributes, method_name, metric_
_LOGGER.debug('Error: ', exc_info=True)
return input_validator.generate_control_treatments(list(feature_flags), method_name)

def _evaluate_features_if_ready(self, matching_key, bucketing_key, feature_flags, attributes=None):
def _evaluate_features_if_ready(self, matching_key, bucketing_key, feature_flags, method, attributes=None):
if not self.ready:
_LOGGER.warning("%s: The SDK is not ready, results may be incorrect for feature flags %s. Make sure to wait for SDK readiness before using this method", method, ', '.join([feature for feature in feature_flags]))
self._telemetry_init_producer.record_not_ready_usage()
return {
feature_flag: {
Expand Down Expand Up @@ -309,6 +311,132 @@ def get_treatments(self, key, feature_flags, attributes=None):
MethodExceptionsAndLatencies.TREATMENTS)
return {feature_flag: result[0] for (feature_flag, result) in with_config.items()}

def get_treatments_by_flag_set(self, key, flag_set, attributes=None):
"""
Get treatments for feature flags that contain given flag set.

This method never raises an exception. If there's a problem, the appropriate log message
will be generated and the method will return the CONTROL treatment.

:param key: The key for which to get the treatment
:type key: str
:param flag_set: flag set
:type flag_sets: str
:param attributes: An optional dictionary of attributes
:type attributes: dict

:return: Dictionary with the result of all the feature flags provided
:rtype: dict
"""
return self._get_treatments_by_flag_sets( key, [flag_set], MethodExceptionsAndLatencies.TREATMENTS_BY_FLAG_SET, attributes)

def get_treatments_by_flag_sets(self, key, flag_sets, attributes=None):
"""
Get treatments for feature flags that contain given flag sets.

This method never raises an exception. If there's a problem, the appropriate log message
will be generated and the method will return the CONTROL treatment.

:param key: The key for which to get the treatment
:type key: str
:param flag_sets: list of flag sets
:type flag_sets: list
:param attributes: An optional dictionary of attributes
:type attributes: dict

:return: Dictionary with the result of all the feature flags provided
:rtype: dict
"""
return self._get_treatments_by_flag_sets( key, flag_sets, MethodExceptionsAndLatencies.TREATMENTS_BY_FLAG_SETS, attributes)

def get_treatments_with_config_by_flag_set(self, key, flag_set, attributes=None):
"""
Get treatments for feature flags that contain given flag set.

This method never raises an exception. If there's a problem, the appropriate log message
will be generated and the method will return the CONTROL treatment.

:param key: The key for which to get the treatment
:type key: str
:param flag_set: flag set
:type flag_sets: str
:param attributes: An optional dictionary of attributes
:type attributes: dict

:return: Dictionary with the result of all the feature flags provided
:rtype: dict
"""
return self._get_treatments_by_flag_sets( key, [flag_set], MethodExceptionsAndLatencies.TREATMENTS_WITH_CONFIG_BY_FLAG_SET, attributes)

def get_treatments_with_config_by_flag_sets(self, key, flag_sets, attributes=None):
"""
Get treatments for feature flags that contain given flag set.

This method never raises an exception. If there's a problem, the appropriate log message
will be generated and the method will return the CONTROL treatment.

:param key: The key for which to get the treatment
:type key: str
:param flag_set: flag set
:type flag_sets: str
:param attributes: An optional dictionary of attributes
:type attributes: dict

:return: Dictionary with the result of all the feature flags provided
:rtype: dict
"""
return self._get_treatments_by_flag_sets( key, flag_sets, MethodExceptionsAndLatencies.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, attributes)

def _get_treatments_by_flag_sets(self, key, flag_sets, method, attributes=None):
"""
Get treatments for feature flags that contain given flag sets.

This method never raises an exception. If there's a problem, the appropriate log message
will be generated and the method will return the CONTROL treatment.

:param key: The key for which to get the treatment
:type key: str
:param flag_sets: list of flag sets
:type flag_sets: list
:param method: Treatment by flag set method flavor
:type method: splitio.models.telemetry.MethodExceptionsAndLatencies
:param attributes: An optional dictionary of attributes
:type attributes: dict

:return: Dictionary with the result of all the feature flags provided
:rtype: dict
"""
feature_flags_names = self._get_feature_flag_names_by_flag_sets(flag_sets, method.value)
if feature_flags_names == []:
_LOGGER.warning("%s: No valid Flag set or no feature flags found for evaluating treatments" % (method.value))
return {}

if 'config' in method.value:
return self._make_evaluations(key, feature_flags_names, attributes, method.value,
method)

with_config = self._make_evaluations(key, feature_flags_names, attributes, method.value,
method)
return {feature_flag: result[0] for (feature_flag, result) in with_config.items()}


def _get_feature_flag_names_by_flag_sets(self, flag_sets, method_name):
"""
Sanitize given flag sets and return list of feature flag names associated with them

:param flag_sets: list of flag sets
:type flag_sets: list

:return: list of feature flag names
:rtype: list
"""
sanitized_flag_sets = input_validator.validate_flag_sets(flag_sets, method_name)
feature_flags_by_set = self._split_storage.get_feature_flags_by_sets(sanitized_flag_sets)
if feature_flags_by_set is None:
_LOGGER.warning("Fetching feature flags for flag set %s encountered an error, skipping this flag set." % (flag_sets))
return []
return feature_flags_by_set

def _build_impression( # pylint: disable=too-many-arguments
self,
matching_key,
Expand Down
13 changes: 9 additions & 4 deletions splitio/client/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
import logging

from splitio.engine.impressions import ImpressionsMode
from splitio.client.input_validator import validate_flag_sets


_LOGGER = logging.getLogger(__name__)
DEFAULT_DATA_SAMPLING = 1


DEFAULT_CONFIG = {
'operationMode': 'standalone',
'connectionTimeout': 1500,
Expand Down Expand Up @@ -58,10 +58,10 @@
'dataSampling': DEFAULT_DATA_SAMPLING,
'storageWrapper': None,
'storagePrefix': None,
'storageType': None
'storageType': None,
'flagSetsFilter': None
}


def _parse_operation_mode(sdk_key, config):
"""
Process incoming config to determine operation mode and storage type
Expand Down Expand Up @@ -118,7 +118,6 @@ def _sanitize_impressions_mode(storage_type, mode, refresh_rate=None):

return mode, refresh_rate


def sanitize(sdk_key, config):
"""
Look for inconsistencies or ill-formed configs and tune it accordingly.
Expand All @@ -143,4 +142,10 @@ def sanitize(sdk_key, config):
_LOGGER.warning('metricRefreshRate parameter minimum value is 60 seconds, defaulting to 3600 seconds.')
processed['metricsRefreshRate'] = 3600

if config['operationMode'] == 'consumer' and config.get('flagSetsFilter') is not None:
processed['flagSetsFilter'] = None
_LOGGER.warning('config: FlagSets filter is not applicable for Consumer modes where the SDK does keep rollout data in sync. FlagSet filter was discarded.')
else:
processed['flagSetsFilter'] = sorted(validate_flag_sets(processed['flagSetsFilter'], 'SDK Config')) if processed['flagSetsFilter'] is not None else None

return processed
Loading