Skip to content

Commit 5183350

Browse files
Merge branch 'master' into oakbani/hook-fps
2 parents d539b21 + 5d35a59 commit 5183350

File tree

6 files changed

+131
-26
lines changed

6 files changed

+131
-26
lines changed

.travis.yml

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ stages:
2020
- 'Integration tests'
2121
- 'Full stack production tests'
2222
- 'Test'
23+
- 'Source Clear'
2324

2425
jobs:
2526
include:
@@ -32,18 +33,6 @@ jobs:
3233
notifications:
3334
email: false
3435

35-
- stage: 'Lint markdown files'
36-
os: linux
37-
language: generic
38-
before_install: skip
39-
install:
40-
- npm i -g markdown-spellcheck
41-
before_script:
42-
- wget --quiet https://raw.githubusercontent.com/optimizely/mdspell-config/master/.spelling
43-
script:
44-
- mdspell -a -n -r --en-us '**/*.md'
45-
after_success: skip
46-
4736
- stage: 'Linting'
4837
language: python
4938
python: "2.7"
@@ -74,12 +63,18 @@ jobs:
7463
FULLSTACK_TEST_REPO=ProdTesting
7564

7665
- stage: 'Test'
77-
addons:
78-
srcclr: true
7966
dist: xenial
8067
python: "3.7"
8168
- stage: 'Test'
82-
addons:
83-
srcclr: true
8469
dist: xenial
8570
python: "3.8"
71+
72+
- stage: 'Source Clear'
73+
if: type = cron
74+
addons:
75+
srcclr: true
76+
before_install: skip
77+
install: skip
78+
before_script: skip
79+
script: skip
80+
after_success: skip

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Optimizely Python SDK Changelog
22

3+
## 3.5.2
4+
July 14th, 2020
5+
6+
### Bug Fixes:
7+
* Fixed handling of network and no status code errors when polling for datafile in `PollingConfigManager` and `AuthDatafilePollingConfigManager`. ([#287](https://github.com/optimizely/python-sdk/pull/287))
8+
39
## 3.5.1
410
July 10th, 2020
511

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ Build and install the SDK with pip, using the following command:
176176
To get test dependencies installed, use a modified version of the
177177
install command:
178178

179-
pip install -e .[test]
179+
pip install -e '.[test]'
180180

181181
You can run all unit tests with:
182182

optimizely/config_manager.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -344,9 +344,14 @@ def fetch_datafile(self):
344344
if self.last_modified:
345345
request_headers[enums.HTTPHeaders.IF_MODIFIED_SINCE] = self.last_modified
346346

347-
response = requests.get(
348-
self.datafile_url, headers=request_headers, timeout=enums.ConfigManager.REQUEST_TIMEOUT,
349-
)
347+
try:
348+
response = requests.get(
349+
self.datafile_url, headers=request_headers, timeout=enums.ConfigManager.REQUEST_TIMEOUT,
350+
)
351+
except requests_exceptions.RequestException as err:
352+
self.logger.error('Fetching datafile from {} failed. Error: {}'.format(self.datafile_url, str(err)))
353+
return
354+
350355
self._handle_response(response)
351356

352357
@property
@@ -411,7 +416,12 @@ def fetch_datafile(self):
411416
if self.last_modified:
412417
request_headers[enums.HTTPHeaders.IF_MODIFIED_SINCE] = self.last_modified
413418

414-
response = requests.get(
415-
self.datafile_url, headers=request_headers, timeout=enums.ConfigManager.REQUEST_TIMEOUT,
416-
)
419+
try:
420+
response = requests.get(
421+
self.datafile_url, headers=request_headers, timeout=enums.ConfigManager.REQUEST_TIMEOUT,
422+
)
423+
except requests_exceptions.RequestException as err:
424+
self.logger.error('Fetching datafile from {} failed. Error: {}'.format(self.datafile_url, str(err)))
425+
return
426+
417427
self._handle_response(response)

optimizely/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@
1111
# See the License for the specific language governing permissions and
1212
# limitations under the License.
1313

14-
version_info = (3, 5, 1)
14+
version_info = (3, 5, 2)
1515
__version__ = '.'.join(str(v) for v in version_info)

tests/test_config_manager.py

Lines changed: 96 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -394,8 +394,8 @@ def test_fetch_datafile(self, _):
394394
self.assertIsInstance(project_config_manager.get_config(), project_config.ProjectConfig)
395395
self.assertTrue(project_config_manager.is_running)
396396

397-
def test_fetch_datafile__exception_raised(self, _):
398-
""" Test that config_manager keeps running if exception is raised when fetching datafile. """
397+
def test_fetch_datafile__status_exception_raised(self, _):
398+
""" Test that config_manager keeps running if status code exception is raised when fetching datafile. """
399399
class MockExceptionResponse(object):
400400
def raise_for_status(self):
401401
raise requests.exceptions.RequestException('Error Error !!')
@@ -434,6 +434,45 @@ def raise_for_status(self):
434434
# Confirm that config manager keeps running
435435
self.assertTrue(project_config_manager.is_running)
436436

437+
def test_fetch_datafile__request_exception_raised(self, _):
438+
""" Test that config_manager keeps running if a request exception is raised when fetching datafile. """
439+
sdk_key = 'some_key'
440+
mock_logger = mock.Mock()
441+
with mock.patch('optimizely.config_manager.PollingConfigManager.fetch_datafile'):
442+
project_config_manager = config_manager.PollingConfigManager(sdk_key=sdk_key, logger=mock_logger)
443+
expected_datafile_url = enums.ConfigManager.DATAFILE_URL_TEMPLATE.format(sdk_key=sdk_key)
444+
test_headers = {'Last-Modified': 'New Time'}
445+
test_datafile = json.dumps(self.config_dict_with_features)
446+
test_response = requests.Response()
447+
test_response.status_code = 200
448+
test_response.headers = test_headers
449+
test_response._content = test_datafile
450+
with mock.patch('requests.get', return_value=test_response):
451+
project_config_manager.fetch_datafile()
452+
453+
self.assertEqual(test_headers['Last-Modified'], project_config_manager.last_modified)
454+
self.assertIsInstance(project_config_manager.get_config(), project_config.ProjectConfig)
455+
456+
# Call fetch_datafile again, but raise exception this time
457+
with mock.patch(
458+
'requests.get',
459+
side_effect=requests.exceptions.RequestException('Error Error !!'),
460+
) as mock_requests:
461+
project_config_manager.fetch_datafile()
462+
463+
mock_requests.assert_called_once_with(
464+
expected_datafile_url,
465+
headers={'If-Modified-Since': test_headers['Last-Modified']},
466+
timeout=enums.ConfigManager.REQUEST_TIMEOUT,
467+
)
468+
mock_logger.error.assert_called_once_with('Fetching datafile from {} failed. Error: Error Error !!'.format(
469+
expected_datafile_url
470+
))
471+
self.assertEqual(test_headers['Last-Modified'], project_config_manager.last_modified)
472+
self.assertIsInstance(project_config_manager.get_config(), project_config.ProjectConfig)
473+
# Confirm that config manager keeps running
474+
self.assertTrue(project_config_manager.is_running)
475+
437476
def test_is_running(self, _):
438477
""" Test that polling thread is running after instance of PollingConfigManager is created. """
439478
with mock.patch('optimizely.config_manager.PollingConfigManager.fetch_datafile'):
@@ -492,3 +531,58 @@ def test_fetch_datafile(self, _):
492531
)
493532

494533
self.assertIsInstance(project_config_manager.get_config(), project_config.ProjectConfig)
534+
535+
def test_fetch_datafile__request_exception_raised(self, _):
536+
""" Test that config_manager keeps running if a request exception is raised when fetching datafile. """
537+
datafile_access_token = 'some_token'
538+
sdk_key = 'some_key'
539+
mock_logger = mock.Mock()
540+
541+
with mock.patch('optimizely.config_manager.AuthDatafilePollingConfigManager.fetch_datafile'):
542+
project_config_manager = config_manager.AuthDatafilePollingConfigManager(
543+
datafile_access_token=datafile_access_token, sdk_key=sdk_key, logger=mock_logger)
544+
expected_datafile_url = enums.ConfigManager.AUTHENTICATED_DATAFILE_URL_TEMPLATE.format(sdk_key=sdk_key)
545+
test_headers = {'Last-Modified': 'New Time'}
546+
test_datafile = json.dumps(self.config_dict_with_features)
547+
test_response = requests.Response()
548+
test_response.status_code = 200
549+
test_response.headers = test_headers
550+
test_response._content = test_datafile
551+
552+
# Call fetch_datafile and assert that request was sent with correct authorization header
553+
with mock.patch('requests.get',
554+
return_value=test_response) as mock_request:
555+
project_config_manager.fetch_datafile()
556+
557+
mock_request.assert_called_once_with(
558+
expected_datafile_url,
559+
headers={'Authorization': 'Bearer {datafile_access_token}'.format(
560+
datafile_access_token=datafile_access_token)},
561+
timeout=enums.ConfigManager.REQUEST_TIMEOUT,
562+
)
563+
564+
self.assertIsInstance(project_config_manager.get_config(), project_config.ProjectConfig)
565+
566+
# Call fetch_datafile again, but raise exception this time
567+
with mock.patch(
568+
'requests.get',
569+
side_effect=requests.exceptions.RequestException('Error Error !!'),
570+
) as mock_requests:
571+
project_config_manager.fetch_datafile()
572+
573+
mock_requests.assert_called_once_with(
574+
expected_datafile_url,
575+
headers={
576+
'If-Modified-Since': test_headers['Last-Modified'],
577+
'Authorization': 'Bearer {datafile_access_token}'.format(
578+
datafile_access_token=datafile_access_token),
579+
},
580+
timeout=enums.ConfigManager.REQUEST_TIMEOUT,
581+
)
582+
mock_logger.error.assert_called_once_with('Fetching datafile from {} failed. Error: Error Error !!'.format(
583+
expected_datafile_url
584+
))
585+
self.assertEqual(test_headers['Last-Modified'], project_config_manager.last_modified)
586+
self.assertIsInstance(project_config_manager.get_config(), project_config.ProjectConfig)
587+
# Confirm that config manager keeps running
588+
self.assertTrue(project_config_manager.is_running)

0 commit comments

Comments
 (0)