1212# limitations under the License.
1313
1414import math
15+
1516try :
16- import mmh3
17+ import mmh3
1718except ImportError :
18- from .lib import pymmh3 as mmh3
19+ from .lib import pymmh3 as mmh3
1920
2021
2122MAX_TRAFFIC_VALUE = 10000
2728
2829
2930class Bucketer (object ):
30- """ Optimizely bucketing algorithm that evenly distributes visitors. """
31+ """ Optimizely bucketing algorithm that evenly distributes visitors. """
3132
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. """
3435
35- self .bucket_seed = HASH_SEED
36+ self .bucket_seed = HASH_SEED
3637
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.
3940
4041 Args:
4142 bucketing_id: ID for bucketing.
@@ -44,11 +45,11 @@ def _generate_unsigned_hash_code_32_bit(self, bucketing_id):
4445 Hash code which is a 32 bit unsigned integer.
4546 """
4647
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
4950
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).
5253
5354 Args:
5455 bucketing_id: ID for bucketing.
@@ -57,11 +58,11 @@ def _generate_bucket_value(self, bucketing_id):
5758 Bucket value corresponding to the provided bucketing ID.
5859 """
5960
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 )
6263
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.
6566
6667 Args:
6768 project_config: Instance of ProjectConfig.
@@ -73,22 +74,21 @@ def find_bucket(self, project_config, bucketing_id, parent_id, traffic_allocatio
7374 Entity ID which may represent experiment or variation.
7475 """
7576
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+ )
8282
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' )
8787
88- return None
88+ return None
8989
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.
9292
9393 Args:
9494 project_config: Instance of ProjectConfig.
@@ -100,45 +100,41 @@ def bucket(self, project_config, experiment, user_id, bucketing_id):
100100 Variation in which user with ID user_id will be put in. None if no variation.
101101 """
102102
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 )
111140 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