posthoganalytics 6.7.13__tar.gz → 6.7.14__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 (62) hide show
  1. {posthoganalytics-6.7.13/posthoganalytics.egg-info → posthoganalytics-6.7.14}/PKG-INFO +1 -1
  2. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/client.py +1 -2
  3. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/integrations/django.py +81 -13
  4. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/version.py +1 -1
  5. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14/posthoganalytics.egg-info}/PKG-INFO +1 -1
  6. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/pyproject.toml +6 -0
  7. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/LICENSE +0 -0
  8. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/MANIFEST.in +0 -0
  9. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/README.md +0 -0
  10. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/__init__.py +0 -0
  11. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/ai/__init__.py +0 -0
  12. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/ai/anthropic/__init__.py +0 -0
  13. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/ai/anthropic/anthropic.py +0 -0
  14. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/ai/anthropic/anthropic_async.py +0 -0
  15. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/ai/anthropic/anthropic_converter.py +0 -0
  16. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/ai/anthropic/anthropic_providers.py +0 -0
  17. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/ai/gemini/__init__.py +0 -0
  18. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/ai/gemini/gemini.py +0 -0
  19. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/ai/gemini/gemini_converter.py +0 -0
  20. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/ai/langchain/__init__.py +0 -0
  21. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/ai/langchain/callbacks.py +0 -0
  22. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/ai/openai/__init__.py +0 -0
  23. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/ai/openai/openai.py +0 -0
  24. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/ai/openai/openai_async.py +0 -0
  25. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/ai/openai/openai_converter.py +0 -0
  26. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/ai/openai/openai_providers.py +0 -0
  27. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/ai/sanitization.py +0 -0
  28. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/ai/types.py +0 -0
  29. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/ai/utils.py +0 -0
  30. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/args.py +0 -0
  31. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/consumer.py +0 -0
  32. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/contexts.py +0 -0
  33. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/exception_capture.py +0 -0
  34. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/exception_utils.py +0 -0
  35. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/feature_flags.py +0 -0
  36. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/integrations/__init__.py +0 -0
  37. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/poller.py +0 -0
  38. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/py.typed +0 -0
  39. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/request.py +0 -0
  40. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/test/__init__.py +0 -0
  41. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/test/test_before_send.py +0 -0
  42. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/test/test_client.py +0 -0
  43. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/test/test_consumer.py +0 -0
  44. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/test/test_contexts.py +0 -0
  45. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/test/test_exception_capture.py +0 -0
  46. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/test/test_feature_flag.py +0 -0
  47. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/test/test_feature_flag_result.py +0 -0
  48. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/test/test_feature_flags.py +0 -0
  49. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/test/test_module.py +0 -0
  50. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/test/test_request.py +0 -0
  51. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/test/test_size_limited_dict.py +0 -0
  52. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/test/test_types.py +0 -0
  53. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/test/test_utils.py +0 -0
  54. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/types.py +0 -0
  55. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics/utils.py +0 -0
  56. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics.egg-info/SOURCES.txt +0 -0
  57. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics.egg-info/dependency_links.txt +0 -0
  58. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics.egg-info/requires.txt +0 -0
  59. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/posthoganalytics.egg-info/top_level.txt +0 -0
  60. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/setup.cfg +0 -0
  61. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/setup.py +0 -0
  62. {posthoganalytics-6.7.13 → posthoganalytics-6.7.14}/setup_analytics.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: posthoganalytics
3
- Version: 6.7.13
3
+ Version: 6.7.14
4
4
  Summary: Integrate PostHog into any python application.
5
5
  Home-page: https://github.com/posthog/posthog-python
6
6
  Author: Posthog
@@ -3,7 +3,7 @@ import logging
3
3
  import os
4
4
  import sys
5
5
  from datetime import datetime, timedelta
6
- from typing import Any, Callable, Dict, Optional, Union
6
+ from typing import Any, Dict, Optional, Union
7
7
  from typing_extensions import Unpack
8
8
  from uuid import uuid4
9
9
 
@@ -60,7 +60,6 @@ from posthoganalytics.utils import (
60
60
  SizeLimitedDict,
61
61
  clean,
62
62
  guess_timezone,
63
- remove_trailing_slash,
64
63
  system_context,
65
64
  )
66
65
  from posthoganalytics.version import VERSION
@@ -112,9 +112,18 @@ class PosthogContextMiddleware:
112
112
 
113
113
  def extract_tags(self, request):
114
114
  # type: (HttpRequest) -> Dict[str, Any]
115
- tags = {}
115
+ """Extract tags from request in sync context."""
116
+ user_id, user_email = self.extract_request_user(request)
117
+ return self._build_tags(request, user_id, user_email)
118
+
119
+ def _build_tags(self, request, user_id, user_email):
120
+ # type: (HttpRequest, Optional[str], Optional[str]) -> Dict[str, Any]
121
+ """
122
+ Build tags dict from request and user info.
116
123
 
117
- (user_id, user_email) = self.extract_request_user(request)
124
+ Centralized tag extraction logic used by both sync and async paths.
125
+ """
126
+ tags = {}
118
127
 
119
128
  # Extract session ID from X-POSTHOG-SESSION-ID header
120
129
  session_id = request.headers.get("X-POSTHOG-SESSION-ID")
@@ -166,21 +175,78 @@ class PosthogContextMiddleware:
166
175
  return tags
167
176
 
168
177
  def extract_request_user(self, request):
169
- user_id = None
170
- email = None
171
-
178
+ # type: (HttpRequest) -> tuple[Optional[str], Optional[str]]
179
+ """Extract user ID and email from request in sync context."""
172
180
  user = getattr(request, "user", None)
181
+ return self._resolve_user_details(user)
173
182
 
174
- if user and getattr(user, "is_authenticated", False):
175
- try:
176
- user_id = str(user.pk)
177
- except Exception:
178
- pass
183
+ async def aextract_tags(self, request):
184
+ # type: (HttpRequest) -> Dict[str, Any]
185
+ """
186
+ Async version of extract_tags for use in async request handling.
187
+
188
+ Uses await request.auser() instead of request.user to avoid
189
+ SynchronousOnlyOperation in async context.
190
+
191
+ Follows Django's naming convention for async methods (auser, asave, etc.).
192
+ """
193
+ user_id, user_email = await self.aextract_request_user(request)
194
+ return self._build_tags(request, user_id, user_email)
195
+
196
+ async def aextract_request_user(self, request):
197
+ # type: (HttpRequest) -> tuple[Optional[str], Optional[str]]
198
+ """
199
+ Async version of extract_request_user for use in async request handling.
179
200
 
201
+ Uses await request.auser() instead of request.user to avoid
202
+ SynchronousOnlyOperation in async context.
203
+
204
+ Follows Django's naming convention for async methods (auser, asave, etc.).
205
+ """
206
+ auser = getattr(request, "auser", None)
207
+ if callable(auser):
180
208
  try:
181
- email = str(user.email)
209
+ user = await auser()
210
+ return self._resolve_user_details(user)
182
211
  except Exception:
183
- pass
212
+ # If auser() fails, return empty - don't break the request
213
+ # Real errors (permissions, broken auth) will be logged by Django
214
+ return None, None
215
+
216
+ # Fallback for test requests without auser
217
+ return None, None
218
+
219
+ def _resolve_user_details(self, user):
220
+ # type: (Any) -> tuple[Optional[str], Optional[str]]
221
+ """
222
+ Extract user ID and email from a user object.
223
+
224
+ Handles both authenticated and unauthenticated users, as well as
225
+ legacy Django where is_authenticated was a method.
226
+ """
227
+ user_id = None
228
+ email = None
229
+
230
+ if user is None:
231
+ return user_id, email
232
+
233
+ # Handle is_authenticated (property in modern Django, method in legacy)
234
+ is_authenticated = getattr(user, "is_authenticated", False)
235
+ if callable(is_authenticated):
236
+ is_authenticated = is_authenticated()
237
+
238
+ if not is_authenticated:
239
+ return user_id, email
240
+
241
+ # Extract user primary key
242
+ user_pk = getattr(user, "pk", None)
243
+ if user_pk is not None:
244
+ user_id = str(user_pk)
245
+
246
+ # Extract user email
247
+ user_email = getattr(user, "email", None)
248
+ if user_email:
249
+ email = str(user_email)
184
250
 
185
251
  return user_id, email
186
252
 
@@ -211,12 +277,14 @@ class PosthogContextMiddleware:
211
277
  Asynchronous entry point for async request handling.
212
278
 
213
279
  This method is called when the middleware chain is async.
280
+ Uses aextract_tags() which calls request.auser() to avoid
281
+ SynchronousOnlyOperation when accessing user in async context.
214
282
  """
215
283
  if self.request_filter and not self.request_filter(request):
216
284
  return await self.get_response(request)
217
285
 
218
286
  with contexts.new_context(self.capture_exceptions, client=self.client):
219
- for k, v in self.extract_tags(request).items():
287
+ for k, v in (await self.aextract_tags(request)).items():
220
288
  contexts.tag(k, v)
221
289
 
222
290
  return await self.get_response(request)
@@ -1,4 +1,4 @@
1
- VERSION = "6.7.13"
1
+ VERSION = "6.7.14"
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.7.13
3
+ Version: 6.7.14
4
4
  Summary: Integrate PostHog into any python application.
5
5
  Home-page: https://github.com/posthog/posthog-python
6
6
  Author: Posthog
@@ -109,3 +109,9 @@ attr = "posthoganalytics.version.VERSION"
109
109
  [tool.pytest.ini_options]
110
110
  asyncio_mode = "auto"
111
111
  asyncio_default_fixture_loop_scope = "function"
112
+ testpaths = [
113
+ "posthog/test",
114
+ ]
115
+ norecursedirs = [
116
+ "integration_tests",
117
+ ]