Compare commits

..
Author SHA1 Message Date
Dylan MartinandGitHub 337f7da7c5 fix(flags): remove lower() when evaluating feature flag payloads – these payloads are case-sensitive! (#191)
* haha okay

* tests workin

* format

* use case-sensitive comparisons

* omg LOL

* fix tests

* jeez
2025-02-19 19:51:40 -05:00
5 changed files with 96 additions and 8 deletions
+2 -2
View File
@@ -13,10 +13,10 @@ jobs:
with:
fetch-depth: 1
- name: Set up Python 3.13
- name: Set up Python 3.11
uses: actions/setup-python@v2
with:
python-version: "3.13"
python-version: 3.11.11
- uses: actions/cache@v3
with:
+3
View File
@@ -1,3 +1,6 @@
## 3.14.2 - 2025-02-19
1. Evaluate feature flag payloads with case sensitivity correctly. Fixes <https://github.com/PostHog/posthog-python/issues/178>
## 3.14.1 - 2025-02-18
+9 -5
View File
@@ -822,7 +822,7 @@ class Client(object):
distinct_id, groups, person_properties, group_properties, disable_geoip
)
response = responses_and_payloads["featureFlags"].get(key, None)
payload = responses_and_payloads["featureFlagPayloads"].get(str(key).lower(), None)
payload = responses_and_payloads["featureFlagPayloads"].get(str(key), None)
except Exception as e:
self.log.exception(f"[FEATURE FLAGS] Unable to get feature flags and payloads: {e}")
@@ -875,10 +875,14 @@ class Client(object):
if self.feature_flags_by_key is None:
return payload
flag_definition = self.feature_flags_by_key.get(key) or {}
flag_filters = flag_definition.get("filters") or {}
flag_payloads = flag_filters.get("payloads") or {}
payload = flag_payloads.get(str(match_value).lower(), None)
flag_definition = self.feature_flags_by_key.get(key)
if flag_definition:
flag_filters = flag_definition.get("filters") or {}
flag_payloads = flag_filters.get("payloads") or {}
# For boolean flags, convert True to "true"
# For multivariate flags, use the variant string as-is
lookup_value = "true" if isinstance(match_value, bool) and match_value else str(match_value)
payload = flag_payloads.get(lookup_value, None)
return payload
def get_all_flags(
+81
View File
@@ -4640,3 +4640,84 @@ class TestConsistency(unittest.TestCase):
self.assertEqual(feature_flag_match, results[i])
else:
self.assertFalse(feature_flag_match)
@mock.patch("posthog.client.decide")
def test_feature_flag_case_sensitive(self, mock_decide):
mock_decide.return_value = {"featureFlags": {}} # Ensure decide returns empty flags
client = Client(api_key=FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY)
client.feature_flags = [
{
"id": 1,
"key": "Beta-Feature",
"active": True,
"filters": {
"groups": [{"properties": [], "rollout_percentage": 100}],
},
}
]
# Test that flag evaluation is case-sensitive
self.assertTrue(client.feature_enabled("Beta-Feature", "user1"))
self.assertFalse(client.feature_enabled("beta-feature", "user1"))
self.assertFalse(client.feature_enabled("BETA-FEATURE", "user1"))
@mock.patch("posthog.client.decide")
def test_feature_flag_payload_case_sensitive(self, mock_decide):
mock_decide.return_value = {
"featureFlags": {"Beta-Feature": True},
"featureFlagPayloads": {"Beta-Feature": {"some": "value"}},
}
client = Client(api_key=FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY)
client.feature_flags = [
{
"id": 1,
"key": "Beta-Feature",
"active": True,
"filters": {
"groups": [{"properties": [], "rollout_percentage": 100}],
"payloads": {
"true": {"some": "value"},
},
},
}
]
# Test that payload retrieval is case-sensitive
self.assertEqual(client.get_feature_flag_payload("Beta-Feature", "user1"), {"some": "value"})
self.assertIsNone(client.get_feature_flag_payload("beta-feature", "user1"))
self.assertIsNone(client.get_feature_flag_payload("BETA-FEATURE", "user1"))
@mock.patch("posthog.client.decide")
def test_feature_flag_case_sensitive_consistency(self, mock_decide):
mock_decide.return_value = {
"featureFlags": {"Beta-Feature": True},
"featureFlagPayloads": {"Beta-Feature": {"some": "value"}},
}
client = Client(api_key=FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY)
client.feature_flags = [
{
"id": 1,
"key": "Beta-Feature",
"active": True,
"filters": {
"groups": [{"properties": [], "rollout_percentage": 100}],
"payloads": {
"true": {"some": "value"},
},
},
}
]
# Test that flag evaluation and payload retrieval are consistently case-sensitive
# Only exact match should work
self.assertTrue(client.feature_enabled("Beta-Feature", "user1"))
self.assertEqual(client.get_feature_flag_payload("Beta-Feature", "user1"), {"some": "value"})
# Different cases should not match
test_cases = ["beta-feature", "BETA-FEATURE", "bEtA-FeAtUrE"]
for case in test_cases:
self.assertFalse(client.feature_enabled(case, "user1"))
self.assertIsNone(client.get_feature_flag_payload(case, "user1"))
+1 -1
View File
@@ -1,4 +1,4 @@
VERSION = "3.14.1"
VERSION = "3.14.2"
if __name__ == "__main__":
print(VERSION, end="") # noqa: T201