From fb1e6a36d8891f5c5123369a71d12554abdf69eb Mon Sep 17 00:00:00 2001 From: wadii Date: Wed, 23 Sep 2026 11:06:55 +0200 Subject: [PATCH] fix: skip exposure event when in-experiment flag has no variant --- flagsmith/flagsmith.py | 9 ++++++++- tests/test_flagsmith.py | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/flagsmith/flagsmith.py b/flagsmith/flagsmith.py index bd4b1b0..d8f34b2 100644 --- a/flagsmith/flagsmith.py +++ b/flagsmith/flagsmith.py @@ -369,7 +369,8 @@ def get_experiment_flag( The exposure event's ``value`` is the flag's variant key. It is only sent when the flag exists, is enabled and the identity is enrolled in - the feature's experiment (``flag.experiment.in_experiment``); any + the feature's experiment (``flag.experiment.in_experiment``) with a + variant; any other outcome is logged and skipped to keep experimentation data clean. A `DefaultFlag` served via the `default_flag_handler` counts as the feature not existing. @@ -395,6 +396,12 @@ def get_experiment_flag( FLAG_EXPOSURE_EVENT, feature_name, ) + elif flag.variant is None: + logger.debug( + "Not sending %s for feature %s: flag has no variant.", + FLAG_EXPOSURE_EVENT, + feature_name, + ) else: self.track_exposure_event( feature_name=feature_name, diff --git a/tests/test_flagsmith.py b/tests/test_flagsmith.py index 7e010c2..9296feb 100644 --- a/tests/test_flagsmith.py +++ b/tests/test_flagsmith.py @@ -1268,6 +1268,46 @@ def test_get_experiment_flag__not_in_experiment__skips_exposure( ) +def test_get_experiment_flag__in_experiment_without_variant__skips_exposure( + mocker: MockerFixture, api_key: str, caplog: pytest.LogCaptureFixture +) -> None: + # Given + config = EventProcessorConfig(events_api_url="http://test/") + flagsmith = Flagsmith( + environment_key=api_key, enable_events=True, event_processor_config=config + ) + flag = Flag( + enabled=True, + value="blue", + feature_name="checkout_v2", + feature_id=1, + variant=None, + experiment=ExperimentMetadata( + id=42, name="New checkout CTA", in_experiment=True + ), + ) + mocker.patch.object( + flagsmith, + "get_identity_flags", + return_value=Flags(flags={"checkout_v2": flag}), + ) + mock_track = mocker.patch.object(flagsmith._event_processor, "track_exposure_event") + + # When + with caplog.at_level(logging.DEBUG, logger="flagsmith.flagsmith"): + result = flagsmith.get_experiment_flag( + feature_name="checkout_v2", identifier="user1" + ) + + # Then + assert result is flag + mock_track.assert_not_called() + assert ( + "Not sending $flag_exposure for feature checkout_v2: flag has no variant." + in caplog.messages + ) + + @responses.activate() def test_flagsmith_posts_analytics_to_analytics_url_when_set( api_key: str, flags_json: str, mocker: MockerFixture