Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
70cc481
docs: link the Celery headers guide by its main URL in the 8.0 guide
eli-r-ph Nov 11, 2026
540bdb8
docs: describe Celery eager headers without an external link
eli-r-ph Nov 11, 2026
07d32c5
docs: sort the 8.0.0 migration guide by when you make each change
gewenyu99 Nov 12, 2026
9c61c79
docs: replace filler lead-ins in the 8.0.0 migration guide
gewenyu99 Nov 12, 2026
fac1c95
docs: keep the 8.0.0 guide's table cells to one value
gewenyu99 Nov 12, 2026
2eb31ff
Merge eli/v1-sync-main-20261111 into docs/migration-guide-structure
gewenyu99 Nov 12, 2026
b49870b
docs: title a single migration guide's page with its H1
gewenyu99 Nov 12, 2026
bc7bc9c
docs: move the posthog.com Python SDK pages into the reference
gewenyu99 Nov 12, 2026
7abca08
docs: link the JavaScript Web reference instead of its retired page
gewenyu99 Nov 12, 2026
c8843cd
Revert "docs: link the JavaScript Web reference instead of its retire…
gewenyu99 Nov 12, 2026
15faa60
Revert "docs: move the posthog.com Python SDK pages into the reference"
gewenyu99 Nov 12, 2026
caaf61e
docs: add the Python facts from the retired posthog.com page that no …
gewenyu99 Nov 12, 2026
2020146
docs: move the Python one-API facts from getting-started into docstrings
gewenyu99 Nov 12, 2026
629f54c
docs: preserve Python reference details
gewenyu99 Nov 12, 2026
4ce00a0
Merge origin/v1 into docs/migration-guide-structure
eli-r-ph Nov 12, 2026
af3bde7
docs: restore the self-hosted, 404 and duplicate-uuid notes in the so…
eli-r-ph Nov 12, 2026
f93e373
docs: describe the identify-style person processing default in the so…
eli-r-ph Nov 12, 2026
c40da12
docs: correct the span limit order and the record_exception loss note
eli-r-ph Nov 12, 2026
709f2a0
docs: regenerate the 8.x and migration references
eli-r-ph Nov 12, 2026
78a0994
docs: name the project setting for keeping identify-style events pers…
eli-r-ph Nov 12, 2026
564989f
docs: regenerate the migration reference
eli-r-ph Nov 12, 2026
17ee74c
docs: name the calls whose $process_person_profile property becomes t…
eli-r-ph Nov 13, 2026
cdd55a6
Merge branch 'eli/v1-capture-dropped-event' into docs/migration-guide…
eli-r-ph Nov 13, 2026
804dc4f
docs: name the CaptureDroppedEvent entries of CaptureError.drops in t…
eli-r-ph Nov 13, 2026
6885b26
docs: restore upgrade notes that the retired posthog.com page carried
eli-r-ph Nov 13, 2026
3c7d3ed
docs: name the SDK in versions, cut self-hosted notes, and split dash…
gewenyu99 Nov 13, 2026
756fe08
docs: drop self-hosted lines from the migration guide
gewenyu99 Nov 13, 2026
5432eaf
docs: drop the "(experimental)" title from the next-major reference page
gewenyu99 Nov 13, 2026
3002d0e
docs: say to pass process_person_profile in options on set and group_…
gewenyu99 Nov 13, 2026
5b249fb
docs: cut the 404 hint and self-hosted lines from the migration guide
gewenyu99 Nov 13, 2026
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/ai-integrations-require-capture-ai.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
pypi/posthog: major
---

The AI integrations no longer fall back to `capture` for a client object without `capture_ai`. Creating an OpenAI, Anthropic or Gemini wrapper, the LangChain `CallbackHandler`, or the OpenAI Agents or Claude Agent SDK processor with such a client now logs one error and captures no AI events; provider calls still work. The error shows up when you set the integration up, and not after a provider call. A `Client` or `AsyncPosthog` instance, and the default global client, work as before.
The AI integrations no longer fall back to `capture` for a client object without `capture_ai`. Creating an OpenAI, Anthropic or Gemini wrapper, the LangChain `CallbackHandler`, or the OpenAI Agents or Claude Agent SDK processor with such a client now logs one error and captures no AI events. Provider calls still work. The error shows up when you set the integration up, and not after a provider call. A `Client` or `AsyncPosthog` instance, and the default global client, work as before.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pypi/posthog: patch
---

Capture makes one connection try per attempt, so every physical request carries its own attempt number and request timestamp. 7.x also retried a failed connection up to twice inside each attempt, reusing the request timestamp, which shifted the events' stored time by the time spent connecting.
Capture makes one connection try per attempt, so every physical request carries its own attempt number and request timestamp. posthog-python 7.x.x also retried a failed connection up to twice inside each attempt, reusing the request timestamp, which shifted the events' stored time by the time spent connecting.

When `before_send` raises, the error line names the event and the exception type, not the exception message. The traceback is logged at debug level.

Expand Down
2 changes: 1 addition & 1 deletion .sampo/changesets/capture-same-origin-redirects.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
pypi/posthog: major
---

Capture follows a `307` or `308` redirect only to the origin of `host`, at most 5 times, for the sync and async clients. Any other redirect fails the batch and reaches `on_error`. In 7.x the sync client followed redirects to any origin and resent the event batch there.
Capture follows a `307` or `308` redirect only to the origin of `host`, at most 5 times, for the sync and async clients. Any other redirect fails the batch and reaches `on_error`. In posthog-python 7.x.x the sync client followed redirects to any origin and resent the event batch there.
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
pypi/posthog: major
---

`capture_exception` takes a `level=` argument on `Client`, `AsyncPosthog` and the module, which sets `$exception_level` (default `"error"`). Reserved exception properties passed in `properties`, such as `$exception_list` and `$exception_level`, are now ignored. In 7.x they overrode the SDK's values with a `DeprecationWarning`. `AsyncPosthog.capture_exception` now sends `$exception_level`.
`capture_exception` takes a `level=` argument on `Client`, `AsyncPosthog` and the module, which sets `$exception_level` (default `"error"`). Reserved exception properties passed in `properties`, such as `$exception_list` and `$exception_level`, are now ignored. In posthog-python 7.x.x they overrode the SDK's values with a `DeprecationWarning`. `AsyncPosthog.capture_exception` now sends `$exception_level`.
2 changes: 1 addition & 1 deletion .sampo/changesets/identity-and-context-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
pypi/posthog: major
---

`group_identify` falls back to the context distinct ID and then to `$<group_type>_<group_key>` instead of a random ID, and no longer adds the context session ID. `alias()` no longer takes its target from the context distinct ID. Context tags and options now apply to `alias` and `group_identify`, but context tags, context options, `super_options` and `super_properties` do not set `process_person_profile` on identify-style events (`$identify`, `$create_alias`, `$merge_dangerously`, `$groupidentify`, `$set` and `$set_once`) whose distinct ID the SDK did not generate; those default to `True` unless the call sets it. An empty or whitespace-only `distinct_id` counts as missing, and generated personless distinct IDs are UUIDv7. `AsyncPosthog` follows the same rules.
`group_identify` falls back to the context distinct ID and then to `$<group_type>_<group_key>` instead of a random ID, and no longer adds the context session ID. `alias()` no longer takes its target from the context distinct ID. Context tags and options now apply to `alias` and `group_identify`, but context tags, context options, `super_options` and `super_properties` do not set `process_person_profile` on identify-style events (`$identify`, `$create_alias`, `$merge_dangerously`, `$groupidentify`, `$set` and `$set_once`) whose distinct ID the SDK did not generate. Those events default to `True` unless the call sets it. An empty or whitespace-only `distinct_id` counts as missing, and generated personless distinct IDs are UUIDv7. `AsyncPosthog` follows the same rules.
2 changes: 1 addition & 1 deletion .sampo/changesets/lib-is-an-sdk-value.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
pypi/posthog: major
---

Events send `$lib` and `$lib_version` as they are at send time, whoever set them. If no `$lib` is set in the call's `properties`, a context tag or `super_properties`, the SDK sets both to `posthog-python` and the installed version. If `$lib` is `posthog-python` and `$lib_version` is missing, the SDK fills in the version. A `$lib` that names another library is sent with whatever `$lib_version` it has, so set both when you override them. Only a non-empty string counts as set. In 7.x the SDK overwrote a `$lib` or `$lib_version` set in a call or a context tag. `before_send` can still change or remove both, and `AsyncPosthog` follows the same rules.
Events send `$lib` and `$lib_version` as they are at send time, whoever set them. If no `$lib` is set in the call's `properties`, a context tag or `super_properties`, the SDK sets both to `posthog-python` and the installed version. If `$lib` is `posthog-python` and `$lib_version` is missing, the SDK fills in the version. A `$lib` that names another library is sent with whatever `$lib_version` it has, so set both when you override them. Only a non-empty string counts as set. In posthog-python 7.x.x the SDK overwrote a `$lib` or `$lib_version` set in a call or a context tag. `before_send` can still change or remove both, and `AsyncPosthog` follows the same rules.
2 changes: 1 addition & 1 deletion .sampo/changesets/mcp-keeps-sdk-identity.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
pypi/posthog: major
---

MCP instrumentation no longer relabels the whole client. In 7.x, `posthog.mcp.instrument()` and `PostHogMCP` changed `$lib` on every event the client sent, including the app's own, and the SDK name in its request headers, feature flag requests included. `instrument()` without a client relabeled the global client. Now only MCP analytics events report `posthog-python-mcp`, and everything else reports `posthog-python`. The label is set in each MCP event's own properties, so a `$lib` in `super_properties` no longer replaces it; `before_send` can.
MCP instrumentation no longer relabels the whole client. In posthog-python 7.x.x, `posthog.mcp.instrument()` and `PostHogMCP` changed `$lib` on every event the client sent, including the app's own, and the SDK name in its request headers, feature flag requests included. `instrument()` without a client relabeled the global client. Now only MCP analytics events report `posthog-python-mcp`, and everything else reports `posthog-python`. The label is set in each MCP event's own properties, so a `$lib` in `super_properties` no longer replaces it. `before_send` can.
2 changes: 1 addition & 1 deletion .sampo/changesets/openfeature-provider-posthog-8.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
pypi/openfeature-provider-posthog: minor
---

Supports posthog 8.x as well as 7.x.
Supports posthog-python 8.x.x as well as 7.x.x.
2 changes: 1 addition & 1 deletion .sampo/changesets/request-keyword-only.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
pypi/posthog: major
---

The parameters after `path` in `posthog.request.post` and after `host` in `posthog.request.flags` are keyword-only. With `gzip` removed, a 7.x call that passed them by position bound them to the wrong parameter. It now raises `TypeError`.
The parameters after `path` in `posthog.request.post` and after `host` in `posthog.request.flags` are keyword-only. With `gzip` removed, a posthog-python 7.x.x call that passed them by position bound them to the wrong parameter. It now raises `TypeError`.
2 changes: 1 addition & 1 deletion .sampo/changesets/session-id-drop-warning.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
pypi/posthog: major
---

A `$session_id` or `$window_id` that is not a string is not sent, because capture v1 would reject the whole batch, and the SDK logs a warning for each one. The warning names the key and the value's type, not the value. `None` counts as unset and drops without a warning. An empty string is sent. In 7.x these values were sent as properties.
A `$session_id` or `$window_id` that is not a string is not sent, because capture v1 would reject the whole batch, and the SDK logs a warning for each one. The warning names the key and the value's type, not the value. `None` counts as unset and drops without a warning. An empty string is sent. In posthog-python 7.x.x these values were sent as properties.
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,21 @@ SDK usage examples and code snippets live in the official documentation so they

| SDK Version | Python Versions Supported | Notes |
| ------------- | ---------------------------- | -------------------------- |
| 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 |
| 8.0.0+ | 3.10, 3.11, 3.12, 3.13, 3.14 | Capture v1 only |
| 7.3.1 - 7.x.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 |
| 4.0.1 - 6.x.x | 3.9, 3.10, 3.11, 3.12, 3.13 | Python 3.9+ required |

To upgrade from posthog-python 7.x.x to 8.0.0, see the [migration guide](docs/migration-7.x-to-8.0.md).

## 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)
- [Migrating from posthog-python 7.x.x to 8.0.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)
- [Request integration configuration](references/middleware_configuration.md) — shared middleware options and compatibility notes
- [OpenFeature provider docs](https://posthog.com/docs/feature-flags/installation/openfeature) — use PostHog flags through the [OpenFeature](https://openfeature.dev) Python SDK
- [Request integration configuration](references/middleware_configuration.md), with shared middleware options and compatibility notes
- [OpenFeature provider docs](https://posthog.com/docs/feature-flags/installation/openfeature), to use PostHog flags through the [OpenFeature](https://openfeature.dev) Python SDK

## Contributing

Expand Down
17 changes: 12 additions & 5 deletions bin/docs_scripts/generate_markdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,16 @@ def nest(markdown: str) -> str:
return "\n".join(out)


def migration_page(guides: list[str]) -> list[str]:
"""The migration page. One guide's H1 becomes the page title, so the page shows one title."""
title = "PostHog Python SDK migration guides"
if len(guides) == 1 and guides[0].startswith("# "):
heading, _, rest = guides[0].partition("\n")
title, guides = heading[2:].strip(), [rest.strip()]
head = f"---\ntitle: {json.dumps(title)}\nsidebar: Docs\nshowTitle: true\n---"
return [head, *guides]


def getting_started(major: int) -> str:
"""docs/getting-started.md for one major. Its install line pins that major, so a
reader never installs a major the page doesn't describe."""
Expand Down Expand Up @@ -255,20 +265,17 @@ def main() -> None:
started,
*reference(pkg, capabilities(opts.sdk_specs), declared >= major),
]
title = f"PostHog Python SDK {major}.x" + (
"" if declared >= major else " (experimental)"
)
title = f"PostHog Python SDK {major}.x"
write(f"posthog-python-{major}.md", [front_matter(title, body), *body])
if declared >= major:
write(
"posthog-python-reference.md",
[front_matter("PostHog Python SDK", body), *body],
)
if guides := sorted((ROOT / "docs").glob("migration-*.md"), reverse=True):
head = "---\ntitle: PostHog Python SDK migration guides\nsidebar: Docs\nshowTitle: true\n---"
write(
"posthog-python-migration.md",
[head, *(g.read_text().strip() for g in guides)],
migration_page([g.read_text().strip() for g in guides]),
)


Expand Down
23 changes: 23 additions & 0 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,26 @@ posthog.shutdown()
```

In an asyncio app, use `AsyncPosthog` as an async context manager instead. See [AsyncClient](#asyncclient).

## Use AsyncPosthog in FastAPI

Keep one `AsyncPosthog` client for the lifetime of your app. A FastAPI lifespan handler does this.

```python
from contextlib import asynccontextmanager

from fastapi import FastAPI
from posthog import AsyncPosthog

@asynccontextmanager
async def lifespan(app: FastAPI):
async with AsyncPosthog("<ph_project_token>", host="<ph_client_api_host>") as posthog:
app.state.posthog = posthog
yield

app = FastAPI(lifespan=lifespan)
```

## The posthoganalytics package

`posthoganalytics` is the same SDK under another module name. Use it when another module named `posthog` is on your import path.
Loading
Loading