Skip to content

Commit

Permalink
Merge 5cbf39d into 3731be4
Browse files Browse the repository at this point in the history
  • Loading branch information
rashidsp committed Oct 8, 2019
2 parents 3731be4 + 5cbf39d commit bd2d833
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 2 deletions.
48 changes: 46 additions & 2 deletions optimizely/config_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# limitations under the License.

import abc
import numbers
import requests
import threading
import time
Expand Down Expand Up @@ -95,6 +96,7 @@ def __init__(self,
notification_center=notification_center)
self._config = None
self.validate_schema = not skip_json_validation
self._config_ready_event = threading.Event()
self._set_config(datafile)

def _set_config(self, datafile):
Expand Down Expand Up @@ -133,6 +135,7 @@ def _set_config(self, datafile):
return

self._config = config
self._config_ready_event.set()
self.notification_center.send_notifications(enums.NotificationTypes.OPTIMIZELY_CONFIG_UPDATE)
self.logger.debug(
'Received new datafile and updated config. '
Expand All @@ -145,6 +148,7 @@ def get_config(self):
Returns:
ProjectConfig. None if not set.
"""

return self._config


Expand All @@ -155,6 +159,7 @@ def __init__(self,
sdk_key=None,
datafile=None,
update_interval=None,
blocking_timeout=None,
url=None,
url_template=None,
logger=None,
Expand All @@ -168,6 +173,8 @@ def __init__(self,
datafile: Optional JSON string representing the project.
update_interval: Optional floating point number representing time interval in seconds
at which to request datafile and set ProjectConfig.
blocking_timeout: Optional Time in seconds to block the get_config call until config object
has been initialized.
url: Optional string representing URL from where to fetch the datafile. If set it supersedes the sdk_key.
url_template: Optional string template which in conjunction with sdk_key
determines URL from where to fetch the datafile.
Expand All @@ -187,6 +194,7 @@ def __init__(self,
self.datafile_url = self.get_datafile_url(sdk_key, url,
url_template or enums.ConfigManager.DATAFILE_URL_TEMPLATE)
self.set_update_interval(update_interval)
self.set_blocking_timeout(blocking_timeout)
self.last_modified = None
self._polling_thread = threading.Thread(target=self._run)
self._polling_thread.setDaemon(True)
Expand Down Expand Up @@ -224,15 +232,26 @@ def get_datafile_url(sdk_key, url, url_template):

return url

def get_config(self):
""" Returns instance of ProjectConfig. Returns immediately if project config is ready otherwise
blocks maximum for value of blocking_timeout in seconds.
Returns:
ProjectConfig. None if not set.
"""

self._config_ready_event.wait(self.blocking_timeout)
return self._config

def set_update_interval(self, update_interval):
""" Helper method to set frequency at which datafile has to be polled and ProjectConfig updated.
Args:
update_interval: Time in seconds after which to update datafile.
"""
if not update_interval:
if update_interval is None:
update_interval = enums.ConfigManager.DEFAULT_UPDATE_INTERVAL
self.logger.debug('Set config update interval to default value {}.'.format(update_interval))
self.logger.debug('Setting config update interval to default value {}.'.format(update_interval))

if not isinstance(update_interval, (int, float)):
raise optimizely_exceptions.InvalidInputException(
Expand All @@ -249,6 +268,31 @@ def set_update_interval(self, update_interval):

self.update_interval = update_interval

def set_blocking_timeout(self, blocking_timeout):
""" Helper method to set time in seconds to block the config call until config has been initialized.
Args:
blocking_timeout: Time in seconds to block the config call.
"""
if blocking_timeout is None:
blocking_timeout = enums.ConfigManager.DEFAULT_BLOCKING_TIMEOUT
self.logger.debug('Setting config blocking timeout to default value {}.'.format(blocking_timeout))

if not isinstance(blocking_timeout, (numbers.Integral, float)):
raise optimizely_exceptions.InvalidInputException(
'Invalid blocking timeout "{}" provided.'.format(blocking_timeout)
)

# If blocking timeout is less than 0 then set it to default blocking timeout.
if blocking_timeout < 0:
self.logger.debug('blocking timeout value {} too small. Defaulting to {}'.format(
blocking_timeout,
enums.ConfigManager.DEFAULT_BLOCKING_TIMEOUT)
)
blocking_timeout = enums.ConfigManager.DEFAULT_BLOCKING_TIMEOUT

self.blocking_timeout = blocking_timeout

def set_last_modified(self, response_headers):
""" Looks up and sets last modified time based on Last-Modified header in the response.
Expand Down
2 changes: 2 additions & 0 deletions optimizely/helpers/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ class AudienceEvaluationLogs(object):

class ConfigManager(object):
DATAFILE_URL_TEMPLATE = 'https://cdn.optimizely.com/datafiles/{sdk_key}.json'
# Default time in seconds to block the 'get_config' method call until 'config' instance has been initialized.
DEFAULT_BLOCKING_TIMEOUT = 10
# Default config update interval of 5 minutes
DEFAULT_UPDATE_INTERVAL = 5 * 60
# Time in seconds before which request for datafile times out
Expand Down
32 changes: 32 additions & 0 deletions tests/test_config_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import json
import mock
import requests
import time

from optimizely import config_manager
from optimizely import exceptions as optimizely_exceptions
Expand Down Expand Up @@ -235,6 +236,37 @@ def test_set_update_interval(self, _):
project_config_manager.set_update_interval(42)
self.assertEqual(42, project_config_manager.update_interval)

def test_set_blocking_timeout(self, _):
""" Test set_blocking_timeout with different inputs. """
project_config_manager = config_manager.PollingConfigManager(sdk_key='some_key')

# Assert that if invalid blocking_timeout is set, then exception is raised.
with self.assertRaisesRegexp(optimizely_exceptions.InvalidInputException,
'Invalid blocking timeout "invalid timeout" provided.'):
project_config_manager.set_blocking_timeout('invalid timeout')

# Assert that blocking_timeout cannot be set to less than allowed minimum and instead is set to default value.
project_config_manager.set_blocking_timeout(-4)
self.assertEqual(enums.ConfigManager.DEFAULT_BLOCKING_TIMEOUT, project_config_manager.blocking_timeout)

# Assert that blocking_timeout can be set to 0.
project_config_manager.set_blocking_timeout(0)
self.assertIs(0, project_config_manager.blocking_timeout)

# Assert that if no blocking_timeout is provided, it is set to default value.
project_config_manager.set_blocking_timeout(None)
self.assertEqual(enums.ConfigManager.DEFAULT_BLOCKING_TIMEOUT, project_config_manager.blocking_timeout)

# Assert that if valid blocking_timeout is provided, it is set to that value.
project_config_manager.set_blocking_timeout(5)
self.assertEqual(5, project_config_manager.blocking_timeout)

# Assert get_config should block until blocking timeout.
start_time = time.time()
project_config_manager.get_config()
end_time = time.time()
self.assertEqual(5, round(end_time - start_time))

def test_set_last_modified(self, _):
""" Test that set_last_modified sets last_modified field based on header. """
project_config_manager = config_manager.PollingConfigManager(sdk_key='some_key')
Expand Down

0 comments on commit bd2d833

Please sign in to comment.