posthoganalytics 6.7.0__py3-none-any.whl → 7.4.3__py3-none-any.whl
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.
- posthoganalytics/__init__.py +84 -7
- posthoganalytics/ai/anthropic/__init__.py +10 -0
- posthoganalytics/ai/anthropic/anthropic.py +95 -65
- posthoganalytics/ai/anthropic/anthropic_async.py +95 -65
- posthoganalytics/ai/anthropic/anthropic_converter.py +443 -0
- posthoganalytics/ai/gemini/__init__.py +15 -1
- posthoganalytics/ai/gemini/gemini.py +66 -71
- posthoganalytics/ai/gemini/gemini_async.py +423 -0
- posthoganalytics/ai/gemini/gemini_converter.py +652 -0
- posthoganalytics/ai/langchain/callbacks.py +58 -13
- posthoganalytics/ai/openai/__init__.py +16 -1
- posthoganalytics/ai/openai/openai.py +140 -149
- posthoganalytics/ai/openai/openai_async.py +127 -82
- posthoganalytics/ai/openai/openai_converter.py +741 -0
- posthoganalytics/ai/sanitization.py +248 -0
- posthoganalytics/ai/types.py +125 -0
- posthoganalytics/ai/utils.py +339 -356
- posthoganalytics/client.py +345 -97
- posthoganalytics/contexts.py +81 -0
- posthoganalytics/exception_utils.py +250 -2
- posthoganalytics/feature_flags.py +26 -10
- posthoganalytics/flag_definition_cache.py +127 -0
- posthoganalytics/integrations/django.py +157 -19
- posthoganalytics/request.py +203 -23
- posthoganalytics/test/test_client.py +250 -22
- posthoganalytics/test/test_exception_capture.py +418 -0
- posthoganalytics/test/test_feature_flag_result.py +441 -2
- posthoganalytics/test/test_feature_flags.py +308 -104
- posthoganalytics/test/test_flag_definition_cache.py +612 -0
- posthoganalytics/test/test_module.py +0 -8
- posthoganalytics/test/test_request.py +536 -0
- posthoganalytics/test/test_utils.py +4 -1
- posthoganalytics/types.py +40 -0
- posthoganalytics/version.py +1 -1
- {posthoganalytics-6.7.0.dist-info → posthoganalytics-7.4.3.dist-info}/METADATA +12 -12
- posthoganalytics-7.4.3.dist-info/RECORD +57 -0
- posthoganalytics-6.7.0.dist-info/RECORD +0 -49
- {posthoganalytics-6.7.0.dist-info → posthoganalytics-7.4.3.dist-info}/WHEEL +0 -0
- {posthoganalytics-6.7.0.dist-info → posthoganalytics-7.4.3.dist-info}/licenses/LICENSE +0 -0
- {posthoganalytics-6.7.0.dist-info → posthoganalytics-7.4.3.dist-info}/top_level.txt +0 -0
|
@@ -9,7 +9,7 @@ from parameterized import parameterized
|
|
|
9
9
|
|
|
10
10
|
from posthoganalytics.client import Client
|
|
11
11
|
from posthoganalytics.contexts import get_context_session_id, new_context, set_context_session
|
|
12
|
-
from posthoganalytics.request import APIError
|
|
12
|
+
from posthoganalytics.request import APIError, GetResponse
|
|
13
13
|
from posthoganalytics.test.test_utils import FAKE_TEST_API_KEY
|
|
14
14
|
from posthoganalytics.types import FeatureFlag, LegacyFlagMetadata
|
|
15
15
|
from posthoganalytics.version import VERSION
|
|
@@ -198,12 +198,6 @@ class TestClient(unittest.TestCase):
|
|
|
198
198
|
print(capture_call)
|
|
199
199
|
self.assertEqual(capture_call[1]["distinct_id"], "distinct_id")
|
|
200
200
|
self.assertEqual(capture_call[0][0], "$exception")
|
|
201
|
-
self.assertEqual(
|
|
202
|
-
capture_call[1]["properties"]["$exception_type"], "Exception"
|
|
203
|
-
)
|
|
204
|
-
self.assertEqual(
|
|
205
|
-
capture_call[1]["properties"]["$exception_message"], "test exception"
|
|
206
|
-
)
|
|
207
201
|
self.assertEqual(
|
|
208
202
|
capture_call[1]["properties"]["$exception_list"][0]["mechanism"][
|
|
209
203
|
"type"
|
|
@@ -415,7 +409,9 @@ class TestClient(unittest.TestCase):
|
|
|
415
409
|
)
|
|
416
410
|
client.feature_flags = [multivariate_flag, basic_flag, false_flag]
|
|
417
411
|
|
|
418
|
-
msg_uuid = client.capture(
|
|
412
|
+
msg_uuid = client.capture(
|
|
413
|
+
"python test event", distinct_id="distinct_id", send_feature_flags=True
|
|
414
|
+
)
|
|
419
415
|
self.assertIsNotNone(msg_uuid)
|
|
420
416
|
self.assertFalse(self.failed)
|
|
421
417
|
|
|
@@ -571,6 +567,7 @@ class TestClient(unittest.TestCase):
|
|
|
571
567
|
"python test event",
|
|
572
568
|
distinct_id="distinct_id",
|
|
573
569
|
properties={"$feature/beta-feature-local": "my-custom-variant"},
|
|
570
|
+
send_feature_flags=True,
|
|
574
571
|
)
|
|
575
572
|
self.assertIsNotNone(msg_uuid)
|
|
576
573
|
self.assertFalse(self.failed)
|
|
@@ -752,6 +749,178 @@ class TestClient(unittest.TestCase):
|
|
|
752
749
|
|
|
753
750
|
self.assertEqual(patch_flags.call_count, 0)
|
|
754
751
|
|
|
752
|
+
@mock.patch("posthog.client.flags")
|
|
753
|
+
def test_capture_with_send_feature_flags_false_and_local_evaluation_doesnt_send_flags(
|
|
754
|
+
self, patch_flags
|
|
755
|
+
):
|
|
756
|
+
"""Test that send_feature_flags=False with local evaluation enabled does NOT send flags"""
|
|
757
|
+
patch_flags.return_value = {"featureFlags": {"beta-feature": "remote-variant"}}
|
|
758
|
+
|
|
759
|
+
multivariate_flag = {
|
|
760
|
+
"id": 1,
|
|
761
|
+
"name": "Beta Feature",
|
|
762
|
+
"key": "beta-feature-local",
|
|
763
|
+
"active": True,
|
|
764
|
+
"rollout_percentage": 100,
|
|
765
|
+
"filters": {
|
|
766
|
+
"groups": [
|
|
767
|
+
{
|
|
768
|
+
"rollout_percentage": 100,
|
|
769
|
+
},
|
|
770
|
+
],
|
|
771
|
+
"multivariate": {
|
|
772
|
+
"variants": [
|
|
773
|
+
{
|
|
774
|
+
"key": "first-variant",
|
|
775
|
+
"name": "First Variant",
|
|
776
|
+
"rollout_percentage": 50,
|
|
777
|
+
},
|
|
778
|
+
{
|
|
779
|
+
"key": "second-variant",
|
|
780
|
+
"name": "Second Variant",
|
|
781
|
+
"rollout_percentage": 50,
|
|
782
|
+
},
|
|
783
|
+
]
|
|
784
|
+
},
|
|
785
|
+
},
|
|
786
|
+
}
|
|
787
|
+
simple_flag = {
|
|
788
|
+
"id": 2,
|
|
789
|
+
"name": "Simple Flag",
|
|
790
|
+
"key": "simple-flag",
|
|
791
|
+
"active": True,
|
|
792
|
+
"filters": {
|
|
793
|
+
"groups": [
|
|
794
|
+
{
|
|
795
|
+
"rollout_percentage": 100,
|
|
796
|
+
}
|
|
797
|
+
],
|
|
798
|
+
},
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
with mock.patch("posthog.client.batch_post") as mock_post:
|
|
802
|
+
client = Client(
|
|
803
|
+
FAKE_TEST_API_KEY,
|
|
804
|
+
on_error=self.set_fail,
|
|
805
|
+
personal_api_key=FAKE_TEST_API_KEY,
|
|
806
|
+
sync_mode=True,
|
|
807
|
+
)
|
|
808
|
+
client.feature_flags = [multivariate_flag, simple_flag]
|
|
809
|
+
|
|
810
|
+
msg_uuid = client.capture(
|
|
811
|
+
"python test event",
|
|
812
|
+
distinct_id="distinct_id",
|
|
813
|
+
send_feature_flags=False,
|
|
814
|
+
)
|
|
815
|
+
self.assertIsNotNone(msg_uuid)
|
|
816
|
+
self.assertFalse(self.failed)
|
|
817
|
+
|
|
818
|
+
# Get the enqueued message from the mock
|
|
819
|
+
mock_post.assert_called_once()
|
|
820
|
+
batch_data = mock_post.call_args[1]["batch"]
|
|
821
|
+
msg = batch_data[0]
|
|
822
|
+
|
|
823
|
+
self.assertEqual(msg["event"], "python test event")
|
|
824
|
+
self.assertEqual(msg["distinct_id"], "distinct_id")
|
|
825
|
+
|
|
826
|
+
# CRITICAL: Verify local flags are NOT included in the event
|
|
827
|
+
self.assertNotIn("$feature/beta-feature-local", msg["properties"])
|
|
828
|
+
self.assertNotIn("$feature/simple-flag", msg["properties"])
|
|
829
|
+
self.assertNotIn("$active_feature_flags", msg["properties"])
|
|
830
|
+
|
|
831
|
+
# CRITICAL: Verify the /flags API was NOT called
|
|
832
|
+
self.assertEqual(patch_flags.call_count, 0)
|
|
833
|
+
|
|
834
|
+
@mock.patch("posthog.client.flags")
|
|
835
|
+
def test_capture_with_send_feature_flags_true_and_local_evaluation_uses_local_flags(
|
|
836
|
+
self, patch_flags
|
|
837
|
+
):
|
|
838
|
+
"""Test that send_feature_flags=True with local evaluation enabled uses local flags without API call"""
|
|
839
|
+
patch_flags.return_value = {"featureFlags": {"remote-flag": "remote-variant"}}
|
|
840
|
+
|
|
841
|
+
multivariate_flag = {
|
|
842
|
+
"id": 1,
|
|
843
|
+
"name": "Beta Feature",
|
|
844
|
+
"key": "beta-feature-local",
|
|
845
|
+
"active": True,
|
|
846
|
+
"rollout_percentage": 100,
|
|
847
|
+
"filters": {
|
|
848
|
+
"groups": [
|
|
849
|
+
{
|
|
850
|
+
"rollout_percentage": 100,
|
|
851
|
+
},
|
|
852
|
+
],
|
|
853
|
+
"multivariate": {
|
|
854
|
+
"variants": [
|
|
855
|
+
{
|
|
856
|
+
"key": "first-variant",
|
|
857
|
+
"name": "First Variant",
|
|
858
|
+
"rollout_percentage": 50,
|
|
859
|
+
},
|
|
860
|
+
{
|
|
861
|
+
"key": "second-variant",
|
|
862
|
+
"name": "Second Variant",
|
|
863
|
+
"rollout_percentage": 50,
|
|
864
|
+
},
|
|
865
|
+
]
|
|
866
|
+
},
|
|
867
|
+
},
|
|
868
|
+
}
|
|
869
|
+
simple_flag = {
|
|
870
|
+
"id": 2,
|
|
871
|
+
"name": "Simple Flag",
|
|
872
|
+
"key": "simple-flag",
|
|
873
|
+
"active": True,
|
|
874
|
+
"filters": {
|
|
875
|
+
"groups": [
|
|
876
|
+
{
|
|
877
|
+
"rollout_percentage": 100,
|
|
878
|
+
}
|
|
879
|
+
],
|
|
880
|
+
},
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
with mock.patch("posthog.client.batch_post") as mock_post:
|
|
884
|
+
client = Client(
|
|
885
|
+
FAKE_TEST_API_KEY,
|
|
886
|
+
on_error=self.set_fail,
|
|
887
|
+
personal_api_key=FAKE_TEST_API_KEY,
|
|
888
|
+
sync_mode=True,
|
|
889
|
+
)
|
|
890
|
+
client.feature_flags = [multivariate_flag, simple_flag]
|
|
891
|
+
|
|
892
|
+
msg_uuid = client.capture(
|
|
893
|
+
"python test event",
|
|
894
|
+
distinct_id="distinct_id",
|
|
895
|
+
send_feature_flags=True,
|
|
896
|
+
)
|
|
897
|
+
self.assertIsNotNone(msg_uuid)
|
|
898
|
+
self.assertFalse(self.failed)
|
|
899
|
+
|
|
900
|
+
# Get the enqueued message from the mock
|
|
901
|
+
mock_post.assert_called_once()
|
|
902
|
+
batch_data = mock_post.call_args[1]["batch"]
|
|
903
|
+
msg = batch_data[0]
|
|
904
|
+
|
|
905
|
+
self.assertEqual(msg["event"], "python test event")
|
|
906
|
+
self.assertEqual(msg["distinct_id"], "distinct_id")
|
|
907
|
+
|
|
908
|
+
# Verify local flags are included in the event
|
|
909
|
+
self.assertIn("$feature/beta-feature-local", msg["properties"])
|
|
910
|
+
self.assertIn("$feature/simple-flag", msg["properties"])
|
|
911
|
+
self.assertEqual(msg["properties"]["$feature/simple-flag"], True)
|
|
912
|
+
|
|
913
|
+
# Verify active feature flags are set correctly
|
|
914
|
+
active_flags = msg["properties"]["$active_feature_flags"]
|
|
915
|
+
self.assertIn("beta-feature-local", active_flags)
|
|
916
|
+
self.assertIn("simple-flag", active_flags)
|
|
917
|
+
|
|
918
|
+
# The remote flag should NOT be included since we used local evaluation
|
|
919
|
+
self.assertNotIn("$feature/remote-flag", msg["properties"])
|
|
920
|
+
|
|
921
|
+
# CRITICAL: Verify the /flags API was NOT called
|
|
922
|
+
self.assertEqual(patch_flags.call_count, 0)
|
|
923
|
+
|
|
755
924
|
@mock.patch("posthog.client.flags")
|
|
756
925
|
def test_capture_with_send_feature_flags_options_only_evaluate_locally_true(
|
|
757
926
|
self, patch_flags
|
|
@@ -2095,13 +2264,21 @@ class TestClient(unittest.TestCase):
|
|
|
2095
2264
|
self, patch_get, patch_poller
|
|
2096
2265
|
):
|
|
2097
2266
|
"""Test that when enable_local_evaluation=False, the poller is not started"""
|
|
2098
|
-
patch_get.return_value =
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2267
|
+
patch_get.return_value = GetResponse(
|
|
2268
|
+
data={
|
|
2269
|
+
"flags": [
|
|
2270
|
+
{
|
|
2271
|
+
"id": 1,
|
|
2272
|
+
"name": "Beta Feature",
|
|
2273
|
+
"key": "beta-feature",
|
|
2274
|
+
"active": True,
|
|
2275
|
+
}
|
|
2276
|
+
],
|
|
2277
|
+
"group_type_mapping": {},
|
|
2278
|
+
"cohorts": {},
|
|
2279
|
+
},
|
|
2280
|
+
etag='"test-etag"',
|
|
2281
|
+
)
|
|
2105
2282
|
|
|
2106
2283
|
client = Client(
|
|
2107
2284
|
FAKE_TEST_API_KEY,
|
|
@@ -2123,13 +2300,21 @@ class TestClient(unittest.TestCase):
|
|
|
2123
2300
|
@mock.patch("posthog.client.get")
|
|
2124
2301
|
def test_enable_local_evaluation_true_starts_poller(self, patch_get, patch_poller):
|
|
2125
2302
|
"""Test that when enable_local_evaluation=True (default), the poller is started"""
|
|
2126
|
-
patch_get.return_value =
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2303
|
+
patch_get.return_value = GetResponse(
|
|
2304
|
+
data={
|
|
2305
|
+
"flags": [
|
|
2306
|
+
{
|
|
2307
|
+
"id": 1,
|
|
2308
|
+
"name": "Beta Feature",
|
|
2309
|
+
"key": "beta-feature",
|
|
2310
|
+
"active": True,
|
|
2311
|
+
}
|
|
2312
|
+
],
|
|
2313
|
+
"group_type_mapping": {},
|
|
2314
|
+
"cohorts": {},
|
|
2315
|
+
},
|
|
2316
|
+
etag='"test-etag"',
|
|
2317
|
+
)
|
|
2133
2318
|
|
|
2134
2319
|
client = Client(
|
|
2135
2320
|
FAKE_TEST_API_KEY,
|
|
@@ -2423,3 +2608,46 @@ class TestClient(unittest.TestCase):
|
|
|
2423
2608
|
batch_data = mock_post.call_args[1]["batch"]
|
|
2424
2609
|
msg = batch_data[0]
|
|
2425
2610
|
self.assertEqual(msg["properties"]["$context_tags"], ["random_tag"])
|
|
2611
|
+
|
|
2612
|
+
@mock.patch(
|
|
2613
|
+
"posthog.client.Client._enqueue", side_effect=Exception("Unexpected error")
|
|
2614
|
+
)
|
|
2615
|
+
def test_methods_handle_exceptions(self, mock_enqueue):
|
|
2616
|
+
"""Test that all decorated methods handle exceptions gracefully."""
|
|
2617
|
+
client = Client("test-key")
|
|
2618
|
+
|
|
2619
|
+
test_cases = [
|
|
2620
|
+
("capture", ["test_event"], {}),
|
|
2621
|
+
("set", [], {"distinct_id": "some-id", "properties": {"a": "b"}}),
|
|
2622
|
+
("set_once", [], {"distinct_id": "some-id", "properties": {"a": "b"}}),
|
|
2623
|
+
("group_identify", ["group-type", "group-key"], {}),
|
|
2624
|
+
("alias", ["some-id", "new-id"], {}),
|
|
2625
|
+
]
|
|
2626
|
+
|
|
2627
|
+
for method_name, args, kwargs in test_cases:
|
|
2628
|
+
with self.subTest(method=method_name):
|
|
2629
|
+
method = getattr(client, method_name)
|
|
2630
|
+
result = method(*args, **kwargs)
|
|
2631
|
+
self.assertEqual(result, None)
|
|
2632
|
+
|
|
2633
|
+
@mock.patch(
|
|
2634
|
+
"posthog.client.Client._enqueue", side_effect=Exception("Expected error")
|
|
2635
|
+
)
|
|
2636
|
+
def test_debug_flag_re_raises_exceptions(self, mock_enqueue):
|
|
2637
|
+
"""Test that methods re-raise exceptions when debug=True."""
|
|
2638
|
+
client = Client("test-key", debug=True)
|
|
2639
|
+
|
|
2640
|
+
test_cases = [
|
|
2641
|
+
("capture", ["test_event"], {}),
|
|
2642
|
+
("set", [], {"distinct_id": "some-id", "properties": {"a": "b"}}),
|
|
2643
|
+
("set_once", [], {"distinct_id": "some-id", "properties": {"a": "b"}}),
|
|
2644
|
+
("group_identify", ["group-type", "group-key"], {}),
|
|
2645
|
+
("alias", ["some-id", "new-id"], {}),
|
|
2646
|
+
]
|
|
2647
|
+
|
|
2648
|
+
for method_name, args, kwargs in test_cases:
|
|
2649
|
+
with self.subTest(method=method_name):
|
|
2650
|
+
method = getattr(client, method_name)
|
|
2651
|
+
with self.assertRaises(Exception) as cm:
|
|
2652
|
+
method(*args, **kwargs)
|
|
2653
|
+
self.assertEqual(str(cm.exception), "Expected error")
|