Skip to content

Commit baea887

Browse files
authored
Merge branch 'Feature/Async' into async-sync-synchronizer
2 parents 7bb8065 + 0a87c82 commit baea887

File tree

15 files changed

+1734
-216
lines changed

15 files changed

+1734
-216
lines changed

splitio/client/config.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@
5858
'dataSampling': DEFAULT_DATA_SAMPLING,
5959
'storageWrapper': None,
6060
'storagePrefix': None,
61-
'storageType': None
61+
'storageType': None,
62+
'parallelTasksRunMode': 'threading',
6263
}
6364

6465

@@ -143,4 +144,8 @@ def sanitize(sdk_key, config):
143144
_LOGGER.warning('metricRefreshRate parameter minimum value is 60 seconds, defaulting to 3600 seconds.')
144145
processed['metricsRefreshRate'] = 3600
145146

147+
if processed['parallelTasksRunMode'] not in ['threading', 'asyncio']:
148+
_LOGGER.warning('parallelTasksRunMode parameter value must be either `threading` or `asyncio`, defaulting to `threading`.')
149+
processed['parallelTasksRunMode'] = 'threading'
150+
146151
return processed

splitio/client/input_validator.py

Lines changed: 158 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,14 @@ def validate_key(key, method_name):
232232
return matching_key_result, bucketing_key_result
233233

234234

235+
def _validate_feature_flag_name(feature_flag_name, method_name):
236+
if (not _check_not_null(feature_flag_name, 'feature_flag_name', method_name)) or \
237+
(not _check_is_string(feature_flag_name, 'feature_flag_name', method_name)) or \
238+
(not _check_string_not_empty(feature_flag_name, 'feature_flag_name', method_name)):
239+
return False
240+
return True
241+
242+
235243
def validate_feature_flag_name(feature_flag_name, should_validate_existance, feature_flag_storage, method_name):
236244
"""
237245
Check if feature flag name is valid for get_treatment.
@@ -241,9 +249,7 @@ def validate_feature_flag_name(feature_flag_name, should_validate_existance, fea
241249
:return: feature_flag_name
242250
:rtype: str|None
243251
"""
244-
if (not _check_not_null(feature_flag_name, 'feature_flag_name', method_name)) or \
245-
(not _check_is_string(feature_flag_name, 'feature_flag_name', method_name)) or \
246-
(not _check_string_not_empty(feature_flag_name, 'feature_flag_name', method_name)):
252+
if not _validate_feature_flag_name(feature_flag_name, method_name):
247253
return None
248254

249255
if should_validate_existance and feature_flag_storage.get(feature_flag_name) is None:
@@ -258,6 +264,30 @@ def validate_feature_flag_name(feature_flag_name, should_validate_existance, fea
258264
return _remove_empty_spaces(feature_flag_name, method_name)
259265

260266

267+
async def validate_feature_flag_name_async(feature_flag_name, should_validate_existance, feature_flag_storage, method_name):
268+
"""
269+
Check if feature flag name is valid for get_treatment.
270+
271+
:param feature_flag_name: feature flag name to be checked
272+
:type feature_flag_name: str
273+
:return: feature_flag_name
274+
:rtype: str|None
275+
"""
276+
if not _validate_feature_flag_name(feature_flag_name, method_name):
277+
return None
278+
279+
if should_validate_existance and await feature_flag_storage.get(feature_flag_name) is None:
280+
_LOGGER.warning(
281+
"%s: you passed \"%s\" that does not exist in this environment, "
282+
"please double check what Feature flags exist in the Split user interface.",
283+
method_name,
284+
feature_flag_name
285+
)
286+
return None
287+
288+
return _remove_empty_spaces(feature_flag_name, method_name)
289+
290+
261291
def validate_track_key(key):
262292
"""
263293
Check if key is valid for track.
@@ -277,6 +307,21 @@ def validate_track_key(key):
277307
return key_str
278308

279309

310+
def _validate_traffic_type_value(traffic_type):
311+
if (not _check_not_null(traffic_type, 'traffic_type', 'track')) or \
312+
(not _check_is_string(traffic_type, 'traffic_type', 'track')) or \
313+
(not _check_string_not_empty(traffic_type, 'traffic_type', 'track')):
314+
return False
315+
return True
316+
317+
def _convert_traffic_type_case(traffic_type):
318+
if not traffic_type.islower():
319+
_LOGGER.warning('track: %s should be all lowercase - converting string to lowercase.',
320+
traffic_type)
321+
return traffic_type.lower()
322+
return traffic_type
323+
324+
280325
def validate_traffic_type(traffic_type, should_validate_existance, feature_flag_storage):
281326
"""
282327
Check if traffic_type is valid for track.
@@ -290,14 +335,9 @@ def validate_traffic_type(traffic_type, should_validate_existance, feature_flag_
290335
:return: traffic_type
291336
:rtype: str|None
292337
"""
293-
if (not _check_not_null(traffic_type, 'traffic_type', 'track')) or \
294-
(not _check_is_string(traffic_type, 'traffic_type', 'track')) or \
295-
(not _check_string_not_empty(traffic_type, 'traffic_type', 'track')):
338+
if not _validate_traffic_type_value(traffic_type):
296339
return None
297-
if not traffic_type.islower():
298-
_LOGGER.warning('track: %s should be all lowercase - converting string to lowercase.',
299-
traffic_type)
300-
traffic_type = traffic_type.lower()
340+
traffic_type = _convert_traffic_type_case(traffic_type)
301341

302342
if should_validate_existance and not feature_flag_storage.is_valid_traffic_type(traffic_type):
303343
_LOGGER.warning(
@@ -310,6 +350,34 @@ def validate_traffic_type(traffic_type, should_validate_existance, feature_flag_
310350
return traffic_type
311351

312352

353+
async def validate_traffic_type_async(traffic_type, should_validate_existance, feature_flag_storage):
354+
"""
355+
Check if traffic_type is valid for track.
356+
357+
:param traffic_type: traffic_type to be checked
358+
:type traffic_type: str
359+
:param should_validate_existance: Whether to check for existante in the feature flag storage.
360+
:type should_validate_existance: bool
361+
:param feature_flag_storage: Feature flag storage.
362+
:param feature_flag_storage: splitio.storages.SplitStorage
363+
:return: traffic_type
364+
:rtype: str|None
365+
"""
366+
if not _validate_traffic_type_value(traffic_type):
367+
return None
368+
traffic_type = _convert_traffic_type_case(traffic_type)
369+
370+
if should_validate_existance and not await feature_flag_storage.is_valid_traffic_type(traffic_type):
371+
_LOGGER.warning(
372+
'track: Traffic Type %s does not have any corresponding Feature flags in this environment, '
373+
'make sure you\'re tracking your events to a valid traffic type defined '
374+
'in the Split user interface.',
375+
traffic_type
376+
)
377+
378+
return traffic_type
379+
380+
313381
def validate_event_type(event_type):
314382
"""
315383
Check if event_type is valid for track.
@@ -353,9 +421,7 @@ def validate_manager_feature_flag_name(feature_flag_name, should_validate_exista
353421
:return: feature_flag_name
354422
:rtype: str|None
355423
"""
356-
if (not _check_not_null(feature_flag_name, 'feature_flag_name', 'split')) or \
357-
(not _check_is_string(feature_flag_name, 'feature_flag_name', 'split')) or \
358-
(not _check_string_not_empty(feature_flag_name, 'feature_flag_name', 'split')):
424+
if not _validate_feature_flag_name(feature_flag_name, 'split'):
359425
return None
360426

361427
if should_validate_existance and feature_flag_storage.get(feature_flag_name) is None:
@@ -369,6 +435,47 @@ def validate_manager_feature_flag_name(feature_flag_name, should_validate_exista
369435
return feature_flag_name
370436

371437

438+
async def validate_manager_feature_flag_name_async(feature_flag_name, should_validate_existance, feature_flag_storage):
439+
"""
440+
Check if feature flag name is valid for track.
441+
442+
:param feature_flag_name: feature flag name to be checked
443+
:type feature_flag_name: str
444+
:return: feature_flag_name
445+
:rtype: str|None
446+
"""
447+
if not _validate_feature_flag_name(feature_flag_name, 'split'):
448+
return None
449+
450+
if should_validate_existance and await feature_flag_storage.get(feature_flag_name) is None:
451+
_LOGGER.warning(
452+
"split: you passed \"%s\" that does not exist in this environment, "
453+
"please double check what Feature flags exist in the Split user interface.",
454+
feature_flag_name
455+
)
456+
return None
457+
458+
return feature_flag_name
459+
460+
def _check_feature_flag_instance(feature_flags, method_name):
461+
if feature_flags is None or not isinstance(feature_flags, list):
462+
_LOGGER.error("%s: feature flag names must be a non-empty array.", method_name)
463+
return False
464+
if not feature_flags:
465+
_LOGGER.error("%s: feature flag names must be a non-empty array.", method_name)
466+
return False
467+
return True
468+
469+
470+
def _get_filtered_feature_flag(feature_flags, method_name):
471+
return set(
472+
_remove_empty_spaces(feature_flag, method_name) for feature_flag in feature_flags
473+
if feature_flag is not None and
474+
_check_is_string(feature_flag, 'feature flag name', method_name) and
475+
_check_string_not_empty(feature_flag, 'feature flag name', method_name)
476+
)
477+
478+
372479
def validate_feature_flags_get_treatments( # pylint: disable=invalid-name
373480
method_name,
374481
feature_flags,
@@ -383,26 +490,54 @@ def validate_feature_flags_get_treatments( # pylint: disable=invalid-name
383490
:return: filtered_feature_flags
384491
:rtype: tuple
385492
"""
386-
if feature_flags is None or not isinstance(feature_flags, list):
387-
_LOGGER.error("%s: feature flag names must be a non-empty array.", method_name)
493+
if not _check_feature_flag_instance(feature_flags, method_name):
388494
return None, None
389-
if not feature_flags:
495+
496+
filtered_feature_flags = _get_filtered_feature_flag(feature_flags, method_name)
497+
if not filtered_feature_flags:
390498
_LOGGER.error("%s: feature flag names must be a non-empty array.", method_name)
391499
return None, None
392-
filtered_feature_flags = set(
393-
_remove_empty_spaces(feature_flag, method_name) for feature_flag in feature_flags
394-
if feature_flag is not None and
395-
_check_is_string(feature_flag, 'feature flag name', method_name) and
396-
_check_string_not_empty(feature_flag, 'feature flag name', method_name)
397-
)
500+
501+
if not should_validate_existance:
502+
return filtered_feature_flags, []
503+
504+
valid_missing_feature_flags = set(f for f in filtered_feature_flags if feature_flag_storage.get(f) is None)
505+
for missing_feature_flag in valid_missing_feature_flags:
506+
_LOGGER.warning(
507+
"%s: you passed \"%s\" that does not exist in this environment, "
508+
"please double check what Feature flags exist in the Split user interface.",
509+
method_name,
510+
missing_feature_flag
511+
)
512+
return filtered_feature_flags - valid_missing_feature_flags, valid_missing_feature_flags
513+
514+
515+
async def validate_feature_flags_get_treatments_async( # pylint: disable=invalid-name
516+
method_name,
517+
feature_flags,
518+
should_validate_existance=False,
519+
feature_flag_storage=None
520+
):
521+
"""
522+
Check if feature flags is valid for get_treatments.
523+
524+
:param feature_flags: array of feature flags
525+
:type feature_flags: list
526+
:return: filtered_feature_flags
527+
:rtype: tuple
528+
"""
529+
if not _check_feature_flag_instance(feature_flags, method_name):
530+
return None, None
531+
532+
filtered_feature_flags = _get_filtered_feature_flag(feature_flags, method_name)
398533
if not filtered_feature_flags:
399534
_LOGGER.error("%s: feature flag names must be a non-empty array.", method_name)
400535
return None, None
401536

402537
if not should_validate_existance:
403538
return filtered_feature_flags, []
404539

405-
valid_missing_feature_flags = set(f for f in filtered_feature_flags if feature_flag_storage.get(f) is None)
540+
valid_missing_feature_flags = set(f for f in filtered_feature_flags if await feature_flag_storage.get(f) is None)
406541
for missing_feature_flag in valid_missing_feature_flags:
407542
_LOGGER.warning(
408543
"%s: you passed \"%s\" that does not exist in this environment, "

splitio/client/manager.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,100 @@ def split(self, feature_name):
102102

103103
split = self._storage.get(feature_name)
104104
return split.to_split_view() if split is not None else None
105+
106+
107+
class SplitManagerAsync(object):
108+
"""Split Manager. Gives insights on data cached by splits."""
109+
110+
def __init__(self, factory):
111+
"""
112+
Class constructor.
113+
114+
:param factory: Factory containing all storage references.
115+
:type factory: splitio.client.factory.SplitFactory
116+
"""
117+
self._factory = factory
118+
self._storage = factory._get_storage('splits') # pylint: disable=protected-access
119+
self._telemetry_init_producer = factory._telemetry_init_producer
120+
121+
async def split_names(self):
122+
"""
123+
Get the name of fetched splits.
124+
125+
:return: A list of str
126+
:rtype: list
127+
"""
128+
if self._factory.destroyed:
129+
_LOGGER.error("Client has already been destroyed - no calls possible.")
130+
return []
131+
if self._factory._waiting_fork():
132+
_LOGGER.error("Client is not ready - no calls possible")
133+
return []
134+
135+
if not self._factory.ready:
136+
await self._telemetry_init_producer.record_not_ready_usage()
137+
_LOGGER.warning(
138+
"split_names: The SDK is not ready, results may be incorrect. "
139+
"Make sure to wait for SDK readiness before using this method"
140+
)
141+
142+
return await self._storage.get_split_names()
143+
144+
async def splits(self):
145+
"""
146+
Get the fetched splits. Subclasses need to override this method.
147+
148+
:return: A List of SplitView.
149+
:rtype: list()
150+
"""
151+
if self._factory.destroyed:
152+
_LOGGER.error("Client has already been destroyed - no calls possible.")
153+
return []
154+
if self._factory._waiting_fork():
155+
_LOGGER.error("Client is not ready - no calls possible")
156+
return []
157+
158+
if not self._factory.ready:
159+
await self._telemetry_init_producer.record_not_ready_usage()
160+
_LOGGER.warning(
161+
"splits: The SDK is not ready, results may be incorrect. "
162+
"Make sure to wait for SDK readiness before using this method"
163+
)
164+
165+
return [split.to_split_view() for split in await self._storage.get_all_splits()]
166+
167+
async def split(self, feature_name):
168+
"""
169+
Get the splitView of feature_name. Subclasses need to override this method.
170+
171+
:param feature_name: Name of the feture to retrieve.
172+
:type feature_name: str
173+
174+
:return: The SplitView instance.
175+
:rtype: splitio.models.splits.SplitView
176+
"""
177+
if self._factory.destroyed:
178+
_LOGGER.error("Client has already been destroyed - no calls possible.")
179+
return None
180+
if self._factory._waiting_fork():
181+
_LOGGER.error("Client is not ready - no calls possible")
182+
return None
183+
184+
feature_name = await input_validator.validate_manager_feature_flag_name_async(
185+
feature_name,
186+
self._factory.ready,
187+
self._storage
188+
)
189+
190+
if not self._factory.ready:
191+
await self._telemetry_init_producer.record_not_ready_usage()
192+
_LOGGER.warning(
193+
"split: The SDK is not ready, results may be incorrect. "
194+
"Make sure to wait for SDK readiness before using this method"
195+
)
196+
197+
if feature_name is None:
198+
return None
199+
200+
split = await self._storage.get(feature_name)
201+
return split.to_split_view() if split is not None else None

0 commit comments

Comments
 (0)