Skip to content

Commit 6a097cf

Browse files
authored
fix: Update FlagResult class and fix assignment tracking issue (#37)
1 parent afbd36c commit 6a097cf

File tree

11 files changed

+49
-40
lines changed

11 files changed

+49
-40
lines changed

src/amplitude_experiment/assignment/assignment.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@ class Assignment:
1212
def __init__(self, user: User, results: Dict[str, FlagResult]):
1313
self.user = user
1414
self.results = results
15-
self.timestamp = time.time()
15+
self.timestamp = time.time() * 1000
1616

1717
def canonicalize(self) -> str:
18-
user = self.user.user_id.strip() if self.user.user_id else 'undefined'
19-
device = self.user.device_id.strip() if self.user.device_id else 'undefined'
18+
user = self.user.user_id.strip() if self.user.user_id else 'None'
19+
device = self.user.device_id.strip() if self.user.device_id else 'None'
2020
canonical = user + ' ' + device + ' '
2121
for key in sorted(self.results):
22-
value = self.results[key].value.strip() if self.results[key] else 'undefined'
22+
value = self.results[key].variant['key'].strip() if self.results[key] and self.results[key].variant and \
23+
self.results[key].variant.get('key') else 'None'
2324
canonical += key.strip() + ' ' + value + ' '
2425
return canonical

src/amplitude_experiment/assignment/assignment_config.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33

44
class AssignmentConfig(amplitude.Config):
5-
def __init__(self, api_key: str, cache_capacity: int = 65536, **kw):
6-
self.api_key = api_key
7-
self.cache_capacity = cache_capacity
5+
def __init__(self, cache_capacity: int = 65536, **kw):
86
super(AssignmentConfig, self).__init__(**kw)
7+
self.cache_capacity = cache_capacity

src/amplitude_experiment/assignment/assignment_service.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from ..assignment.assignment import Assignment
33
from ..assignment.assignment import DAY_MILLIS
44
from ..assignment.assignment_filter import AssignmentFilter
5+
from ..util import hash_code
56

67
FLAG_TYPE_MUTUAL_EXCLUSION_GROUP = "mutual-exclusion-group"
78
FLAG_TYPE_HOLDOUT_GROUP = "holdout-group"
@@ -11,7 +12,7 @@ def to_event(assignment: Assignment) -> BaseEvent:
1112
event = BaseEvent(event_type='[Experiment] Assignment', user_id=assignment.user.user_id,
1213
device_id=assignment.user.device_id, event_properties={}, user_properties={})
1314
for key in sorted(assignment.results):
14-
event.event_properties[key + '.variant'] = assignment.results[key].value
15+
event.event_properties[key + '.variant'] = assignment.results[key].variant['key']
1516

1617
set_props = {}
1718
unset_props = {}
@@ -22,12 +23,12 @@ def to_event(assignment: Assignment) -> BaseEvent:
2223
elif assignment.results[key].is_default_variant:
2324
unset_props[f'[Experiment] {key}'] = '-'
2425
else:
25-
set_props[f'[Experiment] {key}'] = assignment.results[key].value
26+
set_props[f'[Experiment] {key}'] = assignment.results[key].variant['key']
2627

2728
event.user_properties['$set'] = set_props
2829
event.user_properties['$unset'] = unset_props
2930

30-
event.insert_id = f'{event.user_id} {event.device_id} {hash(assignment.canonicalize())} {assignment.timestamp / DAY_MILLIS}'
31+
event.insert_id = f'{event.user_id} {event.device_id} {hash_code(assignment.canonicalize())} {int(assignment.timestamp / DAY_MILLIS)}'
3132

3233
return event
3334

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
class FlagResult:
2-
def __init__(self, value: str, is_default_variant: bool, payload: str = None, exp_key: str = None,
3-
deployed: bool = None, type: str = None):
4-
self.value = value
5-
self.payload = payload
6-
self.is_default_variant = is_default_variant
7-
self.exp_key = exp_key
8-
self.deployed = deployed
9-
self.type = type
2+
def __init__(self, result):
3+
self.variant = result.get('variant')
4+
self.description = result.get('description')
5+
self.is_default_variant = result.get('isDefaultVariant')
6+
self.exp_key = result.get('exp_key')
7+
self.deployed = result.get('deployed')
8+
self.type = result.get('type')

src/amplitude_experiment/local/client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from amplitude import Amplitude
77

88
from .config import LocalEvaluationConfig
9+
from ..flagresult import FlagResult
910
from ..assignment import Assignment, AssignmentFilter, AssignmentService
1011
from ..assignment.assignment_service import FLAG_TYPE_MUTUAL_EXCLUSION_GROUP, FLAG_TYPE_HOLDOUT_GROUP
1112
from ..user import User
@@ -84,8 +85,7 @@ def evaluate(self, user: User, flag_keys: List[str] = None) -> Dict[str, Variant
8485
variants[key] = Variant(value['variant'].get('key'), value['variant'].get('payload'))
8586
if included or evaluation_result[key]['type'] == FLAG_TYPE_MUTUAL_EXCLUSION_GROUP or \
8687
evaluation_result[key]['type'] == FLAG_TYPE_HOLDOUT_GROUP:
87-
assignment_result[key] = evaluation_result[key]
88-
88+
assignment_result[key] = FlagResult(value)
8989
if self.assignment_service:
9090
self.assignment_service.track(Assignment(user, assignment_result))
9191
return variants
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
from .cache import Cache
2+
from .hash_code import hash_code

src/amplitude_experiment/util/cache.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import time
22

3+
34
class Cache:
45
class Node:
56
def __init__(self, key, value):
@@ -64,4 +65,3 @@ def put(self, key, value):
6465
new_node = self.Node(key, value)
6566
self._add_node(new_node)
6667
self.cache[key] = new_node
67-
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
def hash_code(string) -> int:
2+
hash_value = 0
3+
if len(string) == 0:
4+
return hash_value
5+
for char in string:
6+
chr_code = ord(char)
7+
hash_value = ((hash_value << 5) - hash_value) + chr_code
8+
hash_value &= 0xFFFFFFFF
9+
return hash_value

tests/local/assignment/assignment_filter_test.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ def test_single_assignment(self):
1010
assignment_filter = AssignmentFilter(100)
1111
user = User(user_id='user', device_id='device')
1212
results = {}
13-
result1 = FlagResult(value='on', is_default_variant=False)
14-
result2 = FlagResult(value='control', is_default_variant=True)
13+
result1 = FlagResult({'variant': {'key': 'on'}, 'isDefaultVariant': False})
14+
result2 = FlagResult({'variant': {'key': 'control'}, 'isDefaultVariant': True})
1515
results['flag-key-1'] = result1
1616
results['flag-key-2'] = result2
1717
assignment = Assignment(user, results)
@@ -21,8 +21,8 @@ def test_duplicate_assignments(self):
2121
assignment_filter = AssignmentFilter(100)
2222
user = User(user_id='user', device_id='device')
2323
results = {}
24-
result1 = FlagResult(value='on', is_default_variant=False)
25-
result2 = FlagResult(value='control', is_default_variant=True)
24+
result1 = FlagResult({'variant': {'key': 'on'}, 'isDefaultVariant': False})
25+
result2 = FlagResult({'variant': {'key': 'control'}, 'isDefaultVariant': True})
2626
results['flag-key-1'] = result1
2727
results['flag-key-2'] = result2
2828
assignment1 = Assignment(user, results)
@@ -35,8 +35,8 @@ def test_same_user_different_results(self):
3535
user = User(user_id='user', device_id='device')
3636
results1 = {}
3737
results2 = {}
38-
result1 = FlagResult(value='on', is_default_variant=False)
39-
result2 = FlagResult(value='control', is_default_variant=True)
38+
result1 = FlagResult({'variant': {'key': 'on'}, 'isDefaultVariant': False})
39+
result2 = FlagResult({'variant': {'key': 'control'}, 'isDefaultVariant': True})
4040
results1['flag-key-1'] = result1
4141
results1['flag-key-2'] = result2
4242
results2['flag-key-2'] = result1
@@ -51,8 +51,8 @@ def test_same_results_different_users(self):
5151
user1 = User(user_id='user', device_id='device')
5252
user2 = User(user_id='different user', device_id='device')
5353
results = {}
54-
result1 = FlagResult(value='on', is_default_variant=False)
55-
result2 = FlagResult(value='control', is_default_variant=True)
54+
result1 = FlagResult({'variant': {'key': 'on'}, 'isDefaultVariant': False})
55+
result2 = FlagResult({'variant': {'key': 'control'}, 'isDefaultVariant': True})
5656
results['flag-key-1'] = result1
5757
results['flag-key-2'] = result2
5858
assignment1 = Assignment(user1, results)
@@ -76,8 +76,8 @@ def test_duplicate_assignments_with_different_ordering(self):
7676
user = User(user_id='user', device_id='device')
7777
results1 = {}
7878
results2 = {}
79-
result1 = FlagResult(value='on', is_default_variant=False)
80-
result2 = FlagResult(value='control', is_default_variant=True)
79+
result1 = FlagResult({'variant': {'key': 'on'}, 'isDefaultVariant': False})
80+
result2 = FlagResult({'variant': {'key': 'control'}, 'isDefaultVariant': True})
8181
results1['flag-key-1'] = result1
8282
results1['flag-key-2'] = result2
8383
results2['flag-key-2'] = result2
@@ -93,8 +93,8 @@ def test_lru_replacement(self):
9393
user2 = User(user_id='user2', device_id='device')
9494
user3 = User(user_id='user3', device_id='device')
9595
results = {}
96-
result1 = FlagResult(value='on', is_default_variant=False)
97-
result2 = FlagResult(value='control', is_default_variant=True)
96+
result1 = FlagResult({'variant': {'key': 'on'}, 'isDefaultVariant': False})
97+
result2 = FlagResult({'variant': {'key': 'control'}, 'isDefaultVariant': True})
9898
results['flag-key-1'] = result1
9999
results['flag-key-2'] = result2
100100
assignment1 = Assignment(user1, results)
@@ -110,8 +110,8 @@ def test_lru_expiration(self):
110110
user1 = User(user_id='user1', device_id='device')
111111
user2 = User(user_id='user2', device_id='device')
112112
results = {}
113-
result1 = FlagResult(value='on', is_default_variant=False)
114-
result2 = FlagResult(value='control', is_default_variant=True)
113+
result1 = FlagResult({'variant': {'key': 'on'}, 'isDefaultVariant': False})
114+
result2 = FlagResult({'variant': {'key': 'control'}, 'isDefaultVariant': True})
115115
results['flag-key-1'] = result1
116116
results['flag-key-2'] = result2
117117
assignment1 = Assignment(user1, results)

tests/local/assignment/assignment_service_test.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from src.amplitude_experiment import User, FlagResult
77
from src.amplitude_experiment.assignment import AssignmentFilter, Assignment, DAY_MILLIS, to_event, AssignmentService
8+
from src.amplitude_experiment.util import hash_code
89

910
user = User(user_id='user', device_id='device')
1011

@@ -13,8 +14,8 @@ class AssignmentServiceTestCase(unittest.TestCase):
1314

1415
def test_to_event(self):
1516
results = {}
16-
result1 = FlagResult(value='on', is_default_variant=False)
17-
result2 = FlagResult(value='control', is_default_variant=True)
17+
result1 = FlagResult({'variant': {'key': 'on'}, 'isDefaultVariant': False})
18+
result2 = FlagResult({'variant': {'key': 'control'}, 'isDefaultVariant': True})
1819
results['flag-key-1'] = result1
1920
results['flag-key-2'] = result2
2021
assignment = Assignment(user, results)
@@ -31,7 +32,7 @@ def test_to_event(self):
3132
self.assertEqual(1, len(user_properties['$set']))
3233
self.assertEqual(1, len(user_properties['$unset']))
3334
canonicalization = 'user device flag-key-1 on flag-key-2 control '
34-
expected = f'user device {hash(canonicalization)} {assignment.timestamp / DAY_MILLIS}'
35+
expected = f'user device {hash_code(canonicalization)} {int(assignment.timestamp / DAY_MILLIS)}'
3536
self.assertEqual(expected, event.insert_id)
3637

3738
def test_tracking_called(self):

0 commit comments

Comments
 (0)