1- # Copyright 2016-2017, 2019-2021 Optimizely
1+ # Copyright 2016-2017, 2019-2022 Optimizely
22# Licensed under the Apache License, Version 2.0 (the "License");
33# you may not use this file except in compliance with the License.
44# You may obtain a copy of the License at
1111# See the License for the specific language governing permissions and
1212# limitations under the License.
1313
14+ from __future__ import annotations
15+ from typing import Optional , TYPE_CHECKING
1416import math
17+ from sys import version_info
1518
1619from .lib import pymmh3 as mmh3
1720
1821
19- MAX_TRAFFIC_VALUE = 10000
20- UNSIGNED_MAX_32_BIT_VALUE = 0xFFFFFFFF
21- MAX_HASH_VALUE = math .pow (2 , 32 )
22- HASH_SEED = 1
23- BUCKETING_ID_TEMPLATE = '{bucketing_id}{parent_id}'
24- GROUP_POLICIES = ['random' ]
22+ if version_info < (3 , 8 ):
23+ from typing_extensions import Final
24+ else :
25+ from typing import Final # type: ignore
26+
27+
28+ if TYPE_CHECKING :
29+ # prevent circular dependenacy by skipping import at runtime
30+ from .project_config import ProjectConfig
31+ from .entities import Experiment , Variation
32+ from .helpers .types import TrafficAllocation
33+
34+
35+ MAX_TRAFFIC_VALUE : Final = 10000
36+ UNSIGNED_MAX_32_BIT_VALUE : Final = 0xFFFFFFFF
37+ MAX_HASH_VALUE : Final = math .pow (2 , 32 )
38+ HASH_SEED : Final = 1
39+ BUCKETING_ID_TEMPLATE : Final = '{bucketing_id}{parent_id}'
40+ GROUP_POLICIES : Final = ['random' ]
2541
2642
2743class Bucketer :
2844 """ Optimizely bucketing algorithm that evenly distributes visitors. """
2945
30- def __init__ (self ):
46+ def __init__ (self ) -> None :
3147 """ Bucketer init method to set bucketing seed and logger instance. """
3248
3349 self .bucket_seed = HASH_SEED
3450
35- def _generate_unsigned_hash_code_32_bit (self , bucketing_id ) :
51+ def _generate_unsigned_hash_code_32_bit (self , bucketing_id : str ) -> int :
3652 """ Helper method to retrieve hash code.
3753
3854 Args:
@@ -45,7 +61,7 @@ def _generate_unsigned_hash_code_32_bit(self, bucketing_id):
4561 # Adjusting MurmurHash code to be unsigned
4662 return mmh3 .hash (bucketing_id , self .bucket_seed ) & UNSIGNED_MAX_32_BIT_VALUE
4763
48- def _generate_bucket_value (self , bucketing_id ) :
64+ def _generate_bucket_value (self , bucketing_id : str ) -> int :
4965 """ Helper function to generate bucket value in half-closed interval [0, MAX_TRAFFIC_VALUE).
5066
5167 Args:
@@ -58,7 +74,10 @@ def _generate_bucket_value(self, bucketing_id):
5874 ratio = float (self ._generate_unsigned_hash_code_32_bit (bucketing_id )) / MAX_HASH_VALUE
5975 return math .floor (ratio * MAX_TRAFFIC_VALUE )
6076
61- def find_bucket (self , project_config , bucketing_id , parent_id , traffic_allocations ):
77+ def find_bucket (
78+ self , project_config : ProjectConfig , bucketing_id : str ,
79+ parent_id : Optional [str ], traffic_allocations : list [TrafficAllocation ]
80+ ) -> Optional [str ]:
6281 """ Determine entity based on bucket value and traffic allocations.
6382
6483 Args:
@@ -78,12 +97,15 @@ def find_bucket(self, project_config, bucketing_id, parent_id, traffic_allocatio
7897
7998 for traffic_allocation in traffic_allocations :
8099 current_end_of_range = traffic_allocation .get ('endOfRange' )
81- if bucketing_number < current_end_of_range :
100+ if current_end_of_range is not None and bucketing_number < current_end_of_range :
82101 return traffic_allocation .get ('entityId' )
83102
84103 return None
85104
86- def bucket (self , project_config , experiment , user_id , bucketing_id ):
105+ def bucket (
106+ self , project_config : ProjectConfig ,
107+ experiment : Experiment , user_id : str , bucketing_id : str
108+ ) -> tuple [Optional [Variation ], list [str ]]:
87109 """ For a given experiment and bucketing ID determines variation to be shown to user.
88110
89111 Args:
@@ -97,7 +119,7 @@ def bucket(self, project_config, experiment, user_id, bucketing_id):
97119 and array of log messages representing decision making.
98120 */.
99121 """
100- decide_reasons = []
122+ decide_reasons : list [ str ] = []
101123 if not experiment :
102124 return None , decide_reasons
103125
0 commit comments