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
13 changes: 10 additions & 3 deletions optimizely/config_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
from . import exceptions as optimizely_exceptions
from . import logger as optimizely_logger
from . import project_config
from .error_handler import NoOpErrorHandler as noop_error_handler
from .error_handler import NoOpErrorHandler
from .notification_center import NotificationCenter
from .helpers import enums
from .helpers import validator

Expand All @@ -41,7 +42,7 @@ def __init__(self,
error_handler: Provides a handle_error method to handle exceptions.
"""
self.logger = logger or optimizely_logger.adapt_logger(logger or optimizely_logger.NoOpLogger())
self.error_handler = error_handler or noop_error_handler
self.error_handler = error_handler or NoOpErrorHandler()

@abc.abstractmethod
def get_config(self):
Expand All @@ -57,18 +58,21 @@ def __init__(self,
datafile=None,
logger=None,
error_handler=None,
notification_center=None,
skip_json_validation=False):
""" Initialize config manager. Datafile has to be provided to use.

Args:
datafile: JSON string representing the Optimizely project.
logger: Provides a logger instance.
error_handler: Provides a handle_error method to handle exceptions.
notification_center: Notification center to generate config update notification.
skip_json_validation: Optional boolean param which allows skipping JSON schema
validation upon object invocation. By default
JSON schema validation will be performed.
"""
super(StaticConfigManager, self).__init__(logger=logger, error_handler=error_handler)
self.notification_center = notification_center or NotificationCenter(self.logger)
self._config = None
self.validate_schema = not skip_json_validation
self._set_config(datafile)
Expand Down Expand Up @@ -108,8 +112,8 @@ def _set_config(self, datafile):
if previous_revision == config.get_revision():
return

# TODO(ali): Add notification listener.
self._config = config
self.notification_center.send_notifications(enums.NotificationTypes.OPTIMIZELY_CONFIG_UPDATE)
self.logger.debug(
'Received new datafile and updated config. '
'Old revision number: {}. New revision number: {}.'.format(previous_revision, config.get_revision())
Expand All @@ -135,6 +139,7 @@ def __init__(self,
url_template=None,
logger=None,
error_handler=None,
notification_center=None,
skip_json_validation=False):
""" Initialize config manager. One of sdk_key or url has to be set to be able to use.

Expand All @@ -148,13 +153,15 @@ def __init__(self,
determines URL from where to fetch the datafile.
logger: Provides a logger instance.
error_handler: Provides a handle_error method to handle exceptions.
notification_center: Notification center to generate config update notification.
skip_json_validation: Optional boolean param which allows skipping JSON schema
validation upon object invocation. By default
JSON schema validation will be performed.

"""
super(PollingConfigManager, self).__init__(logger=logger,
error_handler=error_handler,
notification_center=notification_center,
skip_json_validation=skip_json_validation)
self.datafile_url = self.get_datafile_url(sdk_key, url,
url_template or enums.ConfigManager.DATAFILE_URL_TEMPLATE)
Expand Down
3 changes: 3 additions & 0 deletions optimizely/helpers/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,12 @@ class NotificationTypes(object):
DECISION notification listener has the following parameters:
DecisionNotificationTypes type, str user_id, dict attributes, dict decision_info

OPTIMIZELY_CONFIG_UPDATE notification listener has no associated parameters.

TRACK notification listener has the following parameters:
str event_key, str user_id, dict attributes (can be None), event_tags (can be None), Event event
"""
ACTIVATE = 'ACTIVATE:experiment, user_id, attributes, variation, event'
DECISION = 'DECISION:type, user_id, attributes, decision_info'
OPTIMIZELY_CONFIG_UPDATE = 'OPTIMIZELY_CONFIG_UPDATE'
TRACK = 'TRACK:event_key, user_id, attributes, event_tags, event'
7 changes: 5 additions & 2 deletions optimizely/optimizely.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from .event_dispatcher import EventDispatcher as default_event_dispatcher
from .helpers import enums
from .helpers import validator
from .notification_center import NotificationCenter as notification_center
from .notification_center import NotificationCenter


class Optimizely(object):
Expand Down Expand Up @@ -70,22 +70,25 @@ def __init__(self,
self.logger.exception(str(error))
return

self.notification_center = NotificationCenter(self.logger)

if not self.config_manager:
if sdk_key:
self.config_manager = PollingConfigManager(sdk_key=sdk_key,
datafile=datafile,
logger=self.logger,
error_handler=self.error_handler,
notification_center=self.notification_center,
skip_json_validation=skip_json_validation)
else:
self.config_manager = StaticConfigManager(datafile=datafile,
logger=self.logger,
error_handler=self.error_handler,
notification_center=self.notification_center,
skip_json_validation=skip_json_validation)

self.event_builder = event_builder.EventBuilder()
self.decision_service = decision_service.DecisionService(self.logger, user_profile_service)
self.notification_center = notification_center(self.logger)

def _validate_instantiation_options(self):
""" Helper method to validate all instantiation parameters.
Expand Down
26 changes: 21 additions & 5 deletions tests/test_config_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,28 +28,38 @@ def test_set_config__success(self):
""" Test set_config when datafile is valid. """
test_datafile = json.dumps(self.config_dict_with_features)
mock_logger = mock.Mock()
mock_notification_center = mock.Mock()
project_config_manager = config_manager.StaticConfigManager(datafile=test_datafile,
logger=mock_logger)
logger=mock_logger,
notification_center=mock_notification_center)

project_config_manager._set_config(test_datafile)
mock_logger.debug.assert_called_with('Received new datafile and updated config. '
'Old revision number: None. New revision number: 1.')
mock_notification_center.send_notifications.assert_called_once_with('OPTIMIZELY_CONFIG_UPDATE')

def test_set_config__twice(self):
""" Test calling set_config twice with same content to ensure config is not updated. """
test_datafile = json.dumps(self.config_dict_with_features)
mock_logger = mock.Mock()
mock_notification_center = mock.Mock()
project_config_manager = config_manager.StaticConfigManager(datafile=test_datafile,
logger=mock_logger)
logger=mock_logger,
notification_center=mock_notification_center)

project_config_manager._set_config(test_datafile)
mock_logger.debug.assert_called_with('Received new datafile and updated config. '
'Old revision number: None. New revision number: 1.')
self.assertEqual(1, mock_logger.debug.call_count)
mock_notification_center.send_notifications.assert_called_once_with('OPTIMIZELY_CONFIG_UPDATE')

mock_logger.reset_mock()
mock_notification_center.reset_mock()

# Call set config again and confirm that no new log message denoting config update is there
project_config_manager._set_config(test_datafile)
self.assertEqual(1, mock_logger.debug.call_count)
self.assertEqual(0, mock_logger.debug.call_count)
self.assertEqual(0, mock_notification_center.call_count)

def test_set_config__schema_validation(self):
""" Test set_config calls or does not call schema validation based on skip_json_validation value. """
Expand Down Expand Up @@ -78,9 +88,11 @@ def test_set_config__unsupported_datafile_version(self):

test_datafile = json.dumps(self.config_dict_with_features)
mock_logger = mock.Mock()
mock_notification_center = mock.Mock()

project_config_manager = config_manager.StaticConfigManager(datafile=test_datafile,
logger=mock_logger)
logger=mock_logger,
notification_center=mock_notification_center)

invalid_version_datafile = self.config_dict_with_features.copy()
invalid_version_datafile['version'] = 'invalid_version'
Expand All @@ -90,19 +102,23 @@ def test_set_config__unsupported_datafile_version(self):
project_config_manager._set_config(test_datafile)
mock_logger.error.assert_called_once_with('This version of the Python SDK does not support '
'the given datafile version: "invalid_version".')
self.assertEqual(0, mock_notification_center.call_count)

def test_set_config__invalid_datafile(self):
""" Test set_config when datafile is invalid. """

test_datafile = json.dumps(self.config_dict_with_features)
mock_logger = mock.Mock()
mock_notification_center = mock.Mock()

project_config_manager = config_manager.StaticConfigManager(datafile=test_datafile,
logger=mock_logger)
logger=mock_logger,
notification_center=mock_notification_center)

# Call set_config with invalid content
project_config_manager._set_config('invalid_datafile')
mock_logger.error.assert_called_once_with('Provided "datafile" is in an invalid format.')
self.assertEqual(0, mock_notification_center.call_count)

def test_get_config(self):
""" Test get_config. """
Expand Down