Skip to content

Commit 42e4d76

Browse files
authored
Merge pull request #98 from splitio/feature/ImpressionListener
[SDKS-153]: Add Impression Listener for Python
2 parents 3b3b98a + 659fd8b commit 42e4d76

14 files changed

+390
-106
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ var/
2323
*.egg-info/
2424
.installed.cfg
2525
*.egg
26+
venv/
2627
.vscode
2728

2829
# PyInstaller

Detailed-README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,42 @@ except TimeoutException:
110110
sys.exit()
111111
```
112112

113+
## Impression Listener
114+
Split SDKs send impression data back to Split servers periodically and as a result of evaluating splits. In order to additionally send this information to a location of your choice, you could define and attach an Impression Listener. For that purpose, SDK's options have a parameter called `impressionListener` where an implementation of `ImpressionListener` could be added. This implementation **must** define the `log_impression` method and it will receive data in the following schema:
115+
116+
| Name | Type | Description |
117+
| --- | --- | --- |
118+
| impression | Impression | Impression object that has the feature_name, treatment result, label, etc. |
119+
| attributes | Array | A list of attributes passed by the client. |
120+
| instance-id | String | Corresponds to the IP of the machine where the SDK is running. |
121+
| sdk-language-version | String | Indicates the version of the sdk. In this case the language will be python plus the version of it. |
122+
123+
### Implementing custom Impression Listener
124+
Below you could find an example of how implement a custom Impression Listener:
125+
```python
126+
# Import ImpressionListener interface
127+
from splitio.impressions import ImpressionListener
128+
129+
# Implementation Sample for a Custom Impression Listener
130+
class CustomImpressionListener(ImpressionListener)
131+
{
132+
def log_impression(self, data):
133+
# Custom behavior
134+
}
135+
```
136+
137+
### Attaching custom Impression Listener
138+
```python
139+
factory = get_factory(
140+
'YOUR_API_KEY',
141+
config={
142+
# ...
143+
'impressionListener': CustomImpressionListener()
144+
},
145+
# ...
146+
)
147+
split = factory.client()
148+
113149
## Additional information
114150

115151
You can get more information on how to use this package in the included documentation.

splitio/brokers.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,13 @@ class BaseBroker(object):
5757

5858
__metaclass__ = abc.ABCMeta
5959

60-
def __init__(self):
60+
def __init__(self, config=None):
6161
"""
6262
Class constructor, only sets up the logger
6363
"""
6464
self._logger = logging.getLogger(self.__class__.__name__)
6565
self._destroyed = False
66+
self._config = config
6667

6768
def fetch_feature(self, name):
6869
"""
@@ -120,7 +121,7 @@ def destroy(self):
120121
pass
121122

122123
class JSONFileBroker(BaseBroker):
123-
def __init__(self, segment_changes_file_name, split_changes_file_name):
124+
def __init__(self, config, segment_changes_file_name, split_changes_file_name):
124125
"""
125126
A Broker implementation that uses responses from the segmentChanges and
126127
splitChanges resources to provide access to splits. It is intended to be
@@ -133,7 +134,7 @@ def __init__(self, segment_changes_file_name, split_changes_file_name):
133134
splitChanges response
134135
:type split_changes_file_name: str
135136
"""
136-
super(JSONFileBroker, self).__init__()
137+
super(JSONFileBroker, self).__init__(config)
137138
self._segment_changes_file_name = segment_changes_file_name
138139
self._split_changes_file_name = split_changes_file_name
139140
self._split_fetcher = self._build_split_fetcher()
@@ -310,7 +311,6 @@ def _build_treatment_log(self):
310311
self._sdk_api,
311312
max_count=self._max_impressions_log_size,
312313
interval=self._impressions_interval,
313-
listener=self._impression_listener
314314
)
315315
return AsyncTreatmentLog(self_updating_treatment_log)
316316

@@ -388,7 +388,7 @@ class LocalhostEventStorage(object):
388388
def log(self, event):
389389
pass
390390

391-
def __init__(self, split_definition_file_name=None, auto_refresh_period=2):
391+
def __init__(self, config, split_definition_file_name=None, auto_refresh_period=2):
392392
"""
393393
A broker implementation that builds its configuration from a split
394394
definition file. By default the definition is taken from $HOME/.split
@@ -398,7 +398,7 @@ def __init__(self, split_definition_file_name=None, auto_refresh_period=2):
398398
:param auto_refresh_period: Number of seconds between split refresh calls
399399
:type auto_refresh_period: int
400400
"""
401-
super(LocalhostBroker, self).__init__()
401+
super(LocalhostBroker, self).__init__(config)
402402

403403
if split_definition_file_name is None:
404404
self._split_definition_file_name = os.path.join(
@@ -503,11 +503,11 @@ def destroy(self):
503503

504504

505505
class RedisBroker(BaseBroker):
506-
def __init__(self, redis):
506+
def __init__(self, redis, config):
507507
"""A Broker implementation that uses Redis as its backend.
508508
:param redis: A redis broker
509509
:type redis: StrctRedis"""
510-
super(RedisBroker, self).__init__()
510+
super(RedisBroker, self).__init__(config)
511511

512512
split_cache = RedisSplitCache(redis)
513513
split_fetcher = CacheBasedSplitFetcher(split_cache)
@@ -571,7 +571,7 @@ def __init__(self, uwsgi, config=None):
571571
:param config: The configuration dictionary
572572
:type config: dict
573573
"""
574-
super(UWSGIBroker, self).__init__()
574+
super(UWSGIBroker, self).__init__(config)
575575

576576
split_cache = UWSGISplitCache(uwsgi)
577577
split_fetcher = CacheBasedSplitFetcher(split_cache)
@@ -712,7 +712,7 @@ def get_self_refreshing_broker(api_key, **kwargs):
712712
)
713713

714714
if api_key == 'localhost':
715-
return LocalhostBroker(**kwargs)
715+
return LocalhostBroker(config, **kwargs)
716716

717717
return SelfRefreshingBroker(
718718
api_key,
@@ -776,11 +776,11 @@ def get_redis_broker(api_key, **kwargs):
776776
api_key, config, _, _ = _init_config(api_key, **kwargs)
777777

778778
if api_key == 'localhost':
779-
return LocalhostBroker(**kwargs)
779+
return LocalhostBroker(config, **kwargs)
780780

781781
redis = get_redis(config)
782782

783-
redis_broker = RedisBroker(redis)
783+
redis_broker = RedisBroker(redis, config)
784784

785785
return redis_broker
786786

@@ -836,7 +836,7 @@ def get_uwsgi_broker(api_key, **kwargs):
836836
api_key, config, _, _ = _init_config(api_key, **kwargs)
837837

838838
if api_key == 'localhost':
839-
return LocalhostBroker(**kwargs)
839+
return LocalhostBroker(config, **kwargs)
840840

841841
uwsgi = get_uwsgi()
842842
uwsgi_broker = UWSGIBroker(uwsgi, config)

splitio/clients.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@
66
import time
77
from splitio.treatments import CONTROL
88
from splitio.splitters import Splitter
9-
from splitio.impressions import Impression, Label
9+
from splitio.impressions import Impression, Label, ImpressionListenerException
1010
from splitio.metrics import SDK_GET_TREATMENT
1111
from splitio.splits import ConditionType
1212
from splitio.events import Event
1313

14+
1415
class Key(object):
1516
def __init__(self, matching_key, bucketing_key):
1617
"""Bucketing Key implementation"""
@@ -19,7 +20,7 @@ def __init__(self, matching_key, bucketing_key):
1920

2021

2122
class Client(object):
22-
def __init__(self, broker, labels_enabled=True):
23+
def __init__(self, broker, labels_enabled=True, impression_listener=None):
2324
"""Basic interface of a Client. Specific implementations need to override the
2425
get_split_fetcher method (and optionally the get_splitter method).
2526
"""
@@ -28,6 +29,7 @@ def __init__(self, broker, labels_enabled=True):
2829
self._broker = broker
2930
self._labels_enabled = labels_enabled
3031
self._destroyed = False
32+
self._impression_listener = impression_listener
3133

3234
@staticmethod
3335
def _get_keys(key):
@@ -49,6 +51,17 @@ def destroy(self):
4951
self._destroyed = True
5052
self._broker.destroy()
5153

54+
def _handle_custom_impression(self, impression, attributes):
55+
'''
56+
Handles custom impression if is present. Basically, sends the data
57+
to client if some logic is wanted to do.
58+
'''
59+
if self._impression_listener is not None:
60+
try:
61+
self._impression_listener.log_impression(impression, attributes)
62+
except ImpressionListenerException as e:
63+
self._logger.exception(e)
64+
5265
def get_treatment(self, key, feature, attributes=None):
5366
"""
5467
Get the treatment for a feature and key, with an optional dictionary of attributes. This
@@ -106,6 +119,9 @@ def get_treatment(self, key, feature, attributes=None):
106119
impression = self._build_impression(matching_key, feature, _treatment, label,
107120
_change_number, bucketing_key, start)
108121
self._record_stats(impression, start, SDK_GET_TREATMENT)
122+
123+
self._handle_custom_impression(impression, attributes)
124+
109125
return _treatment
110126
except:
111127
self._logger.exception('Exception caught getting treatment for feature')
@@ -114,6 +130,8 @@ def get_treatment(self, key, feature, attributes=None):
114130
impression = self._build_impression(matching_key, feature, CONTROL, Label.EXCEPTION,
115131
self._broker.get_change_number(), bucketing_key, start)
116132
self._record_stats(impression, start, SDK_GET_TREATMENT)
133+
134+
self._handle_custom_impression(impression, attributes)
117135
except:
118136
self._logger.exception('Exception reporting impression into get_treatment exception block')
119137

@@ -200,6 +218,7 @@ def track(self, key, traffic_type, event_type, value=None):
200218
)
201219
return self._broker.get_events_log().log_event(e)
202220

221+
203222
class MatcherClient(Client):
204223
"""
205224
"""

splitio/factories.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from splitio.brokers import get_self_refreshing_broker, get_redis_broker, get_uwsgi_broker
66
from splitio.managers import RedisSplitManager, SelfRefreshingSplitManager, \
77
LocalhostSplitManager, UWSGISplitManager
8+
from splitio.impressions import ImpressionListenerWrapper
89

910
import logging
1011

@@ -40,18 +41,21 @@ def __init__(self, api_key, **kwargs):
4041
config = kwargs['config']
4142

4243
labels_enabled = config.get('labelsEnabled', True)
44+
45+
impression_listener = ImpressionListenerWrapper(config.get('impressionListener')) if 'impressionListener' in config else None # noqa: E501,E261
46+
4347
if 'redisHost' in config or 'redisSentinels' in config:
4448
broker = get_redis_broker(api_key, **kwargs)
45-
self._client = Client(broker, labels_enabled)
49+
self._client = Client(broker, labels_enabled, impression_listener)
4650
self._manager = RedisSplitManager(broker)
4751
else:
4852
if 'uwsgiClient' in config and config['uwsgiClient']:
4953
broker = get_uwsgi_broker(api_key, **kwargs)
50-
self._client = Client(broker, labels_enabled)
54+
self._client = Client(broker, labels_enabled, impression_listener)
5155
self._manager = UWSGISplitManager(broker)
5256
else:
5357
broker = get_self_refreshing_broker(api_key, **kwargs)
54-
self._client = Client(broker, labels_enabled)
58+
self._client = Client(broker, labels_enabled, impression_listener)
5559
self._manager = SelfRefreshingSplitManager(broker)
5660

5761
def client(self): # pragma: no cover

0 commit comments

Comments
 (0)