Skip to content

Commit a91117b

Browse files
Creating object for groups (#23)
1 parent 7bacec4 commit a91117b

File tree

6 files changed

+71
-42
lines changed

6 files changed

+71
-42
lines changed

optimizely/bucketer.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from .lib import pymmh3 as mmh3
66

77
from .helpers import enums
8-
from . import exceptions
98

109
MAX_TRAFFIC_VALUE = 10000
1110
UNSIGNED_MAX_32_BIT_VALUE = 0xFFFFFFFF
@@ -103,16 +102,12 @@ def bucket(self, experiment, user_id):
103102

104103
# Determine if experiment is in a mutually exclusive group
105104
if experiment.groupPolicy in GROUP_POLICIES:
106-
group_traffic_allocations = self.config.get_traffic_allocation(self.config.group_id_map, experiment.groupId)
105+
group = self.config.get_group(experiment.groupId)
107106

108-
if not group_traffic_allocations:
109-
self.config.logger.log(enums.LogLevels.ERROR, 'Group ID "%s" is not in datafile.' % experiment.groupId)
110-
self.config.error_handler.handle_error(
111-
exceptions.InvalidExperimentException(enums.Errors.INVALID_GROUP_ID_ERROR)
112-
)
107+
if not group:
113108
return None
114109

115-
user_experiment_id = self._find_bucket(user_id, experiment.groupId, group_traffic_allocations)
110+
user_experiment_id = self._find_bucket(user_id, experiment.groupId, group.trafficAllocation)
116111
if not user_experiment_id:
117112
self.config.logger.log(enums.LogLevels.INFO, 'User "%s" is in no experiment.' % user_id)
118113
return None

optimizely/entities.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,12 @@ def __init__(self, id, key, status, audienceIds, variations, forcedVariations,
4444
self.layerId = layerId
4545
self.groupId = groupId
4646
self.groupPolicy = groupPolicy
47+
48+
49+
class Group(BaseEntity):
50+
51+
def __init__(self, id, policy, experiments, trafficAllocation):
52+
self.id = id
53+
self.policy = policy
54+
self.experiments = experiments
55+
self.trafficAllocation = trafficAllocation

optimizely/exceptions.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ class InvalidEventException(Exception):
1818
pass
1919

2020

21+
class InvalidGroupException(Exception):
22+
""" Raised when provided group ID is invalid. """
23+
pass
24+
25+
2126
class InvalidVariationException(Exception):
2227
""" Raised when provided variation is invalid. """
2328
pass

optimizely/project_config.py

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,18 @@ def __init__(self, datafile, logger, error_handler):
3737
self.audiences = config.get('audiences', [])
3838

3939
# Utility maps for quick lookup
40-
self.group_id_map = self._generate_key_map(self.groups, 'id')
40+
self.group_id_map = self._generate_key_map_entity(self.groups, 'id', entities.Group)
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)
4444
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():
47-
experiments_in_group_key_map = self._generate_key_map_entity(group['experiments'], 'key', entities.Experiment)
47+
experiments_in_group_key_map = self._generate_key_map_entity(group.experiments, 'key', entities.Experiment)
4848
for experiment in experiments_in_group_key_map.values():
4949
experiment.__dict__.update({
50-
'groupId': group.get('id'),
51-
'groupPolicy': group.get('policy')
50+
'groupId': group.id,
51+
'groupPolicy': group.policy
5252
})
5353
self.experiment_key_map.update(experiments_in_group_key_map)
5454

@@ -97,7 +97,6 @@ def _generate_key_map_entity(list, key, entity_class):
9797
"""
9898

9999
key_map = {}
100-
101100
for obj in list:
102101
key_map[obj[key]] = entity_class(**obj)
103102

@@ -188,6 +187,25 @@ def get_experiment_from_id(self, experiment_id):
188187
self.error_handler.handle_error(exceptions.InvalidExperimentException(enums.Errors.INVALID_EXPERIMENT_KEY_ERROR))
189188
return None
190189

190+
def get_group(self, group_id):
191+
""" Get group for the provided group ID.
192+
193+
Args:
194+
group_id: Group ID for which group is to be determined.
195+
196+
Returns:
197+
Group corresponding to the provided group ID.
198+
"""
199+
200+
group = self.group_id_map.get(group_id)
201+
202+
if group:
203+
return group
204+
205+
self.logger.log(enums.LogLevels.ERROR, 'Group ID "%s" is not in datafile.' % group_id)
206+
self.error_handler.handle_error(exceptions.InvalidGroupException(enums.Errors.INVALID_GROUP_ID_ERROR))
207+
return None
208+
191209
def get_audience(self, audience_id):
192210
""" Get audience object for the provided audience ID.
193211
@@ -304,21 +322,3 @@ def get_attribute(self, attribute_key):
304322
self.logger.log(enums.LogLevels.ERROR, 'Attribute "%s" is not in datafile.' % attribute_key)
305323
self.error_handler.handle_error(exceptions.InvalidAttributeException(enums.Errors.INVALID_ATTRIBUTE_ERROR))
306324
return None
307-
308-
def get_traffic_allocation(self, entity_key_map, entity_key):
309-
""" Given an entity key map and entity key, returns the traffic allocation for that entity.
310-
311-
Args:
312-
entity_key_map: Map representing the entity information.
313-
entity_key: Key for whcih traffic allocation is to be retrieved from the map
314-
315-
Returns:
316-
Traffic allocation for the experiment.
317-
"""
318-
319-
entity = entity_key_map.get(entity_key)
320-
321-
if entity:
322-
return entity.get('trafficAllocation')
323-
324-
return None

tests/base.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ def setUp(self):
4646
'groups': [{
4747
'id': '19228',
4848
'policy': 'random',
49-
'holdback': 0,
5049
'experiments': [{
5150
'id': '32222',
5251
'key': 'group_exp_1',
@@ -164,7 +163,6 @@ def setUp(self):
164163
'groups': [{
165164
'id': '19228',
166165
'policy': 'random',
167-
'holdback': 0,
168166
'experiments': [{
169167
'id': '32222',
170168
'key': 'group_exp_1',

tests/test_config.py

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,12 @@ def test_init(self):
2323
self.assertEqual(self.config_dict['experiments'], self.project_config.experiments)
2424
self.assertEqual(self.config_dict['events'], self.project_config.events)
2525
expected_group_id_map = {
26-
'19228': copy.deepcopy(self.config_dict['groups'][0])
26+
'19228': entities.Group(
27+
self.config_dict['groups'][0]['id'],
28+
self.config_dict['groups'][0]['policy'],
29+
self.config_dict['groups'][0]['experiments'],
30+
self.config_dict['groups'][0]['trafficAllocation']
31+
)
2732
}
2833
expected_experiment_key_map = {
2934
'test_experiment': entities.Experiment(
@@ -316,18 +321,20 @@ def test_get_attribute__invalid_key(self):
316321

317322
self.assertIsNone(self.project_config.get_attribute('invalid_key'))
318323

319-
def test_get_traffic_allocation__valid_key(self):
320-
""" Test that trafficAllocation is retrieved correctly for valid group ID. """
324+
def test_get_group__valid_id(self):
325+
""" Test that group is retrieved correctly for valid group ID. """
326+
327+
self.assertEqual(entities.Group(self.config_dict['groups'][0]['id'],
328+
self.config_dict['groups'][0]['policy'],
329+
self.config_dict['groups'][0]['experiments'],
330+
self.config_dict['groups'][0]['trafficAllocation']),
331+
self.project_config.get_group('19228'))
321332

322-
self.assertEqual(self.config_dict['groups'][0]['trafficAllocation'],
323-
self.project_config.get_traffic_allocation(self.project_config.group_id_map,
324-
'19228'))
325333

326-
def test_get_traffic_allocation__invalid_key(self):
334+
def test_get_group__invalid_id(self):
327335
""" Test that None is returned when provided group ID is invalid. """
328336

329-
self.assertIsNone(self.project_config.get_traffic_allocation(self.project_config.group_id_map,
330-
'invalid_key'))
337+
self.assertIsNone(self.project_config.get_group('42'))
331338

332339

333340
class ConfigTestV2(base.BaseTestV2):
@@ -446,6 +453,14 @@ def test_get_attribute__invalid_key(self):
446453

447454
mock_logging.assert_called_once_with(enums.LogLevels.ERROR, 'Attribute "invalid_key" is not in datafile.')
448455

456+
def test_get_group__invalid_id(self):
457+
""" Test that message is logged when provided group ID is invalid. """
458+
459+
with mock.patch('optimizely.logger.SimpleLogger.log') as mock_logging:
460+
self.project_config.get_group('42')
461+
462+
mock_logging.assert_called_once_with(enums.LogLevels.ERROR, 'Group ID "42" is not in datafile.')
463+
449464

450465
class ConfigExceptionTest(base.BaseTestV1):
451466

@@ -503,3 +518,10 @@ def test_get_attribute__invalid_key(self):
503518
self.assertRaisesRegexp(exceptions.InvalidAttributeException,
504519
enums.Errors.INVALID_ATTRIBUTE_ERROR,
505520
self.project_config.get_attribute, 'invalid_key')
521+
522+
def test_get_group__invalid_id(self):
523+
""" Test that exception is raised when provided group ID is invalid. """
524+
525+
self.assertRaisesRegexp(exceptions.InvalidGroupException,
526+
enums.Errors.INVALID_GROUP_ID_ERROR,
527+
self.project_config.get_group, '42')

0 commit comments

Comments
 (0)