Skip to content

Commit 09dad81

Browse files
authored
fix (#300)
1 parent 09b9b5d commit 09dad81

File tree

5 files changed

+125
-7
lines changed

5 files changed

+125
-7
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# 6.3.3 - 2025-08-01
2+
3+
- fix: `get_feature_flag_result` now correctly returns FeatureFlagResult when payload is empty string instead of None
4+
15
# 6.3.2 - 2025-07-31
26

37
- fix: Anthropic's tool calls are now handled properly

posthog/client.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1285,7 +1285,7 @@ def _get_feature_flag_result(
12851285
lookup_match_value = override_match_value or flag_value
12861286
payload = (
12871287
self._compute_payload_locally(key, lookup_match_value)
1288-
if lookup_match_value
1288+
if lookup_match_value is not None
12891289
else None
12901290
)
12911291
flag_result = FeatureFlagResult.from_value_and_payload(
@@ -1586,7 +1586,7 @@ def _capture_feature_flag_called(
15861586
f"$feature/{key}": response,
15871587
}
15881588

1589-
if payload:
1589+
if payload is not None:
15901590
# if payload is not a string, json serialize it to a string
15911591
properties["$feature_flag_payload"] = payload
15921592

@@ -1790,7 +1790,7 @@ def _get_all_flags_and_payloads_locally(
17901790
matched_payload = self._compute_payload_locally(
17911791
flag["key"], flags[flag["key"]]
17921792
)
1793-
if matched_payload:
1793+
if matched_payload is not None:
17941794
payloads[flag["key"]] = matched_payload
17951795
except InconclusiveMatchError:
17961796
# No need to log this, since it's just telling us to fall back to `/decide`

posthog/test/test_client.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2246,3 +2246,112 @@ def test_parse_send_feature_flags_method(self):
22462246
with self.assertRaises(TypeError) as cm:
22472247
client._parse_send_feature_flags(None)
22482248
self.assertIn("Invalid type for send_feature_flags", str(cm.exception))
2249+
2250+
@mock.patch("posthog.client.batch_post")
2251+
def test_get_feature_flag_result_with_empty_string_payload(self, patch_batch_post):
2252+
"""Test that get_feature_flag_result returns a FeatureFlagResult when payload is empty string"""
2253+
client = Client(
2254+
FAKE_TEST_API_KEY,
2255+
personal_api_key="test_personal_api_key",
2256+
sync_mode=True,
2257+
)
2258+
2259+
# Set up local evaluation with a flag that has empty string payload
2260+
client.feature_flags = [
2261+
{
2262+
"id": 1,
2263+
"name": "Test flag",
2264+
"key": "test-flag",
2265+
"is_simple_flag": False,
2266+
"active": True,
2267+
"rollout_percentage": None,
2268+
"filters": {
2269+
"groups": [
2270+
{
2271+
"properties": [],
2272+
"rollout_percentage": None,
2273+
"variant": "empty-variant",
2274+
}
2275+
],
2276+
"multivariate": {
2277+
"variants": [
2278+
{
2279+
"key": "empty-variant",
2280+
"name": "Empty Variant",
2281+
"rollout_percentage": 100,
2282+
}
2283+
]
2284+
},
2285+
"payloads": {
2286+
"empty-variant": "" # Empty string payload
2287+
},
2288+
},
2289+
}
2290+
]
2291+
2292+
# Test get_feature_flag_result
2293+
result = client.get_feature_flag_result(
2294+
"test-flag", "test-user", only_evaluate_locally=True
2295+
)
2296+
2297+
# Should return a FeatureFlagResult, not None
2298+
self.assertIsNotNone(result)
2299+
self.assertEqual(result.key, "test-flag")
2300+
self.assertEqual(result.get_value(), "empty-variant")
2301+
self.assertEqual(result.payload, "") # Should be empty string, not None
2302+
2303+
@mock.patch("posthog.client.batch_post")
2304+
def test_get_all_flags_and_payloads_with_empty_string(self, patch_batch_post):
2305+
"""Test that get_all_flags_and_payloads includes flags with empty string payloads"""
2306+
client = Client(
2307+
FAKE_TEST_API_KEY,
2308+
personal_api_key="test_personal_api_key",
2309+
sync_mode=True,
2310+
)
2311+
2312+
# Set up multiple flags with different payload types
2313+
client.feature_flags = [
2314+
{
2315+
"id": 1,
2316+
"name": "Flag with empty payload",
2317+
"key": "empty-payload-flag",
2318+
"is_simple_flag": False,
2319+
"active": True,
2320+
"filters": {
2321+
"groups": [{"properties": [], "variant": "variant1"}],
2322+
"multivariate": {
2323+
"variants": [{"key": "variant1", "rollout_percentage": 100}]
2324+
},
2325+
"payloads": {"variant1": ""}, # Empty string
2326+
},
2327+
},
2328+
{
2329+
"id": 2,
2330+
"name": "Flag with normal payload",
2331+
"key": "normal-payload-flag",
2332+
"is_simple_flag": False,
2333+
"active": True,
2334+
"filters": {
2335+
"groups": [{"properties": [], "variant": "variant2"}],
2336+
"multivariate": {
2337+
"variants": [{"key": "variant2", "rollout_percentage": 100}]
2338+
},
2339+
"payloads": {"variant2": "normal payload"},
2340+
},
2341+
},
2342+
]
2343+
2344+
result = client.get_all_flags_and_payloads(
2345+
"test-user", only_evaluate_locally=True
2346+
)
2347+
2348+
# Check that both flags are included
2349+
self.assertEqual(result["featureFlags"]["empty-payload-flag"], "variant1")
2350+
self.assertEqual(result["featureFlags"]["normal-payload-flag"], "variant2")
2351+
2352+
# Check that empty string payload is included (not filtered out)
2353+
self.assertIn("empty-payload-flag", result["featureFlagPayloads"])
2354+
self.assertEqual(result["featureFlagPayloads"]["empty-payload-flag"], "")
2355+
self.assertEqual(
2356+
result["featureFlagPayloads"]["normal-payload-flag"], "normal payload"
2357+
)

posthog/types.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ def from_value_and_payload(
110110
variant=variant,
111111
reason=None,
112112
metadata=LegacyFlagMetadata(
113-
payload=payload if payload else None,
113+
payload=payload,
114114
),
115115
)
116116

@@ -178,7 +178,9 @@ def from_value_and_payload(
178178
key=key,
179179
enabled=enabled,
180180
variant=variant,
181-
payload=json.loads(payload) if isinstance(payload, str) else payload,
181+
payload=json.loads(payload)
182+
if isinstance(payload, str) and payload
183+
else payload,
182184
reason=None,
183185
)
184186

@@ -219,6 +221,7 @@ def from_flag_details(
219221
payload=(
220222
json.loads(details.metadata.payload)
221223
if isinstance(details.metadata.payload, str)
224+
and details.metadata.payload
222225
else details.metadata.payload
223226
),
224227
reason=details.reason.description if details.reason else None,
@@ -296,5 +299,7 @@ def to_payloads(response: FlagsResponse) -> Optional[dict[str, str]]:
296299
return {
297300
key: value.metadata.payload
298301
for key, value in response.get("flags", {}).items()
299-
if isinstance(value, FeatureFlag) and value.enabled and value.metadata.payload
302+
if isinstance(value, FeatureFlag)
303+
and value.enabled
304+
and value.metadata.payload is not None
300305
}

posthog/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
VERSION = "6.3.2"
1+
VERSION = "6.3.3"
22

33
if __name__ == "__main__":
44
print(VERSION, end="") # noqa: T201

0 commit comments

Comments
 (0)