Skip to content

Commit 103a7ad

Browse files
authored
fix: capture enriches with local eval when enabled (#380)
Captured events now use local evaluation results when `send_feature_flags` is `True` and local evaluation is enabled.
1 parent fff9992 commit 103a7ad

File tree

2 files changed

+113
-2
lines changed

2 files changed

+113
-2
lines changed

posthog/client.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -623,7 +623,28 @@ def capture(
623623
if flag_options["should_send"]:
624624
try:
625625
if flag_options["only_evaluate_locally"] is True:
626-
# Only use local evaluation
626+
# Local evaluation explicitly requested
627+
feature_variants = self.get_all_flags(
628+
distinct_id,
629+
groups=(groups or {}),
630+
person_properties=flag_options["person_properties"],
631+
group_properties=flag_options["group_properties"],
632+
disable_geoip=disable_geoip,
633+
only_evaluate_locally=True,
634+
flag_keys_to_evaluate=flag_options["flag_keys_filter"],
635+
)
636+
elif flag_options["only_evaluate_locally"] is False:
637+
# Remote evaluation explicitly requested
638+
feature_variants = self.get_feature_variants(
639+
distinct_id,
640+
groups,
641+
person_properties=flag_options["person_properties"],
642+
group_properties=flag_options["group_properties"],
643+
disable_geoip=disable_geoip,
644+
flag_keys_to_evaluate=flag_options["flag_keys_filter"],
645+
)
646+
elif self.feature_flags:
647+
# Local flags available, prefer local evaluation
627648
feature_variants = self.get_all_flags(
628649
distinct_id,
629650
groups=(groups or {}),
@@ -634,7 +655,7 @@ def capture(
634655
flag_keys_to_evaluate=flag_options["flag_keys_filter"],
635656
)
636657
else:
637-
# Default behavior - use remote evaluation
658+
# Fall back to remote evaluation
638659
feature_variants = self.get_feature_variants(
639660
distinct_id,
640661
groups,

posthog/test/test_client.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -752,6 +752,96 @@ def test_basic_capture_with_feature_flags_switched_off_doesnt_send_them(
752752

753753
self.assertEqual(patch_flags.call_count, 0)
754754

755+
@mock.patch("posthog.client.flags")
756+
def test_capture_with_send_feature_flags_true_and_local_evaluation_uses_local_flags(
757+
self, patch_flags
758+
):
759+
"""Test that send_feature_flags=True with local evaluation enabled uses local flags without API call"""
760+
patch_flags.return_value = {"featureFlags": {"remote-flag": "remote-variant"}}
761+
762+
multivariate_flag = {
763+
"id": 1,
764+
"name": "Beta Feature",
765+
"key": "beta-feature-local",
766+
"active": True,
767+
"rollout_percentage": 100,
768+
"filters": {
769+
"groups": [
770+
{
771+
"rollout_percentage": 100,
772+
},
773+
],
774+
"multivariate": {
775+
"variants": [
776+
{
777+
"key": "first-variant",
778+
"name": "First Variant",
779+
"rollout_percentage": 50,
780+
},
781+
{
782+
"key": "second-variant",
783+
"name": "Second Variant",
784+
"rollout_percentage": 50,
785+
},
786+
]
787+
},
788+
},
789+
}
790+
simple_flag = {
791+
"id": 2,
792+
"name": "Simple Flag",
793+
"key": "simple-flag",
794+
"active": True,
795+
"filters": {
796+
"groups": [
797+
{
798+
"rollout_percentage": 100,
799+
}
800+
],
801+
},
802+
}
803+
804+
with mock.patch("posthog.client.batch_post") as mock_post:
805+
client = Client(
806+
FAKE_TEST_API_KEY,
807+
on_error=self.set_fail,
808+
personal_api_key=FAKE_TEST_API_KEY,
809+
sync_mode=True,
810+
)
811+
client.feature_flags = [multivariate_flag, simple_flag]
812+
813+
msg_uuid = client.capture(
814+
"python test event",
815+
distinct_id="distinct_id",
816+
send_feature_flags=True,
817+
)
818+
self.assertIsNotNone(msg_uuid)
819+
self.assertFalse(self.failed)
820+
821+
# Get the enqueued message from the mock
822+
mock_post.assert_called_once()
823+
batch_data = mock_post.call_args[1]["batch"]
824+
msg = batch_data[0]
825+
826+
self.assertEqual(msg["event"], "python test event")
827+
self.assertEqual(msg["distinct_id"], "distinct_id")
828+
829+
# Verify local flags are included in the event
830+
self.assertIn("$feature/beta-feature-local", msg["properties"])
831+
self.assertIn("$feature/simple-flag", msg["properties"])
832+
self.assertEqual(msg["properties"]["$feature/simple-flag"], True)
833+
834+
# Verify active feature flags are set correctly
835+
active_flags = msg["properties"]["$active_feature_flags"]
836+
self.assertIn("beta-feature-local", active_flags)
837+
self.assertIn("simple-flag", active_flags)
838+
839+
# The remote flag should NOT be included since we used local evaluation
840+
self.assertNotIn("$feature/remote-flag", msg["properties"])
841+
842+
# CRITICAL: Verify the /flags API was NOT called
843+
self.assertEqual(patch_flags.call_count, 0)
844+
755845
@mock.patch("posthog.client.flags")
756846
def test_capture_with_send_feature_flags_options_only_evaluate_locally_true(
757847
self, patch_flags

0 commit comments

Comments
 (0)