Skip to content

Commit 6858c3b

Browse files
authored
Merge pull request #493 from splitio/async-flagsets-client
Async flagsets client
2 parents 3810663 + 66f9b72 commit 6858c3b

File tree

12 files changed

+4670
-2797
lines changed

12 files changed

+4670
-2797
lines changed

splitio/client/client.py

Lines changed: 216 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from splitio.models.impressions import Impression, Label
77
from splitio.models.events import Event, EventWrapper
88
from splitio.models.telemetry import get_latency_bucket_index, MethodExceptionsAndLatencies
9-
from splitio.client import input_validator
9+
from splitio.client import input_validator, config
1010
from splitio.util.time import get_current_epoch_time_ms, utctime_ms
1111

1212

@@ -18,7 +18,7 @@ class ClientBase(object): # pylint: disable=too-many-instance-attributes
1818

1919
_FAILED_EVAL_RESULT = {
2020
'treatment': CONTROL,
21-
'config': None,
21+
'configurations': None,
2222
'impression': {
2323
'label': Label.EXCEPTION,
2424
'change_number': None,
@@ -86,8 +86,6 @@ def _validate_treatment_input(key, feature, attributes, method):
8686
matching_key, bucketing_key = input_validator.validate_key(key, 'get_' + method.value)
8787
if not matching_key:
8888
raise _InvalidInputError()
89-
# if bucketing_key is None:
90-
# bucketing_key = matching_key
9189

9290
feature = input_validator.validate_feature_flag_name(feature, 'get_' + method.value)
9391
if not feature:
@@ -104,8 +102,6 @@ def _validate_treatments_input(key, features, attributes, method):
104102
matching_key, bucketing_key = input_validator.validate_key(key, 'get_' + method.value)
105103
if not matching_key:
106104
raise _InvalidInputError()
107-
# if bucketing_key is None:
108-
# bucketing_key = matching_key
109105

110106
features = input_validator.validate_feature_flags_get_treatments('get_' + method.value, features)
111107
if not features:
@@ -345,6 +341,113 @@ def get_treatments_with_config(self, key, feature_flag_names, attributes=None):
345341
except Exception:
346342
return {feature: (CONTROL, None) for feature in feature_flag_names}
347343

344+
def get_treatments_by_flag_set(self, key, flag_set, attributes=None):
345+
"""
346+
Get treatments for feature flags that contain given flag set.
347+
This method never raises an exception. If there's a problem, the appropriate log message
348+
will be generated and the method will return the CONTROL treatment.
349+
:param key: The key for which to get the treatment
350+
:type key: str
351+
:param flag_set: flag set
352+
:type flag_sets: str
353+
:param attributes: An optional dictionary of attributes
354+
:type attributes: dict
355+
:return: Dictionary with the result of all the feature flags provided
356+
:rtype: dict
357+
"""
358+
return self._get_treatments_by_flag_sets( key, [flag_set], MethodExceptionsAndLatencies.TREATMENTS_BY_FLAG_SET, attributes)
359+
360+
def get_treatments_by_flag_sets(self, key, flag_sets, attributes=None):
361+
"""
362+
Get treatments for feature flags that contain given flag sets.
363+
This method never raises an exception. If there's a problem, the appropriate log message
364+
will be generated and the method will return the CONTROL treatment.
365+
:param key: The key for which to get the treatment
366+
:type key: str
367+
:param flag_sets: list of flag sets
368+
:type flag_sets: list
369+
:param attributes: An optional dictionary of attributes
370+
:type attributes: dict
371+
:return: Dictionary with the result of all the feature flags provided
372+
:rtype: dict
373+
"""
374+
return self._get_treatments_by_flag_sets( key, flag_sets, MethodExceptionsAndLatencies.TREATMENTS_BY_FLAG_SETS, attributes)
375+
376+
def get_treatments_with_config_by_flag_set(self, key, flag_set, attributes=None):
377+
"""
378+
Get treatments for feature flags that contain given flag set.
379+
This method never raises an exception. If there's a problem, the appropriate log message
380+
will be generated and the method will return the CONTROL treatment.
381+
:param key: The key for which to get the treatment
382+
:type key: str
383+
:param flag_set: flag set
384+
:type flag_sets: str
385+
:param attributes: An optional dictionary of attributes
386+
:type attributes: dict
387+
:return: Dictionary with the result of all the feature flags provided
388+
:rtype: dict
389+
"""
390+
return self._get_treatments_by_flag_sets( key, [flag_set], MethodExceptionsAndLatencies.TREATMENTS_WITH_CONFIG_BY_FLAG_SET, attributes)
391+
392+
def get_treatments_with_config_by_flag_sets(self, key, flag_sets, attributes=None):
393+
"""
394+
Get treatments for feature flags that contain given flag set.
395+
This method never raises an exception. If there's a problem, the appropriate log message
396+
will be generated and the method will return the CONTROL treatment.
397+
:param key: The key for which to get the treatment
398+
:type key: str
399+
:param flag_set: flag set
400+
:type flag_sets: str
401+
:param attributes: An optional dictionary of attributes
402+
:type attributes: dict
403+
:return: Dictionary with the result of all the feature flags provided
404+
:rtype: dict
405+
"""
406+
return self._get_treatments_by_flag_sets( key, flag_sets, MethodExceptionsAndLatencies.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, attributes)
407+
408+
def _get_treatments_by_flag_sets(self, key, flag_sets, method, attributes=None):
409+
"""
410+
Get treatments for feature flags that contain given flag sets.
411+
This method never raises an exception. If there's a problem, the appropriate log message
412+
will be generated and the method will return the CONTROL treatment.
413+
:param key: The key for which to get the treatment
414+
:type key: str
415+
:param flag_sets: list of flag sets
416+
:type flag_sets: list
417+
:param method: Treatment by flag set method flavor
418+
:type method: splitio.models.telemetry.MethodExceptionsAndLatencies
419+
:param attributes: An optional dictionary of attributes
420+
:type attributes: dict
421+
:return: Dictionary with the result of all the feature flags provided
422+
:rtype: dict
423+
"""
424+
feature_flags_names = self._get_feature_flag_names_by_flag_sets(flag_sets, 'get_' + method.value)
425+
if feature_flags_names == []:
426+
_LOGGER.warning("%s: No valid Flag set or no feature flags found for evaluating treatments", 'get_' + method.value)
427+
return {}
428+
429+
if 'config' in method.value:
430+
return self._get_treatments(key, feature_flags_names, method, attributes)
431+
432+
with_config = self._get_treatments(key, feature_flags_names, method, attributes)
433+
return {feature_flag: result[0] for (feature_flag, result) in with_config.items()}
434+
435+
436+
def _get_feature_flag_names_by_flag_sets(self, flag_sets, method_name):
437+
"""
438+
Sanitize given flag sets and return list of feature flag names associated with them
439+
:param flag_sets: list of flag sets
440+
:type flag_sets: list
441+
:return: list of feature flag names
442+
:rtype: list
443+
"""
444+
sanitized_flag_sets = input_validator.validate_flag_sets(flag_sets, method_name)
445+
feature_flags_by_set = self._feature_flag_storage.get_feature_flags_by_sets(sanitized_flag_sets)
446+
if feature_flags_by_set is None:
447+
_LOGGER.warning("Fetching feature flags for flag set %s encountered an error, skipping this flag set." % (flag_sets))
448+
return []
449+
return feature_flags_by_set
450+
348451
def _get_treatments(self, key, features, method, attributes=None):
349452
"""
350453
Validate key, feature flag names and objects, and get the treatments and configs with an optional dictionary of attributes.
@@ -623,6 +726,113 @@ async def get_treatments_with_config(self, key, feature_flag_names, attributes=N
623726
_LOGGER.error("AA", exc_info=True)
624727
return {feature: (CONTROL, None) for feature in feature_flag_names}
625728

729+
async def get_treatments_by_flag_set(self, key, flag_set, attributes=None):
730+
"""
731+
Get treatments for feature flags that contain given flag set.
732+
This method never raises an exception. If there's a problem, the appropriate log message
733+
will be generated and the method will return the CONTROL treatment.
734+
:param key: The key for which to get the treatment
735+
:type key: str
736+
:param flag_set: flag set
737+
:type flag_sets: str
738+
:param attributes: An optional dictionary of attributes
739+
:type attributes: dict
740+
:return: Dictionary with the result of all the feature flags provided
741+
:rtype: dict
742+
"""
743+
return await self._get_treatments_by_flag_sets( key, [flag_set], MethodExceptionsAndLatencies.TREATMENTS_BY_FLAG_SET, attributes)
744+
745+
async def get_treatments_by_flag_sets(self, key, flag_sets, attributes=None):
746+
"""
747+
Get treatments for feature flags that contain given flag sets.
748+
This method never raises an exception. If there's a problem, the appropriate log message
749+
will be generated and the method will return the CONTROL treatment.
750+
:param key: The key for which to get the treatment
751+
:type key: str
752+
:param flag_sets: list of flag sets
753+
:type flag_sets: list
754+
:param attributes: An optional dictionary of attributes
755+
:type attributes: dict
756+
:return: Dictionary with the result of all the feature flags provided
757+
:rtype: dict
758+
"""
759+
return await self._get_treatments_by_flag_sets( key, flag_sets, MethodExceptionsAndLatencies.TREATMENTS_BY_FLAG_SETS, attributes)
760+
761+
async def get_treatments_with_config_by_flag_set(self, key, flag_set, attributes=None):
762+
"""
763+
Get treatments for feature flags that contain given flag set.
764+
This method never raises an exception. If there's a problem, the appropriate log message
765+
will be generated and the method will return the CONTROL treatment.
766+
:param key: The key for which to get the treatment
767+
:type key: str
768+
:param flag_set: flag set
769+
:type flag_sets: str
770+
:param attributes: An optional dictionary of attributes
771+
:type attributes: dict
772+
:return: Dictionary with the result of all the feature flags provided
773+
:rtype: dict
774+
"""
775+
return await self._get_treatments_by_flag_sets( key, [flag_set], MethodExceptionsAndLatencies.TREATMENTS_WITH_CONFIG_BY_FLAG_SET, attributes)
776+
777+
async def get_treatments_with_config_by_flag_sets(self, key, flag_sets, attributes=None):
778+
"""
779+
Get treatments for feature flags that contain given flag set.
780+
This method never raises an exception. If there's a problem, the appropriate log message
781+
will be generated and the method will return the CONTROL treatment.
782+
:param key: The key for which to get the treatment
783+
:type key: str
784+
:param flag_set: flag set
785+
:type flag_sets: str
786+
:param attributes: An optional dictionary of attributes
787+
:type attributes: dict
788+
:return: Dictionary with the result of all the feature flags provided
789+
:rtype: dict
790+
"""
791+
return await self._get_treatments_by_flag_sets( key, flag_sets, MethodExceptionsAndLatencies.TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, attributes)
792+
793+
async def _get_treatments_by_flag_sets(self, key, flag_sets, method, attributes=None):
794+
"""
795+
Get treatments for feature flags that contain given flag sets.
796+
This method never raises an exception. If there's a problem, the appropriate log message
797+
will be generated and the method will return the CONTROL treatment.
798+
:param key: The key for which to get the treatment
799+
:type key: str
800+
:param flag_sets: list of flag sets
801+
:type flag_sets: list
802+
:param method: Treatment by flag set method flavor
803+
:type method: splitio.models.telemetry.MethodExceptionsAndLatencies
804+
:param attributes: An optional dictionary of attributes
805+
:type attributes: dict
806+
:return: Dictionary with the result of all the feature flags provided
807+
:rtype: dict
808+
"""
809+
feature_flags_names = await self._get_feature_flag_names_by_flag_sets(flag_sets, 'get_' + method.value)
810+
if feature_flags_names == []:
811+
_LOGGER.warning("%s: No valid Flag set or no feature flags found for evaluating treatments", 'get_' + method.value)
812+
return {}
813+
814+
if 'config' in method.value:
815+
return await self._get_treatments(key, feature_flags_names, method, attributes)
816+
817+
with_config = await self._get_treatments(key, feature_flags_names, method, attributes)
818+
return {feature_flag: result[0] for (feature_flag, result) in with_config.items()}
819+
820+
821+
async def _get_feature_flag_names_by_flag_sets(self, flag_sets, method_name):
822+
"""
823+
Sanitize given flag sets and return list of feature flag names associated with them
824+
:param flag_sets: list of flag sets
825+
:type flag_sets: list
826+
:return: list of feature flag names
827+
:rtype: list
828+
"""
829+
sanitized_flag_sets = input_validator.validate_flag_sets(flag_sets, method_name)
830+
feature_flags_by_set = await self._feature_flag_storage.get_feature_flags_by_sets(sanitized_flag_sets)
831+
if feature_flags_by_set is None:
832+
_LOGGER.warning("Fetching feature flags for flag set %s encountered an error, skipping this flag set." % (flag_sets))
833+
return []
834+
return feature_flags_by_set
835+
626836
async def _get_treatments(self, key, features, method, attributes=None):
627837
"""
628838
Validate key, feature flag names and objects, and get the treatments and configs with an optional dictionary of attributes, for async calls

splitio/client/config.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import logging
44

55
from splitio.engine.impressions import ImpressionsMode
6-
6+
from splitio.client.input_validator import validate_flag_sets
77

88
_LOGGER = logging.getLogger(__name__)
99
DEFAULT_DATA_SAMPLING = 1
@@ -58,7 +58,8 @@
5858
'dataSampling': DEFAULT_DATA_SAMPLING,
5959
'storageWrapper': None,
6060
'storagePrefix': None,
61-
'storageType': None
61+
'storageType': None,
62+
'flagSetsFilter': None
6263
}
6364

6465

@@ -143,4 +144,10 @@ def sanitize(sdk_key, config):
143144
_LOGGER.warning('metricRefreshRate parameter minimum value is 60 seconds, defaulting to 3600 seconds.')
144145
processed['metricsRefreshRate'] = 3600
145146

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

0 commit comments

Comments
 (0)