Skip to content

Commit c2ea908

Browse files
oakbanimikeproeng37
authored andcommitted
fix (datafile-parsing): Prevent newer versions datafile (#141)
1 parent f3e395a commit c2ea908

File tree

6 files changed

+217
-118
lines changed

6 files changed

+217
-118
lines changed

optimizely/exceptions.py

Lines changed: 57 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,57 @@
1-
# Copyright 2016-2017, 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-
15-
class InvalidAttributeException(Exception):
16-
""" Raised when provided attribute is invalid. """
17-
pass
18-
19-
20-
class InvalidAudienceException(Exception):
21-
""" Raised when provided audience is invalid. """
22-
pass
23-
24-
25-
class InvalidEventTagException(Exception):
26-
""" Raised when provided event tag is invalid. """
27-
pass
28-
29-
30-
class InvalidExperimentException(Exception):
31-
""" Raised when provided experiment key is invalid. """
32-
pass
33-
34-
35-
class InvalidEventException(Exception):
36-
""" Raised when provided event key is invalid. """
37-
pass
38-
39-
40-
class InvalidGroupException(Exception):
41-
""" Raised when provided group ID is invalid. """
42-
pass
43-
44-
45-
class InvalidInputException(Exception):
46-
""" Raised when provided datafile, event dispatcher, logger or error handler is invalid. """
47-
pass
48-
49-
50-
class InvalidVariationException(Exception):
51-
""" Raised when provided variation is invalid. """
52-
pass
1+
# Copyright 2016-2018, 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+
15+
class InvalidAttributeException(Exception):
16+
""" Raised when provided attribute is invalid. """
17+
pass
18+
19+
20+
class InvalidAudienceException(Exception):
21+
""" Raised when provided audience is invalid. """
22+
pass
23+
24+
25+
class InvalidEventException(Exception):
26+
""" Raised when provided event key is invalid. """
27+
pass
28+
29+
30+
class InvalidEventTagException(Exception):
31+
""" Raised when provided event tag is invalid. """
32+
pass
33+
34+
35+
class InvalidExperimentException(Exception):
36+
""" Raised when provided experiment key is invalid. """
37+
pass
38+
39+
40+
class InvalidGroupException(Exception):
41+
""" Raised when provided group ID is invalid. """
42+
pass
43+
44+
45+
class InvalidInputException(Exception):
46+
""" Raised when provided datafile, event dispatcher, logger or error handler is invalid. """
47+
pass
48+
49+
50+
class InvalidVariationException(Exception):
51+
""" Raised when provided variation is invalid. """
52+
pass
53+
54+
55+
class UnsupportedDatafileVersionException(Exception):
56+
""" Raised when provided version in datafile is not supported. """
57+
pass

optimizely/helpers/enums.py

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,16 @@
1414
import logging
1515

1616

17-
class HTTPVerbs(object):
18-
GET = 'GET'
19-
POST = 'POST'
17+
class ControlAttributes(object):
18+
BOT_FILTERING = '$opt_bot_filtering'
19+
BUCKETING_ID = '$opt_bucketing_id'
20+
USER_AGENT = '$opt_user_agent'
2021

2122

22-
class LogLevels(object):
23-
NOTSET = logging.NOTSET
24-
DEBUG = logging.DEBUG
25-
INFO = logging.INFO
26-
WARNING = logging.WARNING
27-
ERROR = logging.ERROR
28-
CRITICAL = logging.CRITICAL
23+
class DatafileVersions(object):
24+
V2 = '2'
25+
V3 = '3'
26+
V4 = '4'
2927

3028

3129
class Errors(object):
@@ -44,8 +42,21 @@ class Errors(object):
4442
NONE_FEATURE_KEY_PARAMETER = '"None" is an invalid value for feature key.'
4543
NONE_USER_ID_PARAMETER = '"None" is an invalid value for user ID.'
4644
NONE_VARIABLE_KEY_PARAMETER = '"None" is an invalid value for variable key.'
47-
UNSUPPORTED_DATAFILE_VERSION = 'Provided datafile has unsupported version. ' \
48-
'Please use SDK version 1.1.0 or earlier for datafile version 1.'
45+
UNSUPPORTED_DATAFILE_VERSION = 'This version of the Python SDK does not support the given datafile version: "{}".'
46+
47+
48+
class HTTPVerbs(object):
49+
GET = 'GET'
50+
POST = 'POST'
51+
52+
53+
class LogLevels(object):
54+
NOTSET = logging.NOTSET
55+
DEBUG = logging.DEBUG
56+
INFO = logging.INFO
57+
WARNING = logging.WARNING
58+
ERROR = logging.ERROR
59+
CRITICAL = logging.CRITICAL
4960

5061

5162
class NotificationTypes(object):
@@ -59,9 +70,3 @@ class NotificationTypes(object):
5970
"""
6071
ACTIVATE = "ACTIVATE:experiment, user_id, attributes, variation, event"
6172
TRACK = "TRACK:event_key, user_id, attributes, event_tags, event"
62-
63-
64-
class ControlAttributes(object):
65-
BOT_FILTERING = '$opt_bot_filtering'
66-
BUCKETING_ID = '$opt_bucketing_id'
67-
USER_AGENT = '$opt_user_agent'

optimizely/optimizely.py

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -61,23 +61,24 @@ def __init__(self,
6161
self.logger.exception(str(error))
6262
return
6363

64+
error_msg = None
6465
try:
6566
self.config = project_config.ProjectConfig(datafile, self.logger, self.error_handler)
67+
except exceptions.UnsupportedDatafileVersionException as error:
68+
error_msg = error.args[0]
69+
error_to_handle = error
6670
except:
67-
self.is_valid = False
68-
self.config = None
69-
# We actually want to log this error to stderr, so make sure the logger
70-
# has a handler capable of doing that.
71-
self.logger = _logging.reset_logger(self.logger_name)
72-
self.logger.error(enums.Errors.INVALID_INPUT_ERROR.format('datafile'))
73-
return
74-
75-
if not self.config.was_parsing_successful():
76-
self.is_valid = False
77-
# We actually want to log this error to stderr, so make sure the logger
78-
# has a handler capable of doing that.
79-
self.logger.error(enums.Errors.UNSUPPORTED_DATAFILE_VERSION)
80-
return
71+
error_msg = enums.Errors.INVALID_INPUT_ERROR.format('datafile')
72+
error_to_handle = exceptions.InvalidInputException(error_msg)
73+
finally:
74+
if error_msg:
75+
self.is_valid = False
76+
# We actually want to log this error to stderr, so make sure the logger
77+
# has a handler capable of doing that.
78+
self.logger = _logging.reset_logger(self.logger_name)
79+
self.logger.exception(error_msg)
80+
self.error_handler.handle_error(error_to_handle)
81+
return
8182

8283
self.event_builder = event_builder.EventBuilder(self.config)
8384
self.decision_service = decision_service.DecisionService(self.config, user_profile_service)

optimizely/project_config.py

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,7 @@
1818
from . import entities
1919
from . import exceptions
2020

21-
REVENUE_GOAL_KEY = 'Total Revenue'
22-
V1_CONFIG_VERSION = '1'
23-
V2_CONFIG_VERSION = '2'
24-
25-
SUPPORTED_VERSIONS = [V2_CONFIG_VERSION]
26-
UNSUPPORTED_VERSIONS = [V1_CONFIG_VERSION]
21+
SUPPORTED_VERSIONS = [enums.DatafileVersions.V2, enums.DatafileVersions.V3, enums.DatafileVersions.V4]
2722

2823
RESERVED_ATTRIBUTE_PREFIX = '$opt_'
2924

@@ -41,12 +36,14 @@ def __init__(self, datafile, logger, error_handler):
4136
"""
4237

4338
config = json.loads(datafile)
44-
self.parsing_succeeded = False
4539
self.logger = logger
4640
self.error_handler = error_handler
4741
self.version = config.get('version')
48-
if self.version in UNSUPPORTED_VERSIONS:
49-
return
42+
if self.version not in SUPPORTED_VERSIONS:
43+
raise exceptions.UnsupportedDatafileVersionException(
44+
enums.Errors.UNSUPPORTED_DATAFILE_VERSION.format(self.version)
45+
)
46+
5047
self.account_id = config.get('accountId')
5148
self.project_id = config.get('projectId')
5249
self.revision = config.get('revision')
@@ -109,8 +106,6 @@ def __init__(self, datafile, logger, error_handler):
109106
# Experiments in feature can only belong to one mutex group
110107
break
111108

112-
self.parsing_succeeded = True
113-
114109
# Map of user IDs to another map of experiments to variations.
115110
# This contains all the forced variations set by the user
116111
# by calling set_forced_variation (it is not the same as the
@@ -176,15 +171,6 @@ def get_typecast_value(self, value, type):
176171
else:
177172
return value
178173

179-
def was_parsing_successful(self):
180-
""" Helper method to determine if parsing the datafile was successful.
181-
182-
Returns:
183-
Boolean depending on whether parsing the datafile succeeded or not.
184-
"""
185-
186-
return self.parsing_succeeded
187-
188174
def get_version(self):
189175
""" Get version of the datafile.
190176

tests/base.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,56 @@ def setUp(self, config_dict='config_dict'):
521521
'projectId': '111001'
522522
}
523523

524+
self.config_dict_with_unsupported_version = {
525+
'version': '5',
526+
'rollouts': [],
527+
'projectId': '10431130345',
528+
'variables': [],
529+
'featureFlags': [],
530+
'experiments': [
531+
{
532+
'status': 'Running',
533+
'key': 'ab_running_exp_untargeted',
534+
'layerId': '10417730432',
535+
'trafficAllocation': [
536+
{
537+
'entityId': '10418551353',
538+
'endOfRange': 10000
539+
}
540+
],
541+
'audienceIds': [],
542+
'variations': [
543+
{
544+
'variables': [],
545+
'id': '10418551353',
546+
'key': 'all_traffic_variation'
547+
},
548+
{
549+
'variables': [],
550+
'id': '10418510624',
551+
'key': 'no_traffic_variation'
552+
}
553+
],
554+
'forcedVariations': {},
555+
'id': '10420810910'
556+
}
557+
],
558+
'audiences': [],
559+
'groups': [],
560+
'attributes': [],
561+
'accountId': '10367498574',
562+
'events': [
563+
{
564+
'experimentIds': [
565+
'10420810910'
566+
],
567+
'id': '10404198134',
568+
'key': 'winning'
569+
}
570+
],
571+
'revision': '1337'
572+
}
573+
524574
config = getattr(self, config_dict)
525575
self.optimizely = optimizely.Optimizely(json.dumps(config))
526576
self.project_config = self.optimizely.config

0 commit comments

Comments
 (0)