Skip to content

Commit f539d7d

Browse files
refactor: type hints private interface (#389)
* add type hints * add null checks/disambiguation for typing * enable mypy strict check * bucket returns None instead of empty dict
1 parent 2a8d2e7 commit f539d7d

35 files changed

+1155
-616
lines changed

.github/workflows/python.yml

+2-3
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ jobs:
4646
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
4747
4848
integration_tests:
49-
uses: optimizely/python-sdk/.github/workflows/integration_test.yml@uzair/test-with-fsc
49+
uses: optimizely/python-sdk/.github/workflows/integration_test.yml@master
5050
secrets:
5151
CI_USER_TOKEN: ${{ secrets.CI_USER_TOKEN }}
5252
TRAVIS_COM_TOKEN: ${{ secrets.TRAVIS_COM_TOKEN }}
@@ -98,5 +98,4 @@ jobs:
9898
- name: Type check with mypy
9999
run: |
100100
mypy .
101-
# disabled until entire sdk is type hinted
102-
# mypy . --exclude "tests/" --strict
101+
mypy . --exclude "tests/" --strict

mypy.ini

+1-9
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,5 @@ show_error_codes = True
1111
pretty = True
1212

1313
# suppress error on conditional import of typing_extensions module
14-
[mypy-optimizely.entities]
15-
no_warn_unused_ignores = True
16-
17-
# suppress error on conditional import of typing_extensions module
18-
[mypy-event_dispatcher]
19-
no_warn_unused_ignores = True
20-
21-
# suppress error on conditional import of typing_extensions module
22-
[mypy-optimizely.condition]
14+
[mypy-optimizely.helpers.types]
2315
no_warn_unused_ignores = True

optimizely/bucketer.py

+36-14
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
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
@@ -11,28 +11,44 @@
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
1416
import math
17+
from sys import version_info
1518

1619
from .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

2743
class 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

Comments
 (0)