Skip to content

Commit 88a852c

Browse files
committed
formatting
1 parent 5a52af6 commit 88a852c

File tree

4 files changed

+193
-3
lines changed

4 files changed

+193
-3
lines changed

example.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,29 @@
3939
)
4040

4141

42-
# Capture an event
42+
# Capture an event with all feature flags
4343
posthog.capture(
4444
"event",
4545
distinct_id="distinct_id",
4646
properties={"property1": "value", "property2": "value"},
4747
send_feature_flags=True,
4848
)
4949

50+
# Capture an event with specific feature flags using flag_keys
51+
posthog.capture(
52+
"event-with-specific-flags",
53+
distinct_id="distinct_id",
54+
properties={"property1": "value", "property2": "value"},
55+
send_feature_flags={
56+
"only_evaluate_locally": True,
57+
"flag_keys": [
58+
"beta-feature",
59+
"person-on-events-enabled",
60+
], # Only evaluate these two flags
61+
"person_properties": {"plan": "premium"},
62+
},
63+
)
64+
5065
print(posthog.feature_enabled("beta-feature", "distinct_id"))
5166
print(
5267
posthog.feature_enabled(

posthog/client.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,7 @@ def capture(
539539
group_properties=flag_options["group_properties"],
540540
disable_geoip=disable_geoip,
541541
only_evaluate_locally=True,
542+
flag_keys=flag_options["flag_keys"],
542543
)
543544
else:
544545
# Default behavior - use remote evaluation
@@ -549,6 +550,15 @@ def capture(
549550
group_properties=flag_options["group_properties"],
550551
disable_geoip=disable_geoip,
551552
)
553+
554+
# Filter by flag_keys if provided
555+
if flag_options["flag_keys"] is not None:
556+
flag_keys_set = set(flag_options["flag_keys"])
557+
feature_variants = {
558+
key: value
559+
for key, value in feature_variants.items()
560+
if key in flag_keys_set
561+
}
552562
except Exception as e:
553563
self.log.exception(
554564
f"[FEATURE FLAGS] Unable to get feature variants: {e}"
@@ -561,6 +571,7 @@ def capture(
561571
groups=(groups or {}),
562572
disable_geoip=disable_geoip,
563573
only_evaluate_locally=True,
574+
flag_keys=flag_options.get("flag_keys") if flag_options else None,
564575
)
565576

566577
for feature, variant in (feature_variants or {}).items():
@@ -588,7 +599,7 @@ def _parse_send_feature_flags(self, send_feature_flags) -> dict:
588599
589600
Returns:
590601
dict: Normalized options with keys: should_send, only_evaluate_locally,
591-
person_properties, group_properties
602+
person_properties, group_properties, flag_keys
592603
593604
Raises:
594605
TypeError: If send_feature_flags is not bool or dict
@@ -601,13 +612,15 @@ def _parse_send_feature_flags(self, send_feature_flags) -> dict:
601612
),
602613
"person_properties": send_feature_flags.get("person_properties"),
603614
"group_properties": send_feature_flags.get("group_properties"),
615+
"flag_keys": send_feature_flags.get("flag_keys"),
604616
}
605617
elif isinstance(send_feature_flags, bool):
606618
return {
607619
"should_send": send_feature_flags,
608620
"only_evaluate_locally": None,
609621
"person_properties": None,
610622
"group_properties": None,
623+
"flag_keys": None,
611624
}
612625
else:
613626
raise TypeError(
@@ -1667,6 +1680,7 @@ def get_all_flags(
16671680
group_properties={},
16681681
only_evaluate_locally=False,
16691682
disable_geoip=None,
1683+
flag_keys=None,
16701684
) -> Optional[dict[str, Union[bool, str]]]:
16711685
"""
16721686
Get all feature flags for a user.
@@ -1678,6 +1692,7 @@ def get_all_flags(
16781692
group_properties: A dictionary of group properties.
16791693
only_evaluate_locally: Whether to only evaluate locally.
16801694
disable_geoip: Whether to disable GeoIP for this request.
1695+
flag_keys: List of specific feature flag keys to evaluate.
16811696
16821697
Examples:
16831698
```python
@@ -1694,6 +1709,7 @@ def get_all_flags(
16941709
group_properties=group_properties,
16951710
only_evaluate_locally=only_evaluate_locally,
16961711
disable_geoip=disable_geoip,
1712+
flag_keys=flag_keys,
16971713
)
16981714

16991715
return response["featureFlags"]
@@ -1707,6 +1723,7 @@ def get_all_flags_and_payloads(
17071723
group_properties={},
17081724
only_evaluate_locally=False,
17091725
disable_geoip=None,
1726+
flag_keys=None,
17101727
) -> FlagsAndPayloads:
17111728
"""
17121729
Get all feature flags and their payloads for a user.
@@ -1718,6 +1735,7 @@ def get_all_flags_and_payloads(
17181735
group_properties: A dictionary of group properties.
17191736
only_evaluate_locally: Whether to only evaluate locally.
17201737
disable_geoip: Whether to disable GeoIP for this request.
1738+
flag_keys: List of specific feature flag keys to evaluate.
17211739
17221740
Examples:
17231741
```python
@@ -1741,6 +1759,7 @@ def get_all_flags_and_payloads(
17411759
groups=groups,
17421760
person_properties=person_properties,
17431761
group_properties=group_properties,
1762+
flag_keys=flag_keys,
17441763
)
17451764

17461765
if fallback_to_decide and not only_evaluate_locally:
@@ -1768,6 +1787,7 @@ def _get_all_flags_and_payloads_locally(
17681787
person_properties={},
17691788
group_properties={},
17701789
warn_on_unknown_groups=False,
1790+
flag_keys=None,
17711791
) -> tuple[FlagsAndPayloads, bool]:
17721792
if self.feature_flags is None and self.personal_api_key:
17731793
self.load_feature_flags()
@@ -1777,7 +1797,16 @@ def _get_all_flags_and_payloads_locally(
17771797
fallback_to_decide = False
17781798
# If loading in previous line failed
17791799
if self.feature_flags:
1780-
for flag in self.feature_flags:
1800+
flags_to_evaluate = self.feature_flags
1801+
1802+
# Filter by flag_keys if provided
1803+
if flag_keys is not None:
1804+
flag_keys_set = set(flag_keys)
1805+
flags_to_evaluate = [
1806+
flag for flag in self.feature_flags if flag["key"] in flag_keys_set
1807+
]
1808+
1809+
for flag in flags_to_evaluate:
17811810
try:
17821811
flags[flag["key"]] = self._compute_flag_locally(
17831812
flag,

posthog/test/test_client.py

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -890,6 +890,143 @@ def test_capture_with_send_feature_flags_options_default_behavior(
890890
msg["properties"]["$feature/default-flag"], "default-value"
891891
)
892892

893+
@mock.patch("posthog.client.flags")
894+
def test_capture_with_send_feature_flags_flag_keys_filter(self, patch_flags):
895+
"""Test that flag_keys filters the feature flags that are evaluated"""
896+
with mock.patch("posthog.client.batch_post") as mock_post:
897+
client = Client(
898+
FAKE_TEST_API_KEY,
899+
on_error=self.set_fail,
900+
personal_api_key=FAKE_TEST_API_KEY,
901+
sync_mode=True,
902+
)
903+
904+
# Set up multiple local flags
905+
client.feature_flags = [
906+
{
907+
"id": 1,
908+
"key": "flag-one",
909+
"active": True,
910+
"filters": {
911+
"groups": [
912+
{
913+
"properties": [],
914+
"rollout_percentage": 100,
915+
}
916+
],
917+
},
918+
},
919+
{
920+
"id": 2,
921+
"key": "flag-two",
922+
"active": True,
923+
"filters": {
924+
"groups": [
925+
{
926+
"properties": [],
927+
"rollout_percentage": 100,
928+
}
929+
],
930+
},
931+
},
932+
{
933+
"id": 3,
934+
"key": "flag-three",
935+
"active": True,
936+
"filters": {
937+
"groups": [
938+
{
939+
"properties": [],
940+
"rollout_percentage": 100,
941+
}
942+
],
943+
},
944+
},
945+
]
946+
947+
# Only evaluate flag-one and flag-three
948+
send_options = {
949+
"only_evaluate_locally": True,
950+
"flag_keys": ["flag-one", "flag-three"],
951+
}
952+
953+
msg_uuid = client.capture(
954+
"test event", distinct_id="distinct_id", send_feature_flags=send_options
955+
)
956+
957+
self.assertIsNotNone(msg_uuid)
958+
self.assertFalse(self.failed)
959+
960+
# Check the message only includes flag-one and flag-three
961+
mock_post.assert_called_once()
962+
batch_data = mock_post.call_args[1]["batch"]
963+
msg = batch_data[0]
964+
965+
# Should have flag-one and flag-three, but not flag-two
966+
self.assertEqual(msg["properties"]["$feature/flag-one"], True)
967+
self.assertEqual(msg["properties"]["$feature/flag-three"], True)
968+
self.assertNotIn("$feature/flag-two", msg["properties"])
969+
970+
# Active flags should only include flag-one and flag-three
971+
self.assertEqual(
972+
sorted(msg["properties"]["$active_feature_flags"]),
973+
["flag-one", "flag-three"],
974+
)
975+
976+
@mock.patch("posthog.client.flags")
977+
def test_capture_with_send_feature_flags_flag_keys_remote_evaluation(
978+
self, patch_flags
979+
):
980+
"""Test that flag_keys filters remote evaluation results"""
981+
# Mock remote flags response with multiple flags
982+
patch_flags.return_value = {
983+
"featureFlags": {
984+
"remote-flag-one": "value-one",
985+
"remote-flag-two": "value-two",
986+
"remote-flag-three": "value-three",
987+
}
988+
}
989+
990+
with mock.patch("posthog.client.batch_post") as mock_post:
991+
client = Client(
992+
FAKE_TEST_API_KEY,
993+
on_error=self.set_fail,
994+
sync_mode=True,
995+
)
996+
997+
# Only evaluate remote-flag-one and remote-flag-three
998+
send_options = {
999+
"flag_keys": ["remote-flag-one", "remote-flag-three"],
1000+
}
1001+
1002+
msg_uuid = client.capture(
1003+
"test event", distinct_id="distinct_id", send_feature_flags=send_options
1004+
)
1005+
1006+
self.assertIsNotNone(msg_uuid)
1007+
self.assertFalse(self.failed)
1008+
1009+
# Verify flags() was called
1010+
patch_flags.assert_called_once()
1011+
1012+
# Check the message only includes remote-flag-one and remote-flag-three
1013+
mock_post.assert_called_once()
1014+
batch_data = mock_post.call_args[1]["batch"]
1015+
msg = batch_data[0]
1016+
1017+
# Should have remote-flag-one and remote-flag-three, but not remote-flag-two
1018+
self.assertEqual(msg["properties"]["$feature/remote-flag-one"], "value-one")
1019+
self.assertEqual(
1020+
msg["properties"]["$feature/remote-flag-three"], "value-three"
1021+
)
1022+
self.assertNotIn("$feature/remote-flag-two", msg["properties"])
1023+
1024+
# Active flags should only include remote-flag-one and remote-flag-three
1025+
self.assertEqual(
1026+
sorted(msg["properties"]["$active_feature_flags"]),
1027+
["remote-flag-one", "remote-flag-three"],
1028+
)
1029+
8931030
@mock.patch("posthog.client.flags")
8941031
def test_capture_exception_with_send_feature_flags_options(self, patch_flags):
8951032
"""Test that capture_exception also supports SendFeatureFlagsOptions"""
@@ -2185,6 +2322,7 @@ def test_parse_send_feature_flags_method(self):
21852322
"only_evaluate_locally": None,
21862323
"person_properties": None,
21872324
"group_properties": None,
2325+
"flag_keys": None,
21882326
}
21892327
self.assertEqual(result, expected)
21902328

@@ -2195,6 +2333,7 @@ def test_parse_send_feature_flags_method(self):
21952333
"only_evaluate_locally": None,
21962334
"person_properties": None,
21972335
"group_properties": None,
2336+
"flag_keys": None,
21982337
}
21992338
self.assertEqual(result, expected)
22002339

@@ -2203,13 +2342,15 @@ def test_parse_send_feature_flags_method(self):
22032342
"only_evaluate_locally": True,
22042343
"person_properties": {"plan": "premium"},
22052344
"group_properties": {"company": {"type": "enterprise"}},
2345+
"flag_keys": ["beta-feature", "my-flag"],
22062346
}
22072347
result = client._parse_send_feature_flags(options)
22082348
expected = {
22092349
"should_send": True,
22102350
"only_evaluate_locally": True,
22112351
"person_properties": {"plan": "premium"},
22122352
"group_properties": {"company": {"type": "enterprise"}},
2353+
"flag_keys": ["beta-feature", "my-flag"],
22132354
}
22142355
self.assertEqual(result, expected)
22152356

@@ -2221,6 +2362,7 @@ def test_parse_send_feature_flags_method(self):
22212362
"only_evaluate_locally": None,
22222363
"person_properties": {"user_id": "123"},
22232364
"group_properties": None,
2365+
"flag_keys": None,
22242366
}
22252367
self.assertEqual(result, expected)
22262368

@@ -2231,6 +2373,7 @@ def test_parse_send_feature_flags_method(self):
22312373
"only_evaluate_locally": None,
22322374
"person_properties": None,
22332375
"group_properties": None,
2376+
"flag_keys": None,
22342377
}
22352378
self.assertEqual(result, expected)
22362379

posthog/types.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,14 @@ class SendFeatureFlagsOptions(TypedDict, total=False):
2020
These properties will be merged with any existing person properties.
2121
group_properties: Group properties to use for feature flag evaluation specific to this event.
2222
Format: { group_type_name: { group_properties } }
23+
flag_keys: List of specific feature flag keys to evaluate. Only these flags will be evaluated
24+
and included in the event. If not provided, all flags will be evaluated.
2325
"""
2426

2527
only_evaluate_locally: Optional[bool]
2628
person_properties: Optional[dict[str, Any]]
2729
group_properties: Optional[dict[str, dict[str, Any]]]
30+
flag_keys: Optional[list[str]]
2831

2932

3033
@dataclass(frozen=True)

0 commit comments

Comments
 (0)