Skip to content

Commit f70602b

Browse files
authored
fix(flags): honor false payload overrides (#813)
* fix(flags): honor false payload overrides * fix(flags): keep payload overrides out of fallback cache * fix(flags): narrow local evaluation value for mypy
1 parent 38a09b8 commit f70602b

3 files changed

Lines changed: 51 additions & 9 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
pypi/posthog: patch
3+
---
4+
5+
Honor false feature flag payload overrides

‎posthog/client.py‎

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2639,8 +2639,10 @@ def _get_feature_flag_result(
26392639
)
26402640
flag_was_locally_evaluated = flag_value is not None
26412641

2642-
if flag_was_locally_evaluated:
2643-
lookup_match_value = override_match_value or flag_value
2642+
if flag_value is not None:
2643+
lookup_match_value = (
2644+
override_match_value if override_match_value is not None else flag_value
2645+
)
26442646
payload = (
26452647
self._compute_payload_locally(key, lookup_match_value)
26462648
if lookup_match_value is not None
@@ -2650,10 +2652,15 @@ def _get_feature_flag_result(
26502652
key, lookup_match_value, payload
26512653
)
26522654

2653-
# Cache successful local evaluation
2654-
if self.flag_cache and flag_result:
2655+
# Cache the local evaluation, not a payload lookup override.
2656+
cached_flag_result = flag_result
2657+
if override_match_value is not None:
2658+
cached_flag_result = FeatureFlagResult.from_value_and_payload(
2659+
key, flag_value, self._compute_payload_locally(key, flag_value)
2660+
)
2661+
if self.flag_cache and cached_flag_result:
26552662
self.flag_cache.set_cached_flag(
2656-
distinct_id, key, flag_result, self.flag_definition_version
2663+
distinct_id, key, cached_flag_result, self.flag_definition_version
26572664
)
26582665
elif only_evaluate_locally:
26592666
if self.feature_flags is None:
@@ -3205,11 +3212,11 @@ def _compute_payload_locally(
32053212
if flag_definition:
32063213
flag_filters = flag_definition.get("filters") or {}
32073214
flag_payloads = flag_filters.get("payloads") or {}
3208-
# For boolean flags, convert True to "true"
3215+
# For boolean flags, use lowercase keys ("true" or "false")
32093216
# For multivariate flags, use the variant string as-is
32103217
lookup_value = (
3211-
"true"
3212-
if isinstance(match_value, bool) and match_value
3218+
str(match_value).lower()
3219+
if isinstance(match_value, bool)
32133220
else str(match_value)
32143221
)
32153222
payload = flag_payloads.get(lookup_value, None)

‎posthog/test/test_feature_flags.py‎

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
)
2222
from posthog.request import APIError, GetResponse
2323
from posthog.test.test_utils import FAKE_TEST_API_KEY
24+
from posthog.utils import FlagCache
2425

2526

2627
# This module preserves the legacy single-flag API's compatibility behavior;
@@ -3777,10 +3778,11 @@ def test_boolean_feature_flag_payloads_local(self, patch_flags):
37773778
"rollout_percentage": 100,
37783779
}
37793780
],
3780-
"payloads": {"true": 300},
3781+
"payloads": {"true": 300, "false": 400},
37813782
},
37823783
}
37833784
self.client.feature_flags = [basic_flag]
3785+
self.client.flag_cache = FlagCache()
37843786

37853787
self.assertEqual(
37863788
self.client.get_feature_flag_payload(
@@ -3798,6 +3800,34 @@ def test_boolean_feature_flag_payloads_local(self, patch_flags):
37983800
),
37993801
300,
38003802
)
3803+
3804+
# A false override must take precedence over a locally evaluated true value.
3805+
self.assertEqual(
3806+
self.client.get_feature_flag_payload(
3807+
"person-flag",
3808+
"some-distinct-id",
3809+
match_value=False,
3810+
person_properties={"region": "USA"},
3811+
),
3812+
400,
3813+
)
3814+
3815+
cached_result = self.client.flag_cache.get_stale_cached_flag(
3816+
"some-distinct-id", "person-flag"
3817+
)
3818+
assert cached_result is not None
3819+
self.assertIs(cached_result.get_value(), True)
3820+
self.assertEqual(cached_result.payload, 300)
3821+
3822+
# Locally evaluated false values use the lowercase "false" payload key.
3823+
self.assertEqual(
3824+
self.client.get_feature_flag_payload(
3825+
"person-flag",
3826+
"some-distinct-id",
3827+
person_properties={"region": "Canada"},
3828+
),
3829+
400,
3830+
)
38013831
self.assertEqual(patch_flags.call_count, 0)
38023832

38033833
@mock.patch.object(Client, "capture")

0 commit comments

Comments
 (0)