posthoganalytics 5.4.0__py3-none-any.whl → 6.0.1__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 +93 -242
- posthoganalytics/ai/langchain/callbacks.py +2 -2
- posthoganalytics/args.py +68 -0
- posthoganalytics/client.py +142 -315
- posthoganalytics/{scopes.py → contexts.py} +27 -18
- posthoganalytics/exception_capture.py +3 -25
- posthoganalytics/exception_utils.py +85 -168
- posthoganalytics/integrations/django.py +46 -7
- posthoganalytics/test/test_before_send.py +124 -77
- posthoganalytics/test/test_client.py +903 -719
- posthoganalytics/test/{test_scopes.py → test_contexts.py} +2 -15
- posthoganalytics/test/test_exception_capture.py +0 -29
- posthoganalytics/test/test_feature_flag_result.py +12 -12
- posthoganalytics/test/test_feature_flags.py +30 -22
- posthoganalytics/test/test_module.py +2 -12
- posthoganalytics/utils.py +57 -0
- posthoganalytics/version.py +1 -1
- {posthoganalytics-5.4.0.dist-info → posthoganalytics-6.0.1.dist-info}/METADATA +5 -2
- {posthoganalytics-5.4.0.dist-info → posthoganalytics-6.0.1.dist-info}/RECORD +22 -23
- posthoganalytics/exception_integrations/__init__.py +0 -5
- posthoganalytics/exception_integrations/django.py +0 -117
- {posthoganalytics-5.4.0.dist-info → posthoganalytics-6.0.1.dist-info}/WHEEL +0 -0
- {posthoganalytics-5.4.0.dist-info → posthoganalytics-6.0.1.dist-info}/licenses/LICENSE +0 -0
- {posthoganalytics-5.4.0.dist-info → posthoganalytics-6.0.1.dist-info}/top_level.txt +0 -0
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import unittest
|
|
2
2
|
from unittest.mock import patch
|
|
3
3
|
|
|
4
|
-
from posthoganalytics.
|
|
5
|
-
clear_tags,
|
|
4
|
+
from posthoganalytics.contexts import (
|
|
6
5
|
get_tags,
|
|
7
6
|
new_context,
|
|
8
7
|
scoped,
|
|
@@ -14,11 +13,7 @@ from posthoganalytics.scopes import (
|
|
|
14
13
|
)
|
|
15
14
|
|
|
16
15
|
|
|
17
|
-
class
|
|
18
|
-
def setUp(self):
|
|
19
|
-
# Reset any context between tests
|
|
20
|
-
clear_tags()
|
|
21
|
-
|
|
16
|
+
class TestContexts(unittest.TestCase):
|
|
22
17
|
def test_tag_and_get_tags(self):
|
|
23
18
|
with new_context(fresh=True):
|
|
24
19
|
tag("key1", "value1")
|
|
@@ -28,14 +23,6 @@ class TestScopes(unittest.TestCase):
|
|
|
28
23
|
assert tags["key1"] == "value1"
|
|
29
24
|
assert tags["key2"] == 2
|
|
30
25
|
|
|
31
|
-
def test_clear_tags(self):
|
|
32
|
-
with new_context(fresh=True):
|
|
33
|
-
tag("key1", "value1")
|
|
34
|
-
assert get_tags()["key1"] == "value1"
|
|
35
|
-
|
|
36
|
-
clear_tags()
|
|
37
|
-
assert get_tags() == {}
|
|
38
|
-
|
|
39
26
|
def test_new_context_isolation(self):
|
|
40
27
|
with new_context(fresh=True):
|
|
41
28
|
# Set tag in outer context
|
|
@@ -32,32 +32,3 @@ def test_excepthook(tmpdir):
|
|
|
32
32
|
b'"$exception_list": [{"mechanism": {"type": "generic", "handled": true}, "module": null, "type": "ZeroDivisionError", "value": "division by zero", "stacktrace": {"frames": [{"platform": "python", "filename": "app.py", "abs_path"'
|
|
33
33
|
in output
|
|
34
34
|
)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def test_trying_to_use_django_integration(tmpdir):
|
|
38
|
-
app = tmpdir.join("app.py")
|
|
39
|
-
app.write(
|
|
40
|
-
dedent(
|
|
41
|
-
"""
|
|
42
|
-
from posthoganalytics import Posthog, Integrations
|
|
43
|
-
posthog = Posthog('phc_x', host='https://eu.i.posthog.com', enable_exception_autocapture=True, exception_autocapture_integrations=[Integrations.Django], debug=True, on_error=lambda e, batch: print('error handling batch: ', e, batch))
|
|
44
|
-
|
|
45
|
-
# frame_value = "LOL"
|
|
46
|
-
|
|
47
|
-
1/0
|
|
48
|
-
"""
|
|
49
|
-
)
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
with pytest.raises(subprocess.CalledProcessError) as excinfo:
|
|
53
|
-
subprocess.check_output([sys.executable, str(app)], stderr=subprocess.STDOUT)
|
|
54
|
-
|
|
55
|
-
output = excinfo.value.output
|
|
56
|
-
|
|
57
|
-
assert b"ZeroDivisionError" in output
|
|
58
|
-
assert b"LOL" in output
|
|
59
|
-
assert b"DEBUG:posthog:data uploaded successfully" in output
|
|
60
|
-
assert (
|
|
61
|
-
b'"$exception_list": [{"mechanism": {"type": "generic", "handled": true}, "module": null, "type": "ZeroDivisionError", "value": "division by zero", "stacktrace": {"frames": [{"platform": "python", "filename": "app.py", "abs_path"'
|
|
62
|
-
in output
|
|
63
|
-
)
|
|
@@ -229,9 +229,9 @@ class TestGetFeatureFlagResult(unittest.TestCase):
|
|
|
229
229
|
self.assertEqual(flag_result.variant, None)
|
|
230
230
|
self.assertEqual(flag_result.payload, 300)
|
|
231
231
|
patch_capture.assert_called_with(
|
|
232
|
-
"some-distinct-id",
|
|
233
232
|
"$feature_flag_called",
|
|
234
|
-
|
|
233
|
+
distinct_id="some-distinct-id",
|
|
234
|
+
properties={
|
|
235
235
|
"$feature_flag": "person-flag",
|
|
236
236
|
"$feature_flag_response": True,
|
|
237
237
|
"locally_evaluated": True,
|
|
@@ -283,9 +283,9 @@ class TestGetFeatureFlagResult(unittest.TestCase):
|
|
|
283
283
|
self.assertEqual(flag_result.payload, {"some": "value"})
|
|
284
284
|
|
|
285
285
|
patch_capture.assert_called_with(
|
|
286
|
-
"distinct_id",
|
|
287
286
|
"$feature_flag_called",
|
|
288
|
-
|
|
287
|
+
distinct_id="distinct_id",
|
|
288
|
+
properties={
|
|
289
289
|
"$feature_flag": "person-flag",
|
|
290
290
|
"$feature_flag_response": "variant-1",
|
|
291
291
|
"locally_evaluated": True,
|
|
@@ -305,9 +305,9 @@ class TestGetFeatureFlagResult(unittest.TestCase):
|
|
|
305
305
|
self.assertIsNone(another_flag_result.payload)
|
|
306
306
|
|
|
307
307
|
patch_capture.assert_called_with(
|
|
308
|
-
"another-distinct-id",
|
|
309
308
|
"$feature_flag_called",
|
|
310
|
-
|
|
309
|
+
distinct_id="another-distinct-id",
|
|
310
|
+
properties={
|
|
311
311
|
"$feature_flag": "person-flag",
|
|
312
312
|
"$feature_flag_response": "variant-2",
|
|
313
313
|
"locally_evaluated": True,
|
|
@@ -345,9 +345,9 @@ class TestGetFeatureFlagResult(unittest.TestCase):
|
|
|
345
345
|
self.assertEqual(flag_result.variant, None)
|
|
346
346
|
self.assertEqual(flag_result.payload, 300)
|
|
347
347
|
patch_capture.assert_called_with(
|
|
348
|
-
"some-distinct-id",
|
|
349
348
|
"$feature_flag_called",
|
|
350
|
-
|
|
349
|
+
distinct_id="some-distinct-id",
|
|
350
|
+
properties={
|
|
351
351
|
"$feature_flag": "person-flag",
|
|
352
352
|
"$feature_flag_response": True,
|
|
353
353
|
"locally_evaluated": False,
|
|
@@ -388,9 +388,9 @@ class TestGetFeatureFlagResult(unittest.TestCase):
|
|
|
388
388
|
self.assertEqual(flag_result.get_value(), "variant-1")
|
|
389
389
|
self.assertEqual(flag_result.payload, [1, 2, 3])
|
|
390
390
|
patch_capture.assert_called_with(
|
|
391
|
-
"distinct_id",
|
|
392
391
|
"$feature_flag_called",
|
|
393
|
-
|
|
392
|
+
distinct_id="distinct_id",
|
|
393
|
+
properties={
|
|
394
394
|
"$feature_flag": "person-flag",
|
|
395
395
|
"$feature_flag_response": "variant-1",
|
|
396
396
|
"locally_evaluated": False,
|
|
@@ -431,9 +431,9 @@ class TestGetFeatureFlagResult(unittest.TestCase):
|
|
|
431
431
|
|
|
432
432
|
self.assertIsNone(flag_result)
|
|
433
433
|
patch_capture.assert_called_with(
|
|
434
|
-
"some-distinct-id",
|
|
435
434
|
"$feature_flag_called",
|
|
436
|
-
|
|
435
|
+
distinct_id="some-distinct-id",
|
|
436
|
+
properties={
|
|
437
437
|
"$feature_flag": "no-person-flag",
|
|
438
438
|
"$feature_flag_response": None,
|
|
439
439
|
"locally_evaluated": False,
|
|
@@ -2695,9 +2695,9 @@ class TestCaptureCalls(unittest.TestCase):
|
|
|
2695
2695
|
)
|
|
2696
2696
|
self.assertEqual(patch_capture.call_count, 1)
|
|
2697
2697
|
patch_capture.assert_called_with(
|
|
2698
|
-
"some-distinct-id",
|
|
2699
2698
|
"$feature_flag_called",
|
|
2700
|
-
|
|
2699
|
+
distinct_id="some-distinct-id",
|
|
2700
|
+
properties={
|
|
2701
2701
|
"$feature_flag": "complex-flag",
|
|
2702
2702
|
"$feature_flag_response": True,
|
|
2703
2703
|
"locally_evaluated": True,
|
|
@@ -2729,9 +2729,9 @@ class TestCaptureCalls(unittest.TestCase):
|
|
|
2729
2729
|
)
|
|
2730
2730
|
self.assertEqual(patch_capture.call_count, 1)
|
|
2731
2731
|
patch_capture.assert_called_with(
|
|
2732
|
-
"some-distinct-id2",
|
|
2733
2732
|
"$feature_flag_called",
|
|
2734
|
-
|
|
2733
|
+
distinct_id="some-distinct-id2",
|
|
2734
|
+
properties={
|
|
2735
2735
|
"$feature_flag": "complex-flag",
|
|
2736
2736
|
"$feature_flag_response": True,
|
|
2737
2737
|
"locally_evaluated": True,
|
|
@@ -2767,9 +2767,9 @@ class TestCaptureCalls(unittest.TestCase):
|
|
|
2767
2767
|
self.assertEqual(patch_flags.call_count, 1)
|
|
2768
2768
|
self.assertEqual(patch_capture.call_count, 1)
|
|
2769
2769
|
patch_capture.assert_called_with(
|
|
2770
|
-
"some-distinct-id2",
|
|
2771
2770
|
"$feature_flag_called",
|
|
2772
|
-
|
|
2771
|
+
distinct_id="some-distinct-id2",
|
|
2772
|
+
properties={
|
|
2773
2773
|
"$feature_flag": "decide-flag",
|
|
2774
2774
|
"$feature_flag_response": "decide-value",
|
|
2775
2775
|
"locally_evaluated": False,
|
|
@@ -2820,9 +2820,9 @@ class TestCaptureCalls(unittest.TestCase):
|
|
|
2820
2820
|
)
|
|
2821
2821
|
self.assertEqual(patch_capture.call_count, 1)
|
|
2822
2822
|
patch_capture.assert_called_with(
|
|
2823
|
-
"some-distinct-id",
|
|
2824
2823
|
"$feature_flag_called",
|
|
2825
|
-
|
|
2824
|
+
distinct_id="some-distinct-id",
|
|
2825
|
+
properties={
|
|
2826
2826
|
"$feature_flag": "decide-flag",
|
|
2827
2827
|
"$feature_flag_response": "decide-variant",
|
|
2828
2828
|
"locally_evaluated": False,
|
|
@@ -2871,9 +2871,9 @@ class TestCaptureCalls(unittest.TestCase):
|
|
|
2871
2871
|
)
|
|
2872
2872
|
self.assertEqual(patch_capture.call_count, 1)
|
|
2873
2873
|
patch_capture.assert_called_with(
|
|
2874
|
-
"some-distinct-id",
|
|
2875
2874
|
"$feature_flag_called",
|
|
2876
|
-
|
|
2875
|
+
distinct_id="some-distinct-id",
|
|
2876
|
+
properties={
|
|
2877
2877
|
"$feature_flag": "decide-flag-with-payload",
|
|
2878
2878
|
"$feature_flag_response": True,
|
|
2879
2879
|
"locally_evaluated": False,
|
|
@@ -2948,7 +2948,9 @@ class TestCaptureCalls(unittest.TestCase):
|
|
|
2948
2948
|
"featureFlags": {"person-flag": True},
|
|
2949
2949
|
"featureFlagPayloads": {"person-flag": 300},
|
|
2950
2950
|
}
|
|
2951
|
-
client = Client(
|
|
2951
|
+
client = Client(
|
|
2952
|
+
project_api_key=FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY
|
|
2953
|
+
)
|
|
2952
2954
|
|
|
2953
2955
|
client.feature_flags = [
|
|
2954
2956
|
{
|
|
@@ -2977,9 +2979,9 @@ class TestCaptureCalls(unittest.TestCase):
|
|
|
2977
2979
|
# Assert that capture was called once, with the correct parameters
|
|
2978
2980
|
self.assertEqual(patch_capture.call_count, 1)
|
|
2979
2981
|
patch_capture.assert_called_with(
|
|
2980
|
-
"some-distinct-id",
|
|
2981
2982
|
"$feature_flag_called",
|
|
2982
|
-
|
|
2983
|
+
distinct_id="some-distinct-id",
|
|
2984
|
+
properties={
|
|
2983
2985
|
"$feature_flag": "person-flag",
|
|
2984
2986
|
"$feature_flag_response": True,
|
|
2985
2987
|
"locally_evaluated": True,
|
|
@@ -3012,9 +3014,9 @@ class TestCaptureCalls(unittest.TestCase):
|
|
|
3012
3014
|
|
|
3013
3015
|
self.assertEqual(patch_capture.call_count, 1)
|
|
3014
3016
|
patch_capture.assert_called_with(
|
|
3015
|
-
"some-distinct-id2",
|
|
3016
3017
|
"$feature_flag_called",
|
|
3017
|
-
|
|
3018
|
+
distinct_id="some-distinct-id2",
|
|
3019
|
+
properties={
|
|
3018
3020
|
"$feature_flag": "person-flag",
|
|
3019
3021
|
"$feature_flag_response": True,
|
|
3020
3022
|
"locally_evaluated": True,
|
|
@@ -3058,9 +3060,9 @@ class TestCaptureCalls(unittest.TestCase):
|
|
|
3058
3060
|
)
|
|
3059
3061
|
|
|
3060
3062
|
patch_capture.assert_called_with(
|
|
3061
|
-
"some-distinct-id",
|
|
3062
3063
|
"$feature_flag_called",
|
|
3063
|
-
|
|
3064
|
+
distinct_id="some-distinct-id",
|
|
3065
|
+
properties={
|
|
3064
3066
|
"$feature_flag": "complex-flag",
|
|
3065
3067
|
"$feature_flag_response": True,
|
|
3066
3068
|
"locally_evaluated": True,
|
|
@@ -3102,9 +3104,9 @@ class TestCaptureCalls(unittest.TestCase):
|
|
|
3102
3104
|
person_properties={"region": "USA", "name": "Aloha"},
|
|
3103
3105
|
)
|
|
3104
3106
|
patch_capture.assert_called_with(
|
|
3105
|
-
distinct_id,
|
|
3106
3107
|
"$feature_flag_called",
|
|
3107
|
-
|
|
3108
|
+
distinct_id=distinct_id,
|
|
3109
|
+
properties={
|
|
3108
3110
|
"$feature_flag": "complex-flag",
|
|
3109
3111
|
"$feature_flag_response": True,
|
|
3110
3112
|
"locally_evaluated": True,
|
|
@@ -5229,7 +5231,9 @@ class TestConsistency(unittest.TestCase):
|
|
|
5229
5231
|
"featureFlags": {}
|
|
5230
5232
|
} # Ensure decide returns empty flags
|
|
5231
5233
|
|
|
5232
|
-
client = Client(
|
|
5234
|
+
client = Client(
|
|
5235
|
+
project_api_key=FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY
|
|
5236
|
+
)
|
|
5233
5237
|
client.feature_flags = [
|
|
5234
5238
|
{
|
|
5235
5239
|
"id": 1,
|
|
@@ -5253,7 +5257,9 @@ class TestConsistency(unittest.TestCase):
|
|
|
5253
5257
|
"featureFlagPayloads": {"Beta-Feature": {"some": "value"}},
|
|
5254
5258
|
}
|
|
5255
5259
|
|
|
5256
|
-
client = Client(
|
|
5260
|
+
client = Client(
|
|
5261
|
+
project_api_key=FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY
|
|
5262
|
+
)
|
|
5257
5263
|
client.feature_flags = [
|
|
5258
5264
|
{
|
|
5259
5265
|
"id": 1,
|
|
@@ -5282,7 +5288,9 @@ class TestConsistency(unittest.TestCase):
|
|
|
5282
5288
|
"featureFlagPayloads": {"Beta-Feature": {"some": "value"}},
|
|
5283
5289
|
}
|
|
5284
5290
|
|
|
5285
|
-
client = Client(
|
|
5291
|
+
client = Client(
|
|
5292
|
+
project_api_key=FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY
|
|
5293
|
+
)
|
|
5286
5294
|
client.feature_flags = [
|
|
5287
5295
|
{
|
|
5288
5296
|
"id": 1,
|
|
@@ -7,8 +7,7 @@ class TestModule(unittest.TestCase):
|
|
|
7
7
|
posthog = None
|
|
8
8
|
|
|
9
9
|
def _assert_enqueue_result(self, result):
|
|
10
|
-
self.assertEqual(type(result[0]),
|
|
11
|
-
self.assertEqual(type(result[1]), dict)
|
|
10
|
+
self.assertEqual(type(result[0]), str)
|
|
12
11
|
|
|
13
12
|
def failed(self):
|
|
14
13
|
self.failed = True
|
|
@@ -28,12 +27,7 @@ class TestModule(unittest.TestCase):
|
|
|
28
27
|
self.assertRaises(Exception, self.posthog.capture)
|
|
29
28
|
|
|
30
29
|
def test_track(self):
|
|
31
|
-
res = self.posthog.capture("
|
|
32
|
-
self._assert_enqueue_result(res)
|
|
33
|
-
self.posthog.flush()
|
|
34
|
-
|
|
35
|
-
def test_identify(self):
|
|
36
|
-
res = self.posthog.identify("distinct_id", {"email": "user@email.com"})
|
|
30
|
+
res = self.posthog.capture("python module event", distinct_id="distinct_id")
|
|
37
31
|
self._assert_enqueue_result(res)
|
|
38
32
|
self.posthog.flush()
|
|
39
33
|
|
|
@@ -42,9 +36,5 @@ class TestModule(unittest.TestCase):
|
|
|
42
36
|
self._assert_enqueue_result(res)
|
|
43
37
|
self.posthog.flush()
|
|
44
38
|
|
|
45
|
-
def test_page(self):
|
|
46
|
-
self.posthog.page("distinct_id", "https://posthog.com/contact")
|
|
47
|
-
self.posthog.flush()
|
|
48
|
-
|
|
49
39
|
def test_flush(self):
|
|
50
40
|
self.posthog.flush()
|
posthoganalytics/utils.py
CHANGED
|
@@ -7,6 +7,9 @@ from datetime import date, datetime, timezone
|
|
|
7
7
|
from decimal import Decimal
|
|
8
8
|
from typing import Any, Optional
|
|
9
9
|
from uuid import UUID
|
|
10
|
+
import sys
|
|
11
|
+
import platform
|
|
12
|
+
import distro # For Linux OS detection
|
|
10
13
|
|
|
11
14
|
import six
|
|
12
15
|
from dateutil.tz import tzlocal, tzutc
|
|
@@ -198,3 +201,57 @@ def str_iequals(value, comparand):
|
|
|
198
201
|
False
|
|
199
202
|
"""
|
|
200
203
|
return str(value).casefold() == str(comparand).casefold()
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def get_os_info():
|
|
207
|
+
"""
|
|
208
|
+
Returns standardized OS name and version information.
|
|
209
|
+
Similar to how user agent parsing works in JS.
|
|
210
|
+
"""
|
|
211
|
+
os_name = ""
|
|
212
|
+
os_version = ""
|
|
213
|
+
|
|
214
|
+
platform_name = sys.platform
|
|
215
|
+
|
|
216
|
+
if platform_name.startswith("win"):
|
|
217
|
+
os_name = "Windows"
|
|
218
|
+
if hasattr(platform, "win32_ver"):
|
|
219
|
+
win_version = platform.win32_ver()[0]
|
|
220
|
+
if win_version:
|
|
221
|
+
os_version = win_version
|
|
222
|
+
|
|
223
|
+
elif platform_name == "darwin":
|
|
224
|
+
os_name = "Mac OS X"
|
|
225
|
+
if hasattr(platform, "mac_ver"):
|
|
226
|
+
mac_version = platform.mac_ver()[0]
|
|
227
|
+
if mac_version:
|
|
228
|
+
os_version = mac_version
|
|
229
|
+
|
|
230
|
+
elif platform_name.startswith("linux"):
|
|
231
|
+
os_name = "Linux"
|
|
232
|
+
linux_info = distro.info()
|
|
233
|
+
if linux_info["version"]:
|
|
234
|
+
os_version = linux_info["version"]
|
|
235
|
+
|
|
236
|
+
elif platform_name.startswith("freebsd"):
|
|
237
|
+
os_name = "FreeBSD"
|
|
238
|
+
if hasattr(platform, "release"):
|
|
239
|
+
os_version = platform.release()
|
|
240
|
+
|
|
241
|
+
else:
|
|
242
|
+
os_name = platform_name
|
|
243
|
+
if hasattr(platform, "release"):
|
|
244
|
+
os_version = platform.release()
|
|
245
|
+
|
|
246
|
+
return os_name, os_version
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def system_context() -> dict[str, Any]:
|
|
250
|
+
os_name, os_version = get_os_info()
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
"$python_runtime": platform.python_implementation(),
|
|
254
|
+
"$python_version": "%s.%s.%s" % (sys.version_info[:3]),
|
|
255
|
+
"$os": os_name,
|
|
256
|
+
"$os_version": os_version,
|
|
257
|
+
}
|
posthoganalytics/version.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: posthoganalytics
|
|
3
|
-
Version:
|
|
3
|
+
Version: 6.0.1
|
|
4
4
|
Summary: Integrate PostHog into any python application.
|
|
5
5
|
Home-page: https://github.com/posthog/posthog-python
|
|
6
6
|
Author: Posthog
|
|
@@ -28,6 +28,7 @@ Requires-Dist: six>=1.5
|
|
|
28
28
|
Requires-Dist: python-dateutil>=2.2
|
|
29
29
|
Requires-Dist: backoff>=1.10.0
|
|
30
30
|
Requires-Dist: distro>=1.5.0
|
|
31
|
+
Requires-Dist: typing-extensions>=4.2.0
|
|
31
32
|
Provides-Extra: langchain
|
|
32
33
|
Requires-Dist: langchain>=0.2.0; extra == "langchain"
|
|
33
34
|
Provides-Extra: dev
|
|
@@ -119,7 +120,9 @@ Assuming you have a [local version of PostHog](https://posthog.com/docs/developi
|
|
|
119
120
|
|
|
120
121
|
### Releasing Versions
|
|
121
122
|
|
|
122
|
-
Updates are released using GitHub Actions
|
|
123
|
+
Updates are released automatically using GitHub Actions when `version.py` is updated on `master`. After bumping `version.py` in `master` and adding to `CHANGELOG.md`, the [release workflow](https://github.com/PostHog/posthog-python/blob/master/.github/workflows/release.yaml) will automatically trigger and deploy the new version.
|
|
124
|
+
|
|
125
|
+
If you need to check the latest runs or manually trigger a release, you can go to [our release workflow's page](https://github.com/PostHog/posthog-python/actions/workflows/release.yaml) and dispatch it manually, using workflow from `master`.
|
|
123
126
|
|
|
124
127
|
|
|
125
128
|
### Testing changes locally with the PostHog app
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
posthoganalytics/__init__.py,sha256=
|
|
2
|
-
posthoganalytics/
|
|
1
|
+
posthoganalytics/__init__.py,sha256=kijo4odBr7QnGezx-o2yAzZlbg51sP4u6SXC8TADwr0,16692
|
|
2
|
+
posthoganalytics/args.py,sha256=1t-zFF96ep95spr8YGDdntXBaX7_JHddEr0Xa6MPLnM,3144
|
|
3
|
+
posthoganalytics/client.py,sha256=HsOefqDYB05w4h-5bewZzcpX9ifeLeWIplzsdNKwH8o,44690
|
|
3
4
|
posthoganalytics/consumer.py,sha256=CiNbJBdyW9jER3ZYCKbX-JFmEDXlE1lbDy1MSl43-a0,4617
|
|
4
|
-
posthoganalytics/
|
|
5
|
-
posthoganalytics/
|
|
5
|
+
posthoganalytics/contexts.py,sha256=B3Y62sX7w-MCqNqgguUceQnKn5RCBFIqen3VeR3qems,9020
|
|
6
|
+
posthoganalytics/exception_capture.py,sha256=1VHBfffrXXrkK0PT8iVgKPpj_R1pGAzG5f3Qw0WF79w,1783
|
|
7
|
+
posthoganalytics/exception_utils.py,sha256=P_75873Y2jayqlLiIkbxCNE7Bc8cM6J9kfrdZ5ZSnA0,26696
|
|
6
8
|
posthoganalytics/feature_flags.py,sha256=s87jlo515OV2A0GOtMezwImt5vFUAn8kqsMwzdoOURo,13652
|
|
7
9
|
posthoganalytics/poller.py,sha256=jBz5rfH_kn_bBz7wCB46Fpvso4ttx4uzqIZWvXBCFmQ,595
|
|
8
10
|
posthoganalytics/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
11
|
posthoganalytics/request.py,sha256=TaeySYpcvHMf5Ftf5KqqlO0VPJpirKBCRrThlS04Kew,6124
|
|
10
|
-
posthoganalytics/scopes.py,sha256=rx79WerS6vmNP2IDR1dfZQVNDHJ3xUnVz6be1YWD2gM,8194
|
|
11
12
|
posthoganalytics/types.py,sha256=INxWBOEQc0xgPcap6FdQNSU7zuQBmKShYaGzyuHKql8,9128
|
|
12
|
-
posthoganalytics/utils.py,sha256=
|
|
13
|
-
posthoganalytics/version.py,sha256=
|
|
13
|
+
posthoganalytics/utils.py,sha256=rp23PTgYw4r-Kus-Ga1UbAtkKYXMrz2c5Y-j-a7syGo,7119
|
|
14
|
+
posthoganalytics/version.py,sha256=b2T4GeejTNDKeomLv0BSpkpwZcmrZY786_LbLp2CmyM,87
|
|
14
15
|
posthoganalytics/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
16
|
posthoganalytics/ai/utils.py,sha256=5-2XfmetCs0v9otBoux7-IEG933wAnKLSGS6oYLqCkw,19529
|
|
16
17
|
posthoganalytics/ai/anthropic/__init__.py,sha256=fFhDOiRzTXzGQlgnrRDL-4yKC8EYIl8NW4a2QNR6xRU,368
|
|
@@ -20,31 +21,29 @@ posthoganalytics/ai/anthropic/anthropic_providers.py,sha256=6gnL_Z43FTar2TGNPDud
|
|
|
20
21
|
posthoganalytics/ai/gemini/__init__.py,sha256=bMNBnJ6NO_PCQCwmxKIiw4adFuEQ06hFFBALt-aDW-0,174
|
|
21
22
|
posthoganalytics/ai/gemini/gemini.py,sha256=NmVfsG3VaTmDoNCIqXgw2n48H-00zHKfqVY46WFPbEI,13152
|
|
22
23
|
posthoganalytics/ai/langchain/__init__.py,sha256=9CqAwLynTGj3ASAR80C3PmdTdrYGmu99tz0JL-HPFgI,70
|
|
23
|
-
posthoganalytics/ai/langchain/callbacks.py,sha256=
|
|
24
|
+
posthoganalytics/ai/langchain/callbacks.py,sha256=qZlHsD8QJiXbvykBIDvNlPa0lQS4zXaxTmZ0R9JHpDQ,28848
|
|
24
25
|
posthoganalytics/ai/openai/__init__.py,sha256=_flZxkyaDZme9hxJsY31sMlq4nP1dtc75HmNgj-21Kg,197
|
|
25
26
|
posthoganalytics/ai/openai/openai.py,sha256=iL_cwctaAhPdXNo4EpIZooOWGyjNj0W-OUEoLchTj9s,23394
|
|
26
27
|
posthoganalytics/ai/openai/openai_async.py,sha256=KxPCd5imF5iZ9VkJ12HjCO2skaF1tHsHveAknIqV93g,23769
|
|
27
28
|
posthoganalytics/ai/openai/openai_providers.py,sha256=EMuEvdHSOFbrhmfuU0is7pBVWS3ReAUT0PZqgMXdyjk,3884
|
|
28
|
-
posthoganalytics/exception_integrations/__init__.py,sha256=Xcrhc37EXc0mSfkUhFzglx0nCvGivZtohBqBZ2VdIsU,187
|
|
29
|
-
posthoganalytics/exception_integrations/django.py,sha256=1QY7mRfhW4kopS4bNgDbe1kJf4BajSwxfYJP9waYPW8,3708
|
|
30
29
|
posthoganalytics/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
31
|
-
posthoganalytics/integrations/django.py,sha256=
|
|
30
|
+
posthoganalytics/integrations/django.py,sha256=NUC2XHZxQ3XmfqPaVKrAvVma4284eFqq-x0NQTRdaUU,5998
|
|
32
31
|
posthoganalytics/test/__init__.py,sha256=VYgM6xPbJbvS-xhIcDiBRs0MFC9V_jT65uNeerCz_rM,299
|
|
33
|
-
posthoganalytics/test/test_before_send.py,sha256=
|
|
34
|
-
posthoganalytics/test/test_client.py,sha256=
|
|
32
|
+
posthoganalytics/test/test_before_send.py,sha256=A1_UVMewhHAvO39rZDWfS606vG_X-q0KNXvh5DAKiB8,7930
|
|
33
|
+
posthoganalytics/test/test_client.py,sha256=msR101AXAyDrglWMJ1MjSw59o6CsXSXnjZ0pwIG-MDk,74571
|
|
35
34
|
posthoganalytics/test/test_consumer.py,sha256=HGMfU9PzQ5ZAe_R3kHnZNsMvD7jUjHL-gie0isrvMMk,7107
|
|
36
|
-
posthoganalytics/test/
|
|
35
|
+
posthoganalytics/test/test_contexts.py,sha256=c--hNUIEf6SHQ7H9vdPhU1oLCN0SnD4wDbFr-eLPHDo,7013
|
|
36
|
+
posthoganalytics/test/test_exception_capture.py,sha256=al37Kg6wjzL_IBCFUUXRvkP6nVrqS6IZRCOKSo29Nh8,1063
|
|
37
37
|
posthoganalytics/test/test_feature_flag.py,sha256=9RQwB5eCvVAGrrO7UnR3Z1OidP_YoL4iBl3A83fuAig,6824
|
|
38
|
-
posthoganalytics/test/test_feature_flag_result.py,sha256=
|
|
39
|
-
posthoganalytics/test/test_feature_flags.py,sha256=
|
|
40
|
-
posthoganalytics/test/test_module.py,sha256=
|
|
38
|
+
posthoganalytics/test/test_feature_flag_result.py,sha256=z2OgD97r85LKMqCnoCqAs74WjUMucayAtC3qWaITGCA,15898
|
|
39
|
+
posthoganalytics/test/test_feature_flags.py,sha256=2CW_E1TrqHC-BXAP68LEBi8YleM7J82XQc_gHX1Bxtc,169056
|
|
40
|
+
posthoganalytics/test/test_module.py,sha256=viqaAWA_uHt8r20fHIeME6IQkeXmQ8ZyrJTtPGQAb1E,1070
|
|
41
41
|
posthoganalytics/test/test_request.py,sha256=Zc0VbkjpVmj8mKokQm9rzdgTr0b1U44vvMYSkB_IQLs,4467
|
|
42
|
-
posthoganalytics/test/test_scopes.py,sha256=e3hk1QJ8RRs9FZB6A_LidCzQigkVhZTxSXl9dZxsKqU,7326
|
|
43
42
|
posthoganalytics/test/test_size_limited_dict.py,sha256=-5IQjIEr_-Dql24M0HusdR_XroOMrtgiT0v6ZQCRvzo,774
|
|
44
43
|
posthoganalytics/test/test_types.py,sha256=bRPHdwVpP7hu7emsplU8UVyzSQptv6PaG5lAoOD_BtM,7595
|
|
45
44
|
posthoganalytics/test/test_utils.py,sha256=GYLJp4ud_RP31-NnYJINOY0G0ra-QcGJszpp9MTyYq8,5428
|
|
46
|
-
posthoganalytics-
|
|
47
|
-
posthoganalytics-
|
|
48
|
-
posthoganalytics-
|
|
49
|
-
posthoganalytics-
|
|
50
|
-
posthoganalytics-
|
|
45
|
+
posthoganalytics-6.0.1.dist-info/licenses/LICENSE,sha256=wGf9JBotDkSygFj43m49oiKlFnpMnn97keiZKF-40vE,2450
|
|
46
|
+
posthoganalytics-6.0.1.dist-info/METADATA,sha256=J2gB4_kwciV-2awv3rMegvpR9HrYqFFbVNTaQUG7rP8,6028
|
|
47
|
+
posthoganalytics-6.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
48
|
+
posthoganalytics-6.0.1.dist-info/top_level.txt,sha256=8QsNIqIkBh1p2TXvKp0Em9ZLZKwe3uIqCETyW4s1GOE,17
|
|
49
|
+
posthoganalytics-6.0.1.dist-info/RECORD,,
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
# Portions of this file are derived from getsentry/sentry-javascript by Software, Inc. dba Sentry
|
|
2
|
-
# Licensed under the MIT License
|
|
3
|
-
|
|
4
|
-
# 💖open source (under MIT License)
|
|
5
|
-
|
|
6
|
-
import re
|
|
7
|
-
import sys
|
|
8
|
-
from typing import TYPE_CHECKING
|
|
9
|
-
|
|
10
|
-
from posthoganalytics.exception_integrations import IntegrationEnablingError
|
|
11
|
-
|
|
12
|
-
try:
|
|
13
|
-
from django import VERSION as DJANGO_VERSION
|
|
14
|
-
from django.core import signals
|
|
15
|
-
|
|
16
|
-
except ImportError:
|
|
17
|
-
raise IntegrationEnablingError("Django not installed")
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if TYPE_CHECKING:
|
|
21
|
-
from typing import Any, Dict # noqa: F401
|
|
22
|
-
|
|
23
|
-
from django.core.handlers.wsgi import WSGIRequest # noqa: F401
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class DjangoIntegration:
|
|
27
|
-
# TODO: Abstract integrations one we have more and can see patterns
|
|
28
|
-
"""
|
|
29
|
-
Autocapture errors from a Django application.
|
|
30
|
-
"""
|
|
31
|
-
|
|
32
|
-
identifier = "django"
|
|
33
|
-
|
|
34
|
-
def __init__(self, capture_exception_fn=None):
|
|
35
|
-
if DJANGO_VERSION < (4, 2):
|
|
36
|
-
raise IntegrationEnablingError("Django 4.2 or newer is required.")
|
|
37
|
-
|
|
38
|
-
# TODO: Right now this seems too complicated / overkill for us, but seems like we can automatically plug in middlewares
|
|
39
|
-
# which is great for users (they don't need to do this) and everything should just work.
|
|
40
|
-
# We should consider this in the future, but for now we can just use the middleware and signals handlers.
|
|
41
|
-
# See: https://github.com/getsentry/sentry-python/blob/269d96d6e9821122fbff280e6a26956e5ed03c0b/sentry_sdk/integrations/django/__init__.py
|
|
42
|
-
|
|
43
|
-
self.capture_exception_fn = capture_exception_fn
|
|
44
|
-
|
|
45
|
-
def _got_request_exception(request=None, **kwargs):
|
|
46
|
-
# type: (WSGIRequest, **Any) -> None
|
|
47
|
-
|
|
48
|
-
extra_props = {}
|
|
49
|
-
if request is not None:
|
|
50
|
-
# get headers metadata
|
|
51
|
-
extra_props = DjangoRequestExtractor(request).extract_person_data()
|
|
52
|
-
|
|
53
|
-
self.capture_exception_fn(sys.exc_info(), extra_props)
|
|
54
|
-
|
|
55
|
-
signals.got_request_exception.connect(_got_request_exception)
|
|
56
|
-
|
|
57
|
-
def uninstall(self):
|
|
58
|
-
pass
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
class DjangoRequestExtractor:
|
|
62
|
-
def __init__(self, request):
|
|
63
|
-
# type: (Any) -> None
|
|
64
|
-
self.request = request
|
|
65
|
-
|
|
66
|
-
def extract_person_data(self):
|
|
67
|
-
headers = self.headers()
|
|
68
|
-
|
|
69
|
-
# Extract traceparent and tracestate headers
|
|
70
|
-
traceparent = headers.get("Traceparent")
|
|
71
|
-
tracestate = headers.get("Tracestate")
|
|
72
|
-
|
|
73
|
-
# Extract the distinct_id from tracestate
|
|
74
|
-
distinct_id = None
|
|
75
|
-
if tracestate:
|
|
76
|
-
# TODO: Align on the format of the distinct_id in tracestate
|
|
77
|
-
# We can't have comma or equals in header values here, so maybe we should base64 encode it?
|
|
78
|
-
match = re.search(r"posthog-distinct-id=([^,]+)", tracestate)
|
|
79
|
-
if match:
|
|
80
|
-
distinct_id = match.group(1)
|
|
81
|
-
|
|
82
|
-
return {
|
|
83
|
-
**self.user(),
|
|
84
|
-
"distinct_id": distinct_id,
|
|
85
|
-
"ip": headers.get("X-Forwarded-For"),
|
|
86
|
-
"user_agent": headers.get("User-Agent"),
|
|
87
|
-
"traceparent": traceparent,
|
|
88
|
-
"$request_path": self.request.path,
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
def user(self):
|
|
92
|
-
user_data: dict[str, str] = {}
|
|
93
|
-
|
|
94
|
-
user = getattr(self.request, "user", None)
|
|
95
|
-
|
|
96
|
-
if user is None or not user.is_authenticated:
|
|
97
|
-
return user_data
|
|
98
|
-
|
|
99
|
-
try:
|
|
100
|
-
user_id = str(user.pk)
|
|
101
|
-
if user_id:
|
|
102
|
-
user_data.setdefault("$user_id", user_id)
|
|
103
|
-
except Exception:
|
|
104
|
-
pass
|
|
105
|
-
|
|
106
|
-
try:
|
|
107
|
-
email = str(user.email)
|
|
108
|
-
if email:
|
|
109
|
-
user_data.setdefault("email", email)
|
|
110
|
-
except Exception:
|
|
111
|
-
pass
|
|
112
|
-
|
|
113
|
-
return user_data
|
|
114
|
-
|
|
115
|
-
def headers(self):
|
|
116
|
-
# type: () -> Dict[str, str]
|
|
117
|
-
return dict(self.request.headers)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|