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.
@@ -1,8 +1,7 @@
1
1
  import unittest
2
2
  from unittest.mock import patch
3
3
 
4
- from posthoganalytics.scopes import (
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 TestScopes(unittest.TestCase):
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(api_key=FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY)
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(api_key=FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY)
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(api_key=FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY)
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(api_key=FAKE_TEST_API_KEY, personal_api_key=FAKE_TEST_API_KEY)
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]), bool)
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("distinct_id", "python module event")
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
+ }
@@ -1,4 +1,4 @@
1
- VERSION = "5.4.0"
1
+ VERSION = "6.0.1"
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: 5.4.0
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: after bumping `version.py` in `master` and adding to `CHANGELOG.md`, 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
+ 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=1VxnAzs1ndcud3A2RkZ_Pys1mJcCEqmRSzdR7kbk06E,19680
2
- posthoganalytics/client.py,sha256=if7J6pyp8XaBrHYXiyqgIRTpJ7bVDskw69kfIRfg9E4,49047
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/exception_capture.py,sha256=tO--YrFqaMqH4h2SYsm8Oeo3KUUky6JyxA0ubo-_xE0,2724
5
- posthoganalytics/exception_utils.py,sha256=T7vh88VWO1gWES5VkE8uR7zKOez6KBC-kRDnSPQKA0A,29680
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=4qw-5PMYVXS66367olLYn8IqGNKcwfevv5n7w1Mjh2Q,5587
13
- posthoganalytics/version.py,sha256=I85JOowUqLrC4sAZT80knLum_fFLg-MOlCH3fUL7AoE,87
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=Wdu2efBd3RLn2U5xDcA9g_rmt2neSkaJyEssdPpoIqA,28862
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=IT3qiFfzK_XqEW0XgXNabMBZRj2viwf97Yc9aTuVxqg,4934
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=UrapOB5SHL6N6yleNnIa-H37Ih603Xa-bVJ4AdgjQRM,5959
34
- posthoganalytics/test/test_client.py,sha256=4adaYx9R-mm32n3cQLz6X1cZzQQCxFs9b1FRycdMQJE,64716
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/test_exception_capture.py,sha256=SQ14AFhqe_VaS34C1IMqEFWq04HOTrjwQ0JRX31R_ik,2148
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=i6IdM4rMxU2vD-P951UQ4NXHKO4ypn0H0FdXB6o8YJA,15760
39
- posthoganalytics/test/test_feature_flags.py,sha256=ETp347ngYmsADlNEApBr44AKE3c-q4vHwh8G3MMDEiU,168729
40
- posthoganalytics/test/test_module.py,sha256=UsdY17P2HuDy8Ei6Z6zxN3LUmxkehR8PDFCXVRw61Bo,1414
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-5.4.0.dist-info/licenses/LICENSE,sha256=wGf9JBotDkSygFj43m49oiKlFnpMnn97keiZKF-40vE,2450
47
- posthoganalytics-5.4.0.dist-info/METADATA,sha256=75Pd57i7U7L0hP2qbEqmpNcg3qrpBIF6xH-Qs8y6CXw,5692
48
- posthoganalytics-5.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
49
- posthoganalytics-5.4.0.dist-info/top_level.txt,sha256=8QsNIqIkBh1p2TXvKp0Em9ZLZKwe3uIqCETyW4s1GOE,17
50
- posthoganalytics-5.4.0.dist-info/RECORD,,
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,5 +0,0 @@
1
- class IntegrationEnablingError(Exception):
2
- """
3
- The integration could not be enabled due to a user error like
4
- `django` not being installed for the `DjangoIntegration`.
5
- """
@@ -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)