Skip to content

Commit e7c3076

Browse files
Making notification_center optional for optimizely.Optimizely. (#186)
1 parent dd0ff90 commit e7c3076

File tree

5 files changed

+111
-31
lines changed

5 files changed

+111
-31
lines changed

optimizely/config_manager.py

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,34 @@ class BaseConfigManager(ABC):
3434

3535
def __init__(self,
3636
logger=None,
37-
error_handler=None):
37+
error_handler=None,
38+
notification_center=None):
3839
""" Initialize config manager.
3940
4041
Args:
4142
logger: Provides a logger instance.
4243
error_handler: Provides a handle_error method to handle exceptions.
44+
notification_center: Provides instance of notification_center.NotificationCenter.
4345
"""
4446
self.logger = optimizely_logger.adapt_logger(logger or optimizely_logger.NoOpLogger())
4547
self.error_handler = error_handler or NoOpErrorHandler()
48+
self.notification_center = notification_center or NotificationCenter(self.logger)
49+
self._validate_instantiation_options()
50+
51+
def _validate_instantiation_options(self):
52+
""" Helper method to validate all parameters.
53+
54+
Raises:
55+
Exception if provided options are invalid.
56+
"""
57+
if not validator.is_logger_valid(self.logger):
58+
raise optimizely_exceptions.InvalidInputException(enums.Errors.INVALID_INPUT.format('logger'))
59+
60+
if not validator.is_error_handler_valid(self.error_handler):
61+
raise optimizely_exceptions.InvalidInputException(enums.Errors.INVALID_INPUT.format('error_handler'))
62+
63+
if not validator.is_notification_center_valid(self.notification_center):
64+
raise optimizely_exceptions.InvalidInputException(enums.Errors.INVALID_INPUT.format('notification_center'))
4665

4766
@abc.abstractmethod
4867
def get_config(self):
@@ -71,8 +90,9 @@ def __init__(self,
7190
validation upon object invocation. By default
7291
JSON schema validation will be performed.
7392
"""
74-
super(StaticConfigManager, self).__init__(logger=logger, error_handler=error_handler)
75-
self.notification_center = notification_center or NotificationCenter(self.logger)
93+
super(StaticConfigManager, self).__init__(logger=logger,
94+
error_handler=error_handler,
95+
notification_center=notification_center)
7696
self._config = None
7797
self.validate_schema = not skip_json_validation
7898
self._set_config(datafile)
@@ -159,19 +179,17 @@ def __init__(self,
159179
JSON schema validation will be performed.
160180
161181
"""
162-
super(PollingConfigManager, self).__init__(logger=logger,
182+
super(PollingConfigManager, self).__init__(datafile=datafile,
183+
logger=logger,
163184
error_handler=error_handler,
164185
notification_center=notification_center,
165186
skip_json_validation=skip_json_validation)
166187
self.datafile_url = self.get_datafile_url(sdk_key, url,
167188
url_template or enums.ConfigManager.DATAFILE_URL_TEMPLATE)
168189
self.set_update_interval(update_interval)
169190
self.last_modified = None
170-
self._config = None
171191
self._polling_thread = threading.Thread(target=self._run)
172192
self._polling_thread.setDaemon(True)
173-
if datafile:
174-
self._set_config(datafile)
175193
self._polling_thread.start()
176194

177195
@staticmethod

optimizely/helpers/validator.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import numbers
1818
from six import string_types
1919

20+
from optimizely.notification_center import NotificationCenter
2021
from optimizely.user_profile import UserProfile
2122
from . import constants
2223

@@ -110,6 +111,19 @@ def is_logger_valid(logger):
110111
return _has_method(logger, 'log')
111112

112113

114+
def is_notification_center_valid(notification_center):
115+
""" Given notification_center determine if it is valid or not.
116+
117+
Args:
118+
notification_center: Instance of notification_center.NotificationCenter
119+
120+
Returns:
121+
Boolean denoting instance is valid or not.
122+
"""
123+
124+
return isinstance(notification_center, NotificationCenter)
125+
126+
113127
def are_attributes_valid(attributes):
114128
""" Determine if attributes provided are dict or not.
115129

optimizely/optimizely.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ def __init__(self,
3737
skip_json_validation=False,
3838
user_profile_service=None,
3939
sdk_key=None,
40-
config_manager=None):
40+
config_manager=None,
41+
notification_center=None):
4142
""" Optimizely init method for managing Custom projects.
4243
4344
Args:
@@ -52,13 +53,17 @@ def __init__(self,
5253
sdk_key: Optional string uniquely identifying the datafile corresponding to project and environment combination.
5354
Must provide at least one of datafile or sdk_key.
5455
config_manager: Optional component which implements optimizely.config_manager.BaseConfigManager.
56+
notification_center: Optional instance of notification_center.NotificationCenter. Useful when providing own
57+
config_manager.BaseConfigManager implementation which can be using the
58+
same NotificationCenter instance.
5559
"""
5660
self.logger_name = '.'.join([__name__, self.__class__.__name__])
5761
self.is_valid = True
5862
self.event_dispatcher = event_dispatcher or default_event_dispatcher
5963
self.logger = _logging.adapt_logger(logger or _logging.NoOpLogger())
6064
self.error_handler = error_handler or noop_error_handler
6165
self.config_manager = config_manager
66+
self.notification_center = notification_center or NotificationCenter(self.logger)
6267

6368
try:
6469
self._validate_instantiation_options()
@@ -70,8 +75,6 @@ def __init__(self,
7075
self.logger.exception(str(error))
7176
return
7277

73-
self.notification_center = NotificationCenter(self.logger)
74-
7578
if not self.config_manager:
7679
if sdk_key:
7780
self.config_manager = PollingConfigManager(sdk_key=sdk_key,
@@ -96,7 +99,6 @@ def _validate_instantiation_options(self):
9699
Raises:
97100
Exception if provided instantiation options are valid.
98101
"""
99-
100102
if self.config_manager and not validator.is_config_manager_valid(self.config_manager):
101103
raise exceptions.InvalidInputException(enums.Errors.INVALID_INPUT.format('config_manager'))
102104

@@ -109,6 +111,9 @@ def _validate_instantiation_options(self):
109111
if not validator.is_error_handler_valid(self.error_handler):
110112
raise exceptions.InvalidInputException(enums.Errors.INVALID_INPUT.format('error_handler'))
111113

114+
if not validator.is_notification_center_valid(self.notification_center):
115+
raise exceptions.InvalidInputException(enums.Errors.INVALID_INPUT.format('notification_center'))
116+
112117
def _validate_user_inputs(self, attributes=None, event_tags=None):
113118
""" Helper method to validate user inputs.
114119

tests/test_config_manager.py

Lines changed: 50 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,40 @@
2424

2525

2626
class StaticConfigManagerTest(base.BaseTest):
27+
def test_init__invalid_logger_fails(self):
28+
""" Test that initialization fails if logger is invalid. """
29+
class InvalidLogger(object):
30+
pass
31+
with self.assertRaisesRegexp(optimizely_exceptions.InvalidInputException,
32+
'Provided "logger" is in an invalid format.'):
33+
config_manager.StaticConfigManager(logger=InvalidLogger())
34+
35+
def test_init__invalid_error_handler_fails(self):
36+
""" Test that initialization fails if error_handler is invalid. """
37+
class InvalidErrorHandler(object):
38+
pass
39+
with self.assertRaisesRegexp(optimizely_exceptions.InvalidInputException,
40+
'Provided "error_handler" is in an invalid format.'):
41+
config_manager.StaticConfigManager(error_handler=InvalidErrorHandler())
42+
43+
def test_init__invalid_notification_center_fails(self):
44+
""" Test that initialization fails if notification_center is invalid. """
45+
class InvalidNotificationCenter(object):
46+
pass
47+
with self.assertRaisesRegexp(optimizely_exceptions.InvalidInputException,
48+
'Provided "notification_center" is in an invalid format.'):
49+
config_manager.StaticConfigManager(notification_center=InvalidNotificationCenter())
50+
2751
def test_set_config__success(self):
2852
""" Test set_config when datafile is valid. """
2953
test_datafile = json.dumps(self.config_dict_with_features)
3054
mock_logger = mock.Mock()
3155
mock_notification_center = mock.Mock()
32-
project_config_manager = config_manager.StaticConfigManager(datafile=test_datafile,
33-
logger=mock_logger,
34-
notification_center=mock_notification_center)
56+
57+
with mock.patch('optimizely.config_manager.BaseConfigManager._validate_instantiation_options'):
58+
project_config_manager = config_manager.StaticConfigManager(datafile=test_datafile,
59+
logger=mock_logger,
60+
notification_center=mock_notification_center)
3561

3662
project_config_manager._set_config(test_datafile)
3763
mock_logger.debug.assert_called_with('Received new datafile and updated config. '
@@ -43,9 +69,11 @@ def test_set_config__twice(self):
4369
test_datafile = json.dumps(self.config_dict_with_features)
4470
mock_logger = mock.Mock()
4571
mock_notification_center = mock.Mock()
46-
project_config_manager = config_manager.StaticConfigManager(datafile=test_datafile,
47-
logger=mock_logger,
48-
notification_center=mock_notification_center)
72+
73+
with mock.patch('optimizely.config_manager.BaseConfigManager._validate_instantiation_options'):
74+
project_config_manager = config_manager.StaticConfigManager(datafile=test_datafile,
75+
logger=mock_logger,
76+
notification_center=mock_notification_center)
4977

5078
project_config_manager._set_config(test_datafile)
5179
mock_logger.debug.assert_called_with('Received new datafile and updated config. '
@@ -71,16 +99,16 @@ def test_set_config__schema_validation(self):
7199
# Note: set_config is called in __init__ itself.
72100
with mock.patch('optimizely.helpers.validator.is_datafile_valid',
73101
return_value=True) as mock_validate_datafile:
74-
config_manager.StaticConfigManager(datafile=test_datafile,
75-
logger=mock_logger)
102+
config_manager.StaticConfigManager(datafile=test_datafile,
103+
logger=mock_logger)
76104
mock_validate_datafile.assert_called_once_with(test_datafile)
77105

78106
# Test that schema is not validated if skip_json_validation option is set to True.
79107
with mock.patch('optimizely.helpers.validator.is_datafile_valid',
80108
return_value=True) as mock_validate_datafile:
81-
config_manager.StaticConfigManager(datafile=test_datafile,
82-
logger=mock_logger,
83-
skip_json_validation=True)
109+
config_manager.StaticConfigManager(datafile=test_datafile,
110+
logger=mock_logger,
111+
skip_json_validation=True)
84112
mock_validate_datafile.assert_not_called()
85113

86114
def test_set_config__unsupported_datafile_version(self):
@@ -90,9 +118,10 @@ def test_set_config__unsupported_datafile_version(self):
90118
mock_logger = mock.Mock()
91119
mock_notification_center = mock.Mock()
92120

93-
project_config_manager = config_manager.StaticConfigManager(datafile=test_datafile,
94-
logger=mock_logger,
95-
notification_center=mock_notification_center)
121+
with mock.patch('optimizely.config_manager.BaseConfigManager._validate_instantiation_options'):
122+
project_config_manager = config_manager.StaticConfigManager(datafile=test_datafile,
123+
logger=mock_logger,
124+
notification_center=mock_notification_center)
96125

97126
invalid_version_datafile = self.config_dict_with_features.copy()
98127
invalid_version_datafile['version'] = 'invalid_version'
@@ -111,9 +140,10 @@ def test_set_config__invalid_datafile(self):
111140
mock_logger = mock.Mock()
112141
mock_notification_center = mock.Mock()
113142

114-
project_config_manager = config_manager.StaticConfigManager(datafile=test_datafile,
115-
logger=mock_logger,
116-
notification_center=mock_notification_center)
143+
with mock.patch('optimizely.config_manager.BaseConfigManager._validate_instantiation_options'):
144+
project_config_manager = config_manager.StaticConfigManager(datafile=test_datafile,
145+
logger=mock_logger,
146+
notification_center=mock_notification_center)
117147

118148
# Call set_config with invalid content
119149
project_config_manager._set_config('invalid_datafile')
@@ -220,7 +250,7 @@ def test_set_last_modified(self, _):
220250
def test_fetch_datafile(self, _):
221251
""" Test that fetch_datafile sets config and last_modified based on response. """
222252
with mock.patch('optimizely.config_manager.PollingConfigManager.fetch_datafile'):
223-
project_config_manager = config_manager.PollingConfigManager(sdk_key='some_key')
253+
project_config_manager = config_manager.PollingConfigManager(sdk_key='some_key')
224254
expected_datafile_url = 'https://cdn.optimizely.com/datafiles/some_key.json'
225255
test_headers = {
226256
'Last-Modified': 'New Time'
@@ -249,6 +279,6 @@ def test_fetch_datafile(self, _):
249279
def test_is_running(self, _):
250280
""" Test that polling thread is running after instance of PollingConfigManager is created. """
251281
with mock.patch('optimizely.config_manager.PollingConfigManager.fetch_datafile') as mock_fetch_datafile:
252-
project_config_manager = config_manager.PollingConfigManager(sdk_key='some_key')
253-
self.assertTrue(project_config_manager.is_running)
282+
project_config_manager = config_manager.PollingConfigManager(sdk_key='some_key')
283+
self.assertTrue(project_config_manager.is_running)
254284
mock_fetch_datafile.assert_called_with()

tests/test_optimizely.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,19 @@ class InvalidErrorHandler(object):
155155
mock_client_logger.exception.assert_called_once_with('Provided "error_handler" is in an invalid format.')
156156
self.assertFalse(opt_obj.is_valid)
157157

158+
def test_init__invalid_notification_center__logs_error(self):
159+
""" Test that invalid notification_center logs error on init. """
160+
161+
class InvalidNotificationCenter(object):
162+
pass
163+
164+
mock_client_logger = mock.MagicMock()
165+
with mock.patch('optimizely.logger.reset_logger', return_value=mock_client_logger):
166+
opt_obj = optimizely.Optimizely(json.dumps(self.config_dict), notification_center=InvalidNotificationCenter())
167+
168+
mock_client_logger.exception.assert_called_once_with('Provided "notification_center" is in an invalid format.')
169+
self.assertFalse(opt_obj.is_valid)
170+
158171
def test_init__unsupported_datafile_version__logs_error(self):
159172
""" Test that datafile with unsupported version logs error on init. """
160173

0 commit comments

Comments
 (0)