Skip to content

Commit b0e92bf

Browse files
authored
Merge pull request #461 from splitio/flagsets-storage-redis
added flagsets to redis split storage
2 parents a69f8f6 + aa2a2fe commit b0e92bf

22 files changed

+662
-288
lines changed

splitio/client/client.py

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -404,9 +404,9 @@ def _get_treatments_by_flag_sets(self, key, flag_sets, method, attributes=None):
404404
:return: Dictionary with the result of all the feature flags provided
405405
:rtype: dict
406406
"""
407-
feature_flags_names = self._get_feature_flag_names_by_flag_sets(flag_sets)
407+
feature_flags_names = self._get_feature_flag_names_by_flag_sets(flag_sets, method)
408408
if feature_flags_names == []:
409-
_LOGGER.warning("No valid Flag set or no feature flags found for evaluating treatments")
409+
_LOGGER.warning("%s: No valid Flag set or no feature flags found for evaluating treatments" % (method))
410410
return {}
411411

412412
if 'config' in method.value:
@@ -418,7 +418,7 @@ def _get_treatments_by_flag_sets(self, key, flag_sets, method, attributes=None):
418418
return {feature_flag: result[0] for (feature_flag, result) in with_config.items()}
419419

420420

421-
def _get_feature_flag_names_by_flag_sets(self, flag_sets):
421+
def _get_feature_flag_names_by_flag_sets(self, flag_sets, method_name):
422422
"""
423423
Sanitize given flag sets and return list of feature flag names associated with them
424424
@@ -428,17 +428,13 @@ def _get_feature_flag_names_by_flag_sets(self, flag_sets):
428428
:return: list of feature flag names
429429
:rtype: list
430430
"""
431-
sanitized_flag_sets = config.sanitize_flag_sets(flag_sets)
432-
feature_flags = []
433-
for flag_set in sanitized_flag_sets:
434-
feature_flags_by_set = self._split_storage.get_feature_flags_by_sets(flag_set)
435-
if feature_flags_by_set is None:
436-
_LOGGER.warning("Fetching feature flags for flag set %s encountered an error, skipping this flag set." % (flag_set))
437-
continue
438-
feature_flags.extend(feature_flags_by_set)
439-
feature_flags_names = []
440-
[feature_flags_names.append(feature_flag) for feature_flag in feature_flags]
441-
return feature_flags_names
431+
sanitized_flag_sets = input_validator.validate_flag_sets(flag_sets, method_name)
432+
feature_flags_by_set = self._split_storage.get_feature_flags_by_sets(sanitized_flag_sets)
433+
if feature_flags_by_set is None:
434+
_LOGGER.warning("%s: Fetching feature flags for flag set %s encountered an error, skipping this flag set." % (method_name, flag_sets))
435+
return []
436+
return feature_flags_by_set
437+
442438

443439
def _build_impression( # pylint: disable=too-many-arguments
444440
self,

splitio/client/config.py

Lines changed: 2 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44
import re
55

66
from splitio.engine.impressions import ImpressionsMode
7+
from splitio.client.input_validator import validate_flag_sets
78

89

910
_LOGGER = logging.getLogger(__name__)
1011
DEFAULT_DATA_SAMPLING = 1
11-
_FLAG_SETS_REGEX = '^[a-z0-9][_a-z0-9]{0,49}$'
1212

1313
DEFAULT_CONFIG = {
1414
'operationMode': 'standalone',
@@ -119,42 +119,6 @@ def _sanitize_impressions_mode(storage_type, mode, refresh_rate=None):
119119

120120
return mode, refresh_rate
121121

122-
123-
def sanitize_flag_sets(flag_sets):
124-
"""
125-
Check supplied flag sets list
126-
127-
:param flag_set: list of flag sets
128-
:type flag_set: list[str]
129-
130-
:returns: Sanitized and sorted flag sets
131-
:rtype: list[str]
132-
"""
133-
if not isinstance(flag_sets, list):
134-
_LOGGER.warning("SDK config: FlagSets config parameters type should be list object, parameter is discarded")
135-
return []
136-
137-
sanitized_flag_sets = set()
138-
for flag_set in flag_sets:
139-
if not isinstance(flag_set, str):
140-
_LOGGER.warning("SDK config: Flag Set name %s should be str object, this flag set is discarded" % (flag_set))
141-
continue
142-
if flag_set != flag_set.strip():
143-
_LOGGER.warning("SDK config: Flag Set name %s has extra whitespace, trimming" % (flag_set))
144-
flag_set = flag_set.strip()
145-
146-
if flag_set != flag_set.lower():
147-
_LOGGER.warning("SDK config: Flag Set name %s should be all lowercase - converting string to lowercase" % (flag_set))
148-
flag_set = flag_set.lower()
149-
150-
if re.search(_FLAG_SETS_REGEX, flag_set) is None or re.search(_FLAG_SETS_REGEX, flag_set).group() != flag_set:
151-
_LOGGER.warning("SDK config: you passed %s, Flag Set must adhere to the regular expressions %s. This means a Flag Set must start with a letter, be in lowercase, alphanumeric and have a max length of 50 characteres. %s was discarded.", flag_set, _FLAG_SETS_REGEX, flag_set)
152-
continue
153-
154-
sanitized_flag_sets.add(flag_set.strip())
155-
156-
return sorted(list(sanitized_flag_sets))
157-
158122
def sanitize(sdk_key, config):
159123
"""
160124
Look for inconsistencies or ill-formed configs and tune it accordingly.
@@ -179,6 +143,6 @@ def sanitize(sdk_key, config):
179143
_LOGGER.warning('metricRefreshRate parameter minimum value is 60 seconds, defaulting to 3600 seconds.')
180144
processed['metricsRefreshRate'] = 3600
181145

182-
processed['flagSetsFilter'] = sanitize_flag_sets(processed['flagSetsFilter']) if processed['flagSetsFilter'] is not None else None
146+
processed['flagSetsFilter'] = validate_flag_sets(processed['flagSetsFilter'], 'SDK Config') if processed['flagSetsFilter'] is not None else None
183147

184148
return processed

splitio/client/factory.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ def _build_redis_factory(api_key, cfg):
440440
cache_enabled = cfg.get('redisLocalCacheEnabled', False)
441441
cache_ttl = cfg.get('redisLocalCacheTTL', 5)
442442
storages = {
443-
'splits': RedisSplitStorage(redis_adapter, cache_enabled, cache_ttl),
443+
'splits': RedisSplitStorage(redis_adapter, cache_enabled, cache_ttl, cfg['flagSetsFilter'] if cfg['flagSetsFilter'] is not None else []),
444444
'segments': RedisSegmentStorage(redis_adapter),
445445
'impressions': RedisImpressionsStorage(redis_adapter, sdk_metadata),
446446
'events': RedisEventsStorage(redis_adapter, sdk_metadata),

splitio/client/input_validator.py

Lines changed: 51 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
MAX_LENGTH = 250
1616
EVENT_TYPE_PATTERN = r'^[a-zA-Z0-9][-_.:a-zA-Z0-9]{0,79}$'
1717
MAX_PROPERTIES_LENGTH_BYTES = 32768
18+
_FLAG_SETS_REGEX = '^[a-z0-9][_a-z0-9]{0,49}$'
1819

1920

2021
def _check_not_null(value, name, operation):
@@ -79,7 +80,7 @@ def _check_string_not_empty(value, name, operation):
7980
return True
8081

8182

82-
def _check_string_matches(value, operation, pattern):
83+
def _check_string_matches(value, operation, pattern, name, length):
8384
"""
8485
Check if value is adhere to a regular expression passed.
8586
@@ -92,14 +93,14 @@ def _check_string_matches(value, operation, pattern):
9293
:return: The result of validation
9394
:rtype: True|False
9495
"""
95-
if not re.match(pattern, value):
96+
if re.search(pattern, value) is None or re.search(pattern, value).group() != value:
9697
_LOGGER.error(
9798
'%s: you passed %s, event_type must ' +
9899
'adhere to the regular expression %s. ' +
99-
'This means an event name must be alphanumeric, cannot be more ' +
100-
'than 80 characters long, and can only include a dash, underscore, ' +
100+
'This means %s must be alphanumeric, cannot be more ' +
101+
'than %s characters long, and can only include a dash, underscore, ' +
101102
'period, or colon as separators of alphanumeric characters.',
102-
operation, value, pattern
103+
operation, value, pattern, name, length
103104
)
104105
return False
105106
return True
@@ -165,10 +166,7 @@ def _check_valid_object_key(key, name, operation):
165166
:return: The result of validation
166167
:rtype: str|None
167168
"""
168-
if key is None:
169-
_LOGGER.error(
170-
'%s: you passed a null %s, %s must be a non-empty string.',
171-
operation, name, name)
169+
if not _check_not_null(key, 'key', operation):
172170
return None
173171
if isinstance(key, str):
174172
if not _check_string_not_empty(key, name, operation):
@@ -179,7 +177,7 @@ def _check_valid_object_key(key, name, operation):
179177
return key_str
180178

181179

182-
def _remove_empty_spaces(value, operation):
180+
def _remove_empty_spaces(value, name, operation):
183181
"""
184182
Check if an string has whitespaces.
185183
@@ -192,10 +190,17 @@ def _remove_empty_spaces(value, operation):
192190
"""
193191
strip_value = value.strip()
194192
if value != strip_value:
195-
_LOGGER.warning("%s: feature flag name '%s' has extra whitespace, trimming.", operation, value)
193+
_LOGGER.warning("%s: %s '%s' has extra whitespace, trimming.", operation, name, value)
196194
return strip_value
197195

198196

197+
def _convert_str_to_lower(value, name, operation):
198+
lower_value = value.lower()
199+
if value != lower_value:
200+
_LOGGER.warning("%s: %s '%s' should be all lowercase - converting string to lowercase" % (operation, name, value))
201+
return lower_value
202+
203+
199204
def validate_key(key, method_name):
200205
"""
201206
Validate Key parameter for get_treatment/s.
@@ -211,8 +216,7 @@ def validate_key(key, method_name):
211216
"""
212217
matching_key_result = None
213218
bucketing_key_result = None
214-
if key is None:
215-
_LOGGER.error('%s: you passed a null key, key must be a non-empty string.', method_name)
219+
if not _check_not_null(key, 'key', method_name):
216220
return None, None
217221

218222
if isinstance(key, Key):
@@ -255,7 +259,7 @@ def validate_feature_flag_name(feature_flag_name, should_validate_existance, fea
255259
)
256260
return None
257261

258-
return _remove_empty_spaces(feature_flag_name, method_name)
262+
return _remove_empty_spaces(feature_flag_name, 'feature flag name', method_name)
259263

260264

261265
def validate_track_key(key):
@@ -294,10 +298,7 @@ def validate_traffic_type(traffic_type, should_validate_existance, feature_flag_
294298
(not _check_is_string(traffic_type, 'traffic_type', 'track')) or \
295299
(not _check_string_not_empty(traffic_type, 'traffic_type', 'track')):
296300
return None
297-
if not traffic_type.islower():
298-
_LOGGER.warning('track: %s should be all lowercase - converting string to lowercase.',
299-
traffic_type)
300-
traffic_type = traffic_type.lower()
301+
traffic_type = _convert_str_to_lower(traffic_type, 'traffic type', 'track')
301302

302303
if should_validate_existance and not feature_flag_storage.is_valid_traffic_type(traffic_type):
303304
_LOGGER.warning(
@@ -322,7 +323,7 @@ def validate_event_type(event_type):
322323
if (not _check_not_null(event_type, 'event_type', 'track')) or \
323324
(not _check_is_string(event_type, 'event_type', 'track')) or \
324325
(not _check_string_not_empty(event_type, 'event_type', 'track')) or \
325-
(not _check_string_matches(event_type, 'track', EVENT_TYPE_PATTERN)):
326+
(not _check_string_matches(event_type, 'track', EVENT_TYPE_PATTERN, 'an event name', 80)):
326327
return None
327328
return event_type
328329

@@ -390,7 +391,7 @@ def validate_feature_flags_get_treatments( # pylint: disable=invalid-name
390391
_LOGGER.error("%s: feature flag names must be a non-empty array.", method_name)
391392
return None, None
392393
filtered_feature_flags = set(
393-
_remove_empty_spaces(feature_flag, method_name) for feature_flag in feature_flags
394+
_remove_empty_spaces(feature_flag, 'feature flag name', method_name) for feature_flag in feature_flags
394395
if feature_flag is not None and
395396
_check_is_string(feature_flag, 'feature flag name', method_name) and
396397
_check_string_not_empty(feature_flag, 'feature flag name', method_name)
@@ -566,3 +567,33 @@ def validate_pluggable_adapter(config):
566567
_LOGGER.error("Pluggable adapter method %s has less than required arguments count: %s : " % (exp_method, len(get_method_args)))
567568
return False
568569
return True
570+
571+
def validate_flag_sets(flag_sets, method_name):
572+
"""
573+
Validate flag sets list
574+
575+
:param flag_set: list of flag sets
576+
:type flag_set: list[str]
577+
578+
:returns: Sanitized and sorted flag sets
579+
:rtype: list[str]
580+
"""
581+
if not isinstance(flag_sets, list):
582+
_LOGGER.warning("%s: flag sets parameter type should be list object, parameter is discarded" % (method_name))
583+
return []
584+
585+
sanitized_flag_sets = set()
586+
for flag_set in flag_sets:
587+
if not _check_not_null(flag_set, 'flag set', method_name):
588+
continue
589+
if not _check_is_string(flag_set, 'flag set', method_name):
590+
continue
591+
flag_set = _remove_empty_spaces(flag_set, 'flag set', method_name)
592+
flag_set = _convert_str_to_lower(flag_set, 'flag set', method_name)
593+
594+
if not _check_string_matches(flag_set, method_name, _FLAG_SETS_REGEX, 'a flag set', 50):
595+
continue
596+
597+
sanitized_flag_sets.add(flag_set)
598+
599+
return sorted(list(sanitized_flag_sets))

0 commit comments

Comments
 (0)