1
1
import hashlib
2
2
import datetime
3
3
import logging
4
- from typing import List , Optional
4
+ from typing import Optional
5
5
from eppo_client .assignment_logger import AssignmentLogger
6
6
from eppo_client .configuration_requestor import (
7
7
ExperimentConfigurationDto ,
8
8
ExperimentConfigurationRequestor ,
9
9
)
10
10
from eppo_client .constants import POLL_INTERVAL_MILLIS , POLL_JITTER_MILLIS
11
11
from eppo_client .poller import Poller
12
- from eppo_client .rules import Rule , matches_any_rule
12
+ from eppo_client .rules import find_matching_rule
13
13
from eppo_client .shard import get_shard , is_in_shard_range
14
14
from eppo_client .validation import validate_not_blank
15
15
@@ -32,47 +32,65 @@ def __init__(
32
32
self .__poller .start ()
33
33
34
34
def get_assignment (
35
- self , subject_key : str , experiment_key : str , subject_attributes = dict ()
35
+ self , subject_key : str , flag_key : str , subject_attributes = dict ()
36
36
) -> Optional [str ]:
37
37
"""Maps a subject to a variation for a given experiment
38
38
Returns None if the subject is not part of the experiment sample.
39
39
40
40
:param subject_key: an identifier of the experiment subject, for example a user ID.
41
- :param experiment_key : an experiment identifier
41
+ :param flag_key : an experiment or feature flag identifier
42
42
:param subject_attributes: optional attributes associated with the subject, for example name and email.
43
43
The subject attributes are used for evaluating any targeting rules tied to the experiment.
44
44
"""
45
45
validate_not_blank ("subject_key" , subject_key )
46
- validate_not_blank ("experiment_key " , experiment_key )
47
- experiment_config = self .__config_requestor .get_configuration (experiment_key )
46
+ validate_not_blank ("flag_key " , flag_key )
47
+ experiment_config = self .__config_requestor .get_configuration (flag_key )
48
48
override = self ._get_subject_variation_override (experiment_config , subject_key )
49
49
if override :
50
50
return override
51
- if (
52
- experiment_config is None
53
- or not experiment_config . enabled
54
- or not self . _subject_attributes_satisfy_rules (
55
- subject_attributes , experiment_config . rules
51
+
52
+ if experiment_config is None or not experiment_config . enabled :
53
+ logger . info (
54
+ "[Eppo SDK] No assigned variation. No active experiment or flag for key: "
55
+ + flag_key
56
56
)
57
- or not self ._is_in_experiment_sample (
58
- subject_key , experiment_key , experiment_config
57
+ return None
58
+
59
+ matched_rule = find_matching_rule (subject_attributes , experiment_config .rules )
60
+ if matched_rule is None :
61
+ logger .info (
62
+ "[Eppo SDK] No assigned variation. Subject attributes do not match targeting rules: {0}" .format (
63
+ subject_attributes
64
+ )
59
65
)
66
+ return None
67
+
68
+ allocation = experiment_config .allocations [matched_rule .allocation_key ]
69
+ if not self ._is_in_experiment_sample (
70
+ subject_key ,
71
+ flag_key ,
72
+ experiment_config .subject_shards ,
73
+ allocation .percent_exposure ,
60
74
):
75
+ logger .info (
76
+ "[Eppo SDK] No assigned variation. Subject is not part of experiment sample population"
77
+ )
61
78
return None
79
+
62
80
shard = get_shard (
63
- "assignment-{}-{}" .format (subject_key , experiment_key ),
81
+ "assignment-{}-{}" .format (subject_key , flag_key ),
64
82
experiment_config .subject_shards ,
65
83
)
66
84
assigned_variation = next (
67
85
(
68
- variation .name
69
- for variation in experiment_config .variations
86
+ variation .value
87
+ for variation in allocation .variations
70
88
if is_in_shard_range (shard , variation .shard_range )
71
89
),
72
90
None ,
73
91
)
74
92
assignment_event = {
75
- "experiment" : experiment_key ,
93
+ "experiment" : flag_key ,
76
94
"variation" : assigned_variation ,
77
95
"subject" : subject_key ,
78
96
"timestamp" : datetime .datetime .utcnow ().isoformat (),
@@ -84,13 +102,6 @@ def get_assignment(
84
102
logger .error ("[Eppo SDK] Error logging assignment event: " + str (e ))
85
103
return assigned_variation
86
104
87
- def _subject_attributes_satisfy_rules (
88
- self , subject_attributes : dict , rules : List [Rule ]
89
- ) -> bool :
90
- if len (rules ) == 0 :
91
- return True
92
- return matches_any_rule (subject_attributes , rules )
93
-
94
105
def _shutdown (self ):
95
106
"""Stops all background processes used by the client
96
107
Do not use the client after calling this method.
@@ -112,13 +123,11 @@ def _is_in_experiment_sample(
112
123
self ,
113
124
subject : str ,
114
125
experiment_key : str ,
115
- experiment_config : ExperimentConfigurationDto ,
126
+ subject_shards : int ,
127
+ percent_exposure : float ,
116
128
):
117
129
shard = get_shard (
118
130
"exposure-{}-{}" .format (subject , experiment_key ),
119
- experiment_config .subject_shards ,
120
- )
121
- return (
122
- shard
123
- <= experiment_config .percent_exposure * experiment_config .subject_shards
131
+ subject_shards ,
124
132
)
133
+ return shard <= percent_exposure * subject_shards
0 commit comments