Skip to content

Commit 6def2c3

Browse files
committed
rename endpoint
1 parent 818edc2 commit 6def2c3

File tree

4 files changed

+233
-23
lines changed

4 files changed

+233
-23
lines changed

example.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@
112112

113113
# Local Evaluation
114114

115-
# If flag has City=Sydney, this call doesn't go to `/decide`
115+
# If flag has City=Sydney, this call doesn't go to `/flags`
116116
print(
117117
posthog.feature_enabled(
118118
"test-flag",

posthog/client.py

Lines changed: 69 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@ def get_feature_variants(
313313
person_properties=None,
314314
group_properties=None,
315315
disable_geoip=None,
316+
flag_keys_to_evaluate: Optional[list[str]] = None,
316317
) -> dict[str, Union[bool, str]]:
317318
"""
318319
Get feature flag variants for a user by calling decide.
@@ -323,12 +324,19 @@ def get_feature_variants(
323324
person_properties: A dictionary of person properties.
324325
group_properties: A dictionary of group properties.
325326
disable_geoip: Whether to disable GeoIP for this request.
327+
flag_keys_to_evaluate: A list of specific flag keys to evaluate. If provided,
328+
only these flags will be evaluated, improving performance.
326329
327330
Category:
328331
Feature Flags
329332
"""
330333
resp_data = self.get_flags_decision(
331-
distinct_id, groups, person_properties, group_properties, disable_geoip
334+
distinct_id,
335+
groups,
336+
person_properties,
337+
group_properties,
338+
disable_geoip,
339+
flag_keys_to_evaluate,
332340
)
333341
return to_values(resp_data) or {}
334342

@@ -339,6 +347,7 @@ def get_feature_payloads(
339347
person_properties=None,
340348
group_properties=None,
341349
disable_geoip=None,
350+
flag_keys_to_evaluate: Optional[list[str]] = None,
342351
) -> dict[str, str]:
343352
"""
344353
Get feature flag payloads for a user by calling decide.
@@ -349,6 +358,8 @@ def get_feature_payloads(
349358
person_properties: A dictionary of person properties.
350359
group_properties: A dictionary of group properties.
351360
disable_geoip: Whether to disable GeoIP for this request.
361+
flag_keys_to_evaluate: A list of specific flag keys to evaluate. If provided,
362+
only these flags will be evaluated, improving performance.
352363
353364
Examples:
354365
```python
@@ -359,7 +370,12 @@ def get_feature_payloads(
359370
Feature Flags
360371
"""
361372
resp_data = self.get_flags_decision(
362-
distinct_id, groups, person_properties, group_properties, disable_geoip
373+
distinct_id,
374+
groups,
375+
person_properties,
376+
group_properties,
377+
disable_geoip,
378+
flag_keys_to_evaluate,
363379
)
364380
return to_payloads(resp_data) or {}
365381

@@ -370,6 +386,7 @@ def get_feature_flags_and_payloads(
370386
person_properties=None,
371387
group_properties=None,
372388
disable_geoip=None,
389+
flag_keys_to_evaluate: Optional[list[str]] = None,
373390
) -> FlagsAndPayloads:
374391
"""
375392
Get feature flags and payloads for a user by calling decide.
@@ -380,6 +397,8 @@ def get_feature_flags_and_payloads(
380397
person_properties: A dictionary of person properties.
381398
group_properties: A dictionary of group properties.
382399
disable_geoip: Whether to disable GeoIP for this request.
400+
flag_keys_to_evaluate: A list of specific flag keys to evaluate. If provided,
401+
only these flags will be evaluated, improving performance.
383402
384403
Examples:
385404
```python
@@ -390,7 +409,12 @@ def get_feature_flags_and_payloads(
390409
Feature Flags
391410
"""
392411
resp = self.get_flags_decision(
393-
distinct_id, groups, person_properties, group_properties, disable_geoip
412+
distinct_id,
413+
groups,
414+
person_properties,
415+
group_properties,
416+
disable_geoip,
417+
flag_keys_to_evaluate,
394418
)
395419
return to_flags_and_payloads(resp)
396420

@@ -401,6 +425,7 @@ def get_flags_decision(
401425
person_properties=None,
402426
group_properties=None,
403427
disable_geoip=None,
428+
flag_keys_to_evaluate: Optional[list[str]] = None,
404429
) -> FlagsResponse:
405430
"""
406431
Get feature flags decision.
@@ -411,6 +436,8 @@ def get_flags_decision(
411436
person_properties: A dictionary of person properties.
412437
group_properties: A dictionary of group properties.
413438
disable_geoip: Whether to disable GeoIP for this request.
439+
flag_keys_to_evaluate: A list of specific flag keys to evaluate. If provided,
440+
only these flags will be evaluated, improving performance.
414441
415442
Examples:
416443
```python
@@ -441,6 +468,9 @@ def get_flags_decision(
441468
"geoip_disable": disable_geoip,
442469
}
443470

471+
if flag_keys_to_evaluate:
472+
request_data["flag_keys_to_evaluate"] = flag_keys_to_evaluate
473+
444474
resp_data = flags(
445475
self.api_key,
446476
self.host,
@@ -545,6 +575,7 @@ def capture(
545575
group_properties=flag_options["group_properties"],
546576
disable_geoip=disable_geoip,
547577
only_evaluate_locally=True,
578+
flag_keys_to_evaluate=flag_options["flag_keys_filter"],
548579
)
549580
else:
550581
# Default behavior - use remote evaluation
@@ -554,6 +585,7 @@ def capture(
554585
person_properties=flag_options["person_properties"],
555586
group_properties=flag_options["group_properties"],
556587
disable_geoip=disable_geoip,
588+
flag_keys_to_evaluate=flag_options["flag_keys_filter"],
557589
)
558590
except Exception as e:
559591
self.log.exception(
@@ -595,7 +627,7 @@ def _parse_send_feature_flags(self, send_feature_flags) -> dict:
595627
596628
Returns:
597629
dict: Normalized options with keys: should_send, only_evaluate_locally,
598-
person_properties, group_properties
630+
person_properties, group_properties, flag_keys_filter
599631
600632
Raises:
601633
TypeError: If send_feature_flags is not bool or dict
@@ -608,13 +640,15 @@ def _parse_send_feature_flags(self, send_feature_flags) -> dict:
608640
),
609641
"person_properties": send_feature_flags.get("person_properties"),
610642
"group_properties": send_feature_flags.get("group_properties"),
643+
"flag_keys_filter": send_feature_flags.get("flag_keys_filter"),
611644
}
612645
elif isinstance(send_feature_flags, bool):
613646
return {
614647
"should_send": send_feature_flags,
615648
"only_evaluate_locally": None,
616649
"person_properties": None,
617650
"group_properties": None,
651+
"flag_keys_filter": None,
618652
}
619653
else:
620654
raise TypeError(
@@ -1184,12 +1218,12 @@ def _compute_flag_locally(
11841218
self.log.warning(
11851219
f"[FEATURE FLAGS] Unknown group type index {aggregation_group_type_index} for feature flag {feature_flag['key']}"
11861220
)
1187-
# failover to `/decide/`
1221+
# failover to `/flags`
11881222
raise InconclusiveMatchError("Flag has unknown group type index")
11891223

11901224
if group_name not in groups:
11911225
# Group flags are never enabled in `groups` aren't passed in
1192-
# don't failover to `/decide/`, since response will be the same
1226+
# don't failover to `/flags`, since response will be the same
11931227
if warn_on_unknown_groups:
11941228
self.log.warning(
11951229
f"[FEATURE FLAGS] Can't compute group feature flag: {feature_flag['key']} without group names passed in"
@@ -1317,7 +1351,7 @@ def _get_feature_flag_result(
13171351
)
13181352
elif not only_evaluate_locally:
13191353
try:
1320-
flag_details, request_id = self._get_feature_flag_details_from_decide(
1354+
flag_details, request_id = self._get_feature_flag_details_from_server(
13211355
key,
13221356
distinct_id,
13231357
groups,
@@ -1557,7 +1591,7 @@ def get_feature_flag_payload(
15571591
)
15581592
return feature_flag_result.payload if feature_flag_result else None
15591593

1560-
def _get_feature_flag_details_from_decide(
1594+
def _get_feature_flag_details_from_server(
15611595
self,
15621596
key: str,
15631597
distinct_id: ID_TYPES,
@@ -1567,10 +1601,15 @@ def _get_feature_flag_details_from_decide(
15671601
disable_geoip: Optional[bool],
15681602
) -> tuple[Optional[FeatureFlag], Optional[str]]:
15691603
"""
1570-
Calls /decide and returns the flag details and request id
1604+
Calls /flags and returns the flag details and request id
15711605
"""
15721606
resp_data = self.get_flags_decision(
1573-
distinct_id, groups, person_properties, group_properties, disable_geoip
1607+
distinct_id,
1608+
groups,
1609+
person_properties,
1610+
group_properties,
1611+
disable_geoip,
1612+
flag_keys_to_evaluate=[key],
15741613
)
15751614
request_id = resp_data.get("requestId")
15761615
flags = resp_data.get("flags")
@@ -1686,6 +1725,7 @@ def get_all_flags(
16861725
group_properties=None,
16871726
only_evaluate_locally=False,
16881727
disable_geoip=None,
1728+
flag_keys_to_evaluate: Optional[list[str]] = None,
16891729
) -> Optional[dict[str, Union[bool, str]]]:
16901730
"""
16911731
Get all feature flags for a user.
@@ -1697,6 +1737,8 @@ def get_all_flags(
16971737
group_properties: A dictionary of group properties.
16981738
only_evaluate_locally: Whether to only evaluate locally.
16991739
disable_geoip: Whether to disable GeoIP for this request.
1740+
flag_keys_to_evaluate: A list of specific flag keys to evaluate. If provided,
1741+
only these flags will be evaluated, improving performance.
17001742
17011743
Examples:
17021744
```python
@@ -1713,6 +1755,7 @@ def get_all_flags(
17131755
group_properties=group_properties,
17141756
only_evaluate_locally=only_evaluate_locally,
17151757
disable_geoip=disable_geoip,
1758+
flag_keys_to_evaluate=flag_keys_to_evaluate,
17161759
)
17171760

17181761
return response["featureFlags"]
@@ -1726,6 +1769,7 @@ def get_all_flags_and_payloads(
17261769
group_properties=None,
17271770
only_evaluate_locally=False,
17281771
disable_geoip=None,
1772+
flag_keys_to_evaluate: Optional[list[str]] = None,
17291773
) -> FlagsAndPayloads:
17301774
"""
17311775
Get all feature flags and their payloads for a user.
@@ -1737,6 +1781,8 @@ def get_all_flags_and_payloads(
17371781
group_properties: A dictionary of group properties.
17381782
only_evaluate_locally: Whether to only evaluate locally.
17391783
disable_geoip: Whether to disable GeoIP for this request.
1784+
flag_keys_to_evaluate: A list of specific flag keys to evaluate. If provided,
1785+
only these flags will be evaluated, improving performance.
17401786
17411787
Examples:
17421788
```python
@@ -1760,6 +1806,7 @@ def get_all_flags_and_payloads(
17601806
groups=groups,
17611807
person_properties=person_properties,
17621808
group_properties=group_properties,
1809+
flag_keys_to_evaluate=flag_keys_to_evaluate,
17631810
)
17641811

17651812
if fallback_to_decide and not only_evaluate_locally:
@@ -1770,6 +1817,7 @@ def get_all_flags_and_payloads(
17701817
person_properties=person_properties,
17711818
group_properties=group_properties,
17721819
disable_geoip=disable_geoip,
1820+
flag_keys_to_evaluate=flag_keys_to_evaluate,
17731821
)
17741822
return to_flags_and_payloads(decide_response)
17751823
except Exception as e:
@@ -1787,6 +1835,7 @@ def _get_all_flags_and_payloads_locally(
17871835
person_properties=None,
17881836
group_properties=None,
17891837
warn_on_unknown_groups=False,
1838+
flag_keys_to_evaluate: Optional[list[str]] = None,
17901839
) -> tuple[FlagsAndPayloads, bool]:
17911840
person_properties = person_properties or {}
17921841
group_properties = group_properties or {}
@@ -1799,7 +1848,15 @@ def _get_all_flags_and_payloads_locally(
17991848
fallback_to_decide = False
18001849
# If loading in previous line failed
18011850
if self.feature_flags:
1802-
for flag in self.feature_flags:
1851+
# Filter flags based on flag_keys_to_evaluate if provided
1852+
flags_to_process = self.feature_flags
1853+
if flag_keys_to_evaluate:
1854+
flag_keys_set = set(flag_keys_to_evaluate)
1855+
flags_to_process = [
1856+
flag for flag in self.feature_flags if flag["key"] in flag_keys_set
1857+
]
1858+
1859+
for flag in flags_to_process:
18031860
try:
18041861
flags[flag["key"]] = self._compute_flag_locally(
18051862
flag,
@@ -1815,7 +1872,7 @@ def _get_all_flags_and_payloads_locally(
18151872
if matched_payload is not None:
18161873
payloads[flag["key"]] = matched_payload
18171874
except InconclusiveMatchError:
1818-
# No need to log this, since it's just telling us to fall back to `/decide`
1875+
# No need to log this, since it's just telling us to fall back to `/flags`
18191876
fallback_to_decide = True
18201877
except Exception as e:
18211878
self.log.exception(

posthog/test/test_client.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2249,6 +2249,53 @@ def test_parse_send_feature_flags_method(self):
22492249
client._parse_send_feature_flags(None)
22502250
self.assertIn("Invalid type for send_feature_flags", str(cm.exception))
22512251

2252+
@mock.patch("posthog.client.flags")
2253+
def test_capture_with_send_feature_flags_flag_keys_filter(self, patch_flags):
2254+
"""Test that SendFeatureFlagsOptions with flag_keys_filter only evaluates specified flags"""
2255+
# When flag_keys_to_evaluate is provided, the API should only return the requested flags
2256+
patch_flags.return_value = {
2257+
"featureFlags": {
2258+
"flag1": "value1",
2259+
"flag3": "value3",
2260+
}
2261+
}
2262+
2263+
with mock.patch("posthog.client.batch_post") as mock_post:
2264+
client = Client(
2265+
FAKE_TEST_API_KEY,
2266+
on_error=self.set_fail,
2267+
personal_api_key=FAKE_TEST_API_KEY,
2268+
sync_mode=True,
2269+
)
2270+
2271+
send_options = {
2272+
"flag_keys_filter": ["flag1", "flag3"],
2273+
"person_properties": {"subscription": "pro"},
2274+
}
2275+
2276+
msg_uuid = client.capture(
2277+
"test event", distinct_id="distinct_id", send_feature_flags=send_options
2278+
)
2279+
2280+
self.assertIsNotNone(msg_uuid)
2281+
self.assertFalse(self.failed)
2282+
2283+
# Verify flags() was called with flag_keys_to_evaluate
2284+
patch_flags.assert_called_once()
2285+
call_args = patch_flags.call_args[1]
2286+
self.assertEqual(call_args["flag_keys_to_evaluate"], ["flag1", "flag3"])
2287+
self.assertEqual(call_args["person_properties"], {"subscription": "pro"})
2288+
2289+
# Check the message includes only the filtered flags
2290+
mock_post.assert_called_once()
2291+
batch_data = mock_post.call_args[1]["batch"]
2292+
msg = batch_data[0]
2293+
2294+
self.assertEqual(msg["properties"]["$feature/flag1"], "value1")
2295+
self.assertEqual(msg["properties"]["$feature/flag3"], "value3")
2296+
# flag2 should not be included since it wasn't requested
2297+
self.assertNotIn("$feature/flag2", msg["properties"])
2298+
22522299
@mock.patch("posthog.client.batch_post")
22532300
def test_get_feature_flag_result_with_empty_string_payload(self, patch_batch_post):
22542301
"""Test that get_feature_flag_result returns a FeatureFlagResult when payload is empty string"""

0 commit comments

Comments
 (0)