Skip to content
This repository was archived by the owner on Nov 8, 2024. It is now read-only.

Commit 566bee2

Browse files
authored
Allow assigning subject from overrides when experiment is not enabled (#11)
* return subject from overrides when experiment not enabled * make logger callback required
1 parent 8a659a2 commit 566bee2

File tree

4 files changed

+61
-14
lines changed

4 files changed

+61
-14
lines changed

eppo_client/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from eppo_client.http_client import HttpClient, SdkParams
1111
from eppo_client.read_write_lock import ReadWriteLock
1212

13-
__version__ = "1.0.2"
13+
__version__ = "1.0.3"
1414

1515
__client: Optional[EppoClient] = None
1616
__lock = ReadWriteLock()

eppo_client/client.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class EppoClient:
2020
def __init__(
2121
self,
2222
config_requestor: ExperimentConfigurationRequestor,
23-
assignment_logger: AssignmentLogger = AssignmentLogger(),
23+
assignment_logger: AssignmentLogger,
2424
):
2525
self.__config_requestor = config_requestor
2626
self.__assignment_logger = assignment_logger
@@ -45,6 +45,9 @@ def get_assignment(
4545
validate_not_blank("subject_key", subject_key)
4646
validate_not_blank("experiment_key", experiment_key)
4747
experiment_config = self.__config_requestor.get_configuration(experiment_key)
48+
override = self._get_subject_variation_override(experiment_config, subject_key)
49+
if override:
50+
return override
4851
if (
4952
experiment_config is None
5053
or not experiment_config.enabled
@@ -56,9 +59,6 @@ def get_assignment(
5659
)
5760
):
5861
return None
59-
override = self._get_subject_variation_override(experiment_config, subject_key)
60-
if override:
61-
return override
6262
shard = get_shard(
6363
"assignment-{}-{}".format(subject_key, experiment_key),
6464
experiment_config.subject_shards,
@@ -98,10 +98,13 @@ def _shutdown(self):
9898
self.__poller.stop()
9999

100100
def _get_subject_variation_override(
101-
self, experiment_config: ExperimentConfigurationDto, subject: str
101+
self, experiment_config: Optional[ExperimentConfigurationDto], subject: str
102102
) -> Optional[str]:
103103
subject_hash = hashlib.md5(subject.encode("utf-8")).hexdigest()
104-
if subject_hash in experiment_config.overrides:
104+
if (
105+
experiment_config is not None
106+
and subject_hash in experiment_config.overrides
107+
):
105108
return experiment_config.overrides[subject_hash]
106109
return None
107110

eppo_client/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
class Config(SdkBaseModel):
88
api_key: str
99
base_url: str = "https://eppo.cloud/api"
10-
assignment_logger: AssignmentLogger = AssignmentLogger()
10+
assignment_logger: AssignmentLogger
1111

1212
def _validate(self):
1313
validate_not_blank("api_key", self.api_key)

test/client_test.py

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from unittest.mock import patch
55
import httpretty # type: ignore
66
import pytest
7+
from eppo_client.assignment_logger import AssignmentLogger
78
from eppo_client.client import EppoClient
89
from eppo_client.config import Config
910
from eppo_client.configuration_requestor import (
@@ -43,7 +44,13 @@ def init_fixture():
4344
MOCK_BASE_URL + "/randomized_assignment/config",
4445
body=config_response_json,
4546
)
46-
client = init(Config(base_url=MOCK_BASE_URL, api_key="dummy"))
47+
client = init(
48+
Config(
49+
base_url=MOCK_BASE_URL,
50+
api_key="dummy",
51+
assignment_logger=AssignmentLogger(),
52+
)
53+
)
4754
sleep(0.1) # wait for initialization
4855
yield
4956
client._shutdown()
@@ -52,15 +59,19 @@ def init_fixture():
5259

5360
@patch("eppo_client.configuration_requestor.ExperimentConfigurationRequestor")
5461
def test_assign_blank_experiment(mock_config_requestor):
55-
client = EppoClient(config_requestor=mock_config_requestor)
62+
client = EppoClient(
63+
config_requestor=mock_config_requestor, assignment_logger=AssignmentLogger()
64+
)
5665
with pytest.raises(Exception) as exc_info:
5766
client.get_assignment("subject-1", "")
5867
assert exc_info.value.args[0] == "Invalid value for experiment_key: cannot be blank"
5968

6069

6170
@patch("eppo_client.configuration_requestor.ExperimentConfigurationRequestor")
6271
def test_assign_blank_subject(mock_config_requestor):
63-
client = EppoClient(config_requestor=mock_config_requestor)
72+
client = EppoClient(
73+
config_requestor=mock_config_requestor, assignment_logger=AssignmentLogger()
74+
)
6475
with pytest.raises(Exception) as exc_info:
6576
client.get_assignment("", "experiment-1")
6677
assert exc_info.value.args[0] == "Invalid value for subject_key: cannot be blank"
@@ -78,7 +89,9 @@ def test_assign_subject_not_in_sample(mock_config_requestor):
7889
name="recommendation_algo",
7990
overrides=dict(),
8091
)
81-
client = EppoClient(config_requestor=mock_config_requestor)
92+
client = EppoClient(
93+
config_requestor=mock_config_requestor, assignment_logger=AssignmentLogger()
94+
)
8295
assert client.get_assignment("user-1", "experiment-key-1") is None
8396

8497

@@ -139,7 +152,9 @@ def test_assign_subject_with_with_attributes_and_rules(mock_config_requestor):
139152
overrides=dict(),
140153
rules=[text_rule],
141154
)
142-
client = EppoClient(config_requestor=mock_config_requestor)
155+
client = EppoClient(
156+
config_requestor=mock_config_requestor, assignment_logger=AssignmentLogger()
157+
)
143158
assert client.get_assignment("user-1", "experiment-key-1") is None
144159
assert (
145160
client.get_assignment(
@@ -165,10 +180,39 @@ def test_with_subject_in_overrides(mock_config_requestor):
165180
name="recommendation_algo",
166181
overrides={"d6d7705392bc7af633328bea8c4c6904": "override-variation"},
167182
)
168-
client = EppoClient(config_requestor=mock_config_requestor)
183+
client = EppoClient(
184+
config_requestor=mock_config_requestor, assignment_logger=AssignmentLogger()
185+
)
169186
assert client.get_assignment("user-1", "experiment-key-1") == "override-variation"
170187

171188

189+
@patch("eppo_client.configuration_requestor.ExperimentConfigurationRequestor")
190+
def test_with_subject_in_overrides_exp_disabled(mock_config_requestor):
191+
mock_config_requestor.get_configuration.return_value = ExperimentConfigurationDto(
192+
subjectShards=10000,
193+
percentExposure=0,
194+
enabled=False,
195+
variations=[
196+
VariationDto(name="control", shardRange=ShardRange(start=0, end=100))
197+
],
198+
name="recommendation_algo",
199+
overrides={"d6d7705392bc7af633328bea8c4c6904": "override-variation"},
200+
)
201+
client = EppoClient(
202+
config_requestor=mock_config_requestor, assignment_logger=AssignmentLogger()
203+
)
204+
assert client.get_assignment("user-1", "experiment-key-1") == "override-variation"
205+
206+
207+
@patch("eppo_client.configuration_requestor.ExperimentConfigurationRequestor")
208+
def test_with_null_experiment_config(mock_config_requestor):
209+
mock_config_requestor.get_configuration.return_value = None
210+
client = EppoClient(
211+
config_requestor=mock_config_requestor, assignment_logger=AssignmentLogger()
212+
)
213+
assert client.get_assignment("user-1", "experiment-key-1") is None
214+
215+
172216
@pytest.mark.parametrize("test_case", test_data)
173217
def test_assign_subject_in_sample(test_case):
174218
print("---- Test case for {} Experiment".format(test_case["experiment"]))

0 commit comments

Comments
 (0)