12
12
# limitations under the License.
13
13
14
14
import math
15
+
15
16
try :
16
- import mmh3
17
+ import mmh3
17
18
except ImportError :
18
- from .lib import pymmh3 as mmh3
19
+ from .lib import pymmh3 as mmh3
19
20
20
21
21
22
MAX_TRAFFIC_VALUE = 10000
27
28
28
29
29
30
class Bucketer (object ):
30
- """ Optimizely bucketing algorithm that evenly distributes visitors. """
31
+ """ Optimizely bucketing algorithm that evenly distributes visitors. """
31
32
32
- def __init__ (self ):
33
- """ Bucketer init method to set bucketing seed and logger instance. """
33
+ def __init__ (self ):
34
+ """ Bucketer init method to set bucketing seed and logger instance. """
34
35
35
- self .bucket_seed = HASH_SEED
36
+ self .bucket_seed = HASH_SEED
36
37
37
- def _generate_unsigned_hash_code_32_bit (self , bucketing_id ):
38
- """ Helper method to retrieve hash code.
38
+ def _generate_unsigned_hash_code_32_bit (self , bucketing_id ):
39
+ """ Helper method to retrieve hash code.
39
40
40
41
Args:
41
42
bucketing_id: ID for bucketing.
@@ -44,11 +45,11 @@ def _generate_unsigned_hash_code_32_bit(self, bucketing_id):
44
45
Hash code which is a 32 bit unsigned integer.
45
46
"""
46
47
47
- # Adjusting MurmurHash code to be unsigned
48
- return ( mmh3 .hash (bucketing_id , self .bucket_seed ) & UNSIGNED_MAX_32_BIT_VALUE )
48
+ # Adjusting MurmurHash code to be unsigned
49
+ return mmh3 .hash (bucketing_id , self .bucket_seed ) & UNSIGNED_MAX_32_BIT_VALUE
49
50
50
- def _generate_bucket_value (self , bucketing_id ):
51
- """ Helper function to generate bucket value in half-closed interval [0, MAX_TRAFFIC_VALUE).
51
+ def _generate_bucket_value (self , bucketing_id ):
52
+ """ Helper function to generate bucket value in half-closed interval [0, MAX_TRAFFIC_VALUE).
52
53
53
54
Args:
54
55
bucketing_id: ID for bucketing.
@@ -57,11 +58,11 @@ def _generate_bucket_value(self, bucketing_id):
57
58
Bucket value corresponding to the provided bucketing ID.
58
59
"""
59
60
60
- ratio = float (self ._generate_unsigned_hash_code_32_bit (bucketing_id )) / MAX_HASH_VALUE
61
- return math .floor (ratio * MAX_TRAFFIC_VALUE )
61
+ ratio = float (self ._generate_unsigned_hash_code_32_bit (bucketing_id )) / MAX_HASH_VALUE
62
+ return math .floor (ratio * MAX_TRAFFIC_VALUE )
62
63
63
- def find_bucket (self , project_config , bucketing_id , parent_id , traffic_allocations ):
64
- """ Determine entity based on bucket value and traffic allocations.
64
+ def find_bucket (self , project_config , bucketing_id , parent_id , traffic_allocations ):
65
+ """ Determine entity based on bucket value and traffic allocations.
65
66
66
67
Args:
67
68
project_config: Instance of ProjectConfig.
@@ -73,22 +74,21 @@ def find_bucket(self, project_config, bucketing_id, parent_id, traffic_allocatio
73
74
Entity ID which may represent experiment or variation.
74
75
"""
75
76
76
- bucketing_key = BUCKETING_ID_TEMPLATE .format (bucketing_id = bucketing_id , parent_id = parent_id )
77
- bucketing_number = self ._generate_bucket_value (bucketing_key )
78
- project_config .logger .debug ('Assigned bucket %s to user with bucketing ID "%s".' % (
79
- bucketing_number ,
80
- bucketing_id
81
- ))
77
+ bucketing_key = BUCKETING_ID_TEMPLATE .format (bucketing_id = bucketing_id , parent_id = parent_id )
78
+ bucketing_number = self ._generate_bucket_value (bucketing_key )
79
+ project_config .logger .debug (
80
+ 'Assigned bucket %s to user with bucketing ID "%s".' % (bucketing_number , bucketing_id )
81
+ )
82
82
83
- for traffic_allocation in traffic_allocations :
84
- current_end_of_range = traffic_allocation .get ('endOfRange' )
85
- if bucketing_number < current_end_of_range :
86
- return traffic_allocation .get ('entityId' )
83
+ for traffic_allocation in traffic_allocations :
84
+ current_end_of_range = traffic_allocation .get ('endOfRange' )
85
+ if bucketing_number < current_end_of_range :
86
+ return traffic_allocation .get ('entityId' )
87
87
88
- return None
88
+ return None
89
89
90
- def bucket (self , project_config , experiment , user_id , bucketing_id ):
91
- """ For a given experiment and bucketing ID determines variation to be shown to user.
90
+ def bucket (self , project_config , experiment , user_id , bucketing_id ):
91
+ """ For a given experiment and bucketing ID determines variation to be shown to user.
92
92
93
93
Args:
94
94
project_config: Instance of ProjectConfig.
@@ -100,45 +100,41 @@ def bucket(self, project_config, experiment, user_id, bucketing_id):
100
100
Variation in which user with ID user_id will be put in. None if no variation.
101
101
"""
102
102
103
- if not experiment :
104
- return None
105
-
106
- # Determine if experiment is in a mutually exclusive group
107
- if experiment .groupPolicy in GROUP_POLICIES :
108
- group = project_config .get_group (experiment .groupId )
109
-
110
- if not group :
103
+ if not experiment :
104
+ return None
105
+
106
+ # Determine if experiment is in a mutually exclusive group
107
+ if experiment .groupPolicy in GROUP_POLICIES :
108
+ group = project_config .get_group (experiment .groupId )
109
+
110
+ if not group :
111
+ return None
112
+
113
+ user_experiment_id = self .find_bucket (
114
+ project_config , bucketing_id , experiment .groupId , group .trafficAllocation ,
115
+ )
116
+ if not user_experiment_id :
117
+ project_config .logger .info ('User "%s" is in no experiment.' % user_id )
118
+ return None
119
+
120
+ if user_experiment_id != experiment .id :
121
+ project_config .logger .info (
122
+ 'User "%s" is not in experiment "%s" of group %s.' % (user_id , experiment .key , experiment .groupId )
123
+ )
124
+ return None
125
+
126
+ project_config .logger .info (
127
+ 'User "%s" is in experiment %s of group %s.' % (user_id , experiment .key , experiment .groupId )
128
+ )
129
+
130
+ # Bucket user if not in white-list and in group (if any)
131
+ variation_id = self .find_bucket (project_config , bucketing_id , experiment .id , experiment .trafficAllocation )
132
+ if variation_id :
133
+ variation = project_config .get_variation_from_id (experiment .key , variation_id )
134
+ project_config .logger .info (
135
+ 'User "%s" is in variation "%s" of experiment %s.' % (user_id , variation .key , experiment .key )
136
+ )
137
+ return variation
138
+
139
+ project_config .logger .info ('User "%s" is in no variation.' % user_id )
111
140
return None
112
-
113
- user_experiment_id = self .find_bucket (project_config , bucketing_id , experiment .groupId , group .trafficAllocation )
114
- if not user_experiment_id :
115
- project_config .logger .info ('User "%s" is in no experiment.' % user_id )
116
- return None
117
-
118
- if user_experiment_id != experiment .id :
119
- project_config .logger .info ('User "%s" is not in experiment "%s" of group %s.' % (
120
- user_id ,
121
- experiment .key ,
122
- experiment .groupId
123
- ))
124
- return None
125
-
126
- project_config .logger .info ('User "%s" is in experiment %s of group %s.' % (
127
- user_id ,
128
- experiment .key ,
129
- experiment .groupId
130
- ))
131
-
132
- # Bucket user if not in white-list and in group (if any)
133
- variation_id = self .find_bucket (project_config , bucketing_id , experiment .id , experiment .trafficAllocation )
134
- if variation_id :
135
- variation = project_config .get_variation_from_id (experiment .key , variation_id )
136
- project_config .logger .info ('User "%s" is in variation "%s" of experiment %s.' % (
137
- user_id ,
138
- variation .key ,
139
- experiment .key
140
- ))
141
- return variation
142
-
143
- project_config .logger .info ('User "%s" is in no variation.' % user_id )
144
- return None
0 commit comments