posthoganalytics 6.5.0__tar.gz → 6.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. {posthoganalytics-6.5.0/posthoganalytics.egg-info → posthoganalytics-6.6.0}/PKG-INFO +1 -1
  2. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/client.py +72 -14
  3. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/test/test_client.py +56 -0
  4. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/test/test_feature_flags.py +118 -7
  5. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/types.py +3 -0
  6. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/version.py +1 -1
  7. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0/posthoganalytics.egg-info}/PKG-INFO +1 -1
  8. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/LICENSE +0 -0
  9. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/MANIFEST.in +0 -0
  10. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/README.md +0 -0
  11. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/__init__.py +0 -0
  12. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/ai/__init__.py +0 -0
  13. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/ai/anthropic/__init__.py +0 -0
  14. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/ai/anthropic/anthropic.py +0 -0
  15. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/ai/anthropic/anthropic_async.py +0 -0
  16. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/ai/anthropic/anthropic_providers.py +0 -0
  17. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/ai/gemini/__init__.py +0 -0
  18. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/ai/gemini/gemini.py +0 -0
  19. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/ai/langchain/__init__.py +0 -0
  20. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/ai/langchain/callbacks.py +0 -0
  21. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/ai/openai/__init__.py +0 -0
  22. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/ai/openai/openai.py +0 -0
  23. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/ai/openai/openai_async.py +0 -0
  24. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/ai/openai/openai_providers.py +0 -0
  25. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/ai/utils.py +0 -0
  26. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/args.py +0 -0
  27. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/consumer.py +0 -0
  28. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/contexts.py +0 -0
  29. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/exception_capture.py +0 -0
  30. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/exception_utils.py +0 -0
  31. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/feature_flags.py +0 -0
  32. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/integrations/__init__.py +0 -0
  33. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/integrations/django.py +0 -0
  34. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/poller.py +0 -0
  35. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/py.typed +0 -0
  36. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/request.py +0 -0
  37. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/test/__init__.py +0 -0
  38. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/test/test_before_send.py +0 -0
  39. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/test/test_consumer.py +0 -0
  40. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/test/test_contexts.py +0 -0
  41. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/test/test_exception_capture.py +0 -0
  42. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/test/test_feature_flag.py +0 -0
  43. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/test/test_feature_flag_result.py +0 -0
  44. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/test/test_module.py +0 -0
  45. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/test/test_request.py +0 -0
  46. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/test/test_size_limited_dict.py +0 -0
  47. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/test/test_types.py +0 -0
  48. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/test/test_utils.py +0 -0
  49. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics/utils.py +0 -0
  50. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics.egg-info/SOURCES.txt +0 -0
  51. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics.egg-info/dependency_links.txt +0 -0
  52. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics.egg-info/requires.txt +0 -0
  53. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/posthoganalytics.egg-info/top_level.txt +0 -0
  54. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/pyproject.toml +0 -0
  55. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/setup.cfg +0 -0
  56. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/setup.py +0 -0
  57. {posthoganalytics-6.5.0 → posthoganalytics-6.6.0}/setup_analytics.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: posthoganalytics
3
- Version: 6.5.0
3
+ Version: 6.6.0
4
4
  Summary: Integrate PostHog into any python application.
5
5
  Home-page: https://github.com/posthog/posthog-python
6
6
  Author: Posthog
@@ -44,6 +44,7 @@ from posthoganalytics.types import (
44
44
  FlagsAndPayloads,
45
45
  FlagsResponse,
46
46
  FlagValue,
47
+ SendFeatureFlagsOptions,
47
48
  normalize_flags_response,
48
49
  to_flags_and_payloads,
49
50
  to_payloads,
@@ -313,6 +314,7 @@ class Client(object):
313
314
  person_properties=None,
314
315
  group_properties=None,
315
316
  disable_geoip=None,
317
+ flag_keys_to_evaluate: Optional[list[str]] = None,
316
318
  ) -> dict[str, Union[bool, str]]:
317
319
  """
318
320
  Get feature flag variants for a user by calling decide.
@@ -323,12 +325,19 @@ class Client(object):
323
325
  person_properties: A dictionary of person properties.
324
326
  group_properties: A dictionary of group properties.
325
327
  disable_geoip: Whether to disable GeoIP for this request.
328
+ flag_keys_to_evaluate: A list of specific flag keys to evaluate. If provided,
329
+ only these flags will be evaluated, improving performance.
326
330
 
327
331
  Category:
328
332
  Feature Flags
329
333
  """
330
334
  resp_data = self.get_flags_decision(
331
- distinct_id, groups, person_properties, group_properties, disable_geoip
335
+ distinct_id,
336
+ groups,
337
+ person_properties,
338
+ group_properties,
339
+ disable_geoip,
340
+ flag_keys_to_evaluate,
332
341
  )
333
342
  return to_values(resp_data) or {}
334
343
 
@@ -339,6 +348,7 @@ class Client(object):
339
348
  person_properties=None,
340
349
  group_properties=None,
341
350
  disable_geoip=None,
351
+ flag_keys_to_evaluate: Optional[list[str]] = None,
342
352
  ) -> dict[str, str]:
343
353
  """
344
354
  Get feature flag payloads for a user by calling decide.
@@ -349,6 +359,8 @@ class Client(object):
349
359
  person_properties: A dictionary of person properties.
350
360
  group_properties: A dictionary of group properties.
351
361
  disable_geoip: Whether to disable GeoIP for this request.
362
+ flag_keys_to_evaluate: A list of specific flag keys to evaluate. If provided,
363
+ only these flags will be evaluated, improving performance.
352
364
 
353
365
  Examples:
354
366
  ```python
@@ -359,7 +371,12 @@ class Client(object):
359
371
  Feature Flags
360
372
  """
361
373
  resp_data = self.get_flags_decision(
362
- distinct_id, groups, person_properties, group_properties, disable_geoip
374
+ distinct_id,
375
+ groups,
376
+ person_properties,
377
+ group_properties,
378
+ disable_geoip,
379
+ flag_keys_to_evaluate,
363
380
  )
364
381
  return to_payloads(resp_data) or {}
365
382
 
@@ -370,6 +387,7 @@ class Client(object):
370
387
  person_properties=None,
371
388
  group_properties=None,
372
389
  disable_geoip=None,
390
+ flag_keys_to_evaluate: Optional[list[str]] = None,
373
391
  ) -> FlagsAndPayloads:
374
392
  """
375
393
  Get feature flags and payloads for a user by calling decide.
@@ -380,6 +398,8 @@ class Client(object):
380
398
  person_properties: A dictionary of person properties.
381
399
  group_properties: A dictionary of group properties.
382
400
  disable_geoip: Whether to disable GeoIP for this request.
401
+ flag_keys_to_evaluate: A list of specific flag keys to evaluate. If provided,
402
+ only these flags will be evaluated, improving performance.
383
403
 
384
404
  Examples:
385
405
  ```python
@@ -390,7 +410,12 @@ class Client(object):
390
410
  Feature Flags
391
411
  """
392
412
  resp = self.get_flags_decision(
393
- distinct_id, groups, person_properties, group_properties, disable_geoip
413
+ distinct_id,
414
+ groups,
415
+ person_properties,
416
+ group_properties,
417
+ disable_geoip,
418
+ flag_keys_to_evaluate,
394
419
  )
395
420
  return to_flags_and_payloads(resp)
396
421
 
@@ -401,6 +426,7 @@ class Client(object):
401
426
  person_properties=None,
402
427
  group_properties=None,
403
428
  disable_geoip=None,
429
+ flag_keys_to_evaluate: Optional[list[str]] = None,
404
430
  ) -> FlagsResponse:
405
431
  """
406
432
  Get feature flags decision.
@@ -411,6 +437,8 @@ class Client(object):
411
437
  person_properties: A dictionary of person properties.
412
438
  group_properties: A dictionary of group properties.
413
439
  disable_geoip: Whether to disable GeoIP for this request.
440
+ flag_keys_to_evaluate: A list of specific flag keys to evaluate. If provided,
441
+ only these flags will be evaluated, improving performance.
414
442
 
415
443
  Examples:
416
444
  ```python
@@ -441,6 +469,9 @@ class Client(object):
441
469
  "geoip_disable": disable_geoip,
442
470
  }
443
471
 
472
+ if flag_keys_to_evaluate:
473
+ request_data["flag_keys_to_evaluate"] = flag_keys_to_evaluate
474
+
444
475
  resp_data = flags(
445
476
  self.api_key,
446
477
  self.host,
@@ -545,6 +576,7 @@ class Client(object):
545
576
  group_properties=flag_options["group_properties"],
546
577
  disable_geoip=disable_geoip,
547
578
  only_evaluate_locally=True,
579
+ flag_keys_to_evaluate=flag_options["flag_keys_filter"],
548
580
  )
549
581
  else:
550
582
  # Default behavior - use remote evaluation
@@ -554,6 +586,7 @@ class Client(object):
554
586
  person_properties=flag_options["person_properties"],
555
587
  group_properties=flag_options["group_properties"],
556
588
  disable_geoip=disable_geoip,
589
+ flag_keys_to_evaluate=flag_options["flag_keys_filter"],
557
590
  )
558
591
  except Exception as e:
559
592
  self.log.exception(
@@ -586,7 +619,7 @@ class Client(object):
586
619
 
587
620
  return self._enqueue(msg, disable_geoip)
588
621
 
589
- def _parse_send_feature_flags(self, send_feature_flags) -> dict:
622
+ def _parse_send_feature_flags(self, send_feature_flags) -> SendFeatureFlagsOptions:
590
623
  """
591
624
  Parse and normalize send_feature_flags parameter into a standard format.
592
625
 
@@ -594,8 +627,8 @@ class Client(object):
594
627
  send_feature_flags: Either bool or SendFeatureFlagsOptions dict
595
628
 
596
629
  Returns:
597
- dict: Normalized options with keys: should_send, only_evaluate_locally,
598
- person_properties, group_properties
630
+ SendFeatureFlagsOptions: Normalized options with keys: should_send, only_evaluate_locally,
631
+ person_properties, group_properties, flag_keys_filter
599
632
 
600
633
  Raises:
601
634
  TypeError: If send_feature_flags is not bool or dict
@@ -608,6 +641,7 @@ class Client(object):
608
641
  ),
609
642
  "person_properties": send_feature_flags.get("person_properties"),
610
643
  "group_properties": send_feature_flags.get("group_properties"),
644
+ "flag_keys_filter": send_feature_flags.get("flag_keys_filter"),
611
645
  }
612
646
  elif isinstance(send_feature_flags, bool):
613
647
  return {
@@ -615,6 +649,7 @@ class Client(object):
615
649
  "only_evaluate_locally": None,
616
650
  "person_properties": None,
617
651
  "group_properties": None,
652
+ "flag_keys_filter": None,
618
653
  }
619
654
  else:
620
655
  raise TypeError(
@@ -1184,12 +1219,12 @@ class Client(object):
1184
1219
  self.log.warning(
1185
1220
  f"[FEATURE FLAGS] Unknown group type index {aggregation_group_type_index} for feature flag {feature_flag['key']}"
1186
1221
  )
1187
- # failover to `/decide/`
1222
+ # failover to `/flags`
1188
1223
  raise InconclusiveMatchError("Flag has unknown group type index")
1189
1224
 
1190
1225
  if group_name not in groups:
1191
1226
  # Group flags are never enabled in `groups` aren't passed in
1192
- # don't failover to `/decide/`, since response will be the same
1227
+ # don't failover to `/flags`, since response will be the same
1193
1228
  if warn_on_unknown_groups:
1194
1229
  self.log.warning(
1195
1230
  f"[FEATURE FLAGS] Can't compute group feature flag: {feature_flag['key']} without group names passed in"
@@ -1317,7 +1352,7 @@ class Client(object):
1317
1352
  )
1318
1353
  elif not only_evaluate_locally:
1319
1354
  try:
1320
- flag_details, request_id = self._get_feature_flag_details_from_decide(
1355
+ flag_details, request_id = self._get_feature_flag_details_from_server(
1321
1356
  key,
1322
1357
  distinct_id,
1323
1358
  groups,
@@ -1557,7 +1592,7 @@ class Client(object):
1557
1592
  )
1558
1593
  return feature_flag_result.payload if feature_flag_result else None
1559
1594
 
1560
- def _get_feature_flag_details_from_decide(
1595
+ def _get_feature_flag_details_from_server(
1561
1596
  self,
1562
1597
  key: str,
1563
1598
  distinct_id: ID_TYPES,
@@ -1567,10 +1602,15 @@ class Client(object):
1567
1602
  disable_geoip: Optional[bool],
1568
1603
  ) -> tuple[Optional[FeatureFlag], Optional[str]]:
1569
1604
  """
1570
- Calls /decide and returns the flag details and request id
1605
+ Calls /flags and returns the flag details and request id
1571
1606
  """
1572
1607
  resp_data = self.get_flags_decision(
1573
- distinct_id, groups, person_properties, group_properties, disable_geoip
1608
+ distinct_id,
1609
+ groups,
1610
+ person_properties,
1611
+ group_properties,
1612
+ disable_geoip,
1613
+ flag_keys_to_evaluate=[key],
1574
1614
  )
1575
1615
  request_id = resp_data.get("requestId")
1576
1616
  flags = resp_data.get("flags")
@@ -1686,6 +1726,7 @@ class Client(object):
1686
1726
  group_properties=None,
1687
1727
  only_evaluate_locally=False,
1688
1728
  disable_geoip=None,
1729
+ flag_keys_to_evaluate: Optional[list[str]] = None,
1689
1730
  ) -> Optional[dict[str, Union[bool, str]]]:
1690
1731
  """
1691
1732
  Get all feature flags for a user.
@@ -1697,6 +1738,8 @@ class Client(object):
1697
1738
  group_properties: A dictionary of group properties.
1698
1739
  only_evaluate_locally: Whether to only evaluate locally.
1699
1740
  disable_geoip: Whether to disable GeoIP for this request.
1741
+ flag_keys_to_evaluate: A list of specific flag keys to evaluate. If provided,
1742
+ only these flags will be evaluated, improving performance.
1700
1743
 
1701
1744
  Examples:
1702
1745
  ```python
@@ -1713,6 +1756,7 @@ class Client(object):
1713
1756
  group_properties=group_properties,
1714
1757
  only_evaluate_locally=only_evaluate_locally,
1715
1758
  disable_geoip=disable_geoip,
1759
+ flag_keys_to_evaluate=flag_keys_to_evaluate,
1716
1760
  )
1717
1761
 
1718
1762
  return response["featureFlags"]
@@ -1726,6 +1770,7 @@ class Client(object):
1726
1770
  group_properties=None,
1727
1771
  only_evaluate_locally=False,
1728
1772
  disable_geoip=None,
1773
+ flag_keys_to_evaluate: Optional[list[str]] = None,
1729
1774
  ) -> FlagsAndPayloads:
1730
1775
  """
1731
1776
  Get all feature flags and their payloads for a user.
@@ -1737,6 +1782,8 @@ class Client(object):
1737
1782
  group_properties: A dictionary of group properties.
1738
1783
  only_evaluate_locally: Whether to only evaluate locally.
1739
1784
  disable_geoip: Whether to disable GeoIP for this request.
1785
+ flag_keys_to_evaluate: A list of specific flag keys to evaluate. If provided,
1786
+ only these flags will be evaluated, improving performance.
1740
1787
 
1741
1788
  Examples:
1742
1789
  ```python
@@ -1760,6 +1807,7 @@ class Client(object):
1760
1807
  groups=groups,
1761
1808
  person_properties=person_properties,
1762
1809
  group_properties=group_properties,
1810
+ flag_keys_to_evaluate=flag_keys_to_evaluate,
1763
1811
  )
1764
1812
 
1765
1813
  if fallback_to_decide and not only_evaluate_locally:
@@ -1770,6 +1818,7 @@ class Client(object):
1770
1818
  person_properties=person_properties,
1771
1819
  group_properties=group_properties,
1772
1820
  disable_geoip=disable_geoip,
1821
+ flag_keys_to_evaluate=flag_keys_to_evaluate,
1773
1822
  )
1774
1823
  return to_flags_and_payloads(decide_response)
1775
1824
  except Exception as e:
@@ -1787,6 +1836,7 @@ class Client(object):
1787
1836
  person_properties=None,
1788
1837
  group_properties=None,
1789
1838
  warn_on_unknown_groups=False,
1839
+ flag_keys_to_evaluate: Optional[list[str]] = None,
1790
1840
  ) -> tuple[FlagsAndPayloads, bool]:
1791
1841
  person_properties = person_properties or {}
1792
1842
  group_properties = group_properties or {}
@@ -1799,7 +1849,15 @@ class Client(object):
1799
1849
  fallback_to_decide = False
1800
1850
  # If loading in previous line failed
1801
1851
  if self.feature_flags:
1802
- for flag in self.feature_flags:
1852
+ # Filter flags based on flag_keys_to_evaluate if provided
1853
+ flags_to_process = self.feature_flags
1854
+ if flag_keys_to_evaluate:
1855
+ flag_keys_set = set(flag_keys_to_evaluate)
1856
+ flags_to_process = [
1857
+ flag for flag in self.feature_flags if flag["key"] in flag_keys_set
1858
+ ]
1859
+
1860
+ for flag in flags_to_process:
1803
1861
  try:
1804
1862
  flags[flag["key"]] = self._compute_flag_locally(
1805
1863
  flag,
@@ -1815,7 +1873,7 @@ class Client(object):
1815
1873
  if matched_payload is not None:
1816
1874
  payloads[flag["key"]] = matched_payload
1817
1875
  except InconclusiveMatchError:
1818
- # No need to log this, since it's just telling us to fall back to `/decide`
1876
+ # No need to log this, since it's just telling us to fall back to `/flags`
1819
1877
  fallback_to_decide = True
1820
1878
  except Exception as e:
1821
1879
  self.log.exception(
@@ -1742,6 +1742,7 @@ class TestClient(unittest.TestCase):
1742
1742
  person_properties={"distinct_id": "some_id"},
1743
1743
  group_properties={},
1744
1744
  geoip_disable=True,
1745
+ flag_keys_to_evaluate=["random_key"],
1745
1746
  )
1746
1747
  patch_flags.reset_mock()
1747
1748
  client.feature_enabled(
@@ -1756,6 +1757,7 @@ class TestClient(unittest.TestCase):
1756
1757
  person_properties={"distinct_id": "feature_enabled_distinct_id"},
1757
1758
  group_properties={},
1758
1759
  geoip_disable=True,
1760
+ flag_keys_to_evaluate=["random_key"],
1759
1761
  )
1760
1762
  patch_flags.reset_mock()
1761
1763
  client.get_all_flags_and_payloads("all_flags_payloads_id")
@@ -1816,6 +1818,7 @@ class TestClient(unittest.TestCase):
1816
1818
  "instance": {"$group_key": "app.posthog.com"},
1817
1819
  },
1818
1820
  geoip_disable=False,
1821
+ flag_keys_to_evaluate=["random_key"],
1819
1822
  )
1820
1823
 
1821
1824
  patch_flags.reset_mock()
@@ -1842,6 +1845,7 @@ class TestClient(unittest.TestCase):
1842
1845
  "instance": {"$group_key": "app.posthog.com"},
1843
1846
  },
1844
1847
  geoip_disable=False,
1848
+ flag_keys_to_evaluate=["random_key"],
1845
1849
  )
1846
1850
 
1847
1851
  patch_flags.reset_mock()
@@ -2187,6 +2191,7 @@ class TestClient(unittest.TestCase):
2187
2191
  "only_evaluate_locally": None,
2188
2192
  "person_properties": None,
2189
2193
  "group_properties": None,
2194
+ "flag_keys_filter": None,
2190
2195
  }
2191
2196
  self.assertEqual(result, expected)
2192
2197
 
@@ -2197,6 +2202,7 @@ class TestClient(unittest.TestCase):
2197
2202
  "only_evaluate_locally": None,
2198
2203
  "person_properties": None,
2199
2204
  "group_properties": None,
2205
+ "flag_keys_filter": None,
2200
2206
  }
2201
2207
  self.assertEqual(result, expected)
2202
2208
 
@@ -2212,6 +2218,7 @@ class TestClient(unittest.TestCase):
2212
2218
  "only_evaluate_locally": True,
2213
2219
  "person_properties": {"plan": "premium"},
2214
2220
  "group_properties": {"company": {"type": "enterprise"}},
2221
+ "flag_keys_filter": None,
2215
2222
  }
2216
2223
  self.assertEqual(result, expected)
2217
2224
 
@@ -2223,6 +2230,7 @@ class TestClient(unittest.TestCase):
2223
2230
  "only_evaluate_locally": None,
2224
2231
  "person_properties": {"user_id": "123"},
2225
2232
  "group_properties": None,
2233
+ "flag_keys_filter": None,
2226
2234
  }
2227
2235
  self.assertEqual(result, expected)
2228
2236
 
@@ -2233,6 +2241,7 @@ class TestClient(unittest.TestCase):
2233
2241
  "only_evaluate_locally": None,
2234
2242
  "person_properties": None,
2235
2243
  "group_properties": None,
2244
+ "flag_keys_filter": None,
2236
2245
  }
2237
2246
  self.assertEqual(result, expected)
2238
2247
 
@@ -2249,6 +2258,53 @@ class TestClient(unittest.TestCase):
2249
2258
  client._parse_send_feature_flags(None)
2250
2259
  self.assertIn("Invalid type for send_feature_flags", str(cm.exception))
2251
2260
 
2261
+ @mock.patch("posthog.client.flags")
2262
+ def test_capture_with_send_feature_flags_flag_keys_filter(self, patch_flags):
2263
+ """Test that SendFeatureFlagsOptions with flag_keys_filter only evaluates specified flags"""
2264
+ # When flag_keys_to_evaluate is provided, the API should only return the requested flags
2265
+ patch_flags.return_value = {
2266
+ "featureFlags": {
2267
+ "flag1": "value1",
2268
+ "flag3": "value3",
2269
+ }
2270
+ }
2271
+
2272
+ with mock.patch("posthog.client.batch_post") as mock_post:
2273
+ client = Client(
2274
+ FAKE_TEST_API_KEY,
2275
+ on_error=self.set_fail,
2276
+ personal_api_key=FAKE_TEST_API_KEY,
2277
+ sync_mode=True,
2278
+ )
2279
+
2280
+ send_options = {
2281
+ "flag_keys_filter": ["flag1", "flag3"],
2282
+ "person_properties": {"subscription": "pro"},
2283
+ }
2284
+
2285
+ msg_uuid = client.capture(
2286
+ "test event", distinct_id="distinct_id", send_feature_flags=send_options
2287
+ )
2288
+
2289
+ self.assertIsNotNone(msg_uuid)
2290
+ self.assertFalse(self.failed)
2291
+
2292
+ # Verify flags() was called with flag_keys_to_evaluate
2293
+ patch_flags.assert_called_once()
2294
+ call_args = patch_flags.call_args[1]
2295
+ self.assertEqual(call_args["flag_keys_to_evaluate"], ["flag1", "flag3"])
2296
+ self.assertEqual(call_args["person_properties"], {"subscription": "pro"})
2297
+
2298
+ # Check the message includes only the filtered flags
2299
+ mock_post.assert_called_once()
2300
+ batch_data = mock_post.call_args[1]["batch"]
2301
+ msg = batch_data[0]
2302
+
2303
+ self.assertEqual(msg["properties"]["$feature/flag1"], "value1")
2304
+ self.assertEqual(msg["properties"]["$feature/flag3"], "value3")
2305
+ # flag2 should not be included since it wasn't requested
2306
+ self.assertNotIn("$feature/flag2", msg["properties"])
2307
+
2252
2308
  @mock.patch("posthog.client.batch_post")
2253
2309
  def test_get_feature_flag_result_with_empty_string_payload(self, patch_batch_post):
2254
2310
  """Test that get_feature_flag_result returns a FeatureFlagResult when payload is empty string"""
@@ -215,7 +215,7 @@ class TestLocalEvaluation(unittest.TestCase):
215
215
  )
216
216
  self.assertEqual(patch_flags.call_count, 0)
217
217
 
218
- # Now group type mappings are gone, so fall back to /decide/
218
+ # Now group type mappings are gone, so fall back to /flags/
219
219
  patch_flags.return_value = {
220
220
  "featureFlags": {"group-flag": "decide-fallback-value"}
221
221
  }
@@ -311,7 +311,7 @@ class TestLocalEvaluation(unittest.TestCase):
311
311
  )
312
312
  self.assertEqual(patch_flags.call_count, 0)
313
313
 
314
- # will fall back on `/decide`, as all properties present for second group, but that group resolves to false
314
+ # will fall back on `/flags`, as all properties present for second group, but that group resolves to false
315
315
  self.assertEqual(
316
316
  client.get_feature_flag(
317
317
  "complex-flag",
@@ -651,7 +651,7 @@ class TestLocalEvaluation(unittest.TestCase):
651
651
  },
652
652
  },
653
653
  ]
654
- # beta-feature value overridden by /decide
654
+ # beta-feature value overridden by /flags
655
655
  self.assertEqual(
656
656
  client.get_all_flags("distinct_id"),
657
657
  {
@@ -725,7 +725,7 @@ class TestLocalEvaluation(unittest.TestCase):
725
725
  },
726
726
  },
727
727
  ]
728
- # beta-feature value overridden by /decide
728
+ # beta-feature value overridden by /flags
729
729
  self.assertEqual(
730
730
  client.get_all_flags_and_payloads("distinct_id")["featureFlagPayloads"],
731
731
  {
@@ -746,7 +746,7 @@ class TestLocalEvaluation(unittest.TestCase):
746
746
  }
747
747
  client = self.client
748
748
  client.feature_flags = []
749
- # beta-feature value overridden by /decide
749
+ # beta-feature value overridden by /flags
750
750
  self.assertEqual(
751
751
  client.get_all_flags("distinct_id"),
752
752
  {"beta-feature": "variant-1", "beta-feature2": "variant-2"},
@@ -765,7 +765,7 @@ class TestLocalEvaluation(unittest.TestCase):
765
765
  }
766
766
  client = self.client
767
767
  client.feature_flags = []
768
- # beta-feature value overridden by /decide
768
+ # beta-feature value overridden by /flags
769
769
  self.assertEqual(
770
770
  client.get_all_flags_and_payloads("distinct_id")["featureFlagPayloads"],
771
771
  {"beta-feature": 100, "beta-feature2": 300},
@@ -5387,4 +5387,115 @@ class TestConsistency(unittest.TestCase):
5387
5387
  test_cases = ["beta-feature", "BETA-FEATURE", "bEtA-FeAtUrE"]
5388
5388
  for case in test_cases:
5389
5389
  self.assertFalse(client.feature_enabled(case, "user1"))
5390
- self.assertIsNone(client.get_feature_flag_payload(case, "user1"))
5390
+
5391
+ @mock.patch("posthog.client.flags")
5392
+ def test_get_all_flags_with_flag_keys_to_evaluate(self, mock_flags):
5393
+ """Test that get_all_flags with flag_keys_to_evaluate only evaluates specified flags"""
5394
+ mock_flags.return_value = {
5395
+ "featureFlags": {
5396
+ "flag1": "value1",
5397
+ "flag2": True,
5398
+ }
5399
+ }
5400
+
5401
+ client = Client(
5402
+ project_api_key=FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY
5403
+ )
5404
+
5405
+ # Call get_all_flags with flag_keys_to_evaluate
5406
+ result = client.get_all_flags(
5407
+ "user123",
5408
+ flag_keys_to_evaluate=["flag1", "flag2"],
5409
+ person_properties={"region": "USA"},
5410
+ )
5411
+
5412
+ # Verify flags() was called with flag_keys_to_evaluate
5413
+ mock_flags.assert_called_once()
5414
+ call_args = mock_flags.call_args[1]
5415
+ self.assertEqual(call_args["flag_keys_to_evaluate"], ["flag1", "flag2"])
5416
+ self.assertEqual(
5417
+ call_args["person_properties"], {"distinct_id": "user123", "region": "USA"}
5418
+ )
5419
+
5420
+ # Check the result
5421
+ self.assertEqual(result, {"flag1": "value1", "flag2": True})
5422
+
5423
+ @mock.patch("posthog.client.flags")
5424
+ def test_get_all_flags_and_payloads_with_flag_keys_to_evaluate(self, mock_flags):
5425
+ """Test that get_all_flags_and_payloads with flag_keys_to_evaluate only evaluates specified flags"""
5426
+ mock_flags.return_value = {
5427
+ "featureFlags": {
5428
+ "flag1": "variant1",
5429
+ "flag3": True,
5430
+ },
5431
+ "featureFlagPayloads": {
5432
+ "flag1": {"data": "payload1"},
5433
+ "flag3": {"data": "payload3"},
5434
+ },
5435
+ }
5436
+
5437
+ client = Client(
5438
+ project_api_key=FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY
5439
+ )
5440
+
5441
+ # Call get_all_flags_and_payloads with flag_keys_to_evaluate
5442
+ result = client.get_all_flags_and_payloads(
5443
+ "user123",
5444
+ flag_keys_to_evaluate=["flag1", "flag3"],
5445
+ person_properties={"subscription": "pro"},
5446
+ )
5447
+
5448
+ # Verify flags() was called with flag_keys_to_evaluate
5449
+ mock_flags.assert_called_once()
5450
+ call_args = mock_flags.call_args[1]
5451
+ self.assertEqual(call_args["flag_keys_to_evaluate"], ["flag1", "flag3"])
5452
+ self.assertEqual(
5453
+ call_args["person_properties"],
5454
+ {"distinct_id": "user123", "subscription": "pro"},
5455
+ )
5456
+
5457
+ # Check the result
5458
+ self.assertEqual(result["featureFlags"], {"flag1": "variant1", "flag3": True})
5459
+ self.assertEqual(
5460
+ result["featureFlagPayloads"],
5461
+ {"flag1": {"data": "payload1"}, "flag3": {"data": "payload3"}},
5462
+ )
5463
+
5464
+ def test_get_all_flags_locally_with_flag_keys_to_evaluate(self):
5465
+ """Test that local evaluation with flag_keys_to_evaluate only evaluates specified flags"""
5466
+ client = Client(
5467
+ project_api_key=FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY
5468
+ )
5469
+
5470
+ # Set up multiple flags
5471
+ client.feature_flags = [
5472
+ {
5473
+ "id": 1,
5474
+ "key": "flag1",
5475
+ "active": True,
5476
+ "filters": {"groups": [{"properties": [], "rollout_percentage": 100}]},
5477
+ },
5478
+ {
5479
+ "id": 2,
5480
+ "key": "flag2",
5481
+ "active": True,
5482
+ "filters": {"groups": [{"properties": [], "rollout_percentage": 100}]},
5483
+ },
5484
+ {
5485
+ "id": 3,
5486
+ "key": "flag3",
5487
+ "active": True,
5488
+ "filters": {"groups": [{"properties": [], "rollout_percentage": 100}]},
5489
+ },
5490
+ ]
5491
+
5492
+ # Call get_all_flags with flag_keys_to_evaluate
5493
+ result = client.get_all_flags(
5494
+ "user123",
5495
+ flag_keys_to_evaluate=["flag1", "flag3"],
5496
+ only_evaluate_locally=True,
5497
+ )
5498
+
5499
+ # Should only return flag1 and flag3
5500
+ self.assertEqual(result, {"flag1": True, "flag3": True})
5501
+ self.assertNotIn("flag2", result)
@@ -9,6 +9,7 @@ FlagValue = Union[bool, str]
9
9
  BeforeSendCallback = Callable[[dict[str, Any]], Optional[dict[str, Any]]]
10
10
 
11
11
 
12
+ # Type alias for the send_feature_flags parameter
12
13
  class SendFeatureFlagsOptions(TypedDict, total=False):
13
14
  """Options for sending feature flags with capture events.
14
15
 
@@ -22,9 +23,11 @@ class SendFeatureFlagsOptions(TypedDict, total=False):
22
23
  Format: { group_type_name: { group_properties } }
23
24
  """
24
25
 
26
+ should_send: bool
25
27
  only_evaluate_locally: Optional[bool]
26
28
  person_properties: Optional[dict[str, Any]]
27
29
  group_properties: Optional[dict[str, dict[str, Any]]]
30
+ flag_keys_filter: Optional[list[str]]
28
31
 
29
32
 
30
33
  @dataclass(frozen=True)
@@ -1,4 +1,4 @@
1
- VERSION = "6.5.0"
1
+ VERSION = "6.6.0"
2
2
 
3
3
  if __name__ == "__main__":
4
4
  print(VERSION, end="") # noqa: T201
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: posthoganalytics
3
- Version: 6.5.0
3
+ Version: 6.6.0
4
4
  Summary: Integrate PostHog into any python application.
5
5
  Home-page: https://github.com/posthog/posthog-python
6
6
  Author: Posthog