Skip to content

Commit 5d814b5

Browse files
committed
added events manager and events delivery
1 parent c65fd01 commit 5d814b5

File tree

7 files changed

+357
-50
lines changed

7 files changed

+357
-50
lines changed

splitio/events/__init__.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
"""Base storage interfaces."""
2+
import abc
3+
4+
class EventsManagerInterface(object, metaclass=abc.ABCMeta):
5+
"""Events manager interface implemented as an abstract class."""
6+
7+
@abc.abstractmethod
8+
def register(self, sdk_event, event_handler):
9+
pass
10+
11+
@abc.abstractmethod
12+
def unregister(self, sdk_event):
13+
pass
14+
15+
@abc.abstractmethod
16+
def notify_internal_event(self, sdk_internal_event, event_metadata):
17+
pass
18+
19+
20+
class EventsDeliveryInterface(object, metaclass=abc.ABCMeta):
21+
"""Events Delivery interface."""
22+
23+
@abc.abstractmethod
24+
def deliver(self, sdk_event, event_metadata, event_handler):
25+
pass

splitio/events/events_delivery.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""Events Manager."""
2+
import logging
3+
4+
from splitio.events import EventsDeliveryInterface
5+
6+
_LOGGER = logging.getLogger(__name__)
7+
8+
class EventsDelivery(EventsDeliveryInterface):
9+
"""Events Manager class."""
10+
11+
def __init__(self):
12+
"""
13+
Construct Events Manager instance.
14+
"""
15+
16+
def deliver(self, sdk_event, event_metadata, event_handler):
17+
try:
18+
event_handler(event_metadata)
19+
except Exception as ex:
20+
_LOGGER.error("Exception when calling handler for Sdk Event %s", sdk_event)
21+
_LOGGER.error(ex)

splitio/events/events_manager.py

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
"""Events Manager."""
2+
import threading
3+
import logging
4+
from collections import namedtuple
5+
import pytest
6+
7+
from splitio.events import EventsManagerInterface
8+
9+
_LOGGER = logging.getLogger(__name__)
10+
11+
ValidSdkEvent = namedtuple('ValidSdkEvent', ['sdk_event', 'valid'])
12+
ActiveSubscriptions = namedtuple('ActiveSubscriptions', ['triggered', 'handler'])
13+
14+
class EventsManager(EventsManagerInterface):
15+
"""Events Manager class."""
16+
17+
def __init__(self, events_configurations, events_delivery):
18+
"""
19+
Construct Events Manager instance.
20+
"""
21+
self._active_subscriptions = {}
22+
self._internal_events_status = {}
23+
self._events_delivery = events_delivery
24+
self._manager_config = events_configurations
25+
self._lock = threading.RLock()
26+
27+
def register(self, sdk_event, event_handler):
28+
if self._active_subscriptions.get(sdk_event) != None:
29+
return
30+
31+
with self._lock:
32+
self._active_subscriptions[sdk_event] = ActiveSubscriptions(False, event_handler)
33+
34+
def unregister(self, sdk_event):
35+
if self._active_subscriptions.get(sdk_event) == None:
36+
return
37+
38+
with self._lock:
39+
del self._active_subscriptions[sdk_event]
40+
41+
def notify_internal_event(self, sdk_internal_event, event_metadata):
42+
with self._lock:
43+
for sorted_event in self._manager_config.evaluation_order:
44+
if sorted_event in self._get_sdk_event_if_applicable(sdk_internal_event):
45+
_LOGGER.debug("EventsManager: Firing Sdk event %s", sorted_event)
46+
if self._get_event_handler(sorted_event) != None:
47+
notify_event = threading.Thread(target=self._events_delivery.deliver, args=[sorted_event, event_metadata, self._get_event_handler(sorted_event)],
48+
name='SplitSDKEventNotify', daemon=True)
49+
notify_event.start()
50+
self._set_sdk_event_triggered(sorted_event)
51+
52+
def _event_already_triggered(self, sdk_event):
53+
if self._active_subscriptions.get(sdk_event) != None:
54+
return self._active_subscriptions.get(sdk_event).triggered
55+
56+
return False
57+
58+
def _get_internal_event_status(self, sdk_internal_event):
59+
if self._internal_events_status.get(sdk_internal_event) != None:
60+
return self._internal_events_status[sdk_internal_event]
61+
62+
return False
63+
64+
def _update_internal_event_status(self, sdk_internal_event, status):
65+
with self._lock:
66+
self._internal_events_status[sdk_internal_event] = status
67+
68+
def _set_sdk_event_triggered(self, sdk_event):
69+
if self._active_subscriptions.get(sdk_event) == None:
70+
return
71+
72+
if self._active_subscriptions.get(sdk_event).triggered == True:
73+
return
74+
75+
self._active_subscriptions[sdk_event] = self._active_subscriptions[sdk_event]._replace(triggered = True)
76+
77+
def _get_event_handler(self, sdk_event):
78+
if self._active_subscriptions.get(sdk_event) == None:
79+
return None
80+
81+
return self._active_subscriptions.get(sdk_event).handler
82+
83+
def _get_sdk_event_if_applicable(self, sdk_internal_event):
84+
final_sdk_event = ValidSdkEvent(None, False)
85+
self._update_internal_event_status(sdk_internal_event, True)
86+
87+
events_to_fire = []
88+
require_any_sdk_event = self._check_require_any(sdk_internal_event)
89+
if require_any_sdk_event.valid:
90+
if (not self._set_sdk_event_triggered(require_any_sdk_event.sdk_event) and
91+
self._execution_limit(require_any_sdk_event.sdk_event) == 1) or \
92+
self._execution_limit(require_any_sdk_event.sdk_event) == -1:
93+
final_sdk_event = final_sdk_event._replace(sdk_event = require_any_sdk_event.sdk_event,
94+
valid = self._check_prerequisites(require_any_sdk_event.sdk_event) and \
95+
self._check_suppressed_by(require_any_sdk_event.sdk_event))
96+
97+
if final_sdk_event.valid:
98+
events_to_fire.append(final_sdk_event.sdk_event)
99+
100+
[events_to_fire.append(sdk_event) for sdk_event in self._check_require_all()]
101+
102+
return events_to_fire
103+
104+
def _check_require_all(self):
105+
events = []
106+
for require_name, require_value in self._manager_config.require_all.items():
107+
final_status = True
108+
for val in require_value:
109+
final_status &= self._get_internal_event_status(val)
110+
111+
if final_status and \
112+
self._check_prerequisites(require_name) and \
113+
((not self._event_already_triggered(require_name) and
114+
self._execution_limit(require_name) == 1) or \
115+
self._execution_limit(require_name) == -1) and \
116+
len(require_value) > 0:
117+
118+
events.append(require_name)
119+
120+
return events
121+
122+
def _check_prerequisites(self, sdk_event):
123+
for name, value in self._manager_config.prerequisites.items():
124+
for val in value:
125+
if name == sdk_event and not self._event_already_triggered(val):
126+
return False
127+
128+
return True
129+
130+
def _check_suppressed_by(self, sdk_event):
131+
for name, value in self._manager_config.suppressed_by.items():
132+
for val in value:
133+
if name == sdk_event and self._event_already_triggered(val):
134+
return False
135+
136+
return True
137+
138+
def _execution_limit(self, sdk_event):
139+
limit = self._manager_config.execution_limits.get(sdk_event)
140+
if limit == None:
141+
return -1
142+
143+
return limit
144+
145+
def _check_require_any(self, sdk_internal_event):
146+
valid_sdk_event = ValidSdkEvent(None, False)
147+
for name, val in self._manager_config.require_any.items():
148+
if sdk_internal_event in val:
149+
valid_sdk_event = valid_sdk_event._replace(valid = True, sdk_event = name)
150+
return valid_sdk_event
151+
152+
return valid_sdk_event

splitio/events/events_metadata.py

Lines changed: 22 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,35 @@
11
"""Events Metadata."""
2-
from splitio.models.events import SdkEvent, SdkInternalEvent
2+
from enum import Enum
3+
4+
class SdkEventType(Enum):
5+
"""Public event types"""
6+
7+
FLAG_UPDATE = 'FLAG_UPDATE'
8+
SEGMENT_UPDATE = 'SEGMENT_UPDATE'
39

410
class EventsMetadata(object):
511
"""Events Metadata class."""
612

7-
def __init__(self, metadata):
13+
def __init__(self, type, names):
814
"""
915
Construct Events Metadata instance.
1016
"""
11-
self._metadata = self._sanitize(metadata)
17+
self._type = type
18+
self._names = self._sanitize(names)
1219

13-
def get_data(self):
14-
"""Return metadata dict"""
15-
return self._metadata
20+
def get_type(self):
21+
"""Return type"""
22+
return self._type
1623

17-
def get_keys(self):
18-
"""Return metadata dict keys"""
19-
return self._metadata.keys()
20-
21-
def get_values(self):
22-
"""Return metadata dict values"""
23-
return self._metadata.values()
24-
25-
def contain_key(self, key):
26-
"""Return True if key is contained in metadata"""
27-
return key in self._metadata.keys()
24+
def get_names(self):
25+
"""Return names"""
26+
return self._names
2827

29-
def _sanitize(self, data):
30-
"""Return sanitized metadata dict with values either int, bool, str or list """
31-
santized_data = {}
32-
for item_name, item_value in data.items():
33-
if self._value_is_valid(item_value):
34-
santized_data[item_name] = item_value
28+
def _sanitize(self, names):
29+
"""Return sanitized names list with values str"""
30+
santized_data = set()
31+
for name in names:
32+
if isinstance(name, str):
33+
santized_data.add(name)
3534

3635
return santized_data
37-
38-
def _value_is_valid(self, value):
39-
"""Return bool if values is int, bool, str or list[str] """
40-
if (value is not None) and (isinstance(value, int) or isinstance(value, bool) or isinstance(value, str)):
41-
return True
42-
43-
if isinstance(value, set):
44-
return any([isinstance(item, str) for item in value])
45-
46-
return False
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"""EventsManager test module."""
2+
from splitio.models.events import SdkEvent, SdkInternalEvent
3+
from splitio.events.events_metadata import EventsMetadata
4+
from splitio.events.events_delivery import EventsDelivery
5+
from splitio.events.events_metadata import SdkEventType
6+
7+
class EventsDeliveryTests(object):
8+
"""Tests for EventsManager."""
9+
10+
sdk_ready_flag = False
11+
metadata = None
12+
13+
def test_firing_events(self):
14+
events_delivery = EventsDelivery()
15+
16+
metadata = EventsMetadata(SdkEventType.FLAG_UPDATE, { "feature1" })
17+
events_delivery.deliver(SdkEvent.SDK_READY, metadata, self._sdk_ready_callback)
18+
assert self.sdk_ready_flag
19+
self._verify_metadata(metadata)
20+
21+
def _sdk_ready_callback(self, metadata):
22+
self.sdk_ready_flag = True
23+
self.metadata = metadata
24+
25+
def _verify_metadata(self, metadata):
26+
assert metadata.get_type() == self.metadata.get_type()
27+
assert metadata.get_names() == self.metadata.get_names()

0 commit comments

Comments
 (0)