Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
8.1.3 (Oct XX, 2019)
- Added logic to fetch multiple splits at once on get_treatments/get_treatments_with_config.
- Added flag `ipAddressesEnabled` into config to enable/disable sending machineName and machineIp when data is posted in headers.

8.1.2 (Jul 19, 2019)
- Validated TLS support for redis connections
Expand Down
3 changes: 3 additions & 0 deletions splitio/api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Split API module."""


class APIException(Exception):
"""Exception to raise when an API call fails."""

Expand Down Expand Up @@ -28,4 +29,6 @@ def headers_from_metadata(sdk_metadata):
'SplitSDKVersion': sdk_metadata.sdk_version,
'SplitSDKMachineIP': sdk_metadata.instance_ip,
'SplitSDKMachineName': sdk_metadata.instance_name
} if sdk_metadata.instance_ip != 'NA' and sdk_metadata.instance_ip != 'unknown' else {
'SplitSDKVersion': sdk_metadata.sdk_version,
}
1 change: 1 addition & 0 deletions splitio/client/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
'eventsBulkSize': 5000,
'eventsQueueSize': 10000,
'labelsEnabled': True,
'ipAddressesEnabled': True,
'impressionListener': None,
'redisHost': 'localhost',
'redisPort': 6379,
Expand Down
15 changes: 11 additions & 4 deletions splitio/client/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ def _get_hostname(ip_address):
return 'unknown' if ip_address == 'unknown' else 'ip-' + ip_address.replace('.', '-')


def _get_hostname_and_ip(config):
if config.get('ipAddressesEnabled') is False:
return 'NA', 'NA'
ip_from_config = config.get('machineIp')
machine_from_config = config.get('machineName')
ip_address = ip_from_config if ip_from_config is not None else _get_ip()
hostname = machine_from_config if machine_from_config is not None else _get_hostname(ip_address)
return ip_address, hostname


def get_metadata(config):
"""
Gather SDK metadata and return a tuple with such info.
Expand All @@ -39,10 +49,7 @@ def get_metadata(config):
:rtype: SdkMetadata
"""
version = 'python-%s' % __version__
ip_from_config = config.get('machineIp')
machine_from_config = config.get('machineName')
ip_address = ip_from_config if ip_from_config is not None else _get_ip()
hostname = machine_from_config if machine_from_config is not None else _get_hostname(ip_address)
ip_address, hostname = _get_hostname_and_ip(config)
return SdkMetadata(version, hostname, ip_address)


Expand Down
66 changes: 45 additions & 21 deletions tests/api/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,35 @@
import pytest
from splitio.api import events, client, APIException
from splitio.models.events import Event
from splitio.client.util import SdkMetadata
from splitio.client.util import get_metadata
from splitio.client.config import DEFAULT_CONFIG
from splitio.version import __version__


class EventsAPITests(object):
"""Impressions API test cases."""
events = [
Event('k1', 'user', 'purchase', 12.50, 123456, None),
Event('k2', 'user', 'purchase', 12.50, 123456, None),
Event('k3', 'user', 'purchase', None, 123456, {"test": 1234}),
Event('k4', 'user', 'purchase', None, 123456, None)
]
eventsExpected = [
{'key': 'k1', 'trafficTypeName': 'user', 'eventTypeId': 'purchase', 'value': 12.50, 'timestamp': 123456, 'properties': None},
{'key': 'k2', 'trafficTypeName': 'user', 'eventTypeId': 'purchase', 'value': 12.50, 'timestamp': 123456, 'properties': None},
{'key': 'k3', 'trafficTypeName': 'user', 'eventTypeId': 'purchase', 'value': None, 'timestamp': 123456, 'properties': {"test": 1234}},
{'key': 'k4', 'trafficTypeName': 'user', 'eventTypeId': 'purchase', 'value': None, 'timestamp': 123456, 'properties': None},
]

def test_post_events(self, mocker):
"""Test impressions posting API call."""
httpclient = mocker.Mock(spec=client.HttpClient)
httpclient.post.return_value = client.HttpResponse(200, '')
sdk_metadata = SdkMetadata('python-1.2.3', 'some_machine_name', '123.123.123.123')
cfg = DEFAULT_CONFIG.copy()
cfg.update({'ipAddressesEnabled': True, 'machineName': 'some_machine_name', 'machineIp': '123.123.123.123'})
sdk_metadata = get_metadata(cfg)
events_api = events.EventsAPI(httpclient, 'some_api_key', sdk_metadata)
response = events_api.flush_events([
Event('k1', 'user', 'purchase', 12.50, 123456, None),
Event('k2', 'user', 'purchase', 12.50, 123456, None),
Event('k3', 'user', 'purchase', None, 123456, {"test": 1234}),
Event('k4', 'user', 'purchase', None, 123456, None)
])
response = events_api.flush_events(self.events)

call_made = httpclient.post.mock_calls[0]

Expand All @@ -29,29 +40,42 @@ def test_post_events(self, mocker):

# validate key-value args (headers)
assert call_made[2]['extra_headers'] == {
'SplitSDKVersion': 'python-1.2.3',
'SplitSDKVersion': 'python-%s' % __version__,
'SplitSDKMachineIP': '123.123.123.123',
'SplitSDKMachineName': 'some_machine_name'
}

# validate key-value args (body)
assert call_made[2]['body'] == [
{'key': 'k1', 'trafficTypeName': 'user', 'eventTypeId': 'purchase', 'value': 12.50, 'timestamp': 123456, 'properties': None},
{'key': 'k2', 'trafficTypeName': 'user', 'eventTypeId': 'purchase', 'value': 12.50, 'timestamp': 123456, 'properties': None},
{'key': 'k3', 'trafficTypeName': 'user', 'eventTypeId': 'purchase', 'value': None, 'timestamp': 123456, 'properties': {"test": 1234}},
{'key': 'k4', 'trafficTypeName': 'user', 'eventTypeId': 'purchase', 'value': None, 'timestamp': 123456, 'properties': None},
]
assert call_made[2]['body'] == self.eventsExpected

httpclient.reset_mock()
def raise_exception(*args, **kwargs):
raise client.HttpClientException('some_message')
httpclient.post.side_effect = raise_exception
with pytest.raises(APIException) as exc_info:
response = events_api.flush_events([
Event('k1', 'user', 'purchase', 12.50, 123456, None),
Event('k2', 'user', 'purchase', 12.50, 123456, None),
Event('k3', 'user', 'purchase', None, 123456, None),
Event('k4', 'user', 'purchase', None, 123456, None)
])
response = events_api.flush_events(self.events)
assert exc_info.type == APIException
assert exc_info.value.message == 'some_message'

def test_post_events_ip_address_disabled(self, mocker):
"""Test impressions posting API call."""
httpclient = mocker.Mock(spec=client.HttpClient)
httpclient.post.return_value = client.HttpResponse(200, '')
cfg = DEFAULT_CONFIG.copy()
cfg.update({'ipAddressesEnabled': False})
sdk_metadata = get_metadata(cfg)
events_api = events.EventsAPI(httpclient, 'some_api_key', sdk_metadata)
response = events_api.flush_events(self.events)

call_made = httpclient.post.mock_calls[0]

# validate positional arguments
assert call_made[1] == ('events', '/events/bulk', 'some_api_key')

# validate key-value args (headers)
assert call_made[2]['extra_headers'] == {
'SplitSDKVersion': 'python-%s' % __version__,
}

# validate key-value args (body)
assert call_made[2]['body'] == self.eventsExpected
79 changes: 51 additions & 28 deletions tests/api/test_impressions_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,40 @@
import pytest
from splitio.api import impressions, client, APIException
from splitio.models.impressions import Impression
from splitio.client.util import SdkMetadata
from splitio.client.util import get_metadata
from splitio.client.config import DEFAULT_CONFIG
from splitio.version import __version__


class ImpressionsAPITests(object):
"""Impressions API test cases."""
impressions = [
Impression('k1', 'f1', 'on', 'l1', 123456, 'b1', 321654),
Impression('k2', 'f2', 'off', 'l1', 123456, 'b1', 321654),
Impression('k3', 'f1', 'on', 'l1', 123456, 'b1', 321654)
]
expectedImpressions = [{
'testName': 'f1',
'keyImpressions': [
{'keyName': 'k1', 'bucketingKey': 'b1', 'treatment': 'on', 'label': 'l1', 'time': 321654, 'changeNumber': 123456},
{'keyName': 'k3', 'bucketingKey': 'b1', 'treatment': 'on', 'label': 'l1', 'time': 321654, 'changeNumber': 123456},
],
}, {
'testName': 'f2',
'keyImpressions': [
{'keyName': 'k2', 'bucketingKey': 'b1', 'treatment': 'off', 'label': 'l1', 'time': 321654, 'changeNumber': 123456},
]
}]

def test_post_impressions(self, mocker):
"""Test impressions posting API call."""
httpclient = mocker.Mock(spec=client.HttpClient)
httpclient.post.return_value = client.HttpResponse(200, '')
sdk_metadata = SdkMetadata('python-1.2.3', 'some_machine_name', '123.123.123.123')
cfg = DEFAULT_CONFIG.copy()
cfg.update({'ipAddressesEnabled': True, 'machineName': 'some_machine_name', 'machineIp': '123.123.123.123'})
sdk_metadata = get_metadata(cfg)
impressions_api = impressions.ImpressionsAPI(httpclient, 'some_api_key', sdk_metadata)
response = impressions_api.flush_impressions([
Impression('k1', 'f1', 'on', 'l1', 123456, 'b1', 321654),
Impression('k2', 'f2', 'off', 'l1', 123456, 'b1', 321654),
Impression('k3', 'f1', 'on', 'l1', 123456, 'b1', 321654),
])
response = impressions_api.flush_impressions(self.impressions)

call_made = httpclient.post.mock_calls[0]

Expand All @@ -27,37 +45,42 @@ def test_post_impressions(self, mocker):

# validate key-value args (headers)
assert call_made[2]['extra_headers'] == {
'SplitSDKVersion': 'python-1.2.3',
'SplitSDKVersion': 'python-%s' % __version__,
'SplitSDKMachineIP': '123.123.123.123',
'SplitSDKMachineName': 'some_machine_name'
}

# validate key-value args (body)
assert call_made[2]['body'] == [
{
'testName': 'f1',
'keyImpressions': [
{'keyName': 'k1', 'bucketingKey': 'b1', 'treatment': 'on', 'label': 'l1', 'time': 321654, 'changeNumber': 123456},
{'keyName': 'k3', 'bucketingKey': 'b1', 'treatment': 'on', 'label': 'l1', 'time': 321654, 'changeNumber': 123456},
],
},
{
'testName': 'f2',
'keyImpressions': [
{'keyName': 'k2', 'bucketingKey': 'b1', 'treatment': 'off', 'label': 'l1', 'time': 321654, 'changeNumber': 123456},
]
}
]
assert call_made[2]['body'] == self.expectedImpressions

httpclient.reset_mock()
def raise_exception(*args, **kwargs):
raise client.HttpClientException('some_message')
httpclient.post.side_effect = raise_exception
with pytest.raises(APIException) as exc_info:
response = impressions_api.flush_impressions([
Impression('k1', 'f1', 'on', 'l1', 123456, 'b1', 321654),
Impression('k2', 'f2', 'off', 'l1', 123456, 'b1', 321654),
Impression('k3', 'f1', 'on', 'l1', 123456, 'b1', 321654),
])
response = impressions_api.flush_impressions(self.impressions)
assert exc_info.type == APIException
assert exc_info.value.message == 'some_message'

def test_post_impressions_ip_address_disabled(self, mocker):
"""Test impressions posting API call."""
httpclient = mocker.Mock(spec=client.HttpClient)
httpclient.post.return_value = client.HttpResponse(200, '')
cfg = DEFAULT_CONFIG.copy()
cfg.update({'ipAddressesEnabled': False})
sdk_metadata = get_metadata(cfg)
impressions_api = impressions.ImpressionsAPI(httpclient, 'some_api_key', sdk_metadata)
response = impressions_api.flush_impressions(self.impressions)

call_made = httpclient.post.mock_calls[0]

# validate positional arguments
assert call_made[1] == ('events', '/testImpressions/bulk', 'some_api_key')

# validate key-value args (headers)
assert call_made[2]['extra_headers'] == {
'SplitSDKVersion': 'python-%s' % __version__,
}

# validate key-value args (body)
assert call_made[2]['body'] == self.expectedImpressions
10 changes: 10 additions & 0 deletions tests/client/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
"""Split client utilities test module."""
#pylint: disable=no-self-use,too-few-public-methods

import socket


from splitio.client import util, config
from splitio.version import __version__
from splitio.client.config import DEFAULT_CONFIG

class ClientUtilsTests(object):
"""Client utilities test cases."""
Expand All @@ -25,6 +29,12 @@ def test_get_metadata(self, mocker):
assert get_ip_mock.mock_calls == [mocker.call()]
assert get_host_mock.mock_calls == [mocker.call(mocker.ANY)]

cfg = DEFAULT_CONFIG.copy()
cfg.update({'ipAddressesEnabled': False})
meta = util.get_metadata(cfg)
assert meta.instance_ip == 'NA'
assert meta.instance_name == 'NA'

get_ip_mock.reset_mock()
get_host_mock.reset_mock()
meta = util.get_metadata({})
Expand Down
Loading