splitio-client 10.4.0__tar.gz → 10.5.1__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.
Files changed (126) hide show
  1. {splitio_client-10.4.0 → splitio_client-10.5.1}/PKG-INFO +2 -2
  2. {splitio_client-10.4.0 → splitio_client-10.5.1}/setup.py +1 -1
  3. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/client/client.py +57 -38
  4. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/client/config.py +38 -2
  5. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/client/factory.py +24 -13
  6. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/client/input_validator.py +34 -3
  7. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/client/util.py +1 -1
  8. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/engine/evaluator.py +9 -3
  9. splitio_client-10.5.1/splitio/models/fallback_config.py +100 -0
  10. splitio_client-10.5.1/splitio/models/fallback_treatment.py +34 -0
  11. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/push/splitsse.py +1 -1
  12. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/push/sse.py +1 -1
  13. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/storage/adapters/redis.py +0 -2
  14. splitio_client-10.5.1/splitio/version.py +1 -0
  15. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio_client.egg-info/PKG-INFO +2 -2
  16. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio_client.egg-info/SOURCES.txt +2 -0
  17. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio_client.egg-info/requires.txt +1 -1
  18. splitio_client-10.4.0/splitio/version.py +0 -1
  19. {splitio_client-10.4.0 → splitio_client-10.5.1}/LICENSE.txt +0 -0
  20. {splitio_client-10.4.0 → splitio_client-10.5.1}/README.md +0 -0
  21. {splitio_client-10.4.0 → splitio_client-10.5.1}/setup.cfg +0 -0
  22. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/__init__.py +0 -0
  23. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/api/__init__.py +0 -0
  24. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/api/auth.py +0 -0
  25. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/api/client.py +0 -0
  26. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/api/commons.py +0 -0
  27. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/api/events.py +0 -0
  28. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/api/impressions.py +0 -0
  29. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/api/segments.py +0 -0
  30. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/api/splits.py +0 -0
  31. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/api/telemetry.py +0 -0
  32. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/client/__init__.py +0 -0
  33. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/client/key.py +0 -0
  34. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/client/listener.py +0 -0
  35. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/client/localhost.py +0 -0
  36. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/client/manager.py +0 -0
  37. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/engine/__init__.py +0 -0
  38. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/engine/cache/__init__.py +0 -0
  39. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/engine/cache/lru.py +0 -0
  40. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/engine/filters.py +0 -0
  41. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/engine/hashfns/__init__.py +0 -0
  42. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/engine/hashfns/legacy.py +0 -0
  43. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/engine/hashfns/murmur3py.py +0 -0
  44. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/engine/impressions/__init__.py +0 -0
  45. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/engine/impressions/adapters.py +0 -0
  46. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/engine/impressions/impressions.py +0 -0
  47. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/engine/impressions/manager.py +0 -0
  48. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/engine/impressions/strategies.py +0 -0
  49. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/engine/impressions/unique_keys_tracker.py +0 -0
  50. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/engine/splitters.py +0 -0
  51. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/engine/telemetry.py +0 -0
  52. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/exceptions.py +0 -0
  53. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/factories.py +0 -0
  54. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/impressions.py +0 -0
  55. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/key.py +0 -0
  56. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/models/__init__.py +0 -0
  57. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/models/datatypes.py +0 -0
  58. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/models/events.py +0 -0
  59. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/models/grammar/__init__.py +0 -0
  60. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/models/grammar/condition.py +0 -0
  61. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/models/grammar/matchers/__init__.py +0 -0
  62. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/models/grammar/matchers/base.py +0 -0
  63. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/models/grammar/matchers/keys.py +0 -0
  64. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/models/grammar/matchers/misc.py +0 -0
  65. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/models/grammar/matchers/numeric.py +0 -0
  66. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/models/grammar/matchers/prerequisites.py +0 -0
  67. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/models/grammar/matchers/rule_based_segment.py +0 -0
  68. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/models/grammar/matchers/semver.py +0 -0
  69. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/models/grammar/matchers/sets.py +0 -0
  70. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/models/grammar/matchers/string.py +0 -0
  71. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/models/grammar/matchers/utils/__init__.py +0 -0
  72. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/models/grammar/matchers/utils/utils.py +0 -0
  73. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/models/grammar/partitions.py +0 -0
  74. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/models/impressions.py +0 -0
  75. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/models/notification.py +0 -0
  76. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/models/rule_based_segments.py +0 -0
  77. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/models/segments.py +0 -0
  78. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/models/splits.py +0 -0
  79. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/models/telemetry.py +0 -0
  80. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/models/token.py +0 -0
  81. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/optional/__init__.py +0 -0
  82. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/optional/loaders.py +0 -0
  83. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/push/__init__.py +0 -0
  84. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/push/manager.py +0 -0
  85. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/push/parser.py +0 -0
  86. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/push/processor.py +0 -0
  87. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/push/status_tracker.py +0 -0
  88. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/push/workers.py +0 -0
  89. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/recorder/__init__.py +0 -0
  90. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/recorder/recorder.py +0 -0
  91. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/spec.py +0 -0
  92. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/storage/__init__.py +0 -0
  93. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/storage/adapters/__init__.py +0 -0
  94. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/storage/adapters/cache_trait.py +0 -0
  95. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/storage/adapters/util.py +0 -0
  96. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/storage/inmemmory.py +0 -0
  97. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/storage/pluggable.py +0 -0
  98. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/storage/redis.py +0 -0
  99. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/sync/__init__.py +0 -0
  100. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/sync/event.py +0 -0
  101. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/sync/impression.py +0 -0
  102. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/sync/manager.py +0 -0
  103. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/sync/segment.py +0 -0
  104. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/sync/split.py +0 -0
  105. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/sync/synchronizer.py +0 -0
  106. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/sync/telemetry.py +0 -0
  107. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/sync/unique_keys.py +0 -0
  108. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/sync/util.py +0 -0
  109. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/tasks/__init__.py +0 -0
  110. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/tasks/events_sync.py +0 -0
  111. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/tasks/impressions_sync.py +0 -0
  112. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/tasks/segment_sync.py +0 -0
  113. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/tasks/split_sync.py +0 -0
  114. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/tasks/telemetry_sync.py +0 -0
  115. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/tasks/unique_keys_sync.py +0 -0
  116. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/tasks/util/__init__.py +0 -0
  117. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/tasks/util/asynctask.py +0 -0
  118. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/tasks/util/workerpool.py +0 -0
  119. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/util/__init__.py +0 -0
  120. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/util/backoff.py +0 -0
  121. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/util/decorators.py +0 -0
  122. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/util/storage_helper.py +0 -0
  123. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/util/threadutil.py +0 -0
  124. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio/util/time.py +0 -0
  125. {splitio_client-10.4.0 → splitio_client-10.5.1}/splitio_client.egg-info/dependency_links.txt +0 -0
  126. {splitio_client-10.4.0 → splitio_client-10.5.1}/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.4.0
3
+ Version: 10.5.1
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.4.0
6
+ Download-URL: https://github.com/splitio/python-client/tarball/10.5.1
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
- return CONTROL
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 CONTROL, None
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 CONTROL, None
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 CONTROL, None
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['impression']['label'] != Label.SPLIT_NOT_FOUND:
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: CONTROL for feature in feature_flag_names}
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: (CONTROL, None) for feature in feature_flag_names}
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
- return CONTROL
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 CONTROL, None
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 CONTROL, None
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 CONTROL, None
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['impression']['label'] != Label.SPLIT_NOT_FOUND:
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: CONTROL for feature in feature_flag_names}
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
- _LOGGER.error("AA", exc_info=True)
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
- to_return[feature_flag] = (CONTROL, None)
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
@@ -50,4 +50,4 @@ def get_metadata(config):
50
50
  """
51
51
  version = 'python-%s' % __version__
52
52
  ip_address, hostname = _get_hostname_and_ip(config)
53
- return SdkMetadata(version, hostname, ip_address)
53
+ return SdkMetadata(version, hostname, ip_address)
@@ -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': feature.get_configurations_for(_treatment) if feature else None,
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)