Skip to content

Commit 48e2aa9

Browse files
authored
Merge pull request #596 from splitio/FME-9616-fallback-client
Update client class
2 parents 65b97e8 + c1fdc5f commit 48e2aa9

File tree

13 files changed

+810
-102
lines changed

13 files changed

+810
-102
lines changed

splitio/client/client.py

Lines changed: 59 additions & 39 deletions
Large diffs are not rendered by default.

splitio/client/factory.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,8 @@ def __init__( # pylint: disable=too-many-arguments
170170
telemetry_producer=None,
171171
telemetry_init_producer=None,
172172
telemetry_submitter=None,
173-
preforked_initialization=False
173+
preforked_initialization=False,
174+
fallback_treatments_configuration=None
174175
):
175176
"""
176177
Class constructor.
@@ -201,6 +202,7 @@ def __init__( # pylint: disable=too-many-arguments
201202
self._ready_time = get_current_epoch_time_ms()
202203
_LOGGER.debug("Running in threading mode")
203204
self._sdk_internal_ready_flag = sdk_ready_flag
205+
self._fallback_treatments_configuration = fallback_treatments_configuration
204206
self._start_status_updater()
205207

206208
def _start_status_updater(self):
@@ -242,7 +244,7 @@ def client(self):
242244
This client is only a set of references to structures hold by the factory.
243245
Creating one a fast operation and safe to be used anywhere.
244246
"""
245-
return Client(self, self._recorder, self._labels_enabled)
247+
return Client(self, self._recorder, self._labels_enabled, self._fallback_treatments_configuration)
246248

247249
def manager(self):
248250
"""
@@ -338,7 +340,8 @@ def __init__( # pylint: disable=too-many-arguments
338340
telemetry_init_producer=None,
339341
telemetry_submitter=None,
340342
manager_start_task=None,
341-
api_client=None
343+
api_client=None,
344+
fallback_treatments_configuration=None
342345
):
343346
"""
344347
Class constructor.
@@ -372,6 +375,7 @@ def __init__( # pylint: disable=too-many-arguments
372375
self._sdk_ready_flag = asyncio.Event()
373376
self._ready_task = asyncio.get_running_loop().create_task(self._update_status_when_ready_async())
374377
self._api_client = api_client
378+
self._fallback_treatments_configuration = fallback_treatments_configuration
375379

376380
async def _update_status_when_ready_async(self):
377381
"""Wait until the sdk is ready and update the status for async mode."""
@@ -460,7 +464,7 @@ def client(self):
460464
This client is only a set of references to structures hold by the factory.
461465
Creating one a fast operation and safe to be used anywhere.
462466
"""
463-
return ClientAsync(self, self._recorder, self._labels_enabled)
467+
return ClientAsync(self, self._recorder, self._labels_enabled, self._fallback_treatments_configuration)
464468

465469
def _wrap_impression_listener(listener, metadata):
466470
"""
@@ -623,15 +627,16 @@ def _build_in_memory_factory(api_key, cfg, sdk_url=None, events_url=None, # pyl
623627
synchronizer._split_synchronizers._segment_sync.shutdown()
624628

625629
return SplitFactory(api_key, storages, cfg['labelsEnabled'],
626-
recorder, manager, None, telemetry_producer, telemetry_init_producer, telemetry_submitter, preforked_initialization=preforked_initialization)
630+
recorder, manager, None, telemetry_producer, telemetry_init_producer, telemetry_submitter, preforked_initialization=preforked_initialization,
631+
fallback_treatments_configuration=cfg['fallbackTreatments'])
627632

628633
initialization_thread = threading.Thread(target=manager.start, name="SDKInitializer", daemon=True)
629634
initialization_thread.start()
630635

631636
return SplitFactory(api_key, storages, cfg['labelsEnabled'],
632637
recorder, manager, sdk_ready_flag,
633638
telemetry_producer, telemetry_init_producer,
634-
telemetry_submitter)
639+
telemetry_submitter, fallback_treatments_configuration=cfg['fallbackTreatments'])
635640

636641
async def _build_in_memory_factory_async(api_key, cfg, sdk_url=None, events_url=None, # pylint:disable=too-many-arguments,too-many-localsa
637642
auth_api_base_url=None, streaming_api_base_url=None, telemetry_api_base_url=None,
@@ -750,7 +755,7 @@ async def _build_in_memory_factory_async(api_key, cfg, sdk_url=None, events_url=
750755
recorder, manager,
751756
telemetry_producer, telemetry_init_producer,
752757
telemetry_submitter, manager_start_task=manager_start_task,
753-
api_client=http_client)
758+
api_client=http_client, fallback_treatments_configuration=cfg['fallbackTreatments'])
754759

755760
def _build_redis_factory(api_key, cfg):
756761
"""Build and return a split factory with redis-based storage."""
@@ -828,7 +833,8 @@ def _build_redis_factory(api_key, cfg):
828833
manager,
829834
sdk_ready_flag=None,
830835
telemetry_producer=telemetry_producer,
831-
telemetry_init_producer=telemetry_init_producer
836+
telemetry_init_producer=telemetry_init_producer,
837+
fallback_treatments_configuration=cfg['fallbackTreatments']
832838
)
833839
redundant_factory_count, active_factory_count = _get_active_and_redundant_count()
834840
storages['telemetry'].record_active_and_redundant_factories(active_factory_count, redundant_factory_count)
@@ -910,7 +916,8 @@ async def _build_redis_factory_async(api_key, cfg):
910916
manager,
911917
telemetry_producer=telemetry_producer,
912918
telemetry_init_producer=telemetry_init_producer,
913-
telemetry_submitter=telemetry_submitter
919+
telemetry_submitter=telemetry_submitter,
920+
fallback_treatments_configuration=cfg['fallbackTreatments']
914921
)
915922
redundant_factory_count, active_factory_count = _get_active_and_redundant_count()
916923
await storages['telemetry'].record_active_and_redundant_factories(active_factory_count, redundant_factory_count)
@@ -992,7 +999,8 @@ def _build_pluggable_factory(api_key, cfg):
992999
manager,
9931000
sdk_ready_flag=None,
9941001
telemetry_producer=telemetry_producer,
995-
telemetry_init_producer=telemetry_init_producer
1002+
telemetry_init_producer=telemetry_init_producer,
1003+
fallback_treatments_configuration=cfg['fallbackTreatments']
9961004
)
9971005
redundant_factory_count, active_factory_count = _get_active_and_redundant_count()
9981006
storages['telemetry'].record_active_and_redundant_factories(active_factory_count, redundant_factory_count)
@@ -1072,7 +1080,8 @@ async def _build_pluggable_factory_async(api_key, cfg):
10721080
manager,
10731081
telemetry_producer=telemetry_producer,
10741082
telemetry_init_producer=telemetry_init_producer,
1075-
telemetry_submitter=telemetry_submitter
1083+
telemetry_submitter=telemetry_submitter,
1084+
fallback_treatments_configuration=cfg['fallbackTreatments']
10761085
)
10771086
redundant_factory_count, active_factory_count = _get_active_and_redundant_count()
10781087
await storages['telemetry'].record_active_and_redundant_factories(active_factory_count, redundant_factory_count)
@@ -1150,6 +1159,7 @@ def _build_localhost_factory(cfg):
11501159
telemetry_producer=telemetry_producer,
11511160
telemetry_init_producer=telemetry_producer.get_telemetry_init_producer(),
11521161
telemetry_submitter=LocalhostTelemetrySubmitter(),
1162+
fallback_treatments_configuration=cfg['fallbackTreatments']
11531163
)
11541164

11551165
async def _build_localhost_factory_async(cfg):
@@ -1220,7 +1230,8 @@ async def _build_localhost_factory_async(cfg):
12201230
telemetry_producer=telemetry_producer,
12211231
telemetry_init_producer=telemetry_producer.get_telemetry_init_producer(),
12221232
telemetry_submitter=LocalhostTelemetrySubmitterAsync(),
1223-
manager_start_task=manager_start_task
1233+
manager_start_task=manager_start_task,
1234+
fallback_treatments_configuration=cfg['fallbackTreatments']
12241235
)
12251236

12261237
def get_factory(api_key, **kwargs):

splitio/client/input_validator.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from splitio.client.key import Key
99
from splitio.client import client
10+
from splitio.client.util import get_fallback_treatment_and_label
1011
from splitio.engine.evaluator import CONTROL
1112
from splitio.models.fallback_treatment import FallbackTreatment
1213

@@ -16,7 +17,7 @@
1617
EVENT_TYPE_PATTERN = r'^[a-zA-Z0-9][-_.:a-zA-Z0-9]{0,79}$'
1718
MAX_PROPERTIES_LENGTH_BYTES = 32768
1819
_FLAG_SETS_REGEX = '^[a-z0-9][_a-z0-9]{0,49}$'
19-
_FALLBACK_TREATMENT_REGEX = '^[a-zA-Z][a-zA-Z0-9-_;]+$'
20+
_FALLBACK_TREATMENT_REGEX = '^[0-9]+[.a-zA-Z0-9_-]*$|^[a-zA-Z]+[a-zA-Z0-9_-]*$'
2021
_FALLBACK_TREATMENT_SIZE = 100
2122

2223
def _check_not_null(value, name, operation):
@@ -502,7 +503,7 @@ def validate_feature_flags_get_treatments( # pylint: disable=invalid-name
502503
valid_feature_flags.append(ff)
503504
return valid_feature_flags
504505

505-
def generate_control_treatments(feature_flags):
506+
def generate_control_treatments(feature_flags, fallback_treatments_configuration):
506507
"""
507508
Generate valid feature flags to control.
508509
@@ -517,7 +518,13 @@ def generate_control_treatments(feature_flags):
517518
to_return = {}
518519
for feature_flag in feature_flags:
519520
if isinstance(feature_flag, str) and len(feature_flag.strip())> 0:
520-
to_return[feature_flag] = (CONTROL, None)
521+
treatment = CONTROL
522+
config = None
523+
label = ""
524+
label, treatment, config = get_fallback_treatment_and_label(fallback_treatments_configuration,
525+
feature_flag, treatment, label, _LOGGER)
526+
527+
to_return[feature_flag] = (treatment, config)
521528
return to_return
522529

523530

@@ -719,6 +726,10 @@ def validate_fallback_treatment(fallback_treatment):
719726
if not isinstance(fallback_treatment, FallbackTreatment):
720727
_LOGGER.warning("Config: Fallback treatment instance should be FallbackTreatment, input is discarded")
721728
return False
729+
730+
if not isinstance(fallback_treatment.treatment, str):
731+
_LOGGER.warning("Config: Fallback treatment value should be str type, input is discarded")
732+
return False
722733

723734
if not validate_regex_name(fallback_treatment.treatment):
724735
_LOGGER.warning("Config: Fallback treatment should match regex %s", _FALLBACK_TREATMENT_REGEX)

splitio/client/util.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,22 @@ def get_metadata(config):
5151
version = 'python-%s' % __version__
5252
ip_address, hostname = _get_hostname_and_ip(config)
5353
return SdkMetadata(version, hostname, ip_address)
54+
55+
def get_fallback_treatment_and_label(fallback_treatments_configuration, feature_name, treatment, label, _logger):
56+
if fallback_treatments_configuration == None:
57+
return label, treatment, None
58+
59+
if fallback_treatments_configuration.by_flag_fallback_treatment != None and \
60+
fallback_treatments_configuration.by_flag_fallback_treatment.get(feature_name) != None:
61+
_logger.debug('Using Fallback Treatment for feature: %s', feature_name)
62+
return fallback_treatments_configuration.by_flag_fallback_treatment.get(feature_name).label_prefix + label, \
63+
fallback_treatments_configuration.by_flag_fallback_treatment.get(feature_name).treatment, \
64+
fallback_treatments_configuration.by_flag_fallback_treatment.get(feature_name).config
65+
66+
if fallback_treatments_configuration.global_fallback_treatment != None:
67+
_logger.debug('Using Global Fallback Treatment.')
68+
return fallback_treatments_configuration.global_fallback_treatment.label_prefix + label, \
69+
fallback_treatments_configuration.global_fallback_treatment.treatment, \
70+
fallback_treatments_configuration.global_fallback_treatment.config
71+
72+
return label, treatment, None

splitio/engine/evaluator.py

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import logging
33
from collections import namedtuple
44

5+
from splitio.client.util import get_fallback_treatment_and_label
56
from splitio.models.impressions import Label
67
from splitio.models.grammar.condition import ConditionType
78
from splitio.models.grammar.matchers.misc import DependencyMatcher
@@ -52,7 +53,8 @@ def eval_with_context(self, key, bucketing, feature_name, attrs, ctx):
5253
if not feature:
5354
_LOGGER.warning('Unknown or invalid feature: %s', feature)
5455
label = Label.SPLIT_NOT_FOUND
55-
label, _treatment, config = self._get_fallback_treatment_and_label(feature_name, _treatment, label)
56+
label, _treatment, config = get_fallback_treatment_and_label(self._fallback_treatments_configuration,
57+
feature_name, _treatment, label, _LOGGER)
5658
else:
5759
_change_number = feature.change_number
5860
if feature.killed:
@@ -72,25 +74,6 @@ def eval_with_context(self, key, bucketing, feature_name, attrs, ctx):
7274
},
7375
'impressions_disabled': feature.impressions_disabled if feature else None
7476
}
75-
76-
def _get_fallback_treatment_and_label(self, feature_name, treatment, label):
77-
if self._fallback_treatments_configuration == None:
78-
return label, treatment, None
79-
80-
if self._fallback_treatments_configuration.by_flag_fallback_treatment != None and \
81-
self._fallback_treatments_configuration.by_flag_fallback_treatment.get(feature_name) != None:
82-
_LOGGER.debug('Using Fallback Treatment for feature: %s', feature_name)
83-
return self._fallback_treatments_configuration.by_flag_fallback_treatment.get(feature_name).label_prefix + label, \
84-
self._fallback_treatments_configuration.by_flag_fallback_treatment.get(feature_name).treatment, \
85-
self._fallback_treatments_configuration.by_flag_fallback_treatment.get(feature_name).config
86-
87-
if self._fallback_treatments_configuration.global_fallback_treatment != None:
88-
_LOGGER.debug('Using Global Fallback Treatment.')
89-
return self._fallback_treatments_configuration.global_fallback_treatment.label_prefix + label, \
90-
self._fallback_treatments_configuration.global_fallback_treatment.treatment, \
91-
self._fallback_treatments_configuration.global_fallback_treatment.config
92-
93-
return label, treatment, None
9477

9578
def _get_treatment(self, feature, bucketing, key, attrs, ctx, label, _treatment):
9679
if _treatment == CONTROL:

splitio/storage/adapters/redis.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -715,7 +715,7 @@ def _build_default_client(config): # pylint: disable=too-many-locals
715715
unix_socket_path = config.get('redisUnixSocketPath', None)
716716
encoding = config.get('redisEncoding', 'utf-8')
717717
encoding_errors = config.get('redisEncodingErrors', 'strict')
718-
errors = config.get('redisErrors', None)
718+
# errors = config.get('redisErrors', None)
719719
decode_responses = config.get('redisDecodeResponses', True)
720720
retry_on_timeout = config.get('redisRetryOnTimeout', False)
721721
ssl = config.get('redisSsl', False)
@@ -740,7 +740,7 @@ def _build_default_client(config): # pylint: disable=too-many-locals
740740
unix_socket_path=unix_socket_path,
741741
encoding=encoding,
742742
encoding_errors=encoding_errors,
743-
errors=errors,
743+
# errors=errors, Starting from redis 6.0.0 errors argument is removed
744744
decode_responses=decode_responses,
745745
retry_on_timeout=retry_on_timeout,
746746
ssl=ssl,

0 commit comments

Comments
 (0)