Skip to content

Feature/impressions properties #587

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jul 2, 2025
34 changes: 25 additions & 9 deletions splitio/api/impressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,7 @@ def _build_bulk(impressions):
{
'f': test_name,
'i': [
{
'k': impression.matching_key,
't': impression.treatment,
'm': impression.time,
'c': impression.change_number,
'r': impression.label,
'b': impression.bucketing_key,
'pt': impression.previous_time
}
ImpressionsAPIBase._filter_out_null_prop(impression)
for impression in imps
]
}
Expand All @@ -48,6 +40,30 @@ def _build_bulk(impressions):
)
]

@staticmethod
def _filter_out_null_prop(impression):
if impression.properties == None:
return {
'k': impression.matching_key,
't': impression.treatment,
'm': impression.time,
'c': impression.change_number,
'r': impression.label,
'b': impression.bucketing_key,
'pt': impression.previous_time
}

return {
'k': impression.matching_key,
't': impression.treatment,
'm': impression.time,
'c': impression.change_number,
'r': impression.label,
'b': impression.bucketing_key,
'pt': impression.previous_time,
'properties': impression.properties
}

@staticmethod
def _build_counters(counters):
"""
Expand Down
148 changes: 80 additions & 68 deletions splitio/client/client.py

Large diffs are not rendered by default.

25 changes: 15 additions & 10 deletions splitio/client/input_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,7 @@ def validate_factory_instantiation(sdk_key):
return True


def valid_properties(properties):
def valid_properties(properties, source):
"""
Check if properties is a valid dict and returns the properties
that will be sent to the track method, avoiding unexpected types.
Expand All @@ -580,7 +580,7 @@ def valid_properties(properties):
return True, None, size

if not isinstance(properties, dict):
_LOGGER.error('track: properties must be of type dictionary.')
_LOGGER.error('%s: properties must be of type dictionary.', source)
return False, None, 0

valid_properties = dict()
Expand All @@ -595,9 +595,8 @@ def valid_properties(properties):
if element is None:
continue

if not isinstance(element, str) and not isinstance(element, Number) \
and not isinstance(element, bool):
_LOGGER.warning('Property %s is of invalid type. Setting value to None', element)
if not _check_element_type(element):
_LOGGER.warning('%s: Property %s is of invalid type. Setting value to None', source, element)
element = None

valid_properties[property] = element
Expand All @@ -607,16 +606,22 @@ def valid_properties(properties):

if size > MAX_PROPERTIES_LENGTH_BYTES:
_LOGGER.error(
'The maximum size allowed for the properties is 32768 bytes. ' +
'Current one is ' + str(size) + ' bytes. Event not queued'
)
'%s: The maximum size allowed for the properties is 32768 bytes. ' +
'Current one is ' + str(size) + ' bytes. Event not queued', source)
return False, None, size

if len(valid_properties.keys()) > 300:
_LOGGER.warning('Event has more than 300 properties. Some of them will be trimmed' +
' when processed')
_LOGGER.warning('%s: Event has more than 300 properties. Some of them will be trimmed' +
' when processed', source)
return True, valid_properties if len(valid_properties) else None, size

def _check_element_type(element):
if not isinstance(element, str) and not isinstance(element, Number) \
and not isinstance(element, bool):
return False

return True

def validate_pluggable_adapter(config):
"""
Check if pluggable adapter contains the expected method signature
Expand Down
18 changes: 16 additions & 2 deletions splitio/engine/impressions/strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,14 @@ def process_impressions(self, impressions):
:returns: Tuple of to be stored, observed and counted impressions, and unique keys tuple
:rtype: list[tuple[splitio.models.impression.Impression, dict]], list[], list[], list[]
"""
imps = [(self._observer.test_and_set(imp), attrs) for imp, attrs in impressions]
imps = []
for imp, attrs in impressions:
if imp.properties is not None:
imps.append((imp, attrs))
continue

imps.append((self._observer.test_and_set(imp), attrs))

return [i for i, _ in imps], imps, [], []

class StrategyNoneMode(BaseStrategy):
Expand Down Expand Up @@ -85,7 +92,14 @@ def process_impressions(self, impressions):
:returns: Tuple of to be stored, observed and counted impressions, and unique keys tuple
:rtype: list[tuple[splitio.models.impression.Impression, dict]], list[splitio.models.impression.Impression], list[splitio.models.impression.Impression], list[]
"""
imps = [(self._observer.test_and_set(imp), attrs) for imp, attrs in impressions]
imps = []
for imp, attrs in impressions:
if imp.properties is not None:
imps.append((imp, attrs))
continue

imps.append((self._observer.test_and_set(imp), attrs))

counter_imps = [imp for imp, _ in imps if imp.previous_time != None]
this_hour = truncate_time(utctime_ms())
return [i for i, _ in imps if i.previous_time is None or i.previous_time < this_hour], imps, counter_imps, []
3 changes: 2 additions & 1 deletion splitio/models/impressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
'change_number',
'bucketing_key',
'time',
'previous_time'
'previous_time',
'properties'
]
)

Expand Down
1 change: 1 addition & 0 deletions splitio/storage/pluggable.py
Original file line number Diff line number Diff line change
Expand Up @@ -1231,6 +1231,7 @@ def _wrap_impressions(self, impressions):
'r': impression.label,
'c': impression.change_number,
'm': impression.time,
'properties': impression.properties
}
}
bulk_impressions.append(json.dumps(to_store))
Expand Down
1 change: 1 addition & 0 deletions splitio/storage/redis.py
Original file line number Diff line number Diff line change
Expand Up @@ -1100,6 +1100,7 @@ def _wrap_impressions(self, impressions):
'r': impression.label,
'c': impression.change_number,
'm': impression.time,
'properties': impression.properties
}
}
bulk_impressions.append(json.dumps(to_store))
Expand Down
8 changes: 4 additions & 4 deletions tests/api/test_impressions_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@
from splitio.storage.inmemmory import InMemoryTelemetryStorage, InMemoryTelemetryStorageAsync

impressions_mock = [
Impression('k1', 'f1', 'on', 'l1', 123456, 'b1', 321654),
Impression('k2', 'f2', 'off', 'l1', 123456, 'b1', 321654),
Impression('k3', 'f1', 'on', 'l1', 123456, 'b1', 321654)
Impression('k1', 'f1', 'on', 'l1', 123456, 'b1', 321654, None, {'prop': 'val'}),
Impression('k2', 'f2', 'off', 'l1', 123456, 'b1', 321654, None, None),
Impression('k3', 'f1', 'on', 'l1', 123456, 'b1', 321654, None, None)
]
expectedImpressions = [{
'f': 'f1',
'i': [
{'k': 'k1', 'b': 'b1', 't': 'on', 'r': 'l1', 'm': 321654, 'c': 123456, 'pt': None},
{'k': 'k1', 'b': 'b1', 't': 'on', 'r': 'l1', 'm': 321654, 'c': 123456, 'pt': None, 'properties': {"prop": "val"}},
{'k': 'k3', 'b': 'b1', 't': 'on', 'r': 'l1', 'm': 321654, 'c': 123456, 'pt': None},
],
}, {
Expand Down
Loading