From c65fd014fa7d971e50c1ac6c5da81ddb91aaddc7 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Wed, 7 Jan 2026 13:17:53 -0800 Subject: [PATCH 1/2] added models, events config data and events metadata --- splitio/events/__init__.py | 0 splitio/events/events_manager_config.py | 124 +++++++++++++++++++++ splitio/events/events_metadata.py | 46 ++++++++ splitio/models/events.py | 22 +++- tests/events/test_events_manager_config.py | 43 +++++++ tests/events/test_events_metadata.py | 28 +++++ 6 files changed, 262 insertions(+), 1 deletion(-) create mode 100644 splitio/events/__init__.py create mode 100644 splitio/events/events_manager_config.py create mode 100644 splitio/events/events_metadata.py create mode 100644 tests/events/test_events_manager_config.py create mode 100644 tests/events/test_events_metadata.py diff --git a/splitio/events/__init__.py b/splitio/events/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/splitio/events/events_manager_config.py b/splitio/events/events_manager_config.py new file mode 100644 index 00000000..891d17a5 --- /dev/null +++ b/splitio/events/events_manager_config.py @@ -0,0 +1,124 @@ +"""Events Manager Configuration.""" +from splitio.models.events import SdkEvent, SdkInternalEvent + +class EventsManagerConfig(object): + """Events Manager Configurations class.""" + + def __init__(self): + """ + Construct Events Manager Configuration instance. + """ + self._require_all = self._get_require_all() + self._prerequisites = self._get_prerequisites() + self._require_any = self._get_require_any() + self._suppressed_by = self._get_suppressed_by() + self._execution_limits = self._get_execution_limits() + self._evaluation_order = self._get_sorted_events() + + @property + def require_all(self): + """Return require all dict""" + return self._require_all + + @property + def prerequisites(self): + """Return prerequisites dict""" + return self._prerequisites + + @property + def require_any(self): + """Return require_any dict""" + return self._require_any + + @property + def suppressed_by(self): + """Return suppressed_by dict""" + return self._suppressed_by + + @property + def execution_limits(self): + """Return execution_limits dict""" + return self._execution_limits + + @property + def prerequisites(self): + """Return require all dict""" + return self._prerequisites + + @property + def evaluation_order(self): + """Return evaluation_order dict""" + return self._evaluation_order + + @property + def sorted_events(self): + """Return sorted_events dict""" + return self._sorted_events + + def _get_require_all(self): + """Return require all dict""" + return { + SdkEvent.SDK_READY: {SdkInternalEvent.SDK_READY} + } + + def _get_prerequisites(self): + """Return prerequisites dict""" + return { + SdkEvent.SDK_UPDATE: {SdkEvent.SDK_READY} + } + + def _get_require_any(self): + """Return require_any dict""" + return { + SdkEvent.SDK_UPDATE: {SdkInternalEvent.FLAG_KILLED_NOTIFICATION, SdkInternalEvent.FLAGS_UPDATED, + SdkInternalEvent.RB_SEGMENTS_UPDATED, SdkInternalEvent.SEGMENTS_UPDATED}, + SdkEvent.SDK_READY_TIMED_OUT: {SdkInternalEvent.SDK_TIMED_OUT} + } + + def _get_suppressed_by(self): + """Return suppressed_by dict""" + return { + SdkEvent.SDK_READY_TIMED_OUT: {SdkEvent.SDK_READY} + } + + def _get_execution_limits(self): + """Return execution_limits dict""" + return { + SdkEvent.SDK_READY: 1, + SdkEvent.SDK_READY_TIMED_OUT: -1, + SdkEvent.SDK_UPDATE: -1 + } + + def _get_sorted_events(self): + """Return dorted events set""" + sorted_events = [] + for sdk_event in [SdkEvent.SDK_READY, SdkEvent.SDK_READY_TIMED_OUT, SdkEvent.SDK_UPDATE]: + sorted_events = self._dfs_recursive(sdk_event, sorted_events) + + return sorted_events + + + def _dfs_recursive(self, sdk_event, added): + """Return sorted events set based on the dependency rules""" + if sdk_event in added: + return added + + for dependent_event in self._get_dependencies(sdk_event): + added = self._dfs_recursive(dependent_event, added) + + added.append(sdk_event) + return added + + def _get_dependencies(self, sdk_event): + """Return dependencies set from prerequisites and suppressed events for a given event""" + dependencies = set() + for prerequisites_event_name, prerequisites_event_value in self.prerequisites.items(): + if prerequisites_event_name == sdk_event: + for prereq_event in prerequisites_event_value: + dependencies.add(prereq_event) + + for suppressed_event_name, suppressed_event_value in self.suppressed_by.items(): + if sdk_event in suppressed_event_value: + dependencies.add(suppressed_event_name) + + return dependencies diff --git a/splitio/events/events_metadata.py b/splitio/events/events_metadata.py new file mode 100644 index 00000000..3e024b66 --- /dev/null +++ b/splitio/events/events_metadata.py @@ -0,0 +1,46 @@ +"""Events Metadata.""" +from splitio.models.events import SdkEvent, SdkInternalEvent + +class EventsMetadata(object): + """Events Metadata class.""" + + def __init__(self, metadata): + """ + Construct Events Metadata instance. + """ + self._metadata = self._sanitize(metadata) + + def get_data(self): + """Return metadata dict""" + return self._metadata + + def get_keys(self): + """Return metadata dict keys""" + return self._metadata.keys() + + def get_values(self): + """Return metadata dict values""" + return self._metadata.values() + + def contain_key(self, key): + """Return True if key is contained in metadata""" + return key in self._metadata.keys() + + def _sanitize(self, data): + """Return sanitized metadata dict with values either int, bool, str or list """ + santized_data = {} + for item_name, item_value in data.items(): + if self._value_is_valid(item_value): + santized_data[item_name] = item_value + + return santized_data + + def _value_is_valid(self, value): + """Return bool if values is int, bool, str or list[str] """ + if (value is not None) and (isinstance(value, int) or isinstance(value, bool) or isinstance(value, str)): + return True + + if isinstance(value, set): + return any([isinstance(item, str) for item in value]) + + return False \ No newline at end of file diff --git a/splitio/models/events.py b/splitio/models/events.py index b924417b..efcd3ef1 100644 --- a/splitio/models/events.py +++ b/splitio/models/events.py @@ -4,7 +4,7 @@ The dto is implemented as a namedtuple for performance matters. """ from collections import namedtuple - +from enum import Enum Event = namedtuple('Event', [ 'key', @@ -19,3 +19,23 @@ 'event', 'size', ]) + +class SdkEvent(Enum): + """Public SDK events""" + + SDK_READY = 'SDK_READY' + SDK_READY_TIMED_OUT = 'SDK_READY_TIMED_OUT' + SDK_UPDATE = 'SDK_UPDATE' + +class SdkInternalEvent(Enum): + """Internal SDK events""" + + SDK_READY = 'SDK_READY' + SDK_TIMED_OUT = 'SDK_TIMED_OUT' + FLAGS_UPDATED = 'FLAGS_UPDATED' + FLAG_KILLED_NOTIFICATION = 'FLAG_KILLED_NOTIFICATION' + SEGMENTS_UPDATED = 'SEGMENTS_UPDATED' + RB_SEGMENTS_UPDATED = 'RB_SEGMENTS_UPDATED' + LARGE_SEGMENTS_UPDATED = 'LARGE_SEGMENTS_UPDATED' + + diff --git a/tests/events/test_events_manager_config.py b/tests/events/test_events_manager_config.py new file mode 100644 index 00000000..5c9748c0 --- /dev/null +++ b/tests/events/test_events_manager_config.py @@ -0,0 +1,43 @@ +"""EventsManagerConfig test module.""" +import pytest + +from splitio.events.events_manager_config import EventsManagerConfig +from splitio.models.events import SdkEvent, SdkInternalEvent + +class EventsManagerConfigTests(object): + """Tests for EventsManagerConfig.""" + + def test_build_instance(self): + config = EventsManagerConfig() + + assert len(config.require_all[SdkEvent.SDK_READY]) == 1 + assert SdkInternalEvent.SDK_READY in config.require_all[SdkEvent.SDK_READY] + + assert SdkEvent.SDK_READY in config.prerequisites[SdkEvent.SDK_UPDATE] + + assert config.execution_limits[SdkEvent.SDK_READY_TIMED_OUT] == -1 + assert config.execution_limits[SdkEvent.SDK_UPDATE] == -1 + assert config.execution_limits[SdkEvent.SDK_READY] == 1 + + assert len(config.require_any[SdkEvent.SDK_READY_TIMED_OUT]) == 1 + assert SdkInternalEvent.SDK_TIMED_OUT in config.require_any[SdkEvent.SDK_READY_TIMED_OUT] + + assert len(config.require_any[SdkEvent.SDK_UPDATE]) == 4 + assert SdkInternalEvent.FLAG_KILLED_NOTIFICATION in config.require_any[SdkEvent.SDK_UPDATE] + assert SdkInternalEvent.FLAGS_UPDATED in config.require_any[SdkEvent.SDK_UPDATE] + assert SdkInternalEvent.RB_SEGMENTS_UPDATED in config.require_any[SdkEvent.SDK_UPDATE] + assert SdkInternalEvent.SEGMENTS_UPDATED in config.require_any[SdkEvent.SDK_UPDATE] + + assert len(config.suppressed_by[SdkEvent.SDK_READY_TIMED_OUT]) == 1 + assert SdkEvent.SDK_READY in config.suppressed_by[SdkEvent.SDK_READY_TIMED_OUT] + + order = 0 + assert len(config.evaluation_order) == 3 + for sdk_event in config.evaluation_order: + order += 1 + if order == 1: + assert sdk_event == SdkEvent.SDK_READY_TIMED_OUT + if order == 2: + assert sdk_event == SdkEvent.SDK_READY + if order == 3: + assert sdk_event == SdkEvent.SDK_UPDATE \ No newline at end of file diff --git a/tests/events/test_events_metadata.py b/tests/events/test_events_metadata.py new file mode 100644 index 00000000..0d321ca2 --- /dev/null +++ b/tests/events/test_events_metadata.py @@ -0,0 +1,28 @@ +"""EventsMetadata test module.""" +import pytest + +from splitio.events.events_metadata import EventsMetadata +from splitio.models.events import SdkEvent, SdkInternalEvent + +class EventsMetadataTests(object): + """Tests for EventsMetadata.""" + + def test_build_instance(self): + data = { "updatedFlags": { "feature1" }, "sdkTimeout": 10 , "boolValue": True, "strValue": "value" } + metadata = EventsMetadata(data) + + assert len(metadata.get_keys()) == 4 + assert metadata.get_data()["updatedFlags"].pop() == "feature1" + assert len(metadata.get_data()["updatedFlags"]) == 0 + assert metadata.get_data()["sdkTimeout"] == 10 + assert metadata.get_data()["boolValue"] == True + assert metadata.get_data()["strValue"] == "value" + assert metadata.contain_key("updatedFlags") + assert not metadata.contain_key("not_exist") + assert len(metadata.get_values()) == 4 + + def test_sanitize_none_input(self): + data = { "updatedFlags": { "feature1" }, "sdkTimeout": None, "strValue": [1, 2, 3] } + metadata = EventsMetadata(data) + assert len(metadata.get_keys()) == 1 + assert metadata.get_data()["updatedFlags"].pop() == "feature1" \ No newline at end of file From 661d248723872cf5f519fd3e21e09fcd8600aaa5 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Thu, 8 Jan 2026 14:46:10 -0800 Subject: [PATCH 2/2] updated metadata to recent spec --- splitio/events/events_metadata.py | 55 +++++++++++----------------- tests/events/test_events_metadata.py | 27 +++++--------- 2 files changed, 32 insertions(+), 50 deletions(-) diff --git a/splitio/events/events_metadata.py b/splitio/events/events_metadata.py index 3e024b66..5d6f4961 100644 --- a/splitio/events/events_metadata.py +++ b/splitio/events/events_metadata.py @@ -1,46 +1,35 @@ """Events Metadata.""" -from splitio.models.events import SdkEvent, SdkInternalEvent +from enum import Enum + +class SdkEventType(Enum): + """Public event types""" + + FLAG_UPDATE = 'FLAG_UPDATE' + SEGMENT_UPDATE = 'SEGMENT_UPDATE' class EventsMetadata(object): """Events Metadata class.""" - def __init__(self, metadata): + def __init__(self, type, names): """ Construct Events Metadata instance. """ - self._metadata = self._sanitize(metadata) + self._type = type + self._names = self._sanitize(names) - def get_data(self): - """Return metadata dict""" - return self._metadata + def get_type(self): + """Return type""" + return self._type - def get_keys(self): - """Return metadata dict keys""" - return self._metadata.keys() - - def get_values(self): - """Return metadata dict values""" - return self._metadata.values() - - def contain_key(self, key): - """Return True if key is contained in metadata""" - return key in self._metadata.keys() + def get_names(self): + """Return names""" + return self._names - def _sanitize(self, data): - """Return sanitized metadata dict with values either int, bool, str or list """ - santized_data = {} - for item_name, item_value in data.items(): - if self._value_is_valid(item_value): - santized_data[item_name] = item_value + def _sanitize(self, names): + """Return sanitized names list with values str""" + santized_data = set() + for name in names: + if isinstance(name, str): + santized_data.add(name) return santized_data - - def _value_is_valid(self, value): - """Return bool if values is int, bool, str or list[str] """ - if (value is not None) and (isinstance(value, int) or isinstance(value, bool) or isinstance(value, str)): - return True - - if isinstance(value, set): - return any([isinstance(item, str) for item in value]) - - return False \ No newline at end of file diff --git a/tests/events/test_events_metadata.py b/tests/events/test_events_metadata.py index 0d321ca2..3ce90d0f 100644 --- a/tests/events/test_events_metadata.py +++ b/tests/events/test_events_metadata.py @@ -2,27 +2,20 @@ import pytest from splitio.events.events_metadata import EventsMetadata -from splitio.models.events import SdkEvent, SdkInternalEvent +from splitio.events.events_metadata import SdkEventType class EventsMetadataTests(object): """Tests for EventsMetadata.""" def test_build_instance(self): - data = { "updatedFlags": { "feature1" }, "sdkTimeout": 10 , "boolValue": True, "strValue": "value" } - metadata = EventsMetadata(data) - - assert len(metadata.get_keys()) == 4 - assert metadata.get_data()["updatedFlags"].pop() == "feature1" - assert len(metadata.get_data()["updatedFlags"]) == 0 - assert metadata.get_data()["sdkTimeout"] == 10 - assert metadata.get_data()["boolValue"] == True - assert metadata.get_data()["strValue"] == "value" - assert metadata.contain_key("updatedFlags") - assert not metadata.contain_key("not_exist") - assert len(metadata.get_values()) == 4 + metadata = EventsMetadata(SdkEventType.FLAG_UPDATE, { "feature1" }) + assert len(metadata.get_names()) == 1 + assert metadata.get_names().pop() == "feature1" + assert len(metadata.get_names()) == 0 + assert metadata.get_type() == SdkEventType.FLAG_UPDATE def test_sanitize_none_input(self): - data = { "updatedFlags": { "feature1" }, "sdkTimeout": None, "strValue": [1, 2, 3] } - metadata = EventsMetadata(data) - assert len(metadata.get_keys()) == 1 - assert metadata.get_data()["updatedFlags"].pop() == "feature1" \ No newline at end of file + metadata = EventsMetadata(SdkEventType.FLAG_UPDATE, { "feature1", None, 123, False }) + assert len(metadata.get_names()) == 1 + assert metadata.get_names().pop() == "feature1" + assert len(metadata.get_names()) == 0