Skip to content

Commit 538fd29

Browse files
authored
Merge pull request #138 from splitio/feature/input-validation-v2-traffic-types
Feature/input validation v2 traffic types
2 parents 4546046 + e86c00b commit 538fd29

File tree

13 files changed

+435
-87
lines changed

13 files changed

+435
-87
lines changed

splitio/client/client.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,13 @@ def track(self, key, traffic_type, event_type, value=None):
337337

338338
key = input_validator.validate_track_key(key)
339339
event_type = input_validator.validate_event_type(event_type)
340-
traffic_type = input_validator.validate_traffic_type(traffic_type)
340+
should_validate_existance = self.ready and self._factory._apikey != 'localhost' #pylint: disable=protected-access
341+
traffic_type = input_validator.validate_traffic_type(
342+
traffic_type,
343+
should_validate_existance,
344+
self._factory._get_storage('splits'), #pylint: disable=protected-access
345+
)
346+
341347
value = input_validator.validate_value(value)
342348

343349
if key is None or event_type is None or traffic_type is None or value is False:

splitio/client/input_validator.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,12 +288,16 @@ def validate_track_key(key):
288288
return key_str
289289

290290

291-
def validate_traffic_type(traffic_type):
291+
def validate_traffic_type(traffic_type, should_validate_existance, split_storage):
292292
"""
293293
Check if traffic_type is valid for track.
294294
295295
:param traffic_type: traffic_type to be checked
296296
:type traffic_type: str
297+
:param should_validate_existance: Whether to check for existante in the split storage.
298+
:type should_validate_existance: bool
299+
:param split_storage: Split storage.
300+
:param split_storage: splitio.storages.SplitStorage
297301
:return: traffic_type
298302
:rtype: str|None
299303
"""
@@ -305,6 +309,15 @@ def validate_traffic_type(traffic_type):
305309
_LOGGER.warning('track: %s should be all lowercase - converting string to lowercase.',
306310
traffic_type)
307311
traffic_type = traffic_type.lower()
312+
313+
if should_validate_existance and not split_storage.is_valid_traffic_type(traffic_type):
314+
_LOGGER.warning(
315+
'track: Traffic Type %s does not have any corresponding Splits in this environment, '
316+
'make sure you\'re tracking your events to a valid traffic type defined '
317+
'in the Split console.',
318+
traffic_type
319+
)
320+
308321
return traffic_type
309322

310323

splitio/storage/__init__.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,19 @@ def get_all_splits(self):
8383
"""
8484
pass
8585

86+
@abc.abstractmethod
87+
def is_valid_traffic_type(self, traffic_type_name):
88+
"""
89+
Return whether the traffic type exists in at least one split in cache.
90+
91+
:param traffic_type_name: Traffic type to validate.
92+
:type traffic_type_name: str
93+
94+
:return: True if the traffic type is valid. False otherwise.
95+
:rtype: bool
96+
"""
97+
pass
98+
8699
def get_segment_names(self):
87100
"""
88101
Return a set of all segments referenced by splits in storage.

splitio/storage/inmemmory.py

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
import logging
55
import threading
6+
from collections import Counter
7+
68
from six.moves import queue
79
from splitio.models.segments import Segment
810
from splitio.storage import SplitStorage, SegmentStorage, ImpressionStorage, EventStorage, \
@@ -18,6 +20,7 @@ def __init__(self):
1820
self._lock = threading.RLock()
1921
self._splits = {}
2022
self._change_number = -1
23+
self._traffic_types = Counter()
2124

2225
def get(self, split_name):
2326
"""
@@ -40,6 +43,7 @@ def put(self, split):
4043
"""
4144
with self._lock:
4245
self._splits[split.name] = split
46+
self._increase_traffic_type_count(split.traffic_type_name)
4347

4448
def remove(self, split_name):
4549
"""
@@ -52,13 +56,15 @@ def remove(self, split_name):
5256
:rtype: bool
5357
"""
5458
with self._lock:
55-
try:
56-
self._splits.pop(split_name)
57-
return True
58-
except KeyError:
59+
split = self._splits.get(split_name)
60+
if not split:
5961
self._logger.warning("Tried to delete nonexistant split %s. Skipping", split_name)
6062
return False
6163

64+
self._splits.pop(split_name)
65+
self._decrease_traffic_type_count(split.traffic_type_name)
66+
return True
67+
6268
def get_change_number(self):
6369
"""
6470
Retrieve latest split change number.
@@ -98,6 +104,38 @@ def get_all_splits(self):
98104
with self._lock:
99105
return list(self._splits.values())
100106

107+
def is_valid_traffic_type(self, traffic_type_name):
108+
"""
109+
Return whether the traffic type exists in at least one split in cache.
110+
111+
:param traffic_type_name: Traffic type to validate.
112+
:type traffic_type_name: str
113+
114+
:return: True if the traffic type is valid. False otherwise.
115+
:rtype: bool
116+
"""
117+
with self._lock:
118+
return traffic_type_name in self._traffic_types
119+
120+
def _increase_traffic_type_count(self, traffic_type_name):
121+
"""
122+
Increase by one the count for a specific traffic type name.
123+
124+
:param traffic_type_name: Traffic type to increase the count.
125+
:type traffic_type_name: str
126+
"""
127+
self._traffic_types.update([traffic_type_name])
128+
129+
def _decrease_traffic_type_count(self, traffic_type_name):
130+
"""
131+
Decrease by one the count for a specific traffic type name.
132+
133+
:param traffic_type_name: Traffic type to decrease the count.
134+
:type traffic_type_name: str
135+
"""
136+
self._traffic_types.subtract([traffic_type_name])
137+
self._traffic_types += Counter()
138+
101139

102140
class InMemorySegmentStorage(SegmentStorage):
103141
"""In-memory implementation of a segment storage."""

splitio/storage/redis.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class RedisSplitStorage(SplitStorage):
1616

1717
_SPLIT_KEY = 'SPLITIO.split.{split_name}'
1818
_SPLIT_TILL_KEY = 'SPLITIO.splits.till'
19+
_TRAFFIC_TYPE_KEY = 'SPLITIO.trafficType.{traffic_type_name}'
1920

2021
def __init__(self, redis_client):
2122
"""
@@ -39,6 +40,18 @@ def _get_key(self, split_name):
3940
"""
4041
return self._SPLIT_KEY.format(split_name=split_name)
4142

43+
def _get_traffic_type_key(self, traffic_type_name):
44+
"""
45+
Use the provided split_name to build the appropriate redis key.
46+
47+
:param split_name: Name of the split to interact with in redis.
48+
:type split_name: str
49+
50+
:return: Redis key.
51+
:rtype: str.
52+
"""
53+
return self._TRAFFIC_TYPE_KEY.format(traffic_type_name=traffic_type_name)
54+
4255
def get(self, split_name):
4356
"""
4457
Retrieve a split.
@@ -57,6 +70,25 @@ def get(self, split_name):
5770
self._logger.debug('Error: ', exc_info=True)
5871
return None
5972

73+
def is_valid_traffic_type(self, traffic_type_name):
74+
"""
75+
Return whether the traffic type exists in at least one split in cache.
76+
77+
:param traffic_type_name: Traffic type to validate.
78+
:type traffic_type_name: str
79+
80+
:return: True if the traffic type is valid. False otherwise.
81+
:rtype: bool
82+
"""
83+
try:
84+
raw = self._redis.get(self._get_traffic_type_key(traffic_type_name))
85+
count = json.loads(raw) if raw else 0
86+
return count > 0
87+
except RedisAdapterException:
88+
self._logger.error('Error fetching split from storage')
89+
self._logger.debug('Error: ', exc_info=True)
90+
return False
91+
6092
def put(self, split):
6193
"""
6294
Store a split.

0 commit comments

Comments
 (0)