Skip to content

Commit 1438708

Browse files
authored
feat: use the variant as the exposure event value (#222)
1 parent 7fc7224 commit 1438708

3 files changed

Lines changed: 105 additions & 17 deletions

File tree

‎flagsmith/flagsmith.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -362,11 +362,11 @@ def get_experiment_flag(
362362
if not self._event_processor:
363363
raise ValueError("Events must be enabled to use experiment flags.")
364364
flag = self.get_identity_flags(identifier, traits).get_flag(feature_name)
365-
if not flag.is_default:
365+
if isinstance(flag, Flag):
366366
self.track_exposure_event(
367367
feature_name=feature_name,
368368
identifier=identifier,
369-
value=flag.value,
369+
value=flag.variant if flag.variant is not None else flag.value,
370370
traits=traits,
371371
)
372372
return flag

‎tests/conftest.py‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,32 @@
2323
DATA_DIR = os.path.join(os.path.dirname(__file__), "data")
2424

2525

26+
@pytest.fixture(autouse=True)
27+
def stop_flagsmith_background_threads(
28+
monkeypatch: pytest.MonkeyPatch,
29+
) -> Generator[None, None, None]:
30+
# Flagsmith starts polling, streaming and event-processor background
31+
# threads. Track every instance created during a test and stop its threads
32+
# on teardown so they don't leak across the suite (mirroring __del__, but
33+
# deterministically rather than at GC).
34+
instances: typing.List[Flagsmith] = []
35+
original_init = Flagsmith.__init__
36+
37+
def tracking_init(self: Flagsmith, *args: typing.Any, **kwargs: typing.Any) -> None:
38+
instances.append(self)
39+
original_init(self, *args, **kwargs)
40+
41+
monkeypatch.setattr(Flagsmith, "__init__", tracking_init)
42+
yield
43+
for flagsmith in instances:
44+
if getattr(flagsmith, "environment_data_polling_manager_thread", None):
45+
flagsmith.environment_data_polling_manager_thread.stop()
46+
if getattr(flagsmith, "event_stream_thread", None):
47+
flagsmith.event_stream_thread.stop()
48+
if flagsmith._event_processor:
49+
flagsmith._event_processor.stop()
50+
51+
2652
@pytest.fixture()
2753
def analytics_processor() -> AnalyticsProcessor:
2854
return AnalyticsProcessor(

‎tests/test_flagsmith.py‎

Lines changed: 77 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -959,12 +959,8 @@ def test_track_event_raises_without_config(api_key: str) -> None:
959959

960960
def test_track_event_rejects_reserved_prefix(api_key: str) -> None:
961961
flagsmith = Flagsmith(environment_key=api_key, enable_events=True)
962-
try:
963-
with pytest.raises(ValueError, match='reserved "\\$" prefix'):
964-
flagsmith.track_event("$made_up")
965-
finally:
966-
if flagsmith._event_processor:
967-
flagsmith._event_processor.stop()
962+
with pytest.raises(ValueError, match='reserved "\\$" prefix'):
963+
flagsmith.track_event("$made_up")
968964

969965

970966
def test_event_processor_config_without_enable_events_raises(api_key: str) -> None:
@@ -978,15 +974,11 @@ def test_event_processor_config_without_enable_events_raises(api_key: str) -> No
978974

979975
def test_enable_events_without_config_uses_default(api_key: str) -> None:
980976
flagsmith = Flagsmith(environment_key=api_key, enable_events=True)
981-
try:
982-
assert flagsmith._event_processor is not None
983-
assert (
984-
flagsmith._event_processor._batch_endpoint
985-
== "https://events.api.flagsmith.com/v1/events"
986-
)
987-
finally:
988-
if flagsmith._event_processor:
989-
flagsmith._event_processor.stop()
977+
assert flagsmith._event_processor is not None
978+
assert (
979+
flagsmith._event_processor._batch_endpoint
980+
== "https://events.api.flagsmith.com/v1/events"
981+
)
990982

991983

992984
def test_track_event_delegates_to_event_processor(
@@ -1116,3 +1108,73 @@ def default_flag_handler(feature_name: str) -> DefaultFlag:
11161108
assert result.is_default is True
11171109
assert result.value == "default-variant"
11181110
mock_track.assert_not_called()
1111+
1112+
1113+
def test_get_experiment_flag_uses_variant_as_exposure_value(
1114+
mocker: MockerFixture, api_key: str
1115+
) -> None:
1116+
# Given - a resolved flag carrying a variant
1117+
config = EventProcessorConfig(events_api_url="http://test/")
1118+
flagsmith = Flagsmith(
1119+
environment_key=api_key, enable_events=True, event_processor_config=config
1120+
)
1121+
flag = Flag(
1122+
enabled=True,
1123+
value="blue",
1124+
feature_name="checkout_v2",
1125+
feature_id=1,
1126+
variant="control",
1127+
)
1128+
mocker.patch.object(
1129+
flagsmith,
1130+
"get_identity_flags",
1131+
return_value=Flags(flags={"checkout_v2": flag}),
1132+
)
1133+
mock_track = mocker.patch.object(flagsmith._event_processor, "track_exposure_event")
1134+
1135+
# When
1136+
flagsmith.get_experiment_flag(feature_name="checkout_v2", identifier="user1")
1137+
1138+
# Then - the exposure value is the variant, not the flag value
1139+
mock_track.assert_called_once_with(
1140+
feature_name="checkout_v2",
1141+
identifier="user1",
1142+
value="control",
1143+
traits=None,
1144+
metadata=None,
1145+
)
1146+
1147+
1148+
def test_get_experiment_flag_falls_back_to_value_without_variant(
1149+
mocker: MockerFixture, api_key: str
1150+
) -> None:
1151+
# Given - a resolved flag with no variant
1152+
config = EventProcessorConfig(events_api_url="http://test/")
1153+
flagsmith = Flagsmith(
1154+
environment_key=api_key, enable_events=True, event_processor_config=config
1155+
)
1156+
flag = Flag(
1157+
enabled=True,
1158+
value="blue",
1159+
feature_name="checkout_v2",
1160+
feature_id=1,
1161+
variant=None,
1162+
)
1163+
mocker.patch.object(
1164+
flagsmith,
1165+
"get_identity_flags",
1166+
return_value=Flags(flags={"checkout_v2": flag}),
1167+
)
1168+
mock_track = mocker.patch.object(flagsmith._event_processor, "track_exposure_event")
1169+
1170+
# When
1171+
flagsmith.get_experiment_flag(feature_name="checkout_v2", identifier="user1")
1172+
1173+
# Then - the exposure value falls back to the flag value
1174+
mock_track.assert_called_once_with(
1175+
feature_name="checkout_v2",
1176+
identifier="user1",
1177+
value="blue",
1178+
traits=None,
1179+
metadata=None,
1180+
)

0 commit comments

Comments
 (0)