Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions posthog/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -622,7 +622,28 @@ def capture(
if flag_options["should_send"]:
try:
if flag_options["only_evaluate_locally"] is True:
# Only use local evaluation
# Local evaluation explicitly requested
feature_variants = self.get_all_flags(
distinct_id,
groups=(groups or {}),
person_properties=flag_options["person_properties"],
group_properties=flag_options["group_properties"],
disable_geoip=disable_geoip,
only_evaluate_locally=True,
flag_keys_to_evaluate=flag_options["flag_keys_filter"],
)
elif flag_options["only_evaluate_locally"] is False:
# Remote evaluation explicitly requested
feature_variants = self.get_feature_variants(
distinct_id,
groups,
person_properties=flag_options["person_properties"],
group_properties=flag_options["group_properties"],
disable_geoip=disable_geoip,
flag_keys_to_evaluate=flag_options["flag_keys_filter"],
)
elif self.feature_flags:
# Local flags available, prefer local evaluation
feature_variants = self.get_all_flags(
distinct_id,
groups=(groups or {}),
Expand All @@ -633,7 +654,7 @@ def capture(
flag_keys_to_evaluate=flag_options["flag_keys_filter"],
)
else:
# Default behavior - use remote evaluation
# Fall back to remote evaluation
feature_variants = self.get_feature_variants(
distinct_id,
groups,
Expand Down
90 changes: 90 additions & 0 deletions posthog/test/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,96 @@ def test_basic_capture_with_feature_flags_switched_off_doesnt_send_them(

self.assertEqual(patch_flags.call_count, 0)

@mock.patch("posthog.client.flags")
def test_capture_with_send_feature_flags_true_and_local_evaluation_uses_local_flags(
self, patch_flags
):
"""Test that send_feature_flags=True with local evaluation enabled uses local flags without API call"""
patch_flags.return_value = {"featureFlags": {"remote-flag": "remote-variant"}}

multivariate_flag = {
"id": 1,
"name": "Beta Feature",
"key": "beta-feature-local",
"active": True,
"rollout_percentage": 100,
"filters": {
"groups": [
{
"rollout_percentage": 100,
},
],
"multivariate": {
"variants": [
{
"key": "first-variant",
"name": "First Variant",
"rollout_percentage": 50,
},
{
"key": "second-variant",
"name": "Second Variant",
"rollout_percentage": 50,
},
]
},
},
}
simple_flag = {
"id": 2,
"name": "Simple Flag",
"key": "simple-flag",
"active": True,
"filters": {
"groups": [
{
"rollout_percentage": 100,
}
],
},
}

with mock.patch("posthog.client.batch_post") as mock_post:
client = Client(
FAKE_TEST_API_KEY,
on_error=self.set_fail,
personal_api_key=FAKE_TEST_API_KEY,
sync_mode=True,
)
client.feature_flags = [multivariate_flag, simple_flag]

msg_uuid = client.capture(
"python test event",
distinct_id="distinct_id",
send_feature_flags=True,
)
self.assertIsNotNone(msg_uuid)
self.assertFalse(self.failed)

# Get the enqueued message from the mock
mock_post.assert_called_once()
batch_data = mock_post.call_args[1]["batch"]
msg = batch_data[0]

self.assertEqual(msg["event"], "python test event")
self.assertEqual(msg["distinct_id"], "distinct_id")

# Verify local flags are included in the event
self.assertIn("$feature/beta-feature-local", msg["properties"])
self.assertIn("$feature/simple-flag", msg["properties"])
self.assertEqual(msg["properties"]["$feature/simple-flag"], True)

# Verify active feature flags are set correctly
active_flags = msg["properties"]["$active_feature_flags"]
self.assertIn("beta-feature-local", active_flags)
self.assertIn("simple-flag", active_flags)

# The remote flag should NOT be included since we used local evaluation
self.assertNotIn("$feature/remote-flag", msg["properties"])

# CRITICAL: Verify the /flags API was NOT called
self.assertEqual(patch_flags.call_count, 0)

@mock.patch("posthog.client.flags")
def test_capture_with_send_feature_flags_options_only_evaluate_locally_true(
self, patch_flags
Expand Down
Loading