Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
11 changes: 7 additions & 4 deletions splitio/client/client.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""A module for Split.io SDK API clients."""
import logging
import json
from collections import namedtuple

from splitio.engine.evaluator import Evaluator, CONTROL, EvaluationDataFactory, AsyncEvaluationDataFactory
from splitio.engine.splitters import Splitter
Expand All @@ -12,6 +13,7 @@


_LOGGER = logging.getLogger(__name__)
EvaluationOptions = namedtuple('EvaluationOptions', ['properties'])


class ClientBase(object): # pylint: disable=too-many-instance-attributes
Expand Down Expand Up @@ -124,10 +126,11 @@ def _validate_treatment_options(method_name, evaluation_options=None):
if evaluation_options == None:
return None

if evaluation_options["properties"] is not None:
valid, evaluation_options["properties"], size = input_validator.valid_properties(evaluation_options["properties"], method_name)
if evaluation_options.properties is not None:
valid, properties, size = input_validator.valid_properties(evaluation_options.properties, method_name)
evaluation_options = EvaluationOptions(properties)
if not valid:
evaluation_options["properties"] = None
evaluation_options = EvaluationOptions(None)
return evaluation_options

def _build_impression(self, key, bucketing, feature, result, properties=None):
Expand Down Expand Up @@ -198,7 +201,7 @@ def _validate_track(self, key, traffic_type, event_type, value=None, properties=
return True, event, size

def _get_properties(self, evaluation_options):
return evaluation_options["properties"] if evaluation_options != None and evaluation_options.get("properties") != None else None
return evaluation_options.properties if evaluation_options != None else None


class Client(ClientBase): # pylint: disable=too-many-instance-attributes
Expand Down
9 changes: 3 additions & 6 deletions splitio/client/input_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import inspect

from splitio.client.key import Key
from splitio.client import client
from splitio.engine.evaluator import CONTROL


Expand Down Expand Up @@ -542,12 +543,8 @@ def validate_evaluation_options(evaluation_options, method_name):
if evaluation_options == None:
return None

if not isinstance(evaluation_options, dict):
_LOGGER.error("%s: evaluaiton option should be dictionary, setting its value to None.", method_name)
return None

if evaluation_options.get("properties") == None:
_LOGGER.error("%s: evaluaiton option must have `properties` key, setting its value to None.", method_name)
if not isinstance(evaluation_options, client.EvaluationOptions):
_LOGGER.error("%s: evaluaiton option should be an instance of EvaluationOptions, setting its value to None.", method_name)
Comment thread
chillaq marked this conversation as resolved.
Outdated
return None

return evaluation_options
Expand Down
82 changes: 41 additions & 41 deletions tests/client/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import time
import pytest

from splitio.client.client import Client, _LOGGER as _logger, CONTROL, ClientAsync
from splitio.client.client import Client, _LOGGER as _logger, CONTROL, ClientAsync, EvaluationOptions
from splitio.client.factory import SplitFactory, Status as FactoryStatus, SplitFactoryAsync
from splitio.models.impressions import Impression, Label
from splitio.models.events import Event, EventWrapper
Expand Down Expand Up @@ -1327,52 +1327,52 @@ def synchronize_config(*_):

_logger = mocker.Mock()
mocker.patch('splitio.client.input_validator._LOGGER', new=_logger)
assert client.get_treatment('some_key', 'SPLIT_2', evaluation_options={"properties":{"prop": "value"}}) == 'on'
assert client.get_treatment('some_key', 'SPLIT_2', evaluation_options=EvaluationOptions({"prop": "value"})) == 'on'
assert impression_storage.pop_many(100) == [Impression('some_key', 'SPLIT_2', 'on', 'some_label', 123, None, 1000, None, '{"prop": "value"}')]

assert client.get_treatment('some_key', 'SPLIT_2', evaluation_options=12) == 'on'
assert client.get_treatment('some_key', 'SPLIT_2', evaluation_options=EvaluationOptions(12)) == 'on'
assert impression_storage.pop_many(100) == [Impression('some_key', 'SPLIT_2', 'on', 'some_label', 123, None, 1000, None, None)]
assert _logger.error.mock_calls == [mocker.call('%s: evaluaiton option should be dictionary, setting its value to None.', 'get_treatment')]
assert _logger.error.mock_calls == [mocker.call('%s: properties must be of type dictionary.', 'get_treatment')]

_logger.reset_mock()
assert client.get_treatment('some_key', 'SPLIT_2', evaluation_options='12') == 'on'
assert client.get_treatment('some_key', 'SPLIT_2', evaluation_options=EvaluationOptions('12')) == 'on'
assert impression_storage.pop_many(100) == [Impression('some_key', 'SPLIT_2', 'on', 'some_label', 123, None, 1000, 1000, None)]
assert _logger.error.mock_calls == [mocker.call('%s: evaluaiton option should be dictionary, setting its value to None.', 'get_treatment')]
assert _logger.error.mock_calls == [mocker.call('%s: properties must be of type dictionary.', 'get_treatment')]

_logger.reset_mock()
assert client.get_treatment('some_key', 'SPLIT_2', evaluation_options={"property":{"prop": "value"}}) == 'on'
assert impression_storage.pop_many(100) == [Impression('some_key', 'SPLIT_2', 'on', 'some_label', 123, None, 1000, 1000, None)]
assert _logger.error.mock_calls == [mocker.call('%s: evaluaiton option must have `properties` key, setting its value to None.', 'get_treatment')]

assert client.get_treatment_with_config('some_key', 'SPLIT_2', evaluation_options={"properties":{"prop": "value"}}) == ('on', None)
assert client.get_treatment_with_config('some_key', 'SPLIT_2', evaluation_options=EvaluationOptions({"prop": "value"})) == ('on', None)
assert impression_storage.pop_many(100) == [Impression('some_key', 'SPLIT_2', 'on', 'some_label', 123, None, 1000, None, '{"prop": "value"}')]

assert client.get_treatments('some_key', ['SPLIT_2'], evaluation_options={"properties":{"prop": "value"}}) == {'SPLIT_2': 'on'}
assert client.get_treatments('some_key', ['SPLIT_2'], evaluation_options=EvaluationOptions({"prop": "value"})) == {'SPLIT_2': 'on'}
assert impression_storage.pop_many(100) == [Impression('some_key', 'SPLIT_2', 'on', 'some_label', 123, None, 1000, None, '{"prop": "value"}')]

_logger.reset_mock()
assert client.get_treatments('some_key', ['SPLIT_2'], evaluation_options="prop") == {'SPLIT_2': 'on'}
assert client.get_treatments('some_key', ['SPLIT_2'], evaluation_options=EvaluationOptions("prop")) == {'SPLIT_2': 'on'}
assert impression_storage.pop_many(100) == [Impression('some_key', 'SPLIT_2', 'on', 'some_label', 123, None, 1000, 1000, None)]
assert _logger.error.mock_calls == [mocker.call('%s: properties must be of type dictionary.', 'get_treatments')]

_logger.reset_mock()
assert client.get_treatments('some_key', ['SPLIT_2'], evaluation_options=EvaluationOptions(123)) == {'SPLIT_2': 'on'}
assert impression_storage.pop_many(100) == [Impression('some_key', 'SPLIT_2', 'on', 'some_label', 123, None, 1000, 1000, None)]
assert _logger.error.mock_calls == [mocker.call('%s: evaluaiton option should be dictionary, setting its value to None.', 'get_treatments')]
assert _logger.error.mock_calls == [mocker.call('%s: properties must be of type dictionary.', 'get_treatments')]

_logger.reset_mock()
assert client.get_treatments('some_key', ['SPLIT_2'], evaluation_options=123) == {'SPLIT_2': 'on'}
assert impression_storage.pop_many(100) == [Impression('some_key', 'SPLIT_2', 'on', 'some_label', 123, None, 1000, 1000, None)]
assert _logger.error.mock_calls == [mocker.call('%s: evaluaiton option should be dictionary, setting its value to None.', 'get_treatments')]
assert _logger.error.mock_calls == [mocker.call('%s: evaluaiton option should be an instance of EvaluationOptions, setting its value to None.', 'get_treatments')]
Comment thread
chillaq marked this conversation as resolved.
Outdated

assert client.get_treatments_with_config('some_key', ['SPLIT_2'], evaluation_options={"properties":{"prop": "value"}}) == {'SPLIT_2': ('on', None)}
assert client.get_treatments_with_config('some_key', ['SPLIT_2'], evaluation_options=EvaluationOptions({"prop": "value"})) == {'SPLIT_2': ('on', None)}
assert impression_storage.pop_many(100) == [Impression('some_key', 'SPLIT_2', 'on', 'some_label', 123, None, 1000, None, '{"prop": "value"}')]

assert client.get_treatments_by_flag_set('some_key', 'set_1', evaluation_options={"properties":{"prop": "value"}}) == {'SPLIT_2': 'on'}
assert client.get_treatments_by_flag_set('some_key', 'set_1', evaluation_options=EvaluationOptions({"prop": "value"})) == {'SPLIT_2': 'on'}
assert impression_storage.pop_many(100) == [Impression('some_key', 'SPLIT_2', 'on', 'some_label', 123, None, 1000, None, '{"prop": "value"}')]

assert client.get_treatments_by_flag_sets('some_key', ['set_1'], evaluation_options={"properties":{"prop": "value"}}) == {'SPLIT_2': 'on'}
assert client.get_treatments_by_flag_sets('some_key', ['set_1'], evaluation_options=EvaluationOptions({"prop": "value"})) == {'SPLIT_2': 'on'}
assert impression_storage.pop_many(100) == [Impression('some_key', 'SPLIT_2', 'on', 'some_label', 123, None, 1000, None, '{"prop": "value"}')]

assert client.get_treatments_with_config_by_flag_set('some_key', 'set_1', evaluation_options={"properties":{"prop": "value"}}) == {'SPLIT_2': ('on', None)}
assert client.get_treatments_with_config_by_flag_set('some_key', 'set_1', evaluation_options=EvaluationOptions({"prop": "value"})) == {'SPLIT_2': ('on', None)}
assert impression_storage.pop_many(100) == [Impression('some_key', 'SPLIT_2', 'on', 'some_label', 123, None, 1000, None, '{"prop": "value"}')]

assert client.get_treatments_with_config_by_flag_sets('some_key', ['set_1'], evaluation_options={"properties":{"prop": "value"}}) == {'SPLIT_2': ('on', None)}
assert client.get_treatments_with_config_by_flag_sets('some_key', ['set_1'], evaluation_options=EvaluationOptions({"prop": "value"})) == {'SPLIT_2': ('on', None)}
assert impression_storage.pop_many(100) == [Impression('some_key', 'SPLIT_2', 'on', 'some_label', 123, None, 1000, None, '{"prop": "value"}')]

class ClientAsyncTests(object): # pylint: disable=too-few-public-methods
Expand Down Expand Up @@ -2538,50 +2538,50 @@ async def synchronize_config(*_):

_logger = mocker.Mock()
mocker.patch('splitio.client.input_validator._LOGGER', new=_logger)
assert await client.get_treatment('some_key', 'SPLIT_2', evaluation_options={"properties":{"prop": "value"}}) == 'on'
assert await client.get_treatment('some_key', 'SPLIT_2', evaluation_options=EvaluationOptions({"prop": "value"})) == 'on'
assert await impression_storage.pop_many(100) == [Impression('some_key', 'SPLIT_2', 'on', 'some_label', 123, None, 1000, None, '{"prop": "value"}')]

assert await client.get_treatment('some_key', 'SPLIT_2', evaluation_options=12) == 'on'
assert await client.get_treatment('some_key', 'SPLIT_2', evaluation_options=EvaluationOptions(12)) == 'on'
assert await impression_storage.pop_many(100) == [Impression('some_key', 'SPLIT_2', 'on', 'some_label', 123, None, 1000, None, None)]
assert _logger.error.mock_calls == [mocker.call('%s: evaluaiton option should be dictionary, setting its value to None.', 'get_treatment')]
assert _logger.error.mock_calls == [mocker.call('%s: properties must be of type dictionary.', 'get_treatment')]

_logger.reset_mock()
assert await client.get_treatment('some_key', 'SPLIT_2', evaluation_options='12') == 'on'
assert await client.get_treatment('some_key', 'SPLIT_2', evaluation_options=EvaluationOptions('12')) == 'on'
assert await impression_storage.pop_many(100) == [Impression('some_key', 'SPLIT_2', 'on', 'some_label', 123, None, 1000, 1000, None)]
assert _logger.error.mock_calls == [mocker.call('%s: evaluaiton option should be dictionary, setting its value to None.', 'get_treatment')]
assert _logger.error.mock_calls == [mocker.call('%s: properties must be of type dictionary.', 'get_treatment')]

_logger.reset_mock()
assert await client.get_treatment('some_key', 'SPLIT_2', evaluation_options={"property":{"prop": "value"}}) == 'on'
assert await impression_storage.pop_many(100) == [Impression('some_key', 'SPLIT_2', 'on', 'some_label', 123, None, 1000, 1000, None)]
assert _logger.error.mock_calls == [mocker.call('%s: evaluaiton option must have `properties` key, setting its value to None.', 'get_treatment')]

assert await client.get_treatment_with_config('some_key', 'SPLIT_2', evaluation_options={"properties":{"prop": "value"}}) == ('on', None)
assert await client.get_treatment_with_config('some_key', 'SPLIT_2', evaluation_options=EvaluationOptions({"prop": "value"})) == ('on', None)
assert await impression_storage.pop_many(100) == [Impression('some_key', 'SPLIT_2', 'on', 'some_label', 123, None, 1000, None, '{"prop": "value"}')]

assert await client.get_treatments('some_key', ['SPLIT_2'], evaluation_options={"properties":{"prop": "value"}}) == {'SPLIT_2': 'on'}
assert await client.get_treatments('some_key', ['SPLIT_2'], evaluation_options=EvaluationOptions({"prop": "value"})) == {'SPLIT_2': 'on'}
assert await impression_storage.pop_many(100) == [Impression('some_key', 'SPLIT_2', 'on', 'some_label', 123, None, 1000, None, '{"prop": "value"}')]

_logger.reset_mock()
assert await client.get_treatments('some_key', ['SPLIT_2'], evaluation_options="prop") == {'SPLIT_2': 'on'}
assert await client.get_treatments('some_key', ['SPLIT_2'], evaluation_options=EvaluationOptions("prop")) == {'SPLIT_2': 'on'}
assert await impression_storage.pop_many(100) == [Impression('some_key', 'SPLIT_2', 'on', 'some_label', 123, None, 1000, 1000, None)]
assert _logger.error.mock_calls == [mocker.call('%s: properties must be of type dictionary.', 'get_treatments')]

_logger.reset_mock()
assert await client.get_treatments('some_key', ['SPLIT_2'], evaluation_options=EvaluationOptions(123)) == {'SPLIT_2': 'on'}
assert await impression_storage.pop_many(100) == [Impression('some_key', 'SPLIT_2', 'on', 'some_label', 123, None, 1000, 1000, None)]
assert _logger.error.mock_calls == [mocker.call('%s: evaluaiton option should be dictionary, setting its value to None.', 'get_treatments')]
assert _logger.error.mock_calls == [mocker.call('%s: properties must be of type dictionary.', 'get_treatments')]

_logger.reset_mock()
assert await client.get_treatments('some_key', ['SPLIT_2'], evaluation_options=123) == {'SPLIT_2': 'on'}
assert await impression_storage.pop_many(100) == [Impression('some_key', 'SPLIT_2', 'on', 'some_label', 123, None, 1000, 1000, None)]
assert _logger.error.mock_calls == [mocker.call('%s: evaluaiton option should be dictionary, setting its value to None.', 'get_treatments')]
assert _logger.error.mock_calls == [mocker.call('%s: evaluaiton option should be an instance of EvaluationOptions, setting its value to None.', 'get_treatments')]
Comment thread
chillaq marked this conversation as resolved.
Outdated

assert await client.get_treatments_with_config('some_key', ['SPLIT_2'], evaluation_options={"properties":{"prop": "value"}}) == {'SPLIT_2': ('on', None)}
assert await client.get_treatments_with_config('some_key', ['SPLIT_2'], evaluation_options=EvaluationOptions({"prop": "value"})) == {'SPLIT_2': ('on', None)}
assert await impression_storage.pop_many(100) == [Impression('some_key', 'SPLIT_2', 'on', 'some_label', 123, None, 1000, None, '{"prop": "value"}')]

assert await client.get_treatments_by_flag_set('some_key', 'set_1', evaluation_options={"properties":{"prop": "value"}}) == {'SPLIT_2': 'on'}
assert await client.get_treatments_by_flag_set('some_key', 'set_1', evaluation_options=EvaluationOptions({"prop": "value"})) == {'SPLIT_2': 'on'}
assert await impression_storage.pop_many(100) == [Impression('some_key', 'SPLIT_2', 'on', 'some_label', 123, None, 1000, None, '{"prop": "value"}')]

assert await client.get_treatments_by_flag_sets('some_key', ['set_1'], evaluation_options={"properties":{"prop": "value"}}) == {'SPLIT_2': 'on'}
assert await client.get_treatments_by_flag_sets('some_key', ['set_1'], evaluation_options=EvaluationOptions({"prop": "value"})) == {'SPLIT_2': 'on'}
assert await impression_storage.pop_many(100) == [Impression('some_key', 'SPLIT_2', 'on', 'some_label', 123, None, 1000, None, '{"prop": "value"}')]

assert await client.get_treatments_with_config_by_flag_set('some_key', 'set_1', evaluation_options={"properties":{"prop": "value"}}) == {'SPLIT_2': ('on', None)}
assert await client.get_treatments_with_config_by_flag_set('some_key', 'set_1', evaluation_options=EvaluationOptions({"prop": "value"})) == {'SPLIT_2': ('on', None)}
assert await impression_storage.pop_many(100) == [Impression('some_key', 'SPLIT_2', 'on', 'some_label', 123, None, 1000, None, '{"prop": "value"}')]

assert await client.get_treatments_with_config_by_flag_sets('some_key', ['set_1'], evaluation_options={"properties":{"prop": "value"}}) == {'SPLIT_2': ('on', None)}
assert await client.get_treatments_with_config_by_flag_sets('some_key', ['set_1'], evaluation_options=EvaluationOptions({"prop": "value"})) == {'SPLIT_2': ('on', None)}
assert await impression_storage.pop_many(100) == [Impression('some_key', 'SPLIT_2', 'on', 'some_label', 123, None, 1000, None, '{"prop": "value"}')]
5 changes: 3 additions & 2 deletions tests/integration/test_client_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
from splitio.exceptions import TimeoutException
from splitio.client.factory import get_factory, SplitFactory, get_factory_async, SplitFactoryAsync
from splitio.client.util import SdkMetadata
from splitio.client.config import DEFAULT_CONFIG
from splitio.client.client import EvaluationOptions
from splitio.storage.inmemmory import InMemoryEventStorage, InMemoryImpressionStorage, \
InMemorySegmentStorage, InMemorySplitStorage, InMemoryTelemetryStorage, InMemorySplitStorageAsync,\
InMemoryEventStorageAsync, InMemoryImpressionStorageAsync, InMemorySegmentStorageAsync, \
Expand All @@ -35,7 +37,6 @@
from splitio.engine.impressions.manager import Counter as ImpressionsCounter
from splitio.engine.impressions.unique_keys_tracker import UniqueKeysTracker, UniqueKeysTrackerAsync
from splitio.recorder.recorder import StandardRecorder, PipelinedRecorder, StandardRecorderAsync, PipelinedRecorderAsync
from splitio.client.config import DEFAULT_CONFIG
from splitio.sync.synchronizer import SplitTasks, SplitSynchronizers, Synchronizer, RedisSynchronizer, SynchronizerAsync,\
RedisSynchronizerAsync
from splitio.sync.manager import Manager, RedisManager, ManagerAsync, RedisManagerAsync
Expand Down Expand Up @@ -122,7 +123,7 @@ def _get_treatment(factory, skip_rbs=False):
except:
pass

assert client.get_treatment('user1', 'sample_feature', evaluation_options={"properties":{"prop": "value"}}) == 'on'
assert client.get_treatment('user1', 'sample_feature', evaluation_options=EvaluationOptions({"prop": "value"})) == 'on'
if not isinstance(factory._recorder._impressions_manager._strategy, StrategyNoneMode):
_validate_last_impressions(client, ('sample_feature', 'user1', 'on', '{"prop": "value"}'))

Expand Down