Skip to content

Commit cdc652e

Browse files
authored
feat: Optimizely factory added (#325)
* polling interval updated * Revert "polling interval updated" This reverts commit e696f1c. * Create optimizely_factory.py * linting fixed * whitespaced removed * comments addressed and unit tests added * comments addressed * comments addressed
1 parent 7607cf4 commit cdc652e

File tree

2 files changed

+327
-0
lines changed

2 files changed

+327
-0
lines changed

Diff for: optimizely/optimizely_factory.py

+165
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
# Copyright 2021, Optimizely
2+
# Licensed under the Apache License, Version 2.0 (the "License");
3+
# you may not use this file except in compliance with the License.
4+
# You may obtain a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
from . import logger as optimizely_logger
14+
from .config_manager import PollingConfigManager
15+
from .error_handler import NoOpErrorHandler
16+
from .event.event_processor import BatchEventProcessor
17+
from .event_dispatcher import EventDispatcher
18+
from .notification_center import NotificationCenter
19+
from .optimizely import Optimizely
20+
21+
22+
class OptimizelyFactory(object):
23+
""" Optimizely factory to provides basic utility to instantiate the Optimizely
24+
SDK with a minimal number of configuration options."""
25+
26+
max_event_batch_size = None
27+
max_event_flush_interval = None
28+
polling_interval = None
29+
blocking_timeout = None
30+
31+
@staticmethod
32+
def set_batch_size(batch_size):
33+
""" Convenience method for setting the maximum number of events contained within a batch.
34+
Args:
35+
batch_size: Sets size of event_queue.
36+
"""
37+
38+
OptimizelyFactory.max_event_batch_size = batch_size
39+
return OptimizelyFactory.max_event_batch_size
40+
41+
@staticmethod
42+
def set_flush_interval(flush_interval):
43+
""" Convenience method for setting the maximum time interval in milliseconds between event dispatches.
44+
Args:
45+
flush_interval: Time interval between event dispatches.
46+
"""
47+
48+
OptimizelyFactory.max_event_flush_interval = flush_interval
49+
return OptimizelyFactory.max_event_flush_interval
50+
51+
@staticmethod
52+
def set_polling_interval(polling_interval):
53+
""" Method to set frequency at which datafile has to be polled.
54+
Args:
55+
polling_interval: Time in seconds after which to update datafile.
56+
"""
57+
OptimizelyFactory.polling_interval = polling_interval
58+
return OptimizelyFactory.polling_interval
59+
60+
@staticmethod
61+
def set_blocking_timeout(blocking_timeout):
62+
""" Method to set time in seconds to block the config call until config has been initialized.
63+
Args:
64+
blocking_timeout: Time in seconds to block the config call.
65+
"""
66+
OptimizelyFactory.blocking_timeout = blocking_timeout
67+
return OptimizelyFactory.blocking_timeout
68+
69+
@staticmethod
70+
def default_instance(sdk_key, datafile=None):
71+
""" Returns a new optimizely instance..
72+
Args:
73+
sdk_key: Required string uniquely identifying the fallback datafile corresponding to project.
74+
datafile: Optional JSON string datafile.
75+
"""
76+
error_handler = NoOpErrorHandler()
77+
logger = optimizely_logger.NoOpLogger()
78+
notification_center = NotificationCenter(logger)
79+
80+
config_manager_options = {
81+
'sdk_key': sdk_key,
82+
'update_interval': OptimizelyFactory.polling_interval,
83+
'blocking_timeout': OptimizelyFactory.blocking_timeout,
84+
'datafile': datafile,
85+
'logger': logger,
86+
'error_handler': error_handler,
87+
'notification_center': notification_center,
88+
}
89+
90+
config_manager = PollingConfigManager(**config_manager_options)
91+
92+
event_processor = BatchEventProcessor(
93+
event_dispatcher=EventDispatcher(),
94+
logger=logger,
95+
batch_size=OptimizelyFactory.max_event_batch_size,
96+
flush_interval=OptimizelyFactory.max_event_flush_interval,
97+
notification_center=notification_center,
98+
)
99+
100+
optimizely = Optimizely(
101+
datafile, None, logger, error_handler, None, None, sdk_key, config_manager, notification_center,
102+
event_processor
103+
)
104+
return optimizely
105+
106+
@staticmethod
107+
def default_instance_with_config_manager(config_manager):
108+
return Optimizely(
109+
config_manager=config_manager
110+
)
111+
112+
@staticmethod
113+
def custom_instance(sdk_key, datafile=None, event_dispatcher=None, logger=None, error_handler=None,
114+
skip_json_validation=None, user_profile_service=None, config_manager=None,
115+
notification_center=None):
116+
117+
""" Returns a new optimizely instance.
118+
if max_event_batch_size and max_event_flush_interval are None then default batch_size and flush_interval
119+
will be used to setup BatchEventProcessor.
120+
121+
Args:
122+
sdk_key: Required string uniquely identifying the fallback datafile corresponding to project.
123+
datafile: Optional JSON string datafile.
124+
event_dispatcher: Optional EventDispatcher interface provides a dispatch_event method which if given a
125+
URL and params sends a request to it.
126+
logger: Optional Logger interface provides a log method to log messages.
127+
By default nothing would be logged.
128+
error_handler: Optional ErrorHandler interface which provides a handle_error method to handle exceptions.
129+
By default all exceptions will be suppressed.
130+
skip_json_validation: Optional boolean param to skip JSON schema validation of the provided datafile.
131+
user_profile_service: Optional UserProfileService interface provides methods to store and retrieve
132+
user profiles.
133+
config_manager: Optional ConfigManager interface responds to 'config' method.
134+
notification_center: Optional Instance of NotificationCenter.
135+
"""
136+
137+
error_handler = error_handler or NoOpErrorHandler()
138+
logger = logger or optimizely_logger.NoOpLogger()
139+
notification_center = notification_center if isinstance(notification_center,
140+
NotificationCenter) else NotificationCenter(logger)
141+
142+
event_processor = BatchEventProcessor(
143+
event_dispatcher=event_dispatcher or EventDispatcher(),
144+
logger=logger,
145+
batch_size=OptimizelyFactory.max_event_batch_size,
146+
flush_interval=OptimizelyFactory.max_event_flush_interval,
147+
notification_center=notification_center,
148+
)
149+
150+
config_manager_options = {
151+
'sdk_key': sdk_key,
152+
'update_interval': OptimizelyFactory.polling_interval,
153+
'blocking_timeout': OptimizelyFactory.blocking_timeout,
154+
'datafile': datafile,
155+
'logger': logger,
156+
'error_handler': error_handler,
157+
'skip_json_validation': skip_json_validation,
158+
'notification_center': notification_center,
159+
}
160+
config_manager = config_manager or PollingConfigManager(**config_manager_options)
161+
162+
return Optimizely(
163+
datafile, event_dispatcher, logger, error_handler, skip_json_validation, user_profile_service,
164+
sdk_key, config_manager, notification_center, event_processor
165+
)

Diff for: tests/test_optimizely_factory.py

+162
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
# Copyright 2021, Optimizely
2+
# Licensed under the Apache License, Version 2.0 (the "License");
3+
# you may not use this file except in compliance with the License.
4+
# You may obtain a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
14+
import mock
15+
16+
from optimizely.config_manager import PollingConfigManager
17+
from optimizely.error_handler import NoOpErrorHandler
18+
from optimizely.event_dispatcher import EventDispatcher
19+
from optimizely.notification_center import NotificationCenter
20+
from optimizely.optimizely_factory import OptimizelyFactory
21+
from optimizely.user_profile import UserProfileService
22+
from . import base
23+
24+
25+
@mock.patch('requests.get')
26+
class OptimizelyFactoryTest(base.BaseTest):
27+
def setUp(self):
28+
self.datafile = '{ revision: "42" }'
29+
self.error_handler = NoOpErrorHandler()
30+
self.mock_client_logger = mock.MagicMock()
31+
self.notification_center = NotificationCenter(self.mock_client_logger)
32+
self.event_dispatcher = EventDispatcher()
33+
self.user_profile_service = UserProfileService()
34+
35+
def test_default_instance__should_create_config_manager_when_sdk_key_is_given(self, _):
36+
optimizely_instance = OptimizelyFactory.default_instance('sdk_key')
37+
self.assertIsInstance(optimizely_instance.config_manager, PollingConfigManager)
38+
39+
def test_default_instance__should_create_config_manager_when_params_are_set_valid(self, _):
40+
OptimizelyFactory.set_polling_interval(40)
41+
OptimizelyFactory.set_blocking_timeout(5)
42+
OptimizelyFactory.set_flush_interval(30)
43+
OptimizelyFactory.set_batch_size(10)
44+
optimizely_instance = OptimizelyFactory.default_instance('sdk_key', datafile=self.datafile)
45+
# Verify that values set in OptimizelyFactory are being used inside config manager.
46+
self.assertEqual(optimizely_instance.config_manager.update_interval, 40)
47+
self.assertEqual(optimizely_instance.config_manager.blocking_timeout, 5)
48+
# Verify values set for batch_size and flush_interval
49+
self.assertEqual(optimizely_instance.event_processor.flush_interval.seconds, 30)
50+
self.assertEqual(optimizely_instance.event_processor.batch_size, 10)
51+
52+
def test_default_instance__should_create_config_set_default_values_params__invalid(self, _):
53+
OptimizelyFactory.set_polling_interval(-40)
54+
OptimizelyFactory.set_blocking_timeout(-85)
55+
OptimizelyFactory.set_flush_interval(30)
56+
OptimizelyFactory.set_batch_size(10)
57+
58+
optimizely_instance = OptimizelyFactory.default_instance('sdk_key', datafile=self.datafile)
59+
# Verify that values set in OptimizelyFactory are not being used inside config manager.
60+
self.assertEqual(optimizely_instance.config_manager.update_interval, 300)
61+
self.assertEqual(optimizely_instance.config_manager.blocking_timeout, 10)
62+
# Verify values set for batch_size and flush_interval
63+
self.assertEqual(optimizely_instance.event_processor.flush_interval.seconds, 30)
64+
self.assertEqual(optimizely_instance.event_processor.batch_size, 10)
65+
66+
def test_default_instance__should_create_http_config_manager_with_the_same_components_as_the_instance(self, _):
67+
optimizely_instance = OptimizelyFactory.default_instance('sdk_key', None)
68+
self.assertEqual(optimizely_instance.error_handler, optimizely_instance.config_manager.error_handler)
69+
self.assertEqual(optimizely_instance.logger, optimizely_instance.config_manager.logger)
70+
self.assertEqual(optimizely_instance.notification_center,
71+
optimizely_instance.config_manager.notification_center)
72+
73+
def test_custom_instance__should_set_input_values_when_sdk_key_polling_interval_and_blocking_timeout_are_given(
74+
self, _):
75+
OptimizelyFactory.set_polling_interval(50)
76+
OptimizelyFactory.set_blocking_timeout(10)
77+
78+
optimizely_instance = OptimizelyFactory.custom_instance('sdk_key', None, self.event_dispatcher,
79+
self.mock_client_logger, self.error_handler, False,
80+
self.user_profile_service, None,
81+
self.notification_center)
82+
83+
self.assertEqual(optimizely_instance.config_manager.update_interval, 50)
84+
self.assertEqual(optimizely_instance.config_manager.blocking_timeout, 10)
85+
86+
def test_custom_instance__should_set_default_values_when_sdk_key_polling_interval_and_blocking_timeout_are_invalid(
87+
self, _):
88+
OptimizelyFactory.set_polling_interval(-50)
89+
OptimizelyFactory.set_blocking_timeout(-10)
90+
optimizely_instance = OptimizelyFactory.custom_instance('sdk_key', None, self.event_dispatcher,
91+
self.mock_client_logger, self.error_handler, False,
92+
self.user_profile_service, None,
93+
self.notification_center)
94+
self.assertEqual(optimizely_instance.config_manager.update_interval, 300)
95+
self.assertEqual(optimizely_instance.config_manager.blocking_timeout, 10)
96+
97+
def test_custom_instance__should_take_event_processor_when_flush_interval_and_batch_size_are_set_valid(self, _):
98+
OptimizelyFactory.set_flush_interval(5)
99+
OptimizelyFactory.set_batch_size(100)
100+
101+
optimizely_instance = OptimizelyFactory.custom_instance('sdk_key')
102+
self.assertEqual(optimizely_instance.event_processor.flush_interval.seconds, 5)
103+
self.assertEqual(optimizely_instance.event_processor.batch_size, 100)
104+
105+
def test_custom_instance__should_take_event_processor_set_default_values_when_flush_int_and_batch_size_are_invalid(
106+
self, _):
107+
OptimizelyFactory.set_flush_interval(-50)
108+
OptimizelyFactory.set_batch_size(-100)
109+
optimizely_instance = OptimizelyFactory.custom_instance('sdk_key')
110+
self.assertEqual(optimizely_instance.event_processor.flush_interval.seconds, 30)
111+
self.assertEqual(optimizely_instance.event_processor.batch_size, 10)
112+
113+
def test_custom_instance__should_assign_passed_components_to_both_the_instance_and_config_manager(self, _):
114+
optimizely_instance = OptimizelyFactory.custom_instance('sdk_key', None, self.event_dispatcher,
115+
self.mock_client_logger, self.error_handler, False,
116+
self.user_profile_service, None,
117+
self.notification_center)
118+
# Config manager assertion
119+
self.assertEqual(self.error_handler, optimizely_instance.config_manager.error_handler)
120+
self.assertEqual(self.mock_client_logger, optimizely_instance.config_manager.logger)
121+
self.assertEqual(self.notification_center,
122+
optimizely_instance.config_manager.notification_center)
123+
124+
# instance assertions
125+
self.assertEqual(self.error_handler, optimizely_instance.error_handler)
126+
self.assertEqual(self.mock_client_logger, optimizely_instance.logger)
127+
self.assertEqual(self.notification_center,
128+
optimizely_instance.notification_center)
129+
130+
def test_set_batch_size_and_set_flush_interval___should_set_values_valid_or_invalid(self, _):
131+
132+
# pass valid value so no default value is set
133+
OptimizelyFactory.set_flush_interval(5)
134+
OptimizelyFactory.set_batch_size(100)
135+
optimizely_instance = OptimizelyFactory.custom_instance('sdk_key')
136+
self.assertEqual(optimizely_instance.event_processor.flush_interval.seconds, 5)
137+
self.assertEqual(optimizely_instance.event_processor.batch_size, 100)
138+
139+
# pass invalid value so set default value
140+
OptimizelyFactory.set_flush_interval('test')
141+
OptimizelyFactory.set_batch_size('test')
142+
optimizely_instance = OptimizelyFactory.custom_instance('sdk_key')
143+
self.assertEqual(optimizely_instance.event_processor.flush_interval.seconds, 30)
144+
self.assertEqual(optimizely_instance.event_processor.batch_size, 10)
145+
146+
OptimizelyFactory.set_flush_interval(20.5)
147+
OptimizelyFactory.set_batch_size(85.5)
148+
optimizely_instance = OptimizelyFactory.custom_instance('sdk_key')
149+
self.assertEqual(optimizely_instance.event_processor.flush_interval.seconds, 20)
150+
self.assertEqual(optimizely_instance.event_processor.batch_size, 10)
151+
152+
OptimizelyFactory.set_flush_interval(None)
153+
OptimizelyFactory.set_batch_size(None)
154+
optimizely_instance = OptimizelyFactory.custom_instance('sdk_key')
155+
self.assertEqual(optimizely_instance.event_processor.flush_interval.seconds, 30)
156+
self.assertEqual(optimizely_instance.event_processor.batch_size, 10)
157+
158+
OptimizelyFactory.set_flush_interval(True)
159+
OptimizelyFactory.set_batch_size(True)
160+
optimizely_instance = OptimizelyFactory.custom_instance('sdk_key')
161+
self.assertEqual(optimizely_instance.event_processor.flush_interval.seconds, 30)
162+
self.assertEqual(optimizely_instance.event_processor.batch_size, 10)

0 commit comments

Comments
 (0)