posthoganalytics 5.3.0__py3-none-any.whl → 6.0.0__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,17 +1,15 @@
1
1
  import datetime # noqa: F401
2
- import warnings
3
- from typing import Callable, Dict, List, Optional, Tuple # noqa: F401
2
+ from typing import Callable, Dict, Optional, Any # noqa: F401
3
+ from typing_extensions import Unpack
4
4
 
5
+ from posthoganalytics.args import OptionalCaptureArgs, OptionalSetArgs, ExceptionArg
5
6
  from posthoganalytics.client import Client
6
- from posthoganalytics.exception_capture import Integrations # noqa: F401
7
- from posthoganalytics.scopes import (
8
- clear_tags,
9
- get_tags,
10
- new_context,
11
- scoped,
12
- tag,
13
- set_context_session,
14
- identify_context,
7
+ from posthoganalytics.contexts import (
8
+ new_context as inner_new_context,
9
+ scoped as inner_scoped,
10
+ tag as inner_tag,
11
+ set_context_session as inner_set_context_session,
12
+ identify_context as inner_identify_context,
15
13
  )
16
14
  from posthoganalytics.types import FeatureFlag, FlagsAndPayloads
17
15
  from posthoganalytics.version import VERSION
@@ -19,13 +17,26 @@ from posthoganalytics.version import VERSION
19
17
  __version__ = VERSION
20
18
 
21
19
  """Context management."""
22
- new_context = new_context
23
- tag = tag
24
- get_tags = get_tags
25
- clear_tags = clear_tags
26
- scoped = scoped
27
- identify_context = identify_context
28
- set_context_session = set_context_session
20
+
21
+
22
+ def new_context(fresh=False, capture_exceptions=True):
23
+ return inner_new_context(fresh=fresh, capture_exceptions=capture_exceptions)
24
+
25
+
26
+ def scoped(fresh=False, capture_exceptions=True):
27
+ return inner_scoped(fresh=fresh, capture_exceptions=capture_exceptions)
28
+
29
+
30
+ def set_context_session(session_id: str):
31
+ return inner_set_context_session(session_id)
32
+
33
+
34
+ def identify_context(distinct_id: str):
35
+ return inner_identify_context(distinct_id)
36
+
37
+
38
+ def tag(name: str, value: Any):
39
+ return inner_tag(name, value)
29
40
 
30
41
 
31
42
  """Settings."""
@@ -44,7 +55,6 @@ feature_flags_request_timeout_seconds = 3 # type: int
44
55
  super_properties = None # type: Optional[Dict]
45
56
  # Currently alpha, use at your own risk
46
57
  enable_exception_autocapture = False # type: bool
47
- exception_autocapture_integrations = [] # type: List[Integrations]
48
58
  log_captured_exceptions = False # type: bool
49
59
  # Used to determine in app paths for exception autocapture. Defaults to the current working directory
50
60
  project_root = None # type: Optional[str]
@@ -54,206 +64,100 @@ privacy_mode = False # type: bool
54
64
  default_client = None # type: Optional[Client]
55
65
 
56
66
 
57
- def capture(
58
- distinct_id, # type: str
59
- event, # type: str
60
- properties=None, # type: Optional[Dict]
61
- context=None, # type: Optional[Dict]
62
- timestamp=None, # type: Optional[datetime.datetime]
63
- uuid=None, # type: Optional[str]
64
- groups=None, # type: Optional[Dict]
65
- send_feature_flags=False,
66
- disable_geoip=None, # type: Optional[bool]
67
- ):
68
- # type: (...) -> Tuple[bool, dict]
67
+ # NOTE - this and following functions take unpacked kwargs because we needed to make
68
+ # it impossible to write `posthog.capture(distinct-id, event-name)` - basically, to enforce
69
+ # the breaking change made between 5.3.0 and 6.0.0. This decision can be unrolled in later
70
+ # versions, without a breaking change, to get back the type information in function signatures
71
+ def capture(event: str, **kwargs: Unpack[OptionalCaptureArgs]) -> Optional[str]:
69
72
  """
70
73
  Capture allows you to capture anything a user does within your system, which you can later use in PostHog to find patterns in usage, work out which features to improve or where people are giving up.
71
74
 
72
75
  A `capture` call requires
73
- - `distinct id` which uniquely identifies your user
74
76
  - `event name` to specify the event
75
77
  - We recommend using [verb] [noun], like `movie played` or `movie updated` to easily identify what your events mean later on.
76
78
 
77
- Optionally you can submit
78
- - `properties`, which can be a dict with any information you'd like to add
79
- - `groups`, which is a dict of group type -> group key mappings
79
+ Capture takes a number of optional arguments, which are defined by the `OptionalCaptureArgs` type.
80
80
 
81
81
  For example:
82
82
  ```python
83
- posthog.capture('distinct id', 'opened app')
84
- posthog.capture('distinct id', 'movie played', {'movie_id': '123', 'category': 'romcom'})
83
+ # Enter a new context (e.g. a request/response cycle, an instance of a background job, etc)
84
+ with posthog.new_context():
85
+ # Associate this context with some user, by distinct_id
86
+ posthog.identify_context('some user')
85
87
 
86
- posthog.capture('distinct id', 'purchase', groups={'company': 'id:5'})
87
- ```
88
- """
88
+ # Capture an event, associated with the context-level distinct ID ('some user')
89
+ posthog.capture('movie started')
89
90
 
90
- if context is not None:
91
- warnings.warn(
92
- "The 'context' parameter is deprecated and will be removed in a future version.",
93
- DeprecationWarning,
94
- stacklevel=2,
95
- )
91
+ # Capture an event associated with some other user (overriding the context-level distinct ID)
92
+ posthog.capture('movie joined', distinct_id='some-other-user')
96
93
 
97
- return _proxy(
98
- "capture",
99
- distinct_id=distinct_id,
100
- event=event,
101
- properties=properties,
102
- context=context,
103
- timestamp=timestamp,
104
- uuid=uuid,
105
- groups=groups,
106
- send_feature_flags=send_feature_flags,
107
- disable_geoip=disable_geoip,
108
- )
94
+ # Capture an event with some properties
95
+ posthog.capture('movie played', properties={'movie_id': '123', 'category': 'romcom'})
109
96
 
97
+ # Capture an event with some properties
98
+ posthog.capture('purchase', properties={'product_id': '123', 'category': 'romcom'})
99
+ # Capture an event with some associated group
100
+ posthog.capture('purchase', groups={'company': 'id:5'})
110
101
 
111
- def identify(
112
- distinct_id, # type: str
113
- properties=None, # type: Optional[Dict]
114
- context=None, # type: Optional[Dict]
115
- timestamp=None, # type: Optional[datetime.datetime]
116
- uuid=None, # type: Optional[str]
117
- disable_geoip=None, # type: Optional[bool]
118
- ):
119
- # type: (...) -> Tuple[bool, dict]
120
- """
121
- Identify lets you add metadata on your users so you can more easily identify who they are in PostHog, and even do things like segment users by these properties.
122
-
123
- An `identify` call requires
124
- - `distinct id` which uniquely identifies your user
125
- - `properties` with a dict with any key: value pairs
102
+ # Adding a tag to the current context will cause it to appear on all subsequent events
103
+ posthog.tag_context('some-tag', 'some-value')
126
104
 
127
- For example:
128
- ```python
129
- posthog.identify('distinct id', {
130
- 'email': 'dwayne@gmail.com',
131
- 'name': 'Dwayne Johnson'
132
- })
105
+ posthog.capture('another-event') # Will be captured with `'some-tag': 'some-value'` in the properties dict
133
106
  ```
134
107
  """
135
108
 
136
- if context is not None:
137
- warnings.warn(
138
- "The 'context' parameter is deprecated and will be removed in a future version.",
139
- DeprecationWarning,
140
- stacklevel=2,
141
- )
142
-
143
- return _proxy(
144
- "identify",
145
- distinct_id=distinct_id,
146
- properties=properties,
147
- context=context,
148
- timestamp=timestamp,
149
- uuid=uuid,
150
- disable_geoip=disable_geoip,
151
- )
109
+ return _proxy("capture", event, **kwargs)
152
110
 
153
111
 
154
- def set(
155
- distinct_id, # type: str
156
- properties=None, # type: Optional[Dict]
157
- context=None, # type: Optional[Dict]
158
- timestamp=None, # type: Optional[datetime.datetime]
159
- uuid=None, # type: Optional[str]
160
- disable_geoip=None, # type: Optional[bool]
161
- ):
162
- # type: (...) -> Tuple[bool, dict]
112
+ def set(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str]:
163
113
  """
164
114
  Set properties on a user record.
165
- This will overwrite previous people property values, just like `identify`.
115
+ This will overwrite previous people property values. Generally operates similar to `capture`, with
116
+ distinct_id being an optional argument, defaulting to the current context's distinct ID.
166
117
 
167
- A `set` call requires
168
- - `distinct id` which uniquely identifies your user
169
- - `properties` with a dict with any key: value pairs
118
+ If there is no context-level distinct ID, and no override distinct_id is passed, this function
119
+ will do nothing.
120
+
121
+ Context tags are folded into $set properties, so tagging the current context and then calling `set` will
122
+ cause those tags to be set on the user (unlike capture, which causes them to just be set on the event).
170
123
 
171
124
  For example:
172
125
  ```python
173
- posthog.set('distinct id', {
126
+ posthog.set(distinct_id='distinct id', properties={
174
127
  'current_browser': 'Chrome',
175
128
  })
176
129
  ```
177
130
  """
178
131
 
179
- if context is not None:
180
- warnings.warn(
181
- "The 'context' parameter is deprecated and will be removed in a future version.",
182
- DeprecationWarning,
183
- stacklevel=2,
184
- )
185
-
186
- return _proxy(
187
- "set",
188
- distinct_id=distinct_id,
189
- properties=properties,
190
- context=context,
191
- timestamp=timestamp,
192
- uuid=uuid,
193
- disable_geoip=disable_geoip,
194
- )
132
+ return _proxy("set", **kwargs)
195
133
 
196
134
 
197
- def set_once(
198
- distinct_id, # type: str
199
- properties=None, # type: Optional[Dict]
200
- context=None, # type: Optional[Dict]
201
- timestamp=None, # type: Optional[datetime.datetime]
202
- uuid=None, # type: Optional[str]
203
- disable_geoip=None, # type: Optional[bool]
204
- ):
205
- # type: (...) -> Tuple[bool, dict]
135
+ def set_once(**kwargs: Unpack[OptionalSetArgs]) -> Optional[str]:
206
136
  """
207
137
  Set properties on a user record, only if they do not yet exist.
208
- This will not overwrite previous people property values, unlike `identify`.
209
-
210
- A `set_once` call requires
211
- - `distinct id` which uniquely identifies your user
212
- - `properties` with a dict with any key: value pairs
138
+ This will not overwrite previous people property values, unlike `set`.
213
139
 
214
- For example:
215
- ```python
216
- posthog.set_once('distinct id', {
217
- 'referred_by': 'friend',
218
- })
140
+ Otherwise, operates in an identical manner to `set`.
219
141
  ```
220
142
  """
221
-
222
- if context is not None:
223
- warnings.warn(
224
- "The 'context' parameter is deprecated and will be removed in a future version.",
225
- DeprecationWarning,
226
- stacklevel=2,
227
- )
228
-
229
- return _proxy(
230
- "set_once",
231
- distinct_id=distinct_id,
232
- properties=properties,
233
- context=context,
234
- timestamp=timestamp,
235
- uuid=uuid,
236
- disable_geoip=disable_geoip,
237
- )
143
+ return _proxy("set_once", **kwargs)
238
144
 
239
145
 
240
146
  def group_identify(
241
147
  group_type, # type: str
242
148
  group_key, # type: str
243
149
  properties=None, # type: Optional[Dict]
244
- context=None, # type: Optional[Dict]
245
150
  timestamp=None, # type: Optional[datetime.datetime]
246
151
  uuid=None, # type: Optional[str]
247
152
  disable_geoip=None, # type: Optional[bool]
248
153
  ):
249
- # type: (...) -> Tuple[bool, dict]
154
+ # type: (...) -> Optional[str]
250
155
  """
251
156
  Set properties on a group
252
157
 
253
158
  A `group_identify` call requires
254
159
  - `group_type` type of your group
255
160
  - `group_key` unique identifier of the group
256
- - `properties` with a dict with any key: value pairs
257
161
 
258
162
  For example:
259
163
  ```python
@@ -263,19 +167,11 @@ def group_identify(
263
167
  ```
264
168
  """
265
169
 
266
- if context is not None:
267
- warnings.warn(
268
- "The 'context' parameter is deprecated and will be removed in a future version.",
269
- DeprecationWarning,
270
- stacklevel=2,
271
- )
272
-
273
170
  return _proxy(
274
171
  "group_identify",
275
172
  group_type=group_type,
276
173
  group_key=group_key,
277
174
  properties=properties,
278
- context=context,
279
175
  timestamp=timestamp,
280
176
  uuid=uuid,
281
177
  disable_geoip=disable_geoip,
@@ -285,18 +181,16 @@ def group_identify(
285
181
  def alias(
286
182
  previous_id, # type: str
287
183
  distinct_id, # type: str
288
- context=None, # type: Optional[Dict]
289
184
  timestamp=None, # type: Optional[datetime.datetime]
290
185
  uuid=None, # type: Optional[str]
291
186
  disable_geoip=None, # type: Optional[bool]
292
187
  ):
293
- # type: (...) -> Tuple[bool, dict]
188
+ # type: (...) -> Optional[str]
294
189
  """
295
- To marry up whatever a user does before they sign up or log in with what they do after you need to make an alias call. This will allow you to answer questions like "Which marketing channels leads to users churning after a month?" or "What do users do on our website before signing up?"
296
-
297
- In a purely back-end implementation, this means whenever an anonymous user does something, you'll want to send a session ID ([Django](https://stackoverflow.com/questions/526179/in-django-how-can-i-find-out-the-request-session-sessionid-and-use-it-as-a-vari), [Flask](https://stackoverflow.com/questions/15156132/flask-login-how-to-get-session-id)) with the capture call. Then, when that users signs up, you want to do an alias call with the session ID and the newly created user ID.
298
-
299
- The same concept applies for when a user logs in.
190
+ To marry up whatever a user does before they sign up or log in with what they do after you need to make an alias call.
191
+ This will allow you to answer questions like "Which marketing channels leads to users churning after a month?" or
192
+ "What do users do on our website before signing up?". Particularly useful for associating user behaviour before and after
193
+ they e.g. register, login, or perform some other identifying action.
300
194
 
301
195
  An `alias` call requires
302
196
  - `previous distinct id` the unique ID of the user before
@@ -308,18 +202,10 @@ def alias(
308
202
  ```
309
203
  """
310
204
 
311
- if context is not None:
312
- warnings.warn(
313
- "The 'context' parameter is deprecated and will be removed in a future version.",
314
- DeprecationWarning,
315
- stacklevel=2,
316
- )
317
-
318
205
  return _proxy(
319
206
  "alias",
320
207
  previous_id=previous_id,
321
208
  distinct_id=distinct_id,
322
- context=context,
323
209
  timestamp=timestamp,
324
210
  uuid=uuid,
325
211
  disable_geoip=disable_geoip,
@@ -327,58 +213,33 @@ def alias(
327
213
 
328
214
 
329
215
  def capture_exception(
330
- exception=None, # type: Optional[BaseException]
331
- distinct_id=None, # type: Optional[str]
332
- properties=None, # type: Optional[Dict]
333
- context=None, # type: Optional[Dict]
334
- timestamp=None, # type: Optional[datetime.datetime]
335
- uuid=None, # type: Optional[str]
336
- groups=None, # type: Optional[Dict]
337
- **kwargs,
216
+ exception: Optional[ExceptionArg] = None,
217
+ **kwargs: Unpack[OptionalCaptureArgs],
338
218
  ):
339
- # type: (...) -> Tuple[bool, dict]
340
219
  """
341
- capture_exception allows you to capture exceptions that happen in your code. This is useful for debugging and understanding what errors your users are encountering.
342
- This function never raises an exception, even if it fails to send the event.
220
+ capture_exception allows you to capture exceptions that happen in your code.
343
221
 
344
- A `capture_exception` call does not require any fields, but we recommend sending:
345
- - `distinct id` which uniquely identifies your user for which this exception happens
222
+ Capture exception is idempotent - if it is called twice with the same exception instance, only a occurrence will be tracked in posthog.
223
+ This is because, generally, contexts will cause exceptions to be captured automatically. However, to ensure you track an exception,
224
+ if you catch and do not re-raise it, capturing it manually is recommended, unless you are certain it will have crossed a context
225
+ boundary (e.g. by existing a `with posthog.new_context():` block already)
226
+
227
+ A `capture_exception` call does not require any fields, but we recommend passing an exception of some kind:
346
228
  - `exception` to specify the exception to capture. If not provided, the current exception is captured via `sys.exc_info()`
347
229
 
348
- Optionally you can submit
349
- - `properties`, which can be a dict with any information you'd like to add
350
- - `groups`, which is a dict of group type -> group key mappings
351
- - remaining `kwargs` will be logged if `log_captured_exceptions` is enabled
230
+ If the passed exception was raised and caught, the captured stack trace will consist of every frame between where the exception was raised
231
+ and the point at which it is captured (the "traceback").
352
232
 
353
- For example:
354
- ```python
355
- try:
356
- 1 / 0
357
- except Exception as e:
358
- posthog.capture_exception(e, 'my specific distinct id')
359
- posthog.capture_exception(distinct_id='my specific distinct id')
233
+ If the passed exception was never raised, e.g. if you call `posthog.capture_exception(ValueError("Some Error"))`, the stack trace
234
+ captured will be the full stack trace at the moment the exception was captured.
360
235
 
361
- ```
362
- """
236
+ Note that heavy use of contexts will lead to truncated stack traces, as the exception will be captured by the context entered most recently,
237
+ which may not be the point you catch the exception for the final time in your code. It's recommended to use contexts sparingly, for this reason.
363
238
 
364
- if context is not None:
365
- warnings.warn(
366
- "The 'context' parameter is deprecated and will be removed in a future version.",
367
- DeprecationWarning,
368
- stacklevel=2,
369
- )
239
+ `capture_exception` takes the same set of optional arguments as `capture`.
240
+ """
370
241
 
371
- return _proxy(
372
- "capture_exception",
373
- exception=exception,
374
- distinct_id=distinct_id,
375
- properties=properties,
376
- context=context,
377
- timestamp=timestamp,
378
- uuid=uuid,
379
- groups=groups,
380
- **kwargs,
381
- )
242
+ return _proxy("capture_exception", exception=exception, **kwargs)
382
243
 
383
244
 
384
245
  def feature_enabled(
@@ -565,16 +426,6 @@ def load_feature_flags():
565
426
  return _proxy("load_feature_flags")
566
427
 
567
428
 
568
- def page(*args, **kwargs):
569
- """Send a page call."""
570
- _proxy("page", *args, **kwargs)
571
-
572
-
573
- def screen(*args, **kwargs):
574
- """Send a screen call."""
575
- _proxy("screen", *args, **kwargs)
576
-
577
-
578
429
  def flush():
579
430
  """Tell the client to flush."""
580
431
  _proxy("flush")
@@ -594,6 +445,8 @@ def shutdown():
594
445
  def setup():
595
446
  global default_client
596
447
  if not default_client:
448
+ if not api_key:
449
+ raise ValueError("API key is required")
597
450
  default_client = Client(
598
451
  api_key,
599
452
  host=host,
@@ -602,7 +455,6 @@ def setup():
602
455
  send=send,
603
456
  sync_mode=sync_mode,
604
457
  personal_api_key=personal_api_key,
605
- project_api_key=project_api_key,
606
458
  poll_interval=poll_interval,
607
459
  disabled=disabled,
608
460
  disable_geoip=disable_geoip,
@@ -613,7 +465,6 @@ def setup():
613
465
  # or deprecate this proxy option fully (it's already in the process of deprecation, no new clients should be using this method since like 5-6 months)
614
466
  enable_exception_autocapture=enable_exception_autocapture,
615
467
  log_captured_exceptions=log_captured_exceptions,
616
- exception_autocapture_integrations=exception_autocapture_integrations,
617
468
  )
618
469
 
619
470
  # always set incase user changes it
@@ -84,7 +84,7 @@ class CallbackHandler(BaseCallbackHandler):
84
84
  _client: Client
85
85
  """PostHog client instance."""
86
86
 
87
- _distinct_id: Optional[Union[str, int, float, UUID]]
87
+ _distinct_id: Optional[Union[str, int, UUID]]
88
88
  """Distinct ID of the user to associate the trace with."""
89
89
 
90
90
  _trace_id: Optional[Union[str, int, float, UUID]]
@@ -112,7 +112,7 @@ class CallbackHandler(BaseCallbackHandler):
112
112
  self,
113
113
  client: Optional[Client] = None,
114
114
  *,
115
- distinct_id: Optional[Union[str, int, float, UUID]] = None,
115
+ distinct_id: Optional[Union[str, int, UUID]] = None,
116
116
  trace_id: Optional[Union[str, int, float, UUID]] = None,
117
117
  properties: Optional[Dict[str, Any]] = None,
118
118
  privacy_mode: bool = False,
@@ -0,0 +1,68 @@
1
+ from typing import TypedDict, Optional, Any, Dict, Union, Tuple, Type
2
+ from types import TracebackType
3
+ from typing_extensions import NotRequired # For Python < 3.11 compatibility
4
+ from datetime import datetime
5
+ import numbers
6
+ from uuid import UUID
7
+
8
+ ID_TYPES = Union[numbers.Number, str, UUID, int]
9
+
10
+
11
+ class OptionalCaptureArgs(TypedDict):
12
+ """Optional arguments for the capture method.
13
+
14
+ Args:
15
+ distinct_id: Unique identifier for the person associated with this event. If not set, the context
16
+ distinct_id is used, if available, otherwise a UUID is generated, and the event is marked
17
+ as personless. Setting context-level distinct_id's is recommended.
18
+ properties: Dictionary of properties to track with the event
19
+ timestamp: When the event occurred (defaults to current time)
20
+ uuid: Unique identifier for this specific event. If not provided, one is generated. The event
21
+ UUID is returned, so you can correlate it with actions in your app (like showing users an
22
+ error ID if you capture an exception).
23
+ groups: Group identifiers to associate with this event (format: {group_type: group_key})
24
+ send_feature_flags: Whether to include currently active feature flags in the event properties.
25
+ Defaults to True
26
+ disable_geoip: Whether to disable GeoIP lookup for this event. Defaults to False.
27
+ """
28
+
29
+ distinct_id: NotRequired[Optional[ID_TYPES]]
30
+ properties: NotRequired[Optional[Dict[str, Any]]]
31
+ timestamp: NotRequired[Optional[Union[datetime, str]]]
32
+ uuid: NotRequired[Optional[str]]
33
+ groups: NotRequired[Optional[Dict[str, str]]]
34
+ send_feature_flags: NotRequired[
35
+ Optional[bool]
36
+ ] # Optional so we can tell if the user is intentionally overriding a client setting or not
37
+ disable_geoip: NotRequired[
38
+ Optional[bool]
39
+ ] # As above, optional so we can tell if the user is intentionally overriding a client setting or not
40
+
41
+
42
+ class OptionalSetArgs(TypedDict):
43
+ """Optional arguments for the set method.
44
+
45
+ Args:
46
+ distinct_id: Unique identifier for the user to set properties on. If not set, the context
47
+ distinct_id is used, if available, otherwise this function does nothing. Setting
48
+ context-level distinct_id's is recommended.
49
+ properties: Dictionary of properties to set on the person
50
+ timestamp: When the properties were set (defaults to current time)
51
+ uuid: Unique identifier for this operation. If not provided, one is generated. This
52
+ UUID is returned, so you can correlate it with actions in your app.
53
+ disable_geoip: Whether to disable GeoIP lookup for this operation. Defaults to False.
54
+ """
55
+
56
+ distinct_id: NotRequired[Optional[ID_TYPES]]
57
+ properties: NotRequired[Optional[Dict[str, Any]]]
58
+ timestamp: NotRequired[Optional[Union[datetime, str]]]
59
+ uuid: NotRequired[Optional[str]]
60
+ disable_geoip: NotRequired[Optional[bool]]
61
+
62
+
63
+ ExcInfo = Union[
64
+ Tuple[Type[BaseException], BaseException, Optional[TracebackType]],
65
+ Tuple[None, None, None],
66
+ ]
67
+
68
+ ExceptionArg = Union[BaseException, ExcInfo]