Skip to content

Commit 1579eb0

Browse files
committed
updated client, factory, validator and config classes
1 parent 14a7266 commit 1579eb0

File tree

4 files changed

+189
-44
lines changed

4 files changed

+189
-44
lines changed

splitio/client/client.py

Lines changed: 108 additions & 1 deletion
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

@@ -346,6 +346,113 @@ def get_treatments_with_config(self, key, feature_flag_names, attributes=None):
346346
except Exception:
347347
return {feature: (CONTROL, None) for feature in feature_flag_names}
348348

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

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

splitio/client/factory.py

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,8 @@ def _wrap_impression_listener_async(listener, metadata):
498498
return None
499499

500500
def _build_in_memory_factory(api_key, cfg, sdk_url=None, events_url=None, # pylint:disable=too-many-arguments,too-many-locals
501-
auth_api_base_url=None, streaming_api_base_url=None, telemetry_api_base_url=None):
501+
auth_api_base_url=None, streaming_api_base_url=None, telemetry_api_base_url=None,
502+
total_flag_sets=0, invalid_flag_sets=0):
502503
"""Build and return a split factory tailored to the supplied config."""
503504
if not input_validator.validate_factory_instantiation(api_key):
504505
return None
@@ -536,7 +537,7 @@ def _build_in_memory_factory(api_key, cfg, sdk_url=None, events_url=None, # pyl
536537
}
537538

538539
storages = {
539-
'splits': InMemorySplitStorage(),
540+
'splits': InMemorySplitStorage(cfg['flagSetsFilter'] if cfg['flagSetsFilter'] is not None else []),
540541
'segments': InMemorySegmentStorage(),
541542
'impressions': InMemoryImpressionStorage(cfg['impressionsQueueSize'], telemetry_runtime_producer),
542543
'events': InMemoryEventStorage(cfg['eventsQueueSize'], telemetry_runtime_producer),
@@ -607,7 +608,7 @@ def _build_in_memory_factory(api_key, cfg, sdk_url=None, events_url=None, # pyl
607608
unique_keys_tracker=unique_keys_tracker
608609
)
609610

610-
telemetry_init_producer.record_config(cfg, extra_cfg)
611+
telemetry_init_producer.record_config(cfg, extra_cfg, total_flag_sets, invalid_flag_sets)
611612

612613
if preforked_initialization:
613614
synchronizer.sync_all(max_retry_attempts=_MAX_RETRY_SYNC_ALL)
@@ -625,7 +626,8 @@ def _build_in_memory_factory(api_key, cfg, sdk_url=None, events_url=None, # pyl
625626
telemetry_submitter)
626627

627628
async def _build_in_memory_factory_async(api_key, cfg, sdk_url=None, events_url=None, # pylint:disable=too-many-arguments,too-many-localsa
628-
auth_api_base_url=None, streaming_api_base_url=None, telemetry_api_base_url=None):
629+
auth_api_base_url=None, streaming_api_base_url=None, telemetry_api_base_url=None,
630+
total_flag_sets=0, invalid_flag_sets=0):
629631
"""Build and return a split factory tailored to the supplied config in async mode."""
630632
if not input_validator.validate_factory_instantiation(api_key):
631633
return None
@@ -663,7 +665,7 @@ async def _build_in_memory_factory_async(api_key, cfg, sdk_url=None, events_url=
663665
}
664666

665667
storages = {
666-
'splits': InMemorySplitStorageAsync(),
668+
'splits': InMemorySplitStorageAsync(cfg['flagSetsFilter'] if cfg['flagSetsFilter'] is not None else []),
667669
'segments': InMemorySegmentStorageAsync(),
668670
'impressions': InMemoryImpressionStorageAsync(cfg['impressionsQueueSize'], telemetry_runtime_producer),
669671
'events': InMemoryEventStorageAsync(cfg['eventsQueueSize'], telemetry_runtime_producer),
@@ -733,7 +735,7 @@ async def _build_in_memory_factory_async(api_key, cfg, sdk_url=None, events_url=
733735
unique_keys_tracker=unique_keys_tracker
734736
)
735737

736-
await telemetry_init_producer.record_config(cfg, extra_cfg)
738+
await telemetry_init_producer.record_config(cfg, extra_cfg, total_flag_sets, invalid_flag_sets)
737739

738740
if preforked_initialization:
739741
await synchronizer.sync_all(max_retry_attempts=_MAX_RETRY_SYNC_ALL)
@@ -814,7 +816,7 @@ def _build_redis_factory(api_key, cfg):
814816
initialization_thread = threading.Thread(target=manager.start, name="SDKInitializer", daemon=True)
815817
initialization_thread.start()
816818

817-
telemetry_init_producer.record_config(cfg, {})
819+
telemetry_init_producer.record_config(cfg, {}, 0, 0)
818820

819821
split_factory = SplitFactory(
820822
api_key,
@@ -894,7 +896,7 @@ async def _build_redis_factory_async(api_key, cfg):
894896
)
895897

896898
manager = RedisManagerAsync(synchronizer)
897-
await telemetry_init_producer.record_config(cfg, {})
899+
await telemetry_init_producer.record_config(cfg, {}, 0, 0)
898900
manager.start()
899901

900902
split_factory = SplitFactoryAsync(
@@ -977,7 +979,7 @@ def _build_pluggable_factory(api_key, cfg):
977979
initialization_thread = threading.Thread(target=manager.start, name="SDKInitializer", daemon=True)
978980
initialization_thread.start()
979981

980-
telemetry_init_producer.record_config(cfg, {})
982+
telemetry_init_producer.record_config(cfg, {}, 0, 0)
981983

982984
split_factory = SplitFactory(
983985
api_key,
@@ -1056,7 +1058,7 @@ async def _build_pluggable_factory_async(api_key, cfg):
10561058
# Using same class as redis for consumer mode only
10571059
manager = RedisManagerAsync(synchronizer)
10581060
manager.start()
1059-
await telemetry_init_producer.record_config(cfg, {})
1061+
await telemetry_init_producer.record_config(cfg, {}, 0, 0)
10601062

10611063
split_factory = SplitFactoryAsync(
10621064
api_key,
@@ -1083,7 +1085,7 @@ def _build_localhost_factory(cfg):
10831085
telemetry_evaluation_producer = telemetry_producer.get_telemetry_evaluation_producer()
10841086

10851087
storages = {
1086-
'splits': InMemorySplitStorage(),
1088+
'splits': InMemorySplitStorage(cfg['flagSetsFilter'] if cfg['flagSetsFilter'] is not None else []),
10871089
'segments': InMemorySegmentStorage(), # not used, just to avoid possible future errors.
10881090
'impressions': LocalhostImpressionsStorage(),
10891091
'events': LocalhostEventsStorage(),
@@ -1282,8 +1284,14 @@ async def get_factory_async(api_key, **kwargs):
12821284
_INSTANTIATED_FACTORIES.update([api_key])
12831285
_INSTANTIATED_FACTORIES_LOCK.release()
12841286

1285-
config = sanitize_config(api_key, kwargs.get('config', {}))
1287+
config_raw = kwargs.get('config', {})
1288+
total_flag_sets = 0
1289+
invalid_flag_sets = 0
1290+
if config_raw.get('flagSetsFilter') is not None and isinstance(config_raw.get('flagSetsFilter'), list):
1291+
total_flag_sets = len(config_raw.get('flagSetsFilter'))
1292+
invalid_flag_sets = total_flag_sets - len(input_validator.validate_flag_sets(config_raw.get('flagSetsFilter'), 'Telemetry Init'))
12861293

1294+
config = sanitize_config(api_key, config_raw)
12871295
if config['operationMode'] == 'localhost':
12881296
split_factory = await _build_localhost_factory_async(config)
12891297
elif config['storageType'] == 'redis':
@@ -1298,8 +1306,9 @@ async def get_factory_async(api_key, **kwargs):
12981306
kwargs.get('events_api_base_url'),
12991307
kwargs.get('auth_api_base_url'),
13001308
kwargs.get('streaming_api_base_url'),
1301-
kwargs.get('telemetry_api_base_url'))
1302-
1309+
kwargs.get('telemetry_api_base_url'),
1310+
total_flag_sets,
1311+
invalid_flag_sets)
13031312
return split_factory
13041313

13051314
def _get_active_and_redundant_count():

0 commit comments

Comments
 (0)