Skip to content

Commit 7bacec4

Browse files
Moving audiences to object (#22)
1 parent d37cb9f commit 7bacec4

File tree

7 files changed

+68
-26
lines changed

7 files changed

+68
-26
lines changed

optimizely/entities.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@ def __init__(self, id, key, segmentId=None):
1212
self.segmentId = segmentId
1313

1414

15+
class Audience(BaseEntity):
16+
17+
def __init__(self, id, name, conditions, conditionStructure=None, conditionList=None):
18+
self.id = id
19+
self.name = name
20+
self.conditions = conditions
21+
self.conditionStructure = conditionStructure
22+
self.conditionList = conditionList
23+
24+
1525
class Event(BaseEntity):
1626

1727
def __init__(self, id, key, experimentIds):

optimizely/exceptions.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ class InvalidAttributeException(Exception):
33
pass
44

55

6+
class InvalidAudienceException(Exception):
7+
""" Raised when provided audience is invalid. """
8+
pass
9+
10+
611
class InvalidExperimentException(Exception):
712
""" Raised when provided experiment key is invalid. """
813
pass

optimizely/helpers/audience.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ def is_match(audience, attributes):
1111
Return:
1212
Boolean representing if user satisfies audience conditions or not.
1313
"""
14-
condition_evaluator = condition_helper.ConditionEvaluator(audience.get('conditionList'), attributes)
15-
return condition_evaluator.evaluate(audience.get('conditionStructure'))
14+
condition_evaluator = condition_helper.ConditionEvaluator(audience.conditionList, attributes)
15+
return condition_evaluator.evaluate(audience.conditionStructure)
1616

1717

1818
def is_user_in_experiment(config, experiment, attributes):
@@ -37,7 +37,7 @@ def is_user_in_experiment(config, experiment, attributes):
3737

3838
# Return True if conditions for any one audience are met
3939
for audience_id in experiment.audienceIds:
40-
audience = config.get_audience_object_from_id(audience_id)
40+
audience = config.get_audience(audience_id)
4141

4242
if is_match(audience, attributes):
4343
return True

optimizely/helpers/enums.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class Errors(object):
1919
INVALID_INPUT_ERROR = 'Provided "{}" is in an invalid format.'
2020
INVALID_ATTRIBUTE_ERROR = 'Provided attribute is not in datafile.'
2121
INVALID_ATTRIBUTE_FORMAT = 'Attributes provided are in an invalid format.'
22+
INVALID_AUDIENCE_ERROR = 'Provided audience is not in datafile.'
2223
INVALID_EXPERIMENT_KEY_ERROR = 'Provided experiment is not in datafile.'
2324
INVALID_EVENT_KEY_ERROR = 'Provided event is not in datafile.'
2425
INVALID_GROUP_ID_ERROR = 'Provided group is not in datafile.'

optimizely/project_config.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def __init__(self, datafile, logger, error_handler):
4141
self.experiment_key_map = self._generate_key_map_entity(self.experiments, 'key', entities.Experiment)
4242
self.event_key_map = self._generate_key_map_entity(self.events, 'key', entities.Event)
4343
self.attribute_key_map = self._generate_key_map_entity(self.attributes, 'key', entities.Attribute)
44-
self.audience_id_map = self._generate_key_map(self.audiences, 'id')
44+
self.audience_id_map = self._generate_key_map_entity(self.audiences, 'id', entities.Audience)
4545
self.audience_id_map = self._deserialize_audience(self.audience_id_map)
4646
for group in self.group_id_map.values():
4747
experiments_in_group_key_map = self._generate_key_map_entity(group['experiments'], 'key', entities.Experiment)
@@ -84,12 +84,13 @@ def _generate_key_map(list, key):
8484
return key_map
8585

8686
@staticmethod
87-
def _generate_key_map_entity(list, key, named_tuple):
87+
def _generate_key_map_entity(list, key, entity_class):
8888
""" Helper method to generate map from key to entity object for given list of dicts.
8989
9090
Args:
9191
list: List consisting of dict.
9292
key: Key in each dict which will be key in the map.
93+
entity_class: Class representing the entity.
9394
9495
Returns:
9596
Map mapping key to entity object.
@@ -98,24 +99,27 @@ def _generate_key_map_entity(list, key, named_tuple):
9899
key_map = {}
99100

100101
for obj in list:
101-
key_map[obj[key]] = named_tuple(**obj)
102+
key_map[obj[key]] = entity_class(**obj)
102103

103104
return key_map
104105

105106
@staticmethod
106107
def _deserialize_audience(audience_map):
107-
""" Helper method to deserialize and populate audience map with the condition list and structure.
108+
""" Helper method to de-serialize and populate audience map with the condition list and structure.
108109
109110
Args:
110111
audience_map: Dict mapping audience ID to audience object.
111112
112113
Returns:
113-
Dict additionally consisting of condition list and structure for every audience.
114+
Dict additionally consisting of condition list and structure on every audience object.
114115
"""
115116

116-
for audience_id in audience_map.keys():
117-
audience_map[audience_id]['conditionStructure'], audience_map[audience_id]['conditionList'] = \
118-
condition_helper.loads(audience_map[audience_id]['conditions'])
117+
for audience in audience_map.values():
118+
condition_structure, condition_list = condition_helper.loads(audience.conditions)
119+
audience.__dict__.update({
120+
'conditionStructure': condition_structure,
121+
'conditionList': condition_list
122+
})
119123

120124
return audience_map
121125

@@ -184,7 +188,7 @@ def get_experiment_from_id(self, experiment_id):
184188
self.error_handler.handle_error(exceptions.InvalidExperimentException(enums.Errors.INVALID_EXPERIMENT_KEY_ERROR))
185189
return None
186190

187-
def get_audience_object_from_id(self, audience_id):
191+
def get_audience(self, audience_id):
188192
""" Get audience object for the provided audience ID.
189193
190194
Args:
@@ -194,7 +198,13 @@ def get_audience_object_from_id(self, audience_id):
194198
Dict representing the audience.
195199
"""
196200

197-
return self.audience_id_map.get(audience_id)
201+
audience = self.audience_id_map.get(audience_id)
202+
203+
if audience:
204+
return audience
205+
206+
self.logger.log(enums.LogLevels.ERROR, 'Audience ID "%s" is not in datafile.' % audience_id)
207+
self.error_handler.handle_error(exceptions.InvalidAudienceException((enums.Errors.INVALID_AUDIENCE_ERROR)))
198208

199209
def get_variation_key_from_id(self, experiment_key, variation_id):
200210
""" Get variation key given experiment key and variation ID.

tests/helpers_tests/test_audience.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def test_is_match__audience_condition_matches(self):
1515
'location': 'San Francisco'
1616
}
1717

18-
self.assertTrue(audience.is_match(self.optimizely.config.audiences[0], user_attributes))
18+
self.assertTrue(audience.is_match(self.optimizely.config.get_audience('11154'), user_attributes))
1919

2020
def test_is_match__audience_condition_does_not_match(self):
2121
""" Test that is_match returns False when audience conditions are not met. """
@@ -26,7 +26,7 @@ def test_is_match__audience_condition_does_not_match(self):
2626
'location': 'San Francisco'
2727
}
2828

29-
self.assertFalse(audience.is_match(self.optimizely.config.audiences[0], user_attributes))
29+
self.assertFalse(audience.is_match(self.optimizely.config.get_audience('11154'), user_attributes))
3030

3131
def test_is_user_in_experiment__no_audience(self):
3232
""" Test that is_user_in_experiment returns True when experiment is using no audience. """
@@ -54,7 +54,7 @@ def test_is_user_in_experiment__audience_conditions_are_met(self):
5454
self.assertTrue(audience.is_user_in_experiment(self.project_config,
5555
self.project_config.get_experiment_from_key('test_experiment'),
5656
user_attributes))
57-
mock_is_match.assert_called_once_with(self.optimizely.config.audiences[0], user_attributes)
57+
mock_is_match.assert_called_once_with(self.optimizely.config.get_audience('11154'), user_attributes)
5858

5959
def test_is_user_in_experiment__audience_conditions_not_met(self):
6060
""" Test that is_user_in_experiment returns False when audience conditions are not met. """
@@ -69,4 +69,4 @@ def test_is_user_in_experiment__audience_conditions_not_met(self):
6969
self.assertFalse(audience.is_user_in_experiment(self.project_config,
7070
self.project_config.get_experiment_from_key('test_experiment'),
7171
user_attributes))
72-
mock_is_match.assert_called_once_with(self.optimizely.config.audiences[0], user_attributes)
72+
mock_is_match.assert_called_once_with(self.optimizely.config.get_audience('11154'), user_attributes)

tests/test_config.py

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,13 @@ def test_init(self):
9494
'test_attribute': entities.Attribute('111094', 'test_attribute', segmentId='11133')
9595
}
9696
expected_audience_id_map = {
97-
'11154': self.config_dict['audiences'][0]
97+
'11154': entities.Audience(
98+
'11154', 'Test attribute users',
99+
'["and", ["or", ["or", {"name": "test_attribute", "type": "custom_dimension", "value": "test_value"}]]]',
100+
conditionStructure=['and', ['or', ['or', 0]]],
101+
conditionList=[['test_attribute', 'test_value']]
102+
)
98103
}
99-
expected_audience_id_map['11154'].update({
100-
'conditionList': [['test_attribute', 'test_value']],
101-
'conditionStructure': ['and', ['or', ['or', 0]]]
102-
})
103104
expected_variation_key_map = {
104105
'test_experiment': {
105106
'control': {
@@ -243,16 +244,16 @@ def test_get_experiment_from_id__invalid_id(self):
243244

244245
self.assertIsNone(self.project_config.get_experiment_from_id('invalid_id'))
245246

246-
def test_get_audience_object_from_id__valid_id(self):
247+
def test_get_audience__valid_id(self):
247248
""" Test that audience object is retrieved correctly given a valid audience ID. """
248249

249250
self.assertEqual(self.project_config.audience_id_map['11154'],
250-
self.project_config.get_audience_object_from_id('11154'))
251+
self.project_config.get_audience('11154'))
251252

252-
def test_get_audience_object_from_id__invalid_id(self):
253+
def test_get_audience__invalid_id(self):
253254
""" Test that None is returned for an invalid audience ID. """
254255

255-
self.assertIsNone(self.project_config.get_audience_object_from_id('42'))
256+
self.assertIsNone(self.project_config.get_audience('42'))
256257

257258
def test_get_variation_key_from_id__valid_experiment_key(self):
258259
""" Test that variation key is retrieved correctly when valid experiment key and variation ID are provided. """
@@ -397,6 +398,14 @@ def test_get_experiment_from_key__invalid_key(self):
397398

398399
mock_logging.assert_called_once_with(enums.LogLevels.ERROR, 'Experiment key "invalid_key" is not in datafile.')
399400

401+
def test_get_audience__invalid_id(self):
402+
""" Test that message is logged when provided audience ID is invalid. """
403+
404+
with mock.patch('optimizely.logger.SimpleLogger.log') as mock_logging:
405+
self.project_config.get_audience('42')
406+
407+
mock_logging.assert_called_once_with(enums.LogLevels.ERROR, 'Audience ID "42" is not in datafile.')
408+
400409
def test_get_variation_key_from_id__invalid_variation_id(self):
401410
""" Test that message is logged when provided variation ID is invalid. """
402411

@@ -453,6 +462,13 @@ def test_get_experiment_from_key__invalid_key(self):
453462
enums.Errors.INVALID_EXPERIMENT_KEY_ERROR,
454463
self.project_config.get_experiment_from_key, 'invalid_key')
455464

465+
def test_get_audience__invalid_id(self):
466+
""" Test that message is logged when provided audience ID is invalid. """
467+
468+
self.assertRaisesRegexp(exceptions.InvalidAudienceException,
469+
enums.Errors.INVALID_AUDIENCE_ERROR,
470+
self.project_config.get_audience, '42')
471+
456472
def test_get_variation_key_from_id__invalid_variation_id(self):
457473
""" Test that exception is raised when provided variation ID is invalid. """
458474

0 commit comments

Comments
 (0)