Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .sampo/changesets/capture-v1-major.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
pypi/posthog: major
---

Capture v1 is the only capture path. Events and AI events send to the capture v1 endpoints, and the legacy v0 capture path is removed. See the migration guide for breaking changes.
Capture v1 is the only capture path. Events and AI events send to the capture v1 endpoints, and the legacy v0 capture path is removed. See the [migration guide](https://github.com/PostHog/posthog-python/blob/main/docs/migration-7.x-to-8.0.md) for breaking changes.
5 changes: 5 additions & 0 deletions .sampo/changesets/openfeature-provider-posthog-8.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
pypi/openfeature-provider-posthog: minor
---

Supports posthog 8.x as well as 7.x.
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Follow [Public API changes](./CONTRIBUTING.md#public-api-changes). As an agent,

Before changing capture configuration, serialization, routing, or retries, read the relevant implementation and tests.

Capture v1 is the only capture protocol (`capture` posts to `/i/v1/analytics/events`, `capture_ai` to `/i/v1/ai/events`); strictly typed v1 options and `$set`/`$set_once` relocation; compression (gzip, zlib-wrapped deflate, optional zstd, default none), set per lane by `capture_compression` and `capture_ai_compression`; partial-only per-event retries with stable identity; accumulated drop reporting even on 2xx; terminal v1 `429`; `Retry-After` as a minimum bounded by the shared 30s ceiling; and inline blocking retries with `sync_mode=True`.
Capture v1 is the only capture protocol (`capture` posts to `/i/v1/analytics/events`, `capture_ai` to `/i/v1/ai/events`); per-event `options` sent as given, layered over `super_options` and context options, with legacy `$` option properties hoisted once after `before_send`; `$set`/`$set_once` relocation; compression (gzip, zlib-wrapped deflate, optional zstd, default none), set per lane by `capture_compression` and `capture_ai_compression`; partial-only per-event retries with stable identity; accumulated drop reporting even on 2xx; terminal v1 `429`; `Retry-After` as a minimum bounded by the shared 30s ceiling; and inline blocking retries with `sync_mode=True`.

## Mirror and build safety

Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ SDK usage examples and code snippets live in the official documentation so they

| SDK Version | Python Versions Supported | Notes |
| ------------- | ---------------------------- | -------------------------- |
| 7.3.1+ | 3.10, 3.11, 3.12, 3.13, 3.14 | Added Python 3.14 support |
| 8.0.0+ | 3.10, 3.11, 3.12, 3.13, 3.14 | Capture v1 only, see the [migration guide](docs/migration-7.x-to-8.0.md) |
| 7.3.1 - 7.x | 3.10, 3.11, 3.12, 3.13, 3.14 | Added Python 3.14 support |
| 7.0.0 - 7.0.1 | 3.10, 3.11, 3.12, 3.13 | Dropped Python 3.9 support |
| 4.0.1 - 6.x | 3.9, 3.10, 3.11, 3.12, 3.13 | Python 3.9+ required |

## Documentation

- [Python library docs](https://posthog.com/docs/libraries/python)
- [Migrating from 7.x to 8.0](docs/migration-7.x-to-8.0.md)
- [Django framework docs](https://posthog.com/docs/libraries/django)
- [Flask framework docs](https://posthog.com/docs/libraries/flask)
- [OpenFeature provider docs](https://posthog.com/docs/feature-flags/installation/openfeature) — use PostHog flags through the [OpenFeature](https://openfeature.dev) Python SDK
Expand Down
136 changes: 136 additions & 0 deletions docs/migration-7.x-to-8.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# Migrating from posthog 7.x to 8.0

posthog 8.0 sends every event through capture v1 and removes the legacy capture path.
Most apps upgrade without code changes.
Read the checklist first, then the sections that apply to you.

## Checklist

You need to change code if your app does any of these:

- passes `Client` or `AsyncPosthog` constructor arguments by position after `host`
- sets `capture_mode`, `POSTHOG_CAPTURE_MODE` or `gzip`
- imports `CaptureV1Error`, `posthog.capture_v1`, `request.batch_post`, `EVENTS_ENDPOINT` or `AI_EVENTS_ENDPOINT`
- sends events to a self-hosted PostHog that does not serve the capture v1 endpoints
- reuses one event `uuid` for more than one event
- sets `$process_person_profile` to turn person processing on for events without a distinct ID
- passes strings such as `"true"` for `$cookieless_mode`, `$ignore_sent_at` or `$process_person_profile`
- expects `super_properties` to override properties passed to a single call
- tests the AI integrations with a mock client and asserts on `capture`
- passes its own client object to the AI integrations

## Endpoints

| Method | Endpoint |
| --- | --- |
| `capture`, `set`, `set_once`, `alias`, `group_identify`, `capture_exception` | `/i/v1/analytics/events` |
| `capture_ai`, `capture_ai_immediate`, and every built-in AI integration | `/i/v1/ai/events` |

If you send events to a self-hosted PostHog, check that it serves both endpoints before you upgrade.
An endpoint that is not served drops every event sent to it.

## Removed options and APIs

| Removed | Use instead |
| --- | --- |
| `capture_mode`, `CaptureMode`, `POSTHOG_CAPTURE_MODE` | Nothing. Capture v1 is the only path. |
| `gzip=True` | `capture_compression=CaptureCompression.GZIP`. The default is no compression. `POSTHOG_CAPTURE_COMPRESSION` still works. |
| `CaptureV1Error` | `CaptureError`. There is no alias. |
| `posthog.capture_v1` | `posthog.capture_event` and `posthog.capture_send` |
| `request.batch_post`, `async_batch_post`, `EVENTS_ENDPOINT`, `AI_EVENTS_ENDPOINT` | Nothing. Send events through a client. |
| The `gzip` parameter of `request.post` and `request.flags` | Nothing. |
| The `backoff` dependency | Add it to your own requirements if your code imports it. |

Constructor arguments after `host` are keyword-only:

```python
# 7.x
Posthog("<api_key>", "https://us.i.posthog.com", True)

# 8.0
Posthog("<api_key>", host="https://us.i.posthog.com", debug=True)
```

## Errors and `on_error`

Capture v1 returns a result for every event, even when the request succeeds.
An event can be dropped, for example by billing limits or quotas, inside a 2xx response.
8.0 reports those events as failures.

- Every capture failure is a `CaptureError`. It has these fields:
- `status`: the HTTP status, or `0` when the request never got a response
- `endpoint`, `request_id` and `attempts`
- `drops` and `retry_exhausted`: the uuids that were dropped or ran out of retries
- `event_results`: a `CaptureEventResult(result, details)` for each uuid, from `posthog.capture_send`
- `verdict_summary()`: counts such as `drop/billing=1, retry/not_persisted=1`
- `on_error(error, batch)` receives every failure, including in `sync_mode` and from `AsyncPosthog.capture_immediate`.
- Without `on_error`, the SDK logs one line per failed batch, for example `2 event(s) not persisted by /i/v1/analytics/events: ...`. The line never contains event content or the server's response text.
- In `sync_mode`, a failed `capture()` calls `on_error` and returns `None`.
- If a capture inside `on_error` fails, the SDK logs the failure and does not call `on_error` again.
- A `429` response is not retried.
- Retries start at 100 ms and double each attempt, up to 30 seconds. The `Consumer` default is 3 retries, down from 10.

## Event uuids

- Each event needs its own uuid. Capture rejects a whole batch that contains the same uuid twice, so the other events in that batch are lost too.
- The SDK accepts a uuid with hyphens, 32 hex digits, `{...}` braces or a `urn:uuid:` prefix, in any case. It sends the lowercase hyphenated form and returns that form from `capture`.
- Any other value is replaced with a generated uuid, and the SDK logs an error. This applies to `AsyncPosthog` too.
- Generated uuids are UUIDv7.

## Event options

Capture v1 sends processing options in an `options` object, next to `properties`.

- `capture`, `capture_ai`, `set`, `set_once`, `alias`, `group_identify` and `capture_exception` take an `options` argument:

```python
posthog.capture("signed_up", distinct_id="user-1", options={"cookieless_mode": True})
```

- `super_options` sets options on every event, like `super_properties`.
- `set_context_option(key, value)` sets an option for the current context, like `tag()`.
- Options are sent as given. PostHog validates them.
- The legacy properties `$cookieless_mode`, `$ignore_sent_at`, `$product_tour_id` and `$process_person_profile` still work. They move into options after `before_send`. Their values are no longer converted, so pass `True` or `False`, not `"true"`.
- An option set at any layer wins over its legacy property set at any layer. For example, `super_options={"cookieless_mode": True}` wins over an event's `$cookieless_mode: False`. When you move a default to options, move the per-event overrides of that key to options too.
- A `None` option counts as unset.

Values apply in this order, and each layer overrides the ones before it:

1. options the SDK sets, such as turning off person processing for events without a distinct ID
2. `super_options` and `super_properties`
3. context options and tags
4. the `options` and `properties` of the call
5. `before_send`

Two changes follow from this order:

- Properties passed to a call now override `super_properties`. `super_properties` can no longer change `$lib`, `$lib_version` or `$geoip_disable`.
- An event without a distinct ID gets `options.process_person_profile = false`. A `$process_person_profile: true` property no longer turns person processing back on. Set the option instead.

## AI capture

- `capture_ai` and every built-in AI integration send to `/i/v1/ai/events`. The AI endpoint accepts events up to 8 MiB.
- `enable_full_ai_capture` now controls only content: string truncation and media redaction. It no longer chooses the endpoint. `_use_ai_lane` and `_enable_multimodal_capture` still work as aliases.
- The AI integrations call your client's `capture_ai`, and pass `options=`. A client object without `capture_ai` gets `capture` calls instead. A custom client must accept the `options` keyword argument.
- Tests that pass a `Mock` client to an AI integration must assert on `mock.capture_ai`, not `mock.capture`.
- When an AI integration or MCP falls back to a trace, run or session ID, it turns person processing off with a per-event option. A `$process_person_profile: true` property no longer turns it back on. `before_send` can still change it.
- MCP's `PostHogCaptureEvent` has an `options` key.
- New `Client` and `AsyncPosthog` arguments for the AI lane:
- `capture_ai_compression`: default none. `POSTHOG_CAPTURE_COMPRESSION` does not apply to it.
- `capture_ai_max_queue_size`: default 1000
- `capture_ai_timeout`: default 30 seconds
- `capture_ai_max_event_bytes`: default 8 MiB plus 64 KiB. You can only lower it.
- `AsyncPosthog` has `capture_ai` and `capture_ai_immediate`.
- `AsyncPosthog` accepts `privacy_mode` and `enable_full_ai_capture`, with the same meaning as on `Client`. An AI integration given an `AsyncPosthog` client used to always truncate and redact media.

## Batching and size limits

- An analytics event over 900 KiB is dropped and logged. This now applies in `sync_mode` too.
- An AI event over `capture_ai_max_event_bytes` is dropped and logged.
- A batch stops before an event that would take it past 5 MiB. A larger event is sent alone.
- `flush()` and `shutdown()` drain the analytics and AI queues at the same time.

## OpenFeature provider

`openfeature-provider-posthog` 0.2.0 works with posthog 7.x and 8.x.
Older provider versions require posthog below 8.0, so upgrade the provider before or with posthog.
2 changes: 1 addition & 1 deletion openfeature-provider/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ dependencies = [
# single-call API. Pinned to the posthog major this provider is released and
# tested against (both ship from this repo); bump the upper bound when moving
# the provider to a new posthog major.
"posthog>=7.0.0,<8.0.0",
"posthog>=7.0.0,<9.0.0",
"openfeature-sdk>=0.8.0",
]

Expand Down
Loading