splitio-client 10.4.0__tar.gz → 10.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {splitio_client-10.4.0 → splitio_client-10.5.0}/PKG-INFO +2 -2
- {splitio_client-10.4.0 → splitio_client-10.5.0}/setup.py +1 -1
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/client/client.py +57 -38
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/client/config.py +38 -2
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/client/factory.py +24 -13
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/client/input_validator.py +34 -3
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/client/util.py +1 -1
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/engine/evaluator.py +9 -3
- splitio_client-10.5.0/splitio/models/fallback_config.py +81 -0
- splitio_client-10.5.0/splitio/models/fallback_treatment.py +34 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/push/splitsse.py +1 -1
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/push/sse.py +1 -1
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/storage/adapters/redis.py +0 -2
- splitio_client-10.5.0/splitio/version.py +1 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio_client.egg-info/PKG-INFO +2 -2
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio_client.egg-info/SOURCES.txt +2 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio_client.egg-info/requires.txt +1 -1
- splitio_client-10.4.0/splitio/version.py +0 -1
- {splitio_client-10.4.0 → splitio_client-10.5.0}/LICENSE.txt +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/README.md +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/setup.cfg +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/__init__.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/api/__init__.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/api/auth.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/api/client.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/api/commons.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/api/events.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/api/impressions.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/api/segments.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/api/splits.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/api/telemetry.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/client/__init__.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/client/key.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/client/listener.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/client/localhost.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/client/manager.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/engine/__init__.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/engine/cache/__init__.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/engine/cache/lru.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/engine/filters.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/engine/hashfns/__init__.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/engine/hashfns/legacy.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/engine/hashfns/murmur3py.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/engine/impressions/__init__.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/engine/impressions/adapters.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/engine/impressions/impressions.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/engine/impressions/manager.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/engine/impressions/strategies.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/engine/impressions/unique_keys_tracker.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/engine/splitters.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/engine/telemetry.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/exceptions.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/factories.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/impressions.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/key.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/models/__init__.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/models/datatypes.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/models/events.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/models/grammar/__init__.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/models/grammar/condition.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/models/grammar/matchers/__init__.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/models/grammar/matchers/base.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/models/grammar/matchers/keys.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/models/grammar/matchers/misc.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/models/grammar/matchers/numeric.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/models/grammar/matchers/prerequisites.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/models/grammar/matchers/rule_based_segment.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/models/grammar/matchers/semver.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/models/grammar/matchers/sets.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/models/grammar/matchers/string.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/models/grammar/matchers/utils/__init__.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/models/grammar/matchers/utils/utils.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/models/grammar/partitions.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/models/impressions.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/models/notification.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/models/rule_based_segments.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/models/segments.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/models/splits.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/models/telemetry.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/models/token.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/optional/__init__.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/optional/loaders.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/push/__init__.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/push/manager.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/push/parser.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/push/processor.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/push/status_tracker.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/push/workers.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/recorder/__init__.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/recorder/recorder.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/spec.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/storage/__init__.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/storage/adapters/__init__.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/storage/adapters/cache_trait.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/storage/adapters/util.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/storage/inmemmory.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/storage/pluggable.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/storage/redis.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/sync/__init__.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/sync/event.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/sync/impression.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/sync/manager.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/sync/segment.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/sync/split.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/sync/synchronizer.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/sync/telemetry.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/sync/unique_keys.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/sync/util.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/tasks/__init__.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/tasks/events_sync.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/tasks/impressions_sync.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/tasks/segment_sync.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/tasks/split_sync.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/tasks/telemetry_sync.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/tasks/unique_keys_sync.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/tasks/util/__init__.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/tasks/util/asynctask.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/tasks/util/workerpool.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/util/__init__.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/util/backoff.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/util/decorators.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/util/storage_helper.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/util/threadutil.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio/util/time.py +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio_client.egg-info/dependency_links.txt +0 -0
- {splitio_client-10.4.0 → splitio_client-10.5.0}/splitio_client.egg-info/top_level.txt +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: splitio_client
|
|
3
|
-
Version: 10.
|
|
3
|
+
Version: 10.5.0
|
|
4
4
|
Summary: Split.io Python Client
|
|
5
5
|
Home-page: https://github.com/splitio/python-client
|
|
6
|
-
Download-URL: https://github.com/splitio/python-client/tarball/10.
|
|
6
|
+
Download-URL: https://github.com/splitio/python-client/tarball/10.5.0
|
|
7
7
|
Author: Patricio Echague, Sebastian Arrubia
|
|
8
8
|
Author-email: pato@split.io, sebastian@split.io
|
|
9
9
|
License: Apache License 2.0
|
|
@@ -45,7 +45,7 @@ setup(
|
|
|
45
45
|
tests_require=TESTS_REQUIRES,
|
|
46
46
|
extras_require={
|
|
47
47
|
'test': TESTS_REQUIRES,
|
|
48
|
-
'redis': ['redis>=2.10.5'],
|
|
48
|
+
'redis': ['redis>=2.10.5,<7.0.0'],
|
|
49
49
|
'uwsgi': ['uwsgi>=2.0.0'],
|
|
50
50
|
'cpphash': ['mmh3cffi==0.2.1'],
|
|
51
51
|
'asyncio': ['aiohttp>=3.8.4', 'aiofiles>=23.1.0'],
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import logging
|
|
3
3
|
import json
|
|
4
4
|
from collections import namedtuple
|
|
5
|
+
import copy
|
|
5
6
|
|
|
6
7
|
from splitio.engine.evaluator import Evaluator, CONTROL, EvaluationDataFactory, AsyncEvaluationDataFactory
|
|
7
8
|
from splitio.engine.splitters import Splitter
|
|
@@ -39,7 +40,7 @@ class ClientBase(object): # pylint: disable=too-many-instance-attributes
|
|
|
39
40
|
'impressions_disabled': False
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
def __init__(self, factory, recorder, labels_enabled=True):
|
|
43
|
+
def __init__(self, factory, recorder, labels_enabled=True, fallback_treatment_calculator=None):
|
|
43
44
|
"""
|
|
44
45
|
Construct a Client instance.
|
|
45
46
|
|
|
@@ -61,9 +62,10 @@ class ClientBase(object): # pylint: disable=too-many-instance-attributes
|
|
|
61
62
|
self._feature_flag_storage = factory._get_storage('splits') # pylint: disable=protected-access
|
|
62
63
|
self._segment_storage = factory._get_storage('segments') # pylint: disable=protected-access
|
|
63
64
|
self._events_storage = factory._get_storage('events') # pylint: disable=protected-access
|
|
64
|
-
self._evaluator = Evaluator(self._splitter)
|
|
65
|
+
self._evaluator = Evaluator(self._splitter, fallback_treatment_calculator)
|
|
65
66
|
self._telemetry_evaluation_producer = self._factory._telemetry_evaluation_producer
|
|
66
67
|
self._telemetry_init_producer = self._factory._telemetry_init_producer
|
|
68
|
+
self._fallback_treatment_calculator = fallback_treatment_calculator
|
|
67
69
|
|
|
68
70
|
@property
|
|
69
71
|
def ready(self):
|
|
@@ -203,11 +205,26 @@ class ClientBase(object): # pylint: disable=too-many-instance-attributes
|
|
|
203
205
|
def _get_properties(self, evaluation_options):
|
|
204
206
|
return evaluation_options.properties if evaluation_options != None else None
|
|
205
207
|
|
|
208
|
+
def _get_fallback_treatment_with_config(self, feature):
|
|
209
|
+
fallback_treatment = self._fallback_treatment_calculator.resolve(feature, "")
|
|
210
|
+
return fallback_treatment.treatment, fallback_treatment.config
|
|
211
|
+
|
|
212
|
+
def _get_fallback_eval_results(self, eval_result, feature):
|
|
213
|
+
result = copy.deepcopy(eval_result)
|
|
214
|
+
fallback_treatment = self._fallback_treatment_calculator.resolve(feature, result["impression"]["label"])
|
|
215
|
+
result["impression"]["label"] = fallback_treatment.label
|
|
216
|
+
result["treatment"] = fallback_treatment.treatment
|
|
217
|
+
result["configurations"] = fallback_treatment.config
|
|
218
|
+
|
|
219
|
+
return result
|
|
206
220
|
|
|
221
|
+
def _check_impression_label(self, result):
|
|
222
|
+
return result['impression']['label'] == None or (result['impression']['label'] != None and result['impression']['label'].find(Label.SPLIT_NOT_FOUND) == -1)
|
|
223
|
+
|
|
207
224
|
class Client(ClientBase): # pylint: disable=too-many-instance-attributes
|
|
208
225
|
"""Entry point for the split sdk."""
|
|
209
226
|
|
|
210
|
-
def __init__(self, factory, recorder, labels_enabled=True):
|
|
227
|
+
def __init__(self, factory, recorder, labels_enabled=True, fallback_treatment_calculator=None):
|
|
211
228
|
"""
|
|
212
229
|
Construct a Client instance.
|
|
213
230
|
|
|
@@ -222,7 +239,7 @@ class Client(ClientBase): # pylint: disable=too-many-instance-attributes
|
|
|
222
239
|
|
|
223
240
|
:rtype: Client
|
|
224
241
|
"""
|
|
225
|
-
ClientBase.__init__(self, factory, recorder, labels_enabled)
|
|
242
|
+
ClientBase.__init__(self, factory, recorder, labels_enabled, fallback_treatment_calculator)
|
|
226
243
|
self._context_factory = EvaluationDataFactory(factory._get_storage('splits'), factory._get_storage('segments'), factory._get_storage('rule_based_segments'))
|
|
227
244
|
|
|
228
245
|
def destroy(self):
|
|
@@ -254,10 +271,11 @@ class Client(ClientBase): # pylint: disable=too-many-instance-attributes
|
|
|
254
271
|
try:
|
|
255
272
|
treatment, _ = self._get_treatment(MethodExceptionsAndLatencies.TREATMENT, key, feature_flag_name, attributes, evaluation_options)
|
|
256
273
|
return treatment
|
|
257
|
-
|
|
274
|
+
|
|
258
275
|
except:
|
|
259
276
|
_LOGGER.error('get_treatment failed')
|
|
260
|
-
|
|
277
|
+
treatment, _ = self._get_fallback_treatment_with_config(feature_flag_name)
|
|
278
|
+
return treatment
|
|
261
279
|
|
|
262
280
|
def get_treatment_with_config(self, key, feature_flag_name, attributes=None, evaluation_options=None):
|
|
263
281
|
"""
|
|
@@ -282,8 +300,8 @@ class Client(ClientBase): # pylint: disable=too-many-instance-attributes
|
|
|
282
300
|
|
|
283
301
|
except Exception:
|
|
284
302
|
_LOGGER.error('get_treatment_with_config failed')
|
|
285
|
-
return
|
|
286
|
-
|
|
303
|
+
return self._get_fallback_treatment_with_config(feature_flag_name)
|
|
304
|
+
|
|
287
305
|
def _get_treatment(self, method, key, feature, attributes=None, evaluation_options=None):
|
|
288
306
|
"""
|
|
289
307
|
Validate key, feature flag name and object, and get the treatment and config with an optional dictionary of attributes.
|
|
@@ -302,7 +320,7 @@ class Client(ClientBase): # pylint: disable=too-many-instance-attributes
|
|
|
302
320
|
:rtype: dict
|
|
303
321
|
"""
|
|
304
322
|
if not self._client_is_usable(): # not destroyed & not waiting for a fork
|
|
305
|
-
return
|
|
323
|
+
return self._get_fallback_treatment_with_config(feature)
|
|
306
324
|
|
|
307
325
|
start = get_current_epoch_time_ms()
|
|
308
326
|
if not self.ready:
|
|
@@ -312,9 +330,10 @@ class Client(ClientBase): # pylint: disable=too-many-instance-attributes
|
|
|
312
330
|
try:
|
|
313
331
|
key, bucketing, feature, attributes, evaluation_options = self._validate_treatment_input(key, feature, attributes, method, evaluation_options)
|
|
314
332
|
except _InvalidInputError:
|
|
315
|
-
return
|
|
333
|
+
return self._get_fallback_treatment_with_config(feature)
|
|
316
334
|
|
|
317
|
-
result = self._NON_READY_EVAL_RESULT
|
|
335
|
+
result = self._get_fallback_eval_results(self._NON_READY_EVAL_RESULT, feature)
|
|
336
|
+
|
|
318
337
|
if self.ready:
|
|
319
338
|
try:
|
|
320
339
|
ctx = self._context_factory.context_for(key, [feature])
|
|
@@ -324,15 +343,15 @@ class Client(ClientBase): # pylint: disable=too-many-instance-attributes
|
|
|
324
343
|
_LOGGER.error('Error getting treatment for feature flag')
|
|
325
344
|
_LOGGER.debug('Error: ', exc_info=True)
|
|
326
345
|
self._telemetry_evaluation_producer.record_exception(method)
|
|
327
|
-
result = self._FAILED_EVAL_RESULT
|
|
346
|
+
result = self._get_fallback_eval_results(self._FAILED_EVAL_RESULT, feature)
|
|
328
347
|
|
|
329
348
|
properties = self._get_properties(evaluation_options)
|
|
330
|
-
if result
|
|
349
|
+
if self._check_impression_label(result):
|
|
331
350
|
impression_decorated = self._build_impression(key, bucketing, feature, result, properties)
|
|
332
351
|
self._record_stats([(impression_decorated, attributes)], start, method)
|
|
333
352
|
|
|
334
353
|
return result['treatment'], result['configurations']
|
|
335
|
-
|
|
354
|
+
|
|
336
355
|
def get_treatments(self, key, feature_flag_names, attributes=None, evaluation_options=None):
|
|
337
356
|
"""
|
|
338
357
|
Evaluate multiple feature flags and return a dictionary with all the feature flag/treatments.
|
|
@@ -356,7 +375,7 @@ class Client(ClientBase): # pylint: disable=too-many-instance-attributes
|
|
|
356
375
|
return {feature_flag: result[0] for (feature_flag, result) in with_config.items()}
|
|
357
376
|
|
|
358
377
|
except Exception:
|
|
359
|
-
return {feature:
|
|
378
|
+
return {feature: self._get_fallback_treatment_with_config(feature)[0] for feature in feature_flag_names}
|
|
360
379
|
|
|
361
380
|
def get_treatments_with_config(self, key, feature_flag_names, attributes=None, evaluation_options=None):
|
|
362
381
|
"""
|
|
@@ -380,7 +399,7 @@ class Client(ClientBase): # pylint: disable=too-many-instance-attributes
|
|
|
380
399
|
return self._get_treatments(key, feature_flag_names, MethodExceptionsAndLatencies.TREATMENTS_WITH_CONFIG, attributes, evaluation_options)
|
|
381
400
|
|
|
382
401
|
except Exception:
|
|
383
|
-
return {feature: (
|
|
402
|
+
return {feature: (self._get_fallback_treatment_with_config(feature)) for feature in feature_flag_names}
|
|
384
403
|
|
|
385
404
|
def get_treatments_by_flag_set(self, key, flag_set, attributes=None, evaluation_options=None):
|
|
386
405
|
"""
|
|
@@ -604,7 +623,7 @@ class Client(ClientBase): # pylint: disable=too-many-instance-attributes
|
|
|
604
623
|
"""
|
|
605
624
|
start = get_current_epoch_time_ms()
|
|
606
625
|
if not self._client_is_usable():
|
|
607
|
-
return input_validator.generate_control_treatments(features)
|
|
626
|
+
return input_validator.generate_control_treatments(features, self._fallback_treatment_calculator)
|
|
608
627
|
|
|
609
628
|
if not self.ready:
|
|
610
629
|
_LOGGER.error("Client is not ready - no calls possible")
|
|
@@ -613,9 +632,9 @@ class Client(ClientBase): # pylint: disable=too-many-instance-attributes
|
|
|
613
632
|
try:
|
|
614
633
|
key, bucketing, features, attributes, evaluation_options = self._validate_treatments_input(key, features, attributes, method, evaluation_options)
|
|
615
634
|
except _InvalidInputError:
|
|
616
|
-
return input_validator.generate_control_treatments(features)
|
|
635
|
+
return input_validator.generate_control_treatments(features, self._fallback_treatment_calculator)
|
|
617
636
|
|
|
618
|
-
results = {n: self._NON_READY_EVAL_RESULT for n in features}
|
|
637
|
+
results = {n: self._get_fallback_eval_results(self._NON_READY_EVAL_RESULT, n) for n in features}
|
|
619
638
|
if self.ready:
|
|
620
639
|
try:
|
|
621
640
|
ctx = self._context_factory.context_for(key, features)
|
|
@@ -625,12 +644,12 @@ class Client(ClientBase): # pylint: disable=too-many-instance-attributes
|
|
|
625
644
|
_LOGGER.error('Error getting treatment for feature flag')
|
|
626
645
|
_LOGGER.debug('Error: ', exc_info=True)
|
|
627
646
|
self._telemetry_evaluation_producer.record_exception(method)
|
|
628
|
-
results = {n: self._FAILED_EVAL_RESULT for n in features}
|
|
647
|
+
results = {n: self._get_fallback_eval_results(self._FAILED_EVAL_RESULT, n) for n in features}
|
|
629
648
|
|
|
630
649
|
properties = self._get_properties(evaluation_options)
|
|
631
650
|
imp_decorated_attrs = [
|
|
632
651
|
(i, attributes) for i in self._build_impressions(key, bucketing, results, properties)
|
|
633
|
-
if i.Impression.label != Label.SPLIT_NOT_FOUND
|
|
652
|
+
if i.Impression.label == None or (i.Impression.label != None and i.Impression.label.find(Label.SPLIT_NOT_FOUND)) == -1
|
|
634
653
|
]
|
|
635
654
|
self._record_stats(imp_decorated_attrs, start, method)
|
|
636
655
|
|
|
@@ -706,7 +725,7 @@ class Client(ClientBase): # pylint: disable=too-many-instance-attributes
|
|
|
706
725
|
class ClientAsync(ClientBase): # pylint: disable=too-many-instance-attributes
|
|
707
726
|
"""Entry point for the split sdk."""
|
|
708
727
|
|
|
709
|
-
def __init__(self, factory, recorder, labels_enabled=True):
|
|
728
|
+
def __init__(self, factory, recorder, labels_enabled=True, fallback_treatment_calculator=None):
|
|
710
729
|
"""
|
|
711
730
|
Construct a Client instance.
|
|
712
731
|
|
|
@@ -721,7 +740,7 @@ class ClientAsync(ClientBase): # pylint: disable=too-many-instance-attributes
|
|
|
721
740
|
|
|
722
741
|
:rtype: Client
|
|
723
742
|
"""
|
|
724
|
-
ClientBase.__init__(self, factory, recorder, labels_enabled)
|
|
743
|
+
ClientBase.__init__(self, factory, recorder, labels_enabled, fallback_treatment_calculator)
|
|
725
744
|
self._context_factory = AsyncEvaluationDataFactory(factory._get_storage('splits'), factory._get_storage('segments'), factory._get_storage('rule_based_segments'))
|
|
726
745
|
|
|
727
746
|
async def destroy(self):
|
|
@@ -756,7 +775,8 @@ class ClientAsync(ClientBase): # pylint: disable=too-many-instance-attributes
|
|
|
756
775
|
|
|
757
776
|
except:
|
|
758
777
|
_LOGGER.error('get_treatment failed')
|
|
759
|
-
|
|
778
|
+
treatment, _ = self._get_fallback_treatment_with_config(feature_flag_name)
|
|
779
|
+
return treatment
|
|
760
780
|
|
|
761
781
|
async def get_treatment_with_config(self, key, feature_flag_name, attributes=None, evaluation_options=None):
|
|
762
782
|
"""
|
|
@@ -781,7 +801,7 @@ class ClientAsync(ClientBase): # pylint: disable=too-many-instance-attributes
|
|
|
781
801
|
|
|
782
802
|
except Exception:
|
|
783
803
|
_LOGGER.error('get_treatment_with_config failed')
|
|
784
|
-
return
|
|
804
|
+
return self._get_fallback_treatment_with_config(feature_flag_name)
|
|
785
805
|
|
|
786
806
|
async def _get_treatment(self, method, key, feature, attributes=None, evaluation_options=None):
|
|
787
807
|
"""
|
|
@@ -801,7 +821,7 @@ class ClientAsync(ClientBase): # pylint: disable=too-many-instance-attributes
|
|
|
801
821
|
:rtype: dict
|
|
802
822
|
"""
|
|
803
823
|
if not self._client_is_usable(): # not destroyed & not waiting for a fork
|
|
804
|
-
return
|
|
824
|
+
return self._get_fallback_treatment_with_config(feature)
|
|
805
825
|
|
|
806
826
|
start = get_current_epoch_time_ms()
|
|
807
827
|
if not self.ready:
|
|
@@ -811,9 +831,9 @@ class ClientAsync(ClientBase): # pylint: disable=too-many-instance-attributes
|
|
|
811
831
|
try:
|
|
812
832
|
key, bucketing, feature, attributes, evaluation_options = self._validate_treatment_input(key, feature, attributes, method, evaluation_options)
|
|
813
833
|
except _InvalidInputError:
|
|
814
|
-
return
|
|
834
|
+
return self._get_fallback_treatment_with_config(feature)
|
|
815
835
|
|
|
816
|
-
result = self._NON_READY_EVAL_RESULT
|
|
836
|
+
result = self._get_fallback_eval_results(self._NON_READY_EVAL_RESULT, feature)
|
|
817
837
|
if self.ready:
|
|
818
838
|
try:
|
|
819
839
|
ctx = await self._context_factory.context_for(key, [feature])
|
|
@@ -823,10 +843,10 @@ class ClientAsync(ClientBase): # pylint: disable=too-many-instance-attributes
|
|
|
823
843
|
_LOGGER.error('Error getting treatment for feature flag')
|
|
824
844
|
_LOGGER.debug('Error: ', exc_info=True)
|
|
825
845
|
await self._telemetry_evaluation_producer.record_exception(method)
|
|
826
|
-
result = self._FAILED_EVAL_RESULT
|
|
846
|
+
result = self._get_fallback_eval_results(self._FAILED_EVAL_RESULT, feature)
|
|
827
847
|
|
|
828
848
|
properties = self._get_properties(evaluation_options)
|
|
829
|
-
if result
|
|
849
|
+
if self._check_impression_label(result):
|
|
830
850
|
impression_decorated = self._build_impression(key, bucketing, feature, result, properties)
|
|
831
851
|
await self._record_stats([(impression_decorated, attributes)], start, method)
|
|
832
852
|
return result['treatment'], result['configurations']
|
|
@@ -854,7 +874,7 @@ class ClientAsync(ClientBase): # pylint: disable=too-many-instance-attributes
|
|
|
854
874
|
return {feature_flag: result[0] for (feature_flag, result) in with_config.items()}
|
|
855
875
|
|
|
856
876
|
except Exception:
|
|
857
|
-
return {feature:
|
|
877
|
+
return {feature: self._get_fallback_treatment_with_config(feature)[0] for feature in feature_flag_names}
|
|
858
878
|
|
|
859
879
|
async def get_treatments_with_config(self, key, feature_flag_names, attributes=None, evaluation_options=None):
|
|
860
880
|
"""
|
|
@@ -878,8 +898,7 @@ class ClientAsync(ClientBase): # pylint: disable=too-many-instance-attributes
|
|
|
878
898
|
return await self._get_treatments(key, feature_flag_names, MethodExceptionsAndLatencies.TREATMENTS_WITH_CONFIG, attributes, evaluation_options)
|
|
879
899
|
|
|
880
900
|
except Exception:
|
|
881
|
-
|
|
882
|
-
return {feature: (CONTROL, None) for feature in feature_flag_names}
|
|
901
|
+
return {feature: (self._get_fallback_treatment_with_config(feature)) for feature in feature_flag_names}
|
|
883
902
|
|
|
884
903
|
async def get_treatments_by_flag_set(self, key, flag_set, attributes=None, evaluation_options=None):
|
|
885
904
|
"""
|
|
@@ -1017,7 +1036,7 @@ class ClientAsync(ClientBase): # pylint: disable=too-many-instance-attributes
|
|
|
1017
1036
|
"""
|
|
1018
1037
|
start = get_current_epoch_time_ms()
|
|
1019
1038
|
if not self._client_is_usable():
|
|
1020
|
-
return input_validator.generate_control_treatments(features)
|
|
1039
|
+
return input_validator.generate_control_treatments(features, self._fallback_treatment_calculator)
|
|
1021
1040
|
|
|
1022
1041
|
if not self.ready:
|
|
1023
1042
|
_LOGGER.error("Client is not ready - no calls possible")
|
|
@@ -1026,9 +1045,9 @@ class ClientAsync(ClientBase): # pylint: disable=too-many-instance-attributes
|
|
|
1026
1045
|
try:
|
|
1027
1046
|
key, bucketing, features, attributes, evaluation_options = self._validate_treatments_input(key, features, attributes, method, evaluation_options)
|
|
1028
1047
|
except _InvalidInputError:
|
|
1029
|
-
return input_validator.generate_control_treatments(features)
|
|
1048
|
+
return input_validator.generate_control_treatments(features, self._fallback_treatment_calculator)
|
|
1030
1049
|
|
|
1031
|
-
results = {n: self._NON_READY_EVAL_RESULT for n in features}
|
|
1050
|
+
results = {n: self._get_fallback_eval_results(self._NON_READY_EVAL_RESULT, n) for n in features}
|
|
1032
1051
|
if self.ready:
|
|
1033
1052
|
try:
|
|
1034
1053
|
ctx = await self._context_factory.context_for(key, features)
|
|
@@ -1038,12 +1057,12 @@ class ClientAsync(ClientBase): # pylint: disable=too-many-instance-attributes
|
|
|
1038
1057
|
_LOGGER.error('Error getting treatment for feature flag')
|
|
1039
1058
|
_LOGGER.debug('Error: ', exc_info=True)
|
|
1040
1059
|
await self._telemetry_evaluation_producer.record_exception(method)
|
|
1041
|
-
results = {n: self._FAILED_EVAL_RESULT for n in features}
|
|
1060
|
+
results = {n: self._get_fallback_eval_results(self._FAILED_EVAL_RESULT, n) for n in features}
|
|
1042
1061
|
|
|
1043
1062
|
properties = self._get_properties(evaluation_options)
|
|
1044
1063
|
imp_decorated_attrs = [
|
|
1045
1064
|
(i, attributes) for i in self._build_impressions(key, bucketing, results, properties)
|
|
1046
|
-
if i.Impression.label != Label.SPLIT_NOT_FOUND
|
|
1065
|
+
if i.Impression.label == None or (i.Impression.label != None and i.Impression.label.find(Label.SPLIT_NOT_FOUND)) == -1
|
|
1047
1066
|
]
|
|
1048
1067
|
await self._record_stats(imp_decorated_attrs, start, method)
|
|
1049
1068
|
|
|
@@ -4,7 +4,8 @@ import logging
|
|
|
4
4
|
from enum import Enum
|
|
5
5
|
|
|
6
6
|
from splitio.engine.impressions import ImpressionsMode
|
|
7
|
-
from splitio.client.input_validator import validate_flag_sets
|
|
7
|
+
from splitio.client.input_validator import validate_flag_sets, validate_fallback_treatment, validate_regex_name
|
|
8
|
+
from splitio.models.fallback_config import FallbackTreatmentsConfiguration
|
|
8
9
|
|
|
9
10
|
_LOGGER = logging.getLogger(__name__)
|
|
10
11
|
DEFAULT_DATA_SAMPLING = 1
|
|
@@ -69,7 +70,8 @@ DEFAULT_CONFIG = {
|
|
|
69
70
|
'flagSetsFilter': None,
|
|
70
71
|
'httpAuthenticateScheme': AuthenticateScheme.NONE,
|
|
71
72
|
'kerberosPrincipalUser': None,
|
|
72
|
-
'kerberosPrincipalPassword': None
|
|
73
|
+
'kerberosPrincipalPassword': None,
|
|
74
|
+
'fallbackTreatments': FallbackTreatmentsConfiguration(None)
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
def _parse_operation_mode(sdk_key, config):
|
|
@@ -168,4 +170,38 @@ def sanitize(sdk_key, config):
|
|
|
168
170
|
' Defaulting to `none` mode.')
|
|
169
171
|
processed["httpAuthenticateScheme"] = authenticate_scheme
|
|
170
172
|
|
|
173
|
+
processed = _sanitize_fallback_config(config, processed)
|
|
174
|
+
|
|
175
|
+
if config.get("redisErrors") is not None:
|
|
176
|
+
_LOGGER.warning('Parameter `redisErrors` is deprecated as it is no longer supported in redis lib.' \
|
|
177
|
+
' Will ignore this value.')
|
|
178
|
+
|
|
179
|
+
processed["redisErrors"] = None
|
|
171
180
|
return processed
|
|
181
|
+
|
|
182
|
+
def _sanitize_fallback_config(config, processed):
|
|
183
|
+
if config.get('fallbackTreatments') is None:
|
|
184
|
+
return processed
|
|
185
|
+
|
|
186
|
+
if not isinstance(config['fallbackTreatments'], FallbackTreatmentsConfiguration):
|
|
187
|
+
_LOGGER.warning('Config: fallbackTreatments parameter should be of `FallbackTreatmentsConfiguration` class.')
|
|
188
|
+
processed['fallbackTreatments'] = None
|
|
189
|
+
return processed
|
|
190
|
+
|
|
191
|
+
sanitized_global_fallback_treatment = config['fallbackTreatments'].global_fallback_treatment
|
|
192
|
+
if config['fallbackTreatments'].global_fallback_treatment is not None and not validate_fallback_treatment(config['fallbackTreatments'].global_fallback_treatment):
|
|
193
|
+
_LOGGER.warning('Config: global fallbacktreatment parameter is discarded.')
|
|
194
|
+
sanitized_global_fallback_treatment = None
|
|
195
|
+
|
|
196
|
+
sanitized_flag_fallback_treatments = {}
|
|
197
|
+
if config['fallbackTreatments'].by_flag_fallback_treatment is not None:
|
|
198
|
+
for feature_name in config['fallbackTreatments'].by_flag_fallback_treatment.keys():
|
|
199
|
+
if not validate_regex_name(feature_name) or not validate_fallback_treatment(config['fallbackTreatments'].by_flag_fallback_treatment[feature_name]):
|
|
200
|
+
_LOGGER.warning('Config: fallback treatment parameter for feature flag %s is discarded.', feature_name)
|
|
201
|
+
continue
|
|
202
|
+
|
|
203
|
+
sanitized_flag_fallback_treatments[feature_name] = config['fallbackTreatments'].by_flag_fallback_treatment[feature_name]
|
|
204
|
+
|
|
205
|
+
processed['fallbackTreatments'] = FallbackTreatmentsConfiguration(sanitized_global_fallback_treatment, sanitized_flag_fallback_treatments)
|
|
206
|
+
|
|
207
|
+
return processed
|
|
@@ -18,7 +18,7 @@ from splitio.engine.telemetry import TelemetryStorageProducer, TelemetryStorageC
|
|
|
18
18
|
TelemetryStorageProducerAsync, TelemetryStorageConsumerAsync
|
|
19
19
|
from splitio.engine.impressions.manager import Counter as ImpressionsCounter
|
|
20
20
|
from splitio.engine.impressions.unique_keys_tracker import UniqueKeysTracker, UniqueKeysTrackerAsync
|
|
21
|
-
|
|
21
|
+
from splitio.models.fallback_config import FallbackTreatmentCalculator
|
|
22
22
|
# Storage
|
|
23
23
|
from splitio.storage.inmemmory import InMemorySplitStorage, InMemorySegmentStorage, \
|
|
24
24
|
InMemoryImpressionStorage, InMemoryEventStorage, InMemoryTelemetryStorage, LocalhostTelemetryStorage, \
|
|
@@ -170,7 +170,8 @@ class SplitFactory(SplitFactoryBase): # pylint: disable=too-many-instance-attri
|
|
|
170
170
|
telemetry_producer=None,
|
|
171
171
|
telemetry_init_producer=None,
|
|
172
172
|
telemetry_submitter=None,
|
|
173
|
-
preforked_initialization=False
|
|
173
|
+
preforked_initialization=False,
|
|
174
|
+
fallback_treatment_calculator=None
|
|
174
175
|
):
|
|
175
176
|
"""
|
|
176
177
|
Class constructor.
|
|
@@ -201,6 +202,7 @@ class SplitFactory(SplitFactoryBase): # pylint: disable=too-many-instance-attri
|
|
|
201
202
|
self._ready_time = get_current_epoch_time_ms()
|
|
202
203
|
_LOGGER.debug("Running in threading mode")
|
|
203
204
|
self._sdk_internal_ready_flag = sdk_ready_flag
|
|
205
|
+
self._fallback_treatment_calculator = fallback_treatment_calculator
|
|
204
206
|
self._start_status_updater()
|
|
205
207
|
|
|
206
208
|
def _start_status_updater(self):
|
|
@@ -242,7 +244,7 @@ class SplitFactory(SplitFactoryBase): # pylint: disable=too-many-instance-attri
|
|
|
242
244
|
This client is only a set of references to structures hold by the factory.
|
|
243
245
|
Creating one a fast operation and safe to be used anywhere.
|
|
244
246
|
"""
|
|
245
|
-
return Client(self, self._recorder, self._labels_enabled)
|
|
247
|
+
return Client(self, self._recorder, self._labels_enabled, self._fallback_treatment_calculator)
|
|
246
248
|
|
|
247
249
|
def manager(self):
|
|
248
250
|
"""
|
|
@@ -338,7 +340,8 @@ class SplitFactoryAsync(SplitFactoryBase): # pylint: disable=too-many-instance-
|
|
|
338
340
|
telemetry_init_producer=None,
|
|
339
341
|
telemetry_submitter=None,
|
|
340
342
|
manager_start_task=None,
|
|
341
|
-
api_client=None
|
|
343
|
+
api_client=None,
|
|
344
|
+
fallback_treatment_calculator=None
|
|
342
345
|
):
|
|
343
346
|
"""
|
|
344
347
|
Class constructor.
|
|
@@ -372,6 +375,7 @@ class SplitFactoryAsync(SplitFactoryBase): # pylint: disable=too-many-instance-
|
|
|
372
375
|
self._sdk_ready_flag = asyncio.Event()
|
|
373
376
|
self._ready_task = asyncio.get_running_loop().create_task(self._update_status_when_ready_async())
|
|
374
377
|
self._api_client = api_client
|
|
378
|
+
self._fallback_treatment_calculator = fallback_treatment_calculator
|
|
375
379
|
|
|
376
380
|
async def _update_status_when_ready_async(self):
|
|
377
381
|
"""Wait until the sdk is ready and update the status for async mode."""
|
|
@@ -460,7 +464,7 @@ class SplitFactoryAsync(SplitFactoryBase): # pylint: disable=too-many-instance-
|
|
|
460
464
|
This client is only a set of references to structures hold by the factory.
|
|
461
465
|
Creating one a fast operation and safe to be used anywhere.
|
|
462
466
|
"""
|
|
463
|
-
return ClientAsync(self, self._recorder, self._labels_enabled)
|
|
467
|
+
return ClientAsync(self, self._recorder, self._labels_enabled, self._fallback_treatment_calculator)
|
|
464
468
|
|
|
465
469
|
def _wrap_impression_listener(listener, metadata):
|
|
466
470
|
"""
|
|
@@ -623,7 +627,8 @@ def _build_in_memory_factory(api_key, cfg, sdk_url=None, events_url=None, # pyl
|
|
|
623
627
|
synchronizer._split_synchronizers._segment_sync.shutdown()
|
|
624
628
|
|
|
625
629
|
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_treatment_calculator=FallbackTreatmentCalculator(cfg['fallbackTreatments']))
|
|
627
632
|
|
|
628
633
|
initialization_thread = threading.Thread(target=manager.start, name="SDKInitializer", daemon=True)
|
|
629
634
|
initialization_thread.start()
|
|
@@ -631,7 +636,7 @@ def _build_in_memory_factory(api_key, cfg, sdk_url=None, events_url=None, # pyl
|
|
|
631
636
|
return SplitFactory(api_key, storages, cfg['labelsEnabled'],
|
|
632
637
|
recorder, manager, sdk_ready_flag,
|
|
633
638
|
telemetry_producer, telemetry_init_producer,
|
|
634
|
-
telemetry_submitter)
|
|
639
|
+
telemetry_submitter, fallback_treatment_calculator = FallbackTreatmentCalculator(cfg['fallbackTreatments']))
|
|
635
640
|
|
|
636
641
|
async def _build_in_memory_factory_async(api_key, cfg, sdk_url=None, events_url=None, # pylint:disable=too-many-arguments,too-many-localsa
|
|
637
642
|
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=
|
|
|
750
755
|
recorder, manager,
|
|
751
756
|
telemetry_producer, telemetry_init_producer,
|
|
752
757
|
telemetry_submitter, manager_start_task=manager_start_task,
|
|
753
|
-
api_client=http_client)
|
|
758
|
+
api_client=http_client, fallback_treatment_calculator=FallbackTreatmentCalculator(cfg['fallbackTreatments']))
|
|
754
759
|
|
|
755
760
|
def _build_redis_factory(api_key, cfg):
|
|
756
761
|
"""Build and return a split factory with redis-based storage."""
|
|
@@ -828,7 +833,8 @@ def _build_redis_factory(api_key, cfg):
|
|
|
828
833
|
manager,
|
|
829
834
|
sdk_ready_flag=None,
|
|
830
835
|
telemetry_producer=telemetry_producer,
|
|
831
|
-
telemetry_init_producer=telemetry_init_producer
|
|
836
|
+
telemetry_init_producer=telemetry_init_producer,
|
|
837
|
+
fallback_treatment_calculator=FallbackTreatmentCalculator(cfg['fallbackTreatments'])
|
|
832
838
|
)
|
|
833
839
|
redundant_factory_count, active_factory_count = _get_active_and_redundant_count()
|
|
834
840
|
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):
|
|
|
910
916
|
manager,
|
|
911
917
|
telemetry_producer=telemetry_producer,
|
|
912
918
|
telemetry_init_producer=telemetry_init_producer,
|
|
913
|
-
telemetry_submitter=telemetry_submitter
|
|
919
|
+
telemetry_submitter=telemetry_submitter,
|
|
920
|
+
fallback_treatment_calculator=FallbackTreatmentCalculator(cfg['fallbackTreatments'])
|
|
914
921
|
)
|
|
915
922
|
redundant_factory_count, active_factory_count = _get_active_and_redundant_count()
|
|
916
923
|
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):
|
|
|
992
999
|
manager,
|
|
993
1000
|
sdk_ready_flag=None,
|
|
994
1001
|
telemetry_producer=telemetry_producer,
|
|
995
|
-
telemetry_init_producer=telemetry_init_producer
|
|
1002
|
+
telemetry_init_producer=telemetry_init_producer,
|
|
1003
|
+
fallback_treatment_calculator=FallbackTreatmentCalculator(cfg['fallbackTreatments'])
|
|
996
1004
|
)
|
|
997
1005
|
redundant_factory_count, active_factory_count = _get_active_and_redundant_count()
|
|
998
1006
|
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):
|
|
|
1072
1080
|
manager,
|
|
1073
1081
|
telemetry_producer=telemetry_producer,
|
|
1074
1082
|
telemetry_init_producer=telemetry_init_producer,
|
|
1075
|
-
telemetry_submitter=telemetry_submitter
|
|
1083
|
+
telemetry_submitter=telemetry_submitter,
|
|
1084
|
+
fallback_treatment_calculator=FallbackTreatmentCalculator(cfg['fallbackTreatments'])
|
|
1076
1085
|
)
|
|
1077
1086
|
redundant_factory_count, active_factory_count = _get_active_and_redundant_count()
|
|
1078
1087
|
await storages['telemetry'].record_active_and_redundant_factories(active_factory_count, redundant_factory_count)
|
|
@@ -1150,6 +1159,7 @@ def _build_localhost_factory(cfg):
|
|
|
1150
1159
|
telemetry_producer=telemetry_producer,
|
|
1151
1160
|
telemetry_init_producer=telemetry_producer.get_telemetry_init_producer(),
|
|
1152
1161
|
telemetry_submitter=LocalhostTelemetrySubmitter(),
|
|
1162
|
+
fallback_treatment_calculator=FallbackTreatmentCalculator(cfg['fallbackTreatments'])
|
|
1153
1163
|
)
|
|
1154
1164
|
|
|
1155
1165
|
async def _build_localhost_factory_async(cfg):
|
|
@@ -1220,7 +1230,8 @@ async def _build_localhost_factory_async(cfg):
|
|
|
1220
1230
|
telemetry_producer=telemetry_producer,
|
|
1221
1231
|
telemetry_init_producer=telemetry_producer.get_telemetry_init_producer(),
|
|
1222
1232
|
telemetry_submitter=LocalhostTelemetrySubmitterAsync(),
|
|
1223
|
-
manager_start_task=manager_start_task
|
|
1233
|
+
manager_start_task=manager_start_task,
|
|
1234
|
+
fallback_treatment_calculator=FallbackTreatmentCalculator(cfg['fallbackTreatments'])
|
|
1224
1235
|
)
|
|
1225
1236
|
|
|
1226
1237
|
def get_factory(api_key, **kwargs):
|
|
@@ -8,6 +8,7 @@ import inspect
|
|
|
8
8
|
from splitio.client.key import Key
|
|
9
9
|
from splitio.client import client
|
|
10
10
|
from splitio.engine.evaluator import CONTROL
|
|
11
|
+
from splitio.models.fallback_treatment import FallbackTreatment
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
_LOGGER = logging.getLogger(__name__)
|
|
@@ -15,7 +16,8 @@ MAX_LENGTH = 250
|
|
|
15
16
|
EVENT_TYPE_PATTERN = r'^[a-zA-Z0-9][-_.:a-zA-Z0-9]{0,79}$'
|
|
16
17
|
MAX_PROPERTIES_LENGTH_BYTES = 32768
|
|
17
18
|
_FLAG_SETS_REGEX = '^[a-z0-9][_a-z0-9]{0,49}$'
|
|
18
|
-
|
|
19
|
+
_FALLBACK_TREATMENT_REGEX = '^[0-9]+[.a-zA-Z0-9_-]*$|^[a-zA-Z]+[a-zA-Z0-9_-]*$'
|
|
20
|
+
_FALLBACK_TREATMENT_SIZE = 100
|
|
19
21
|
|
|
20
22
|
def _check_not_null(value, name, operation):
|
|
21
23
|
"""
|
|
@@ -500,7 +502,7 @@ def validate_feature_flags_get_treatments( # pylint: disable=invalid-name
|
|
|
500
502
|
valid_feature_flags.append(ff)
|
|
501
503
|
return valid_feature_flags
|
|
502
504
|
|
|
503
|
-
def generate_control_treatments(feature_flags):
|
|
505
|
+
def generate_control_treatments(feature_flags, fallback_treatment_calculator):
|
|
504
506
|
"""
|
|
505
507
|
Generate valid feature flags to control.
|
|
506
508
|
|
|
@@ -515,7 +517,11 @@ def generate_control_treatments(feature_flags):
|
|
|
515
517
|
to_return = {}
|
|
516
518
|
for feature_flag in feature_flags:
|
|
517
519
|
if isinstance(feature_flag, str) and len(feature_flag.strip())> 0:
|
|
518
|
-
|
|
520
|
+
fallback_treatment = fallback_treatment_calculator.resolve(feature_flag, "")
|
|
521
|
+
treatment = fallback_treatment.treatment
|
|
522
|
+
config = fallback_treatment.config
|
|
523
|
+
|
|
524
|
+
to_return[feature_flag] = (treatment, config)
|
|
519
525
|
return to_return
|
|
520
526
|
|
|
521
527
|
|
|
@@ -712,3 +718,28 @@ def validate_flag_sets(flag_sets, method_name):
|
|
|
712
718
|
sanitized_flag_sets.add(flag_set)
|
|
713
719
|
|
|
714
720
|
return list(sanitized_flag_sets)
|
|
721
|
+
|
|
722
|
+
def validate_fallback_treatment(fallback_treatment):
|
|
723
|
+
if not isinstance(fallback_treatment, FallbackTreatment):
|
|
724
|
+
_LOGGER.warning("Config: Fallback treatment instance should be FallbackTreatment, input is discarded")
|
|
725
|
+
return False
|
|
726
|
+
|
|
727
|
+
if not isinstance(fallback_treatment.treatment, str):
|
|
728
|
+
_LOGGER.warning("Config: Fallback treatment value should be str type, input is discarded")
|
|
729
|
+
return False
|
|
730
|
+
|
|
731
|
+
if not validate_regex_name(fallback_treatment.treatment):
|
|
732
|
+
_LOGGER.warning("Config: Fallback treatment should match regex %s", _FALLBACK_TREATMENT_REGEX)
|
|
733
|
+
return False
|
|
734
|
+
|
|
735
|
+
if len(fallback_treatment.treatment) > _FALLBACK_TREATMENT_SIZE:
|
|
736
|
+
_LOGGER.warning("Config: Fallback treatment size should not exceed %s characters", _FALLBACK_TREATMENT_SIZE)
|
|
737
|
+
return False
|
|
738
|
+
|
|
739
|
+
return True
|
|
740
|
+
|
|
741
|
+
def validate_regex_name(name):
|
|
742
|
+
if re.match(_FALLBACK_TREATMENT_REGEX, name) == None:
|
|
743
|
+
return False
|
|
744
|
+
|
|
745
|
+
return True
|
|
@@ -20,7 +20,7 @@ _LOGGER = logging.getLogger(__name__)
|
|
|
20
20
|
class Evaluator(object): # pylint: disable=too-few-public-methods
|
|
21
21
|
"""Split Evaluator class."""
|
|
22
22
|
|
|
23
|
-
def __init__(self, splitter):
|
|
23
|
+
def __init__(self, splitter, fallback_treatment_calculator=None):
|
|
24
24
|
"""
|
|
25
25
|
Construct a Evaluator instance.
|
|
26
26
|
|
|
@@ -28,6 +28,7 @@ class Evaluator(object): # pylint: disable=too-few-public-methods
|
|
|
28
28
|
:type splitter: splitio.engine.splitters.Splitters
|
|
29
29
|
"""
|
|
30
30
|
self._splitter = splitter
|
|
31
|
+
self._fallback_treatment_calculator = fallback_treatment_calculator
|
|
31
32
|
|
|
32
33
|
def eval_many_with_context(self, key, bucketing, features, attrs, ctx):
|
|
33
34
|
"""
|
|
@@ -51,6 +52,10 @@ class Evaluator(object): # pylint: disable=too-few-public-methods
|
|
|
51
52
|
if not feature:
|
|
52
53
|
_LOGGER.warning('Unknown or invalid feature: %s', feature)
|
|
53
54
|
label = Label.SPLIT_NOT_FOUND
|
|
55
|
+
fallback_treatment = self._fallback_treatment_calculator.resolve(feature_name, label)
|
|
56
|
+
label = fallback_treatment.label
|
|
57
|
+
_treatment = fallback_treatment.treatment
|
|
58
|
+
config = fallback_treatment.config
|
|
54
59
|
else:
|
|
55
60
|
_change_number = feature.change_number
|
|
56
61
|
if feature.killed:
|
|
@@ -59,17 +64,18 @@ class Evaluator(object): # pylint: disable=too-few-public-methods
|
|
|
59
64
|
else:
|
|
60
65
|
label, _treatment = self._check_prerequisites(feature, bucketing, key, attrs, ctx, label, _treatment)
|
|
61
66
|
label, _treatment = self._get_treatment(feature, bucketing, key, attrs, ctx, label, _treatment)
|
|
67
|
+
config = feature.get_configurations_for(_treatment)
|
|
62
68
|
|
|
63
69
|
return {
|
|
64
70
|
'treatment': _treatment,
|
|
65
|
-
'configurations':
|
|
71
|
+
'configurations': config,
|
|
66
72
|
'impression': {
|
|
67
73
|
'label': label,
|
|
68
74
|
'change_number': _change_number
|
|
69
75
|
},
|
|
70
76
|
'impressions_disabled': feature.impressions_disabled if feature else None
|
|
71
77
|
}
|
|
72
|
-
|
|
78
|
+
|
|
73
79
|
def _get_treatment(self, feature, bucketing, key, attrs, ctx, label, _treatment):
|
|
74
80
|
if _treatment == CONTROL:
|
|
75
81
|
treatment, label = self._treatment_for_flag(feature, key, bucketing, attrs, ctx)
|